commit
d13dbe5bd3
28 changed files with 889 additions and 94 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -108,25 +108,30 @@ int main(int argc, char* argv[])
|
|||
return 1;
|
||||
}
|
||||
|
||||
sfz::BufferPool bufferPool;
|
||||
|
||||
size_t numLfos = desc.size();
|
||||
std::vector<sfz::LFO> lfos(numLfos);
|
||||
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
|
||||
|
||||
for (size_t l = 0; l < numLfos; ++l) {
|
||||
lfos[l].setSampleRate(sampleRate);
|
||||
lfos[l].configure(&desc[l]);
|
||||
const NumericId<sfz::LFO> id { static_cast<int>(l) };
|
||||
sfz::LFO* lfo = new sfz::LFO(id, bufferPool);
|
||||
lfos[l].reset(lfo);
|
||||
lfo->setSampleRate(sampleRate);
|
||||
lfo->configure(&desc[l]);
|
||||
}
|
||||
|
||||
size_t numFrames = (size_t)std::ceil(sampleRate * duration);
|
||||
std::vector<float> outputMemory(numLfos * numFrames);
|
||||
|
||||
for (size_t l = 0; l < numLfos; ++l) {
|
||||
lfos[l].start(0);
|
||||
lfos[l]->start(0);
|
||||
}
|
||||
|
||||
std::vector<absl::Span<float>> lfoOutputs(numLfos);
|
||||
for (size_t l = 0; l < numLfos; ++l) {
|
||||
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
|
||||
lfos[l].process(lfoOutputs[l]);
|
||||
lfos[l]->process(lfoOutputs[l]);
|
||||
}
|
||||
|
||||
if (saveFlac) {
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -392,7 +392,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.
|
||||
|
|
|
|||
|
|
@ -359,7 +359,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.
|
||||
|
|
|
|||
253
src/sfizz/BeatClock.cpp
Normal file
253
src/sfizz/BeatClock.cpp
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
// 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 "SIMDHelpers.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);
|
||||
}
|
||||
|
||||
///
|
||||
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)
|
||||
{
|
||||
runningBeatNumber_.resize(samplesPerBlock);
|
||||
runningBeatPosition_.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::getRunningBeatNumber()
|
||||
{
|
||||
fillBufferUpTo(currentCycleFrames_);
|
||||
|
||||
return absl::MakeConstSpan(runningBeatNumber_.data(), currentCycleFrames_);
|
||||
}
|
||||
|
||||
absl::Span<const float> BeatClock::getRunningBeatPosition()
|
||||
{
|
||||
fillBufferUpTo(currentCycleFrames_);
|
||||
|
||||
return absl::MakeConstSpan(runningBeatPosition_.data(), currentCycleFrames_);
|
||||
}
|
||||
|
||||
absl::Span<const int> BeatClock::getRunningBeatsPerBar()
|
||||
{
|
||||
fillBufferUpTo(currentCycleFrames_);
|
||||
|
||||
return absl::MakeConstSpan(runningBeatsPerBar_.data(), currentCycleFrames_);
|
||||
}
|
||||
|
||||
void BeatClock::fillBufferUpTo(unsigned delay)
|
||||
{
|
||||
int *beatNumberData = runningBeatNumber_.data();
|
||||
float *beatNumberPosition = runningBeatPosition_.data();
|
||||
int *beatsPerBarData = runningBeatsPerBar_.data();
|
||||
unsigned fillIdx = currentCycleFill_;
|
||||
|
||||
const TimeSignature sig = timeSig_;
|
||||
for (unsigned i = fillIdx; i < delay; ++i)
|
||||
beatsPerBarData[i] = sig.beatsPerBar;
|
||||
|
||||
if (!isPlaying_) {
|
||||
if (fillIdx < delay) {
|
||||
fill(absl::MakeSpan(&beatNumberData[fillIdx], delay - fillIdx), 0);
|
||||
fill(absl::MakeSpan(&beatNumberPosition[fillIdx], delay - fillIdx), 0.0f);
|
||||
}
|
||||
currentCycleFill_ = fillIdx;
|
||||
return;
|
||||
}
|
||||
|
||||
BBT clientPos = lastClientPos_;
|
||||
const double beatsPerFrame = beatsPerSecond_ * samplePeriod_;
|
||||
|
||||
const BBT hostPos = lastHostPos_;
|
||||
bool mustApplyHostPos = mustApplyHostPos_;
|
||||
|
||||
for (; fillIdx < delay; ++fillIdx) {
|
||||
clientPos = BBT::fromBeats(sig, clientPos.toBeats(sig) + beatsPerFrame);
|
||||
clientPos = mustApplyHostPos ? hostPos : clientPos;
|
||||
mustApplyHostPos = false;
|
||||
|
||||
// quantization to nearest for prevention of rounding errors
|
||||
double beats = clientPos.toBeats(sig);
|
||||
beatNumberData[fillIdx] = dequantize<int>(quantize(beats));
|
||||
beatNumberPosition[fillIdx] = static_cast<float>(beats);
|
||||
}
|
||||
|
||||
currentCycleFill_ = fillIdx;
|
||||
lastClientPos_ = clientPos;
|
||||
mustApplyHostPos_ = mustApplyHostPos;
|
||||
}
|
||||
|
||||
void BeatClock::calculatePhase(float beatPeriod, float* phaseOut)
|
||||
{
|
||||
const unsigned numFrames = currentCycleFrames_;
|
||||
|
||||
if (beatPeriod <= 0.0f) {
|
||||
fill(absl::MakeSpan(phaseOut, numFrames), 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
const float invBeatPeriod = 1.0f / beatPeriod;
|
||||
const float* beatPositionData = getRunningBeatPosition().data();
|
||||
|
||||
for (unsigned i = 0; i < numFrames; ++i) {
|
||||
float beatPosition = std::max(0.0f, beatPositionData[i]);
|
||||
float phase = beatPosition * invBeatPeriod;
|
||||
phase -= static_cast<int>(phase);
|
||||
phaseOut[i] = phase;
|
||||
}
|
||||
}
|
||||
|
||||
void BeatClock::calculatePhaseModulated(const float* beatPeriodData, float* phaseOut)
|
||||
{
|
||||
const unsigned numFrames = currentCycleFrames_;
|
||||
const float* beatPositionData = getRunningBeatPosition().data();
|
||||
|
||||
for (unsigned i = 0; i < numFrames; ++i) {
|
||||
float beatPeriod = beatPeriodData[i];
|
||||
float beatPosition = std::max(0.0f, beatPositionData[i]);
|
||||
float phase = beatPosition / beatPeriod;
|
||||
phase -= static_cast<int>(phase);
|
||||
phaseOut[i] = (beatPeriod > 0.0f) ? phase : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
} // 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;
|
||||
}
|
||||
186
src/sfizz/BeatClock.h
Normal file
186
src/sfizz/BeatClock.h
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// 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 "Buffer.h"
|
||||
#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:
|
||||
/**
|
||||
* @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);
|
||||
/**
|
||||
* Check whether the clock is currently ticking.
|
||||
*/
|
||||
bool isPlaying() const noexcept { return isPlaying_; }
|
||||
/**
|
||||
* @brief Get the beat number for each frame of the current cycle.
|
||||
*
|
||||
* This signal is quantized to a fixed resolution, such that it never
|
||||
* suffers 1-off errors due to imprecision in the host time position.
|
||||
*/
|
||||
absl::Span<const int> getRunningBeatNumber();
|
||||
/**
|
||||
* @brief Get the beat position for each frame of the current cycle.
|
||||
*
|
||||
* This is a fractional equivalent of the beat number, however the beat
|
||||
* boundaries can be traversed erratically due to approximation errors.
|
||||
* If you need to perform work on exact beat transitions, prefer
|
||||
* `getRunningBeatNumber` instead.
|
||||
*/
|
||||
absl::Span<const float> getRunningBeatPosition();
|
||||
/**
|
||||
* @brief Get the time signature numerator for each frame of the current cycle.
|
||||
*/
|
||||
absl::Span<const int> getRunningBeatsPerBar();
|
||||
/**
|
||||
* @brief Create a normalized phase signal for LFO which completes a
|
||||
* period every N-th beat.
|
||||
*/
|
||||
void calculatePhase(float beatPeriod, float* phaseOut);
|
||||
/**
|
||||
* @brief Create a normalized phase signal for LFO which completes a
|
||||
* period every N-th beat, where N can vary over time.
|
||||
*/
|
||||
void calculatePhaseModulated(const float* beatPeriodData, float* phaseOut);
|
||||
|
||||
private:
|
||||
void fillBufferUpTo(unsigned delay);
|
||||
|
||||
private:
|
||||
double samplePeriod_ { 1.0 / config::defaultSampleRate };
|
||||
|
||||
// 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_;
|
||||
|
||||
Buffer<int> runningBeatNumber_ { config::defaultSamplesPerBlock };
|
||||
Buffer<float> runningBeatPosition_ { config::defaultSamplesPerBlock };
|
||||
Buffer<int> runningBeatsPerBar_ { config::defaultSamplesPerBlock };
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const sfz::BBT& pos);
|
||||
std::ostream& operator<<(std::ostream& os, const sfz::TimeSignature& sig);
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
#include "Debug.h"
|
||||
#include "Buffer.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "AudioSpan.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ namespace Default
|
|||
constexpr int numLFOSubs { 2 };
|
||||
constexpr int numLFOSteps { 8 };
|
||||
constexpr Range<float> lfoFreqRange { 0.0, 100.0 };
|
||||
constexpr Range<float> lfoFreqModRange { -100.0, 100.0 };
|
||||
constexpr Range<float> lfoBeatsRange { 0.0, 1000.0 };
|
||||
constexpr Range<float> lfoBeatsModRange { -1000.0, 1000.0 };
|
||||
constexpr Range<float> lfoPhaseRange { 0.0, 1.0 };
|
||||
constexpr Range<float> lfoDelayRange { 0.0, 30.0 };
|
||||
constexpr Range<float> lfoFadeRange { 0.0, 30.0 };
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@
|
|||
|
||||
#include "LFO.h"
|
||||
#include "LFODescription.h"
|
||||
#include "BeatClock.h"
|
||||
#include "BufferPool.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "SIMDHelpers.h"
|
||||
#include "Config.h"
|
||||
#include "modulations/ModMatrix.h"
|
||||
#include "modulations/ModKey.h"
|
||||
#include "modulations/ModId.h"
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
|
@ -16,6 +21,20 @@
|
|||
namespace sfz {
|
||||
|
||||
struct LFO::Impl {
|
||||
explicit Impl(NumericId<LFO> id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
||||
: id_(id),
|
||||
bufferPool_(bufferPool),
|
||||
beatClock_(beatClock),
|
||||
modMatrix_(modMatrix),
|
||||
sampleRate_(config::defaultSampleRate),
|
||||
desc_(&LFODescription::getDefault())
|
||||
{
|
||||
}
|
||||
|
||||
NumericId<LFO> id_;
|
||||
BufferPool& bufferPool_;
|
||||
BeatClock* beatClock_ = nullptr;
|
||||
ModMatrix* modMatrix_ = nullptr;
|
||||
float sampleRate_ = 0;
|
||||
|
||||
// control
|
||||
|
|
@ -26,19 +45,23 @@ struct LFO::Impl {
|
|||
float fadePosition_ = 0;
|
||||
std::array<float, config::maxLFOSubs> subPhases_ {{}};
|
||||
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
|
||||
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
|
||||
};
|
||||
|
||||
LFO::LFO()
|
||||
: impl_(new Impl)
|
||||
LFO::LFO(NumericId<LFO> id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
||||
: impl_(new Impl(id, bufferPool, beatClock, modMatrix))
|
||||
{
|
||||
impl_->sampleRate_ = config::defaultSampleRate;
|
||||
impl_->desc_ = &LFODescription::getDefault();
|
||||
}
|
||||
|
||||
LFO::~LFO()
|
||||
{
|
||||
}
|
||||
|
||||
NumericId<LFO> LFO::getId() const noexcept
|
||||
{
|
||||
return impl_->id_;
|
||||
}
|
||||
|
||||
void LFO::setSampleRate(double sampleRate)
|
||||
{
|
||||
impl_->sampleRate_ = sampleRate;
|
||||
|
|
@ -55,8 +78,9 @@ void LFO::start(unsigned triggerDelay)
|
|||
const LFODescription& desc = *impl.desc_;
|
||||
const float sampleRate = impl.sampleRate_;
|
||||
|
||||
impl.subPhases_.fill(desc.phase0);
|
||||
impl.subPhases_.fill(0.0f);
|
||||
impl.sampleHoldMem_.fill(0.0f);
|
||||
impl.sampleHoldState_.fill(0);
|
||||
|
||||
const float delay = desc.delay;
|
||||
size_t delayFrames = (delay > 0) ? static_cast<size_t>(std::ceil(sampleRate * delay)) : 0u;
|
||||
|
|
@ -118,75 +142,57 @@ inline float LFO::eval<LFOWave::Saw>(float phase)
|
|||
}
|
||||
|
||||
template <LFOWave W>
|
||||
void LFO::processWave(unsigned nth, absl::Span<float> out)
|
||||
void LFO::processWave(unsigned nth, absl::Span<float> out, const float* phaseIn)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
const LFODescription& desc = *impl.desc_;
|
||||
const LFODescription::Sub& sub = desc.sub[nth];
|
||||
const size_t numFrames = out.size();
|
||||
|
||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
||||
const float baseFreq = desc.freq;
|
||||
const float offset = sub.offset;
|
||||
const float ratio = sub.ratio;
|
||||
const float scale = sub.scale;
|
||||
float phase = impl.subPhases_[nth];
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
float phase = phaseIn[i];
|
||||
out[i] += offset + scale * eval<W>(phase);
|
||||
|
||||
// TODO(jpc) lfoN_count: number of repetitions
|
||||
|
||||
float incrPhase = ratio * samplePeriod * baseFreq;
|
||||
phase += incrPhase;
|
||||
int numWraps = (int)phase;
|
||||
phase -= numWraps;
|
||||
}
|
||||
|
||||
impl.subPhases_[nth] = phase;
|
||||
}
|
||||
|
||||
template <LFOWave W>
|
||||
void LFO::processSH(unsigned nth, absl::Span<float> out)
|
||||
void LFO::processSH(unsigned nth, absl::Span<float> out, const float* phaseIn)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
const LFODescription& desc = *impl.desc_;
|
||||
const LFODescription::Sub& sub = desc.sub[nth];
|
||||
const size_t numFrames = out.size();
|
||||
|
||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
||||
const float baseFreq = desc.freq;
|
||||
const float offset = sub.offset;
|
||||
const float ratio = sub.ratio;
|
||||
const float scale = sub.scale;
|
||||
float sampleHoldValue = impl.sampleHoldMem_[nth];
|
||||
float phase = impl.subPhases_[nth];
|
||||
int sampleHoldState = impl.sampleHoldState_[nth];
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
out[i] += offset + scale * sampleHoldValue;
|
||||
|
||||
// TODO(jpc) lfoN_count: number of repetitions
|
||||
|
||||
float incrPhase = ratio * samplePeriod * baseFreq;
|
||||
float phase = phaseIn[i];
|
||||
|
||||
int oldState = sampleHoldState;
|
||||
sampleHoldState = phase > 0.5f;
|
||||
|
||||
// value updates twice every period
|
||||
bool updateValue = (int)(phase * 2.0) != (int)((phase + incrPhase) * 2.0);
|
||||
|
||||
phase += incrPhase;
|
||||
int numWraps = (int)phase;
|
||||
phase -= numWraps;
|
||||
|
||||
if (updateValue) {
|
||||
if (sampleHoldState != oldState) {
|
||||
std::uniform_real_distribution<float> dist(-1.0f, +1.0f);
|
||||
sampleHoldValue = dist(Random::randomGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
impl.subPhases_[nth] = phase;
|
||||
impl.sampleHoldMem_[nth] = sampleHoldValue;
|
||||
impl.sampleHoldState_[nth] = sampleHoldState;
|
||||
}
|
||||
|
||||
void LFO::processSteps(absl::Span<float> out)
|
||||
void LFO::processSteps(absl::Span<float> out, const float* phaseIn)
|
||||
{
|
||||
unsigned nth = 0;
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -201,29 +207,17 @@ void LFO::processSteps(absl::Span<float> out)
|
|||
if (numSteps <= 0)
|
||||
return;
|
||||
|
||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
||||
const float baseFreq = desc.freq;
|
||||
const float offset = sub.offset;
|
||||
const float ratio = sub.ratio;
|
||||
const float scale = sub.scale;
|
||||
float phase = impl.subPhases_[nth];
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
float phase = phaseIn[i];
|
||||
float step = steps[static_cast<int>(phase * numSteps)];
|
||||
out[i] += offset + scale * step;
|
||||
|
||||
// TODO(jpc) lfoN_count: number of repetitions
|
||||
|
||||
float incrPhase = ratio * samplePeriod * baseFreq;
|
||||
phase += incrPhase;
|
||||
int numWraps = (int)phase;
|
||||
phase -= numWraps;
|
||||
}
|
||||
|
||||
impl.subPhases_[nth] = phase;
|
||||
}
|
||||
|
||||
void LFO::process(absl::Span<float> out)
|
||||
void LFO::process(absl::Span<float> out, NumericId<Region> regionId)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
const LFODescription& desc = *impl.desc_;
|
||||
|
|
@ -244,39 +238,50 @@ void LFO::process(absl::Span<float> out)
|
|||
if (countSubs < 1)
|
||||
return;
|
||||
|
||||
auto phasesTemp = impl.bufferPool_.getBuffer(numFrames);
|
||||
if (!phasesTemp) {
|
||||
ASSERTFALSE;
|
||||
fill(out, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
absl::Span<float> phases = *phasesTemp;
|
||||
|
||||
if (desc.seq) {
|
||||
processSteps(out);
|
||||
generatePhase(0, phases, regionId);
|
||||
processSteps(out, phases.data());
|
||||
++subno;
|
||||
}
|
||||
|
||||
for (; subno < countSubs; ++subno) {
|
||||
generatePhase(subno, phases, regionId);
|
||||
switch (desc.sub[subno].wave) {
|
||||
case LFOWave::Triangle:
|
||||
processWave<LFOWave::Triangle>(subno, out);
|
||||
processWave<LFOWave::Triangle>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Sine:
|
||||
processWave<LFOWave::Sine>(subno, out);
|
||||
processWave<LFOWave::Sine>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Pulse75:
|
||||
processWave<LFOWave::Pulse75>(subno, out);
|
||||
processWave<LFOWave::Pulse75>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Square:
|
||||
processWave<LFOWave::Square>(subno, out);
|
||||
processWave<LFOWave::Square>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Pulse25:
|
||||
processWave<LFOWave::Pulse25>(subno, out);
|
||||
processWave<LFOWave::Pulse25>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Pulse12_5:
|
||||
processWave<LFOWave::Pulse12_5>(subno, out);
|
||||
processWave<LFOWave::Pulse12_5>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Ramp:
|
||||
processWave<LFOWave::Ramp>(subno, out);
|
||||
processWave<LFOWave::Ramp>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::Saw:
|
||||
processWave<LFOWave::Saw>(subno, out);
|
||||
processWave<LFOWave::Saw>(subno, out, phases.data());
|
||||
break;
|
||||
case LFOWave::RandomSH:
|
||||
processSH<LFOWave::RandomSH>(subno, out);
|
||||
processSH<LFOWave::RandomSH>(subno, out, phases.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -306,4 +311,87 @@ void LFO::processFadeIn(absl::Span<float> out)
|
|||
impl.fadePosition_ = fadePosition;
|
||||
}
|
||||
|
||||
void LFO::generatePhase(unsigned nth, absl::Span<float> phases, NumericId<Region> regionId)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
BufferPool& bufferPool = impl.bufferPool_;
|
||||
BeatClock* beatClock = impl.beatClock_;
|
||||
ModMatrix* modMatrix = impl.modMatrix_;
|
||||
const NumericId<LFO> id { impl.id_ };
|
||||
const LFODescription& desc = *impl.desc_;
|
||||
const LFODescription::Sub& sub = desc.sub[nth];
|
||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
||||
const float baseFreq = desc.freq;
|
||||
const float beats = desc.beats;
|
||||
const float phaseOffset = desc.phase0;
|
||||
const float ratio = sub.ratio;
|
||||
float phase = impl.subPhases_[nth];
|
||||
const size_t numFrames = phases.size();
|
||||
|
||||
// TODO(jpc) lfoN_count: number of repetitions
|
||||
|
||||
// modulations
|
||||
const float* beatsMod = nullptr;
|
||||
const float* freqMod = nullptr;
|
||||
if (modMatrix && id && regionId) {
|
||||
// Note(jpc) we might switch between beats and frequency, if host
|
||||
// switches play state on and off; continually generate both.
|
||||
ModKey beatsKey = ModKey::createNXYZ(ModId::LFOBeats, regionId, id.number());
|
||||
ModKey freqKey = ModKey::createNXYZ(ModId::LFOFrequency, regionId, id.number());
|
||||
beatsMod = modMatrix->getModulationByKey(beatsKey);
|
||||
freqMod = modMatrix->getModulationByKey(freqKey);
|
||||
}
|
||||
|
||||
if (beatClock && beatClock->isPlaying() && beats > 0) {
|
||||
// generate using the beat clock
|
||||
float beatRatio = (ratio > 0) ? (1.0f / ratio) : 0.0f;
|
||||
|
||||
if (!beatsMod)
|
||||
beatClock->calculatePhase(beats * beatRatio, phases.data());
|
||||
else {
|
||||
auto temp = bufferPool.getBuffer(numFrames);
|
||||
if (!temp) {
|
||||
ASSERTFALSE;
|
||||
beatClock->calculatePhase(beats * beatRatio, phases.data());
|
||||
}
|
||||
else {
|
||||
fill(*temp, beats);
|
||||
add(absl::MakeConstSpan(beatsMod, numFrames), *temp);
|
||||
applyGain1(beatRatio, *temp);
|
||||
beatClock->calculatePhaseModulated(temp->data(), phases.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// generate using the frequency
|
||||
if (!freqMod) {
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
phases[i] = phase;
|
||||
float incr = ratio * samplePeriod * baseFreq;
|
||||
phase += incr;
|
||||
int numWraps = (int)phase;
|
||||
phase -= numWraps;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
phases[i] = phase;
|
||||
float incr = ratio * samplePeriod * (baseFreq + freqMod[i]);
|
||||
phase += incr;
|
||||
int numWraps = (int)phase;
|
||||
phase -= numWraps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply phase offsets
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
float withOffset = phases[i] + phaseOffset;
|
||||
withOffset -= (int)withOffset;
|
||||
phases[i] = withOffset;
|
||||
}
|
||||
|
||||
impl.subPhases_[nth] = phase;
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@
|
|||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "utility/NumericId.h"
|
||||
#include <absl/types/span.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sfz {
|
||||
class BufferPool;
|
||||
class BeatClock;
|
||||
class ModMatrix;
|
||||
struct Region;
|
||||
|
||||
enum class LFOWave : int;
|
||||
struct LFODescription;
|
||||
|
|
@ -49,9 +54,15 @@ struct LFODescription;
|
|||
|
||||
class LFO {
|
||||
public:
|
||||
LFO();
|
||||
explicit LFO(
|
||||
NumericId<LFO> id,
|
||||
BufferPool& bufferPool,
|
||||
BeatClock* beatClock = nullptr,
|
||||
ModMatrix* modMatrix = nullptr);
|
||||
~LFO();
|
||||
|
||||
NumericId<LFO> getId() const noexcept;
|
||||
|
||||
/**
|
||||
Sets the sample rate.
|
||||
*/
|
||||
|
|
@ -74,7 +85,7 @@ public:
|
|||
|
||||
TODO(jpc) frequency modulations
|
||||
*/
|
||||
void process(absl::Span<float> out);
|
||||
void process(absl::Span<float> out, NumericId<Region> regionId = {});
|
||||
|
||||
private:
|
||||
/**
|
||||
|
|
@ -91,24 +102,29 @@ private:
|
|||
on wave type inside the frame loop.
|
||||
*/
|
||||
template <LFOWave W>
|
||||
void processWave(unsigned nth, absl::Span<float> out);
|
||||
void processWave(unsigned nth, absl::Span<float> out, const float* phaseIn);
|
||||
|
||||
/**
|
||||
Process a sample-and-hold subwaveform, adding to the buffer.
|
||||
*/
|
||||
template <LFOWave W>
|
||||
void processSH(unsigned nth, absl::Span<float> out);
|
||||
void processSH(unsigned nth, absl::Span<float> out, const float* phaseIn);
|
||||
|
||||
/**
|
||||
Process the step sequencer, adding to the buffer.
|
||||
*/
|
||||
void processSteps(absl::Span<float> out);
|
||||
void processSteps(absl::Span<float> out, const float* phaseIn);
|
||||
|
||||
/**
|
||||
Process the fade in gain, and apply it to the buffer.
|
||||
*/
|
||||
void processFadeIn(absl::Span<float> out);
|
||||
|
||||
/**
|
||||
Generate the phase of the N-th generator
|
||||
*/
|
||||
void generatePhase(unsigned nth, absl::Span<float> phases, NumericId<Region> regionId);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ struct LFODescription {
|
|||
~LFODescription();
|
||||
static const LFODescription& getDefault();
|
||||
float freq = 0; // lfoN_freq
|
||||
float beats = 0; // lfoN_beats
|
||||
float phase0 = 0; // lfoN_phase
|
||||
float delay = 0; // lfoN_delay
|
||||
float fade = 0; // lfoN_fade
|
||||
|
|
|
|||
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
|
||||
|
|
@ -866,6 +866,36 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange);
|
||||
}
|
||||
break;
|
||||
case_any_ccN("lfo&_freq"):
|
||||
{
|
||||
const auto lfoNumber = opcode.parameters.front();
|
||||
if (lfoNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
|
||||
return false;
|
||||
processGenericCc(opcode, Default::lfoFreqModRange, ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber - 1));
|
||||
}
|
||||
break;
|
||||
case hash("lfo&_beats"):
|
||||
{
|
||||
const auto lfoNumber = opcode.parameters.front();
|
||||
if (lfoNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
|
||||
return false;
|
||||
setValueFromOpcode(opcode, lfos[lfoNumber - 1].beats, Default::lfoBeatsRange);
|
||||
}
|
||||
break;
|
||||
case_any_ccN("lfo&_beats"):
|
||||
{
|
||||
const auto lfoNumber = opcode.parameters.front();
|
||||
if (lfoNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
|
||||
return false;
|
||||
processGenericCc(opcode, Default::lfoBeatsModRange, ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber - 1));
|
||||
}
|
||||
break;
|
||||
case hash("lfo&_phase"):
|
||||
{
|
||||
const auto lfoNumber = opcode.parameters.front();
|
||||
|
|
|
|||
|
|
@ -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<StretchTuning> 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -859,6 +859,9 @@ void Synth::renderBlock(AudioSpan<float> 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_) {
|
||||
|
|
@ -921,9 +924,20 @@ void Synth::renderBlock(AudioSpan<float> 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.getRunningBeatNumber().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());
|
||||
|
|
@ -1183,36 +1197,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
|
||||
|
|
|
|||
|
|
@ -405,7 +405,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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1475,10 +1475,13 @@ void Voice::setMaxEQsPerVoice(size_t numFilters)
|
|||
void Voice::setMaxLFOsPerVoice(size_t numLFOs)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
Resources& resources = impl.resources_;
|
||||
|
||||
impl.lfos_.resize(numLFOs);
|
||||
|
||||
for (size_t i = 0; i < numLFOs; ++i) {
|
||||
auto lfo = absl::make_unique<LFO>();
|
||||
const NumericId<LFO> id { static_cast<int>(i) };
|
||||
auto lfo = absl::make_unique<LFO>(id, resources.bufferPool, &resources.beatClock, &resources.modMatrix);
|
||||
lfo->setSampleRate(impl.sampleRate_);
|
||||
impl.lfos_[i] = std::move(lfo);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ int ModIds::flags(ModId id) noexcept
|
|||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::OscillatorModDepth:
|
||||
return kModIsPerVoice|kModIsPercentMultiplicative;
|
||||
case ModId::LFOFrequency:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::LFOBeats:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
|
||||
// unknown
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ enum class ModId : int {
|
|||
EqBandwidth,
|
||||
OscillatorDetune,
|
||||
OscillatorModDepth,
|
||||
LFOFrequency,
|
||||
LFOBeats,
|
||||
|
||||
_TargetsEnd,
|
||||
// [/targets] --------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -136,6 +136,10 @@ std::string ModKey::toString() const
|
|||
return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::OscillatorModDepth:
|
||||
return absl::StrCat("OscillatorModDepth {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::LFOFrequency:
|
||||
return absl::StrCat("LFOFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::LFOBeats:
|
||||
return absl::StrCat("LFOBeats {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
|
||||
default:
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl
|
|||
}
|
||||
|
||||
LFO* lfo = voice->getLFO(lfoIndex);
|
||||
lfo->process(buffer);
|
||||
lfo->process(buffer, region->getId());
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -169,7 +169,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ void sfizz_send_time_signature(sfizz_synth_t* synth, int delay, int beats_per_ba
|
|||
auto* self = reinterpret_cast<sfz::Synth*>(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<sfz::Synth*>(synth);
|
||||
self->timePosition(delay, bar, bar_beat);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRate, size_t numFrames)
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::Resources& resources = synth.getResources();
|
||||
|
||||
if (!synth.loadSfzFile(sfzPath))
|
||||
return false;
|
||||
|
|
@ -22,23 +23,26 @@ static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRat
|
|||
|
||||
const std::vector<sfz::LFODescription>& desc = synth.getRegionView(0)->lfos;
|
||||
size_t numLfos = desc.size();
|
||||
std::vector<sfz::LFO> lfos(numLfos);
|
||||
std::vector<std::unique_ptr<sfz::LFO>> lfos(numLfos);
|
||||
|
||||
for (size_t l = 0; l < numLfos; ++l) {
|
||||
lfos[l].setSampleRate(sampleRate);
|
||||
lfos[l].configure(&desc[l]);
|
||||
const NumericId<sfz::LFO> id { static_cast<int>(l) };
|
||||
sfz::LFO* lfo = new sfz::LFO(id, resources.bufferPool);
|
||||
lfos[l].reset(lfo);
|
||||
lfo->setSampleRate(sampleRate);
|
||||
lfo->configure(&desc[l]);
|
||||
}
|
||||
|
||||
std::vector<float> outputMemory(numLfos * numFrames);
|
||||
|
||||
for (size_t l = 0; l < numLfos; ++l) {
|
||||
lfos[l].start(0);
|
||||
lfos[l]->start(0);
|
||||
}
|
||||
|
||||
std::vector<absl::Span<float>> lfoOutputs(numLfos);
|
||||
for (size_t l = 0; l < numLfos; ++l) {
|
||||
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
|
||||
lfos[l].process(lfoOutputs[l]);
|
||||
lfos[l]->process(lfoOutputs[l]);
|
||||
}
|
||||
|
||||
dp.rows = numFrames;
|
||||
|
|
|
|||
|
|
@ -297,7 +297,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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue