BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

commit b45d6b93453d0ff72018acf23b28e4f874713a40
parent 9e1d3d4789e56c0bf72032363fa6b74d779bbcf6
Author: Matt Demanett <matt@demanett.net>
Date:   Sun,  2 Feb 2025 17:06:55 -0500

Enable linear interpolation for all oscillator sine wave outputs, with an option for FM-OP. #225

Diffstat:
MREADME-prerelease.md | 4++++
Msrc/FMOp.cpp | 20++++++++++++++++++++
Msrc/FMOp.hpp | 1+
Msrc/PEQ14XR.hpp | 2--
Msrc/dsp/oscillator.cpp | 6+++++-
Msrc/dsp/oscillator.hpp | 18+++++++++++++++---
6 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/README-prerelease.md b/README-prerelease.md @@ -167,6 +167,10 @@ A sine-wave oscillator and simple synth voice designed to allow patching up the Anti-aliasing techniques are applied when feedback or external FM are in use. Either condition for anti-aliasing can be disabled on the context menu. Prior to version 1.1.36, due to a long-standing bug, there was no anti-aliasing for external FM, unless feedback was also on. To get that behavior back, **the true vintage FM-OP sound**, disable "Anti-alias external FM" on the menu. +As of version 2.6.46, menu option "Oscillator mode" sets the quality of the oscillator's output sine wave: + - Option "Classic", the default, is the classic FM-OP sound. The sine is produced from a wavetable, without interpolation. This adds small extra harmonics to the output. + - Option "Clean" enables interpolation, yielding a nearly-pure sine wave. + _Polyphony:_ <a href="#polyphony">polyphonic</a>, with channels defined by the V/OCT input. _When <a href="#bypassing">bypassed</a>:_ no output. diff --git a/src/FMOp.cpp b/src/FMOp.cpp @@ -2,6 +2,9 @@ #include "FMOp.hpp" #include "dsp/pitch.hpp" +#define INTERPOLATION "interpolation" +#define INTERPOLATION_VALUE_ON "on" +#define INTERPOLATION_VALUE_OFF "off" #define LINEAR_LEVEL "linearLevel" #define ANTIALIAS_FEEDBACK "antialias_feedback" #define ANTIALIAS_DEPTH "antialias_depth" @@ -57,6 +60,7 @@ void FMOp::Engine::sampleRateChange() { } json_t* FMOp::saveToJson(json_t* root) { + json_object_set_new(root, INTERPOLATION, json_string(_interpolation == SineTableOscillator::INTERPOLATION_ON ? INTERPOLATION_VALUE_ON : INTERPOLATION_VALUE_OFF)); json_object_set_new(root, LINEAR_LEVEL, json_boolean(_linearLevel)); json_object_set_new(root, ANTIALIAS_FEEDBACK, json_boolean(_antiAliasFeedback)); json_object_set_new(root, ANTIALIAS_DEPTH, json_boolean(_antiAliasDepth)); @@ -64,6 +68,14 @@ json_t* FMOp::saveToJson(json_t* root) { } void FMOp::loadFromJson(json_t* root) { + json_t* i = json_object_get(root, INTERPOLATION); + if (i) { + const char *s = json_string_value(i); + if (strcmp(s, INTERPOLATION_VALUE_ON) == 0) { + _interpolation = SineTableOscillator::INTERPOLATION_ON; + } + } + json_t* ll = json_object_get(root, LINEAR_LEVEL); if (ll) { _linearLevel = json_is_true(ll); @@ -174,6 +186,8 @@ void FMOp::modulateChannel(int c) { if (inputs[LEVEL_INPUT].isConnected()) { e.level *= clamp(inputs[LEVEL_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); } + + e.sineTable.setInterpolation(_interpolation); } void FMOp::processAlways(const ProcessArgs& args) { @@ -346,6 +360,12 @@ struct FMOpWidget : BGModuleWidget { void contextMenu(Menu* menu) override { auto fmop = dynamic_cast<FMOp*>(module); assert(fmop); + + OptionsMenuItem* om = new OptionsMenuItem("Oscillator mode"); + om->addItem(OptionMenuItem("Classic (extra harmonics)", [fmop]() { return fmop->_interpolation == SineTableOscillator::INTERPOLATION_OFF; }, [fmop]() { fmop->_interpolation = SineTableOscillator::INTERPOLATION_OFF; })); + om->addItem(OptionMenuItem("Clean (pure sine)", [fmop]() { return fmop->_interpolation == SineTableOscillator::INTERPOLATION_ON; }, [fmop]() { fmop->_interpolation = SineTableOscillator::INTERPOLATION_ON; })); + OptionsMenuItem::addToMenu(om, menu); + menu->addChild(new BoolOptionMenuItem("Linear level response", [fmop]() { return &fmop->_linearLevel; })); menu->addChild(new BoolOptionMenuItem("Anti-alias feedback", [fmop]() { return &fmop->_antiAliasFeedback; })); diff --git a/src/FMOp.hpp b/src/FMOp.hpp @@ -83,6 +83,7 @@ struct FMOp : BGModule { void sampleRateChange(); }; + SineTableOscillator::Interpolation _interpolation = SineTableOscillator::INTERPOLATION_OFF; bool _linearLevel = false; bool _antiAliasFeedback = true; bool _antiAliasDepth = true; diff --git a/src/PEQ14XR.hpp b/src/PEQ14XR.hpp @@ -27,8 +27,6 @@ struct PEQ14XR : ExpanderModule<PEQ14ExpanderMessage, ExpandableModule<PEQ14Expa struct BandOscillator { Phasor _phasor; TriangleOscillator _oscillator; - // SineTableOscillator _oscillator; - // BandLimitedSawOscillator _oscillator; inline void setSampleRate(float sr) { _phasor.setSampleRate(sr); } inline void setFrequency(float f) { _phasor.setFrequency(f); } diff --git a/src/dsp/oscillator.cpp b/src/dsp/oscillator.cpp @@ -59,9 +59,13 @@ float Phasor::nextForPhase(phase_t phase) { } +void TablePhasor::setInterpolation(Interpolation interpolation) { + _interpolation = interpolation; +} + float TablePhasor::nextForPhase(phase_t phase) { phase %= cyclePhase; - if (_tableLength >= 1024) { + if (_interpolation == INTERPOLATION_OFF || (_interpolation == INTERPOLATION_DEFAULT && _tableLength >= 1024)) { int i = (((((uint64_t)phase) << 16) / cyclePhase) * _tableLength) >> 16; i %= _tableLength; return _table.value(i); diff --git a/src/dsp/oscillator.hpp b/src/dsp/oscillator.hpp @@ -102,20 +102,31 @@ struct Phasor : OscillatorGenerator { }; struct TablePhasor : Phasor { + enum Interpolation { + INTERPOLATION_DEFAULT, + INTERPOLATION_ON, + INTERPOLATION_OFF + }; + const Table& _table; int _tableLength; + Interpolation _interpolation; TablePhasor( const Table& table, double sampleRate = 1000.0f, - double frequency = 100.0f + double frequency = 100.0f, + Interpolation interpolation = INTERPOLATION_DEFAULT ) : Phasor(sampleRate, frequency) , _table(table) , _tableLength(table.length()) + , _interpolation(interpolation) { } + void setInterpolation(Interpolation interpolation); + float nextForPhase(phase_t phase) override; }; @@ -151,9 +162,10 @@ struct SineOscillator : OscillatorGenerator { struct SineTableOscillator : TablePhasor { SineTableOscillator( float sampleRate = 1000.0f, - float frequency = 100.0f + float frequency = 100.0f, + Interpolation interpolation = INTERPOLATION_ON ) - : TablePhasor(StaticSineTable::table(), sampleRate, frequency) + : TablePhasor(StaticSineTable::table(), sampleRate, frequency, interpolation) { } };