diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index e76a6805..ef899329 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -57,6 +57,7 @@ namespace Default // common defaults constexpr Range midi7Range { 0, 127 }; constexpr Range normalizedRange { 0.0f, 1.0f }; + constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; constexpr float zeroModifier { 0.0f }; // Wavetable oscillator @@ -79,7 +80,8 @@ namespace Default constexpr Range midiChannelRange { 0, 15 }; constexpr Range ccNumberRange { 0, config::numCCs }; constexpr auto ccValueRange = normalizedRange; - constexpr Range bendRange { -8192, 8192 }; + constexpr Range bendRange = { -8192, 8192 }; + constexpr Range bendValueRange = symmetricNormalizedRange; constexpr int bend { 0 }; constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current }; @@ -107,7 +109,6 @@ namespace Default constexpr float pan { 0.0 }; constexpr Range panRange { -100.0, 100.0 }; constexpr Range panCCRange { -200.0, 200.0 }; - constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; constexpr float position { 0.0 }; constexpr Range positionRange { -100.0, 100.0 }; constexpr Range positionCCRange { -200.0, 200.0 }; diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 434423b0..94964722 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -57,6 +57,10 @@ void sfz::MidiState::advanceTime(int numSamples) noexcept ccEvents.front().delay = 0; ccEvents.resize(1); } + ASSERT(!pitchEvents.empty()); + pitchEvents.front().value = pitchEvents.back().value; + pitchEvents.front().delay = 0; + pitchEvents.resize(1); } void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept @@ -89,16 +93,22 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept } -void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept +void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept { - ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192); + ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f); pitchBend = pitchBendValue; + const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator{}); + if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay) + pitchEvents.insert(insertionPoint, { delay, pitchBendValue }); + else + insertionPoint->value = pitchBendValue; } -int sfz::MidiState::getPitchBend() const noexcept +float sfz::MidiState::getPitchBend() const noexcept { - return pitchBend; + ASSERT(pitchEvents.size() > 0); + return pitchEvents.back().value; } void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept @@ -128,6 +138,9 @@ void sfz::MidiState::reset() noexcept ccEvents.push_back({ 0, 0.0f }); } + pitchEvents.clear(); + pitchEvents.push_back({ 0, 0.0f }); + pitchBend = 0; activeNotes = 0; internalClock = 0; @@ -141,6 +154,7 @@ void sfz::MidiState::resetAllControllers(int delay) noexcept ccEvent(delay, ccIdx, 0.0f); pitchBend = 0; + pitchBendEvent(delay, 0.0f); } const sfz::EventVector& sfz::MidiState::getEvents(int ccIdx) const noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 6c634838..df8061cc 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -79,14 +79,14 @@ public: * * @param pitchBendValue */ - void pitchBendEvent(int delay, int pitchBendValue) noexcept; + void pitchBendEvent(int delay, float pitchBendValue) noexcept; /** * @brief Get the pitch bend status * @return int */ - int getPitchBend() const noexcept; + float getPitchBend() const noexcept; /** * @brief Register a CC event @@ -176,6 +176,7 @@ private: * Pitch bend status */ int pitchBend { 0 }; + EventVector pitchEvents; float sampleRate { config::defaultSampleRate }; int samplesPerBlock { config::defaultSamplesPerBlock }; unsigned internalClock { 0 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index bcc7196d..1bb17db9 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -165,10 +165,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) // Region logic: MIDI conditions case hash("lobend"): - setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); + if (auto value = readOpcode(opcode.value, Default::bendRange)) + bendRange.setStart(normalizeBend(*value)); break; case hash("hibend"): - setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); + if (auto value = readOpcode(opcode.value, Default::bendRange)) + bendRange.setEnd(normalizeBend(*value)); break; case hash("locc&"): if (opcode.parameters.back() > config::numCCs) @@ -937,7 +939,7 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept return false; } -void sfz::Region::registerPitchWheel(int pitch) noexcept +void sfz::Region::registerPitchWheel(float pitch) noexcept { if (bendRange.containsWithEnd(pitch)) pitchSwitched = true; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 3aac8ff8..8cd8e3ad 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -113,7 +113,7 @@ struct Region { * * @param pitch */ - void registerPitchWheel(int pitch) noexcept; + void registerPitchWheel(float pitch) noexcept; /** * @brief Register a new aftertouch event. * @@ -248,7 +248,7 @@ struct Region { Range velocityRange { Default::velocityRange }; // hivel and lovel // Region logic: MIDI conditions - Range bendRange { Default::bendRange }; // hibend and lobend + Range bendRange { Default::bendValueRange }; // hibend and lobend CCMap> ccConditions { Default::ccValueRange }; Range keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey absl::optional keyswitch {}; // sw_last diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index d991e795..601d676f 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -189,7 +189,7 @@ constexpr float normalizePercents(T percentValue) */ constexpr float normalizeBend(float bendValue) { - return min(max(bendValue, -8191.0f), 8191.0f) / 8191.0f; + return clamp(bendValue, -8191.0f, 8191.0f) / 8191.0f; } namespace literals diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index caa51ca0..4ac28d54 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -778,16 +778,17 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept { ASSERT(pitch <= 8192); ASSERT(pitch >= -8192); + const auto normalizedPitch = normalizeBend(pitch); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.pitchBendEvent(delay, pitch); + resources.midiState.pitchBendEvent(delay, normalizedPitch); for (auto& region : regions) { - region->registerPitchWheel(pitch); + region->registerPitchWheel(normalizedPitch); } for (auto& voice : voices) { - voice->registerPitchWheel(delay, pitch); + voice->registerPitchWheel(delay, normalizedPitch); } } void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2cb771f6..4dc90a1c 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -85,12 +85,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - pitchBendEnvelope.setFunction([region](float pitchValue){ - const auto normalizedBend = normalizeBend(pitchValue); - const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * static_cast(region->bendUp) : -normalizedBend * static_cast(region->bendDown); + pitchBendEnvelope.setFunction([region](float bend){ + const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); return centsFactor(bendInCents); }); - pitchBendEnvelope.reset(static_cast(resources.midiState.getPitchBend())); + pitchBendEnvelope.reset(resources.midiState.getPitchBend()); // Check that we can handle the number of filters; filters should be cleared here ASSERT((filters.capacity() - filters.size()) >= region->filters.size()); @@ -183,12 +182,12 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept } } -void sfz::Voice::registerPitchWheel(int delay, int pitch) noexcept +void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept { if (state == State::idle) return; - pitchBendEnvelope.registerEvent(delay, static_cast(pitch)); + pitchBendEnvelope.registerEvent(delay, pitch * 8191.0f); } void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept @@ -279,7 +278,6 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); applyGain(*tempSpan, *modulationSpan); } - DBG("Final gain: " << modulationSpan->back()); applyGain(*modulationSpan, leftBuffer); // Crossfade envelopes @@ -292,7 +290,6 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); applyGain(*tempSpan, *modulationSpan); } - DBG("XF: " << modulationSpan->back()); applyGain(*modulationSpan, leftBuffer); // Volume envelope @@ -326,7 +323,6 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); applyGain(*tempSpan, *modulationSpan); } - DBG("Final gain: " << modulationSpan->back()); buffer.applyGain(*modulationSpan); @@ -340,7 +336,6 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); applyGain(*tempSpan, *modulationSpan); } - DBG("XF: " << modulationSpan->back()); buffer.applyGain(*modulationSpan); // Volume envelope @@ -374,7 +369,6 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); add(*tempSpan, *modulationSpan); } - DBG("Pan: " << modulationSpan->back()); pan(*modulationSpan, leftBuffer, rightBuffer); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index d65a228a..242d045d 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -107,7 +107,7 @@ public: * @param delay * @param pitch */ - void registerPitchWheel(int delay, int pitch) noexcept; + void registerPitchWheel(int delay, float pitch) noexcept; /** * @brief Register an aftertouch event; for now this does nothing * diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index 6a2af4c0..a47a14c8 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -37,20 +37,20 @@ TEST_CASE("[MidiState] Set and get CCs") TEST_CASE("[MidiState] Set and get pitch bends") { sfz::MidiState state; - state.pitchBendEvent(0, 894); - REQUIRE(state.getPitchBend() == 894); - state.pitchBendEvent(0, 0); - REQUIRE(state.getPitchBend() == 0); + state.pitchBendEvent(0, 0.5f); + REQUIRE(state.getPitchBend() == 0.5f); + state.pitchBendEvent(0, 0.0f); + REQUIRE(state.getPitchBend() == 0.0f); } TEST_CASE("[MidiState] Reset") { sfz::MidiState state; - state.pitchBendEvent(0, 894); + state.pitchBendEvent(0, 0.7f); state.noteOnEvent(0, 64, 24_norm); state.ccEvent(0, 123, 124_norm); state.reset(); - REQUIRE(state.getPitchBend() == 0); + REQUIRE(state.getPitchBend() == 0.0f); REQUIRE(state.getNoteVelocity(64) == 0_norm); REQUIRE(state.getCCValue(123) == 0_norm); } @@ -58,12 +58,12 @@ TEST_CASE("[MidiState] Reset") TEST_CASE("[MidiState] Reset all controllers") { sfz::MidiState state; - state.pitchBendEvent(20, 894); + state.pitchBendEvent(20, 0.7f); state.ccEvent(10, 122, 124_norm); - REQUIRE(state.getPitchBend() == 894); + REQUIRE(state.getPitchBend() == 0.7f); REQUIRE(state.getCCValue(122) == 124_norm); state.resetAllControllers(30); - REQUIRE(state.getPitchBend() == 0); + REQUIRE(state.getPitchBend() == 0.0f); REQUIRE(state.getCCValue(122) == 0_norm); REQUIRE(state.getCCValue(4) == 0_norm); } diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index c2aae1d1..e65934b7 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -77,11 +77,11 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "hibend", "243" }); region.registerPitchWheel(0); REQUIRE(!region.isSwitchedOn()); - region.registerPitchWheel(56); + region.registerPitchWheel(sfz::normalizeBend(56)); REQUIRE(region.isSwitchedOn()); - region.registerPitchWheel(243); + region.registerPitchWheel(sfz::normalizeBend(243)); REQUIRE(region.isSwitchedOn()); - region.registerPitchWheel(245); + region.registerPitchWheel(sfz::normalizeBend(245)); REQUIRE(!region.isSwitchedOn()); } diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index c450b7fb..66a959a8 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -223,19 +223,23 @@ TEST_CASE("[Region] Parsing opcodes") SECTION("lobend, hibend") { - REQUIRE(region.bendRange == sfz::Range(-8192, 8192)); - region.parseOpcode({ "lobend", "4" }); - REQUIRE(region.bendRange == sfz::Range(4, 8192)); + REQUIRE(region.bendRange == sfz::Range(-1.0f, 1.0f)); + region.parseOpcode({ "lobend", "400" }); + REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(400))); + REQUIRE(region.bendRange.getEnd() == 1.0_a); region.parseOpcode({ "lobend", "-128" }); - REQUIRE(region.bendRange == sfz::Range(-128, 8192)); + REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(-128))); + REQUIRE(region.bendRange.getEnd() == 1.0_a); region.parseOpcode({ "lobend", "-10000" }); - REQUIRE(region.bendRange == sfz::Range(-8192, 8192)); + REQUIRE(region.bendRange == sfz::Range(-1.0f, 1.0f)); region.parseOpcode({ "hibend", "13" }); - REQUIRE(region.bendRange == sfz::Range(-8192, 13)); + REQUIRE(region.bendRange.getStart() == -1.0_a); + REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(13))); region.parseOpcode({ "hibend", "-1" }); - REQUIRE(region.bendRange == sfz::Range(-8192, -1)); + REQUIRE(region.bendRange.getStart() == -1.0_a); + REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(-1))); region.parseOpcode({ "hibend", "10000" }); - REQUIRE(region.bendRange == sfz::Range(-8192, 8192)); + REQUIRE(region.bendRange == sfz::Range(-1.0f, 1.0f)); } SECTION("locc, hicc")