Store pitch bend internally as floats

This commit is contained in:
Paul Ferrand 2020-03-30 21:07:33 +02:00
parent bc4d88b55a
commit 922cbde6c5
12 changed files with 66 additions and 49 deletions

View file

@ -57,6 +57,7 @@ namespace Default
// common defaults // common defaults
constexpr Range<uint8_t> midi7Range { 0, 127 }; constexpr Range<uint8_t> midi7Range { 0, 127 };
constexpr Range<float> normalizedRange { 0.0f, 1.0f }; constexpr Range<float> normalizedRange { 0.0f, 1.0f };
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
constexpr float zeroModifier { 0.0f }; constexpr float zeroModifier { 0.0f };
// Wavetable oscillator // Wavetable oscillator
@ -79,7 +80,8 @@ namespace Default
constexpr Range<uint8_t> midiChannelRange { 0, 15 }; constexpr Range<uint8_t> midiChannelRange { 0, 15 };
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs }; constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
constexpr auto ccValueRange = normalizedRange; constexpr auto ccValueRange = normalizedRange;
constexpr Range<int> bendRange { -8192, 8192 }; constexpr Range<int> bendRange = { -8192, 8192 };
constexpr Range<float> bendValueRange = symmetricNormalizedRange;
constexpr int bend { 0 }; constexpr int bend { 0 };
constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current }; constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current };
@ -107,7 +109,6 @@ namespace Default
constexpr float pan { 0.0 }; constexpr float pan { 0.0 };
constexpr Range<float> panRange { -100.0, 100.0 }; constexpr Range<float> panRange { -100.0, 100.0 };
constexpr Range<float> panCCRange { -200.0, 200.0 }; constexpr Range<float> panCCRange { -200.0, 200.0 };
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
constexpr float position { 0.0 }; constexpr float position { 0.0 };
constexpr Range<float> positionRange { -100.0, 100.0 }; constexpr Range<float> positionRange { -100.0, 100.0 };
constexpr Range<float> positionCCRange { -200.0, 200.0 }; constexpr Range<float> positionCCRange { -200.0, 200.0 };

View file

@ -57,6 +57,10 @@ void sfz::MidiState::advanceTime(int numSamples) noexcept
ccEvents.front().delay = 0; ccEvents.front().delay = 0;
ccEvents.resize(1); 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 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; 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 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 }); ccEvents.push_back({ 0, 0.0f });
} }
pitchEvents.clear();
pitchEvents.push_back({ 0, 0.0f });
pitchBend = 0; pitchBend = 0;
activeNotes = 0; activeNotes = 0;
internalClock = 0; internalClock = 0;
@ -141,6 +154,7 @@ void sfz::MidiState::resetAllControllers(int delay) noexcept
ccEvent(delay, ccIdx, 0.0f); ccEvent(delay, ccIdx, 0.0f);
pitchBend = 0; pitchBend = 0;
pitchBendEvent(delay, 0.0f);
} }
const sfz::EventVector& sfz::MidiState::getEvents(int ccIdx) const noexcept const sfz::EventVector& sfz::MidiState::getEvents(int ccIdx) const noexcept

View file

@ -79,14 +79,14 @@ public:
* *
* @param pitchBendValue * @param pitchBendValue
*/ */
void pitchBendEvent(int delay, int pitchBendValue) noexcept; void pitchBendEvent(int delay, float pitchBendValue) noexcept;
/** /**
* @brief Get the pitch bend status * @brief Get the pitch bend status
* @return int * @return int
*/ */
int getPitchBend() const noexcept; float getPitchBend() const noexcept;
/** /**
* @brief Register a CC event * @brief Register a CC event
@ -176,6 +176,7 @@ private:
* Pitch bend status * Pitch bend status
*/ */
int pitchBend { 0 }; int pitchBend { 0 };
EventVector pitchEvents;
float sampleRate { config::defaultSampleRate }; float sampleRate { config::defaultSampleRate };
int samplesPerBlock { config::defaultSamplesPerBlock }; int samplesPerBlock { config::defaultSamplesPerBlock };
unsigned internalClock { 0 }; unsigned internalClock { 0 };

View file

@ -165,10 +165,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
// Region logic: MIDI conditions // Region logic: MIDI conditions
case hash("lobend"): case hash("lobend"):
setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); if (auto value = readOpcode(opcode.value, Default::bendRange))
bendRange.setStart(normalizeBend(*value));
break; break;
case hash("hibend"): case hash("hibend"):
setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); if (auto value = readOpcode(opcode.value, Default::bendRange))
bendRange.setEnd(normalizeBend(*value));
break; break;
case hash("locc&"): case hash("locc&"):
if (opcode.parameters.back() > config::numCCs) if (opcode.parameters.back() > config::numCCs)
@ -937,7 +939,7 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
return false; return false;
} }
void sfz::Region::registerPitchWheel(int pitch) noexcept void sfz::Region::registerPitchWheel(float pitch) noexcept
{ {
if (bendRange.containsWithEnd(pitch)) if (bendRange.containsWithEnd(pitch))
pitchSwitched = true; pitchSwitched = true;

View file

@ -113,7 +113,7 @@ struct Region {
* *
* @param pitch * @param pitch
*/ */
void registerPitchWheel(int pitch) noexcept; void registerPitchWheel(float pitch) noexcept;
/** /**
* @brief Register a new aftertouch event. * @brief Register a new aftertouch event.
* *
@ -248,7 +248,7 @@ struct Region {
Range<float> velocityRange { Default::velocityRange }; // hivel and lovel Range<float> velocityRange { Default::velocityRange }; // hivel and lovel
// Region logic: MIDI conditions // Region logic: MIDI conditions
Range<int> bendRange { Default::bendRange }; // hibend and lobend Range<float> bendRange { Default::bendValueRange }; // hibend and lobend
CCMap<Range<float>> ccConditions { Default::ccValueRange }; CCMap<Range<float>> ccConditions { Default::ccValueRange };
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
absl::optional<uint8_t> keyswitch {}; // sw_last absl::optional<uint8_t> keyswitch {}; // sw_last

View file

@ -189,7 +189,7 @@ constexpr float normalizePercents(T percentValue)
*/ */
constexpr float normalizeBend(float bendValue) 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 namespace literals

View file

@ -778,16 +778,17 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
{ {
ASSERT(pitch <= 8192); ASSERT(pitch <= 8192);
ASSERT(pitch >= -8192); ASSERT(pitch >= -8192);
const auto normalizedPitch = normalizeBend(pitch);
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
resources.midiState.pitchBendEvent(delay, pitch); resources.midiState.pitchBendEvent(delay, normalizedPitch);
for (auto& region : regions) { for (auto& region : regions) {
region->registerPitchWheel(pitch); region->registerPitchWheel(normalizedPitch);
} }
for (auto& voice : voices) { for (auto& voice : voices) {
voice->registerPitchWheel(delay, pitch); voice->registerPitchWheel(delay, normalizedPitch);
} }
} }
void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept

View file

@ -85,12 +85,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
if (triggerType != TriggerType::CC) if (triggerType != TriggerType::CC)
baseGain *= region->getNoteGain(number, value); baseGain *= region->getNoteGain(number, value);
pitchBendEnvelope.setFunction([region](float pitchValue){ pitchBendEnvelope.setFunction([region](float bend){
const auto normalizedBend = normalizeBend(pitchValue); const auto bendInCents = bend > 0.0f ? bend * static_cast<float>(region->bendUp) : -bend * static_cast<float>(region->bendDown);
const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * static_cast<float>(region->bendUp) : -normalizedBend * static_cast<float>(region->bendDown);
return centsFactor(bendInCents); return centsFactor(bendInCents);
}); });
pitchBendEnvelope.reset(static_cast<float>(resources.midiState.getPitchBend())); pitchBendEnvelope.reset(resources.midiState.getPitchBend());
// Check that we can handle the number of filters; filters should be cleared here // Check that we can handle the number of filters; filters should be cleared here
ASSERT((filters.capacity() - filters.size()) >= region->filters.size()); 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) if (state == State::idle)
return; return;
pitchBendEnvelope.registerEvent(delay, static_cast<float>(pitch)); pitchBendEnvelope.registerEvent(delay, pitch * 8191.0f);
} }
void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept
@ -279,7 +278,6 @@ void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>); resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>);
applyGain<float>(*tempSpan, *modulationSpan); applyGain<float>(*tempSpan, *modulationSpan);
} }
DBG("Final gain: " << modulationSpan->back());
applyGain<float>(*modulationSpan, leftBuffer); applyGain<float>(*modulationSpan, leftBuffer);
// Crossfade envelopes // Crossfade envelopes
@ -292,7 +290,6 @@ void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind);
applyGain<float>(*tempSpan, *modulationSpan); applyGain<float>(*tempSpan, *modulationSpan);
} }
DBG("XF: " << modulationSpan->back());
applyGain<float>(*modulationSpan, leftBuffer); applyGain<float>(*modulationSpan, leftBuffer);
// Volume envelope // Volume envelope
@ -326,7 +323,6 @@ void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>); resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>);
applyGain<float>(*tempSpan, *modulationSpan); applyGain<float>(*tempSpan, *modulationSpan);
} }
DBG("Final gain: " << modulationSpan->back());
buffer.applyGain(*modulationSpan); buffer.applyGain(*modulationSpan);
@ -340,7 +336,6 @@ void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind);
applyGain<float>(*tempSpan, *modulationSpan); applyGain<float>(*tempSpan, *modulationSpan);
} }
DBG("XF: " << modulationSpan->back());
buffer.applyGain(*modulationSpan); buffer.applyGain(*modulationSpan);
// Volume envelope // Volume envelope
@ -374,7 +369,6 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>); resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>);
add<float>(*tempSpan, *modulationSpan); add<float>(*tempSpan, *modulationSpan);
} }
DBG("Pan: " << modulationSpan->back());
pan<float>(*modulationSpan, leftBuffer, rightBuffer); pan<float>(*modulationSpan, leftBuffer, rightBuffer);
} }

View file

@ -107,7 +107,7 @@ public:
* @param delay * @param delay
* @param pitch * @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 * @brief Register an aftertouch event; for now this does nothing
* *

View file

@ -37,20 +37,20 @@ TEST_CASE("[MidiState] Set and get CCs")
TEST_CASE("[MidiState] Set and get pitch bends") TEST_CASE("[MidiState] Set and get pitch bends")
{ {
sfz::MidiState state; sfz::MidiState state;
state.pitchBendEvent(0, 894); state.pitchBendEvent(0, 0.5f);
REQUIRE(state.getPitchBend() == 894); REQUIRE(state.getPitchBend() == 0.5f);
state.pitchBendEvent(0, 0); state.pitchBendEvent(0, 0.0f);
REQUIRE(state.getPitchBend() == 0); REQUIRE(state.getPitchBend() == 0.0f);
} }
TEST_CASE("[MidiState] Reset") TEST_CASE("[MidiState] Reset")
{ {
sfz::MidiState state; sfz::MidiState state;
state.pitchBendEvent(0, 894); state.pitchBendEvent(0, 0.7f);
state.noteOnEvent(0, 64, 24_norm); state.noteOnEvent(0, 64, 24_norm);
state.ccEvent(0, 123, 124_norm); state.ccEvent(0, 123, 124_norm);
state.reset(); state.reset();
REQUIRE(state.getPitchBend() == 0); REQUIRE(state.getPitchBend() == 0.0f);
REQUIRE(state.getNoteVelocity(64) == 0_norm); REQUIRE(state.getNoteVelocity(64) == 0_norm);
REQUIRE(state.getCCValue(123) == 0_norm); REQUIRE(state.getCCValue(123) == 0_norm);
} }
@ -58,12 +58,12 @@ TEST_CASE("[MidiState] Reset")
TEST_CASE("[MidiState] Reset all controllers") TEST_CASE("[MidiState] Reset all controllers")
{ {
sfz::MidiState state; sfz::MidiState state;
state.pitchBendEvent(20, 894); state.pitchBendEvent(20, 0.7f);
state.ccEvent(10, 122, 124_norm); state.ccEvent(10, 122, 124_norm);
REQUIRE(state.getPitchBend() == 894); REQUIRE(state.getPitchBend() == 0.7f);
REQUIRE(state.getCCValue(122) == 124_norm); REQUIRE(state.getCCValue(122) == 124_norm);
state.resetAllControllers(30); state.resetAllControllers(30);
REQUIRE(state.getPitchBend() == 0); REQUIRE(state.getPitchBend() == 0.0f);
REQUIRE(state.getCCValue(122) == 0_norm); REQUIRE(state.getCCValue(122) == 0_norm);
REQUIRE(state.getCCValue(4) == 0_norm); REQUIRE(state.getCCValue(4) == 0_norm);
} }

View file

@ -77,11 +77,11 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "hibend", "243" }); region.parseOpcode({ "hibend", "243" });
region.registerPitchWheel(0); region.registerPitchWheel(0);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerPitchWheel(56); region.registerPitchWheel(sfz::normalizeBend(56));
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerPitchWheel(243); region.registerPitchWheel(sfz::normalizeBend(243));
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerPitchWheel(245); region.registerPitchWheel(sfz::normalizeBend(245));
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }

View file

@ -223,19 +223,23 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("lobend, hibend") SECTION("lobend, hibend")
{ {
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192)); REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
region.parseOpcode({ "lobend", "4" }); region.parseOpcode({ "lobend", "400" });
REQUIRE(region.bendRange == sfz::Range<int>(4, 8192)); REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(400)));
REQUIRE(region.bendRange.getEnd() == 1.0_a);
region.parseOpcode({ "lobend", "-128" }); region.parseOpcode({ "lobend", "-128" });
REQUIRE(region.bendRange == sfz::Range<int>(-128, 8192)); REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(-128)));
REQUIRE(region.bendRange.getEnd() == 1.0_a);
region.parseOpcode({ "lobend", "-10000" }); region.parseOpcode({ "lobend", "-10000" });
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192)); REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
region.parseOpcode({ "hibend", "13" }); region.parseOpcode({ "hibend", "13" });
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 13)); REQUIRE(region.bendRange.getStart() == -1.0_a);
REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(13)));
region.parseOpcode({ "hibend", "-1" }); region.parseOpcode({ "hibend", "-1" });
REQUIRE(region.bendRange == sfz::Range<int>(-8192, -1)); REQUIRE(region.bendRange.getStart() == -1.0_a);
REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(-1)));
region.parseOpcode({ "hibend", "10000" }); region.parseOpcode({ "hibend", "10000" });
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192)); REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
} }
SECTION("locc, hicc") SECTION("locc, hicc")