Add SFZ support of generators
This commit is contained in:
parent
6e733e8c11
commit
5295a97981
9 changed files with 111 additions and 30 deletions
|
|
@ -18,6 +18,7 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/SfzFilter.cpp
|
sfizz/SfzFilter.cpp
|
||||||
sfizz/Curve.cpp
|
sfizz/Curve.cpp
|
||||||
sfizz/Wavetables.cpp
|
sfizz/Wavetables.cpp
|
||||||
|
sfizz/Resources.cpp
|
||||||
sfizz/Effects.cpp
|
sfizz/Effects.cpp
|
||||||
sfizz/effects/Nothing.cpp
|
sfizz/effects/Nothing.cpp
|
||||||
sfizz/effects/Filter.cpp
|
sfizz/effects/Filter.cpp
|
||||||
|
|
|
||||||
58
src/sfizz/Resources.cpp
Normal file
58
src/sfizz/Resources.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// 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 "Resources.h"
|
||||||
|
#include "Wavetables.h"
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
static constexpr unsigned kTableSize = 1024;
|
||||||
|
|
||||||
|
// amplitude values are matched to reference
|
||||||
|
static constexpr double kAmplitudeSine = 0.625;
|
||||||
|
static constexpr double kAmplitudeTriangle = 0.625;
|
||||||
|
static constexpr double kAmplitudeSaw = 0.515;
|
||||||
|
static constexpr double kAmplitudeSquare = 0.515;
|
||||||
|
|
||||||
|
static const WavetableMulti* getWaveSin()
|
||||||
|
{
|
||||||
|
static auto wave = WavetableMulti::createForHarmonicProfile(
|
||||||
|
HarmonicProfile::getSine(), kAmplitudeSine, kTableSize);
|
||||||
|
return &wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const WavetableMulti* getWaveTriangle()
|
||||||
|
{
|
||||||
|
static auto wave = WavetableMulti::createForHarmonicProfile(
|
||||||
|
HarmonicProfile::getTriangle(), kAmplitudeTriangle, kTableSize);
|
||||||
|
return &wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const WavetableMulti* getWaveSaw()
|
||||||
|
{
|
||||||
|
static auto wave = WavetableMulti::createForHarmonicProfile(
|
||||||
|
HarmonicProfile::getSaw(), kAmplitudeSaw, kTableSize);
|
||||||
|
return &wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const WavetableMulti* getWaveSquare()
|
||||||
|
{
|
||||||
|
static auto wave = WavetableMulti::createForHarmonicProfile(
|
||||||
|
HarmonicProfile::getSquare(), kAmplitudeSquare, kTableSize);
|
||||||
|
return &wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resources::Resources()
|
||||||
|
: waveSin(getWaveSin()),
|
||||||
|
waveTriangle(getWaveTriangle()),
|
||||||
|
waveSaw(getWaveSaw()),
|
||||||
|
waveSquare(getWaveSquare())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
class WavetableMulti;
|
||||||
|
|
||||||
struct Resources
|
struct Resources
|
||||||
{
|
{
|
||||||
MidiState midiState;
|
MidiState midiState;
|
||||||
|
|
@ -19,5 +21,12 @@ struct Resources
|
||||||
FilePool filePool { logger };
|
FilePool filePool { logger };
|
||||||
FilterPool filterPool { midiState };
|
FilterPool filterPool { midiState };
|
||||||
EQPool eqPool { midiState };
|
EQPool eqPool { midiState };
|
||||||
|
|
||||||
|
const WavetableMulti* waveSin { nullptr };
|
||||||
|
const WavetableMulti* waveTriangle { nullptr };
|
||||||
|
const WavetableMulti* waveSaw { nullptr };
|
||||||
|
const WavetableMulti* waveSquare { nullptr };
|
||||||
|
|
||||||
|
Resources();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ sfz::Voice::Voice(sfz::Resources& resources)
|
||||||
{
|
{
|
||||||
filters.reserve(config::filtersPerVoice);
|
filters.reserve(config::filtersPerVoice);
|
||||||
equalizers.reserve(config::eqsPerVoice);
|
equalizers.reserve(config::eqsPerVoice);
|
||||||
|
|
||||||
|
waveOscillator.init(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value, sfz::Voice::TriggerType triggerType) noexcept
|
void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value, sfz::Voice::TriggerType triggerType) noexcept
|
||||||
|
|
@ -35,7 +37,28 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
|
||||||
if (delay < 0)
|
if (delay < 0)
|
||||||
delay = 0;
|
delay = 0;
|
||||||
|
|
||||||
if (!region->isGenerator()) {
|
if (region->isGenerator()) {
|
||||||
|
const WavetableMulti* wave = nullptr;
|
||||||
|
switch (hash(region->sample)) {
|
||||||
|
default:
|
||||||
|
case hash("*silence"):
|
||||||
|
break;
|
||||||
|
case hash("*sine"):
|
||||||
|
wave = resources.waveSin;
|
||||||
|
break;
|
||||||
|
case hash("*triangle"):
|
||||||
|
case hash("*tri"):
|
||||||
|
wave = resources.waveTriangle;
|
||||||
|
break;
|
||||||
|
case hash("*square"):
|
||||||
|
wave = resources.waveSquare;
|
||||||
|
break;
|
||||||
|
case hash("*saw"):
|
||||||
|
wave = resources.waveSaw;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waveOscillator.setWavetable(wave);
|
||||||
|
} else {
|
||||||
currentPromise = resources.filePool.getFilePromise(region->sample);
|
currentPromise = resources.filePool.getFilePromise(region->sample);
|
||||||
if (currentPromise == nullptr) {
|
if (currentPromise == nullptr) {
|
||||||
reset();
|
reset();
|
||||||
|
|
@ -226,6 +249,8 @@ void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept
|
||||||
void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
||||||
{
|
{
|
||||||
this->sampleRate = sampleRate;
|
this->sampleRate = sampleRate;
|
||||||
|
|
||||||
|
waveOscillator.init(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
|
@ -472,38 +497,26 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
{
|
{
|
||||||
const auto leftSpan = buffer.getSpan(0);
|
const auto leftSpan = buffer.getSpan(0);
|
||||||
const auto rightSpan = buffer.getSpan(1);
|
const auto rightSpan = buffer.getSpan(1);
|
||||||
|
|
||||||
if (region->sample == "*noise") {
|
if (region->sample == "*noise") {
|
||||||
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
}
|
} else {
|
||||||
else if (region->sample == "*sine") {
|
// wavetables for sine and other generators
|
||||||
// TODO: wavetables for sine and other generators
|
auto frequencies = tempSpan1.first(buffer.getNumFrames());
|
||||||
if (buffer.getNumFrames() == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto jumps = tempSpan1.first(buffer.getNumFrames());
|
|
||||||
auto bends = tempSpan2.first(buffer.getNumFrames());
|
auto bends = tempSpan2.first(buffer.getNumFrames());
|
||||||
auto phases = tempSpan2.first(buffer.getNumFrames());
|
|
||||||
|
|
||||||
const float step = baseFrequency * twoPi<float>() / sampleRate;
|
fill<float>(frequencies, baseFrequency);
|
||||||
fill<float>(jumps, step);
|
|
||||||
|
|
||||||
if (region->bendStep > 1)
|
if (region->bendStep > 1)
|
||||||
pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor);
|
pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor);
|
||||||
else
|
else
|
||||||
pitchBendEnvelope.getBlock(bends);
|
pitchBendEnvelope.getBlock(bends);
|
||||||
|
|
||||||
applyGain<float>(bends, jumps);
|
applyGain<float>(bends, frequencies);
|
||||||
jumps[0] += phase;
|
|
||||||
cumsum<float>(jumps, phases);
|
|
||||||
phase = phases.back();
|
|
||||||
|
|
||||||
sin<float>(phases, leftSpan);
|
waveOscillator.processModulated(frequencies.data(), leftSpan.data(), buffer.getNumFrames());
|
||||||
copy<float>(leftSpan, rightSpan);
|
copy<float>(leftSpan, rightSpan);
|
||||||
|
|
||||||
// Wrap the phase so we don't loose too much precision on longer notes
|
|
||||||
const auto numTwoPiWraps = static_cast<int>(phase / twoPi<float>());
|
|
||||||
phase -= twoPi<float>() * static_cast<float>(numTwoPiWraps);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
|
#include "Wavetables.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
#include "LeakDetector.h"
|
#include "LeakDetector.h"
|
||||||
|
|
@ -307,6 +308,8 @@ private:
|
||||||
MultiplicativeEnvelope<float> volumeEnvelope;
|
MultiplicativeEnvelope<float> volumeEnvelope;
|
||||||
float bendStepFactor { centsFactor(1) };
|
float bendStepFactor { centsFactor(1) };
|
||||||
|
|
||||||
|
WavetableOscillator waveOscillator;
|
||||||
|
|
||||||
Duration dataDuration;
|
Duration dataDuration;
|
||||||
Duration amplitudeDuration;
|
Duration amplitudeDuration;
|
||||||
Duration panningDuration;
|
Duration panningDuration;
|
||||||
|
|
|
||||||
|
|
@ -218,14 +218,11 @@ WavetableRange WavetableRange::getRangeForFrequency(float f)
|
||||||
constexpr unsigned WavetableMulti::_tableExtra;
|
constexpr unsigned WavetableMulti::_tableExtra;
|
||||||
|
|
||||||
WavetableMulti WavetableMulti::createForHarmonicProfile(
|
WavetableMulti WavetableMulti::createForHarmonicProfile(
|
||||||
const HarmonicProfile& hp, unsigned tableSize, double refSampleRate)
|
const HarmonicProfile& hp, double amplitude, unsigned tableSize, double refSampleRate)
|
||||||
{
|
{
|
||||||
WavetableMulti wm;
|
WavetableMulti wm;
|
||||||
constexpr unsigned multiSize = WavetableMulti::multiSize();
|
constexpr unsigned multiSize = WavetableMulti::multiSize();
|
||||||
|
|
||||||
// amplitude to match ARIA's default generator output
|
|
||||||
constexpr double amplitude = 0.25;
|
|
||||||
|
|
||||||
wm.allocateStorage(tableSize);
|
wm.allocateStorage(tableSize);
|
||||||
|
|
||||||
for (unsigned m = 0; m < multiSize; ++m) {
|
for (unsigned m = 0; m < multiSize; ++m) {
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ public:
|
||||||
// the reference sample rate is the minimum value accepted by the DSP
|
// the reference sample rate is the minimum value accepted by the DSP
|
||||||
// system (most defavorable wrt. aliasing)
|
// system (most defavorable wrt. aliasing)
|
||||||
static WavetableMulti createForHarmonicProfile(
|
static WavetableMulti createForHarmonicProfile(
|
||||||
const HarmonicProfile& hp, unsigned tableSize, double refSampleRate = 44100.0);
|
const HarmonicProfile& hp, double amplitude, unsigned tableSize, double refSampleRate = 44100.0);
|
||||||
|
|
||||||
// create the tiniest wavetable with null content for use with oscillators
|
// create the tiniest wavetable with null content for use with oscillators
|
||||||
static WavetableMulti createSilence();
|
static WavetableMulti createSilence();
|
||||||
|
|
|
||||||
|
|
@ -84,13 +84,13 @@ bool DemoApp::initSound()
|
||||||
fTmpFrequency.reset(new float[bufferSize]);
|
fTmpFrequency.reset(new float[bufferSize]);
|
||||||
|
|
||||||
fMulti[0] = sfz::WavetableMulti::createForHarmonicProfile(
|
fMulti[0] = sfz::WavetableMulti::createForHarmonicProfile(
|
||||||
sfz::HarmonicProfile::getSine(), 2048);
|
sfz::HarmonicProfile::getSine(), 1.0, 2048);
|
||||||
fMulti[1] = sfz::WavetableMulti::createForHarmonicProfile(
|
fMulti[1] = sfz::WavetableMulti::createForHarmonicProfile(
|
||||||
sfz::HarmonicProfile::getTriangle(), 2048);
|
sfz::HarmonicProfile::getTriangle(), 1.0, 2048);
|
||||||
fMulti[2] = sfz::WavetableMulti::createForHarmonicProfile(
|
fMulti[2] = sfz::WavetableMulti::createForHarmonicProfile(
|
||||||
sfz::HarmonicProfile::getSaw(), 2048);
|
sfz::HarmonicProfile::getSaw(), 1.0, 2048);
|
||||||
fMulti[3] = sfz::WavetableMulti::createForHarmonicProfile(
|
fMulti[3] = sfz::WavetableMulti::createForHarmonicProfile(
|
||||||
sfz::HarmonicProfile::getSquare(), 2048);
|
sfz::HarmonicProfile::getSquare(), 1.0, 2048);
|
||||||
|
|
||||||
fClient.reset(client);
|
fClient.reset(client);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ int main(int argc, char* argv[])
|
||||||
std::cout << (i * (1.0 / tableSize)) << ' ' << table[i] << '\n';
|
std::cout << (i * (1.0 / tableSize)) << ' ' << table[i] << '\n';
|
||||||
} else {
|
} else {
|
||||||
sfz::WavetableMulti multi = sfz::WavetableMulti::createForHarmonicProfile(
|
sfz::WavetableMulti multi = sfz::WavetableMulti::createForHarmonicProfile(
|
||||||
*hp, tableSize);
|
*hp, 1.0, tableSize);
|
||||||
unsigned multiSize = multi.multiSize();
|
unsigned multiSize = multi.multiSize();
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue