commit
d13dbe5bd3
28 changed files with 889 additions and 94 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 \
|
||||||
|
|
|
||||||
|
|
@ -108,25 +108,30 @@ int main(int argc, char* argv[])
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sfz::BufferPool bufferPool;
|
||||||
|
|
||||||
size_t numLfos = desc.size();
|
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) {
|
for (size_t l = 0; l < numLfos; ++l) {
|
||||||
lfos[l].setSampleRate(sampleRate);
|
const NumericId<sfz::LFO> id { static_cast<int>(l) };
|
||||||
lfos[l].configure(&desc[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);
|
size_t numFrames = (size_t)std::ceil(sampleRate * duration);
|
||||||
std::vector<float> outputMemory(numLfos * numFrames);
|
std::vector<float> outputMemory(numLfos * numFrames);
|
||||||
|
|
||||||
for (size_t l = 0; l < numLfos; ++l) {
|
for (size_t l = 0; l < numLfos; ++l) {
|
||||||
lfos[l].start(0);
|
lfos[l]->start(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<absl::Span<float>> lfoOutputs(numLfos);
|
std::vector<absl::Span<float>> lfoOutputs(numLfos);
|
||||||
for (size_t l = 0; l < numLfos; ++l) {
|
for (size_t l = 0; l < numLfos; ++l) {
|
||||||
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
|
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
|
||||||
lfos[l].process(lfoOutputs[l]);
|
lfos[l]->process(lfoOutputs[l]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saveFlac) {
|
if (saveFlac) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 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.
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,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.
|
||||||
|
|
|
||||||
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 "Debug.h"
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
|
#include "AudioSpan.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,9 @@ namespace Default
|
||||||
constexpr int numLFOSubs { 2 };
|
constexpr int numLFOSubs { 2 };
|
||||||
constexpr int numLFOSteps { 8 };
|
constexpr int numLFOSteps { 8 };
|
||||||
constexpr Range<float> lfoFreqRange { 0.0, 100.0 };
|
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> lfoPhaseRange { 0.0, 1.0 };
|
||||||
constexpr Range<float> lfoDelayRange { 0.0, 30.0 };
|
constexpr Range<float> lfoDelayRange { 0.0, 30.0 };
|
||||||
constexpr Range<float> lfoFadeRange { 0.0, 30.0 };
|
constexpr Range<float> lfoFadeRange { 0.0, 30.0 };
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,14 @@
|
||||||
|
|
||||||
#include "LFO.h"
|
#include "LFO.h"
|
||||||
#include "LFODescription.h"
|
#include "LFODescription.h"
|
||||||
|
#include "BeatClock.h"
|
||||||
|
#include "BufferPool.h"
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "modulations/ModMatrix.h"
|
||||||
|
#include "modulations/ModKey.h"
|
||||||
|
#include "modulations/ModId.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
@ -16,6 +21,20 @@
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
struct LFO::Impl {
|
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;
|
float sampleRate_ = 0;
|
||||||
|
|
||||||
// control
|
// control
|
||||||
|
|
@ -26,19 +45,23 @@ struct LFO::Impl {
|
||||||
float fadePosition_ = 0;
|
float fadePosition_ = 0;
|
||||||
std::array<float, config::maxLFOSubs> subPhases_ {{}};
|
std::array<float, config::maxLFOSubs> subPhases_ {{}};
|
||||||
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
|
std::array<float, config::maxLFOSubs> sampleHoldMem_ {{}};
|
||||||
|
std::array<int, config::maxLFOSubs> sampleHoldState_ {{}};
|
||||||
};
|
};
|
||||||
|
|
||||||
LFO::LFO()
|
LFO::LFO(NumericId<LFO> id, BufferPool& bufferPool, BeatClock* beatClock, ModMatrix* modMatrix)
|
||||||
: impl_(new Impl)
|
: impl_(new Impl(id, bufferPool, beatClock, modMatrix))
|
||||||
{
|
{
|
||||||
impl_->sampleRate_ = config::defaultSampleRate;
|
|
||||||
impl_->desc_ = &LFODescription::getDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LFO::~LFO()
|
LFO::~LFO()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NumericId<LFO> LFO::getId() const noexcept
|
||||||
|
{
|
||||||
|
return impl_->id_;
|
||||||
|
}
|
||||||
|
|
||||||
void LFO::setSampleRate(double sampleRate)
|
void LFO::setSampleRate(double sampleRate)
|
||||||
{
|
{
|
||||||
impl_->sampleRate_ = sampleRate;
|
impl_->sampleRate_ = sampleRate;
|
||||||
|
|
@ -55,8 +78,9 @@ void LFO::start(unsigned triggerDelay)
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
const float sampleRate = impl.sampleRate_;
|
const float sampleRate = impl.sampleRate_;
|
||||||
|
|
||||||
impl.subPhases_.fill(desc.phase0);
|
impl.subPhases_.fill(0.0f);
|
||||||
impl.sampleHoldMem_.fill(0.0f);
|
impl.sampleHoldMem_.fill(0.0f);
|
||||||
|
impl.sampleHoldState_.fill(0);
|
||||||
|
|
||||||
const float delay = desc.delay;
|
const float delay = desc.delay;
|
||||||
size_t delayFrames = (delay > 0) ? static_cast<size_t>(std::ceil(sampleRate * delay)) : 0u;
|
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>
|
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_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
const LFODescription::Sub& sub = desc.sub[nth];
|
const LFODescription::Sub& sub = desc.sub[nth];
|
||||||
const size_t numFrames = out.size();
|
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 offset = sub.offset;
|
||||||
const float ratio = sub.ratio;
|
|
||||||
const float scale = sub.scale;
|
const float scale = sub.scale;
|
||||||
float phase = impl.subPhases_[nth];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < numFrames; ++i) {
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
|
float phase = phaseIn[i];
|
||||||
out[i] += offset + scale * eval<W>(phase);
|
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>
|
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_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
const LFODescription::Sub& sub = desc.sub[nth];
|
const LFODescription::Sub& sub = desc.sub[nth];
|
||||||
const size_t numFrames = out.size();
|
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 offset = sub.offset;
|
||||||
const float ratio = sub.ratio;
|
|
||||||
const float scale = sub.scale;
|
const float scale = sub.scale;
|
||||||
float sampleHoldValue = impl.sampleHoldMem_[nth];
|
float sampleHoldValue = impl.sampleHoldMem_[nth];
|
||||||
float phase = impl.subPhases_[nth];
|
int sampleHoldState = impl.sampleHoldState_[nth];
|
||||||
|
|
||||||
for (size_t i = 0; i < numFrames; ++i) {
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
out[i] += offset + scale * sampleHoldValue;
|
out[i] += offset + scale * sampleHoldValue;
|
||||||
|
|
||||||
// TODO(jpc) lfoN_count: number of repetitions
|
// 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
|
// value updates twice every period
|
||||||
bool updateValue = (int)(phase * 2.0) != (int)((phase + incrPhase) * 2.0);
|
if (sampleHoldState != oldState) {
|
||||||
|
|
||||||
phase += incrPhase;
|
|
||||||
int numWraps = (int)phase;
|
|
||||||
phase -= numWraps;
|
|
||||||
|
|
||||||
if (updateValue) {
|
|
||||||
std::uniform_real_distribution<float> dist(-1.0f, +1.0f);
|
std::uniform_real_distribution<float> dist(-1.0f, +1.0f);
|
||||||
sampleHoldValue = dist(Random::randomGenerator);
|
sampleHoldValue = dist(Random::randomGenerator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl.subPhases_[nth] = phase;
|
|
||||||
impl.sampleHoldMem_[nth] = sampleHoldValue;
|
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;
|
unsigned nth = 0;
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
@ -201,29 +207,17 @@ void LFO::processSteps(absl::Span<float> out)
|
||||||
if (numSteps <= 0)
|
if (numSteps <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const float samplePeriod = 1.0f / impl.sampleRate_;
|
|
||||||
const float baseFreq = desc.freq;
|
|
||||||
const float offset = sub.offset;
|
const float offset = sub.offset;
|
||||||
const float ratio = sub.ratio;
|
|
||||||
const float scale = sub.scale;
|
const float scale = sub.scale;
|
||||||
float phase = impl.subPhases_[nth];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < numFrames; ++i) {
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
|
float phase = phaseIn[i];
|
||||||
float step = steps[static_cast<int>(phase * numSteps)];
|
float step = steps[static_cast<int>(phase * numSteps)];
|
||||||
out[i] += offset + scale * step;
|
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_;
|
Impl& impl = *impl_;
|
||||||
const LFODescription& desc = *impl.desc_;
|
const LFODescription& desc = *impl.desc_;
|
||||||
|
|
@ -244,39 +238,50 @@ void LFO::process(absl::Span<float> out)
|
||||||
if (countSubs < 1)
|
if (countSubs < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto phasesTemp = impl.bufferPool_.getBuffer(numFrames);
|
||||||
|
if (!phasesTemp) {
|
||||||
|
ASSERTFALSE;
|
||||||
|
fill(out, 0.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Span<float> phases = *phasesTemp;
|
||||||
|
|
||||||
if (desc.seq) {
|
if (desc.seq) {
|
||||||
processSteps(out);
|
generatePhase(0, phases, regionId);
|
||||||
|
processSteps(out, phases.data());
|
||||||
++subno;
|
++subno;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; subno < countSubs; ++subno) {
|
for (; subno < countSubs; ++subno) {
|
||||||
|
generatePhase(subno, phases, regionId);
|
||||||
switch (desc.sub[subno].wave) {
|
switch (desc.sub[subno].wave) {
|
||||||
case LFOWave::Triangle:
|
case LFOWave::Triangle:
|
||||||
processWave<LFOWave::Triangle>(subno, out);
|
processWave<LFOWave::Triangle>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Sine:
|
case LFOWave::Sine:
|
||||||
processWave<LFOWave::Sine>(subno, out);
|
processWave<LFOWave::Sine>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Pulse75:
|
case LFOWave::Pulse75:
|
||||||
processWave<LFOWave::Pulse75>(subno, out);
|
processWave<LFOWave::Pulse75>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Square:
|
case LFOWave::Square:
|
||||||
processWave<LFOWave::Square>(subno, out);
|
processWave<LFOWave::Square>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Pulse25:
|
case LFOWave::Pulse25:
|
||||||
processWave<LFOWave::Pulse25>(subno, out);
|
processWave<LFOWave::Pulse25>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Pulse12_5:
|
case LFOWave::Pulse12_5:
|
||||||
processWave<LFOWave::Pulse12_5>(subno, out);
|
processWave<LFOWave::Pulse12_5>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Ramp:
|
case LFOWave::Ramp:
|
||||||
processWave<LFOWave::Ramp>(subno, out);
|
processWave<LFOWave::Ramp>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::Saw:
|
case LFOWave::Saw:
|
||||||
processWave<LFOWave::Saw>(subno, out);
|
processWave<LFOWave::Saw>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
case LFOWave::RandomSH:
|
case LFOWave::RandomSH:
|
||||||
processSH<LFOWave::RandomSH>(subno, out);
|
processSH<LFOWave::RandomSH>(subno, out, phases.data());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -306,4 +311,87 @@ void LFO::processFadeIn(absl::Span<float> out)
|
||||||
impl.fadePosition_ = fadePosition;
|
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
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,15 @@
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "utility/NumericId.h"
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
class BufferPool;
|
||||||
|
class BeatClock;
|
||||||
|
class ModMatrix;
|
||||||
|
struct Region;
|
||||||
|
|
||||||
enum class LFOWave : int;
|
enum class LFOWave : int;
|
||||||
struct LFODescription;
|
struct LFODescription;
|
||||||
|
|
@ -49,9 +54,15 @@ struct LFODescription;
|
||||||
|
|
||||||
class LFO {
|
class LFO {
|
||||||
public:
|
public:
|
||||||
LFO();
|
explicit LFO(
|
||||||
|
NumericId<LFO> id,
|
||||||
|
BufferPool& bufferPool,
|
||||||
|
BeatClock* beatClock = nullptr,
|
||||||
|
ModMatrix* modMatrix = nullptr);
|
||||||
~LFO();
|
~LFO();
|
||||||
|
|
||||||
|
NumericId<LFO> getId() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sets the sample rate.
|
Sets the sample rate.
|
||||||
*/
|
*/
|
||||||
|
|
@ -74,7 +85,7 @@ public:
|
||||||
|
|
||||||
TODO(jpc) frequency modulations
|
TODO(jpc) frequency modulations
|
||||||
*/
|
*/
|
||||||
void process(absl::Span<float> out);
|
void process(absl::Span<float> out, NumericId<Region> regionId = {});
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|
@ -91,24 +102,29 @@ private:
|
||||||
on wave type inside the frame loop.
|
on wave type inside the frame loop.
|
||||||
*/
|
*/
|
||||||
template <LFOWave W>
|
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.
|
Process a sample-and-hold subwaveform, adding to the buffer.
|
||||||
*/
|
*/
|
||||||
template <LFOWave W>
|
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.
|
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.
|
Process the fade in gain, and apply it to the buffer.
|
||||||
*/
|
*/
|
||||||
void processFadeIn(absl::Span<float> out);
|
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:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl_;
|
std::unique_ptr<Impl> impl_;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ struct LFODescription {
|
||||||
~LFODescription();
|
~LFODescription();
|
||||||
static const LFODescription& getDefault();
|
static const LFODescription& getDefault();
|
||||||
float freq = 0; // lfoN_freq
|
float freq = 0; // lfoN_freq
|
||||||
|
float beats = 0; // lfoN_beats
|
||||||
float phase0 = 0; // lfoN_phase
|
float phase0 = 0; // lfoN_phase
|
||||||
float delay = 0; // lfoN_delay
|
float delay = 0; // lfoN_delay
|
||||||
float fade = 0; // lfoN_fade
|
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);
|
setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange);
|
||||||
}
|
}
|
||||||
break;
|
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"):
|
case hash("lfo&_phase"):
|
||||||
{
|
{
|
||||||
const auto lfoNumber = opcode.parameters.front();
|
const auto lfoNumber = opcode.parameters.front();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -859,6 +859,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_) {
|
||||||
|
|
@ -921,9 +924,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.getRunningBeatNumber().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());
|
||||||
|
|
@ -1183,36 +1197,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
|
||||||
|
|
|
||||||
|
|
@ -405,7 +405,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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1475,10 +1475,13 @@ void Voice::setMaxEQsPerVoice(size_t numFilters)
|
||||||
void Voice::setMaxLFOsPerVoice(size_t numLFOs)
|
void Voice::setMaxLFOsPerVoice(size_t numLFOs)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
Resources& resources = impl.resources_;
|
||||||
|
|
||||||
impl.lfos_.resize(numLFOs);
|
impl.lfos_.resize(numLFOs);
|
||||||
|
|
||||||
for (size_t i = 0; i < numLFOs; ++i) {
|
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_);
|
lfo->setSampleRate(impl.sampleRate_);
|
||||||
impl.lfos_[i] = std::move(lfo);
|
impl.lfos_[i] = std::move(lfo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ int ModIds::flags(ModId id) noexcept
|
||||||
return kModIsPerVoice|kModIsAdditive;
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
case ModId::OscillatorModDepth:
|
case ModId::OscillatorModDepth:
|
||||||
return kModIsPerVoice|kModIsPercentMultiplicative;
|
return kModIsPerVoice|kModIsPercentMultiplicative;
|
||||||
|
case ModId::LFOFrequency:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::LFOBeats:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
|
||||||
// unknown
|
// unknown
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ enum class ModId : int {
|
||||||
EqBandwidth,
|
EqBandwidth,
|
||||||
OscillatorDetune,
|
OscillatorDetune,
|
||||||
OscillatorModDepth,
|
OscillatorModDepth,
|
||||||
|
LFOFrequency,
|
||||||
|
LFOBeats,
|
||||||
|
|
||||||
_TargetsEnd,
|
_TargetsEnd,
|
||||||
// [/targets] --------------------------------------------------------------
|
// [/targets] --------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,10 @@ std::string ModKey::toString() const
|
||||||
return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}");
|
return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||||
case ModId::OscillatorModDepth:
|
case ModId::OscillatorModDepth:
|
||||||
return absl::StrCat("OscillatorModDepth {", region_.number(), ", N=", 1 + params_.N, "}");
|
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:
|
default:
|
||||||
return {};
|
return {};
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl
|
||||||
}
|
}
|
||||||
|
|
||||||
LFO* lfo = voice->getLFO(lfoIndex);
|
LFO* lfo = voice->getLFO(lfoIndex);
|
||||||
lfo->process(buffer);
|
lfo->process(buffer, region->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
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);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRate, size_t numFrames)
|
static bool computeLFO(DataPoints& dp, const fs::path& sfzPath, double sampleRate, size_t numFrames)
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
sfz::Resources& resources = synth.getResources();
|
||||||
|
|
||||||
if (!synth.loadSfzFile(sfzPath))
|
if (!synth.loadSfzFile(sfzPath))
|
||||||
return false;
|
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;
|
const std::vector<sfz::LFODescription>& desc = synth.getRegionView(0)->lfos;
|
||||||
size_t numLfos = desc.size();
|
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) {
|
for (size_t l = 0; l < numLfos; ++l) {
|
||||||
lfos[l].setSampleRate(sampleRate);
|
const NumericId<sfz::LFO> id { static_cast<int>(l) };
|
||||||
lfos[l].configure(&desc[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);
|
std::vector<float> outputMemory(numLfos * numFrames);
|
||||||
|
|
||||||
for (size_t l = 0; l < numLfos; ++l) {
|
for (size_t l = 0; l < numLfos; ++l) {
|
||||||
lfos[l].start(0);
|
lfos[l]->start(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<absl::Span<float>> lfoOutputs(numLfos);
|
std::vector<absl::Span<float>> lfoOutputs(numLfos);
|
||||||
for (size_t l = 0; l < numLfos; ++l) {
|
for (size_t l = 0; l < numLfos; ++l) {
|
||||||
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
|
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
|
||||||
lfos[l].process(lfoOutputs[l]);
|
lfos[l]->process(lfoOutputs[l]);
|
||||||
}
|
}
|
||||||
|
|
||||||
dp.rows = numFrames;
|
dp.rows = numFrames;
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,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