Merge pull request #820 from jpcima/hd-apis

Add high-precision API for note, aftertouch and bend
This commit is contained in:
JP Cimalando 2021-04-12 23:51:31 +02:00 committed by GitHub
commit 5a4b1f5369
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 289 additions and 29 deletions

View file

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

View file

@ -19,12 +19,6 @@
#include <chrono>
#include <cstring>
template<class T>
constexpr int fastRound(T x)
{
return static_cast<int>(x + T{ 0.5 }); // NOLINT
}
static const char defaultSfzText[] =
"<region>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<int32>(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

View file

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

View file

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

View file

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

View file

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

View file

@ -1068,23 +1068,33 @@ void Synth::renderBlock(AudioSpan<float> 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);

View file

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

View file

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

View file

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