diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index ff37c50d..b2ac77a8 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -299,7 +299,10 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("amplitude_cc&"): // fallthrough case hash("amplitude_oncc&"): - setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange); + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + amplitudeCC[opcode.parameters.back()] = *value; break; case hash("pan"): setValueFromOpcode(opcode, pan, Default::panRange); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index a499a5eb..19b7f35d 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -277,7 +277,7 @@ struct Region { float width { Default::width }; // width float position { Default::position }; // position absl::optional> volumeCC; // volume_oncc - absl::optional> amplitudeCC; // amplitude_oncc + CCMap amplitudeCC { Default::amplitude }; // amplitude_oncc absl::optional> panCC; // pan_oncc absl::optional> widthCC; // width_oncc absl::optional> positionCC; // position_oncc diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index fe7df7d5..424c3076 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -85,11 +85,6 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - float gain { baseGain }; - if (region->amplitudeCC) - gain += resources.midiState.getCCValue(region->amplitudeCC->cc) * normalizePercents(region->amplitudeCC->value); - amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain)); - float crossfadeGain { region->getCrossfadeGain() }; crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain)); @@ -203,11 +198,6 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept // TODO: this feels like a hack, revisit this along with the smoothed envelopes... delay = max(delay, minEnvelopeDelay); - if (region->amplitudeCC && ccNumber == region->amplitudeCC->cc) { - const float newGain { baseGain + ccValue * normalizePercents(region->amplitudeCC->value) }; - amplitudeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(newGain)); - } - if (region->volumeCC && ccNumber == region->volumeCC->cc) { const float newVolumedB { baseVolumedB + ccValue * region->volumeCC->value }; volumeEnvelope.registerEvent(delay, db2mag(Default::volumeRange.clamp(newVolumedB))); @@ -311,6 +301,31 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept this->triggerDelay = absl::nullopt; } +template +void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp) +{ + ASSERT(output.size() <= temp.size()); + sfz::fill(output, T{0.0}); + for (auto& mod : ccMods) { + const auto eventList = state.getEvents(mod.cc); + const auto modifier = static_cast(mod.value); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = eventList[0].value; + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (event.value - lastValue) * modifier / length; + lastValue = sfz::linearRamp(temp.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + sfz::fill(temp.subspan(lastDelay), lastValue * modifier); + sfz::add(temp, output); + } +} + void sfz::Voice::processMono(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); @@ -318,12 +333,13 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationSpan = tempSpan1.first(numSamples); + auto tempSpan = tempSpan2.first(numSamples); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeEnvelope.getBlock(modulationSpan); + getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); applyGain(modulationSpan, leftBuffer); // Crossfade envelope @@ -369,6 +385,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); auto modulationSpan = tempSpan1.first(numSamples); + auto tempSpan = tempSpan2.first(numSamples); auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); @@ -376,7 +393,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeEnvelope.getBlock(modulationSpan); + getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); buffer.applyGain(modulationSpan); // Crossfade envelope diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 52d71e0d..497fab57 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -309,9 +309,9 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 52 ); REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 ); REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 ); - REQUIRE( synth.getRegionView(2)->amplitudeCC ); - REQUIRE( synth.getRegionView(2)->amplitudeCC->cc == 10 ); - REQUIRE( synth.getRegionView(2)->amplitudeCC->value == 34.0f ); + REQUIRE( !synth.getRegionView(2)->amplitudeCC.empty() ); + REQUIRE( synth.getRegionView(2)->amplitudeCC.contains(10) ); + REQUIRE( synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 34.0f ); } TEST_CASE("[Files] Specific bug: relative path with backslashes")