From 5295a97981a89a9925c47dc8d2a0d0fb5708440b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 2 Mar 2020 15:47:14 +0100 Subject: [PATCH] Add SFZ support of generators --- src/CMakeLists.txt | 1 + src/sfizz/Resources.cpp | 58 ++++++++++++++++++++++++++++++++++++++++ src/sfizz/Resources.h | 9 +++++++ src/sfizz/Voice.cpp | 53 ++++++++++++++++++++++-------------- src/sfizz/Voice.h | 3 +++ src/sfizz/Wavetables.cpp | 5 +--- src/sfizz/Wavetables.h | 2 +- tests/DemoWavetables.cpp | 8 +++--- tests/PlotWavetables.cpp | 2 +- 9 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 src/sfizz/Resources.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5c8882f..171721f4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/sfizz/Resources.cpp b/src/sfizz/Resources.cpp new file mode 100644 index 00000000..af250853 --- /dev/null +++ b/src/sfizz/Resources.cpp @@ -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()) +{ +} + + +} diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 616e2cac..f4ec3f3d 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -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(); }; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index e6bb2f94..065a6672 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -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 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() / sampleRate; - fill(jumps, step); + fill(frequencies, baseFrequency); if (region->bendStep > 1) pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor); else pitchBendEnvelope.getBlock(bends); - applyGain(bends, jumps); - jumps[0] += phase; - cumsum(jumps, phases); - phase = phases.back(); + applyGain(bends, frequencies); - sin(phases, leftSpan); + waveOscillator.processModulated(frequencies.data(), leftSpan.data(), buffer.getNumFrames()); copy(leftSpan, rightSpan); - - // Wrap the phase so we don't loose too much precision on longer notes - const auto numTwoPiWraps = static_cast(phase / twoPi()); - phase -= twoPi() * static_cast(numTwoPiWraps); } } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index d46c23fa..291b1a38 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -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 volumeEnvelope; float bendStepFactor { centsFactor(1) }; + WavetableOscillator waveOscillator; + Duration dataDuration; Duration amplitudeDuration; Duration panningDuration; diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 4b0fccc0..4c8017fa 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -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) { diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index 6e18978a..8a314fdc 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -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(); diff --git a/tests/DemoWavetables.cpp b/tests/DemoWavetables.cpp index 2e8b77a8..aaaea98b 100644 --- a/tests/DemoWavetables.cpp +++ b/tests/DemoWavetables.cpp @@ -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); diff --git a/tests/PlotWavetables.cpp b/tests/PlotWavetables.cpp index f37a6897..b495f5ae 100644 --- a/tests/PlotWavetables.cpp +++ b/tests/PlotWavetables.cpp @@ -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) {