Merge pull request #854 from paulfd/extended-ccs-2

Store extended CCs in the midistate
This commit is contained in:
Paul Ferrand 2021-04-25 00:31:49 +02:00 committed by GitHub
commit f5bed98356
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 218 additions and 28 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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