Add the beat clock
This commit is contained in:
parent
9813d41588
commit
baf228802b
15 changed files with 604 additions and 19 deletions
|
|
@ -48,6 +48,7 @@ SFIZZ_CXX_FLAGS = $(SFIZZ_C_FLAGS)
|
||||||
SFIZZ_SOURCES = \
|
SFIZZ_SOURCES = \
|
||||||
src/sfizz/ADSREnvelope.cpp \
|
src/sfizz/ADSREnvelope.cpp \
|
||||||
src/sfizz/AudioReader.cpp \
|
src/sfizz/AudioReader.cpp \
|
||||||
|
src/sfizz/BeatClock.cpp \
|
||||||
src/sfizz/Curve.cpp \
|
src/sfizz/Curve.cpp \
|
||||||
src/sfizz/effects/Apan.cpp \
|
src/sfizz/effects/Apan.cpp \
|
||||||
src/sfizz/Effects.cpp \
|
src/sfizz/Effects.cpp \
|
||||||
|
|
@ -90,6 +91,7 @@ SFIZZ_SOURCES = \
|
||||||
src/sfizz/LFO.cpp \
|
src/sfizz/LFO.cpp \
|
||||||
src/sfizz/LFODescription.cpp \
|
src/sfizz/LFODescription.cpp \
|
||||||
src/sfizz/Messaging.cpp \
|
src/sfizz/Messaging.cpp \
|
||||||
|
src/sfizz/Metronome.cpp \
|
||||||
src/sfizz/MidiState.cpp \
|
src/sfizz/MidiState.cpp \
|
||||||
src/sfizz/OpcodeCleanup.cpp \
|
src/sfizz/OpcodeCleanup.cpp \
|
||||||
src/sfizz/Opcode.cpp \
|
src/sfizz/Opcode.cpp \
|
||||||
|
|
|
||||||
|
|
@ -175,11 +175,11 @@ typedef struct
|
||||||
|
|
||||||
// Timing data
|
// Timing data
|
||||||
int bar;
|
int bar;
|
||||||
float bar_beat;
|
double bar_beat;
|
||||||
int beats_per_bar;
|
int beats_per_bar;
|
||||||
int beat_unit;
|
int beat_unit;
|
||||||
float bpm_tempo;
|
double bpm_tempo;
|
||||||
float speed;
|
double speed;
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
char bundle_path[MAX_BUNDLE_PATH_SIZE];
|
char bundle_path[MAX_BUNDLE_PATH_SIZE];
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ set (SFIZZ_HEADERS
|
||||||
sfizz/AudioBuffer.h
|
sfizz/AudioBuffer.h
|
||||||
sfizz/AudioReader.h
|
sfizz/AudioReader.h
|
||||||
sfizz/AudioSpan.h
|
sfizz/AudioSpan.h
|
||||||
|
sfizz/BeatClock.h
|
||||||
sfizz/Buffer.h
|
sfizz/Buffer.h
|
||||||
sfizz/BufferPool.h
|
sfizz/BufferPool.h
|
||||||
sfizz/CCMap.h
|
sfizz/CCMap.h
|
||||||
|
|
@ -79,6 +80,7 @@ set (SFIZZ_HEADERS
|
||||||
sfizz/LFO.h
|
sfizz/LFO.h
|
||||||
sfizz/LFODescription.h
|
sfizz/LFODescription.h
|
||||||
sfizz/MathHelpers.h
|
sfizz/MathHelpers.h
|
||||||
|
sfizz/Metronome.h
|
||||||
sfizz/MidiState.h
|
sfizz/MidiState.h
|
||||||
sfizz/ModifierHelpers.h
|
sfizz/ModifierHelpers.h
|
||||||
sfizz/OnePoleFilter.h
|
sfizz/OnePoleFilter.h
|
||||||
|
|
@ -149,6 +151,8 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/PowerFollower.cpp
|
sfizz/PowerFollower.cpp
|
||||||
sfizz/FlexEGDescription.cpp
|
sfizz/FlexEGDescription.cpp
|
||||||
sfizz/FlexEnvelope.cpp
|
sfizz/FlexEnvelope.cpp
|
||||||
|
sfizz/BeatClock.cpp
|
||||||
|
sfizz/Metronome.cpp
|
||||||
sfizz/SynthMessaging.cpp
|
sfizz/SynthMessaging.cpp
|
||||||
sfizz/modulations/ModId.cpp
|
sfizz/modulations/ModId.cpp
|
||||||
sfizz/modulations/ModKey.cpp
|
sfizz/modulations/ModKey.cpp
|
||||||
|
|
|
||||||
|
|
@ -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 The current bar.
|
||||||
* @param bar_beat The fractional position of the current beat within the 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.
|
* @brief Send the playback state.
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,7 @@ public:
|
||||||
* @param bar The current bar.
|
* @param bar The current bar.
|
||||||
* @param barBeat The fractional position of the current beat within the 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.
|
* @brief Send the playback state.
|
||||||
|
|
|
||||||
230
src/sfizz/BeatClock.cpp
Normal file
230
src/sfizz/BeatClock.cpp
Normal file
|
|
@ -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 <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
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<int>(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<qbeats_t>(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T BeatClock::dequantize(qbeats_t qbeats)
|
||||||
|
{
|
||||||
|
return qbeats / static_cast<T>(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<const int> BeatClock::getRunningBeat()
|
||||||
|
{
|
||||||
|
fillBufferUpTo(currentCycleFrames_);
|
||||||
|
|
||||||
|
return absl::MakeConstSpan(runningBeat_.data(), currentCycleFrames_);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Span<const int> 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<int>(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<int>(oldQbeats);
|
||||||
|
int beatNumber = dequantize<int>(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;
|
||||||
|
}
|
||||||
160
src/sfizz/BeatClock.h
Normal file
160
src/sfizz/BeatClock.h
Normal file
|
|
@ -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 <absl/types/span.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
|
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<const int> getRunningBeat();
|
||||||
|
/**
|
||||||
|
* @brief Get the time signature numerator for each frame of the current cycle.
|
||||||
|
*/
|
||||||
|
absl::Span<const int> 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 <class T> 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<int> runningBeat_;
|
||||||
|
std::vector<int> runningBeatsPerBar_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const sfz::BBT& pos);
|
||||||
|
std::ostream& operator<<(std::ostream& os, const sfz::TimeSignature& sig);
|
||||||
110
src/sfizz/Metronome.cpp
Normal file
110
src/sfizz/Metronome.cpp
Normal file
|
|
@ -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<float>(192000.0f, std::max<float>(1.0f, float(sampleRate)));
|
||||||
|
fConst1 = std::cos((2764.60156f / fConst0));
|
||||||
|
fConst2 = std::sqrt(std::max<float>(0.0f, ((fConst1 + 1.0f) / (1.0f - fConst1))));
|
||||||
|
fConst3 = (1.0f / fConst2);
|
||||||
|
fConst4 = std::cos((5529.20312f / fConst0));
|
||||||
|
fConst5 = std::sqrt(std::max<float>(0.0f, ((fConst4 + 1.0f) / (1.0f - fConst4))));
|
||||||
|
fConst6 = (1.0f / fConst5);
|
||||||
|
fConst7 = std::max<float>(1.0f, (0.00499999989f * fConst0));
|
||||||
|
fConst8 = (1.0f / fConst7);
|
||||||
|
fConst9 = (1.0f / std::max<float>(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<float>(0.0f, std::min<float>((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
|
||||||
59
src/sfizz/Metronome.h
Normal file
59
src/sfizz/Metronome.h
Normal file
|
|
@ -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 <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
#include "Wavetables.h"
|
#include "Wavetables.h"
|
||||||
#include "Curve.h"
|
#include "Curve.h"
|
||||||
#include "Tuning.h"
|
#include "Tuning.h"
|
||||||
|
#include "BeatClock.h"
|
||||||
|
#include "Metronome.h"
|
||||||
#include "modulations/ModMatrix.h"
|
#include "modulations/ModMatrix.h"
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
|
|
||||||
|
|
@ -32,11 +34,15 @@ struct Resources
|
||||||
Tuning tuning;
|
Tuning tuning;
|
||||||
absl::optional<StretchTuning> stretch;
|
absl::optional<StretchTuning> stretch;
|
||||||
ModMatrix modMatrix;
|
ModMatrix modMatrix;
|
||||||
|
BeatClock beatClock;
|
||||||
|
Metronome metronome;
|
||||||
|
|
||||||
void setSampleRate(float samplerate)
|
void setSampleRate(float samplerate)
|
||||||
{
|
{
|
||||||
midiState.setSampleRate(samplerate);
|
midiState.setSampleRate(samplerate);
|
||||||
modMatrix.setSampleRate(samplerate);
|
modMatrix.setSampleRate(samplerate);
|
||||||
|
beatClock.setSampleRate(samplerate);
|
||||||
|
metronome.init(samplerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSamplesPerBlock(int samplesPerBlock)
|
void setSamplesPerBlock(int samplesPerBlock)
|
||||||
|
|
@ -44,6 +50,7 @@ struct Resources
|
||||||
bufferPool.setBufferSize(samplesPerBlock);
|
bufferPool.setBufferSize(samplesPerBlock);
|
||||||
midiState.setSamplesPerBlock(samplesPerBlock);
|
midiState.setSamplesPerBlock(samplesPerBlock);
|
||||||
modMatrix.setSamplesPerBlock(samplesPerBlock);
|
modMatrix.setSamplesPerBlock(samplesPerBlock);
|
||||||
|
beatClock.setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
|
|
@ -54,6 +61,8 @@ struct Resources
|
||||||
logger.clear();
|
logger.clear();
|
||||||
midiState.reset();
|
midiState.reset();
|
||||||
modMatrix.clear();
|
modMatrix.clear();
|
||||||
|
beatClock.clear();
|
||||||
|
metronome.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -856,6 +856,9 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
ModMatrix& mm = impl.resources_.modMatrix;
|
ModMatrix& mm = impl.resources_.modMatrix;
|
||||||
mm.beginCycle(numFrames);
|
mm.beginCycle(numFrames);
|
||||||
|
|
||||||
|
BeatClock& bc = impl.resources_.beatClock;
|
||||||
|
bc.beginCycle(numFrames);
|
||||||
|
|
||||||
{ // Clear effect busses
|
{ // Clear effect busses
|
||||||
ScopedTiming logger { callbackBreakdown.effects };
|
ScopedTiming logger { callbackBreakdown.effects };
|
||||||
for (auto& bus : impl.effectBuses_) {
|
for (auto& bus : impl.effectBuses_) {
|
||||||
|
|
@ -918,9 +921,20 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
// Apply the master volume
|
// Apply the master volume
|
||||||
buffer.applyGain(db2mag(impl.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
|
// Perform any remaining modulators
|
||||||
mm.endCycle();
|
mm.endCycle();
|
||||||
|
|
||||||
|
// Advance the clock to the end of cycle
|
||||||
|
bc.endCycle();
|
||||||
|
|
||||||
{ // Clear events and advance midi time
|
{ // Clear events and advance midi time
|
||||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||||
impl.resources_.midiState.advanceTime(buffer.getNumFrames());
|
impl.resources_.midiState.advanceTime(buffer.getNumFrames());
|
||||||
|
|
@ -1167,36 +1181,33 @@ void Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
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_;
|
Impl& impl = *impl_;
|
||||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
|
impl.resources_.beatClock.setTempo(delay, secondsPerBeat);
|
||||||
}
|
}
|
||||||
void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
|
void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
(void)delay;
|
impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit));
|
||||||
(void)beatsPerBar;
|
|
||||||
(void)beatUnit;
|
|
||||||
}
|
}
|
||||||
void Synth::timePosition(int delay, int bar, float barBeat)
|
void Synth::timePosition(int delay, int bar, double barBeat)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
(void)delay;
|
impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat));
|
||||||
(void)bar;
|
|
||||||
(void)barBeat;
|
|
||||||
}
|
}
|
||||||
void Synth::playbackState(int delay, int playbackState)
|
void Synth::playbackState(int delay, int playbackState)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
(void)delay;
|
impl.resources_.beatClock.setPlaying(delay, playbackState == 1);
|
||||||
(void)playbackState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Synth::getNumRegions() const noexcept
|
int Synth::getNumRegions() const noexcept
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ public:
|
||||||
* @param bar The current bar.
|
* @param bar The current bar.
|
||||||
* @param bar_beat The fractional position of the current beat within the 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.
|
* @brief Send the playback state.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ void sfz::Sfizz::timeSignature(int delay, int beatsPerBar, int beatUnit)
|
||||||
synth->timeSignature(delay, beatsPerBar, 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);
|
synth->timePosition(delay, bar, barBeat);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ void sfizz_send_time_signature(sfizz_synth_t* synth, int delay, int beats_per_ba
|
||||||
auto* self = reinterpret_cast<sfz::Synth*>(synth);
|
auto* self = reinterpret_cast<sfz::Synth*>(synth);
|
||||||
self->timeSignature(delay, beats_per_bar, beat_unit);
|
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<sfz::Synth*>(synth);
|
auto* self = reinterpret_cast<sfz::Synth*>(synth);
|
||||||
self->timePosition(delay, bar, bar_beat);
|
self->timePosition(delay, bar, bar_beat);
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ void SfizzVstProcessor::updateTimeInfo(const Vst::ProcessContext& context)
|
||||||
double beats = context.projectTimeMusic * 0.25 * _timeSigDenominator;
|
double beats = context.projectTimeMusic * 0.25 * _timeSigDenominator;
|
||||||
double bars = beats / _timeSigNumerator;
|
double bars = beats / _timeSigNumerator;
|
||||||
beats -= int(bars) * _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);
|
synth.playbackState(0, (context.state & context.kPlaying) != 0);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue