diff --git a/plugins/vst/SfizzVstParameters.h b/plugins/vst/SfizzVstParameters.h index ee60b33b..1b1eeae9 100644 --- a/plugins/vst/SfizzVstParameters.h +++ b/plugins/vst/SfizzVstParameters.h @@ -77,7 +77,7 @@ struct SfizzRange { case kPidAftertouch: return {0.0, 0.0, 1.0}; case kPidPitchBend: - return {0.5, 0.0, 1.0}; + return {0.0, -1.0, 1.0}; default: if (id >= kPidCC0 && id <= kPidCCLast) return {0.0, 0.0, 1.0}; diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index dca695ad..d3b7156f 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -19,12 +19,6 @@ #include #include -template -constexpr int fastRound(T x) -{ - return static_cast(x + T{ 0.5 }); // NOLINT -} - static const char defaultSfzText[] = "sample=*sine" "\n" "ampeg_attack=0.02 ampeg_release=0.1" "\n"; @@ -389,10 +383,10 @@ void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id _state.oscillatorQuality = static_cast(range.denormalize(value)); break; case kPidAftertouch: - synth.aftertouch(sampleOffset, fastRound(value * 127.0)); + synth.hdAftertouch(sampleOffset, value); break; case kPidPitchBend: - synth.pitchWheel(sampleOffset, fastRound(value * 16383) - 8192); + synth.hdPitchWheel(sampleOffset, range.denormalize(value)); break; default: if (id >= kPidCC0 && id <= kPidCCLast) { @@ -422,7 +416,7 @@ void SfizzVstProcessor::playOrderedEvent(const Vst::Event& event) _noteEventsCurrentCycle[pitch] = 0.0f; } else { - synth.noteOn(sampleOffset, pitch, convertVelocityFromFloat(event.noteOn.velocity)); + synth.hdNoteOn(sampleOffset, pitch, event.noteOn.velocity); _noteEventsCurrentCycle[pitch] = event.noteOn.velocity; } break; @@ -431,7 +425,7 @@ void SfizzVstProcessor::playOrderedEvent(const Vst::Event& event) int pitch = event.noteOn.pitch; if (pitch < 0 || pitch >= 128) break; - synth.noteOff(sampleOffset, pitch, convertVelocityFromFloat(event.noteOff.velocity)); + synth.hdNoteOff(sampleOffset, pitch, event.noteOff.velocity); _noteEventsCurrentCycle[pitch] = 0.0f; break; } @@ -439,7 +433,7 @@ void SfizzVstProcessor::playOrderedEvent(const Vst::Event& event) int pitch = event.polyPressure.pitch; if (pitch < 0 || pitch >= 128) break; - synth.polyAftertouch(sampleOffset, pitch, convertVelocityFromFloat(event.polyPressure.pressure)); + synth.hdPolyAftertouch(sampleOffset, pitch, event.polyPressure.pressure); break; } } @@ -504,11 +498,6 @@ void SfizzVstProcessor::processMessagesFromUi() } } -int SfizzVstProcessor::convertVelocityFromFloat(float x) -{ - return std::min(127, std::max(0, (int)(x * 127.0f))); -} - tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) { // Note(jpc) this notification is not necessarily handled by the RT thread diff --git a/plugins/vst/SfizzVstProcessor.h b/plugins/vst/SfizzVstProcessor.h index d29b00ef..e686b86e 100644 --- a/plugins/vst/SfizzVstProcessor.h +++ b/plugins/vst/SfizzVstProcessor.h @@ -43,7 +43,6 @@ public: void playOrderedEvent(const Vst::Event& event); void processMessagesFromUi(); - static int convertVelocityFromFloat(float x); tresult PLUGIN_API notify(Vst::IMessage* message) override; diff --git a/scripts/appveyor/install.sh b/scripts/appveyor/install.sh index d597d464..308db4bf 100644 --- a/scripts/appveyor/install.sh +++ b/scripts/appveyor/install.sh @@ -40,7 +40,8 @@ fi set -x -brew install dylibbundler +# -- Install if necessary; only if using external libraries (eg. jack, sndfile) +#brew install dylibbundler cd ~; npm install appdmg; cd - ~/node_modules/appdmg/bin/appdmg.js --version diff --git a/src/sfizz.h b/src/sfizz.h index f835bfc7..7d32a414 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -341,6 +341,24 @@ SFIZZ_EXPORTED_API void sfizz_set_sample_rate(sfizz_synth_t* synth, float sample */ SFIZZ_EXPORTED_API void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int note_number, char velocity); +/** + * @brief Send a high-precision on event to the synth. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @param synth The synth. + * @param delay The delay of the event in the block, in samples. + * @param note_number The MIDI note number. + * @param velocity The normalized MIDI velocity, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ +SFIZZ_EXPORTED_API void sfizz_send_hd_note_on(sfizz_synth_t* synth, int delay, int note_number, float velocity); + /** * @brief Send a note off event to the synth. * @since 0.2.0 @@ -362,6 +380,27 @@ SFIZZ_EXPORTED_API void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int */ SFIZZ_EXPORTED_API void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int note_number, char velocity); +/** + * @brief Send a high-precision note off event to the synth. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * As per the SFZ spec the velocity of note-off events is usually replaced by + * the note-on velocity. + * + * @param synth The synth. + * @param delay The delay of the event in the block, in samples. + * @param note_number The MIDI note number. + * @param velocity The normalized MIDI velocity, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ +SFIZZ_EXPORTED_API void sfizz_send_hd_note_off(sfizz_synth_t* synth, int delay, int note_number, float velocity); + /** * @brief Send a CC event to the synth. * @since 0.2.0 @@ -436,6 +475,23 @@ 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 a high-precision pitch wheel event. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @param synth The synth. + * @param delay The delay. + * @param pitch The normalized pitch, in domain -1 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ +SFIZZ_EXPORTED_API void sfizz_send_hd_pitch_wheel(sfizz_synth_t* synth, int delay, float pitch); + /** * @brief Send an aftertouch event. * @since 0.2.0 @@ -455,8 +511,26 @@ 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. + * @brief Send a high-precision aftertouch event. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 aftertouch The normalized aftertouch value, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ +SFIZZ_EXPORTED_API void sfizz_send_hd_aftertouch(sfizz_synth_t* synth, int delay, float aftertouch); + +/** + * @brief Send a polyphonic aftertouch event. + * This feature is experimental and needs more testing in the internal engine. * @since 0.6.0 * * This command should be delay-ordered with all other midi-type events @@ -474,6 +548,26 @@ SFIZZ_EXPORTED_API void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, c */ SFIZZ_EXPORTED_API void sfizz_send_poly_aftertouch(sfizz_synth_t* synth, int delay, int note_number, char aftertouch); +/** + * @brief Send a high-precision polyphonic aftertouch event. + * This feature is experimental and needs more testing in the internal engine. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 normalized aftertouch value, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ +SFIZZ_EXPORTED_API void sfizz_send_hd_poly_aftertouch(sfizz_synth_t* synth, int delay, int note_number, float aftertouch); + /** * @brief Send a tempo event. * diff --git a/src/sfizz.hpp b/src/sfizz.hpp index 88b1abf2..11d55e8e 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -383,6 +383,24 @@ public: */ void noteOn(int delay, int noteNumber, uint8_t velocity) noexcept; + /** + * @brief Send a high-precision note on event to the synth. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 midi note number. + * @param velocity the normalized midi note velocity, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ + void hdNoteOn(int delay, int noteNumber, float velocity) noexcept; + /** * @brief Send a note off event to the synth. * @since 0.2.0 @@ -401,6 +419,24 @@ public: */ void noteOff(int delay, int noteNumber, uint8_t velocity) noexcept; + /** + * @brief Send a note off event to the synth. + * @since 0.6.0 + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 midi note number. + * @param velocity the normalized midi note velocity, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ + void hdNoteOff(int delay, int noteNumber, float velocity) noexcept; + /** * @brief Send a CC event to the synth * @since 0.2.0 @@ -477,6 +513,24 @@ public: */ void pitchWheel(int delay, int pitch) noexcept; + /** + * @brief Send a high-precision pitch bend event to the synth + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 pitch the normalized pitch, in domain -1 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ + void hdPitchWheel(int delay, float pitch) noexcept; + /** * @brief Send an aftertouch event to the synth. * @@ -496,8 +550,26 @@ 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. + * @brief Send a high-precision aftertouch event to the synth. + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 aftertouch the normalized aftertouch value, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ + void hdAftertouch(int delay, float aftertouch) noexcept; + + /** + * @brief Send a polyphonic aftertouch event to the synth. + * This feature is experimental and needs more testing in the internal engine. * * This command should be delay-ordered with all other midi-type events * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the @@ -515,6 +587,26 @@ public: */ void polyAftertouch(int delay, int noteNumber, uint8_t aftertouch) noexcept; + /** + * @brief Send a high-precision polyphonic aftertouch event to the synth. + * This feature is experimental and needs more testing in the internal engine. + * + * This command should be delay-ordered with all other midi-type events + * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the + * synth is undefined. + * + * @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 normalized aftertouch value, in domain 0 to 1. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ + void hdPolyAftertouch(int delay, int noteNumber, float aftertouch) noexcept; + /** * @brief Send a tempo event to the synth. * diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index ee2edf9b..75bc24cb 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1068,23 +1068,33 @@ void Synth::renderBlock(AudioSpan buffer) noexcept } void Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept +{ + const float normalizedVelocity = normalizeVelocity(velocity); + hdNoteOn(delay, noteNumber, normalizedVelocity); +} + +void Synth::hdNoteOn(int delay, int noteNumber, float normalizedVelocity) noexcept { ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); Impl& impl = *impl_; - const auto normalizedVelocity = normalizeVelocity(velocity); ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; impl.noteOnDispatch(delay, noteNumber, normalizedVelocity); impl.resources_.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity); } void Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept +{ + const float normalizedVelocity = normalizeVelocity(velocity); + hdNoteOff(delay, noteNumber, normalizedVelocity); +} + +void Synth::hdNoteOff(int delay, int noteNumber, float normalizedVelocity) noexcept { ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); - UNUSED(velocity); + UNUSED(normalizedVelocity); Impl& impl = *impl_; - const auto normalizedVelocity = normalizeVelocity(velocity); 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 @@ -1319,10 +1329,13 @@ float Synth::getDefaultHdcc(int ccNumber) void Synth::pitchWheel(int delay, int pitch) noexcept { - ASSERT(pitch <= 8192); - ASSERT(pitch >= -8192); + const float normalizedPitch = normalizeBend(float(pitch)); + hdPitchWheel(delay, normalizedPitch); +} + +void Synth::hdPitchWheel(int delay, float normalizedPitch) noexcept +{ Impl& impl = *impl_; - const auto normalizedPitch = normalizeBend(float(pitch)); ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; impl.resources_.midiState.pitchBendEvent(delay, normalizedPitch); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 463e21ab..fd89ffee 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -353,6 +353,15 @@ public: * @param velocity the midi note velocity */ void noteOn(int delay, int noteNumber, uint8_t velocity) noexcept; + /** + * @brief Send a high-precision note on event to 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 noteNumber the midi note number + * @param velocity the normalized midi note velocity, in domain 0 to 1 + */ + void hdNoteOn(int delay, int noteNumber, float velocity) noexcept; /** * @brief Send a note off event to the synth * @@ -362,6 +371,15 @@ public: * @param velocity the midi note velocity */ void noteOff(int delay, int noteNumber, uint8_t velocity) noexcept; + /** + * @brief Send a high-precision note off event to 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 noteNumber the midi note number + * @param velocity the normalized midi note velocity, in domain 0 to 1 + */ + void hdNoteOff(int delay, int noteNumber, float velocity) noexcept; /** * @brief Send a CC event to the synth * @@ -412,6 +430,15 @@ public: * @param pitch the pitch value centered between -8192 and 8192 */ void pitchWheel(int delay, int pitch) noexcept; + /** + * @brief Send a high-precision pitch bend event to 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 pitch the normalized pitch value centered between -1 and 1 + */ + void hdPitchWheel(int delay, float pitch) noexcept; /** * @brief Send a aftertouch event to the synth * diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index eac5dc6f..7ab340ec 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -165,11 +165,21 @@ void sfz::Sfizz::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept synth->synth.noteOn(delay, noteNumber, velocity); } +void sfz::Sfizz::hdNoteOn(int delay, int noteNumber, float velocity) noexcept +{ + synth->synth.hdNoteOn(delay, noteNumber, velocity); +} + void sfz::Sfizz::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept { synth->synth.noteOff(delay, noteNumber, velocity); } +void sfz::Sfizz::hdNoteOff(int delay, int noteNumber, float velocity) noexcept +{ + synth->synth.hdNoteOff(delay, noteNumber, velocity); +} + void sfz::Sfizz::cc(int delay, int ccNumber, uint8_t ccValue) noexcept { synth->synth.cc(delay, ccNumber, ccValue); @@ -190,16 +200,31 @@ void sfz::Sfizz::pitchWheel(int delay, int pitch) noexcept synth->synth.pitchWheel(delay, pitch); } +void sfz::Sfizz::hdPitchWheel(int delay, float pitch) noexcept +{ + synth->synth.hdPitchWheel(delay, pitch); +} + void sfz::Sfizz::aftertouch(int delay, uint8_t aftertouch) noexcept { synth->synth.aftertouch(delay, aftertouch); } +void sfz::Sfizz::hdAftertouch(int delay, float aftertouch) noexcept +{ + synth->synth.hdAftertouch(delay, aftertouch); +} + void sfz::Sfizz::polyAftertouch(int delay, int noteNumber, uint8_t aftertouch) noexcept { synth->synth.polyAftertouch(delay, noteNumber, aftertouch); } +void sfz::Sfizz::hdPolyAftertouch(int delay, int noteNumber, float aftertouch) noexcept +{ + synth->synth.hdPolyAftertouch(delay, noteNumber, aftertouch); +} + void sfz::Sfizz::tempo(int delay, float secondsPerBeat) noexcept { synth->synth.tempo(delay, secondsPerBeat); diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 50afa403..5aa676a4 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -118,10 +118,18 @@ void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int note_number, char v { synth->synth.noteOn(delay, note_number, velocity); } +void sfizz_send_hd_note_on(sfizz_synth_t* synth, int delay, int note_number, float velocity) +{ + synth->synth.hdNoteOn(delay, note_number, velocity); +} void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int note_number, char velocity) { synth->synth.noteOff(delay, note_number, velocity); } +void sfizz_send_hd_note_off(sfizz_synth_t* synth, int delay, int note_number, float velocity) +{ + synth->synth.hdNoteOff(delay, note_number, velocity); +} void sfizz_send_cc(sfizz_synth_t* synth, int delay, int cc_number, char cc_value) { synth->synth.cc(delay, cc_number, cc_value); @@ -138,14 +146,26 @@ void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int pitch) { synth->synth.pitchWheel(delay, pitch); } +void sfizz_send_hd_pitch_wheel(sfizz_synth_t* synth, int delay, float pitch) +{ + synth->synth.hdPitchWheel(delay, pitch); +} void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, char aftertouch) { synth->synth.aftertouch(delay, aftertouch); } +void sfizz_send_hd_aftertouch(sfizz_synth_t* synth, int delay, float aftertouch) +{ + synth->synth.hdAftertouch(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_hd_poly_aftertouch(sfizz_synth_t* synth, int delay, int note_number, float aftertouch) +{ + synth->synth.hdPolyAftertouch(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);