diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 4614f185..d128c6e3 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -9,10 +9,10 @@ sfz::MidiState::MidiState() { - reset(); + reset(0); } -void sfz::MidiState::noteOnEvent(int noteNumber, uint8_t velocity) noexcept +void sfz::MidiState::noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept { ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(velocity >= 0 && velocity <= 127); @@ -25,7 +25,7 @@ void sfz::MidiState::noteOnEvent(int noteNumber, uint8_t velocity) noexcept } -void sfz::MidiState::noteOffEvent(int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept +void sfz::MidiState::noteOffEvent(int delay, int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept { ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(velocity >= 0 && velocity <= 127); @@ -57,7 +57,7 @@ uint8_t sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept return lastNoteVelocities[noteNumber]; } -void sfz::MidiState::pitchBendEvent(int pitchBendValue) noexcept +void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept { ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192); @@ -69,7 +69,7 @@ int sfz::MidiState::getPitchBend() const noexcept return pitchBend; } -void sfz::MidiState::ccEvent(int ccNumber, uint8_t ccValue) noexcept +void sfz::MidiState::ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept { ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ASSERT(ccValue >= 0 && ccValue <= 127); @@ -89,7 +89,7 @@ const sfz::SfzCCArray& sfz::MidiState::getCCArray() const noexcept return cc; } -void sfz::MidiState::reset() noexcept +void sfz::MidiState::reset(int delay) noexcept { for (auto& velocity: lastNoteVelocities) velocity = 0; @@ -101,7 +101,7 @@ void sfz::MidiState::reset() noexcept activeNotes = 0; } -void sfz::MidiState::resetAllControllers() noexcept +void sfz::MidiState::resetAllControllers(int delay) noexcept { for (int idx = 0; idx < config::numCCs; idx++) cc[idx] = 0; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index ad8238fd..b629c88a 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -29,7 +29,7 @@ public: * @param noteNumber * @param velocity */ - void noteOnEvent(int noteNumber, uint8_t velocity) noexcept; + void noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept; /** * @brief Update the state after a note off event @@ -37,7 +37,7 @@ public: * @param noteNumber * @param velocity */ - void noteOffEvent(int noteNumber, uint8_t velocity) noexcept; + void noteOffEvent(int delay, int noteNumber, uint8_t velocity) noexcept; int getActiveNotes() const noexcept { return activeNotes; } @@ -62,7 +62,7 @@ public: * * @param pitchBendValue */ - void pitchBendEvent(int pitchBendValue) noexcept; + void pitchBendEvent(int delay, int pitchBendValue) noexcept; /** * @brief Get the pitch bend status @@ -77,7 +77,7 @@ public: * @param ccNumber * @param ccValue */ - void ccEvent(int ccNumber, uint8_t ccValue) noexcept; + void ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept; /** * @brief Get the CC value for CC number @@ -98,12 +98,12 @@ public: * @brief Reset the midi state (does not impact the last note on time) * */ - void reset() noexcept; + void reset(int delay) noexcept; /** * @brief Reset all the controllers */ - void resetAllControllers() noexcept; + void resetAllControllers(int delay) noexcept; /** * @brief Modulate a value using the last entered CCs in the midiState diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index deb58a8c..100aef69 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -135,7 +135,7 @@ void sfz::Synth::clear() fileTicket = -1; defaultSwitch = absl::nullopt; defaultPath = ""; - resources.midiState.reset(); + resources.midiState.reset(0); ccNames.clear(); globalOpcodes.clear(); masterOpcodes.clear(); @@ -167,7 +167,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) case hash("set_cc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); - resources.midiState.ccEvent(member.parameters.back(), ccValue); + resources.midiState.ccEvent(0, member.parameters.back(), ccValue); } break; case hash("Label_cc&"): [[fallthrough]]; @@ -579,7 +579,7 @@ void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept ASSERT(noteNumber >= 0); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.noteOnEvent(noteNumber, velocity); + resources.midiState.noteOnEvent(delay, noteNumber, velocity); AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) @@ -594,7 +594,7 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unu ASSERT(noteNumber >= 0); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.noteOffEvent(noteNumber, velocity); + resources.midiState.noteOffEvent(delay, noteNumber, velocity); AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) @@ -650,8 +650,7 @@ void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept ASSERT(ccNumber >= 0); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - - resources.midiState.ccEvent(ccNumber, ccValue); + resources.midiState.ccEvent(delay, ccNumber, ccValue); AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) @@ -682,8 +681,7 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept ASSERT(pitch >= -8192); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - - resources.midiState.pitchBendEvent(pitch); + resources.midiState.pitchBendEvent(delay, pitch); for (auto& region: regions) { region->registerPitchWheel(pitch); @@ -924,7 +922,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept if (!canEnterCallback) return; - resources.midiState.resetAllControllers(); + resources.midiState.resetAllControllers(delay); for (auto& voice: voices) { voice->registerPitchWheel(delay, 0); for (int cc = 0; cc < config::numCCs; ++cc) diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index 4a7a161d..d06f2215 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -27,8 +27,8 @@ TEST_CASE("[MidiState] Set and get CCs") { sfz::MidiState state; const auto& cc = state.getCCArray(); - state.ccEvent(24, 23); - state.ccEvent(123, 124); + state.ccEvent(0, 24, 23); + state.ccEvent(0, 123, 124); REQUIRE(state.getCCValue(24) == 23); REQUIRE(cc[24] == 23); REQUIRE(state.getCCValue(123) == 124); @@ -38,19 +38,19 @@ TEST_CASE("[MidiState] Set and get CCs") TEST_CASE("[MidiState] Set and get pitch bends") { sfz::MidiState state; - state.pitchBendEvent(894); + state.pitchBendEvent(0, 894); REQUIRE(state.getPitchBend() == 894); - state.pitchBendEvent(0); + state.pitchBendEvent(0, 0); REQUIRE(state.getPitchBend() == 0); } TEST_CASE("[MidiState] Reset") { sfz::MidiState state; - state.pitchBendEvent(894); - state.noteOnEvent(64, 24); - state.ccEvent(123, 124); - state.reset(); + state.pitchBendEvent(0, 894); + state.noteOnEvent(0, 64, 24); + state.ccEvent(0, 123, 124); + state.reset(0); REQUIRE(state.getPitchBend() == 0); REQUIRE(state.getNoteVelocity(64) == 0); REQUIRE(state.getCCValue(123) == 0); @@ -59,9 +59,9 @@ TEST_CASE("[MidiState] Reset") TEST_CASE("[MidiState] Set and get note velocities") { sfz::MidiState state; - state.noteOnEvent(64, 24); + state.noteOnEvent(0, 64, 24); REQUIRE(+state.getNoteVelocity(64) == 24); - state.noteOnEvent(64, 123); + state.noteOnEvent(0, 64, 123); REQUIRE(+state.getNoteVelocity(64) == 123); } @@ -69,5 +69,5 @@ TEST_CASE("[MidiState] Extended CCs") { sfz::MidiState state; REQUIRE(state.getCCArray().size() >= 142); - state.ccEvent(142, 64); // should not trap + state.ccEvent(0, 142, 64); // should not trap } diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index c2a116c8..fe7f4569 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -135,15 +135,15 @@ TEST_CASE("Legato triggers", "Region triggers") region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "trigger", "first" }); - midiState.noteOnEvent(40, 64); + midiState.noteOnEvent(0, 40, 64); REQUIRE(region.registerNoteOn(40, 64, 0.5f)); - midiState.noteOnEvent(41, 64); + midiState.noteOnEvent(0, 41, 64); REQUIRE(!region.registerNoteOn(41, 64, 0.5f)); - midiState.noteOffEvent(40, 0); + midiState.noteOffEvent(0, 40, 0); region.registerNoteOff(40, 0, 0.5f); - midiState.noteOffEvent(41, 0); + midiState.noteOffEvent(0, 41, 0); region.registerNoteOff(41, 0, 0.5f); - midiState.noteOnEvent(42, 64); + midiState.noteOnEvent(0, 42, 64); REQUIRE(region.registerNoteOn(42, 64, 0.5f)); } @@ -152,15 +152,15 @@ TEST_CASE("Legato triggers", "Region triggers") region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "trigger", "legato" }); - midiState.noteOnEvent(40, 64); + midiState.noteOnEvent(0, 40, 64); REQUIRE(!region.registerNoteOn(40, 64, 0.5f)); - midiState.noteOnEvent(41, 64); + midiState.noteOnEvent(0, 41, 64); REQUIRE(region.registerNoteOn(41, 64, 0.5f)); - midiState.noteOffEvent(40, 64); + midiState.noteOffEvent(0, 40, 64); region.registerNoteOff(40, 0, 0.5f); - midiState.noteOffEvent(41, 64); + midiState.noteOffEvent(0, 41, 64); region.registerNoteOff(41, 0, 0.5f); - midiState.noteOnEvent(42, 64); + midiState.noteOnEvent(0, 42, 64); REQUIRE(!region.registerNoteOn(42, 64, 0.5f)); } } diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index d07da9cc..eae54b0b 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -166,13 +166,13 @@ TEST_CASE("[Region] Crossfade in on CC") region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); } TEST_CASE("[Region] Crossfade in on CC - gain") @@ -184,13 +184,13 @@ TEST_CASE("[Region] Crossfade in on CC - gain") region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); } TEST_CASE("[Region] Crossfade out on CC") { @@ -200,13 +200,13 @@ TEST_CASE("[Region] Crossfade out on CC") region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); } TEST_CASE("[Region] Crossfade out on CC - gain") @@ -218,13 +218,13 @@ TEST_CASE("[Region] Crossfade out on CC - gain") region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); } TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") @@ -265,15 +265,15 @@ TEST_CASE("[Region] rt_decay") region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); - midiState.noteOnEvent(64, 64); + midiState.noteOnEvent(0, 64, 64); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); - midiState.noteOnEvent(64, 64); + midiState.noteOnEvent(0, 64, 64); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); - midiState.noteOnEvent(64, 64); + midiState.noteOnEvent(0, 64, 64); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) ); }