From baf228802b33d0f9e85f91f98b33b71af3285c42 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 14 Aug 2020 04:18:58 +0200 Subject: [PATCH] Add the beat clock --- common.mk | 2 + lv2/sfizz.c | 6 +- src/CMakeLists.txt | 4 + src/sfizz.h | 2 +- src/sfizz.hpp | 2 +- src/sfizz/BeatClock.cpp | 230 ++++++++++++++++++++++++++++++++++++ src/sfizz/BeatClock.h | 160 +++++++++++++++++++++++++ src/sfizz/Metronome.cpp | 110 +++++++++++++++++ src/sfizz/Metronome.h | 59 +++++++++ src/sfizz/Resources.h | 9 ++ src/sfizz/Synth.cpp | 31 +++-- src/sfizz/Synth.h | 2 +- src/sfizz/sfizz.cpp | 2 +- src/sfizz/sfizz_wrapper.cpp | 2 +- vst/SfizzVstProcessor.cpp | 2 +- 15 files changed, 604 insertions(+), 19 deletions(-) create mode 100644 src/sfizz/BeatClock.cpp create mode 100644 src/sfizz/BeatClock.h create mode 100644 src/sfizz/Metronome.cpp create mode 100644 src/sfizz/Metronome.h diff --git a/common.mk b/common.mk index 99c0881c..4f3289fa 100644 --- a/common.mk +++ b/common.mk @@ -48,6 +48,7 @@ SFIZZ_CXX_FLAGS = $(SFIZZ_C_FLAGS) SFIZZ_SOURCES = \ src/sfizz/ADSREnvelope.cpp \ src/sfizz/AudioReader.cpp \ + src/sfizz/BeatClock.cpp \ src/sfizz/Curve.cpp \ src/sfizz/effects/Apan.cpp \ src/sfizz/Effects.cpp \ @@ -90,6 +91,7 @@ SFIZZ_SOURCES = \ src/sfizz/LFO.cpp \ src/sfizz/LFODescription.cpp \ src/sfizz/Messaging.cpp \ + src/sfizz/Metronome.cpp \ src/sfizz/MidiState.cpp \ src/sfizz/OpcodeCleanup.cpp \ src/sfizz/Opcode.cpp \ diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 44d9077a..58edd5bb 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -175,11 +175,11 @@ typedef struct // Timing data int bar; - float bar_beat; + double bar_beat; int beats_per_bar; int beat_unit; - float bpm_tempo; - float speed; + double bpm_tempo; + double speed; // Paths char bundle_path[MAX_BUNDLE_PATH_SIZE]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8171541..b98fbf7d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ set (SFIZZ_HEADERS sfizz/AudioBuffer.h sfizz/AudioReader.h sfizz/AudioSpan.h + sfizz/BeatClock.h sfizz/Buffer.h sfizz/BufferPool.h sfizz/CCMap.h @@ -79,6 +80,7 @@ set (SFIZZ_HEADERS sfizz/LFO.h sfizz/LFODescription.h sfizz/MathHelpers.h + sfizz/Metronome.h sfizz/MidiState.h sfizz/ModifierHelpers.h sfizz/OnePoleFilter.h @@ -149,6 +151,8 @@ set (SFIZZ_SOURCES sfizz/PowerFollower.cpp sfizz/FlexEGDescription.cpp sfizz/FlexEnvelope.cpp + sfizz/BeatClock.cpp + sfizz/Metronome.cpp sfizz/SynthMessaging.cpp sfizz/modulations/ModId.cpp sfizz/modulations/ModKey.cpp diff --git a/src/sfizz.h b/src/sfizz.h index b20e7eae..953bf2a0 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -375,7 +375,7 @@ SFIZZ_EXPORTED_API void sfizz_send_time_signature(sfizz_synth_t* synth, int dela * @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); +SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, double bar_beat); /** * @brief Send the playback state. diff --git a/src/sfizz.hpp b/src/sfizz.hpp index f86eea8a..3a85e31d 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -344,7 +344,7 @@ public: * @param bar The current bar. * @param barBeat The fractional position of the current beat within the bar. */ - void timePosition(int delay, int bar, float barBeat); + void timePosition(int delay, int bar, double barBeat); /** * @brief Send the playback state. diff --git a/src/sfizz/BeatClock.cpp b/src/sfizz/BeatClock.cpp new file mode 100644 index 00000000..3b35f4e7 --- /dev/null +++ b/src/sfizz/BeatClock.cpp @@ -0,0 +1,230 @@ +// 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 "BeatClock.h" +#include "Config.h" +#include "Debug.h" +#include +#include + +namespace sfz { + +bool TimeSignature::operator==(const TimeSignature& other) const +{ + return beatsPerBar == other.beatsPerBar && beatUnit == other.beatUnit; +} + +bool TimeSignature::operator!=(const TimeSignature& other) const +{ + return !operator==(other); +} + +/// +BBT BBT::toSignature(TimeSignature oldSig, TimeSignature newSig) const +{ + double beatsInOldSig = toBeats(oldSig); + double beatsInNewSig = beatsInOldSig * newSig.beatUnit / oldSig.beatUnit; + return BBT::fromBeats(newSig, beatsInNewSig); +} + +double BBT::toBeats(TimeSignature sig) const +{ + return beat + bar * sig.beatsPerBar; +} + +BBT BBT::fromBeats(TimeSignature sig, double beats) +{ + int newBar = static_cast(beats / sig.beatsPerBar); + double newBeat = beats - newBar * sig.beatsPerBar; + return BBT(newBar, newBeat); +} + +double BBT::toBars(TimeSignature sig) const +{ + return bar + beat / sig.beatsPerBar; +} + +/// +constexpr int BeatClock::resolution; + +auto BeatClock::quantize(double beats) -> qbeats_t +{ + double d = beats * (1 << resolution); + d = std::copysign(0.5 + std::fabs(d), d); + return static_cast(d); +} + +template +T BeatClock::dequantize(qbeats_t qbeats) +{ + return qbeats / static_cast(1 << resolution); +} + +/// +BeatClock::BeatClock() +{ + setSampleRate(config::defaultSampleRate); + setSamplesPerBlock(config::defaultSamplesPerBlock); +} + +void BeatClock::clear() +{ + beatsPerSecond_ = 2.0; + timeSig_ = { 4, 4 }; + isPlaying_ = false; + + lastHostPos_ = { 0, 0 }; + lastClientPos_ = { 0, 0 }; +} + +void BeatClock::beginCycle(unsigned numFrames) +{ + currentCycleFrames_ = numFrames; + currentCycleFill_ = 0; + currentCycleStartPos_ = lastClientPos_; +} + +void BeatClock::endCycle() +{ + fillBufferUpTo(currentCycleFrames_); +} + +void BeatClock::setSampleRate(double sampleRate) +{ + samplePeriod_ = 1.0 / sampleRate; +} + +void BeatClock::setSamplesPerBlock(unsigned samplesPerBlock) +{ + runningBeat_.resize(samplesPerBlock); + runningBeatsPerBar_.resize(samplesPerBlock); +} + +void BeatClock::setTempo(unsigned delay, double secondsPerBeat) +{ + fillBufferUpTo(delay); + + beatsPerSecond_ = 1.0 / secondsPerBeat; +} + +void BeatClock::setTimeSignature(unsigned delay, TimeSignature newSig) +{ + fillBufferUpTo(delay); + + if (!newSig.valid()) { + CHECKFALSE; + return; + } + + TimeSignature oldSig = timeSig_; + if (oldSig == newSig) + return; + + timeSig_ = newSig; + + // convert time to new signature + lastHostPos_ = lastHostPos_.toSignature(oldSig, newSig); + lastClientPos_ = lastClientPos_.toSignature(oldSig, newSig); +} + +void BeatClock::setTimePosition(unsigned delay, BBT newPos) +{ + fillBufferUpTo(delay); + + lastHostPos_ = newPos; + + // apply host position in the next frame + mustApplyHostPos_ = true; +} + +void BeatClock::setPlaying(unsigned delay, bool playing) +{ + fillBufferUpTo(delay); + + isPlaying_ = playing; +} + +absl::Span BeatClock::getRunningBeat() +{ + fillBufferUpTo(currentCycleFrames_); + + return absl::MakeConstSpan(runningBeat_.data(), currentCycleFrames_); +} + +absl::Span BeatClock::getRunningBeatsPerBar() +{ + fillBufferUpTo(currentCycleFrames_); + + return absl::MakeConstSpan(runningBeatsPerBar_.data(), currentCycleFrames_); +} + +void BeatClock::fillBufferUpTo(unsigned delay) +{ + int *beatData = runningBeat_.data(); + int *beatsPerBarData = runningBeatsPerBar_.data(); + unsigned fill = currentCycleFill_; + + const TimeSignature sig = timeSig_; + for (unsigned i = fill; i < delay; ++i) + beatsPerBarData[i] = sig.beatsPerBar; + + if (!isPlaying_) { + for (; fill < delay; ++fill) + beatData[fill] = 0; + currentCycleFill_ = fill; + return; + } + + BBT clientPos = lastClientPos_; + const double beatsPerFrame = beatsPerSecond_ * samplePeriod_; + + const BBT hostPos = lastHostPos_; + bool mustApplyHostPos = mustApplyHostPos_; + + for (; fill < delay; ++fill) { + clientPos = BBT::fromBeats(sig, clientPos.toBeats(sig) + beatsPerFrame); + clientPos = mustApplyHostPos ? hostPos : clientPos; + mustApplyHostPos = false; + + // quantization to nearest for prevention of rounding errors + beatData[fill] = dequantize(quantize(clientPos.toBeats(sig))); + +#if 0 + BBT oldClientPos = clientPos; + + // quantization to nearest for prevention of rounding errors + qbeats_t oldQbeats = quantize(oldClientPos.toBeats(sig)); + qbeats_t qbeats = quantize(clientPos.toBeats(sig)); + + int oldBeatNumber = dequantize(oldQbeats); + int beatNumber = dequantize(qbeats); + + int beatIncrement = std::max(0, beatNumber - oldBeatNumber); + int beatDistanceToNextBar = sig.beatsPerBar - (oldBeatNumber % sig.beatsPerBar); + int barIncrement = (beatIncrement < beatDistanceToNextBar) ? 0 : + (1 + (beatIncrement - beatDistanceToNextBar) / sig.beatsPerBar); + + beatData[fill] = beatIncrement; + barData[fill] = barIncrement; +#endif + } + + currentCycleFill_ = fill; + lastClientPos_ = clientPos; + mustApplyHostPos_ = mustApplyHostPos; +} + +} // namespace sfz + +std::ostream& operator<<(std::ostream& os, const sfz::BBT& pos) +{ + return os << pos.bar << ':' << std::fixed << pos.beat; +} + +std::ostream& operator<<(std::ostream& os, const sfz::TimeSignature& sig) +{ + return os << sig.beatsPerBar << '/' << sig.beatUnit; +} diff --git a/src/sfizz/BeatClock.h b/src/sfizz/BeatClock.h new file mode 100644 index 00000000..e5fbfcba --- /dev/null +++ b/src/sfizz/BeatClock.h @@ -0,0 +1,160 @@ +// 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 +#include +#include + +namespace sfz { + +/** + * @brief Musical time signature + */ +struct TimeSignature { + TimeSignature() {} + TimeSignature(int beatsPerBar, int beatUnit) : beatsPerBar(beatsPerBar), beatUnit(beatUnit) {} + + /** + * @brief Check the signature validity. + * Valid signatures have a strictly positive numerator and denominator. + */ + bool valid() const { return beatsPerBar > 0 && beatUnit > 0; } + + bool operator==(const TimeSignature& other) const; + bool operator!=(const TimeSignature& other) const; + + /** + * @brief Time signature numerator, indicating the number of beats in a bar + */ + int beatsPerBar = 0; + /** + * @brief Time signature denominator, indicating the type of note (4=quarter) + */ + int beatUnit = 0; +}; + +/** + * @brief Musical time in BBT form + */ +struct BBT { + BBT() {} + BBT(int bar, double beat) : bar(bar), beat(beat) {} + + /** + * @brief Convert the time to a different signature. + */ + BBT toSignature(TimeSignature oldSig, TimeSignature newSig) const; + /** + * @brief Convert the time to a fractional quantity in beats. + */ + double toBeats(TimeSignature sig) const; + /** + * @brief Convert the time to a fractional quantity in bars. + */ + double toBars(TimeSignature sig) const; + /** + * @brief Convert the fractional quantity in beats to musical time. + */ + static BBT fromBeats(TimeSignature sig, double beats); + + /** + * @brief Bar number + */ + int bar = 0; + /** + * @brief Beat and tick, stored in the integral and fractional parts + */ + double beat = 0; +}; + +class BeatClock { +public: + BeatClock(); + + /** + * @brief Set the sample rate. + */ + void setSampleRate(double sampleRate); + /** + * @brief Set the block size. + */ + void setSamplesPerBlock(unsigned samplesPerBlock); + /** + * @brief Reinitialize the current state. + */ + void clear(); + /** + * @brief Start a new cycle of clock processing. + */ + void beginCycle(unsigned numFrames); + /** + * @brief End the current cycle of clock processing. + */ + void endCycle(); + /** + * @brief Set the tempo. + */ + void setTempo(unsigned delay, double secondsPerBeat); + /** + * @brief Set the time signature. + */ + void setTimeSignature(unsigned delay, TimeSignature newSig); + /** + * @brief Set the time position. + */ + void setTimePosition(unsigned delay, BBT newPos); + /** + * @brief Set whether the clock is ticking or stopped. + */ + void setPlaying(unsigned delay, bool playing); + /** + * @brief Get the beat number for each frame of the current cycle. + */ + absl::Span getRunningBeat(); + /** + * @brief Get the time signature numerator for each frame of the current cycle. + */ + absl::Span getRunningBeatsPerBar(); + +private: + void fillBufferUpTo(unsigned delay); + +private: + double samplePeriod_ = 0; + + // quantization + typedef int64_t qbeats_t; + static constexpr int resolution = 16; // bits + static qbeats_t quantize(int beats) { return beats * (1 << resolution); } + static qbeats_t quantize(double beats); + template static T dequantize(qbeats_t qbeats); + + // status of current cycle + unsigned currentCycleFrames_ = 0; + unsigned currentCycleFill_ = 0; + BBT currentCycleStartPos_; + + // musical time information from host + double beatsPerSecond_ = 2.0; + TimeSignature timeSig_ { 4, 4 }; + bool isPlaying_ = false; + + // last time position received from host + BBT lastHostPos_; + bool mustApplyHostPos_ = false; + + // plugin-side counter + BBT lastClientPos_; + + std::vector runningBeat_; + std::vector runningBeatsPerBar_; +}; + +} // namespace sfz + +std::ostream& operator<<(std::ostream& os, const sfz::BBT& pos); +std::ostream& operator<<(std::ostream& os, const sfz::TimeSignature& sig); diff --git a/src/sfizz/Metronome.cpp b/src/sfizz/Metronome.cpp new file mode 100644 index 00000000..5b0b1ff0 --- /dev/null +++ b/src/sfizz/Metronome.cpp @@ -0,0 +1,110 @@ +// 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 "Metronome.h" +#include "Config.h" + +namespace sfz { + +Metronome::Metronome() +{ + fGain = 0.5f; + init(config::defaultSampleRate); +} + +void Metronome::init(float sampleRate) +{ + fConst0 = std::min(192000.0f, std::max(1.0f, float(sampleRate))); + fConst1 = std::cos((2764.60156f / fConst0)); + fConst2 = std::sqrt(std::max(0.0f, ((fConst1 + 1.0f) / (1.0f - fConst1)))); + fConst3 = (1.0f / fConst2); + fConst4 = std::cos((5529.20312f / fConst0)); + fConst5 = std::sqrt(std::max(0.0f, ((fConst4 + 1.0f) / (1.0f - fConst4)))); + fConst6 = (1.0f / fConst5); + fConst7 = std::max(1.0f, (0.00499999989f * fConst0)); + fConst8 = (1.0f / fConst7); + fConst9 = (1.0f / std::max(1.0f, (0.100000001f * fConst0))); + clear(); +} + +void Metronome::clear() +{ + for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) { + iVec0[l0] = 0; + } + for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) { + iVec1[l1] = 0; + } + for (int l2 = 0; (l2 < 2); l2 = (l2 + 1)) { + iVec2[l2] = 0; + } + for (int l3 = 0; (l3 < 2); l3 = (l3 + 1)) { + iRec0[l3] = 0; + } + for (int l4 = 0; (l4 < 2); l4 = (l4 + 1)) { + fVec3[l4] = 0.0f; + } + for (int l5 = 0; (l5 < 2); l5 = (l5 + 1)) { + fRec1[l5] = 0.0f; + } + for (int l6 = 0; (l6 < 2); l6 = (l6 + 1)) { + fRec2[l6] = 0.0f; + } + for (int l7 = 0; (l7 < 2); l7 = (l7 + 1)) { + fVec4[l7] = 0.0f; + } + for (int l8 = 0; (l8 < 2); l8 = (l8 + 1)) { + fRec3[l8] = 0.0f; + } + for (int l9 = 0; (l9 < 2); l9 = (l9 + 1)) { + fRec4[l9] = 0.0f; + } + for (int l10 = 0; (l10 < 2); l10 = (l10 + 1)) { + iRec5[l10] = 0; + } +} + +void Metronome::processAdding(const int* beats, const int* beatsPerBar, float* outputL, float* outputR, int numFrames) +{ + float fSlow0 = float(fGain); + for (int i = 0; (i < numFrames); i = (i + 1)) { + int iTemp0 = int(beats[i]); + iVec0[0] = iTemp0; + iVec1[0] = 1; + int iTemp1 = ((iTemp0 - iVec0[1]) > 0); + iVec2[0] = iTemp1; + iRec0[0] = (iTemp1 ? ((iTemp0 % int(beatsPerBar[i])) == 0) : iRec0[1]); + fVec3[0] = fConst2; + float fTemp2 = float((1 - iVec1[1])); + float fTemp3 = (fConst3 * (fRec2[1] * (fTemp2 + fVec3[1]))); + float fTemp4 = (fConst1 * (fTemp3 + fRec1[1])); + fRec1[0] = (fTemp4 + (fTemp2 + fTemp3)); + fRec2[0] = (fTemp4 - fRec1[1]); + fVec4[0] = fConst5; + float fTemp5 = (fConst6 * (fRec4[1] * (fTemp2 + fVec4[1]))); + float fTemp6 = (fConst4 * (fTemp5 + fRec3[1])); + fRec3[0] = (fTemp6 + (fTemp2 + fTemp5)); + fRec4[0] = (fTemp6 - fRec3[1]); + iRec5[0] = (((iRec5[1] + (iRec5[1] > 0)) * (iTemp1 <= iVec2[1])) + (iTemp1 > iVec2[1])); + float fTemp7 = float(iRec5[0]); + float fTemp8 = (fSlow0 * ((iRec0[0] ? (0.0f - (fConst5 * fRec4[0])) : (0.0f - (fConst2 * fRec2[0]))) * std::max(0.0f, std::min((fConst8 * fTemp7), ((fConst9 * (fConst7 - fTemp7)) + 1.0f))))); + outputL[i] += float(fTemp8); + outputR[i] += float(fTemp8); + iVec0[1] = iVec0[0]; + iVec1[1] = iVec1[0]; + iVec2[1] = iVec2[0]; + iRec0[1] = iRec0[0]; + fVec3[1] = fVec3[0]; + fRec1[1] = fRec1[0]; + fRec2[1] = fRec2[0]; + fVec4[1] = fVec4[0]; + fRec3[1] = fRec3[0]; + fRec4[1] = fRec4[0]; + iRec5[1] = iRec5[0]; + } +} + +} // namespace sfz diff --git a/src/sfizz/Metronome.h b/src/sfizz/Metronome.h new file mode 100644 index 00000000..55be83bc --- /dev/null +++ b/src/sfizz/Metronome.h @@ -0,0 +1,59 @@ +// 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 +#include + +namespace sfz { + +class Metronome { +public: + Metronome(); + void init(float sampleRate); + void clear(); + void processAdding(const int* beats, const int* beatsPerBar, float* outputL, float* outputR, int numFrames); + void setGain(float gain) { fGain = gain; } + +private: + float fGain; + int iVec0[2]; + int iVec1[2]; + int iVec2[2]; + int iRec0[2]; + float fConst0; + float fConst1; + float fConst2; + float fVec3[2]; + float fConst3; + float fRec1[2]; + float fRec2[2]; + float fConst4; + float fConst5; + float fVec4[2]; + float fConst6; + float fRec3[2]; + float fRec4[2]; + float fConst7; + float fConst8; + int iRec5[2]; + float fConst9; + + /* + import("stdfaust.lib"); + + process(beats, beatsPerBar) = tone : *(envelope) <: (_, _) with { + gain = hslider("[1] Gain", 0.5, 0.0, 1.0, 0.001); + beatNumber = int(beats); + beatIncrement = beatNumber-beatNumber'; + tone = (os.oscws(440.0), os.oscws(880.0)) : select2(toneSelect); + toneSelect = x letrec { 'x = ba.if(beatIncrement>0, (beatNumber%int(beatsPerBar))==0, x); }; + envelope = (beatIncrement>0) : en.ar(5e-3, 100e-3) : *(gain); + }; + */ +}; + +} // namespace sfz diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 577ff756..27de6f1c 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -13,6 +13,8 @@ #include "Wavetables.h" #include "Curve.h" #include "Tuning.h" +#include "BeatClock.h" +#include "Metronome.h" #include "modulations/ModMatrix.h" #include "absl/types/optional.h" @@ -32,11 +34,15 @@ struct Resources Tuning tuning; absl::optional stretch; ModMatrix modMatrix; + BeatClock beatClock; + Metronome metronome; void setSampleRate(float samplerate) { midiState.setSampleRate(samplerate); modMatrix.setSampleRate(samplerate); + beatClock.setSampleRate(samplerate); + metronome.init(samplerate); } void setSamplesPerBlock(int samplesPerBlock) @@ -44,6 +50,7 @@ struct Resources bufferPool.setBufferSize(samplesPerBlock); midiState.setSamplesPerBlock(samplesPerBlock); modMatrix.setSamplesPerBlock(samplesPerBlock); + beatClock.setSamplesPerBlock(samplesPerBlock); } void clear() @@ -54,6 +61,8 @@ struct Resources logger.clear(); midiState.reset(); modMatrix.clear(); + beatClock.clear(); + metronome.clear(); } }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 3ea3236f..458aca2b 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -856,6 +856,9 @@ void Synth::renderBlock(AudioSpan buffer) noexcept ModMatrix& mm = impl.resources_.modMatrix; mm.beginCycle(numFrames); + BeatClock& bc = impl.resources_.beatClock; + bc.beginCycle(numFrames); + { // Clear effect busses ScopedTiming logger { callbackBreakdown.effects }; for (auto& bus : impl.effectBuses_) { @@ -918,9 +921,20 @@ void Synth::renderBlock(AudioSpan buffer) noexcept // Apply the master volume buffer.applyGain(db2mag(impl.volume_)); + // Process the metronome (debugging tool for host time info) + constexpr bool metronomeEnabled = false; + if (metronomeEnabled) { + impl.resources_.metronome.processAdding( + bc.getRunningBeat().data(), bc.getRunningBeatsPerBar().data(), + buffer.getChannel(0), buffer.getChannel(1), numFrames); + } + // Perform any remaining modulators mm.endCycle(); + // Advance the clock to the end of cycle + bc.endCycle(); + { // Clear events and advance midi time ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; impl.resources_.midiState.advanceTime(buffer.getNumFrames()); @@ -1167,36 +1181,33 @@ void Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; } -void Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept +void Synth::tempo(int delay, float secondsPerBeat) noexcept { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + + impl.resources_.beatClock.setTempo(delay, secondsPerBeat); } void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - (void)delay; - (void)beatsPerBar; - (void)beatUnit; + impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit)); } -void Synth::timePosition(int delay, int bar, float barBeat) +void Synth::timePosition(int delay, int bar, double barBeat) { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - (void)delay; - (void)bar; - (void)barBeat; + impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat)); } void Synth::playbackState(int delay, int playbackState) { Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - (void)delay; - (void)playbackState; + impl.resources_.beatClock.setPlaying(delay, playbackState == 1); } int Synth::getNumRegions() const noexcept diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 0e53b17e..6e0a715f 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -396,7 +396,7 @@ public: * @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); + void timePosition(int delay, int bar, double barBeat); /** * @brief Send the playback state. * diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index 18b2a10c..9ed4a0d8 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -164,7 +164,7 @@ 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) +void sfz::Sfizz::timePosition(int delay, int bar, double barBeat) { synth->timePosition(delay, bar, barBeat); } diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 0ca6de29..3e7088c3 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -166,7 +166,7 @@ void sfizz_send_time_signature(sfizz_synth_t* synth, int delay, int beats_per_ba 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) +void sfizz_send_time_position(sfizz_synth_t* synth, int delay, int bar, double bar_beat) { auto* self = reinterpret_cast(synth); self->timePosition(delay, bar, bar_beat); diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 93969467..0380d3c7 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -286,7 +286,7 @@ void SfizzVstProcessor::updateTimeInfo(const Vst::ProcessContext& context) double beats = context.projectTimeMusic * 0.25 * _timeSigDenominator; double bars = beats / _timeSigNumerator; beats -= int(bars) * _timeSigNumerator; - synth.timePosition(0, int(bars), float(beats)); + synth.timePosition(0, int(bars), beats); } synth.playbackState(0, (context.state & context.kPlaying) != 0);