Merge pull request #854 from paulfd/extended-ccs-2
Store extended CCs in the midistate
This commit is contained in:
commit
f5bed98356
8 changed files with 218 additions and 28 deletions
|
|
@ -69,7 +69,7 @@ bool Layer::registerNoteOn(int noteNumber, float velocity, float randValue) noex
|
|||
return false;
|
||||
|
||||
if (region.velocityOverride == VelocityOverride::previous)
|
||||
velocity = midiState_.getLastVelocity();
|
||||
velocity = midiState_.getVelocityOverride();
|
||||
|
||||
const bool velOk = region.velocityRange.containsWithEnd(velocity);
|
||||
const bool randOk = region.randRange.contains(randValue) || (randValue >= 1.0f && region.randRange.isValid() && region.randRange.getEnd() >= 1.0f);
|
||||
|
|
@ -131,8 +131,6 @@ bool Layer::registerNoteOff(int noteNumber, float velocity, float randValue) noe
|
|||
|
||||
bool Layer::registerCC(int ccNumber, float ccValue) noexcept
|
||||
{
|
||||
ASSERT(ccValue >= 0.0f && ccValue <= 1.0f);
|
||||
|
||||
const Region& region = region_;
|
||||
|
||||
if (ccNumber == region.sustainCC)
|
||||
|
|
|
|||
|
|
@ -19,11 +19,19 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
|
|||
ASSERT(velocity >= 0 && velocity <= 1.0);
|
||||
|
||||
if (noteNumber >= 0 && noteNumber < 128) {
|
||||
velocityOverride = lastNoteVelocities[lastNotePlayed];
|
||||
lastNoteVelocities[noteNumber] = velocity;
|
||||
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||
lastNotePlayed = noteNumber;
|
||||
activeNotes++;
|
||||
noteStates[noteNumber] = true;
|
||||
ccEvent(delay, ExtendedCCs::noteOnVelocity, velocity);
|
||||
ccEvent(delay, ExtendedCCs::keyboardNoteNumber, normalize7Bits(noteNumber));
|
||||
ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator));
|
||||
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
||||
ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f);
|
||||
activeNotes++;
|
||||
|
||||
ccEvent(delay, ExtendedCCs::alternate, alternate);
|
||||
alternate = alternate == 0.0f ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +45,10 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe
|
|||
UNUSED(velocity);
|
||||
if (noteNumber >= 0 && noteNumber < 128) {
|
||||
noteOffTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||
ccEvent(delay, ExtendedCCs::noteOffVelocity, velocity);
|
||||
ccEvent(delay, ExtendedCCs::keyboardNoteNumber, normalize7Bits(noteNumber));
|
||||
ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator));
|
||||
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
||||
if (activeNotes > 0)
|
||||
activeNotes--;
|
||||
noteStates[noteNumber] = false;
|
||||
|
|
@ -121,9 +133,9 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
|
|||
return lastNoteVelocities[noteNumber];
|
||||
}
|
||||
|
||||
float sfz::MidiState::getLastVelocity() const noexcept
|
||||
float sfz::MidiState::getVelocityOverride() const noexcept
|
||||
{
|
||||
return lastNoteVelocities[lastNotePlayed];
|
||||
return velocityOverride;
|
||||
}
|
||||
|
||||
void sfz::MidiState::insertEventInVector(EventVector& events, int delay, float value)
|
||||
|
|
@ -172,7 +184,7 @@ float sfz::MidiState::getPolyAftertouch(int noteNumber) const noexcept
|
|||
{
|
||||
if (noteNumber < 0 || noteNumber > 127)
|
||||
return 0.0f;
|
||||
|
||||
|
||||
ASSERT(polyAftertouchEvents[noteNumber].size() > 0);
|
||||
return polyAftertouchEvents[noteNumber].back().value;
|
||||
}
|
||||
|
|
@ -191,7 +203,7 @@ float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
|||
void sfz::MidiState::reset() noexcept
|
||||
{
|
||||
for (auto& velocity: lastNoteVelocities)
|
||||
velocity = 0;
|
||||
velocity = 0.0f;
|
||||
|
||||
auto clearEvents = [] (EventVector& events) {
|
||||
events.clear();
|
||||
|
|
@ -207,6 +219,7 @@ void sfz::MidiState::reset() noexcept
|
|||
clearEvents(pitchEvents);
|
||||
clearEvents(channelAftertouchEvents);
|
||||
|
||||
velocityOverride = 0.0f;
|
||||
activeNotes = 0;
|
||||
internalClock = 0;
|
||||
lastNotePlayed = 0;
|
||||
|
|
|
|||
|
|
@ -86,11 +86,11 @@ public:
|
|||
float getNoteVelocity(int noteNumber) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the velocity of the last note played
|
||||
* @brief Get the velocity override value (sw_vel in SFZ)
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float getLastVelocity() const noexcept;
|
||||
float getVelocityOverride() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Register a pitch bend event
|
||||
|
|
@ -190,13 +190,6 @@ public:
|
|||
const EventVector& getPitchEvents() const noexcept;
|
||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the alternate state value, for extended CC 137
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float getAlternateState() const noexcept { return alternate; }
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
|
@ -236,6 +229,11 @@ private:
|
|||
*/
|
||||
MidiNoteArray<float> lastNoteVelocities;
|
||||
|
||||
/**
|
||||
* @brief Velocity override value (sw_vel in SFZ)
|
||||
*/
|
||||
float velocityOverride;
|
||||
|
||||
/**
|
||||
* @brief Last note played
|
||||
*/
|
||||
|
|
@ -272,5 +270,7 @@ private:
|
|||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
float alternate { 0.0f };
|
||||
unsigned internalClock { 0 };
|
||||
fast_real_distribution<float> unipolarDist { 0.0f, 1.0f };
|
||||
fast_real_distribution<float> bipolarDist { -1.0f, 1.0f };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1073,8 +1073,8 @@ void Synth::hdNoteOn(int delay, int noteNumber, float normalizedVelocity) noexce
|
|||
ASSERT(noteNumber >= 0);
|
||||
Impl& impl = *impl_;
|
||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||
impl.noteOnDispatch(delay, noteNumber, normalizedVelocity);
|
||||
impl.resources_.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity);
|
||||
impl.noteOnDispatch(delay, noteNumber, normalizedVelocity);
|
||||
}
|
||||
|
||||
void Synth::noteOff(int delay, int noteNumber, int velocity) noexcept
|
||||
|
|
@ -1087,20 +1087,19 @@ void Synth::hdNoteOff(int delay, int noteNumber, float normalizedVelocity) noexc
|
|||
{
|
||||
ASSERT(noteNumber < 128);
|
||||
ASSERT(noteNumber >= 0);
|
||||
UNUSED(normalizedVelocity);
|
||||
Impl& impl = *impl_;
|
||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||
|
||||
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
|
||||
// way in sfz to specify that a release trigger should NOT use the note-on velocity?
|
||||
// auto replacedVelocity = (velocity == 0 ? getNoteVelocity(noteNumber) : velocity);
|
||||
impl.resources_.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity);
|
||||
const auto replacedVelocity = impl.resources_.midiState.getNoteVelocity(noteNumber);
|
||||
|
||||
for (auto& voice : impl.voiceManager_)
|
||||
voice.registerNoteOff(delay, noteNumber, replacedVelocity);
|
||||
|
||||
impl.noteOffDispatch(delay, noteNumber, replacedVelocity);
|
||||
impl.resources_.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity);
|
||||
}
|
||||
|
||||
void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept
|
||||
|
|
|
|||
|
|
@ -1454,6 +1454,16 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
|
||||
} break;
|
||||
|
||||
MATCH("/voice&/remaining_delay", "") {
|
||||
GET_VOICE_OR_BREAK(indices[0])
|
||||
client.receive<'i'>(delay, path, voice.getRemainingDelay());
|
||||
} break;
|
||||
|
||||
MATCH("/voice&/source_position", "") {
|
||||
GET_VOICE_OR_BREAK(indices[0])
|
||||
client.receive<'i'>(delay, path, voice.getSourcePosition());
|
||||
} break;
|
||||
|
||||
#undef MATCH
|
||||
// TODO...
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,8 +304,6 @@ struct Voice::Impl
|
|||
PowerFollower powerFollower_;
|
||||
|
||||
ExtendedCCValues extendedCCValues_;
|
||||
fast_real_distribution<float> unipolarDist { 0.0f, 1.0f };
|
||||
fast_real_distribution<float> bipolarDist { -1.0f, 1.0f };
|
||||
};
|
||||
|
||||
Voice::Voice(int voiceNumber, Resources& resources)
|
||||
|
|
@ -389,10 +387,10 @@ const ExtendedCCValues& Voice::getExtendedCCValues() const noexcept
|
|||
|
||||
void Voice::Impl::updateExtendedCCValues() noexcept
|
||||
{
|
||||
extendedCCValues_.unipolar = unipolarDist(Random::randomGenerator);
|
||||
extendedCCValues_.bipolar = bipolarDist(Random::randomGenerator);
|
||||
extendedCCValues_.alternate = resources_.midiState.getAlternateState();
|
||||
extendedCCValues_.noteGate = resources_.midiState.getActiveNotes() > 0 ? 1.0f : 0.0f;
|
||||
extendedCCValues_.unipolar = resources_.midiState.getCCValue(ExtendedCCs::unipolarRandom);
|
||||
extendedCCValues_.bipolar = resources_.midiState.getCCValue(ExtendedCCs::bipolarRandom);
|
||||
extendedCCValues_.alternate = resources_.midiState.getCCValue(ExtendedCCs::alternate);
|
||||
extendedCCValues_.noteGate = resources_.midiState.getCCValue(ExtendedCCs::keyboardNoteGate);
|
||||
}
|
||||
|
||||
bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept
|
||||
|
|
@ -410,7 +408,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
impl.triggerEvent_.number = region.pitchKeycenter;
|
||||
|
||||
if (region.velocityOverride == VelocityOverride::previous)
|
||||
impl.triggerEvent_.value = resources.midiState.getLastVelocity();
|
||||
impl.triggerEvent_.value = resources.midiState.getVelocityOverride();
|
||||
|
||||
if (region.disabled()) {
|
||||
impl.switchState(State::cleanMeUp);
|
||||
|
|
@ -2000,6 +1998,18 @@ const Region* Voice::getRegion() const noexcept
|
|||
return impl.region_;
|
||||
}
|
||||
|
||||
int Voice::getRemainingDelay() const noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
return impl.initialDelay_;
|
||||
}
|
||||
|
||||
int Voice::getSourcePosition() const noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
return impl.sourcePosition_;
|
||||
}
|
||||
|
||||
LFO* Voice::getLFO(size_t index)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
|
|||
|
|
@ -416,6 +416,19 @@ public:
|
|||
*/
|
||||
const ExtendedCCValues& getExtendedCCValues() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the remaining delay before the sample starts, in samples
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int getRemainingDelay() const noexcept;
|
||||
/**
|
||||
* @brief Get the current position in the source sample
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int getSourcePosition() const noexcept;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Check if the voice already belongs to a sister ring
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@
|
|||
*/
|
||||
|
||||
#include "sfizz/MidiState.h"
|
||||
#include "sfizz/Synth.h"
|
||||
#include "sfizz/SfzHelpers.h"
|
||||
#include "catch2/catch.hpp"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "TestHelpers.h"
|
||||
using namespace Catch::literals;
|
||||
using namespace sfz::literals;
|
||||
|
||||
|
|
@ -88,5 +90,150 @@ TEST_CASE("[MidiState] Last note velocity")
|
|||
sfz::MidiState state;
|
||||
state.noteOnEvent(0, 62, 64_norm);
|
||||
state.noteOnEvent(0, 60, 10_norm);
|
||||
REQUIRE(state.getLastVelocity() == 10_norm);
|
||||
REQUIRE(state.getVelocityOverride() == 64_norm);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("[CC] Extended CCs on offset and delay")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
std::vector<std::string> messageList;
|
||||
sfz::Client client(&messageList);
|
||||
client.setReceiveCallback(&simpleMessageReceiver);
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.setSampleRate(48000);
|
||||
|
||||
SECTION("CC131 - Note on velocity")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"(
|
||||
<region> key=60 delay_cc131=1 sample=kick.wav
|
||||
<region> key=61 offset_cc131=100 sample=snare.wav
|
||||
)");
|
||||
synth.hdNoteOn(0, 60, 0.0f);
|
||||
synth.hdNoteOn(0, 60, 0.5f);
|
||||
synth.hdNoteOn(0, 61, 0.0f);
|
||||
synth.hdNoteOn(0, 61, 0.5f);
|
||||
synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice3/source_position", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/voice0/remaining_delay,i : { 0 }",
|
||||
"/voice1/remaining_delay,i : { 24000 }",
|
||||
"/voice2/source_position,i : { 0 }",
|
||||
"/voice3/source_position,i : { 50 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
SECTION("CC132 - Note off velocity")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"(
|
||||
<region> key=60 sample=*silence
|
||||
<region> key=60 delay_cc132=1 sample=kick.wav trigger=release
|
||||
<region> key=61 sample=snare.wav
|
||||
<region> key=61 offset_cc132=100 sample=snare.wav trigger=release
|
||||
)");
|
||||
synth.hdNoteOn(0, 60, 1.0f);
|
||||
synth.hdNoteOff(1, 60, 0.0f);
|
||||
synth.hdNoteOn(2, 60, 1.0f);
|
||||
synth.hdNoteOff(3, 60, 0.5f);
|
||||
synth.hdNoteOn(4, 61, 1.0f);
|
||||
synth.hdNoteOff(5, 61, 0.0f);
|
||||
synth.hdNoteOn(6, 61, 1.0f);
|
||||
synth.hdNoteOff(7, 61, 0.5f);
|
||||
synth.dispatchMessage(client, 10, "/voice1/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 10, "/voice3/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 10, "/voice5/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 10, "/voice7/source_position", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/voice1/remaining_delay,i : { 1 }", // 1 is the note off event delay
|
||||
"/voice3/remaining_delay,i : { 24003 }", // 3 is the note off event delay
|
||||
"/voice5/source_position,i : { 0 }",
|
||||
"/voice7/source_position,i : { 50 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
SECTION("CC133 - Note number")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"(
|
||||
<region> delay_cc133=1 offset_cc133=100 sample=kick.wav
|
||||
)");
|
||||
synth.hdNoteOn(0, 0, 1.0f);
|
||||
synth.hdNoteOn(0, 127, 1.0f);
|
||||
synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/voice0/remaining_delay,i : { 0 }",
|
||||
"/voice1/remaining_delay,i : { 48000 }",
|
||||
"/voice0/source_position,i : { 0 }",
|
||||
"/voice1/source_position,i : { 100 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
SECTION("CC134 - Note gate")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"(
|
||||
<region> delay_cc134=1 offset_cc134=100 sample=kick.wav
|
||||
)");
|
||||
synth.hdNoteOn(0, 60, 1.0f);
|
||||
synth.hdNoteOn(0, 127, 1.0f);
|
||||
synth.hdNoteOff(1, 60, 1.0f);
|
||||
synth.hdNoteOff(1, 127, 1.0f);
|
||||
synth.hdNoteOn(2, 60, 1.0f);
|
||||
synth.hdNoteOn(2, 127, 1.0f);
|
||||
synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice2/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice3/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice3/source_position", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/voice0/remaining_delay,i : { 0 }",
|
||||
"/voice1/remaining_delay,i : { 48000 }",
|
||||
"/voice2/remaining_delay,i : { 2 }", // 2 is the event delay
|
||||
"/voice3/remaining_delay,i : { 48002 }", // 2 is the event delay
|
||||
"/voice0/source_position,i : { 0 }",
|
||||
"/voice1/source_position,i : { 100 }",
|
||||
"/voice2/source_position,i : { 0 }",
|
||||
"/voice3/source_position,i : { 100 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
SECTION("CC137 - Alternate")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"(
|
||||
<region> delay_cc137=1 offset_cc137=100 sample=kick.wav
|
||||
)");
|
||||
synth.hdNoteOn(0, 60, 1.0f);
|
||||
synth.hdNoteOn(0, 127, 1.0f);
|
||||
synth.hdNoteOn(0, 54, 1.0f);
|
||||
synth.hdNoteOn(0, 12, 1.0f);
|
||||
synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice2/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice3/remaining_delay", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/voice3/source_position", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/voice0/remaining_delay,i : { 0 }",
|
||||
"/voice1/remaining_delay,i : { 48000 }",
|
||||
"/voice2/remaining_delay,i : { 0 }",
|
||||
"/voice3/remaining_delay,i : { 48000 }",
|
||||
"/voice0/source_position,i : { 0 }",
|
||||
"/voice1/source_position,i : { 100 }",
|
||||
"/voice2/source_position,i : { 0 }",
|
||||
"/voice3/source_position,i : { 100 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue