FMOp.cpp (12448B)
1 2 #include "FMOp.hpp" 3 #include "dsp/pitch.hpp" 4 5 #define INTERPOLATION "interpolation" 6 #define INTERPOLATION_VALUE_ON "on" 7 #define INTERPOLATION_VALUE_OFF "off" 8 #define LINEAR_LEVEL "linearLevel" 9 #define ANTIALIAS_FEEDBACK "antialias_feedback" 10 #define ANTIALIAS_DEPTH "antialias_depth" 11 12 float FMOp::RatioParamQuantity::getDisplayValue() { 13 float v = getValue(); 14 if (!module) { 15 return v; 16 } 17 18 if (v < 0.0f) { 19 return std::max(1.0f + v, 0.01f); 20 } 21 v *= 9.0f; 22 v += 1.0f; 23 return v; 24 } 25 26 void FMOp::RatioParamQuantity::setDisplayValue(float v) { 27 if (!module) { 28 return; 29 } 30 31 if (v < 1.0f) { 32 v = v - 1.0f; 33 } 34 else { 35 v -= 1.0f; 36 v /= 9.0f; 37 } 38 setValue(v); 39 } 40 41 bool FMOp::LevelParamQuantity::isLinear() { 42 return dynamic_cast<FMOp*>(module)->_linearLevel; 43 } 44 45 void FMOp::Engine::reset() { 46 envelope.reset(); 47 gateTrigger.reset(); 48 } 49 50 void FMOp::Engine::sampleRateChange() { 51 float sampleRate = APP->engine->getSampleRate(); 52 envelope.setSampleRate(sampleRate); 53 phasor.setSampleRate(sampleRate); 54 decimator.setParams(sampleRate, oversample); 55 maxFrequency = 0.475f * sampleRate; 56 feedbackSL.setParams(sampleRate, 5.0f, 1.0f); 57 depthSL.setParams(sampleRate, 5.0f, 1.0f); 58 levelSL.setParams(sampleRate, 10.0f, 1.0f); 59 sustainSL.setParams(sampleRate, 1.0f, 1.0f); 60 } 61 62 json_t* FMOp::saveToJson(json_t* root) { 63 json_object_set_new(root, INTERPOLATION, json_string(_interpolation == SineTableOscillator::INTERPOLATION_ON ? INTERPOLATION_VALUE_ON : INTERPOLATION_VALUE_OFF)); 64 json_object_set_new(root, LINEAR_LEVEL, json_boolean(_linearLevel)); 65 json_object_set_new(root, ANTIALIAS_FEEDBACK, json_boolean(_antiAliasFeedback)); 66 json_object_set_new(root, ANTIALIAS_DEPTH, json_boolean(_antiAliasDepth)); 67 return root; 68 } 69 70 void FMOp::loadFromJson(json_t* root) { 71 json_t* i = json_object_get(root, INTERPOLATION); 72 if (i) { 73 const char *s = json_string_value(i); 74 if (strcmp(s, INTERPOLATION_VALUE_ON) == 0) { 75 _interpolation = SineTableOscillator::INTERPOLATION_ON; 76 } 77 } 78 79 json_t* ll = json_object_get(root, LINEAR_LEVEL); 80 if (ll) { 81 _linearLevel = json_is_true(ll); 82 } 83 84 json_t* aaf = json_object_get(root, ANTIALIAS_FEEDBACK); 85 if (aaf) { 86 _antiAliasFeedback = json_is_true(aaf); 87 } 88 89 json_t* aad = json_object_get(root, ANTIALIAS_DEPTH); 90 if (aad) { 91 _antiAliasDepth = json_is_true(aad); 92 } 93 } 94 95 void FMOp::reset() { 96 for (int c = 0; c < _channels; ++c) { 97 _engines[c]->reset(); 98 } 99 } 100 101 void FMOp::sampleRateChange() { 102 for (int c = 0; c < _channels; ++c) { 103 _engines[c]->sampleRateChange(); 104 } 105 } 106 107 bool FMOp::active() { 108 return outputs[AUDIO_OUTPUT].isConnected(); 109 } 110 111 int FMOp::channels() { 112 return inputs[PITCH_INPUT].getChannels(); 113 } 114 115 void FMOp::addChannel(int c) { 116 _engines[c] = new Engine(); 117 _engines[c]->reset(); 118 _engines[c]->sampleRateChange(); 119 if (c > 0) { 120 _engines[c]->phasor.syncPhase(_engines[0]->phasor); 121 } 122 } 123 124 void FMOp::removeChannel(int c) { 125 delete _engines[c]; 126 _engines[c] = NULL; 127 } 128 129 void FMOp::modulate() { 130 _levelEnvelopeOn = params[ENV_TO_LEVEL_PARAM].getValue() > 0.5f; 131 _feedbackEnvelopeOn = params[ENV_TO_FEEDBACK_PARAM].getValue() > 0.5f; 132 _depthEnvelopeOn = params[ENV_TO_DEPTH_PARAM].getValue() > 0.5f; 133 } 134 135 void FMOp::modulateChannel(int c) { 136 Engine& e = *_engines[c]; 137 138 float pitchIn = 0.0f; 139 if (inputs[PITCH_INPUT].isConnected()) { 140 pitchIn = inputs[PITCH_INPUT].getVoltage(c); 141 } 142 float ratio = params[RATIO_PARAM].getValue(); 143 if (ratio < 0.0f) { 144 ratio = std::max(1.0f + ratio, 0.01f); 145 } 146 else { 147 ratio *= 9.0f; 148 ratio += 1.0f; 149 } 150 151 float frequency = pitchIn; 152 frequency += params[FINE_PARAM].getValue() / 12.0f; 153 frequency = cvToFrequency(frequency); 154 frequency *= ratio; 155 frequency = clamp(frequency, -e.maxFrequency, e.maxFrequency); 156 e.phasor.setFrequency(frequency / (float)oversample); 157 158 bool envelopeOn = _levelEnvelopeOn || _feedbackEnvelopeOn || _depthEnvelopeOn; 159 if (envelopeOn && !e.envelopeOn) { 160 e.envelope.reset(); 161 } 162 e.envelopeOn = envelopeOn; 163 164 if (e.envelopeOn) { 165 float sustain = params[SUSTAIN_PARAM].getValue(); 166 if (inputs[SUSTAIN_INPUT].isConnected()) { 167 sustain *= clamp(inputs[SUSTAIN_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 168 } 169 e.envelope.setAttack(powf(params[ATTACK_PARAM].getValue(), 2.0f) * 10.f); 170 e.envelope.setDecay(powf(params[DECAY_PARAM].getValue(), 2.0f) * 10.f); 171 e.envelope.setSustain(e.sustainSL.next(sustain)); 172 e.envelope.setRelease(powf(params[RELEASE_PARAM].getValue(), 2.0f) * 10.f); 173 } 174 175 e.feedback = params[FEEDBACK_PARAM].getValue(); 176 if (inputs[FEEDBACK_INPUT].isConnected()) { 177 e.feedback *= clamp(inputs[FEEDBACK_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 178 } 179 180 e.depth = params[DEPTH_PARAM].getValue(); 181 if (inputs[DEPTH_INPUT].isConnected()) { 182 e.depth *= clamp(inputs[DEPTH_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 183 } 184 185 e.level = params[LEVEL_PARAM].getValue(); 186 if (inputs[LEVEL_INPUT].isConnected()) { 187 e.level *= clamp(inputs[LEVEL_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 188 } 189 190 e.sineTable.setInterpolation(_interpolation); 191 } 192 193 void FMOp::processAlways(const ProcessArgs& args) { 194 _attackLightSum = _decayLightSum = _sustainLightSum = _releaseLightSum = 0; 195 } 196 197 void FMOp::processChannel(const ProcessArgs& args, int c) { 198 Engine& e = *_engines[c]; 199 200 float envelope = 0.0f; 201 if (e.envelopeOn) { 202 float gateIn = 0.0f; 203 if (inputs[GATE_INPUT].isConnected()) { 204 gateIn = inputs[GATE_INPUT].getPolyVoltage(c); 205 } 206 e.gateTrigger.process(gateIn); 207 e.envelope.setGate(e.gateTrigger.isHigh()); 208 envelope = e.envelope.next(); 209 } 210 211 float feedback = e.feedbackSL.next(e.feedback); 212 if (_feedbackEnvelopeOn) { 213 feedback *= envelope; 214 } 215 bool feedbackOn = feedback > 0.001f; 216 217 float out = e.levelSL.next(e.level); 218 if (_levelEnvelopeOn) { 219 out *= envelope; 220 } 221 222 float offset = 0.0f; 223 if (feedbackOn) { 224 offset = feedback * e.feedbackDelayedSample; 225 } 226 227 bool depthOn = false; 228 if (inputs[FM_INPUT].isConnected()) { 229 float depth = e.depthSL.next(e.depth); 230 if (_depthEnvelopeOn) { 231 depth *= envelope; 232 } 233 offset += inputs[FM_INPUT].getPolyVoltage(c) * depth * 2.0f; 234 depthOn = depth > 0.001f; 235 } 236 237 float sample = 0.0f; 238 if (out > 0.0001f) { 239 Phasor::phase_delta_t o = Phasor::radiansToPhase(offset); 240 if ((feedbackOn && _antiAliasFeedback) || (depthOn && _antiAliasDepth)) { 241 if (e.oversampleMix < 1.0f) { 242 e.oversampleMix += oversampleMixIncrement; 243 } 244 } 245 else if (e.oversampleMix > 0.0f) { 246 e.oversampleMix -= oversampleMixIncrement; 247 } 248 249 if (e.oversampleMix > 0.0f) { 250 for (int i = 0; i < oversample; ++i) { 251 e.phasor.advancePhase(); 252 e.buffer[i] = e.sineTable.nextFromPhasor(e.phasor, o); 253 } 254 sample = e.oversampleMix * e.decimator.next(e.buffer); 255 } 256 else { 257 e.phasor.advancePhase(oversample); 258 } 259 if (e.oversampleMix < 1.0f) { 260 sample += (1.0f - e.oversampleMix) * e.sineTable.nextFromPhasor(e.phasor, o); 261 } 262 263 if (_linearLevel) { 264 sample *= out; 265 } 266 else { 267 out = (1.0f - out) * Amplifier::minDecibels; 268 e.amplifier.setLevel(out); 269 sample = e.amplifier.next(sample); 270 } 271 } 272 else { 273 e.phasor.advancePhase(oversample); 274 } 275 276 outputs[AUDIO_OUTPUT].setChannels(_channels); 277 outputs[AUDIO_OUTPUT].setVoltage(e.feedbackDelayedSample = amplitude * sample, c); 278 279 _attackLightSum += e.envelope.isStage(dsp::ADSR::ATTACK_STAGE); 280 _decayLightSum += e.envelope.isStage(dsp::ADSR::DECAY_STAGE); 281 _sustainLightSum += e.envelope.isStage(dsp::ADSR::SUSTAIN_STAGE); 282 _releaseLightSum += e.envelope.isStage(dsp::ADSR::RELEASE_STAGE); 283 } 284 285 void FMOp::postProcessAlways(const ProcessArgs& args) { 286 lights[ATTACK_LIGHT].value = _attackLightSum * _inverseChannels; 287 lights[DECAY_LIGHT].value = _decayLightSum * _inverseChannels; 288 lights[SUSTAIN_LIGHT].value = _sustainLightSum * _inverseChannels; 289 lights[RELEASE_LIGHT].value = _releaseLightSum * _inverseChannels; 290 } 291 292 struct FMOpWidget : BGModuleWidget { 293 static constexpr int hp = 10; 294 295 FMOpWidget(FMOp* module) { 296 setModule(module); 297 box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); 298 setPanel(box.size, "FMOp"); 299 createScrews(); 300 301 // generated by svg_widgets.rb 302 auto ratioParamPosition = Vec(30.0, 45.0); 303 auto fineParamPosition = Vec(112.0, 57.0); 304 auto attackParamPosition = Vec(107.0, 94.0); 305 auto decayParamPosition = Vec(107.0, 139.0); 306 auto sustainParamPosition = Vec(107.0, 184.0); 307 auto releaseParamPosition = Vec(107.0, 229.0); 308 auto depthParamPosition = Vec(36.0, 106.0); 309 auto envToDepthParamPosition = Vec(54.5, 139.7); 310 auto feedbackParamPosition = Vec(36.0, 162.0); 311 auto envToFeedbackParamPosition = Vec(54.5, 195.7); 312 auto levelParamPosition = Vec(36.0, 218.0); 313 auto envToLevelParamPosition = Vec(54.5, 251.7); 314 315 auto depthInputPosition = Vec(15.0, 274.0); 316 auto feedbackInputPosition = Vec(47.0, 274.0); 317 auto levelInputPosition = Vec(79.0, 274.0); 318 auto sustainInputPosition = Vec(111.0, 274.0); 319 auto pitchInputPosition = Vec(15.0, 318.0); 320 auto fmInputPosition = Vec(47.0, 318.0); 321 auto gateInputPosition = Vec(79.0, 318.0); 322 323 auto audioOutputPosition = Vec(111.0, 318.0); 324 325 auto attackLightPosition = Vec(118.5, 123.0); 326 auto decayLightPosition = Vec(118.5, 168.0); 327 auto sustainLightPosition = Vec(118.5, 213.0); 328 auto releaseLightPosition = Vec(118.5, 258.0); 329 // end generated by svg_widgets.rb 330 331 addParam(createParam<Knob38>(ratioParamPosition, module, FMOp::RATIO_PARAM)); 332 addParam(createParam<Knob16>(fineParamPosition, module, FMOp::FINE_PARAM)); 333 addParam(createParam<Knob26>(attackParamPosition, module, FMOp::ATTACK_PARAM)); 334 addParam(createParam<Knob26>(decayParamPosition, module, FMOp::DECAY_PARAM)); 335 addParam(createParam<Knob26>(sustainParamPosition, module, FMOp::SUSTAIN_PARAM)); 336 addParam(createParam<Knob26>(releaseParamPosition, module, FMOp::RELEASE_PARAM)); 337 addParam(createParam<Knob26>(depthParamPosition, module, FMOp::DEPTH_PARAM)); 338 addParam(createParam<Knob26>(feedbackParamPosition, module, FMOp::FEEDBACK_PARAM)); 339 addParam(createParam<Knob26>(levelParamPosition, module, FMOp::LEVEL_PARAM)); 340 addParam(createParam<IndicatorButtonGreen9>(envToLevelParamPosition, module, FMOp::ENV_TO_LEVEL_PARAM)); 341 addParam(createParam<IndicatorButtonGreen9>(envToFeedbackParamPosition, module, FMOp::ENV_TO_FEEDBACK_PARAM)); 342 addParam(createParam<IndicatorButtonGreen9>(envToDepthParamPosition, module, FMOp::ENV_TO_DEPTH_PARAM)); 343 344 addInput(createInput<Port24>(sustainInputPosition, module, FMOp::SUSTAIN_INPUT)); 345 addInput(createInput<Port24>(depthInputPosition, module, FMOp::DEPTH_INPUT)); 346 addInput(createInput<Port24>(feedbackInputPosition, module, FMOp::FEEDBACK_INPUT)); 347 addInput(createInput<Port24>(levelInputPosition, module, FMOp::LEVEL_INPUT)); 348 addInput(createInput<Port24>(pitchInputPosition, module, FMOp::PITCH_INPUT)); 349 addInput(createInput<Port24>(gateInputPosition, module, FMOp::GATE_INPUT)); 350 addInput(createInput<Port24>(fmInputPosition, module, FMOp::FM_INPUT)); 351 352 addOutput(createOutput<Port24>(audioOutputPosition, module, FMOp::AUDIO_OUTPUT)); 353 354 addChild(createLight<BGTinyLight<GreenLight>>(attackLightPosition, module, FMOp::ATTACK_LIGHT)); 355 addChild(createLight<BGTinyLight<GreenLight>>(decayLightPosition, module, FMOp::DECAY_LIGHT)); 356 addChild(createLight<BGTinyLight<GreenLight>>(sustainLightPosition, module, FMOp::SUSTAIN_LIGHT)); 357 addChild(createLight<BGTinyLight<GreenLight>>(releaseLightPosition, module, FMOp::RELEASE_LIGHT)); 358 } 359 360 void contextMenu(Menu* menu) override { 361 auto fmop = dynamic_cast<FMOp*>(module); 362 assert(fmop); 363 364 OptionsMenuItem* om = new OptionsMenuItem("Oscillator mode"); 365 om->addItem(OptionMenuItem("Classic (extra harmonics)", [fmop]() { return fmop->_interpolation == SineTableOscillator::INTERPOLATION_OFF; }, [fmop]() { fmop->_interpolation = SineTableOscillator::INTERPOLATION_OFF; })); 366 om->addItem(OptionMenuItem("Clean (pure sine)", [fmop]() { return fmop->_interpolation == SineTableOscillator::INTERPOLATION_ON; }, [fmop]() { fmop->_interpolation = SineTableOscillator::INTERPOLATION_ON; })); 367 OptionsMenuItem::addToMenu(om, menu); 368 369 menu->addChild(new BoolOptionMenuItem("Linear level response", [fmop]() { return &fmop->_linearLevel; })); 370 371 menu->addChild(new BoolOptionMenuItem("Anti-alias feedback", [fmop]() { return &fmop->_antiAliasFeedback; })); 372 menu->addChild(new BoolOptionMenuItem("Anti-alias external FM", [fmop]() { return &fmop->_antiAliasDepth; })); 373 } 374 }; 375 376 Model* modelFMOp = bogaudio::createModel<FMOp, FMOpWidget>("Bogaudio-FMOp", "FM-OP", "FM operator / oscillator / synth voice", "Oscillator", "Synth voice", "Polyphonic");