Merge pull request #778 from paulfd/choke-tests
Various corrections regarding choke tests
This commit is contained in:
commit
9c3b31030f
5 changed files with 138 additions and 28 deletions
|
|
@ -101,7 +101,7 @@ float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const
|
|||
if (noteNumber < 0 || noteNumber >= 128)
|
||||
return 0.0f;
|
||||
|
||||
if (noteOnTimes[noteNumber] != 0 && noteOffTimes[noteNumber] != 0 && noteOnTimes[noteNumber] > noteOffTimes[noteNumber])
|
||||
if (!noteStates[noteNumber])
|
||||
return 0.0f;
|
||||
|
||||
const unsigned timeInSamples = internalClock + static_cast<unsigned>(delay) - noteOnTimes[noteNumber];
|
||||
|
|
|
|||
|
|
@ -1088,6 +1088,16 @@ void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& trigge
|
|||
ring.addVoiceToRing(selectedVoice);
|
||||
}
|
||||
|
||||
void Synth::Impl::checkOffGroups(const Region* region, int delay, int number)
|
||||
{
|
||||
for (auto& voice : voiceManager_) {
|
||||
if (voice.checkOffGroup(region, delay, number)) {
|
||||
const TriggerEvent& event = voice.getTriggerEvent();
|
||||
noteOffDispatch(delay, event.number, event.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noexcept
|
||||
{
|
||||
const auto randValue = randNoteDistribution_(Random::randomGenerator);
|
||||
|
|
@ -1106,6 +1116,7 @@ void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noe
|
|||
if (region.trigger == Trigger::release && !region.rtDead && !voiceManager_.playingAttackVoice(®ion))
|
||||
continue;
|
||||
|
||||
checkOffGroups(®ion, delay, noteNumber);
|
||||
startVoice(layer, delay, triggerEvent, ring);
|
||||
}
|
||||
}
|
||||
|
|
@ -1136,17 +1147,8 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex
|
|||
for (Layer* layer : noteActivationLists_[noteNumber]) {
|
||||
if (layer->registerNoteOn(noteNumber, velocity, randValue)) {
|
||||
const Region& region = layer->getRegion();
|
||||
for (auto& voice : voiceManager_) {
|
||||
if (voice.checkOffGroup(®ion, delay, noteNumber)) {
|
||||
const TriggerEvent& event = voice.getTriggerEvent();
|
||||
noteOffDispatch(delay, event.number, event.value);
|
||||
}
|
||||
}
|
||||
|
||||
checkOffGroups(®ion, delay, noteNumber);
|
||||
TriggerEvent triggerEvent { TriggerEventType::NoteOn, noteNumber, velocity };
|
||||
if (region.velocityOverride == VelocityOverride::previous)
|
||||
triggerEvent.value = resources_.midiState.getLastVelocity();
|
||||
|
||||
startVoice(layer, delay, triggerEvent, ring);
|
||||
}
|
||||
}
|
||||
|
|
@ -1199,7 +1201,7 @@ void Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept
|
|||
void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept
|
||||
{
|
||||
SisterVoiceRingBuilder ring;
|
||||
const TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value };
|
||||
TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value };
|
||||
for (Layer* layer : ccActivationLists_[ccNumber]) {
|
||||
const Region& region = layer->getRegion();
|
||||
|
||||
|
|
@ -1217,8 +1219,10 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept
|
|||
}
|
||||
}
|
||||
|
||||
if (layer->registerCC(ccNumber, value))
|
||||
if (layer->registerCC(ccNumber, value)) {
|
||||
checkOffGroups(®ion, delay, ccNumber);
|
||||
startVoice(layer, delay, triggerEvent, ring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,16 @@ struct Synth::Impl final: public Parser::Listener {
|
|||
*/
|
||||
void setDefaultHdcc(int ccNumber, float value);
|
||||
|
||||
/**
|
||||
* @brief Check if we have to kill any voice when starting a new one
|
||||
* on the specified region with the specified note/cc number
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
* @param number
|
||||
*/
|
||||
void checkOffGroups(const Region* region, int delay, int number);
|
||||
|
||||
int numGroups_ { 0 };
|
||||
int numMasters_ { 0 };
|
||||
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ struct Voice::Impl
|
|||
int initialDelay_ { 0 };
|
||||
int age_ { 0 };
|
||||
uint32_t count_ { 1 };
|
||||
int sampleEnd_ { 0 };
|
||||
int sampleSize_ { 0 };
|
||||
|
||||
struct {
|
||||
|
|
@ -401,6 +402,9 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
if (impl.triggerEvent_.type == TriggerEventType::CC)
|
||||
impl.triggerEvent_.number = region.pitchKeycenter;
|
||||
|
||||
if (region.velocityOverride == VelocityOverride::previous)
|
||||
impl.triggerEvent_.value = resources.midiState.getLastVelocity();
|
||||
|
||||
if (region.disabled()) {
|
||||
impl.switchState(State::cleanMeUp);
|
||||
return false;
|
||||
|
|
@ -469,8 +473,9 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
|
||||
impl.baseVolumedB_ = region.getBaseVolumedB(resources.midiState, impl.triggerEvent_.number);
|
||||
impl.baseGain_ = region.getBaseGain();
|
||||
if (impl.triggerEvent_.type != TriggerEventType::CC)
|
||||
if (impl.triggerEvent_.type != TriggerEventType::CC || region.velocityOverride == VelocityOverride::previous)
|
||||
impl.baseGain_ *= region.getNoteGain(impl.triggerEvent_.number, impl.triggerEvent_.value);
|
||||
|
||||
impl.gainSmoother_.reset();
|
||||
impl.resetCrossfades();
|
||||
|
||||
|
|
@ -485,7 +490,8 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
impl.triggerDelay_ = delay;
|
||||
impl.initialDelay_ = delay + static_cast<int>(region.getDelay(resources.midiState) * impl.sampleRate_);
|
||||
impl.baseFrequency_ = resources.tuning.getFrequencyOfKey(impl.triggerEvent_.number);
|
||||
impl.sampleSize_ = int(region.getSampleEnd(resources.midiState, resources.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1);
|
||||
impl.sampleEnd_ = int(region.getSampleEnd(resources.midiState, resources.filePool.getOversamplingFactor()));
|
||||
impl.sampleSize_ = impl.sampleEnd_- impl.sourcePosition_ - 1;
|
||||
impl.bendStepFactor_ = centsFactor(region.bendStep);
|
||||
impl.bendSmoother_.setSmoothing(region.bendSmooth, impl.sampleRate_);
|
||||
impl.bendSmoother_.reset(centsFactor(region.getBendInCents(resources.midiState.getPitchBend())));
|
||||
|
|
@ -608,32 +614,38 @@ void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
|
|||
if (impl.region_ == nullptr)
|
||||
return;
|
||||
|
||||
const Region& region = *impl.region_;
|
||||
|
||||
if (impl.state_ != State::playing)
|
||||
return;
|
||||
|
||||
if (impl.region_->checkSustain && (ccNumber == impl.region_->sostenutoCC)) {
|
||||
if (ccValue < impl.region_->sostenutoThreshold) {
|
||||
if (ccNumber != region.sustainCC && ccNumber != region.sostenutoCC)
|
||||
return;
|
||||
|
||||
if (region.checkSustain && (ccNumber == region.sostenutoCC)) {
|
||||
if (ccValue < region.sostenutoThreshold) {
|
||||
impl.sostenutoState_ = Impl::SostenutoState::Up;
|
||||
} else if (impl.sostenutoState_ == Impl::SostenutoState::Up) {
|
||||
impl.sostenutoState_ = Impl::SostenutoState::Sustaining;
|
||||
}
|
||||
}
|
||||
|
||||
if (impl.region_->checkSostenuto && (ccNumber == impl.region_->sustainCC)) {
|
||||
if (ccValue < impl.region_->sustainThreshold) {
|
||||
if (region.checkSostenuto && (ccNumber == region.sustainCC)) {
|
||||
if (ccValue < region.sustainThreshold) {
|
||||
impl.sustainState_ = Impl::SustainState::Up;
|
||||
} else {
|
||||
impl.sustainState_ = Impl::SustainState::Sustaining;
|
||||
}
|
||||
}
|
||||
|
||||
const bool sustainPedalReleaseCondition = !impl.region_->checkSustain
|
||||
const bool sustainPedalReleaseCondition = !region.checkSustain
|
||||
|| (impl.sustainState_ != Impl::SustainState::Sustaining);
|
||||
|
||||
const bool sostenutoPedalReleaseCondition = !impl.region_->checkSostenuto
|
||||
const bool sostenutoPedalReleaseCondition = !region.checkSostenuto
|
||||
|| (impl.sostenutoState_ != Impl::SostenutoState::Sustaining);
|
||||
|
||||
if (impl.noteIsOff_ && sostenutoPedalReleaseCondition && sustainPedalReleaseCondition)
|
||||
if (impl.noteIsOff_ && region.loopMode != LoopMode::one_shot
|
||||
&& sostenutoPedalReleaseCondition && sustainPedalReleaseCondition)
|
||||
release(delay);
|
||||
}
|
||||
|
||||
|
|
@ -1060,11 +1072,7 @@ void Voice::Impl::fillWithData(AudioSpan<float> buffer) noexcept
|
|||
numPartitions = 1;
|
||||
}
|
||||
|
||||
const auto sampleEnd = min(
|
||||
int(sampleSize_),
|
||||
int(currentPromise_->information.end),
|
||||
int(source.getNumFrames()))
|
||||
- 1;
|
||||
const auto sampleEnd = min( int(sampleEnd_), int(currentPromise_->information.end), int(source.getNumFrames())) - 1;
|
||||
|
||||
int blockRestarts { 0 };
|
||||
int oldIndex {};
|
||||
|
|
@ -1563,8 +1571,11 @@ bool Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexce
|
|||
if (region == nullptr || other == nullptr)
|
||||
return false;
|
||||
|
||||
if (impl.released())
|
||||
return false;
|
||||
|
||||
if (impl.triggerEvent_.type == TriggerEventType::NoteOn
|
||||
&& region->offBy == other->group
|
||||
&& region->offBy && *region->offBy == other->group
|
||||
&& (region->group != other->group || noteNumber != impl.triggerEvent_.number)) {
|
||||
off(delay);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -926,6 +926,52 @@ TEST_CASE("[Synth] Release (sustain + sostenuto)")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] One shot regions with sustain + sostenuto")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/one_shot_sustain.sfz", R"(
|
||||
<region> key=60 sample=kick.wav loop_mode=one_shot
|
||||
)");
|
||||
SECTION("Sustain")
|
||||
{
|
||||
synth.noteOn(0, 60, 85);
|
||||
synth.cc(1, 64, 127);
|
||||
synth.noteOff(2, 60, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
synth.cc(1, 64, 0);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
}
|
||||
SECTION("Sostenuto")
|
||||
{
|
||||
synth.noteOn(0, 60, 85);
|
||||
synth.cc(1, 66, 127);
|
||||
synth.noteOff(2, 60, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
synth.cc(1, 66, 0);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
}
|
||||
SECTION("Sostenuto up first")
|
||||
{
|
||||
synth.noteOn(0, 60, 85);
|
||||
synth.cc(1, 66, 127);
|
||||
synth.cc(1, 64, 127);
|
||||
synth.noteOff(2, 60, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
synth.cc(3, 66, 0);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
synth.cc(4, 64, 0);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Sustain threshold default")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
@ -1563,6 +1609,45 @@ TEST_CASE("[Synth] Off by with CC switches")
|
|||
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "*saw" );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Off by a CC event")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> group=1 off_by=2 sample=*saw
|
||||
<region> group=2 hikey=-1 on_locc67=127 on_hicc67=127 sample=*sine
|
||||
)");
|
||||
synth.noteOn(0, 60, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
synth.cc(10, 67, 127);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Off by a note-off event")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> key=60 group=1 off_by=2 sample=*saw
|
||||
<region> key=62 sample=*silence
|
||||
<region> key=62 trigger=release group=2 sample=*silence
|
||||
)");
|
||||
synth.noteOn(0, 60, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
synth.noteOn(0, 62, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 2 );
|
||||
synth.noteOff(10, 62, 85);
|
||||
synth.renderBlock(buffer);
|
||||
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||
// TODO: check the samples; the last one should be *silence
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Initial values of CC")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue