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
constexpr Range<uint8_t> midi7Range { 0, 127 };
constexpr Range<float> normalizedRange { 0.0f, 1.0f };
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
constexpr float zeroModifier { 0.0f };
// Wavetable oscillator
@ -79,7 +80,8 @@ namespace Default
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
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 SfzVelocityOverride velocityOverride { SfzVelocityOverride::current };
@ -107,7 +109,6 @@ namespace Default
constexpr float pan { 0.0 };
constexpr Range<float> panRange { -100.0, 100.0 };
constexpr Range<float> panCCRange { -200.0, 200.0 };
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
constexpr float position { 0.0 };
constexpr Range<float> positionRange { -100.0, 100.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.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

View file

@ -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 };

View file

@ -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;

View file

@ -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<float> velocityRange { Default::velocityRange }; // hivel and lovel
// 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 };
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
absl::optional<uint8_t> keyswitch {}; // sw_last

View file

@ -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

View file

@ -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

View file

@ -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<float>(region->bendUp) : -normalizedBend * static_cast<float>(region->bendDown);
pitchBendEnvelope.setFunction([region](float bend){
const auto bendInCents = bend > 0.0f ? bend * static_cast<float>(region->bendUp) : -bend * static_cast<float>(region->bendDown);
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
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<float>(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<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>);
applyGain<float>(*tempSpan, *modulationSpan);
}
DBG("Final gain: " << modulationSpan->back());
applyGain<float>(*modulationSpan, leftBuffer);
// Crossfade envelopes
@ -292,7 +290,6 @@ void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind);
applyGain<float>(*tempSpan, *modulationSpan);
}
DBG("XF: " << modulationSpan->back());
applyGain<float>(*modulationSpan, leftBuffer);
// Volume envelope
@ -326,7 +323,6 @@ void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>);
applyGain<float>(*tempSpan, *modulationSpan);
}
DBG("Final gain: " << modulationSpan->back());
buffer.applyGain(*modulationSpan);
@ -340,7 +336,6 @@ void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind);
applyGain<float>(*tempSpan, *modulationSpan);
}
DBG("XF: " << modulationSpan->back());
buffer.applyGain(*modulationSpan);
// Volume envelope
@ -374,7 +369,6 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier<float>);
add<float>(*tempSpan, *modulationSpan);
}
DBG("Pan: " << modulationSpan->back());
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
}

View file

@ -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
*

View file

@ -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);
}

View file

@ -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());
}

View file

@ -223,19 +223,23 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("lobend, hibend")
{
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192));
region.parseOpcode({ "lobend", "4" });
REQUIRE(region.bendRange == sfz::Range<int>(4, 8192));
REQUIRE(region.bendRange == sfz::Range<float>(-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<int>(-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<int>(-8192, 8192));
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
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" });
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" });
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192));
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
}
SECTION("locc, hicc")