This commit is contained in:
paulfd 2019-08-24 15:32:06 +02:00
parent 3598d45c26
commit 43cd631316
11 changed files with 272 additions and 50 deletions

View file

@ -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;
}
}

View file

@ -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
{

View file

@ -53,8 +53,7 @@ public:
_alignedEnd = normalEnd + Alignment - endMisalignment;
else
_alignedEnd = normalEnd;
DBG("Buffer resized (" << newSize << ") at: " << this);
return true;
}

View file

@ -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

View file

@ -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; }

View file

@ -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;
}

View file

@ -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)) {

View file

@ -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

View file

@ -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;
}

View file

@ -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());
}
}
}

View file

@ -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