\o/
This commit is contained in:
parent
3598d45c26
commit
43cd631316
11 changed files with 272 additions and 50 deletions
|
|
@ -199,6 +199,11 @@ void ADSREnvelope<Type>::getBlock(absl::Span<Type> output) noexcept
|
|||
|
||||
}
|
||||
}
|
||||
template<class Type>
|
||||
bool ADSREnvelope<Type>::isSmoothing() noexcept
|
||||
{
|
||||
return currentState != State::Done;
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
void ADSREnvelope<Type>::startRelease(int releaseDelay) noexcept
|
||||
|
|
@ -207,10 +212,4 @@ void ADSREnvelope<Type>::startRelease(int releaseDelay) noexcept
|
|||
this->releaseDelay = releaseDelay;
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
constexpr bool ADSREnvelope<Type>::isSmoothing() noexcept
|
||||
{
|
||||
return currentState != State::Done;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public:
|
|||
Type getNextValue() noexcept;
|
||||
void getBlock(absl::Span<Type> output) noexcept;
|
||||
void startRelease(int releaseDelay) noexcept;
|
||||
constexpr bool isSmoothing() noexcept;
|
||||
bool isSmoothing() noexcept;
|
||||
private:
|
||||
enum class State
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ public:
|
|||
_alignedEnd = normalEnd + Alignment - endMisalignment;
|
||||
else
|
||||
_alignedEnd = normalEnd;
|
||||
|
||||
DBG("Buffer resized (" << newSize << ") at: " << this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace sfz
|
|||
|
||||
namespace config
|
||||
{
|
||||
constexpr double defaultSampleRate { 48000 };
|
||||
constexpr float defaultSampleRate { 48000 };
|
||||
constexpr int defaultSamplesPerBlock { 1024 };
|
||||
constexpr int preloadSize { 8192 };
|
||||
constexpr int numChannels { 2 };
|
||||
|
|
@ -13,9 +13,10 @@ namespace config
|
|||
constexpr int numLoadingThreads { 4 };
|
||||
constexpr int centPerSemitone { 100 };
|
||||
constexpr float virtuallyZero { 0.00005f };
|
||||
constexpr double fastReleaseDuration { 0.01 };
|
||||
constexpr float fastReleaseDuration { 0.01 };
|
||||
constexpr char defineCharacter { '$' };
|
||||
constexpr int oversamplingFactor { 2 };
|
||||
constexpr float A440 { 440.0 };
|
||||
} // namespace config
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include <string_view>
|
||||
#include <signal.h>
|
||||
#include <random>
|
||||
|
||||
inline void trimInPlace(std::string_view& s)
|
||||
{
|
||||
|
|
@ -74,4 +75,43 @@ inline constexpr T min(T op1, T op2, T op3, T op4) { return std::min(op1, std::m
|
|||
#define ASSERTFALSE
|
||||
#define ASSERT(expression)
|
||||
#define DBG(ostream)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
template<class Type>
|
||||
inline constexpr Type db2pow(Type in)
|
||||
{
|
||||
return std::pow(static_cast<Type>(10.0), in * static_cast<Type>(0.1));
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
inline constexpr Type pow2db(Type in)
|
||||
{
|
||||
return static_cast<Type>(10.0) * std::log10(in);
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
inline constexpr Type db2mag(Type in)
|
||||
{
|
||||
return std::pow(static_cast<Type>(10.0), in * static_cast<Type>(0.05));
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
inline constexpr Type mag2db(Type in)
|
||||
{
|
||||
return static_cast<Type>(20.0) * std::log10(in);
|
||||
}
|
||||
|
||||
namespace Random
|
||||
{
|
||||
static inline std::random_device randomDevice;
|
||||
static inline std::mt19937 randomGenerator { randomDevice() };
|
||||
}
|
||||
|
||||
inline float midiNoteFrequency(const int noteNumber)
|
||||
{
|
||||
return 440.0f * std::pow(2.0f, (noteNumber - 69) / 12.0f);
|
||||
}
|
||||
|
||||
constexpr double pi() { return std::acos(-1); }
|
||||
constexpr double twoPi() { return 2*pi(); }
|
||||
constexpr double piTwo() { return pi()/2; }
|
||||
|
|
@ -64,19 +64,19 @@ int process(jack_nframes_t numFrames, void* arg [[maybe_unused]])
|
|||
|
||||
switch (midi::status(event.buffer[0])) {
|
||||
case midi::noteOff:
|
||||
DBG("[MIDI] Note " << event.buffer[1] << " OFF at time " << event.time);
|
||||
synth->noteOff(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]);
|
||||
DBG("[MIDI] Note " << +event.buffer[1] << " OFF at time " << event.time);
|
||||
synth->noteOff(event.time, midi::channel(event.buffer[0]) + 1, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::noteOn:
|
||||
DBG("[MIDI] Note " << event.buffer[1] << " ON at time " << event.time);
|
||||
synth->noteOn(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]);
|
||||
DBG("[MIDI] Note " << +event.buffer[1] << " ON at time " << event.time);
|
||||
synth->noteOn(event.time, midi::channel(event.buffer[0]) + 1, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::polyphonicPressure:
|
||||
DBG("[MIDI] Polyphonic pressure on at time " << event.time);
|
||||
break;
|
||||
case midi::controlChange:
|
||||
DBG("[MIDI] CC " << event.buffer[1] << " at time " << event.time);
|
||||
synth->cc(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]);
|
||||
DBG("[MIDI] CC " << +event.buffer[1] << " at time " << event.time);
|
||||
synth->cc(event.time, midi::channel(event.buffer[0]) + 1, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::programChange:
|
||||
DBG("[MIDI] Program change at time " << event.time);
|
||||
|
|
@ -100,7 +100,6 @@ int process(jack_nframes_t numFrames, void* arg [[maybe_unused]])
|
|||
};
|
||||
|
||||
synth->renderBlock(output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include "Region.h"
|
||||
#include "Helpers.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include <bits/stdint-uintn.h>
|
||||
#include <random>
|
||||
|
||||
bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||
{
|
||||
|
|
@ -14,12 +16,14 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
|||
break;
|
||||
case hash("delay_random"):
|
||||
setValueFromOpcode(opcode, delayRandom, Default::delayRange);
|
||||
delayDistribution.param( std::uniform_real_distribution<float>::param_type(0, delayRandom) );
|
||||
break;
|
||||
case hash("offset"):
|
||||
setValueFromOpcode(opcode, offset, Default::offsetRange);
|
||||
break;
|
||||
case hash("offset_random"):
|
||||
setValueFromOpcode(opcode, offsetRandom, Default::offsetRange);
|
||||
offsetDistribution.param( std::uniform_int_distribution<uint32_t>::param_type(0, offsetRandom) );
|
||||
break;
|
||||
case hash("end"):
|
||||
setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange);
|
||||
|
|
@ -250,6 +254,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
|||
break;
|
||||
case hash("amp_random"):
|
||||
setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange);
|
||||
gainDistribution.param(std::uniform_real_distribution<float>::param_type(-ampRandom, ampRandom));
|
||||
break;
|
||||
case hash("amp_velcurve_"):
|
||||
if (opcode.parameter && Default::ccRange.containsWithEnd(*opcode.parameter)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#pragma once
|
||||
#include "Helpers.h"
|
||||
#include <bits/stdint-uintn.h>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
|
@ -8,6 +10,7 @@
|
|||
#include "Defaults.h"
|
||||
#include "CCMap.h"
|
||||
#include <bitset>
|
||||
#include <random>
|
||||
|
||||
namespace sfz
|
||||
{
|
||||
|
|
@ -31,6 +34,26 @@ struct Region
|
|||
void registerAftertouch(int channel, uint8_t aftertouch);
|
||||
void registerTempo(float secondsPerQuarter);
|
||||
bool isStereo() const noexcept;
|
||||
float getBaseGain()
|
||||
{
|
||||
float baseGaindB { volume };
|
||||
baseGaindB += gainDistribution(Random::randomGenerator);
|
||||
return db2mag(baseGaindB);
|
||||
}
|
||||
uint32_t getOffset()
|
||||
{
|
||||
return offset + offsetDistribution(Random::randomGenerator);
|
||||
}
|
||||
uint32_t getDelay()
|
||||
{
|
||||
return delay + delayDistribution(Random::randomGenerator);
|
||||
}
|
||||
|
||||
uint32_t trueSampleEnd()
|
||||
{
|
||||
return min(sampleEnd, loopRange.getEnd());
|
||||
}
|
||||
|
||||
bool parseOpcode(const Opcode& opcode);
|
||||
// Sound source: sample playback
|
||||
std::string sample {}; // Sample
|
||||
|
|
@ -126,6 +149,10 @@ private:
|
|||
|
||||
int activeNotesInRange { -1 };
|
||||
int sequenceCounter { 0 };
|
||||
|
||||
std::uniform_real_distribution<float> gainDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom };
|
||||
std::uniform_real_distribution<float> delayDistribution { 0, sfz::Default::delayRandom };
|
||||
std::uniform_int_distribution<uint32_t> offsetDistribution { 0, sfz::Default::offsetRandom };
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -41,6 +41,13 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
StereoSpan(StereoBuffer<ValueType>& buffer)
|
||||
: numFrames(buffer.getNumFrames())
|
||||
, leftBuffer(buffer.getSpan(Channel::left))
|
||||
, rightBuffer(buffer.getSpan(Channel::right))
|
||||
{
|
||||
}
|
||||
|
||||
StereoSpan(StereoBuffer<ValueType>& buffer, size_t numFrames)
|
||||
: numFrames(numFrames)
|
||||
, leftBuffer(buffer.getSpan(Channel::left).first(numFrames))
|
||||
|
|
@ -55,7 +62,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
StereoSpan(StereoSpan<ValueType>& span)
|
||||
StereoSpan(const StereoSpan<ValueType>& span)
|
||||
: numFrames(span.size())
|
||||
, leftBuffer(span.left())
|
||||
, rightBuffer(span.right())
|
||||
|
|
@ -75,6 +82,12 @@ public:
|
|||
::fill<Type>(rightBuffer, value);
|
||||
}
|
||||
|
||||
void applyGain(absl::Span<const Type> gain) noexcept
|
||||
{
|
||||
::applyGain<Type>(gain, leftBuffer);
|
||||
::applyGain<Type>(gain, rightBuffer);
|
||||
}
|
||||
|
||||
void readInterleaved(absl::Span<const Type> input) noexcept
|
||||
{
|
||||
ASSERT(input.size() <= static_cast<size_t>(numChannels * numFrames));
|
||||
|
|
@ -93,32 +106,32 @@ public:
|
|||
::add<Type>(buffer.right(), rightBuffer);
|
||||
}
|
||||
|
||||
absl::Span<Type> left()
|
||||
absl::Span<Type> left() const
|
||||
{
|
||||
return leftBuffer;
|
||||
}
|
||||
|
||||
absl::Span<Type> right()
|
||||
absl::Span<Type> right() const
|
||||
{
|
||||
return rightBuffer;
|
||||
}
|
||||
|
||||
StereoSpan<Type> first(size_t length)
|
||||
StereoSpan<Type> first(size_t length) const
|
||||
{
|
||||
return { leftBuffer.first(length), rightBuffer.first(length) };
|
||||
}
|
||||
|
||||
StereoSpan<Type> last(size_t length)
|
||||
StereoSpan<Type> last(size_t length) const
|
||||
{
|
||||
return { leftBuffer.last(length), rightBuffer.last(length) };
|
||||
}
|
||||
|
||||
StereoSpan<Type> subspan(size_t pos, size_t length = absl::Span<Type>::npos)
|
||||
StereoSpan<Type> subspan(size_t pos, size_t length = absl::Span<Type>::npos) const
|
||||
{
|
||||
return { leftBuffer.subspan(pos, length), rightBuffer.subspan(pos, length) };
|
||||
}
|
||||
|
||||
size_t size()
|
||||
size_t size() const
|
||||
{
|
||||
return numFrames;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public:
|
|||
Synth()
|
||||
{
|
||||
for (int i = 0; i < config::numVoices; ++i)
|
||||
voices.push_back(std::make_unique<Voice>());
|
||||
voices.push_back(std::make_unique<Voice>(ccState));
|
||||
}
|
||||
bool loadSfzFile(const std::filesystem::path& file) final;
|
||||
int getNumRegions() const noexcept { return static_cast<int>(regions.size()); }
|
||||
|
|
@ -34,12 +34,16 @@ public:
|
|||
DBG("[Synth] Samples per block set to " << samplesPerBlock);
|
||||
this->samplesPerBlock = samplesPerBlock;
|
||||
this->tempBuffer.resize(samplesPerBlock);
|
||||
for (auto & voice: voices)
|
||||
voice->setSamplesPerBlock(samplesPerBlock);
|
||||
}
|
||||
|
||||
void setSampleRate(double sampleRate)
|
||||
void setSampleRate(float sampleRate)
|
||||
{
|
||||
DBG("[Synth] Sample rate set to " << sampleRate);
|
||||
this->sampleRate = sampleRate;
|
||||
for (auto & voice: voices)
|
||||
voice->setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
void renderBlock(StereoSpan<float> buffer)
|
||||
|
|
@ -59,7 +63,6 @@ public:
|
|||
|
||||
for (auto& region : regions) {
|
||||
if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) {
|
||||
|
||||
for (auto& voice: voices)
|
||||
{
|
||||
if (voice->checkOffGroup(delay, region->group))
|
||||
|
|
@ -70,7 +73,8 @@ public:
|
|||
if (voice == nullptr)
|
||||
continue;
|
||||
|
||||
voice->startVoice(region.get(), channel, noteNumber, velocity, Voice::TriggerType::NoteOn);
|
||||
voice->startVoice(region.get(), delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOn);
|
||||
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +91,8 @@ public:
|
|||
if (voice == nullptr)
|
||||
continue;
|
||||
|
||||
voice->startVoice(region.get(), channel, noteNumber, velocity, Voice::TriggerType::NoteOff);
|
||||
voice->startVoice(region.get(), delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOff);
|
||||
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -96,13 +101,16 @@ public:
|
|||
for (auto& voice : voices)
|
||||
voice->registerCC(delay, channel, ccNumber, ccValue);
|
||||
|
||||
ccState[ccNumber] = ccValue;
|
||||
|
||||
for (auto& region : regions) {
|
||||
if (region->registerCC(channel, ccNumber, ccValue)) {
|
||||
auto voice = findFreeVoice();
|
||||
if (voice == nullptr)
|
||||
continue;
|
||||
|
||||
voice->startVoice(region.get(), channel, ccNumber, ccValue, Voice::TriggerType::NoteOff);
|
||||
voice->startVoice(region.get(), delay, channel, ccNumber, ccValue, Voice::TriggerType::CC);
|
||||
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
167
sources/Voice.h
167
sources/Voice.h
|
|
@ -1,8 +1,12 @@
|
|||
#pragma once
|
||||
#include "ADSREnvelope.h"
|
||||
#include "StereoBuffer.h"
|
||||
#include "StereoSpan.h"
|
||||
#include "Globals.h"
|
||||
#include "Region.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "SIMDHelpers.h"
|
||||
#include "SfzHelpers.h"
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
|
|
@ -11,20 +15,58 @@ namespace sfz
|
|||
class Voice
|
||||
{
|
||||
public:
|
||||
enum class TriggerType { NoteOn, NoteOff, CC };
|
||||
void startVoice(Region* region, int channel, int number, uint8_t value, TriggerType triggerType)
|
||||
{
|
||||
Voice() = delete;
|
||||
Voice(const CCValueArray &ccState)
|
||||
: ccState(ccState) {}
|
||||
enum class TriggerType
|
||||
{
|
||||
NoteOn,
|
||||
NoteOff,
|
||||
CC
|
||||
};
|
||||
void startVoice(Region *region, int delay, int channel, int number, uint8_t value, TriggerType triggerType)
|
||||
{
|
||||
this->triggerType = triggerType;
|
||||
triggerNumber = number;
|
||||
triggerChannel = channel;
|
||||
triggerValue = value;
|
||||
|
||||
this->region = region;
|
||||
|
||||
ASSERT(delay >= 0);
|
||||
if (delay < 0)
|
||||
delay = 0;
|
||||
|
||||
DBG("Starting voice with " << region->sample);
|
||||
|
||||
state = State::playing;
|
||||
speedRatio = static_cast<float>(region->sampleRate / this->sampleRate);
|
||||
baseGain = region->getBaseGain();
|
||||
sourcePosition = region->getOffset();
|
||||
initialDelay = delay + region->getDelay();
|
||||
baseFrequency = midiNoteFrequency(number);
|
||||
prepareEGEnvelope(delay, value);
|
||||
}
|
||||
|
||||
void prepareEGEnvelope(int delay, uint8_t velocity)
|
||||
{
|
||||
auto secondsToSamples = [this](auto timeInSeconds) {
|
||||
return static_cast<int>(timeInSeconds * sampleRate);
|
||||
};
|
||||
|
||||
egEnvelope.reset(
|
||||
secondsToSamples(region->amplitudeEG.getAttack(ccState, velocity)),
|
||||
secondsToSamples(region->amplitudeEG.getRelease(ccState, velocity)),
|
||||
normalizePercents(region->amplitudeEG.getSustain(ccState, velocity)),
|
||||
delay + secondsToSamples(region->amplitudeEG.getDelay(ccState, velocity)),
|
||||
secondsToSamples(region->amplitudeEG.getDecay(ccState, velocity)),
|
||||
secondsToSamples(region->amplitudeEG.getHold(ccState, velocity)),
|
||||
normalizePercents(region->amplitudeEG.getStart(ccState, velocity)));
|
||||
}
|
||||
|
||||
void setFileData(std::unique_ptr<StereoBuffer<float>> file)
|
||||
{
|
||||
fileData = std::move(file);
|
||||
fileData.reset(file.release());
|
||||
dataReady.store(true);
|
||||
}
|
||||
|
||||
|
|
@ -33,27 +75,86 @@ public:
|
|||
return (region == nullptr);
|
||||
}
|
||||
|
||||
void registerNoteOff(int delay [[maybe_unused]], int channel [[maybe_unused]], int noteNumber [[maybe_unused]], uint8_t velocity [[maybe_unused]])
|
||||
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity[[maybe_unused]])
|
||||
{
|
||||
|
||||
if (state == State::playing && triggerChannel == channel && triggerNumber == noteNumber)
|
||||
egEnvelope.startRelease(delay);
|
||||
}
|
||||
void registerCC(int delay [[maybe_unused]], int channel [[maybe_unused]], int ccNumber [[maybe_unused]], uint8_t ccValue [[maybe_unused]])
|
||||
|
||||
void registerCC(int delay[[maybe_unused]], int channel[[maybe_unused]], int ccNumber[[maybe_unused]], uint8_t ccValue[[maybe_unused]])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void registerPitchWheel(int delay, int channel, int pitch);
|
||||
void registerAftertouch(int delay, int channel, uint8_t aftertouch);
|
||||
void registerTempo(int delay, float secondsPerQuarter);
|
||||
|
||||
void prepareToPlay(int samplesPerBlock, double sampleRate);
|
||||
void renderBlock(StereoSpan<float> buffer)
|
||||
void setSampleRate(float sampleRate)
|
||||
{
|
||||
buffer.fill(0.0f);
|
||||
this->sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
bool checkOffGroup(int delay [[maybe_unused]], uint32_t group) noexcept
|
||||
void setSamplesPerBlock(int samplesPerBlock)
|
||||
{
|
||||
if (region != nullptr && triggerType == TriggerType::NoteOn && region->offBy && *region->offBy == group )
|
||||
this->samplesPerBlock = samplesPerBlock;
|
||||
tempBuffer1.resize(samplesPerBlock);
|
||||
tempBuffer2.resize(samplesPerBlock);
|
||||
tempSpan1 = absl::MakeSpan(tempBuffer1);
|
||||
tempSpan2 = absl::MakeSpan(tempBuffer2);
|
||||
}
|
||||
|
||||
void renderBlock(StereoSpan<float> buffer)
|
||||
{
|
||||
const auto numSamples = buffer.size();
|
||||
ASSERT(static_cast<int>(numSamples) <= samplesPerBlock);
|
||||
buffer.fill(0.0f);
|
||||
|
||||
if (state == State::idle || region == nullptr)
|
||||
return;
|
||||
|
||||
if (region->isGenerator())
|
||||
fillWithGenerator(buffer);
|
||||
else
|
||||
fillWithData(buffer);
|
||||
|
||||
egEnvelope.getBlock(tempSpan1.first(numSamples));
|
||||
buffer.applyGain(tempSpan1);
|
||||
|
||||
if (!egEnvelope.isSmoothing())
|
||||
reset();
|
||||
}
|
||||
|
||||
void fillWithData(StereoSpan<float> buffer)
|
||||
{
|
||||
StereoSpan<const float> source([&]() -> StereoBuffer<float> & {
|
||||
if (dataReady)
|
||||
return *fileData;
|
||||
else
|
||||
return *region->preloadedData;
|
||||
}());
|
||||
|
||||
const auto actualBlockSize = min(buffer.size(), source.size() - sourcePosition);
|
||||
buffer.add(source.subspan(sourcePosition, actualBlockSize));
|
||||
sourcePosition += actualBlockSize;
|
||||
if (sourcePosition == source.size())
|
||||
egEnvelope.startRelease(buffer.size());
|
||||
}
|
||||
|
||||
void fillWithGenerator(StereoSpan<float> buffer)
|
||||
{
|
||||
if (region->sample != "*sine")
|
||||
return;
|
||||
|
||||
float step = baseFrequency * twoPi() / sampleRate;
|
||||
::linearRamp<float>(tempSpan1.first(buffer.size()), sourcePosition * step, step);
|
||||
::sin<float>(tempSpan1, buffer.left());
|
||||
absl::c_copy(buffer.left(), buffer.right().begin());
|
||||
sourcePosition += buffer.size();
|
||||
}
|
||||
|
||||
bool checkOffGroup(int delay[[maybe_unused]], uint32_t group) noexcept
|
||||
{
|
||||
if (region != nullptr && triggerType == TriggerType::NoteOn && region->offBy && *region->offBy == group)
|
||||
{
|
||||
// TODO: release
|
||||
return true;
|
||||
|
|
@ -82,24 +183,54 @@ public:
|
|||
return triggerType;
|
||||
}
|
||||
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (region != nullptr)
|
||||
{
|
||||
DBG("Reset voice with sample " << region->sample);
|
||||
}
|
||||
region = nullptr;
|
||||
state = State::idle;
|
||||
dataReady.store(false);
|
||||
}
|
||||
|
||||
private:
|
||||
Region* region;
|
||||
Region *region;
|
||||
|
||||
enum class State
|
||||
{
|
||||
idle,
|
||||
playing,
|
||||
release
|
||||
};
|
||||
State state;
|
||||
|
||||
TriggerType triggerType;
|
||||
int triggerNumber;
|
||||
int triggerChannel;
|
||||
uint8_t triggerValue;
|
||||
|
||||
float speedRatio{1.0};
|
||||
float pitchRatio{1.0};
|
||||
float baseGain{1.0};
|
||||
float baseFrequency{440.0};
|
||||
|
||||
uint32_t sourcePosition;
|
||||
uint32_t initialDelay;
|
||||
|
||||
std::atomic<bool> dataReady;
|
||||
std::unique_ptr<StereoBuffer<float>> fileData;
|
||||
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
double sampleRate { config::defaultSampleRate };
|
||||
Buffer<float> tempBuffer1;
|
||||
Buffer<float> tempBuffer2;
|
||||
absl::Span<float> tempSpan1{absl::MakeSpan(tempBuffer1)};
|
||||
absl::Span<float> tempSpan2{absl::MakeSpan(tempBuffer2)};
|
||||
|
||||
int samplesPerBlock{config::defaultSamplesPerBlock};
|
||||
double sampleRate{config::defaultSampleRate};
|
||||
|
||||
const CCValueArray &ccState;
|
||||
ADSREnvelope<float> egEnvelope;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace sfz
|
||||
Loading…
Add table
Reference in a new issue