Clean up file/header separation
This commit is contained in:
parent
c59bb27286
commit
1de541a3c0
7 changed files with 455 additions and 401 deletions
|
|
@ -72,9 +72,8 @@ set(COMMON_SOURCES
|
||||||
sources/Synth.cpp
|
sources/Synth.cpp
|
||||||
sources/FilePool.cpp
|
sources/FilePool.cpp
|
||||||
sources/Region.cpp
|
sources/Region.cpp
|
||||||
|
sources/Voice.cpp
|
||||||
sources/FloatEnvelopes.cpp
|
sources/FloatEnvelopes.cpp
|
||||||
# sources/LinearEnvelope.cpp
|
|
||||||
# sources/ADSREnvelope.cpp
|
|
||||||
sources/Parser.cpp
|
sources/Parser.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -546,4 +546,40 @@ void sfz::Region::registerTempo(float secondsPerQuarter)
|
||||||
bpmSwitched = true;
|
bpmSwitched = true;
|
||||||
else
|
else
|
||||||
bpmSwitched = false;
|
bpmSwitched = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept
|
||||||
|
{
|
||||||
|
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
||||||
|
pitchVariationInCents += tune; // sample tuning
|
||||||
|
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
||||||
|
pitchVariationInCents += velocity / 127 * pitchVeltrack; // track velocity
|
||||||
|
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
||||||
|
return centsFactor(pitchVariationInCents);
|
||||||
|
}
|
||||||
|
float sfz::Region::getBaseGain() noexcept
|
||||||
|
{
|
||||||
|
float baseGaindB { volume };
|
||||||
|
baseGaindB += gainDistribution(Random::randomGenerator);
|
||||||
|
return db2mag(baseGaindB);
|
||||||
|
}
|
||||||
|
uint32_t sfz::Region::getOffset() noexcept
|
||||||
|
{
|
||||||
|
return offset + offsetDistribution(Random::randomGenerator);
|
||||||
|
}
|
||||||
|
uint32_t sfz::Region::getDelay() noexcept
|
||||||
|
{
|
||||||
|
return delay + delayDistribution(Random::randomGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t sfz::Region::trueSampleEnd() const noexcept
|
||||||
|
{
|
||||||
|
return min(sampleEnd, loopRange.getEnd());
|
||||||
|
}
|
||||||
|
bool sfz::Region::canUsePreloadedData() const noexcept
|
||||||
|
{
|
||||||
|
if (preloadedData == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return trueSampleEnd() < static_cast<uint32_t>(preloadedData->getNumFrames());
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include "CCMap.h"
|
#include "CCMap.h"
|
||||||
#include "Defaults.h"
|
#include "Defaults.h"
|
||||||
#include "EGDescription.h"
|
#include "EGDescription.h"
|
||||||
#include "Helpers.h"
|
|
||||||
#include "Opcode.h"
|
#include "Opcode.h"
|
||||||
#include "StereoBuffer.h"
|
#include "StereoBuffer.h"
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
|
@ -31,44 +30,14 @@ struct Region {
|
||||||
void registerAftertouch(int channel, uint8_t aftertouch);
|
void registerAftertouch(int channel, uint8_t aftertouch);
|
||||||
void registerTempo(float secondsPerQuarter);
|
void registerTempo(float secondsPerQuarter);
|
||||||
bool isStereo() const noexcept;
|
bool isStereo() const noexcept;
|
||||||
float getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept
|
float getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept;
|
||||||
{
|
float getBaseGain() noexcept;
|
||||||
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
uint32_t getOffset() noexcept;
|
||||||
pitchVariationInCents += tune; // sample tuning
|
uint32_t getDelay() noexcept;
|
||||||
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
uint32_t trueSampleEnd() const noexcept;
|
||||||
pitchVariationInCents += velocity / 127 * pitchVeltrack; // track velocity
|
bool canUsePreloadedData() const noexcept;
|
||||||
if (pitchRandom > 0)
|
|
||||||
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
|
||||||
return centsFactor(pitchVariationInCents);
|
|
||||||
}
|
|
||||||
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 canUsePreloadedData()
|
|
||||||
{
|
|
||||||
if (preloadedData == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return trueSampleEnd() < static_cast<uint32_t>(preloadedData->getNumFrames());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parseOpcode(const Opcode& opcode);
|
bool parseOpcode(const Opcode& opcode);
|
||||||
|
|
||||||
// Sound source: sample playback
|
// Sound source: sample playback
|
||||||
std::string sample {}; // Sample
|
std::string sample {}; // Sample
|
||||||
float delay { Default::delay }; // delay
|
float delay { Default::delay }; // delay
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
sfz::Synth::Synth()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < config::numVoices; ++i)
|
||||||
|
voices.push_back(std::make_unique<Voice>(ccState));
|
||||||
|
}
|
||||||
|
|
||||||
void sfz::Synth::callback(std::string_view header, const std::vector<Opcode>& members)
|
void sfz::Synth::callback(std::string_view header, const std::vector<Opcode>& members)
|
||||||
{
|
{
|
||||||
switch (hash(header)) {
|
switch (hash(header)) {
|
||||||
|
|
@ -167,3 +173,115 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename)
|
||||||
regions.resize(std::distance(regions.begin(), lastRegion) + 1);
|
regions.resize(std::distance(regions.begin(), lastRegion) + 1);
|
||||||
return parserReturned;
|
return parserReturned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sfz::Voice* sfz::Synth::findFreeVoice()
|
||||||
|
{
|
||||||
|
auto freeVoice = absl::c_find_if(voices, [](const auto& voice) { return voice->isFree(); });
|
||||||
|
if (freeVoice == voices.end()) {
|
||||||
|
DBG("Voices are overloaded, can't start a new note");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return freeVoice->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::getNumActiveVoices() const
|
||||||
|
{
|
||||||
|
auto activeVoices { 0 };
|
||||||
|
for (const auto& voice : voices) {
|
||||||
|
if (!voice->isFree())
|
||||||
|
activeVoices++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::garbageCollect()
|
||||||
|
{
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
voice->garbageCollect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::setSamplesPerBlock(int samplesPerBlock)
|
||||||
|
{
|
||||||
|
DBG("[Synth] Samples per block set to " << samplesPerBlock);
|
||||||
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
|
this->tempBuffer.resize(samplesPerBlock);
|
||||||
|
for (auto& voice : voices)
|
||||||
|
voice->setSamplesPerBlock(samplesPerBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::setSampleRate(float sampleRate)
|
||||||
|
{
|
||||||
|
DBG("[Synth] Sample rate set to " << sampleRate);
|
||||||
|
this->sampleRate = sampleRate;
|
||||||
|
for (auto& voice : voices)
|
||||||
|
voice->setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::renderBlock(StereoSpan<float> buffer)
|
||||||
|
{
|
||||||
|
ScopedFTZ ftz;
|
||||||
|
buffer.fill(0.0f);
|
||||||
|
StereoSpan<float> tempSpan { tempBuffer, buffer.size() };
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
voice->renderBlock(tempSpan);
|
||||||
|
buffer.add(tempSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity)
|
||||||
|
{
|
||||||
|
auto randValue = randNoteDistribution(Random::randomGenerator);
|
||||||
|
|
||||||
|
for (auto& region : regions) {
|
||||||
|
if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) {
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
if (voice->checkOffGroup(delay, region->group))
|
||||||
|
noteOff(delay, voice->getTriggerChannel(), voice->getTriggerNumber(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto voice = findFreeVoice();
|
||||||
|
if (voice == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
voice->startVoice(region.get(), delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOn);
|
||||||
|
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocity)
|
||||||
|
{
|
||||||
|
auto randValue = randNoteDistribution(Random::randomGenerator);
|
||||||
|
for (auto& voice : voices)
|
||||||
|
voice->registerNoteOff(delay, channel, noteNumber, velocity);
|
||||||
|
|
||||||
|
for (auto& region : regions) {
|
||||||
|
if (region->registerNoteOff(channel, noteNumber, velocity, randValue)) {
|
||||||
|
auto voice = findFreeVoice();
|
||||||
|
if (voice == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
voice->startVoice(region.get(), delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOff);
|
||||||
|
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue)
|
||||||
|
{
|
||||||
|
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(), delay, channel, ccNumber, ccValue, Voice::TriggerType::CC);
|
||||||
|
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
sources/Synth.h
132
sources/Synth.h
|
|
@ -19,16 +19,7 @@ namespace sfz {
|
||||||
|
|
||||||
class Synth : public Parser {
|
class Synth : public Parser {
|
||||||
public:
|
public:
|
||||||
Synth()
|
Synth();
|
||||||
{
|
|
||||||
for (int i = 0; i < config::numVoices; ++i)
|
|
||||||
voices.push_back(std::make_unique<Voice>(ccState));
|
|
||||||
}
|
|
||||||
|
|
||||||
~Synth()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadSfzFile(const std::filesystem::path& file) final;
|
bool loadSfzFile(const std::filesystem::path& file) final;
|
||||||
int getNumRegions() const noexcept { return static_cast<int>(regions.size()); }
|
int getNumRegions() const noexcept { return static_cast<int>(regions.size()); }
|
||||||
|
|
@ -39,124 +30,23 @@ public:
|
||||||
auto getUnknownOpcodes() { return unknownOpcodes; }
|
auto getUnknownOpcodes() { return unknownOpcodes; }
|
||||||
size_t getNumPreloadedSamples() { return filePool.getNumPreloadedSamples(); }
|
size_t getNumPreloadedSamples() { return filePool.getNumPreloadedSamples(); }
|
||||||
|
|
||||||
void setSamplesPerBlock(int samplesPerBlock)
|
void setSamplesPerBlock(int samplesPerBlock);
|
||||||
{
|
void setSampleRate(float sampleRate);
|
||||||
DBG("[Synth] Samples per block set to " << samplesPerBlock);
|
void renderBlock(StereoSpan<float> buffer);
|
||||||
this->samplesPerBlock = samplesPerBlock;
|
|
||||||
this->tempBuffer.resize(samplesPerBlock);
|
|
||||||
for (auto& voice : voices)
|
|
||||||
voice->setSamplesPerBlock(samplesPerBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
ScopedFTZ ftz;
|
|
||||||
buffer.fill(0.0f);
|
|
||||||
StereoSpan<float> tempSpan { tempBuffer, buffer.size() };
|
|
||||||
for (auto& voice : voices) {
|
|
||||||
voice->renderBlock(tempSpan);
|
|
||||||
buffer.add(tempSpan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void noteOn(int delay, int channel, int noteNumber, uint8_t velocity)
|
|
||||||
{
|
|
||||||
auto randValue = randNoteDistribution(Random::randomGenerator);
|
|
||||||
|
|
||||||
for (auto& region : regions) {
|
|
||||||
if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) {
|
|
||||||
for (auto& voice : voices) {
|
|
||||||
if (voice->checkOffGroup(delay, region->group))
|
|
||||||
noteOff(delay, voice->getTriggerChannel(), voice->getTriggerNumber(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto voice = findFreeVoice();
|
|
||||||
if (voice == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
voice->startVoice(region.get(), delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOn);
|
|
||||||
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void noteOff(int delay, int channel, int noteNumber, uint8_t velocity)
|
|
||||||
{
|
|
||||||
auto randValue = randNoteDistribution(Random::randomGenerator);
|
|
||||||
for (auto& voice : voices)
|
|
||||||
voice->registerNoteOff(delay, channel, noteNumber, velocity);
|
|
||||||
|
|
||||||
for (auto& region : regions) {
|
|
||||||
if (region->registerNoteOff(channel, noteNumber, velocity, randValue)) {
|
|
||||||
auto voice = findFreeVoice();
|
|
||||||
if (voice == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
voice->startVoice(region.get(), delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOff);
|
|
||||||
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cc(int delay, int channel, int ccNumber, uint8_t ccValue)
|
|
||||||
{
|
|
||||||
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(), delay, channel, ccNumber, ccValue, Voice::TriggerType::CC);
|
|
||||||
filePool.enqueueLoading(voice, region->sample, region->trueSampleEnd());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void noteOn(int delay, int channel, int noteNumber, uint8_t velocity);
|
||||||
|
void noteOff(int delay, int channel, int noteNumber, uint8_t velocity);
|
||||||
|
void cc(int delay, int channel, int ccNumber, uint8_t ccValue);
|
||||||
void pitchWheel(int delay, int channel, int pitch);
|
void pitchWheel(int delay, int channel, int pitch);
|
||||||
void aftertouch(int delay, int channel, uint8_t aftertouch);
|
void aftertouch(int delay, int channel, uint8_t aftertouch);
|
||||||
void tempo(int delay, float secondsPerQuarter);
|
void tempo(int delay, float secondsPerQuarter);
|
||||||
void getNumActiveVoices() const
|
|
||||||
{
|
|
||||||
auto activeVoices { 0 };
|
|
||||||
for (const auto& voice : voices) {
|
|
||||||
if (!voice->isFree())
|
|
||||||
activeVoices++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void garbageCollect()
|
void getNumActiveVoices() const;
|
||||||
{
|
void garbageCollect();
|
||||||
for (auto& voice : voices) {
|
|
||||||
voice->garbageCollect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected:
|
protected:
|
||||||
void callback(std::string_view header, const std::vector<Opcode>& members) final;
|
void callback(std::string_view header, const std::vector<Opcode>& members) final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Voice* findFreeVoice()
|
|
||||||
{
|
|
||||||
auto freeVoice = absl::c_find_if(voices, [](const auto& voice) { return voice->isFree(); });
|
|
||||||
if (freeVoice == voices.end()) {
|
|
||||||
DBG("Voices are overloaded, can't start a new note");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return freeVoice->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasGlobal { false };
|
bool hasGlobal { false };
|
||||||
bool hasControl { false };
|
bool hasControl { false };
|
||||||
int numGroups { 0 };
|
int numGroups { 0 };
|
||||||
|
|
@ -165,12 +55,15 @@ private:
|
||||||
void clear();
|
void clear();
|
||||||
void handleGlobalOpcodes(const std::vector<Opcode>& members);
|
void handleGlobalOpcodes(const std::vector<Opcode>& members);
|
||||||
void handleControlOpcodes(const std::vector<Opcode>& members);
|
void handleControlOpcodes(const std::vector<Opcode>& members);
|
||||||
|
void buildRegion(const std::vector<Opcode>& regionOpcodes);
|
||||||
|
|
||||||
std::vector<Opcode> globalOpcodes;
|
std::vector<Opcode> globalOpcodes;
|
||||||
std::vector<Opcode> masterOpcodes;
|
std::vector<Opcode> masterOpcodes;
|
||||||
std::vector<Opcode> groupOpcodes;
|
std::vector<Opcode> groupOpcodes;
|
||||||
|
|
||||||
FilePool filePool;
|
FilePool filePool;
|
||||||
CCValueArray ccState;
|
CCValueArray ccState;
|
||||||
|
Voice* findFreeVoice();
|
||||||
std::vector<CCNamePair> ccNames;
|
std::vector<CCNamePair> ccNames;
|
||||||
std::optional<uint8_t> defaultSwitch;
|
std::optional<uint8_t> defaultSwitch;
|
||||||
std::set<std::string_view> unknownOpcodes;
|
std::set<std::string_view> unknownOpcodes;
|
||||||
|
|
@ -179,7 +72,6 @@ private:
|
||||||
std::vector<std::unique_ptr<Voice>> voices;
|
std::vector<std::unique_ptr<Voice>> voices;
|
||||||
std::array<RegionPtrVector, 128> noteActivationLists;
|
std::array<RegionPtrVector, 128> noteActivationLists;
|
||||||
std::array<RegionPtrVector, 128> ccActivationLists;
|
std::array<RegionPtrVector, 128> ccActivationLists;
|
||||||
void buildRegion(const std::vector<Opcode>& regionOpcodes);
|
|
||||||
|
|
||||||
StereoBuffer<float> tempBuffer { config::defaultSamplesPerBlock };
|
StereoBuffer<float> tempBuffer { config::defaultSamplesPerBlock };
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
|
|
|
||||||
259
sources/Voice.cpp
Normal file
259
sources/Voice.cpp
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
#include "Voice.h"
|
||||||
|
#include "SIMDHelpers.h"
|
||||||
|
#include "SfzHelpers.h"
|
||||||
|
|
||||||
|
sfz::Voice::Voice(const CCValueArray& ccState)
|
||||||
|
: ccState(ccState)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, uint8_t value, sfz::Voice::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);
|
||||||
|
pitchRatio = region->getBasePitchVariation(number, value);
|
||||||
|
baseGain = region->getBaseGain();
|
||||||
|
sourcePosition = region->getOffset();
|
||||||
|
initialDelay = delay + region->getDelay();
|
||||||
|
baseFrequency = midiNoteFrequency(number) * pitchRatio;
|
||||||
|
prepareEGEnvelope(delay, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::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 sfz::Voice::setFileData(std::unique_ptr<StereoBuffer<float>> file)
|
||||||
|
{
|
||||||
|
fileData = std::move(file);
|
||||||
|
dataReady.store(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Voice::isFree() const
|
||||||
|
{
|
||||||
|
return (region == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]])
|
||||||
|
{
|
||||||
|
if (region == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (state != State::playing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (triggerChannel == channel && triggerNumber == noteNumber) {
|
||||||
|
noteIsOff = true;
|
||||||
|
|
||||||
|
if (region->loopMode == SfzLoopMode::one_shot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ccState[64] < 63)
|
||||||
|
egEnvelope.startRelease(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::registerCC(int delay, int channel [[maybe_unused]], int ccNumber, uint8_t ccValue)
|
||||||
|
{
|
||||||
|
if (ccNumber == 64 && noteIsOff && ccValue < 63)
|
||||||
|
egEnvelope.startRelease(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::registerPitchWheel(int delay [[maybe_unused]], int channel [[maybe_unused]], int pitch [[maybe_unused]])
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::registerAftertouch(int delay [[maybe_unused]], int channel [[maybe_unused]], uint8_t aftertouch [[maybe_unused]])
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::registerTempo(int delay [[maybe_unused]], float secondsPerQuarter [[maybe_unused]])
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::setSampleRate(float sampleRate)
|
||||||
|
{
|
||||||
|
this->sampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock)
|
||||||
|
{
|
||||||
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
|
tempBuffer1.resize(samplesPerBlock);
|
||||||
|
tempBuffer2.resize(samplesPerBlock);
|
||||||
|
indexBuffer.resize(samplesPerBlock);
|
||||||
|
tempSpan1 = absl::MakeSpan(tempBuffer1);
|
||||||
|
tempSpan2 = absl::MakeSpan(tempBuffer2);
|
||||||
|
indexSpan = absl::MakeSpan(indexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::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);
|
||||||
|
|
||||||
|
buffer.applyGain(baseGain);
|
||||||
|
|
||||||
|
auto envelopeSpan = tempSpan1.first(numSamples);
|
||||||
|
egEnvelope.getBlock(envelopeSpan);
|
||||||
|
buffer.applyGain(envelopeSpan);
|
||||||
|
|
||||||
|
if (!egEnvelope.isSmoothing())
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::fillWithData(StereoSpan<float> buffer)
|
||||||
|
{
|
||||||
|
auto source { [&]() {
|
||||||
|
if (region->canUsePreloadedData() || !dataReady)
|
||||||
|
return StereoSpan<const float>(*region->preloadedData);
|
||||||
|
else
|
||||||
|
return StereoSpan<const float>(*fileData);
|
||||||
|
}() };
|
||||||
|
|
||||||
|
auto indices = indexSpan.first(buffer.size());
|
||||||
|
auto jumps = tempSpan1.first(buffer.size());
|
||||||
|
auto leftCoeffs = tempSpan1.first(buffer.size());
|
||||||
|
auto rightCoeffs = tempSpan2.first(buffer.size());
|
||||||
|
|
||||||
|
::fill<float>(jumps, pitchRatio * speedRatio);
|
||||||
|
|
||||||
|
if (region->shouldLoop() && region->trueSampleEnd() <= source.size()) {
|
||||||
|
floatPosition = ::loopingSFZIndex<float, false>(
|
||||||
|
jumps,
|
||||||
|
leftCoeffs,
|
||||||
|
rightCoeffs,
|
||||||
|
indices,
|
||||||
|
floatPosition,
|
||||||
|
region->trueSampleEnd() - 1,
|
||||||
|
region->loopRange.getStart());
|
||||||
|
} else {
|
||||||
|
floatPosition = ::saturatingSFZIndex<float, false>(
|
||||||
|
jumps,
|
||||||
|
leftCoeffs,
|
||||||
|
rightCoeffs,
|
||||||
|
indices,
|
||||||
|
floatPosition,
|
||||||
|
source.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ind = indices.data();
|
||||||
|
auto leftCoeff = leftCoeffs.data();
|
||||||
|
auto rightCoeff = rightCoeffs.data();
|
||||||
|
auto left = buffer.left().data();
|
||||||
|
auto right = buffer.right().data();
|
||||||
|
while (ind < indices.end()) {
|
||||||
|
*left = source.left()[*ind] * (*leftCoeff) + source.left()[*ind + 1] * (*rightCoeff);
|
||||||
|
*right = source.right()[*ind] * (*leftCoeff) + source.right()[*ind + 1] * (*rightCoeff);
|
||||||
|
left++;
|
||||||
|
right++;
|
||||||
|
ind++;
|
||||||
|
leftCoeff++;
|
||||||
|
rightCoeff++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!region->shouldLoop() && (floatPosition + 1.01) > source.size()) {
|
||||||
|
DBG("Releasing " << region->sample);
|
||||||
|
egEnvelope.startRelease(buffer.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::fillWithGenerator(StereoSpan<float> buffer)
|
||||||
|
{
|
||||||
|
if (region->sample != "*sine")
|
||||||
|
return;
|
||||||
|
|
||||||
|
float step = baseFrequency * twoPi<float> / sampleRate;
|
||||||
|
phase = ::linearRamp<float>(tempSpan1, phase, step);
|
||||||
|
|
||||||
|
::sin<float>(tempSpan1.first(buffer.size()), buffer.left());
|
||||||
|
absl::c_copy(buffer.left(), buffer.right().begin());
|
||||||
|
|
||||||
|
sourcePosition += buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Voice::checkOffGroup(int delay [[maybe_unused]], uint32_t group) noexcept
|
||||||
|
{
|
||||||
|
if (region != nullptr && triggerType == TriggerType::NoteOn && region->offBy && *region->offBy == group) {
|
||||||
|
// TODO: release
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sfz::Voice::getTriggerNumber() const
|
||||||
|
{
|
||||||
|
return triggerNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sfz::Voice::getTriggerChannel() const
|
||||||
|
{
|
||||||
|
return triggerNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sfz::Voice::getTriggerValue() const
|
||||||
|
{
|
||||||
|
return triggerNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
sfz::Voice::TriggerType sfz::Voice::getTriggerType() const
|
||||||
|
{
|
||||||
|
return triggerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::reset()
|
||||||
|
{
|
||||||
|
dataReady.store(false);
|
||||||
|
state = State::idle;
|
||||||
|
if (region != nullptr) {
|
||||||
|
DBG("Reset voice with sample " << region->sample);
|
||||||
|
}
|
||||||
|
sourcePosition = 0;
|
||||||
|
floatPosition = 0.0f;
|
||||||
|
region = nullptr;
|
||||||
|
noteIsOff = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::garbageCollect()
|
||||||
|
{
|
||||||
|
if (state == State::idle && region == nullptr)
|
||||||
|
fileData.reset();
|
||||||
|
}
|
||||||
263
sources/Voice.h
263
sources/Voice.h
|
|
@ -1,268 +1,50 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "ADSREnvelope.h"
|
#include "ADSREnvelope.h"
|
||||||
#include "Defaults.h"
|
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "SIMDHelpers.h"
|
|
||||||
#include "SfzHelpers.h"
|
|
||||||
#include "StereoBuffer.h"
|
#include "StereoBuffer.h"
|
||||||
#include "StereoSpan.h"
|
#include "StereoSpan.h"
|
||||||
#include "absl/types/span.h"
|
#include <absl/types/span.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class Voice {
|
class Voice {
|
||||||
public:
|
public:
|
||||||
Voice() = delete;
|
Voice() = delete;
|
||||||
Voice(const CCValueArray& ccState)
|
Voice(const CCValueArray& ccState);
|
||||||
: ccState(ccState)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
enum class TriggerType {
|
enum class TriggerType {
|
||||||
NoteOn,
|
NoteOn,
|
||||||
NoteOff,
|
NoteOff,
|
||||||
CC
|
CC
|
||||||
};
|
};
|
||||||
void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType)
|
void setSampleRate(float sampleRate);
|
||||||
{
|
void setSamplesPerBlock(int samplesPerBlock);
|
||||||
this->triggerType = triggerType;
|
|
||||||
triggerNumber = number;
|
void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType);
|
||||||
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);
|
|
||||||
pitchRatio = region->getBasePitchVariation(number, value);
|
|
||||||
baseGain = region->getBaseGain();
|
|
||||||
sourcePosition = region->getOffset();
|
|
||||||
initialDelay = delay + region->getDelay();
|
|
||||||
baseFrequency = midiNoteFrequency(number) * pitchRatio;
|
|
||||||
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);
|
|
||||||
dataReady.store(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isFree() const
|
|
||||||
{
|
|
||||||
return (region == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]])
|
|
||||||
{
|
|
||||||
if (region == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (state != State::playing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (triggerChannel == channel && triggerNumber == noteNumber) {
|
|
||||||
noteIsOff = true;
|
|
||||||
|
|
||||||
if (region->loopMode == SfzLoopMode::one_shot)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (ccState[64] < 63)
|
|
||||||
egEnvelope.startRelease(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerCC(int delay [[maybe_unused]], int channel [[maybe_unused]], int ccNumber [[maybe_unused]], uint8_t ccValue [[maybe_unused]])
|
|
||||||
{
|
|
||||||
if (ccNumber == 64 && noteIsOff && ccValue < 63)
|
|
||||||
egEnvelope.startRelease(delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void setFileData(std::unique_ptr<StereoBuffer<float>> file);
|
||||||
|
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity);
|
||||||
|
void registerCC(int delay, int channel, int ccNumber, uint8_t ccValue);
|
||||||
void registerPitchWheel(int delay, int channel, int pitch);
|
void registerPitchWheel(int delay, int channel, int pitch);
|
||||||
void registerAftertouch(int delay, int channel, uint8_t aftertouch);
|
void registerAftertouch(int delay, int channel, uint8_t aftertouch);
|
||||||
void registerTempo(int delay, float secondsPerQuarter);
|
void registerTempo(int delay, float secondsPerQuarter);
|
||||||
|
bool checkOffGroup(int delay [[maybe_unused]], uint32_t group) noexcept;
|
||||||
|
|
||||||
void setSampleRate(float sampleRate)
|
void renderBlock(StereoSpan<float> buffer);
|
||||||
{
|
|
||||||
this->sampleRate = sampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSamplesPerBlock(int samplesPerBlock)
|
bool isFree() const;
|
||||||
{
|
int getTriggerNumber() const;
|
||||||
this->samplesPerBlock = samplesPerBlock;
|
int getTriggerChannel() const;
|
||||||
tempBuffer1.resize(samplesPerBlock);
|
uint8_t getTriggerValue() const;
|
||||||
tempBuffer2.resize(samplesPerBlock);
|
TriggerType getTriggerType() const;
|
||||||
indexBuffer.resize(samplesPerBlock);
|
|
||||||
tempSpan1 = absl::MakeSpan(tempBuffer1);
|
|
||||||
tempSpan2 = absl::MakeSpan(tempBuffer2);
|
|
||||||
indexSpan = absl::MakeSpan(indexBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// buffer.applyGain(baseGain);
|
|
||||||
|
|
||||||
auto envelopeSpan = tempSpan1.first(numSamples);
|
|
||||||
egEnvelope.getBlock(envelopeSpan);
|
|
||||||
// buffer.applyGain(envelopeSpan);
|
|
||||||
|
|
||||||
if (!egEnvelope.isSmoothing())
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void fillWithData(StereoSpan<float> buffer)
|
|
||||||
{
|
|
||||||
auto source { [&]() {
|
|
||||||
if (region->canUsePreloadedData() || !dataReady)
|
|
||||||
return StereoSpan<const float>(*region->preloadedData);
|
|
||||||
else
|
|
||||||
return StereoSpan<const float>(*fileData);
|
|
||||||
}()};
|
|
||||||
|
|
||||||
auto indices = indexSpan.first(buffer.size());
|
|
||||||
auto jumps = tempSpan1.first(buffer.size());
|
|
||||||
auto leftCoeffs = tempSpan1.first(buffer.size());
|
|
||||||
auto rightCoeffs = tempSpan2.first(buffer.size());
|
|
||||||
|
|
||||||
::fill<float>(jumps, pitchRatio * speedRatio);
|
|
||||||
if (region->shouldLoop() && region->trueSampleEnd() <= source.size()) {
|
|
||||||
floatPosition = ::loopingSFZIndex<float, false>(
|
|
||||||
jumps,
|
|
||||||
leftCoeffs,
|
|
||||||
rightCoeffs,
|
|
||||||
indices,
|
|
||||||
floatPosition,
|
|
||||||
region->trueSampleEnd() - 1,
|
|
||||||
region->loopRange.getStart());
|
|
||||||
} else {
|
|
||||||
floatPosition = ::saturatingSFZIndex<float, false>(
|
|
||||||
jumps,
|
|
||||||
leftCoeffs,
|
|
||||||
rightCoeffs,
|
|
||||||
indices,
|
|
||||||
floatPosition,
|
|
||||||
source.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ind = indices.data();
|
|
||||||
auto leftCoeff = leftCoeffs.data();
|
|
||||||
auto rightCoeff = rightCoeffs.data();
|
|
||||||
auto left = buffer.left().data();
|
|
||||||
auto right = buffer.right().data();
|
|
||||||
while (ind < indices.end()) {
|
|
||||||
*left = source.left()[*ind] * (*leftCoeff) + source.left()[*ind + 1] * (*rightCoeff);
|
|
||||||
*right = source.right()[*ind] * (*leftCoeff) + source.right()[*ind + 1] * (*rightCoeff);
|
|
||||||
left++;
|
|
||||||
right++;
|
|
||||||
ind++;
|
|
||||||
leftCoeff++;
|
|
||||||
rightCoeff++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!region->shouldLoop() && (floatPosition + 1.01) > source.size()) {
|
|
||||||
DBG("Releasing " << region->sample);
|
|
||||||
egEnvelope.startRelease(buffer.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void fillWithGenerator(StereoSpan<float> buffer)
|
|
||||||
{
|
|
||||||
if (region->sample != "*sine")
|
|
||||||
return;
|
|
||||||
|
|
||||||
float step = baseFrequency * twoPi<float> / sampleRate;
|
|
||||||
phase = ::linearRamp<float>(tempSpan1, phase, step);
|
|
||||||
::sin<float>(tempSpan1.first(buffer.size()), 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getTriggerNumber() const
|
|
||||||
{
|
|
||||||
return triggerNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getTriggerChannel() const
|
|
||||||
{
|
|
||||||
return triggerNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t getTriggerValue() const
|
|
||||||
{
|
|
||||||
return triggerNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
TriggerType getTriggerType() const
|
|
||||||
{
|
|
||||||
return triggerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
dataReady.store(false);
|
|
||||||
state = State::idle;
|
|
||||||
if (region != nullptr) {
|
|
||||||
DBG("Reset voice with sample " << region->sample);
|
|
||||||
}
|
|
||||||
sourcePosition = 0;
|
|
||||||
floatPosition = 0.0f;
|
|
||||||
region = nullptr;
|
|
||||||
noteIsOff = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void garbageCollect()
|
|
||||||
{
|
|
||||||
if (state == State::idle && region == nullptr)
|
|
||||||
fileData.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
void garbageCollect();
|
||||||
private:
|
private:
|
||||||
|
void fillWithData(StereoSpan<float> buffer);
|
||||||
|
void fillWithGenerator(StereoSpan<float> buffer);
|
||||||
|
void prepareEGEnvelope(int delay, uint8_t velocity);
|
||||||
|
|
||||||
Region* region { nullptr };
|
Region* region { nullptr };
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
|
|
@ -289,7 +71,6 @@ private:
|
||||||
uint32_t initialDelay { 0 };
|
uint32_t initialDelay { 0 };
|
||||||
|
|
||||||
std::atomic<bool> dataReady { false };
|
std::atomic<bool> dataReady { false };
|
||||||
// std::unique_ptr<StereoBuffer<float>, std::function<void(StereoBuffer<float>*)>> fileData { nullptr };
|
|
||||||
std::unique_ptr<StereoBuffer<float>> fileData { nullptr };
|
std::unique_ptr<StereoBuffer<float>> fileData { nullptr };
|
||||||
|
|
||||||
Buffer<float> tempBuffer1;
|
Buffer<float> tempBuffer1;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue