diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 5bafd04b3..ef35867c8 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -131,7 +131,9 @@ typedef struct LV2_URID sample_rate_uri; LV2_URID atom_object_uri; LV2_URID atom_float_uri; + LV2_URID atom_double_uri; LV2_URID atom_int_uri; + LV2_URID atom_long_uri; LV2_URID atom_urid_uri; LV2_URID atom_path_uri; LV2_URID patch_set_uri; @@ -150,6 +152,12 @@ typedef struct LV2_URID sfizz_check_modification_uri; LV2_URID sfizz_active_voices_uri; LV2_URID time_position_uri; + LV2_URID time_bar_uri; + LV2_URID time_bar_beat_uri; + LV2_URID time_beat_unit_uri; + LV2_URID time_beats_per_bar_uri; + LV2_URID time_beats_per_minute_uri; + LV2_URID time_speed_uri; // Sfizz related data sfizz_synth_t *synth; @@ -166,6 +174,14 @@ typedef struct float sample_rate; atomic_int must_update_midnam; + // Timing data + int bar; + float bar_beat; + int beats_per_bar; + int beat_unit; + float bpm_tempo; + float speed; + // Paths char bundle_path[MAX_BUNDLE_PATH_SIZE]; } sfizz_plugin_t; @@ -187,6 +203,14 @@ enum SFIZZ_ACTIVE_VOICES = 12, }; +enum +{ + SFIZZ_TIMEINFO_POSITION = 1 << 0, + SFIZZ_TIMEINFO_SIGNATURE = 1 << 1, + SFIZZ_TIMEINFO_TEMPO = 1 << 2, + SFIZZ_TIMEINFO_SPEED = 1 << 3, +}; + static void sfizz_lv2_state_free_path(LV2_State_Free_Path_Handle handle, char *path) @@ -210,7 +234,9 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self) self->nominal_block_length_uri = map->map(map->handle, LV2_BUF_SIZE__nominalBlockLength); self->sample_rate_uri = map->map(map->handle, LV2_PARAMETERS__sampleRate); self->atom_float_uri = map->map(map->handle, LV2_ATOM__Float); + self->atom_double_uri = map->map(map->handle, LV2_ATOM__Double); self->atom_int_uri = map->map(map->handle, LV2_ATOM__Int); + self->atom_long_uri = map->map(map->handle, LV2_ATOM__Long); self->atom_path_uri = map->map(map->handle, LV2_ATOM__Path); self->atom_urid_uri = map->map(map->handle, LV2_ATOM__URID); self->atom_object_uri = map->map(map->handle, LV2_ATOM__Object); @@ -230,6 +256,68 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self) self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus); self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification); self->time_position_uri = map->map(map->handle, LV2_TIME__Position); + self->time_bar_uri = map->map(map->handle, LV2_TIME__bar); + self->time_bar_beat_uri = map->map(map->handle, LV2_TIME__barBeat); + self->time_beat_unit_uri = map->map(map->handle, LV2_TIME__beatUnit); + self->time_beats_per_bar_uri = map->map(map->handle, LV2_TIME__beatsPerBar); + self->time_beats_per_minute_uri = map->map(map->handle, LV2_TIME__beatsPerMinute); + self->time_speed_uri = map->map(map->handle, LV2_TIME__speed); +} + +static bool +sfizz_atom_extract_real(sfizz_plugin_t *self, const LV2_Atom *atom, double *real) +{ + if (!atom) + return false; + + const LV2_URID type = atom->type; + + if (type == self->atom_int_uri && atom->size >= sizeof(int32_t)) { + *real = ((const LV2_Atom_Int *)atom)->body; + return true; + } + if (type == self->atom_long_uri && atom->size >= sizeof(int64_t)) { + *real = ((const LV2_Atom_Long *)atom)->body; + return true; + } + if (type == self->atom_float_uri && atom->size >= sizeof(float)) { + *real = ((const LV2_Atom_Float *)atom)->body; + return true; + } + if (type == self->atom_double_uri && atom->size >= sizeof(double)) { + *real = ((const LV2_Atom_Double *)atom)->body; + return true; + } + + return false; +} + +static bool +sfizz_atom_extract_integer(sfizz_plugin_t *self, const LV2_Atom *atom, int64_t *integer) +{ + if (!atom) + return false; + + const LV2_URID type = atom->type; + + if (type == self->atom_int_uri && atom->size >= sizeof(int32_t)) { + *integer = ((const LV2_Atom_Int *)atom)->body; + return true; + } + if (type == self->atom_long_uri && atom->size >= sizeof(int64_t)) { + *integer = ((const LV2_Atom_Long *)atom)->body; + return true; + } + if (type == self->atom_float_uri && atom->size >= sizeof(float)) { + *integer = (int64_t)((const LV2_Atom_Float *)atom)->body; + return true; + } + if (type == self->atom_double_uri && atom->size >= sizeof(double)) { + *integer = (int64_t)((const LV2_Atom_Double *)atom)->body; + return true; + } + + return false; } static void @@ -326,6 +414,19 @@ sfizz_lv2_get_default_scala_path(LV2_Handle instance, char *path, size_t size) snprintf(path, size, "%s/%s", self->bundle_path, DEFAULT_SCALA_FILE); } +static void +sfizz_lv2_update_timeinfo(sfizz_plugin_t *self, int delay, int updates) +{ + if (updates & SFIZZ_TIMEINFO_POSITION) + sfizz_send_time_position(self->synth, delay, self->bar, self->bar_beat); + if (updates & SFIZZ_TIMEINFO_SIGNATURE) + sfizz_send_time_signature(self->synth, delay, self->beats_per_bar, self->beat_unit); + if (updates & SFIZZ_TIMEINFO_TEMPO) + sfizz_send_tempo(self->synth, delay, 60.0f / self->bpm_tempo); + if (updates & SFIZZ_TIMEINFO_SPEED) + sfizz_send_playback_state(self->synth, delay, self->speed > 0); +} + static LV2_Handle instantiate(const LV2_Descriptor *descriptor, double rate, @@ -361,6 +462,14 @@ instantiate(const LV2_Descriptor *descriptor, self->check_modification = false; self->sample_counter = 0; + // Initial timing + self->bar = 0; + self->bar_beat = 0; + self->beats_per_bar = 4; + self->beat_unit = 4; + self->bpm_tempo = 120; + self->speed = 1; + // Get the features from the host and populate the structure for (const LV2_Feature *const *f = features; *f; f++) { @@ -472,6 +581,8 @@ instantiate(const LV2_Descriptor *descriptor, sfizz_load_file(self->synth, self->sfz_file_path); sfizz_load_scala_file(self->synth, self->scala_file_path); + sfizz_lv2_update_timeinfo(self, 0, ~0); + return (LV2_Handle)self; } @@ -751,6 +862,8 @@ run(LV2_Handle instance, uint32_t sample_count) LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) { + const int delay = (int)ev->time.frames; + // If the received atom is an object/patch message if (ev->body.type == self->atom_object_uri) { @@ -779,7 +892,60 @@ run(LV2_Handle instance, uint32_t sample_count) } else if (obj->body.otype == self->time_position_uri) { - // TODO: Handle time position atom + const LV2_Atom *bar_atom = NULL; + const LV2_Atom *bar_beat_atom = NULL; + const LV2_Atom *beat_unit_atom = NULL; + const LV2_Atom *beats_per_bar_atom = NULL; + const LV2_Atom *beats_per_minute_atom = NULL; + const LV2_Atom *speed_atom = NULL; + + lv2_atom_object_get( + obj, + self->time_bar_uri, &bar_atom, + self->time_bar_beat_uri, &bar_beat_atom, + self->time_beats_per_bar_uri, &beats_per_bar_atom, + self->time_beats_per_minute_uri, &beats_per_minute_atom, + self->time_beat_unit_uri, &beat_unit_atom, + self->time_speed_uri, &speed_atom, + 0); + + int updates = 0; + + int64_t bar; + double bar_beat; + if (sfizz_atom_extract_integer(self, bar_atom, &bar)) { + self->bar = (int)bar; + updates |= SFIZZ_TIMEINFO_POSITION; + } + if (sfizz_atom_extract_real(self, bar_beat_atom, &bar_beat)) { + self->bar_beat = (float)bar_beat; + updates |= SFIZZ_TIMEINFO_POSITION; + } + + double beats_per_bar; + int64_t beat_unit; + if (sfizz_atom_extract_real(self, beats_per_bar_atom, &beats_per_bar)) { + self->beats_per_bar = (int)beats_per_bar; + updates |= SFIZZ_TIMEINFO_SIGNATURE; + } + if (sfizz_atom_extract_integer(self, beat_unit_atom, &beat_unit)) { + self->beat_unit = (int)beat_unit; + updates |= SFIZZ_TIMEINFO_SIGNATURE; + } + + double tempo; + if (sfizz_atom_extract_real(self, beats_per_minute_atom, &tempo)) { + self->bpm_tempo = (float)tempo; + updates |= SFIZZ_TIMEINFO_TEMPO; + } + + double speed; + if (sfizz_atom_extract_real(self, speed_atom, &speed)) { + self->speed = (float)speed; + updates |= SFIZZ_TIMEINFO_SPEED; + } + + sfizz_lv2_update_timeinfo(self, delay, updates); } else { diff --git a/src/sfizz.h b/src/sfizz.h index 05034e7d6..5c8f7d1a6 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -302,13 +302,43 @@ 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 tempo event. (CURRENTLY UNIMPLEMENTED) + * @brief Send a tempo event. * * @param synth The synth. * @param delay The delay. - * @param seconds_per_quarter The seconds per quarter. + * @param seconds_per_beat The seconds per beat. */ -SFIZZ_EXPORTED_API void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter); +SFIZZ_EXPORTED_API void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_beat); + +/** + * @brief Send the time signature. + * + * @param synth The synth. + * @param delay The delay. + * @param beats_per_bar The number of beats per bar, or time signature numerator. + * @param beat_unit The note corresponding to one beat, or time signature denominator. + */ +SFIZZ_EXPORTED_API void sfizz_send_time_signature(sfizz_synth_t* synth, int delay, int beats_per_bar, int beat_unit); + +/** + * @brief Send the time position. + * + * @param synth The synth. + * @param delay The delay. + * @param bar The current bar. + * @param bar_beat The fractional position of the current beat within the bar. + */ +SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, float bar_beat); + +/** + * @brief Send the playback state. + * + * @param synth The synth. + * @param delay The delay. + * @param playback_state The playback state, 1 if playing, 0 if stopped. + */ +SFIZZ_EXPORTED_API void sfizz_send_playback_state(sfizz_synth_t* synth, int delay, int playback_state); + /** * @brief Render a block audio data into a stereo channel. No other channel diff --git a/src/sfizz.hpp b/src/sfizz.hpp index 40d9f424f..957c7d413 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -291,13 +291,39 @@ class SFIZZ_EXPORTED_API Sfizz void aftertouch(int delay, uint8_t aftertouch) noexcept; /** - * @brief Send a tempo event to the synth. (CURRENTLY UNIMPLEMENTED) + * @brief Send a tempo 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 secondsPerQuarter the new period of the quarter note. + * @param secondsPerBeat the new period of the beat. */ - void tempo(int delay, float secondsPerQuarter) noexcept; + void tempo(int delay, float secondsPerBeat) noexcept; + + /** + * @brief Send the time signature. + * + * @param delay The delay. + * @param beats_per_bar The number of beats per bar, or time signature numerator. + * @param beat_unit The note corresponding to one beat, or time signature denominator. + */ + void timeSignature(int delay, int beatsPerBar, int beatUnit); + + /** + * @brief Send the time position. + * + * @param delay The delay. + * @param bar The current bar. + * @param bar_beat The fractional position of the current beat within the bar. + */ + void timePosition(int delay, int bar, float barBeat); + + /** + * @brief Send the playback state. + * + * @param delay The delay. + * @param playback_state The playback state, 1 if playing, 0 if stopped. + */ + void playbackState(int delay, int playbackState); /** * @brief Render an block of audio data in the buffer. This call will reset diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index afea3140f..4eaff991d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1116,6 +1116,29 @@ void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept { ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; } +void sfz::Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) +{ + ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + + (void)delay; + (void)beatsPerBar; + (void)beatUnit; +} +void sfz::Synth::timePosition(int delay, int bar, float barBeat) +{ + ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + + (void)delay; + (void)bar; + (void)barBeat; +} +void sfz::Synth::playbackState(int delay, int playbackState) +{ + ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + + (void)delay; + (void)playbackState; +} int sfz::Synth::getNumRegions() const noexcept { diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 3b7ce3b8f..30ace997a 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -392,6 +392,29 @@ class Synth final : public Voice::StateListener, public Parser::Listener { * @param secondsPerQuarter the new period of the quarter note */ void tempo(int delay, float secondsPerQuarter) noexcept; + /** + * @brief Send the time signature. + * + * @param delay The delay. + * @param beats_per_bar The number of beats per bar, or time signature numerator. + * @param beat_unit The note corresponding to one beat, or time signature denominator. + */ + void timeSignature(int delay, int beatsPerBar, int beatUnit); + /** + * @brief Send the time position. + * + * @param delay The delay. + * @param bar The current bar. + * @param bar_beat The fractional position of the current beat within the bar. + */ + void timePosition(int delay, int bar, float barBeat); + /** + * @brief Send the playback state. + * + * @param delay The delay. + * @param playback_state The playback state, 1 if playing, 0 if stopped. + */ + void playbackState(int delay, int playbackState); /** * @brief Render an block of audio data in the buffer. This call will reset * the synth in its waiting state for the next batch of events. The size of diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index fbdd041cf..13cfd8a95 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -153,9 +153,24 @@ void sfz::Sfizz::aftertouch(int delay, uint8_t aftertouch) noexcept synth->aftertouch(delay, aftertouch); } -void sfz::Sfizz::tempo(int delay, float secondsPerQuarter) noexcept +void sfz::Sfizz::tempo(int delay, float secondsPerBeat) noexcept { - synth->tempo(delay, secondsPerQuarter); + synth->tempo(delay, secondsPerBeat); +} + +void sfz::Sfizz::timeSignature(int delay, int beatsPerBar, int beatUnit) +{ + synth->timeSignature(delay, beatsPerBar, beatUnit); +} + +void sfz::Sfizz::timePosition(int delay, int bar, float barBeat) +{ + synth->timePosition(delay, bar, barBeat); +} + +void sfz::Sfizz::playbackState(int delay, int playbackState) +{ + synth->playbackState(delay, playbackState); } void sfz::Sfizz::renderBlock(float** buffers, size_t numSamples, int /*numOutputs*/) noexcept diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index de33d8159..3e103b2eb 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -160,6 +160,21 @@ void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter auto self = reinterpret_cast(synth); self->tempo(delay, seconds_per_quarter); } +void sfizz_send_time_signature(sfizz_synth_t* synth, int delay, int beats_per_bar, int beat_unit) +{ + auto self = reinterpret_cast(synth); + self->timeSignature(delay, beats_per_bar, beat_unit); +} +void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, float bar_beat) +{ + auto self = reinterpret_cast(synth); + self->timePosition(delay, bar, bar_beat); +} +void sfizz_send_playback_state(sfizz_synth_t* synth, int delay, int playback_state) +{ + auto self = reinterpret_cast(synth); + self->playbackState(delay, playback_state); +} void sfizz_render_block(sfizz_synth_t* synth, float** channels, int num_channels, int num_frames) { diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 56d951e3e..e64e184fe 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -12,8 +12,6 @@ #include "pluginterfaces/vst/ivstparameterchanges.h" #include -#pragma message("TODO: send tempo") // NOLINT - template constexpr int fastRound(T x) { @@ -55,6 +53,13 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) _currentStretchedTuning = 0.0; loadSfzFileOrDefault(*_synth, {}); + _synth->tempo(0, 0.5); + _timeSigNumerator = 4; + _timeSigDenominator = 4; + _synth->timeSignature(0, _timeSigNumerator, _timeSigDenominator); + _synth->timePosition(0, 0, 0); + _synth->playbackState(0, 0); + return result; } @@ -143,6 +148,9 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) { sfz::Sfizz& synth = *_synth; + if (data.processContext) + updateTimeInfo(*data.processContext); + if (Vst::IParameterChanges* pc = data.inputParameterChanges) processParameterChanges(*pc); @@ -198,6 +206,29 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) return kResultTrue; } +void SfizzVstProcessor::updateTimeInfo(const Vst::ProcessContext& context) +{ + sfz::Sfizz& synth = *_synth; + + if (context.state & context.kTempoValid) + synth.tempo(0, 60.0f / context.tempo); + + if (context.state & context.kTimeSigValid) { + _timeSigNumerator = context.timeSigNumerator; + _timeSigDenominator = context.timeSigDenominator; + synth.timeSignature(0, _timeSigNumerator, _timeSigDenominator); + } + + if (context.state & context.kProjectTimeMusicValid) { + double beats = context.projectTimeMusic * 0.25 * _timeSigDenominator; + double bars = beats / _timeSigNumerator; + beats -= int(bars) * _timeSigNumerator; + synth.timePosition(0, int(bars), float(beats)); + } + + synth.playbackState(0, (context.state & context.kPlaying) != 0); +} + void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc) { uint32 paramCount = pc.getParameterCount(); diff --git a/vst/SfizzVstProcessor.h b/vst/SfizzVstProcessor.h index f215baf23..76af84a67 100644 --- a/vst/SfizzVstProcessor.h +++ b/vst/SfizzVstProcessor.h @@ -64,6 +64,11 @@ class SfizzVstProcessor : public Vst::AudioEffect { uint32 _fileChangeCounter = 0; uint32 _fileChangePeriod = 0; + // time info + int _timeSigNumerator = 0; + int _timeSigDenominator = 0; + void updateTimeInfo(const Vst::ProcessContext& context); + // messaging struct RTMessage { const char* type;