Merge pull request #764 from paulfd/polyaft
Support polyphonic aftertouch basics
This commit is contained in:
commit
d0cd21acf9
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]);
|
synth->noteOn(event.time, event.buffer[1], event.buffer[2]);
|
||||||
break;
|
break;
|
||||||
case midi::polyphonicPressure:
|
case midi::polyphonicPressure:
|
||||||
// Not implemented
|
synth->polyAftertouch(event.time, event.buffer[1], event.buffer[2]);
|
||||||
break;
|
break;
|
||||||
case midi::controlChange:
|
case midi::controlChange:
|
||||||
synth->cc(event.time, event.buffer[1], event.buffer[2]);
|
synth->cc(event.time, event.buffer[1], event.buffer[2]);
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ SFIZZ_SOURCES = \
|
||||||
src/sfizz/modulations/ModMatrix.cpp \
|
src/sfizz/modulations/ModMatrix.cpp \
|
||||||
src/sfizz/modulations/sources/ADSREnvelope.cpp \
|
src/sfizz/modulations/sources/ADSREnvelope.cpp \
|
||||||
src/sfizz/modulations/sources/ChannelAftertouch.cpp \
|
src/sfizz/modulations/sources/ChannelAftertouch.cpp \
|
||||||
|
src/sfizz/modulations/sources/PolyAftertouch.cpp \
|
||||||
src/sfizz/modulations/sources/Controller.cpp \
|
src/sfizz/modulations/sources/Controller.cpp \
|
||||||
src/sfizz/modulations/sources/FlexEnvelope.cpp \
|
src/sfizz/modulations/sources/FlexEnvelope.cpp \
|
||||||
src/sfizz/modulations/sources/LFO.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,
|
(int)ev->time.frames,
|
||||||
msg[1]);
|
msg[1]);
|
||||||
break;
|
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:
|
case LV2_MIDI_MSG_BENDER:
|
||||||
sfizz_send_pitch_wheel(self->synth,
|
sfizz_send_pitch_wheel(self->synth,
|
||||||
(int)ev->time.frames,
|
(int)ev->time.frames,
|
||||||
|
|
|
||||||
|
|
@ -465,9 +465,13 @@ void SfizzVstProcessor::processEvents(Vst::IEventList& events)
|
||||||
_noteEventsCurrentCycle[pitch] = 0.0f;
|
_noteEventsCurrentCycle[pitch] = 0.0f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// case Vst::Event::kPolyPressureEvent:
|
case Vst::Event::kPolyPressureEvent: {
|
||||||
// synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure));
|
int pitch = e.polyPressure.pitch;
|
||||||
// break;
|
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/ModGenerator.h
|
||||||
sfizz/modulations/sources/ADSREnvelope.h
|
sfizz/modulations/sources/ADSREnvelope.h
|
||||||
sfizz/modulations/sources/ChannelAftertouch.h
|
sfizz/modulations/sources/ChannelAftertouch.h
|
||||||
|
sfizz/modulations/sources/PolyAftertouch.h
|
||||||
sfizz/modulations/sources/Controller.h
|
sfizz/modulations/sources/Controller.h
|
||||||
sfizz/modulations/sources/FlexEnvelope.h
|
sfizz/modulations/sources/FlexEnvelope.h
|
||||||
sfizz/modulations/sources/LFO.h
|
sfizz/modulations/sources/LFO.h
|
||||||
|
|
@ -166,6 +167,7 @@ set(SFIZZ_SOURCES
|
||||||
sfizz/modulations/ModMatrix.cpp
|
sfizz/modulations/ModMatrix.cpp
|
||||||
sfizz/modulations/sources/ADSREnvelope.cpp
|
sfizz/modulations/sources/ADSREnvelope.cpp
|
||||||
sfizz/modulations/sources/ChannelAftertouch.cpp
|
sfizz/modulations/sources/ChannelAftertouch.cpp
|
||||||
|
sfizz/modulations/sources/PolyAftertouch.cpp
|
||||||
sfizz/modulations/sources/Controller.cpp
|
sfizz/modulations/sources/Controller.cpp
|
||||||
sfizz/modulations/sources/FlexEnvelope.cpp
|
sfizz/modulations/sources/FlexEnvelope.cpp
|
||||||
sfizz/modulations/sources/LFO.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);
|
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
|
* @since 0.2.0
|
||||||
*
|
*
|
||||||
* @param synth The synth.
|
* @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);
|
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.
|
* @brief Send a tempo event.
|
||||||
* @since 0.2.0
|
* @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.
|
* @brief Index out of bound error for the requested CC/key label.
|
||||||
* @since 0.4.0
|
* @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.
|
* @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;
|
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
|
* @since 0.2.0
|
||||||
*
|
*
|
||||||
|
|
@ -438,6 +438,22 @@ public:
|
||||||
*/
|
*/
|
||||||
void aftertouch(int delay, uint8_t aftertouch) noexcept;
|
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.
|
* @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 hiVel { 127, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
|
||||||
FloatSpec loChannelAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
FloatSpec loChannelAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
||||||
FloatSpec hiChannelAftertouch { 127, {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 loBend { -8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
|
||||||
FloatSpec hiBend { 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 };
|
FloatSpec loNormalized { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,8 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> hiBipolar;
|
extern const OpcodeSpec<float> hiBipolar;
|
||||||
extern const OpcodeSpec<float> loChannelAftertouch;
|
extern const OpcodeSpec<float> loChannelAftertouch;
|
||||||
extern const OpcodeSpec<float> hiChannelAftertouch;
|
extern const OpcodeSpec<float> hiChannelAftertouch;
|
||||||
|
extern const OpcodeSpec<float> loPolyAftertouch;
|
||||||
|
extern const OpcodeSpec<float> hiPolyAftertouch;
|
||||||
extern const OpcodeSpec<uint16_t> ccNumber;
|
extern const OpcodeSpec<uint16_t> ccNumber;
|
||||||
extern const OpcodeSpec<uint8_t> curveCC;
|
extern const OpcodeSpec<uint8_t> curveCC;
|
||||||
extern const OpcodeSpec<uint16_t> smoothCC;
|
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);
|
((sequenceCounter_++ % region.sequenceLength) == region.sequencePosition - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSwitchedOn())
|
const bool polyAftertouchActive =
|
||||||
|
region.polyAftertouchRange.containsWithEnd(midiState_.getPolyAftertouch(noteNumber));
|
||||||
|
|
||||||
|
if (!isSwitchedOn() || !polyAftertouchActive)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!region.triggerOnNote)
|
if (!region.triggerOnNote)
|
||||||
|
|
@ -83,7 +86,10 @@ bool Layer::registerNoteOff(int noteNumber, float velocity, float randValue) noe
|
||||||
|
|
||||||
const Region& region = region_;
|
const Region& region = region_;
|
||||||
|
|
||||||
if (!isSwitchedOn())
|
const bool polyAftertouchActive =
|
||||||
|
region.polyAftertouchRange.containsWithEnd(midiState_.getPolyAftertouch(noteNumber));
|
||||||
|
|
||||||
|
if (!isSwitchedOn() || !polyAftertouchActive)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!region.triggerOnNote)
|
if (!region.triggerOnNote)
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,11 @@ void sfz::MidiState::flushEvents() noexcept
|
||||||
events.resize(1);
|
events.resize(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto& ccEvents : cc)
|
for (auto& events : ccEvents)
|
||||||
flushEventVector(ccEvents);
|
flushEventVector(events);
|
||||||
|
|
||||||
|
for (auto& events: polyAftertouchEvents)
|
||||||
|
flushEventVector(events);
|
||||||
|
|
||||||
flushEventVector(pitchEvents);
|
flushEventVector(pitchEvents);
|
||||||
flushEventVector(channelAftertouchEvents);
|
flushEventVector(channelAftertouchEvents);
|
||||||
|
|
@ -88,8 +91,11 @@ void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
events.reserve(samplesPerBlock);
|
events.reserve(samplesPerBlock);
|
||||||
};
|
};
|
||||||
this->samplesPerBlock = samplesPerBlock;
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
for (auto& ccEvents : cc)
|
for (auto& events: ccEvents)
|
||||||
updateEventBufferSize(ccEvents);
|
updateEventBufferSize(events);
|
||||||
|
|
||||||
|
for (auto& events: polyAftertouchEvents)
|
||||||
|
updateEventBufferSize(events);
|
||||||
|
|
||||||
updateEventBufferSize(pitchEvents);
|
updateEventBufferSize(pitchEvents);
|
||||||
updateEventBufferSize(channelAftertouchEvents);
|
updateEventBufferSize(channelAftertouchEvents);
|
||||||
|
|
@ -147,21 +153,39 @@ void sfz::MidiState::channelAftertouchEvent(int delay, float aftertouch) noexcep
|
||||||
insertEventInVector(channelAftertouchEvents, delay, aftertouch);
|
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
|
float sfz::MidiState::getChannelAftertouch() const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(channelAftertouchEvents.size() > 0);
|
ASSERT(channelAftertouchEvents.size() > 0);
|
||||||
return channelAftertouchEvents.back().value;
|
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
|
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
|
float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
||||||
return cc[ccNumber].back().value;
|
return ccEvents[ccNumber].back().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::reset() noexcept
|
void sfz::MidiState::reset() noexcept
|
||||||
|
|
@ -174,8 +198,11 @@ void sfz::MidiState::reset() noexcept
|
||||||
events.push_back({ 0, 0.0f });
|
events.push_back({ 0, 0.0f });
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto& ccEvents : cc)
|
for (auto& events : ccEvents)
|
||||||
clearEvents(ccEvents);
|
clearEvents(events);
|
||||||
|
|
||||||
|
for (auto& events : polyAftertouchEvents)
|
||||||
|
clearEvents(events);
|
||||||
|
|
||||||
clearEvents(pitchEvents);
|
clearEvents(pitchEvents);
|
||||||
clearEvents(channelAftertouchEvents);
|
clearEvents(channelAftertouchEvents);
|
||||||
|
|
@ -201,7 +228,7 @@ const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
||||||
if (ccIdx < 0 || ccIdx >= config::numCCs)
|
if (ccIdx < 0 || ccIdx >= config::numCCs)
|
||||||
return nullEvent;
|
return nullEvent;
|
||||||
|
|
||||||
return cc[ccIdx];
|
return ccEvents[ccIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
|
const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
|
||||||
|
|
@ -213,3 +240,11 @@ const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexc
|
||||||
{
|
{
|
||||||
return channelAftertouchEvents;
|
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;
|
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
|
* @brief Get the channel aftertouch status
|
||||||
|
|
||||||
|
|
@ -120,6 +127,13 @@ public:
|
||||||
*/
|
*/
|
||||||
float getChannelAftertouch() const noexcept;
|
float getChannelAftertouch() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the polyphonic aftertouch status
|
||||||
|
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
float getPolyAftertouch(int noteNumber) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a CC event
|
* @brief Register a CC event
|
||||||
*
|
*
|
||||||
|
|
@ -172,6 +186,7 @@ public:
|
||||||
void resetAllControllers(int delay) noexcept;
|
void resetAllControllers(int delay) noexcept;
|
||||||
|
|
||||||
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
||||||
|
const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept;
|
||||||
const EventVector& getPitchEvents() const noexcept;
|
const EventVector& getPitchEvents() const noexcept;
|
||||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||||
|
|
||||||
|
|
@ -230,7 +245,7 @@ private:
|
||||||
* @brief Current known values for the CCs.
|
* @brief Current known values for the CCs.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
std::array<EventVector, config::numCCs> cc;
|
std::array<EventVector, config::numCCs> ccEvents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Null event
|
* @brief Null event
|
||||||
|
|
@ -248,6 +263,11 @@ private:
|
||||||
*/
|
*/
|
||||||
EventVector channelAftertouchEvents;
|
EventVector channelAftertouchEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Polyphonic aftertouch status.
|
||||||
|
*/
|
||||||
|
std::array<EventVector, 128> polyAftertouchEvents;
|
||||||
|
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
float alternate { 0.0f };
|
float alternate { 0.0f };
|
||||||
|
|
|
||||||
|
|
@ -348,6 +348,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
case hash("hichanaft"):
|
case hash("hichanaft"):
|
||||||
aftertouchRange.setEnd(opcode.read(Default::hiChannelAftertouch));
|
aftertouchRange.setEnd(opcode.read(Default::hiChannelAftertouch));
|
||||||
break;
|
break;
|
||||||
|
case hash("lopolyaft"):
|
||||||
|
polyAftertouchRange.setStart(opcode.read(Default::loPolyAftertouch));
|
||||||
|
break;
|
||||||
|
case hash("hipolyaft"):
|
||||||
|
polyAftertouchRange.setEnd(opcode.read(Default::hiPolyAftertouch));
|
||||||
|
break;
|
||||||
case hash("lobpm"):
|
case hash("lobpm"):
|
||||||
bpmRange.setStart(opcode.read(Default::loBPM));
|
bpmRange.setStart(opcode.read(Default::loBPM));
|
||||||
break;
|
break;
|
||||||
|
|
@ -926,7 +932,9 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo)
|
||||||
= opcode.read(depthModSpec);
|
= opcode.read(depthModSpec);
|
||||||
break;
|
break;
|
||||||
case_any_lfo("depthpolyaft"): // NOLINT bugprone-branch-clone
|
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;
|
break;
|
||||||
case_any_lfo("fade"):
|
case_any_lfo("fade"):
|
||||||
lfo.fade = opcode.read(Default::lfoFade);
|
lfo.fade = opcode.read(Default::lfoFade);
|
||||||
|
|
@ -942,7 +950,8 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, LFODescription& lfo)
|
||||||
= opcode.read(Default::lfoFreqMod);
|
= opcode.read(Default::lfoFreqMod);
|
||||||
break;
|
break;
|
||||||
case_any_lfo("freqpolyaft"): // NOLINT bugprone-branch-clone
|
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;
|
break;
|
||||||
|
|
||||||
// sfizz extension
|
// sfizz extension
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,7 @@ struct Region {
|
||||||
|
|
||||||
// Region logic: internal conditions
|
// Region logic: internal conditions
|
||||||
UncheckedRange<float> aftertouchRange { Default::loChannelAftertouch, Default::hiChannelAftertouch }; // hichanaft and lochanaft
|
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> bpmRange { Default::loBPM, Default::hiBPM }; // hibpm and lobpm
|
||||||
UncheckedRange<float> randRange { Default::loNormalized, Default::hiNormalized }; // hirand and lorand
|
UncheckedRange<float> randRange { Default::loNormalized, Default::hiNormalized }; // hirand and lorand
|
||||||
uint8_t sequenceLength { Default::sequence }; // seq_length
|
uint8_t sequenceLength { Default::sequence }; // seq_length
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ Synth::Impl::Impl()
|
||||||
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
||||||
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
||||||
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState));
|
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState));
|
||||||
|
genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, resources_.midiState));
|
||||||
}
|
}
|
||||||
|
|
||||||
Synth::Impl::~Impl()
|
Synth::Impl::~Impl()
|
||||||
|
|
@ -1337,6 +1338,26 @@ void Synth::hdAftertouch(int delay, float normAftertouch) noexcept
|
||||||
impl.performHdcc(delay, ExtendedCCs::channelAftertouch, normAftertouch, false);
|
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
|
void Synth::tempo(int delay, float secondsPerBeat) noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
@ -1721,6 +1742,9 @@ void Synth::Impl::setupModMatrix()
|
||||||
case ModId::ChannelAftertouch:
|
case ModId::ChannelAftertouch:
|
||||||
gen = genChannelAftertouch_.get();
|
gen = genChannelAftertouch_.get();
|
||||||
break;
|
break;
|
||||||
|
case ModId::PolyAftertouch:
|
||||||
|
gen = genPolyAftertouch_.get();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
DBG("[sfizz] Have unknown type of source generator");
|
DBG("[sfizz] Have unknown type of source generator");
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -421,6 +421,22 @@ public:
|
||||||
* @param secondsPerQuarter the new period of the quarter note
|
* @param secondsPerQuarter the new period of the quarter note
|
||||||
*/
|
*/
|
||||||
void tempo(int delay, float secondsPerQuarter) noexcept;
|
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.
|
* @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);
|
client.receive(delay, path, "ff", args);
|
||||||
} break;
|
} 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", "") {
|
MATCH("/region&/bpm_range", "") {
|
||||||
GET_REGION_OR_BREAK(indices[0])
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
sfizz_arg_t args[2];
|
sfizz_arg_t args[2];
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "modulations/sources/Controller.h"
|
#include "modulations/sources/Controller.h"
|
||||||
#include "modulations/sources/FlexEnvelope.h"
|
#include "modulations/sources/FlexEnvelope.h"
|
||||||
#include "modulations/sources/ChannelAftertouch.h"
|
#include "modulations/sources/ChannelAftertouch.h"
|
||||||
|
#include "modulations/sources/PolyAftertouch.h"
|
||||||
#include "modulations/sources/LFO.h"
|
#include "modulations/sources/LFO.h"
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
@ -303,6 +304,7 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_;
|
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_;
|
||||||
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_;
|
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_;
|
||||||
std::unique_ptr<ChannelAftertouchSource> genChannelAftertouch_;
|
std::unique_ptr<ChannelAftertouchSource> genChannelAftertouch_;
|
||||||
|
std::unique_ptr<PolyAftertouchSource> genPolyAftertouch_;
|
||||||
|
|
||||||
// Settings per voice
|
// Settings per voice
|
||||||
struct {
|
struct {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include "LFO.h"
|
#include "LFO.h"
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
#include "ModifierHelpers.h"
|
#include "ModifierHelpers.h"
|
||||||
|
#include "TriggerEvent.h"
|
||||||
#include "modulations/ModId.h"
|
#include "modulations/ModId.h"
|
||||||
#include "modulations/ModKey.h"
|
#include "modulations/ModKey.h"
|
||||||
#include "modulations/ModMatrix.h"
|
#include "modulations/ModMatrix.h"
|
||||||
|
|
@ -658,13 +659,28 @@ void Voice::registerPitchWheel(int delay, float pitch) noexcept
|
||||||
UNUSED(pitch);
|
UNUSED(pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept
|
void Voice::registerAftertouch(int delay, float aftertouch) noexcept
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
UNUSED(delay);
|
UNUSED(delay);
|
||||||
UNUSED(aftertouch);
|
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
|
void Voice::registerTempo(int delay, float secondsPerQuarter) noexcept
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,17 @@ public:
|
||||||
* @param delay
|
* @param delay
|
||||||
* @param aftertouch
|
* @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
|
* @brief Register a tempo event; for now this does nothing
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
case ModId::ChannelAftertouch:
|
case ModId::ChannelAftertouch:
|
||||||
return kModIsPerCycle;
|
return kModIsPerCycle;
|
||||||
|
case ModId::PolyAftertouch:
|
||||||
|
return kModIsPerVoice;
|
||||||
case ModId::PerVoiceController:
|
case ModId::PerVoiceController:
|
||||||
return kModIsPerVoice;
|
return kModIsPerVoice;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ enum class ModId : int {
|
||||||
PitchEG,
|
PitchEG,
|
||||||
FilEG,
|
FilEG,
|
||||||
ChannelAftertouch,
|
ChannelAftertouch,
|
||||||
|
PolyAftertouch,
|
||||||
PerVoiceController,
|
PerVoiceController,
|
||||||
_SourcesEnd,
|
_SourcesEnd,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,8 @@ std::string ModKey::toString() const
|
||||||
return absl::StrCat("FilterEG {", region_.number(), "}");
|
return absl::StrCat("FilterEG {", region_.number(), "}");
|
||||||
case ModId::ChannelAftertouch:
|
case ModId::ChannelAftertouch:
|
||||||
return absl::StrCat("ChannelAftertouch");
|
return absl::StrCat("ChannelAftertouch");
|
||||||
|
case ModId::PolyAftertouch:
|
||||||
|
return absl::StrCat("PolyAftertouch");
|
||||||
case ModId::PerVoiceController:
|
case ModId::PerVoiceController:
|
||||||
return absl::StrCat("PerVoiceController ", params_.cc,
|
return absl::StrCat("PerVoiceController ", params_.cc,
|
||||||
" {curve=", params_.curve, ", smooth=", params_.smooth,
|
" {curve=", params_.curve, ", smooth=", params_.smooth,
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,16 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceI
|
||||||
};
|
};
|
||||||
|
|
||||||
switch(p.cc) {
|
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: {
|
case ExtendedCCs::noteOnVelocity: {
|
||||||
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
const float fillValue =
|
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);
|
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
|
void sfz::Sfizz::tempo(int delay, float secondsPerBeat) noexcept
|
||||||
{
|
{
|
||||||
synth->synth.tempo(delay, secondsPerBeat);
|
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);
|
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)
|
void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter)
|
||||||
{
|
{
|
||||||
synth->synth.tempo(delay, seconds_per_quarter);
|
synth->synth.tempo(delay, seconds_per_quarter);
|
||||||
|
|
|
||||||
|
|
@ -430,7 +430,6 @@ TEST_CASE("[Modulations] LFO v1 aftertouch connections")
|
||||||
}, 3));
|
}, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("[Modulations] LFO v1 aftertouch frequency connections")
|
TEST_CASE("[Modulations] LFO v1 aftertouch frequency connections")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
|
@ -451,6 +450,46 @@ TEST_CASE("[Modulations] LFO v1 aftertouch frequency connections")
|
||||||
}, 3));
|
}, 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")
|
TEST_CASE("[Modulations] EG v1 CC connections")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
|
|
||||||
|
|
@ -537,3 +537,47 @@ TEST_CASE("[Keyswitches] Multiple sw_default, in region")
|
||||||
REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
|
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);
|
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")
|
TEST_CASE("[Values] BPM range")
|
||||||
{
|
{
|
||||||
Synth synth;
|
Synth synth;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue