BogaudioModules

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

commit 432b812c93a5b521d336e4a35799598b5924daf5
parent be617d8cf088763c5f70409b007300560798d254
Author: Matt Demanett <matt@demanett.net>
Date:   Sat, 24 Mar 2018 18:27:09 -0400

FM/PM fixes all around.

Diffstat:
Mres-src/VCO-src.svg | 40+++++++++++++++++++++++++++++++++++++++-
Mres/VCO.svg | 0
Msrc/FMOp.cpp | 41+++++++++++++++++++++++++++--------------
Msrc/FMOp.hpp | 2++
Msrc/Test.cpp | 49++++++++++++++++++++++++++++++++++---------------
Msrc/Test.hpp | 16++++++++++------
Msrc/VCO.cpp | 16+++++++++-------
Msrc/XCO.cpp | 14++++++++------
8 files changed, 129 insertions(+), 49 deletions(-)

diff --git a/res-src/VCO-src.svg b/res-src/VCO-src.svg @@ -166,6 +166,44 @@ </g> </symbol> + <symbol id="knobguide-fm" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <g transform="rotate(-240) translate(15 0)"> + <text font-size="5.0pt" transform="translate(3 0) rotate(240) translate(-2.2 2.2)">0</text> + </g> + <g transform="rotate(-210) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-180) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-150) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-120) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-90) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-60) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(-30) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(0) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(30) translate(15 0)"> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" /> + </g> + <g transform="rotate(60) translate(15 0)"> + <text font-size="5.0pt" transform="translate(3 0) rotate(-60) translate(-2.2 2.2)">1</text> + </g> + </g> + </symbol> + <symbol id="knobguide-centertick" viewBox="0 0 40px 40px"> <g transform="translate(20 20)"> <g transform="rotate(-90) translate(10 0)"> @@ -278,7 +316,7 @@ </g> </g> </g> - <use xlink:href="#knobguide-attenuverter" transform="translate(18.5 221.5)" /> + <use xlink:href="#knobguide-fm" transform="translate(18.5 221.5)" /> <!-- <rect width="100" height="6" fill="#0f0" transform="translate(10 263)" /> --> diff --git a/res/VCO.svg b/res/VCO.svg Binary files differ. diff --git a/src/FMOp.cpp b/src/FMOp.cpp @@ -18,7 +18,7 @@ void FMOp::onSampleRateChange() { void FMOp::step() { lights[ENV_TO_LEVEL_LIGHT].value = _levelEnvelopeOn; lights[ENV_TO_FEEDBACK_LIGHT].value = _feedbackEnvelopeOn; - if (outputs[AUDIO_OUTPUT].active) { + if (!outputs[AUDIO_OUTPUT].active) { return; } @@ -48,14 +48,18 @@ void FMOp::step() { _baseHZ = cvToFrequency(_baseHZ); _baseHZ *= ratio; - bool levelEnvelopeOn = params[ENV_TO_LEVEL_PARAM].value < 0.5f; - if (_levelEnvelopeOn != levelEnvelopeOn) { - if (_levelEnvelopeOn) { + bool levelEnvelopeOn = params[ENV_TO_LEVEL_PARAM].value > 0.5f; + bool feedbackEnvelopeOn = params[ENV_TO_FEEDBACK_PARAM].value > 0.5f; + if (_levelEnvelopeOn != levelEnvelopeOn || _feedbackEnvelopeOn != feedbackEnvelopeOn) { + _levelEnvelopeOn = levelEnvelopeOn; + _feedbackEnvelopeOn = feedbackEnvelopeOn; + bool envelopeOn = _levelEnvelopeOn || _feedbackEnvelopeOn; + if (envelopeOn && !_envelopeOn) { _envelope.reset(); } - _levelEnvelopeOn = levelEnvelopeOn; + _envelopeOn = envelopeOn; } - if (_levelEnvelopeOn) { + if (_envelopeOn) { float sustain = params[SUSTAIN_PARAM].value; if (inputs[SUSTAIN_INPUT].active) { sustain *= clamp(inputs[SUSTAIN_INPUT].value / 10.0f, 0.0f, 1.0f); @@ -82,20 +86,29 @@ void FMOp::step() { } } + float envelope = 0.0f; + if (_envelopeOn) { + _gateTrigger.process(gateIn); + _envelope.setGate(_gateTrigger.isHigh()); + envelope = _envelope.next(); + } + + float feedback = _feedback; + if (_feedbackEnvelopeOn) { + feedback *= envelope; + } + _phasor.setFrequency(_baseHZ); - float offset = _feedback * _phasor.next(); + float offset = feedback * _phasor.next(); if (inputs[FM_INPUT].active) { - offset += clamp(inputs[FM_INPUT].value / 5.0f, -1.0f, 1.0f) * _depth * 10.0f; + offset += inputs[FM_INPUT].value * _depth * 2.0f; } float out = _sineTable.nextFromPhasor(_phasor, Phasor::radiansToPhase(offset)); out *= _level; if (_levelEnvelopeOn) { - _gateTrigger.process(gateIn); - _envelope.setGate(_gateTrigger.isHigh()); - out *= _envelope.next(); + out *= envelope; } - - outputs[AUDIO_OUTPUT].value = out * 5.0f; + outputs[AUDIO_OUTPUT].value = amplitude * out; } struct FMOpWidget : ModuleWidget { @@ -147,7 +160,7 @@ struct FMOpWidget : ModuleWidget { addParam(ParamWidget::create<Knob26>(decayParamPosition, module, FMOp::DECAY_PARAM, 0.0, 1.0, 0.1)); addParam(ParamWidget::create<Knob26>(sustainParamPosition, module, FMOp::SUSTAIN_PARAM, 0.0, 1.0, 1.0)); addParam(ParamWidget::create<Knob26>(releaseParamPosition, module, FMOp::RELEASE_PARAM, 0.0, 1.0, 0.3)); - addParam(ParamWidget::create<Knob26>(depthParamPosition, module, FMOp::DEPTH_PARAM, 0.0, 1.0, 0.5)); + addParam(ParamWidget::create<Knob26>(depthParamPosition, module, FMOp::DEPTH_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create<Knob26>(feedbackParamPosition, module, FMOp::FEEDBACK_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create<Knob26>(levelParamPosition, module, FMOp::LEVEL_PARAM, 0.0, 1.0, 1.0)); addParam(ParamWidget::create<StatefulButton9>(envToLevelParamPosition, module, FMOp::ENV_TO_LEVEL_PARAM, 0.0, 1.0, 0.0)); diff --git a/src/FMOp.hpp b/src/FMOp.hpp @@ -48,12 +48,14 @@ struct FMOp : Module { NUM_LIGHTS }; + const float amplitude = 5.0f; const int modulationSteps = 100; int _steps = 0; float _baseHZ = 0.0f; float _feedback = 0.0f; float _depth = 0.0f; float _level = 0.0f; + bool _envelopeOn = false; bool _levelEnvelopeOn = false; bool _feedbackEnvelopeOn = false; ADSR _envelope; diff --git a/src/Test.cpp b/src/Test.cpp @@ -151,29 +151,48 @@ void Test::step() { } #elif FM + const float amplitude = 5.0f; float baseHz = oscillatorPitch(); float ratio = ratio2(); - _modulator.setSampleRate(engineGetSampleRate()); - _modulator.setFrequency(baseHz * ratio2()); - float hz = _modulator.next() * index3() * ratio * baseHz; - _carrier.setSampleRate(engineGetSampleRate()); - _carrier.setFrequency(baseHz + hz); - outputs[OUT_OUTPUT].value = _carrier.next() * 5.0f; + float index = index3(); + float sampleRate = engineGetSampleRate(); + if (_baseHz != baseHz || _ratio != ratio || _index != index || _sampleRate != sampleRate) { + _baseHz = baseHz; + _ratio = ratio; + _index = index; + _sampleRate = sampleRate; + float modHz = _ratio * _baseHz; + // printf("baseHz=%f ratio=%f modHz=%f index=%f\n", _baseHz, _ratio, modHz, _index); + + _modulator.setFrequency(modHz); + _modulator.setSampleRate(_sampleRate); + _carrier.setSampleRate(_sampleRate); + + _carrier2.setSampleRate(engineGetSampleRate()); + _carrier2.setFrequency(baseHz); + _modulator2.setSampleRate(engineGetSampleRate()); + _modulator2.setFrequency(modHz); + } - // _modulator2.setSampleRate(engineGetSampleRate()); - // _modulator2.setFrequency(baseHz * ratio2()); - // float hz2 = _modulator2.next() * index3() * ratio * baseHz; - // _carrier2.setSampleRate(engineGetSampleRate()); - // _carrier2.setFrequency(std::max(baseHz + hz2, 0.0f)); - // outputs[OUT2_OUTPUT].value = _carrier2.next() * 5.0f; + // linear FM. + float modHz = _ratio * _baseHz; + _carrier.setFrequency(_baseHz + _index * _modulator.next() * modHz); // linear FM requires knowing the modulator's frequency. + outputs[OUT_OUTPUT].value = _carrier.next() * amplitude; + + // PM for comparison - identical output. + _carrier2.advancePhase(); + outputs[OUT2_OUTPUT].value = _carrier2.nextFromPhasor(_carrier2, Phasor::radiansToPhase(_index * _modulator2.next())) * amplitude; #elif PM + const float amplitude = 5.0f; + float baseHz = oscillatorPitch(); + float modHz = ratio2() * baseHz; _carrier.setSampleRate(engineGetSampleRate()); - _carrier.setFrequency(oscillatorPitch()); + _carrier.setFrequency(baseHz); _modulator.setSampleRate(engineGetSampleRate()); - _modulator.setFrequency(_carrier._frequency * ratio2()); + _modulator.setFrequency(modHz); _carrier.advancePhase(); - outputs[OUT_OUTPUT].value = _carrier.nextFromPhasor(_carrier, Phasor::radiansToPhase(index3() * _modulator.next())) * 5.0f; + outputs[OUT_OUTPUT].value = _carrier.nextFromPhasor(_carrier, Phasor::radiansToPhase(index3() * _modulator.next())) * amplitude; #elif FEEDBACK_PM _carrier.setSampleRate(engineGetSampleRate()); diff --git a/src/Test.hpp b/src/Test.hpp @@ -9,12 +9,12 @@ extern Model* modelTest; // #define SINE 1 // #define SQUARE 1 // #define SAW 1 -#define SATSAW 1 +// #define SATSAW 1 // #define TRIANGLE 1 // #define SINEBANK 1 // #define OVERSAMPLING 1 // #define OVERSAMPLED_BL 1 -// #define FM 1 +#define FM 1 // #define PM 1 // #define FEEDBACK_PM 1 // #define EG 1 @@ -121,10 +121,14 @@ struct Test : Module { BandLimitedSawOscillator _saw2; LowPassFilter _lpf; #elif FM + float _baseHz = 0.0f; + float _ratio = 0.0f; + float _index = 0.0f; + float _sampleRate = 0.0f; SineTableOscillator _modulator; SineTableOscillator _carrier; - // SineTableOscillator _modulator2; - // SineTableOscillator _carrier2; + SineTableOscillator _modulator2; + SineTableOscillator _carrier2; #elif PM SineTableOscillator _modulator; SineTableOscillator _carrier; @@ -173,8 +177,8 @@ struct Test : Module { #elif FM , _modulator(44100.0, 1000.0, 1.0) , _carrier(44100.0, 1000.0, 1.0) - // , _modulator2(44100.0, 1000.0, 1.0) - // , _carrier2(44100.0, 1000.0, 1.0) + , _modulator2(44100.0, 1000.0, 1.0) + , _carrier2(44100.0, 1000.0, 1.0) #elif PM , _modulator(44100.0, 1000.0, 1.0) , _carrier(44100.0, 1000.0) diff --git a/src/VCO.cpp b/src/VCO.cpp @@ -56,10 +56,12 @@ void VCO::step() { _fmLinearMode = ((int)params[FM_TYPE_PARAM].value) == 1; } - if (inputs[FM_INPUT].active) { + float phaseOffset = 0.0f; + if (inputs[FM_INPUT].active && _fmDepth > 0.01f) { float fm = inputs[FM_INPUT].value * _fmDepth; if (_fmLinearMode) { - _phasor.setFrequency(_baseHz + fm * _baseHz); + _phasor.setFrequency(_baseHz); + phaseOffset = Phasor::radiansToPhase(2.0f * fm); } else { _phasor.setFrequency(cvToFrequency(_baseVOct + fm)); @@ -70,16 +72,16 @@ void VCO::step() { } _phasor.advancePhase(); if (outputs[SQUARE_OUTPUT].active) { - outputs[SQUARE_OUTPUT].value = amplitude * _square.nextFromPhasor(_phasor); + outputs[SQUARE_OUTPUT].value = amplitude * _square.nextFromPhasor(_phasor, phaseOffset); } if (outputs[SAW_OUTPUT].active) { - outputs[SAW_OUTPUT].value = amplitude * _saw.nextFromPhasor(_phasor); + outputs[SAW_OUTPUT].value = amplitude * _saw.nextFromPhasor(_phasor, phaseOffset); } if (outputs[TRIANGLE_OUTPUT].active) { - outputs[TRIANGLE_OUTPUT].value = amplitude * _triangle.nextFromPhasor(_phasor); + outputs[TRIANGLE_OUTPUT].value = amplitude * _triangle.nextFromPhasor(_phasor, phaseOffset); } if (outputs[SINE_OUTPUT].active) { - outputs[SINE_OUTPUT].value = amplitude * _sine.nextFromPhasor(_phasor); + outputs[SINE_OUTPUT].value = amplitude * _sine.nextFromPhasor(_phasor, phaseOffset); } } @@ -126,7 +128,7 @@ struct VCOWidget : ModuleWidget { addParam(ParamWidget::create<Knob16>(fineParamPosition, module, VCO::FINE_PARAM, -1.0, 1.0, 0.0)); addParam(ParamWidget::create<StatefulButton9>(slowParamPosition, module, VCO::SLOW_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create<Knob26>(pwParamPosition, module, VCO::PW_PARAM, -1.0, 1.0, 0.0)); - addParam(ParamWidget::create<Knob26>(fmParamPosition, module, VCO::FM_PARAM, -1.0, 1.0, 0.0)); + addParam(ParamWidget::create<Knob26>(fmParamPosition, module, VCO::FM_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create<StatefulButton9>(fmTypeParamPosition, module, VCO::FM_TYPE_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create<Port24>(pitchInputPosition, Port::INPUT, module, VCO::PITCH_INPUT)); diff --git a/src/XCO.cpp b/src/XCO.cpp @@ -73,10 +73,12 @@ void XCO::step() { _sineMix = level(params[SINE_MIX_PARAM], inputs[SINE_MIX_INPUT]); } - if (inputs[FM_INPUT].active) { + float phaseOffset = 0.0f; + if (inputs[FM_INPUT].active && _fmDepth > 0.01f) { float fm = inputs[FM_INPUT].value * _fmDepth; if (_fmLinearMode) { - _phasor.setFrequency(_baseHz + fm * _baseHz); + _phasor.setFrequency(_baseHz); + phaseOffset = Phasor::radiansToPhase(2.0f * fm); } else { _phasor.setFrequency(cvToFrequency(_baseVOct + fm)); @@ -88,16 +90,16 @@ void XCO::step() { _phasor.advancePhase(); float mix = 0.0f; if (outputs[MIX_OUTPUT].active || outputs[SQUARE_OUTPUT].active) { - mix += outputs[SQUARE_OUTPUT].value = amplitude * _squareMix * _square.nextFromPhasor(_phasor, _squarePhaseOffset); + mix += outputs[SQUARE_OUTPUT].value = amplitude * _squareMix * _square.nextFromPhasor(_phasor, _squarePhaseOffset + phaseOffset); } if (outputs[MIX_OUTPUT].active || outputs[SAW_OUTPUT].active) { - mix += outputs[SAW_OUTPUT].value = amplitude * _sawMix * _saw.nextFromPhasor(_phasor, _sawPhaseOffset); + mix += outputs[SAW_OUTPUT].value = amplitude * _sawMix * _saw.nextFromPhasor(_phasor, _sawPhaseOffset + phaseOffset); } if (outputs[MIX_OUTPUT].active || outputs[TRIANGLE_OUTPUT].active) { - mix += outputs[TRIANGLE_OUTPUT].value = amplitude * _triangleMix * _triangle.nextFromPhasor(_phasor, _trianglePhaseOffset); + mix += outputs[TRIANGLE_OUTPUT].value = amplitude * _triangleMix * _triangle.nextFromPhasor(_phasor, _trianglePhaseOffset + phaseOffset); } if (outputs[MIX_OUTPUT].active || outputs[SINE_OUTPUT].active) { - mix += outputs[SINE_OUTPUT].value = amplitude * _sineMix * _sine.nextFromPhasor(_phasor, _sinePhaseOffset); + mix += outputs[SINE_OUTPUT].value = amplitude * _sineMix * _sine.nextFromPhasor(_phasor, _sinePhaseOffset + phaseOffset); } if (outputs[MIX_OUTPUT].active) { outputs[MIX_OUTPUT].value = mix;