sfizz/sources/Voice.h

261 lines
7.3 KiB
C
Raw Normal View History

2019-07-29 02:13:03 +02:00
#pragma once
2019-08-24 15:32:06 +02:00
#include "ADSREnvelope.h"
2019-08-25 14:01:03 +02:00
#include "Defaults.h"
2019-08-23 01:27:39 +02:00
#include "Globals.h"
#include "Region.h"
2019-08-24 15:32:06 +02:00
#include "SIMDHelpers.h"
#include "SfzHelpers.h"
2019-08-24 15:50:55 +02:00
#include "StereoBuffer.h"
#include "StereoSpan.h"
#include "absl/types/span.h"
#include <atomic>
#include <memory>
2019-07-29 02:13:03 +02:00
2019-08-24 15:50:55 +02:00
namespace sfz {
class Voice {
2019-07-29 02:13:03 +02:00
public:
2019-08-24 15:32:06 +02:00
Voice() = delete;
2019-08-24 15:50:55 +02:00
Voice(const CCValueArray& ccState)
: ccState(ccState)
2019-08-24 15:32:06 +02:00
{
2019-08-24 15:50:55 +02:00
}
enum class TriggerType {
2019-08-24 15:32:06 +02:00
NoteOn,
NoteOff,
CC
};
2019-08-24 15:50:55 +02:00
void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType)
2019-08-24 15:32:06 +02:00
{
2019-08-23 01:27:39 +02:00
this->triggerType = triggerType;
triggerNumber = number;
triggerChannel = channel;
triggerValue = value;
this->region = region;
2019-08-24 15:32:06 +02:00
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)));
2019-08-23 01:27:39 +02:00
}
void setFileData(std::unique_ptr<StereoBuffer<float>, std::function<void(StereoBuffer<float>*)>> file)
{
fileData = std::move(file);
dataReady.store(true, std::memory_order_seq_cst);
}
2019-08-23 01:27:39 +02:00
2019-08-23 14:16:33 +02:00
bool isFree()
{
return (region == nullptr);
}
2019-08-24 15:50:55 +02:00
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]])
2019-08-24 02:46:40 +02:00
{
2019-08-25 12:45:33 +02:00
if (region == nullptr)
return;
if (state != State::playing)
return;
if (triggerChannel == channel && triggerNumber == noteNumber) {
noteIsOff = true;
2019-08-25 12:45:33 +02:00
if (region->loopMode == SfzLoopMode::one_shot)
return;
if (ccState[64] < 63)
egEnvelope.startRelease(delay);
}
2019-08-24 02:46:40 +02:00
}
2019-08-24 15:50:55 +02:00
void registerCC(int delay [[maybe_unused]], int channel [[maybe_unused]], int ccNumber [[maybe_unused]], uint8_t ccValue [[maybe_unused]])
2019-08-24 15:32:06 +02:00
{
if (ccNumber == 64 && noteIsOff && ccValue < 63)
egEnvelope.startRelease(delay);
2019-08-24 02:46:40 +02:00
}
2019-08-24 15:32:06 +02:00
2019-08-23 01:27:39 +02:00
void registerPitchWheel(int delay, int channel, int pitch);
void registerAftertouch(int delay, int channel, uint8_t aftertouch);
void registerTempo(int delay, float secondsPerQuarter);
2019-08-24 15:32:06 +02:00
void setSampleRate(float sampleRate)
{
this->sampleRate = sampleRate;
}
void setSamplesPerBlock(int samplesPerBlock)
{
this->samplesPerBlock = samplesPerBlock;
tempBuffer1.resize(samplesPerBlock);
tempBuffer2.resize(samplesPerBlock);
tempSpan1 = absl::MakeSpan(tempBuffer1);
tempSpan2 = absl::MakeSpan(tempBuffer2);
}
2019-08-24 02:46:40 +02:00
void renderBlock(StereoSpan<float> buffer)
2019-08-23 01:27:39 +02:00
{
2019-08-24 15:32:06 +02:00
const auto numSamples = buffer.size();
ASSERT(static_cast<int>(numSamples) <= samplesPerBlock);
2019-08-23 01:27:39 +02:00
buffer.fill(0.0f);
2019-08-24 15:32:06 +02:00
if (state == State::idle || region == nullptr)
return;
if (region->isGenerator())
fillWithGenerator(buffer);
else
fillWithData(buffer);
egEnvelope.getBlock(tempSpan1.first(numSamples));
buffer.applyGain(tempSpan1);
buffer.applyGain(baseGain);
2019-08-24 15:32:06 +02:00
if (!egEnvelope.isSmoothing())
reset();
}
void fillWithData(StereoSpan<float> buffer)
{
2019-08-24 15:50:55 +02:00
const StereoSpan<const float> source([&]() -> StereoBuffer<float>& {
2019-08-25 00:46:31 +02:00
// TODO: shouldn't need to check fileData here, something is a bit strange...
if (dataReady.load(std::memory_order_seq_cst) && fileData != nullptr)
2019-08-24 15:32:06 +02:00
return *fileData;
else
return *region->preloadedData;
}());
const auto actualBlockSize = min(buffer.size(), source.size() - sourcePosition);
buffer.add(source.subspan(sourcePosition, actualBlockSize));
sourcePosition += actualBlockSize;
2019-08-24 15:32:06 +02:00
if (sourcePosition == source.size())
egEnvelope.startRelease(buffer.size());
2019-08-23 01:27:39 +02:00
}
2019-08-24 15:32:06 +02:00
void fillWithGenerator(StereoSpan<float> buffer)
2019-08-23 20:48:44 +02:00
{
2019-08-24 15:32:06 +02:00
if (region->sample != "*sine")
return;
2019-08-24 15:50:55 +02:00
float step = baseFrequency * twoPi<float> / sampleRate;
2019-08-24 15:32:06 +02:00
::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();
}
2019-08-24 15:50:55 +02:00
bool checkOffGroup(int delay [[maybe_unused]], uint32_t group) noexcept
2019-08-24 15:32:06 +02:00
{
2019-08-24 15:50:55 +02:00
if (region != nullptr && triggerType == TriggerType::NoteOn && region->offBy && *region->offBy == group) {
2019-08-23 20:48:44 +02:00
// 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;
}
2019-08-23 01:27:39 +02:00
void reset()
{
2019-08-25 00:46:31 +02:00
dataReady.store(false);
state = State::idle;
2019-08-24 15:50:55 +02:00
if (region != nullptr) {
2019-08-24 15:32:06 +02:00
DBG("Reset voice with sample " << region->sample);
}
region = nullptr;
noteIsOff = false;
}
void garbageCollect()
{
if (state == State::idle && region == nullptr)
fileData.reset();
2019-08-23 01:27:39 +02:00
}
2019-08-24 15:32:06 +02:00
2019-07-29 02:13:03 +02:00
private:
2019-08-25 14:26:20 +02:00
Region* region { nullptr };
2019-08-24 15:32:06 +02:00
2019-08-24 15:50:55 +02:00
enum class State {
2019-08-24 15:32:06 +02:00
idle,
playing,
release
};
2019-08-25 14:26:20 +02:00
State state { State::idle };
bool noteIsOff { false };
2019-08-23 01:27:39 +02:00
TriggerType triggerType;
int triggerNumber;
int triggerChannel;
uint8_t triggerValue;
2019-08-24 15:50:55 +02:00
float speedRatio { 1.0 };
float pitchRatio { 1.0 };
float baseGain { 1.0 };
float baseFrequency { 440.0 };
2019-08-24 15:32:06 +02:00
2019-08-25 14:26:20 +02:00
uint32_t sourcePosition { 0 };
uint32_t initialDelay { 0 };
2019-08-24 15:32:06 +02:00
std::atomic<bool> dataReady { false };
2019-08-25 14:26:20 +02:00
std::unique_ptr<StereoBuffer<float>, std::function<void(StereoBuffer<float>*)>> fileData { nullptr };
2019-08-23 01:27:39 +02:00
2019-08-24 15:32:06 +02:00
Buffer<float> tempBuffer1;
Buffer<float> tempBuffer2;
2019-08-24 15:50:55 +02:00
absl::Span<float> tempSpan1 { absl::MakeSpan(tempBuffer1) };
absl::Span<float> tempSpan2 { absl::MakeSpan(tempBuffer2) };
2019-08-24 15:32:06 +02:00
2019-08-24 15:50:55 +02:00
int samplesPerBlock { config::defaultSamplesPerBlock };
double sampleRate { config::defaultSampleRate };
2019-08-24 15:32:06 +02:00
2019-08-24 15:50:55 +02:00
const CCValueArray& ccState;
2019-08-24 15:32:06 +02:00
ADSREnvelope<float> egEnvelope;
LEAK_DETECTOR(Voice);
2019-07-29 02:13:03 +02:00
};
2019-08-23 01:27:39 +02:00
2019-08-24 15:32:06 +02:00
} // namespace sfz