Skip to content

Commit

Permalink
Handle extended CCs 140 and 141 (keydelta)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulfd committed Nov 9, 2021
1 parent 222df62 commit aef980c
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 15 deletions.
20 changes: 11 additions & 9 deletions src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 10 additions & 2 deletions src/sfizz/MidiState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(noteNumber - lastNotePlayed);
velocityOverride = lastNoteVelocities[lastNotePlayed];
}

lastNoteVelocities[noteNumber] = velocity;
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
lastNotePlayed = noteNumber;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/sfizz/MidiState.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class MidiState
/**
* @brief Last note played
*/
int lastNotePlayed { 0 };
int lastNotePlayed { -1 };

/**
* @brief Current known values for the CCs.
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1721,6 +1721,8 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> 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:
Expand Down
4 changes: 2 additions & 2 deletions src/sfizz/RegionStateful.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int64_t> 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<uint64_t>(mod.data * midiState.getCCValue(mod.cc));
finalOffset += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
return Default::offset.bounds.clamp(finalOffset);
}

Expand Down
2 changes: 1 addition & 1 deletion src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ void Synth::Impl::handleGlobalOpcodes(const std::vector<Opcode>& members)

void Synth::Impl::handleGroupOpcodes(const std::vector<Opcode>& members, const std::vector<Opcode>& masterMembers)
{
absl::optional<int> groupIdx;
absl::optional<int64_t> groupIdx;
absl::optional<unsigned> maxPolyphony;

const auto parseOpcode = [&](const Opcode& rawMember) {
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/Voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/Voice.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct ExtendedCCValues {
float bipolar {};
float noteGate {};
float alternate {};
float keydelta {};
};

/**
Expand Down
14 changes: 14 additions & 0 deletions src/sfizz/modulations/sources/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> 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);
Expand Down
50 changes: 50 additions & 0 deletions tests/MidiStateT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"(
<region> 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<std::string> 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"(
<region> 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<std::string> 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);
}
}

0 comments on commit aef980c

Please sign in to comment.