Store pitch bend internally as floats
This commit is contained in:
parent
bc4d88b55a
commit
922cbde6c5
12 changed files with 66 additions and 49 deletions
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue