Support polyphonic aftertouch basics
This commit is contained in:
parent
9c3b31030f
commit
993620252b
31 changed files with 430 additions and 24 deletions
|
|
@ -86,7 +86,7 @@ int process(jack_nframes_t numFrames, void* arg)
|
|||
synth->noteOn(event.time, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::polyphonicPressure:
|
||||
// Not implemented
|
||||
synth->polyAftertouch(event.time, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::controlChange:
|
||||
synth->cc(event.time, event.buffer[1], event.buffer[2]);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ SFIZZ_SOURCES = \
|
|||
src/sfizz/modulations/ModMatrix.cpp \
|
||||
src/sfizz/modulations/sources/ADSREnvelope.cpp \
|
||||
src/sfizz/modulations/sources/ChannelAftertouch.cpp \
|
||||
src/sfizz/modulations/sources/PolyAftertouch.cpp \
|
||||
src/sfizz/modulations/sources/Controller.cpp \
|
||||
src/sfizz/modulations/sources/FlexEnvelope.cpp \
|
||||
src/sfizz/modulations/sources/LFO.cpp \
|
||||
|
|
|
|||
|
|
@ -640,6 +640,12 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
|
|||
(int)ev->time.frames,
|
||||
msg[1]);
|
||||
break;
|
||||
case LV2_MIDI_MSG_NOTE_PRESSURE:
|
||||
sfizz_send_poly_aftertouch(self->synth,
|
||||
(int)ev->time.frames,
|
||||
(int)msg[1],
|
||||
msg[2]);
|
||||
break;
|
||||
case LV2_MIDI_MSG_BENDER:
|
||||
sfizz_send_pitch_wheel(self->synth,
|
||||
(int)ev->time.frames,
|
||||
|
|
|
|||
|
|
@ -465,9 +465,13 @@ void SfizzVstProcessor::processEvents(Vst::IEventList& events)
|
|||
_noteEventsCurrentCycle[pitch] = 0.0f;
|
||||
break;
|
||||
}
|
||||
// case Vst::Event::kPolyPressureEvent:
|
||||
// synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure));
|
||||
// break;
|
||||
case Vst::Event::kPolyPressureEvent: {
|
||||
int pitch = e.polyPressure.pitch;
|
||||
if (pitch < 0 || pitch >= 128)
|
||||
break;
|
||||
synth.polyAftertouch(e.sampleOffset, pitch, convertVelocityFromFloat(e.polyPressure.pressure));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ set(SFIZZ_HEADERS
|
|||
sfizz/modulations/ModGenerator.h
|
||||
sfizz/modulations/sources/ADSREnvelope.h
|
||||
sfizz/modulations/sources/ChannelAftertouch.h
|
||||
sfizz/modulations/sources/PolyAftertouch.h
|
||||
sfizz/modulations/sources/Controller.h
|
||||
sfizz/modulations/sources/FlexEnvelope.h
|
||||
sfizz/modulations/sources/LFO.h
|
||||
|
|
@ -166,6 +167,7 @@ set(SFIZZ_SOURCES
|
|||
sfizz/modulations/ModMatrix.cpp
|
||||
sfizz/modulations/sources/ADSREnvelope.cpp
|
||||
sfizz/modulations/sources/ChannelAftertouch.cpp
|
||||
sfizz/modulations/sources/PolyAftertouch.cpp
|
||||
sfizz/modulations/sources/Controller.cpp
|
||||
sfizz/modulations/sources/FlexEnvelope.cpp
|
||||
sfizz/modulations/sources/LFO.cpp
|
||||
|
|
|
|||
20
src/sfizz.h
20
src/sfizz.h
|
|
@ -430,7 +430,7 @@ SFIZZ_EXPORTED_API void sfizz_automate_hdcc(sfizz_synth_t* synth, int delay, int
|
|||
SFIZZ_EXPORTED_API void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int pitch);
|
||||
|
||||
/**
|
||||
* @brief Send an aftertouch event. (CURRENTLY UNIMPLEMENTED)
|
||||
* @brief Send an aftertouch event.
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param synth The synth.
|
||||
|
|
@ -443,6 +443,22 @@ SFIZZ_EXPORTED_API void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay,
|
|||
*/
|
||||
SFIZZ_EXPORTED_API void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, char aftertouch);
|
||||
|
||||
/**
|
||||
* @brief Send a polyphonic aftertouch event. This feature is experimental and needs more testing
|
||||
* in the internal engine.
|
||||
* @since 0.6.0
|
||||
*
|
||||
* @param synth The synth.
|
||||
* @param delay The delay at which the event occurs; this should be lower
|
||||
* than the size of the block in the next call to renderBlock().
|
||||
* @param note_number The note number.
|
||||
* @param aftertouch The aftertouch value.
|
||||
*
|
||||
* @par Thread-safety constraints
|
||||
* - @b RT: the function must be invoked from the Real-time thread
|
||||
*/
|
||||
SFIZZ_EXPORTED_API void sfizz_send_poly_aftertouch(sfizz_synth_t* synth, int delay, int note_number, char aftertouch);
|
||||
|
||||
/**
|
||||
* @brief Send a tempo event.
|
||||
* @since 0.2.0
|
||||
|
|
@ -823,7 +839,7 @@ SFIZZ_EXPORTED_API void sfizz_clear_external_definitions(sfizz_synth_t* synth);
|
|||
* @brief Index out of bound error for the requested CC/key label.
|
||||
* @since 0.4.0
|
||||
*/
|
||||
#define SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX -1
|
||||
#define SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX (-1)
|
||||
|
||||
/**
|
||||
* @brief Get the number of key labels registered in the current sfz file.
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ public:
|
|||
void pitchWheel(int delay, int pitch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Send a aftertouch event to the synth. (CURRENTLY UNIMPLEMENTED)
|
||||
* @brief Send an aftertouch event to the synth.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
|
|
@ -438,6 +438,22 @@ public:
|
|||
*/
|
||||
void aftertouch(int delay, uint8_t aftertouch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Send a polyphonic aftertouch event to the synth. This feature is
|
||||
* experimental and needs more testing in the internal engine.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*
|
||||
* @param delay the delay at which the event occurs; this should be lower
|
||||
* than the size of the block in the next call to renderBlock().
|
||||
* @param noteNumber the note number.
|
||||
* @param aftertouch the aftertouch value.
|
||||
*
|
||||
* @par Thread-safety constraints
|
||||
* - @b RT: the function must be invoked from the Real-time thread
|
||||
*/
|
||||
void polyAftertouch(int delay, int noteNumber, uint8_t aftertouch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Send a tempo event to the synth.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ FloatSpec loVel { 0, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
|
|||
FloatSpec hiVel { 127, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
|
||||
FloatSpec loChannelAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
||||
FloatSpec hiChannelAftertouch { 127, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
||||
FloatSpec loPolyAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
||||
FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
||||
FloatSpec loBend { -8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
|
||||
FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
|
||||
FloatSpec loNormalized { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ namespace Default
|
|||
extern const OpcodeSpec<float> hiBipolar;
|
||||
extern const OpcodeSpec<float> loChannelAftertouch;
|
||||
extern const OpcodeSpec<float> hiChannelAftertouch;
|
||||
extern const OpcodeSpec<float> loPolyAftertouch;
|
||||
extern const OpcodeSpec<float> hiPolyAftertouch;
|
||||
extern const OpcodeSpec<uint16_t> ccNumber;
|
||||
extern const OpcodeSpec<uint8_t> curveCC;
|
||||
extern const OpcodeSpec<uint16_t> smoothCC;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,10 @@ bool Layer::registerNoteOn(int noteNumber, float velocity, float randValue) noex
|
|||
((sequenceCounter_++ % region.sequenceLength) == region.sequencePosition - 1);
|
||||
}
|
||||
|
||||
if (!isSwitchedOn())
|
||||
const bool polyAftertouchActive =
|
||||
region.polyAftertouchRange.containsWithEnd(midiState_.getPolyAftertouch(noteNumber));
|
||||
|
||||
if (!isSwitchedOn() || !polyAftertouchActive)
|
||||
return false;
|
||||
|
||||
if (!region.triggerOnNote)
|
||||
|
|
@ -83,7 +86,10 @@ bool Layer::registerNoteOff(int noteNumber, float velocity, float randValue) noe
|
|||
|
||||
const Region& region = region_;
|
||||
|
||||
if (!isSwitchedOn())
|
||||
const bool polyAftertouchActive =
|
||||
region.polyAftertouchRange.containsWithEnd(midiState_.getPolyAftertouch(noteNumber));
|
||||
|
||||
if (!isSwitchedOn() || !polyAftertouchActive)
|
||||
return false;
|
||||
|
||||
if (!region.triggerOnNote)
|
||||
|
|
|
|||
|
|
@ -73,8 +73,11 @@ void sfz::MidiState::flushEvents() noexcept
|
|||
events.resize(1);
|
||||
};
|
||||
|
||||
for (auto& ccEvents : cc)
|
||||
flushEventVector(ccEvents);
|
||||
for (auto& events : ccEvents)
|
||||
flushEventVector(events);
|
||||
|
||||
for (auto& events: polyAftertouchEvents)
|
||||
flushEventVector(events);
|
||||
|
||||
flushEventVector(pitchEvents);
|
||||
flushEventVector(channelAftertouchEvents);
|
||||
|
|
@ -88,8 +91,11 @@ void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
|
|||
events.reserve(samplesPerBlock);
|
||||
};
|
||||
this->samplesPerBlock = samplesPerBlock;
|
||||
for (auto& ccEvents : cc)
|
||||
updateEventBufferSize(ccEvents);
|
||||
for (auto& events: ccEvents)
|
||||
updateEventBufferSize(events);
|
||||
|
||||
for (auto& events: polyAftertouchEvents)
|
||||
updateEventBufferSize(events);
|
||||
|
||||
updateEventBufferSize(pitchEvents);
|
||||
updateEventBufferSize(channelAftertouchEvents);
|
||||
|
|
@ -147,21 +153,39 @@ void sfz::MidiState::channelAftertouchEvent(int delay, float aftertouch) noexcep
|
|||
insertEventInVector(channelAftertouchEvents, delay, aftertouch);
|
||||
}
|
||||
|
||||
void sfz::MidiState::polyAftertouchEvent(int delay, int noteNumber, float aftertouch) noexcept
|
||||
{
|
||||
ASSERT(aftertouch >= 0.0f && aftertouch <= 1.0f);
|
||||
if (noteNumber < 0 || noteNumber >= static_cast<int>(polyAftertouchEvents.size()))
|
||||
return;
|
||||
|
||||
insertEventInVector(polyAftertouchEvents[noteNumber], delay, aftertouch);
|
||||
}
|
||||
|
||||
float sfz::MidiState::getChannelAftertouch() const noexcept
|
||||
{
|
||||
ASSERT(channelAftertouchEvents.size() > 0);
|
||||
return channelAftertouchEvents.back().value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
|
||||
{
|
||||
insertEventInVector(cc[ccNumber], delay, ccValue);
|
||||
insertEventInVector(ccEvents[ccNumber], delay, ccValue);
|
||||
}
|
||||
|
||||
float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
||||
{
|
||||
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
||||
return cc[ccNumber].back().value;
|
||||
return ccEvents[ccNumber].back().value;
|
||||
}
|
||||
|
||||
void sfz::MidiState::reset() noexcept
|
||||
|
|
@ -174,8 +198,11 @@ void sfz::MidiState::reset() noexcept
|
|||
events.push_back({ 0, 0.0f });
|
||||
};
|
||||
|
||||
for (auto& ccEvents : cc)
|
||||
clearEvents(ccEvents);
|
||||
for (auto& events : ccEvents)
|
||||
clearEvents(events);
|
||||
|
||||
for (auto& events : polyAftertouchEvents)
|
||||
clearEvents(events);
|
||||
|
||||
clearEvents(pitchEvents);
|
||||
clearEvents(channelAftertouchEvents);
|
||||
|
|
@ -201,7 +228,7 @@ const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
|||
if (ccIdx < 0 || ccIdx >= config::numCCs)
|
||||
return nullEvent;
|
||||
|
||||
return cc[ccIdx];
|
||||
return ccEvents[ccIdx];
|
||||
}
|
||||
|
||||
const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
|
||||
|
|
@ -213,3 +240,11 @@ const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexc
|
|||
{
|
||||
return channelAftertouchEvents;
|
||||
}
|
||||
|
||||
const sfz::EventVector& sfz::MidiState::getPolyAftertouchEvents(int noteNumber) const noexcept
|
||||
{
|
||||
if (noteNumber < 0 || noteNumber > 127)
|
||||
return nullEvent;
|
||||
|
||||
return polyAftertouchEvents[noteNumber];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,13 @@ public:
|
|||
*/
|
||||
void channelAftertouchEvent(int delay, float aftertouch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Register a channel aftertouch event
|
||||
*
|
||||
* @param aftertouch
|
||||
*/
|
||||
void polyAftertouchEvent(int delay, int noteNumber, float aftertouch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the channel aftertouch status
|
||||
|
||||
|
|
@ -120,6 +127,13 @@ public:
|
|||
*/
|
||||
float getChannelAftertouch() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the polyphonic aftertouch status
|
||||
|
||||
* @return int
|
||||
*/
|
||||
float getPolyAftertouch(int noteNumber) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Register a CC event
|
||||
*
|
||||
|
|
@ -172,6 +186,7 @@ public:
|
|||
void resetAllControllers(int delay) noexcept;
|
||||
|
||||
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
||||
const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept;
|
||||
const EventVector& getPitchEvents() const noexcept;
|
||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||
|
||||
|
|
@ -230,7 +245,7 @@ private:
|
|||
* @brief Current known values for the CCs.
|
||||
*
|
||||
*/
|
||||
std::array<EventVector, config::numCCs> cc;
|
||||
std::array<EventVector, config::numCCs> ccEvents;
|
||||
|
||||
/**
|
||||
* @brief Null event
|
||||
|
|
@ -248,6 +263,11 @@ private:
|
|||
*/
|
||||
EventVector channelAftertouchEvents;
|
||||
|
||||
/**
|
||||
* @brief Polyphonic aftertouch status.
|
||||
*/
|
||||
std::array<EventVector, 128> polyAftertouchEvents;
|
||||
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
float alternate { 0.0f };
|
||||
|
|
|
|||
|
|
@ -348,6 +348,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
case hash("hichanaft"):
|
||||
aftertouchRange.setEnd(opcode.read(Default::hiChannelAftertouch));
|
||||
break;
|
||||
case hash("lopolyaft"):
|
||||
polyAftertouchRange.setStart(opcode.read(Default::loPolyAftertouch));
|
||||
break;
|
||||
case hash("hipolyaft"):
|
||||
polyAftertouchRange.setEnd(opcode.read(Default::hiPolyAftertouch));
|
||||
break;
|
||||
case hash("lobpm"):
|
||||
bpmRange.setStart(opcode.read(Default::loBPM));
|
||||
break;
|
||||
|
|
@ -926,7 +932,9 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo)
|
|||
= opcode.read(depthModSpec);
|
||||
break;
|
||||
case_any_lfo("depthpolyaft"): // NOLINT bugprone-branch-clone
|
||||
// TODO(jpc) LFO v1
|
||||
getOrCreateConnection(sourceKey, targetKey).sourceDepthMod = sourceDepthKey;
|
||||
getOrCreateConnection(ModKey::createNXYZ(ModId::PolyAftertouch, id), sourceDepthKey).sourceDepth
|
||||
= opcode.read(depthModSpec);
|
||||
break;
|
||||
case_any_lfo("fade"):
|
||||
lfo.fade = opcode.read(Default::lfoFade);
|
||||
|
|
@ -942,7 +950,8 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo)
|
|||
= opcode.read(Default::lfoFreqMod);
|
||||
break;
|
||||
case_any_lfo("freqpolyaft"): // NOLINT bugprone-branch-clone
|
||||
// TODO(jpc) LFO v1
|
||||
getOrCreateConnection(ModKey::createNXYZ(ModId::PolyAftertouch, id), lfo.freqKey).sourceDepth
|
||||
= opcode.read(Default::lfoFreqMod);
|
||||
break;
|
||||
|
||||
// sfizz extension
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ struct Region {
|
|||
|
||||
// Region logic: internal conditions
|
||||
UncheckedRange<float> aftertouchRange { Default::loChannelAftertouch, Default::hiChannelAftertouch }; // hichanaft and lochanaft
|
||||
UncheckedRange<float> polyAftertouchRange { Default::loPolyAftertouch, Default::hiPolyAftertouch }; // hipolyaft and lopolyaft
|
||||
UncheckedRange<float> bpmRange { Default::loBPM, Default::hiBPM }; // hibpm and lobpm
|
||||
UncheckedRange<float> randRange { Default::loNormalized, Default::hiNormalized }; // hirand and lorand
|
||||
uint8_t sequenceLength { Default::sequence }; // seq_length
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ Synth::Impl::Impl()
|
|||
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
||||
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
||||
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState));
|
||||
genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, resources_.midiState));
|
||||
}
|
||||
|
||||
Synth::Impl::~Impl()
|
||||
|
|
@ -1337,6 +1338,26 @@ void Synth::hdAftertouch(int delay, float normAftertouch) noexcept
|
|||
impl.performHdcc(delay, ExtendedCCs::channelAftertouch, normAftertouch, false);
|
||||
}
|
||||
|
||||
void Synth::polyAftertouch(int delay, int noteNumber, uint8_t aftertouch) noexcept
|
||||
{
|
||||
const float normalizedAftertouch = normalize7Bits(aftertouch);
|
||||
hdPolyAftertouch(delay, noteNumber, normalizedAftertouch);
|
||||
}
|
||||
|
||||
void Synth::hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||
|
||||
impl.resources_.midiState.polyAftertouchEvent(delay, noteNumber, normAftertouch);
|
||||
|
||||
for (auto& voice : impl.voiceManager_)
|
||||
voice.registerPolyAftertouch(delay, noteNumber, normAftertouch);
|
||||
|
||||
// Note information is lost on this CC
|
||||
impl.performHdcc(delay, ExtendedCCs::polyphonicAftertouch, normAftertouch, false);
|
||||
}
|
||||
|
||||
void Synth::tempo(int delay, float secondsPerBeat) noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -1721,6 +1742,9 @@ void Synth::Impl::setupModMatrix()
|
|||
case ModId::ChannelAftertouch:
|
||||
gen = genChannelAftertouch_.get();
|
||||
break;
|
||||
case ModId::PolyAftertouch:
|
||||
gen = genPolyAftertouch_.get();
|
||||
break;
|
||||
default:
|
||||
DBG("[sfizz] Have unknown type of source generator");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -421,6 +421,22 @@ public:
|
|||
* @param secondsPerQuarter the new period of the quarter note
|
||||
*/
|
||||
void tempo(int delay, float secondsPerQuarter) noexcept;
|
||||
/**
|
||||
* @brief Send a polyphonic aftertouch event to the synth
|
||||
*
|
||||
* @param delay
|
||||
* @param noteNumber
|
||||
* @param normAftertouch
|
||||
*/
|
||||
void polyAftertouch(int delay, int noteNumber, uint8_t aftertouch) noexcept;
|
||||
/**
|
||||
* @brief Send a polyphonic aftertouch event to the synth
|
||||
*
|
||||
* @param delay
|
||||
* @param noteNumber
|
||||
* @param normAftertouch
|
||||
*/
|
||||
void hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) noexcept;
|
||||
/**
|
||||
* @brief Send the time signature.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -446,6 +446,14 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
client.receive(delay, path, "ff", args);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/polyaft_range", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
sfizz_arg_t args[2];
|
||||
args[0].f = region.polyAftertouchRange.getStart();
|
||||
args[1].f = region.polyAftertouchRange.getEnd();
|
||||
client.receive(delay, path, "ff", args);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/bpm_range", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
sfizz_arg_t args[2];
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "modulations/sources/Controller.h"
|
||||
#include "modulations/sources/FlexEnvelope.h"
|
||||
#include "modulations/sources/ChannelAftertouch.h"
|
||||
#include "modulations/sources/PolyAftertouch.h"
|
||||
#include "modulations/sources/LFO.h"
|
||||
|
||||
namespace sfz {
|
||||
|
|
@ -303,6 +304,7 @@ struct Synth::Impl final: public Parser::Listener {
|
|||
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_;
|
||||
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_;
|
||||
std::unique_ptr<ChannelAftertouchSource> genChannelAftertouch_;
|
||||
std::unique_ptr<PolyAftertouchSource> genPolyAftertouch_;
|
||||
|
||||
// Settings per voice
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "LFO.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "ModifierHelpers.h"
|
||||
#include "TriggerEvent.h"
|
||||
#include "modulations/ModId.h"
|
||||
#include "modulations/ModKey.h"
|
||||
#include "modulations/ModMatrix.h"
|
||||
|
|
@ -658,13 +659,28 @@ void Voice::registerPitchWheel(int delay, float pitch) noexcept
|
|||
UNUSED(pitch);
|
||||
}
|
||||
|
||||
void Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept
|
||||
void Voice::registerAftertouch(int delay, float aftertouch) noexcept
|
||||
{
|
||||
// TODO
|
||||
UNUSED(delay);
|
||||
UNUSED(aftertouch);
|
||||
}
|
||||
|
||||
void Voice::registerPolyAftertouch(int delay, int noteNumber, float aftertouch) noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
if (impl.state_ != State::playing)
|
||||
return;
|
||||
|
||||
if (!(impl.triggerEvent_.type == TriggerEventType::NoteOn || impl.triggerEvent_.type == TriggerEventType::NoteOff)
|
||||
|| impl.triggerEvent_.number != noteNumber)
|
||||
return;
|
||||
|
||||
// TODO
|
||||
UNUSED(delay);
|
||||
UNUSED(aftertouch);
|
||||
}
|
||||
|
||||
void Voice::registerTempo(int delay, float secondsPerQuarter) noexcept
|
||||
{
|
||||
// TODO
|
||||
|
|
|
|||
|
|
@ -153,7 +153,17 @@ public:
|
|||
* @param delay
|
||||
* @param aftertouch
|
||||
*/
|
||||
void registerAftertouch(int delay, uint8_t aftertouch) noexcept;
|
||||
void registerAftertouch(int delay, float aftertouch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Register a polyphonic aftertouch event; for now this does nothing
|
||||
*
|
||||
* @param delay
|
||||
* @param noteNumber
|
||||
* @param aftertouch
|
||||
*/
|
||||
void registerPolyAftertouch(int delay, int noteNumber, float aftertouch) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Register a tempo event; for now this does nothing
|
||||
*
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ int ModIds::flags(ModId id) noexcept
|
|||
return kModIsPerVoice;
|
||||
case ModId::ChannelAftertouch:
|
||||
return kModIsPerCycle;
|
||||
case ModId::PolyAftertouch:
|
||||
return kModIsPerVoice;
|
||||
case ModId::PerVoiceController:
|
||||
return kModIsPerVoice;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ enum class ModId : int {
|
|||
PitchEG,
|
||||
FilEG,
|
||||
ChannelAftertouch,
|
||||
PolyAftertouch,
|
||||
PerVoiceController,
|
||||
_SourcesEnd,
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ std::string ModKey::toString() const
|
|||
return absl::StrCat("FilterEG {", region_.number(), "}");
|
||||
case ModId::ChannelAftertouch:
|
||||
return absl::StrCat("ChannelAftertouch");
|
||||
case ModId::PolyAftertouch:
|
||||
return absl::StrCat("PolyAftertouch");
|
||||
case ModId::PerVoiceController:
|
||||
return absl::StrCat("PerVoiceController ", params_.cc,
|
||||
" {curve=", params_.curve, ", smooth=", params_.smooth,
|
||||
|
|
|
|||
|
|
@ -106,6 +106,16 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceI
|
|||
};
|
||||
|
||||
switch(p.cc) {
|
||||
case ExtendedCCs::polyphonicAftertouch: {
|
||||
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||
const float fillValue =
|
||||
voice && voice->getTriggerEvent().type == TriggerEventType::NoteOn ?
|
||||
impl_->res_->midiState.getPolyAftertouch(voice->getTriggerEvent().number) : 0.0f;
|
||||
|
||||
sfz::fill(buffer, quantize(fillValue));
|
||||
canShortcut = true;
|
||||
break;
|
||||
}
|
||||
case ExtendedCCs::noteOnVelocity: {
|
||||
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||
const float fillValue =
|
||||
|
|
|
|||
48
src/sfizz/modulations/sources/PolyAftertouch.cpp
Normal file
48
src/sfizz/modulations/sources/PolyAftertouch.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "PolyAftertouch.h"
|
||||
#include "../../ModifierHelpers.h"
|
||||
#include "../../ADSREnvelope.h"
|
||||
|
||||
namespace sfz {
|
||||
|
||||
PolyAftertouchSource::PolyAftertouchSource(VoiceManager& manager, MidiState& state)
|
||||
: midiState_(state), manager_(manager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void PolyAftertouchSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
UNUSED(sourceKey);
|
||||
UNUSED(voiceId);
|
||||
UNUSED(delay);
|
||||
}
|
||||
|
||||
void PolyAftertouchSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
UNUSED(sourceKey);
|
||||
UNUSED(voiceId);
|
||||
UNUSED(delay);
|
||||
}
|
||||
|
||||
void PolyAftertouchSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||
{
|
||||
UNUSED(sourceKey);
|
||||
Voice* voice = manager_.getVoiceById(voiceId);
|
||||
if (!voice || voice->getTriggerEvent().type == TriggerEventType::CC) {
|
||||
fill(buffer, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
const int noteNumber = voice->getTriggerEvent().number;
|
||||
|
||||
const EventVector& events = midiState_.getPolyAftertouchEvents(noteNumber);
|
||||
linearEnvelope(events, buffer, [](float x) { return x; });
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
27
src/sfizz/modulations/sources/PolyAftertouch.h
Normal file
27
src/sfizz/modulations/sources/PolyAftertouch.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "../ModGenerator.h"
|
||||
#include "../../VoiceManager.h"
|
||||
#include "../../MidiState.h"
|
||||
|
||||
namespace sfz {
|
||||
class Synth;
|
||||
|
||||
class PolyAftertouchSource : public ModGenerator {
|
||||
public:
|
||||
explicit PolyAftertouchSource(VoiceManager &manager, MidiState& state);
|
||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||
|
||||
private:
|
||||
MidiState& midiState_;
|
||||
VoiceManager& manager_;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -185,6 +185,11 @@ void sfz::Sfizz::aftertouch(int delay, uint8_t aftertouch) noexcept
|
|||
synth->synth.aftertouch(delay, aftertouch);
|
||||
}
|
||||
|
||||
void sfz::Sfizz::polyAftertouch(int delay, int noteNumber, uint8_t aftertouch) noexcept
|
||||
{
|
||||
synth->synth.polyAftertouch(delay, noteNumber, aftertouch);
|
||||
}
|
||||
|
||||
void sfz::Sfizz::tempo(int delay, float secondsPerBeat) noexcept
|
||||
{
|
||||
synth->synth.tempo(delay, secondsPerBeat);
|
||||
|
|
|
|||
|
|
@ -142,6 +142,10 @@ void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, char aftertouch)
|
|||
{
|
||||
synth->synth.aftertouch(delay, aftertouch);
|
||||
}
|
||||
void sfizz_send_poly_aftertouch(sfizz_synth_t* synth, int delay, int note_number, char aftertouch)
|
||||
{
|
||||
synth->synth.polyAftertouch(delay, note_number, aftertouch);
|
||||
}
|
||||
void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter)
|
||||
{
|
||||
synth->synth.tempo(delay, seconds_per_quarter);
|
||||
|
|
|
|||
|
|
@ -430,7 +430,6 @@ TEST_CASE("[Modulations] LFO v1 aftertouch connections")
|
|||
}, 3));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("[Modulations] LFO v1 aftertouch frequency connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
@ -451,6 +450,46 @@ TEST_CASE("[Modulations] LFO v1 aftertouch frequency connections")
|
|||
}, 3));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] LFO v1 poly aftertouch connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine amplfo_depthpolyaft=10
|
||||
<region> sample=*sine pitchlfo_depthpolyaft=1200
|
||||
<region> sample=*sine fillfo_depthpolyaft=-3600
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createDefaultGraph({
|
||||
R"("PolyAftertouch" -> "AmplitudeLFODepth {0}")",
|
||||
R"("PolyAftertouch" -> "PitchLFODepth {1}")",
|
||||
R"("PolyAftertouch" -> "FilterLFODepth {2}")",
|
||||
R"("AmplitudeLFO {0}" -> "Volume {0}")",
|
||||
R"("PitchLFO {1}" -> "Pitch {1}")",
|
||||
R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")",
|
||||
}, 3));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] LFO v1 poly aftertouch frequency connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine amplfo_freqpolyaft=10
|
||||
<region> sample=*sine pitchlfo_freqpolyaft=1200
|
||||
<region> sample=*sine fillfo_freqpolyaft=-3600
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createDefaultGraph({
|
||||
R"("PolyAftertouch" -> "AmplitudeLFOFrequency {0}")",
|
||||
R"("PolyAftertouch" -> "PitchLFOFrequency {1}")",
|
||||
R"("PolyAftertouch" -> "FilterLFOFrequency {2}")",
|
||||
R"("AmplitudeLFO {0}" -> "Volume {0}")",
|
||||
R"("PitchLFO {1}" -> "Pitch {1}")",
|
||||
R"("FilterLFO {2}" -> "FilterCutoff {2, N=1}")",
|
||||
}, 3));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] EG v1 CC connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
|
|||
|
|
@ -537,3 +537,47 @@ TEST_CASE("[Keyswitches] Multiple sw_default, in region")
|
|||
REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
|
||||
}
|
||||
|
||||
TEST_CASE("[Region activation] Polyphonic aftertouch")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
SECTION("Basic sequence, note on")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyaft.sfz", R"(
|
||||
<region> sample=*saw lokey=48 hikey=60
|
||||
<region> lopolyaft=50 hipolyaft=100 sample=*sine lokey=36 hikey=47
|
||||
)");
|
||||
synth.noteOn(0, 50, 100);
|
||||
REQUIRE( synth.getNumActiveVoices() == 1);
|
||||
synth.noteOn(1, 40, 100);
|
||||
REQUIRE( synth.getNumActiveVoices() == 1); // no notes playing
|
||||
synth.polyAftertouch(2, 40, 80);
|
||||
synth.noteOn(3, 40, 100);
|
||||
REQUIRE( synth.getNumActiveVoices() == 2);
|
||||
}
|
||||
|
||||
SECTION("Basic sequence, note off, no polyaft set")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyaft.sfz", R"(
|
||||
<region> sample=*saw
|
||||
<region> lopolyaft=50 hipolyaft=100 sample=*sine trigger=release
|
||||
)");
|
||||
synth.noteOn(0, 50, 100);
|
||||
REQUIRE( synth.getNumActiveVoices() == 1);
|
||||
synth.noteOff(1, 50, 0);
|
||||
REQUIRE( synth.getNumActiveVoices() == 1); // no note off playing
|
||||
}
|
||||
|
||||
SECTION("Basic sequence, note off")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyaft.sfz", R"(
|
||||
<region> sample=*saw
|
||||
<region> lopolyaft=50 hipolyaft=100 sample=*sine trigger=release
|
||||
)");
|
||||
synth.noteOn(0, 50, 100);
|
||||
REQUIRE( synth.getNumActiveVoices() == 1);
|
||||
synth.polyAftertouch(2, 50, 80);
|
||||
synth.noteOff(3, 50, 0);
|
||||
REQUIRE( synth.getNumActiveVoices() == 2); // no note off playing
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -922,6 +922,34 @@ TEST_CASE("[Values] Aftertouch range")
|
|||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("[Values] Polyaftertouch range")
|
||||
{
|
||||
Synth synth;
|
||||
std::vector<std::string> messageList;
|
||||
Client client(&messageList);
|
||||
client.setReceiveCallback(&simpleMessageReceiver);
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav lopolyaft=34 hipolyaft=60
|
||||
<region> sample=kick.wav lopolyaft=-3 hipolyaft=60
|
||||
<region> sample=kick.wav lopolyaft=20 hipolyaft=-1
|
||||
<region> sample=kick.wav lopolyaft=20 hipolyaft=10
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/polyaft_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/polyaft_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/polyaft_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/polyaft_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region4/polyaft_range", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/polyaft_range,ff : { 0, 1 }",
|
||||
"/region1/polyaft_range,ff : { 0.267717, 0.472441 }",
|
||||
"/region2/polyaft_range,ff : { -0.023622, 0.472441 }",
|
||||
"/region3/polyaft_range,ff : { 0.15748, -0.00787402 }",
|
||||
"/region4/polyaft_range,ff : { 0.15748, 0.0787402 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("[Values] BPM range")
|
||||
{
|
||||
Synth synth;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue