BogaudioModules

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

commit cd12f5f743a7df5d167b6a0d6fb2265b1554fc67
parent ed22f3221686792f2269e5ed600d931b8685e60b
Author: Matt Demanett <matt@demanett.net>
Date:   Thu, 26 Sep 2019 19:18:23 -0400

Poly: FM-OP.

Diffstat:
Msrc/FMOp.cpp | 163+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/FMOp.hpp | 65+++++++++++++++++++++++++++++++++++++----------------------------
2 files changed, 134 insertions(+), 94 deletions(-)

diff --git a/src/FMOp.cpp b/src/FMOp.cpp @@ -37,21 +37,21 @@ bool FMOp::LevelParamQuantity::isLinear() { return dynamic_cast<FMOp*>(module)->_linearLevel; } -void FMOp::reset() { - _envelope.reset(); - _gateTrigger.reset(); +void FMOp::Engine::reset() { + envelope.reset(); + gateTrigger.reset(); } -void FMOp::sampleRateChange() { +void FMOp::Engine::sampleRateChange() { float sampleRate = APP->engine->getSampleRate(); - _envelope.setSampleRate(sampleRate); - _phasor.setSampleRate(sampleRate); - _decimator.setParams(sampleRate, oversample); - _maxFrequency = 0.475f * sampleRate; - _feedbackSL.setParams(sampleRate, 5.0f, 1.0f); - _depthSL.setParams(sampleRate, 5.0f, 1.0f); - _levelSL.setParams(sampleRate, 10.0f, 1.0f); - _sustainSL.setParams(sampleRate, 1.0f, 1.0f); + envelope.setSampleRate(sampleRate); + phasor.setSampleRate(sampleRate); + decimator.setParams(sampleRate, oversample); + maxFrequency = 0.475f * sampleRate; + feedbackSL.setParams(sampleRate, 5.0f, 1.0f); + depthSL.setParams(sampleRate, 5.0f, 1.0f); + levelSL.setParams(sampleRate, 10.0f, 1.0f); + sustainSL.setParams(sampleRate, 1.0f, 1.0f); } json_t* FMOp::dataToJson() { @@ -67,14 +67,49 @@ void FMOp::dataFromJson(json_t* root) { } } +void FMOp::reset() { + for (int c = 0; c < _channels; ++c) { + _engines[c]->reset(); + } +} + +void FMOp::sampleRateChange() { + for (int c = 0; c < _channels; ++c) { + _engines[c]->sampleRateChange(); + } +} + bool FMOp::active() { return outputs[AUDIO_OUTPUT].isConnected(); } +int FMOp::channels() { + return std::max(1, inputs[PITCH_INPUT].getChannels()); +} + +void FMOp::addEngine(int c) { + _engines[c] = new Engine(); + _engines[c]->reset(); + _engines[c]->sampleRateChange(); +} + +void FMOp::removeEngine(int c) { + delete _engines[c]; + _engines[c] = NULL; +} + void FMOp::modulate() { + _levelEnvelopeOn = params[ENV_TO_LEVEL_PARAM].getValue() > 0.5f; + _feedbackEnvelopeOn = params[ENV_TO_FEEDBACK_PARAM].getValue() > 0.5f; + _depthEnvelopeOn = params[ENV_TO_DEPTH_PARAM].getValue() > 0.5f; +} + +void FMOp::modulateChannel(int c) { + Engine& e = *_engines[c]; + float pitchIn = 0.0f; if (inputs[PITCH_INPUT].isConnected()) { - pitchIn = inputs[PITCH_INPUT].getVoltage(); + pitchIn = inputs[PITCH_INPUT].getVoltage(c); } float ratio = params[RATIO_PARAM].getValue(); if (ratio < 0.0f) { @@ -89,46 +124,39 @@ void FMOp::modulate() { frequency += params[FINE_PARAM].getValue() / 12.0f; frequency = cvToFrequency(frequency); frequency *= ratio; - frequency = clamp(frequency, -_maxFrequency, _maxFrequency); - _phasor.setFrequency(frequency / (float)oversample); - - bool levelEnvelopeOn = params[ENV_TO_LEVEL_PARAM].getValue() > 0.5f; - bool feedbackEnvelopeOn = params[ENV_TO_FEEDBACK_PARAM].getValue() > 0.5f; - bool depthEnvelopeOn = params[ENV_TO_DEPTH_PARAM].getValue() > 0.5f; - if (_levelEnvelopeOn != levelEnvelopeOn || _feedbackEnvelopeOn != feedbackEnvelopeOn || _depthEnvelopeOn != depthEnvelopeOn) { - _levelEnvelopeOn = levelEnvelopeOn; - _feedbackEnvelopeOn = feedbackEnvelopeOn; - _depthEnvelopeOn = depthEnvelopeOn; - bool envelopeOn = _levelEnvelopeOn || _feedbackEnvelopeOn || _depthEnvelopeOn; - if (envelopeOn && !_envelopeOn) { - _envelope.reset(); - } - _envelopeOn = envelopeOn; + frequency = clamp(frequency, -e.maxFrequency, e.maxFrequency); + e.phasor.setFrequency(frequency / (float)oversample); + + bool envelopeOn = _levelEnvelopeOn || _feedbackEnvelopeOn || _depthEnvelopeOn; + if (envelopeOn && !e.envelopeOn) { + e.envelope.reset(); } - if (_envelopeOn) { + e.envelopeOn = envelopeOn; + + if (e.envelopeOn) { float sustain = params[SUSTAIN_PARAM].getValue(); if (inputs[SUSTAIN_INPUT].isConnected()) { - sustain *= clamp(inputs[SUSTAIN_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + sustain *= clamp(inputs[SUSTAIN_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); } - _envelope.setAttack(powf(params[ATTACK_PARAM].getValue(), 2.0f) * 10.f); - _envelope.setDecay(powf(params[DECAY_PARAM].getValue(), 2.0f) * 10.f); - _envelope.setSustain(_sustainSL.next(sustain)); - _envelope.setRelease(powf(params[RELEASE_PARAM].getValue(), 2.0f) * 10.f); + e.envelope.setAttack(powf(params[ATTACK_PARAM].getValue(), 2.0f) * 10.f); + e.envelope.setDecay(powf(params[DECAY_PARAM].getValue(), 2.0f) * 10.f); + e.envelope.setSustain(e.sustainSL.next(sustain)); + e.envelope.setRelease(powf(params[RELEASE_PARAM].getValue(), 2.0f) * 10.f); } - _feedback = params[FEEDBACK_PARAM].getValue(); + e.feedback = params[FEEDBACK_PARAM].getValue(); if (inputs[FEEDBACK_INPUT].isConnected()) { - _feedback *= clamp(inputs[FEEDBACK_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + e.feedback *= clamp(inputs[FEEDBACK_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); } - _depth = params[DEPTH_PARAM].getValue(); + e.depth = params[DEPTH_PARAM].getValue(); if (inputs[DEPTH_INPUT].isConnected()) { - _depth *= clamp(inputs[DEPTH_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + e.depth *= clamp(inputs[DEPTH_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); } - _level = params[LEVEL_PARAM].getValue(); + e.level = params[LEVEL_PARAM].getValue(); if (inputs[LEVEL_INPUT].isConnected()) { - _level *= clamp(inputs[LEVEL_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + e.level *= clamp(inputs[LEVEL_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); } } @@ -138,66 +166,68 @@ void FMOp::always(const ProcessArgs& args) { lights[ENV_TO_DEPTH_LIGHT].value = params[ENV_TO_DEPTH_PARAM].getValue() > 0.5f; } -void FMOp::processChannel(const ProcessArgs& args, int _c) { +void FMOp::processChannel(const ProcessArgs& args, int c) { + Engine& e = *_engines[c]; + float envelope = 0.0f; - if (_envelopeOn) { + if (e.envelopeOn) { float gateIn = 0.0f; if (inputs[GATE_INPUT].isConnected()) { - gateIn = inputs[GATE_INPUT].getVoltage(); + gateIn = inputs[GATE_INPUT].getPolyVoltage(c); } - _gateTrigger.process(gateIn); - _envelope.setGate(_gateTrigger.isHigh()); - envelope = _envelope.next(); + e.gateTrigger.process(gateIn); + e.envelope.setGate(e.gateTrigger.isHigh()); + envelope = e.envelope.next(); } - float feedback = _feedbackSL.next(_feedback); + float feedback = e.feedbackSL.next(e.feedback); if (_feedbackEnvelopeOn) { feedback *= envelope; } bool feedbackOn = feedback > 0.001f; - float out = _levelSL.next(_level); + float out = e.levelSL.next(e.level); if (_levelEnvelopeOn) { out *= envelope; } float offset = 0.0f; if (feedbackOn) { - offset = feedback * _feedbackDelayedSample; + offset = feedback * e.feedbackDelayedSample; } if (inputs[FM_INPUT].isConnected()) { - float depth = _depthSL.next(_depth); + float depth = e.depthSL.next(e.depth); if (_depthEnvelopeOn) { depth *= envelope; } - offset += inputs[FM_INPUT].getVoltageSum() * depth * 2.0f; + offset += inputs[FM_INPUT].getPolyVoltage(c) * depth * 2.0f; } float sample = 0.0f; if (out > 0.0001f) { Phasor::phase_delta_t o = Phasor::radiansToPhase(offset); if (feedbackOn) { - if (_oversampleMix < 1.0f) { - _oversampleMix += oversampleMixIncrement; + if (e.oversampleMix < 1.0f) { + e.oversampleMix += oversampleMixIncrement; } } - else if (_oversampleMix > 0.0f) { - _oversampleMix -= oversampleMixIncrement; + else if (e.oversampleMix > 0.0f) { + e.oversampleMix -= oversampleMixIncrement; } - if (_oversampleMix > 0.0f) { + if (e.oversampleMix > 0.0f) { for (int i = 0; i < oversample; ++i) { - _phasor.advancePhase(); - _buffer[i] = _sineTable.nextFromPhasor(_phasor, o); + e.phasor.advancePhase(); + e.buffer[i] = e.sineTable.nextFromPhasor(e.phasor, o); } - sample = _oversampleMix * _decimator.next(_buffer); + sample = e.oversampleMix * e.decimator.next(e.buffer); } else { - _phasor.advancePhase(oversample); + e.phasor.advancePhase(oversample); } - if (_oversampleMix < 1.0f) { - sample += (1.0f - _oversampleMix) * _sineTable.nextFromPhasor(_phasor, o); + if (e.oversampleMix < 1.0f) { + sample += (1.0f - e.oversampleMix) * e.sineTable.nextFromPhasor(e.phasor, o); } if (_linearLevel) { @@ -205,15 +235,16 @@ void FMOp::processChannel(const ProcessArgs& args, int _c) { } else { out = (1.0f - out) * Amplifier::minDecibels; - _amplifier.setLevel(out); - sample = _amplifier.next(sample); + e.amplifier.setLevel(out); + sample = e.amplifier.next(sample); } } else { - _phasor.advancePhase(oversample); + e.phasor.advancePhase(oversample); } - outputs[AUDIO_OUTPUT].setVoltage(_feedbackDelayedSample = amplitude * sample); + outputs[AUDIO_OUTPUT].setChannels(_channels); + outputs[AUDIO_OUTPUT].setVoltage(e.feedbackDelayedSample = amplitude * sample, c); } struct FMOpWidget : ModuleWidget { diff --git a/src/FMOp.hpp b/src/FMOp.hpp @@ -52,31 +52,41 @@ struct FMOp : BGModule { NUM_LIGHTS }; - const float amplitude = 5.0f; + static constexpr float amplitude = 5.0f; static constexpr int oversample = 8; - const float oversampleMixIncrement = 0.01f; - float _feedback = 0.0f; - float _feedbackDelayedSample = 0.0f; - float _depth = 0.0f; - float _level = 0.0f; - bool _envelopeOn = false; + static constexpr float oversampleMixIncrement = 0.01f; + + struct Engine { + float feedback = 0.0f; + float feedbackDelayedSample = 0.0f; + float depth = 0.0f; + float level = 0.0f; + bool envelopeOn = false; + float maxFrequency = 0.0f; + float buffer[oversample]; + float oversampleMix = 0.0f; + dsp::ADSR envelope; + Phasor phasor; + SineTableOscillator sineTable; + CICDecimator decimator; + Trigger gateTrigger; + bogaudio::dsp::SlewLimiter feedbackSL; + bogaudio::dsp::SlewLimiter depthSL; + bogaudio::dsp::SlewLimiter levelSL; + bogaudio::dsp::SlewLimiter sustainSL; + Amplifier amplifier; + + Engine() : envelope(true) {} + + void reset(); + void sampleRateChange(); + }; + + bool _linearLevel = false; bool _levelEnvelopeOn = false; bool _feedbackEnvelopeOn = false; bool _depthEnvelopeOn = false; - float _maxFrequency = 0.0f; - float _buffer[oversample]; - float _oversampleMix = 0.0f; - dsp::ADSR _envelope; - Phasor _phasor; - SineTableOscillator _sineTable; - CICDecimator _decimator; - Trigger _gateTrigger; - bogaudio::dsp::SlewLimiter _feedbackSL; - bogaudio::dsp::SlewLimiter _depthSL; - bogaudio::dsp::SlewLimiter _levelSL; - bogaudio::dsp::SlewLimiter _sustainSL; - Amplifier _amplifier; - bool _linearLevel = false; + Engine* _engines[maxChannels] {}; struct RatioParamQuantity : ParamQuantity { float getDisplayValue() override; @@ -87,9 +97,7 @@ struct FMOp : BGModule { bool isLinear() override; }; - FMOp() - : _envelope(true) - { + FMOp() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam<RatioParamQuantity>(RATIO_PARAM, -1.0f, 1.0f, 0.0f, "Frequency ratio"); configParam(FINE_PARAM, -1.0f, 1.0f, 0.0f, "Fine tune", " cents", 0.0f, 100.0f); @@ -103,9 +111,6 @@ struct FMOp : BGModule { configParam(ENV_TO_LEVEL_PARAM, 0.0f, 1.0f, 0.0f, "Level follows envelope"); configParam(ENV_TO_FEEDBACK_PARAM, 0.0f, 1.0f, 0.0f, "Feedback follows envelope"); configParam(ENV_TO_DEPTH_PARAM, 0.0f, 1.0f, 0.0f, "FM depth follows envelope"); - - reset(); - sampleRateChange(); } void reset() override; @@ -113,9 +118,13 @@ struct FMOp : BGModule { json_t* dataToJson() override; void dataFromJson(json_t* root) override; bool active() override; + int channels() override; + void addEngine(int c) override; + void removeEngine(int c) override; void modulate() override; + void modulateChannel(int c) override; void always(const ProcessArgs& args) override; - void processChannel(const ProcessArgs& args, int _c) override; + void processChannel(const ProcessArgs& args, int c) override; }; } // namespace bogaudio