Add SFZ support of generators

This commit is contained in:
Jean Pierre Cimalando 2020-03-02 15:47:14 +01:00 committed by Paul Fd
parent 6e733e8c11
commit 5295a97981
9 changed files with 111 additions and 30 deletions

View file

@ -18,6 +18,7 @@ set (SFIZZ_SOURCES
sfizz/SfzFilter.cpp
sfizz/Curve.cpp
sfizz/Wavetables.cpp
sfizz/Resources.cpp
sfizz/Effects.cpp
sfizz/effects/Nothing.cpp
sfizz/effects/Filter.cpp

58
src/sfizz/Resources.cpp Normal file
View 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())
{
}
}

View file

@ -12,6 +12,8 @@
namespace sfz
{
class WavetableMulti;
struct Resources
{
MidiState midiState;
@ -19,5 +21,12 @@ struct Resources
FilePool filePool { logger };
FilterPool filterPool { midiState };
EQPool eqPool { midiState };
const WavetableMulti* waveSin { nullptr };
const WavetableMulti* waveTriangle { nullptr };
const WavetableMulti* waveSaw { nullptr };
const WavetableMulti* waveSquare { nullptr };
Resources();
};
}

View file

@ -20,6 +20,8 @@ sfz::Voice::Voice(sfz::Resources& resources)
{
filters.reserve(config::filtersPerVoice);
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
@ -35,7 +37,28 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
if (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);
if (currentPromise == nullptr) {
reset();
@ -226,6 +249,8 @@ void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept
void sfz::Voice::setSampleRate(float sampleRate) noexcept
{
this->sampleRate = sampleRate;
waveOscillator.init(sampleRate);
}
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 rightSpan = buffer.getSpan(1);
if (region->sample == "*noise") {
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
}
else if (region->sample == "*sine") {
// TODO: wavetables for sine and other generators
if (buffer.getNumFrames() == 0)
return;
auto jumps = tempSpan1.first(buffer.getNumFrames());
} else {
// wavetables for sine and other generators
auto frequencies = tempSpan1.first(buffer.getNumFrames());
auto bends = tempSpan2.first(buffer.getNumFrames());
auto phases = tempSpan2.first(buffer.getNumFrames());
const float step = baseFrequency * twoPi<float>() / sampleRate;
fill<float>(jumps, step);
fill<float>(frequencies, baseFrequency);
if (region->bendStep > 1)
pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor);
else
pitchBendEnvelope.getBlock(bends);
applyGain<float>(bends, jumps);
jumps[0] += phase;
cumsum<float>(jumps, phases);
phase = phases.back();
applyGain<float>(bends, frequencies);
sin<float>(phases, leftSpan);
waveOscillator.processModulated(frequencies.data(), leftSpan.data(), buffer.getNumFrames());
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);
}
}

View file

@ -12,6 +12,7 @@
#include "Region.h"
#include "AudioBuffer.h"
#include "MidiState.h"
#include "Wavetables.h"
#include "Resources.h"
#include "AudioSpan.h"
#include "LeakDetector.h"
@ -307,6 +308,8 @@ private:
MultiplicativeEnvelope<float> volumeEnvelope;
float bendStepFactor { centsFactor(1) };
WavetableOscillator waveOscillator;
Duration dataDuration;
Duration amplitudeDuration;
Duration panningDuration;

View file

@ -218,14 +218,11 @@ WavetableRange WavetableRange::getRangeForFrequency(float f)
constexpr unsigned WavetableMulti::_tableExtra;
WavetableMulti WavetableMulti::createForHarmonicProfile(
const HarmonicProfile& hp, unsigned tableSize, double refSampleRate)
const HarmonicProfile& hp, double amplitude, unsigned tableSize, double refSampleRate)
{
WavetableMulti wm;
constexpr unsigned multiSize = WavetableMulti::multiSize();
// amplitude to match ARIA's default generator output
constexpr double amplitude = 0.25;
wm.allocateStorage(tableSize);
for (unsigned m = 0; m < multiSize; ++m) {

View file

@ -146,7 +146,7 @@ public:
// the reference sample rate is the minimum value accepted by the DSP
// system (most defavorable wrt. aliasing)
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
static WavetableMulti createSilence();

View file

@ -84,13 +84,13 @@ bool DemoApp::initSound()
fTmpFrequency.reset(new float[bufferSize]);
fMulti[0] = sfz::WavetableMulti::createForHarmonicProfile(
sfz::HarmonicProfile::getSine(), 2048);
sfz::HarmonicProfile::getSine(), 1.0, 2048);
fMulti[1] = sfz::WavetableMulti::createForHarmonicProfile(
sfz::HarmonicProfile::getTriangle(), 2048);
sfz::HarmonicProfile::getTriangle(), 1.0, 2048);
fMulti[2] = sfz::WavetableMulti::createForHarmonicProfile(
sfz::HarmonicProfile::getSaw(), 2048);
sfz::HarmonicProfile::getSaw(), 1.0, 2048);
fMulti[3] = sfz::WavetableMulti::createForHarmonicProfile(
sfz::HarmonicProfile::getSquare(), 2048);
sfz::HarmonicProfile::getSquare(), 1.0, 2048);
fClient.reset(client);

View file

@ -67,7 +67,7 @@ int main(int argc, char* argv[])
std::cout << (i * (1.0 / tableSize)) << ' ' << table[i] << '\n';
} else {
sfz::WavetableMulti multi = sfz::WavetableMulti::createForHarmonicProfile(
*hp, tableSize);
*hp, 1.0, tableSize);
unsigned multiSize = multi.multiSize();
if (true) {