diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index a458a8376..599c70ba5 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -20,15 +20,17 @@ namespace sfz { enum ExtendedCCs { pitchBend = 128, - channelAftertouch, - polyphonicAftertouch, - noteOnVelocity, - noteOffVelocity, - keyboardNoteNumber, - keyboardNoteGate, - unipolarRandom, - bipolarRandom, - alternate + channelAftertouch = 129, + polyphonicAftertouch = 130, + noteOnVelocity = 131, + noteOffVelocity = 132, + keyboardNoteNumber = 133, + keyboardNoteGate = 134, + unipolarRandom = 135, + bipolarRandom = 136, + alternate = 137, + keydelta = 140, + absoluteKeydelta = 141, }; namespace config { diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 4d0d45ebe..a7e5f7ac0 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -19,7 +19,13 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex ASSERT(velocity >= 0 && velocity <= 1.0); if (noteNumber >= 0 && noteNumber < 128) { - velocityOverride = lastNoteVelocities[lastNotePlayed]; + float keydelta { 0 }; + + if (lastNotePlayed >= 0) { + keydelta = static_cast(noteNumber - lastNotePlayed); + velocityOverride = lastNoteVelocities[lastNotePlayed]; + } + lastNoteVelocities[noteNumber] = velocity; noteOnTimes[noteNumber] = internalClock + static_cast(delay); lastNotePlayed = noteNumber; @@ -29,6 +35,8 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator)); ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator)); ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f); + ccEvent(delay, ExtendedCCs::keydelta, keydelta); + ccEvent(delay, ExtendedCCs::absoluteKeydelta, std::abs(keydelta)); activeNotes++; ccEvent(delay, ExtendedCCs::alternate, alternate); @@ -235,7 +243,7 @@ void sfz::MidiState::reset() noexcept velocityOverride = 0.0f; activeNotes = 0; internalClock = 0; - lastNotePlayed = 0; + lastNotePlayed = -1; noteStates.reset(); absl::c_fill(noteOnTimes, 0); absl::c_fill(noteOffTimes, 0); diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index eaf947c60..d6dd0db14 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -241,7 +241,7 @@ class MidiState /** * @brief Last note played */ - int lastNotePlayed { 0 }; + int lastNotePlayed { -1 }; /** * @brief Current known values for the CCs. diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index e7481101b..f78a23466 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1721,6 +1721,8 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, case ExtendedCCs::unipolarRandom: // fallthrough case ExtendedCCs::bipolarRandom: // fallthrough case ExtendedCCs::alternate: + case ExtendedCCs::keydelta: + case ExtendedCCs::absoluteKeydelta: conn->source = ModKey(ModId::PerVoiceController, id, p); break; default: diff --git a/src/sfizz/RegionStateful.cpp b/src/sfizz/RegionStateful.cpp index e0f7bf865..1a4e440dd 100644 --- a/src/sfizz/RegionStateful.cpp +++ b/src/sfizz/RegionStateful.cpp @@ -25,9 +25,9 @@ float baseVolumedB(const Region& region, const MidiState& midiState, int noteNum uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept { std::uniform_int_distribution offsetDistribution { 0, region.offsetRandom }; - uint64_t finalOffset = region.offset + offsetDistribution(Random::randomGenerator); + int64_t finalOffset = region.offset + offsetDistribution(Random::randomGenerator); for (const auto& mod: region.offsetCC) - finalOffset += static_cast(mod.data * midiState.getCCValue(mod.cc)); + finalOffset += static_cast(mod.data * midiState.getCCValue(mod.cc)); return Default::offset.bounds.clamp(finalOffset); } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index fbf5f1589..8aa118f5c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -340,7 +340,7 @@ void Synth::Impl::handleGlobalOpcodes(const std::vector& members) void Synth::Impl::handleGroupOpcodes(const std::vector& members, const std::vector& masterMembers) { - absl::optional groupIdx; + absl::optional groupIdx; absl::optional maxPolyphony; const auto parseOpcode = [&](const Opcode& rawMember) { diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index f08def4e0..e6b245ef3 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -399,6 +399,7 @@ void Voice::Impl::updateExtendedCCValues() noexcept extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom); extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate); extendedCCValues_.noteGate = midiState.getCCValue(ExtendedCCs::keyboardNoteGate); + extendedCCValues_.keydelta = midiState.getCCValue(ExtendedCCs::keydelta); } bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 5b8d42c6e..94a6f3c43 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -26,6 +26,7 @@ struct ExtendedCCValues { float bipolar {}; float noteGate {}; float alternate {}; + float keydelta {}; }; /** diff --git a/src/sfizz/modulations/sources/Controller.cpp b/src/sfizz/modulations/sources/Controller.cpp index eafffd56c..a5d7db93d 100644 --- a/src/sfizz/modulations/sources/Controller.cpp +++ b/src/sfizz/modulations/sources/Controller.cpp @@ -170,6 +170,20 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId voiceI canShortcut = true; break; } + case ExtendedCCs::keydelta: { + const auto voice = impl_->voiceManager_->getVoiceById(voiceId); + const float fillValue = voice ? voice->getExtendedCCValues().keydelta : 0.0f; + sfz::fill(buffer, quantize(fillValue)); + canShortcut = true; + break; + } + case ExtendedCCs::absoluteKeydelta: { + const auto voice = impl_->voiceManager_->getVoiceById(voiceId); + const float fillValue = voice ? std::abs(voice->getExtendedCCValues().keydelta) : 0.0f; + sfz::fill(buffer, quantize(fillValue)); + canShortcut = true; + break; + } case ExtendedCCs::pitchBend: // fallthrough case ExtendedCCs::channelAftertouch: { const EventVector& events = ms.getCCEvents(p.cc); diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index f29c71983..a470c0fe3 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -223,4 +223,54 @@ TEST_CASE("[CC] Extended CCs on offset and delay") }; REQUIRE(messageList == expected); } + + SECTION("CC140 - Keydelta") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + delay=2 offset=200 delay_cc140=1 offset_cc140=100 sample=kick.wav + )"); + synth.hdNoteOn(0, 60, 1.0f); + synth.hdNoteOn(0, 61, 1.0f); + synth.hdNoteOn(0, 59, 1.0f); + synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr); + std::vector expected { + "/voice0/remaining_delay,i : { 96000 }", + "/voice1/remaining_delay,i : { 144000 }", + "/voice2/remaining_delay,i : { 0 }", + "/voice0/source_position,i : { 200 }", + "/voice1/source_position,i : { 300 }", + "/voice2/source_position,i : { 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC141 - Absolute Keydelta") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + delay=2 offset=200 delay_cc141=1 offset_cc141=100 sample=kick.wav + )"); + synth.hdNoteOn(0, 60, 1.0f); + synth.hdNoteOn(0, 61, 1.0f); + synth.hdNoteOn(0, 59, 1.0f); + synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr); + std::vector expected { + "/voice0/remaining_delay,i : { 96000 }", + "/voice1/remaining_delay,i : { 144000 }", + "/voice2/remaining_delay,i : { 192000 }", + "/voice0/source_position,i : { 200 }", + "/voice1/source_position,i : { 300 }", + "/voice2/source_position,i : { 400 }", + }; + REQUIRE(messageList == expected); + } }