Handle extended CCs 140 and 141 (keydelta)
This commit is contained in:
parent
222df62eb0
commit
aef980c94d
10 changed files with 93 additions and 15 deletions
|
|
@ -20,15 +20,17 @@ namespace sfz {
|
||||||
|
|
||||||
enum ExtendedCCs {
|
enum ExtendedCCs {
|
||||||
pitchBend = 128,
|
pitchBend = 128,
|
||||||
channelAftertouch,
|
channelAftertouch = 129,
|
||||||
polyphonicAftertouch,
|
polyphonicAftertouch = 130,
|
||||||
noteOnVelocity,
|
noteOnVelocity = 131,
|
||||||
noteOffVelocity,
|
noteOffVelocity = 132,
|
||||||
keyboardNoteNumber,
|
keyboardNoteNumber = 133,
|
||||||
keyboardNoteGate,
|
keyboardNoteGate = 134,
|
||||||
unipolarRandom,
|
unipolarRandom = 135,
|
||||||
bipolarRandom,
|
bipolarRandom = 136,
|
||||||
alternate
|
alternate = 137,
|
||||||
|
keydelta = 140,
|
||||||
|
absoluteKeydelta = 141,
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace config {
|
namespace config {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,13 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
|
||||||
ASSERT(velocity >= 0 && velocity <= 1.0);
|
ASSERT(velocity >= 0 && velocity <= 1.0);
|
||||||
|
|
||||||
if (noteNumber >= 0 && noteNumber < 128) {
|
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;
|
lastNoteVelocities[noteNumber] = velocity;
|
||||||
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||||
lastNotePlayed = noteNumber;
|
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::unipolarRandom, unipolarDist(Random::randomGenerator));
|
||||||
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
||||||
ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f);
|
ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f);
|
||||||
|
ccEvent(delay, ExtendedCCs::keydelta, keydelta);
|
||||||
|
ccEvent(delay, ExtendedCCs::absoluteKeydelta, std::abs(keydelta));
|
||||||
activeNotes++;
|
activeNotes++;
|
||||||
|
|
||||||
ccEvent(delay, ExtendedCCs::alternate, alternate);
|
ccEvent(delay, ExtendedCCs::alternate, alternate);
|
||||||
|
|
@ -235,7 +243,7 @@ void sfz::MidiState::reset() noexcept
|
||||||
velocityOverride = 0.0f;
|
velocityOverride = 0.0f;
|
||||||
activeNotes = 0;
|
activeNotes = 0;
|
||||||
internalClock = 0;
|
internalClock = 0;
|
||||||
lastNotePlayed = 0;
|
lastNotePlayed = -1;
|
||||||
noteStates.reset();
|
noteStates.reset();
|
||||||
absl::c_fill(noteOnTimes, 0);
|
absl::c_fill(noteOnTimes, 0);
|
||||||
absl::c_fill(noteOffTimes, 0);
|
absl::c_fill(noteOffTimes, 0);
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief Last note played
|
* @brief Last note played
|
||||||
*/
|
*/
|
||||||
int lastNotePlayed { 0 };
|
int lastNotePlayed { -1 };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Current known values for the CCs.
|
* @brief Current known values for the CCs.
|
||||||
|
|
|
||||||
|
|
@ -1721,6 +1721,8 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
||||||
case ExtendedCCs::unipolarRandom: // fallthrough
|
case ExtendedCCs::unipolarRandom: // fallthrough
|
||||||
case ExtendedCCs::bipolarRandom: // fallthrough
|
case ExtendedCCs::bipolarRandom: // fallthrough
|
||||||
case ExtendedCCs::alternate:
|
case ExtendedCCs::alternate:
|
||||||
|
case ExtendedCCs::keydelta:
|
||||||
|
case ExtendedCCs::absoluteKeydelta:
|
||||||
conn->source = ModKey(ModId::PerVoiceController, id, p);
|
conn->source = ModKey(ModId::PerVoiceController, id, p);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ float baseVolumedB(const Region& region, const MidiState& midiState, int noteNum
|
||||||
uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept
|
uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept
|
||||||
{
|
{
|
||||||
std::uniform_int_distribution<int64_t> offsetDistribution { 0, region.offsetRandom };
|
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)
|
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);
|
return Default::offset.bounds.clamp(finalOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
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;
|
absl::optional<unsigned> maxPolyphony;
|
||||||
|
|
||||||
const auto parseOpcode = [&](const Opcode& rawMember) {
|
const auto parseOpcode = [&](const Opcode& rawMember) {
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,7 @@ void Voice::Impl::updateExtendedCCValues() noexcept
|
||||||
extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom);
|
extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom);
|
||||||
extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate);
|
extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate);
|
||||||
extendedCCValues_.noteGate = midiState.getCCValue(ExtendedCCs::keyboardNoteGate);
|
extendedCCValues_.noteGate = midiState.getCCValue(ExtendedCCs::keyboardNoteGate);
|
||||||
|
extendedCCValues_.keydelta = midiState.getCCValue(ExtendedCCs::keydelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept
|
bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ struct ExtendedCCValues {
|
||||||
float bipolar {};
|
float bipolar {};
|
||||||
float noteGate {};
|
float noteGate {};
|
||||||
float alternate {};
|
float alternate {};
|
||||||
|
float keydelta {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,20 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceI
|
||||||
canShortcut = true;
|
canShortcut = true;
|
||||||
break;
|
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::pitchBend: // fallthrough
|
||||||
case ExtendedCCs::channelAftertouch: {
|
case ExtendedCCs::channelAftertouch: {
|
||||||
const EventVector& events = ms.getCCEvents(p.cc);
|
const EventVector& events = ms.getCCEvents(p.cc);
|
||||||
|
|
|
||||||
|
|
@ -223,4 +223,54 @@ TEST_CASE("[CC] Extended CCs on offset and delay")
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue