XCO.cpp (17772B)
1 2 #include "XCO.hpp" 3 #include "dsp/pitch.hpp" 4 5 #define DC_CORRECTION "dc_correction" 6 #define CLIPPING_MODE "clipping_mode" 7 8 float XCO::XCOFrequencyParamQuantity::offset() { 9 auto xco = dynamic_cast<XCO*>(module); 10 return xco->_slowMode ? xco->_slowModeOffset : 0.0f; 11 } 12 13 void XCO::Engine::reset() { 14 syncTrigger.reset(); 15 } 16 17 void XCO::Engine::sampleRateChange(float sampleRate) { 18 phasor.setSampleRate(sampleRate); 19 square.setSampleRate(sampleRate); 20 saw.setSampleRate(sampleRate); 21 22 squareDecimator.setParams(sampleRate, oversample); 23 sawDecimator.setParams(sampleRate, oversample); 24 triangleDecimator.setParams(sampleRate, oversample); 25 sineDecimator.setParams(sampleRate, oversample); 26 27 fmDepthSL.setParams(sampleRate, 5.0f, 1.0f); 28 squarePulseWidthSL.setParams(sampleRate, 0.1f, 2.0f); 29 sawSaturationSL.setParams(sampleRate, 1.0f, 1.0f); 30 triangleSampleWidthSL.setParams(sampleRate, 0.1f, 1.0f); 31 sineFeedbackSL.setParams(sampleRate, 0.1f, 1.0f); 32 squareMixSL.setParams(sampleRate, 5.0f, 1.0f); 33 sawMixSL.setParams(sampleRate, 5.0f, 1.0f); 34 triangleMixSL.setParams(sampleRate, 5.0f, 1.0f); 35 sineMixSL.setParams(sampleRate, 5.0f, 1.0f); 36 } 37 38 void XCO::Engine::setFrequency(float f) { 39 if (frequency != f && frequency < 0.475f * phasor._sampleRate) { 40 frequency = f; 41 phasor.setFrequency(frequency / (float)oversample); 42 square.setFrequency(frequency); 43 saw.setFrequency(frequency); 44 } 45 } 46 47 void XCO::reset() { 48 for (int c = 0; c < _channels; ++c) { 49 _engines[c]->reset(); 50 } 51 } 52 53 void XCO::sampleRateChange() { 54 float sampleRate = APP->engine->getSampleRate(); 55 _oversampleThreshold = 0.06f * sampleRate; 56 57 for (int c = 0; c < _channels; ++c) { 58 _engines[c]->sampleRateChange(sampleRate); 59 } 60 } 61 62 json_t* XCO::saveToJson(json_t* root) { 63 json_object_set_new(root, DC_CORRECTION, json_boolean(_dcCorrection)); 64 json_object_set_new(root, CLIPPING_MODE, json_integer(_clippingMode)); 65 return root; 66 } 67 68 void XCO::loadFromJson(json_t* root) { 69 json_t* dc = json_object_get(root, DC_CORRECTION); 70 if (dc) { 71 _dcCorrection = json_boolean_value(dc); 72 } 73 74 json_t* c = json_object_get(root, CLIPPING_MODE); 75 if (c) { 76 _clippingMode = (Clipping)json_integer_value(c); 77 if (_clippingMode != SOFT_CLIPPING && _clippingMode != HARD_CLIPPING && _clippingMode != NO_CLIPPING) { 78 _clippingMode = COMP_CLIPPING; 79 } 80 } 81 } 82 83 bool XCO::active() { 84 return ( 85 outputs[MIX_OUTPUT].isConnected() || 86 outputs[SQUARE_OUTPUT].isConnected() || 87 outputs[SAW_OUTPUT].isConnected() || 88 outputs[TRIANGLE_OUTPUT].isConnected() || 89 outputs[SINE_OUTPUT].isConnected() 90 ); 91 } 92 93 int XCO::channels() { 94 return inputs[PITCH_INPUT].getChannels(); 95 } 96 97 void XCO::addChannel(int c) { 98 _engines[c] = new Engine(); 99 _engines[c]->reset(); 100 _engines[c]->sampleRateChange(APP->engine->getSampleRate()); 101 if (c > 0) { 102 _engines[c]->phasor.syncPhase(_engines[0]->phasor); 103 } 104 } 105 106 void XCO::removeChannel(int c) { 107 delete _engines[c]; 108 _engines[c] = NULL; 109 } 110 111 void XCO::modulate() { 112 _slowMode = params[SLOW_PARAM].getValue() > 0.5f; 113 _fmLinearMode = params[FM_TYPE_PARAM].getValue() < 0.5f; 114 } 115 116 void XCO::modulateChannel(int c) { 117 Engine& e = *_engines[c]; 118 119 e.baseVOct = params[FREQUENCY_PARAM].getValue(); 120 e.baseVOct += params[FINE_PARAM].getValue() / 12.0f;; 121 if (inputs[PITCH_INPUT].isConnected()) { 122 e.baseVOct += clamp(inputs[PITCH_INPUT].getVoltage(c), -5.0f, 5.0f); 123 } 124 if (_slowMode) { 125 e.baseVOct += _slowModeOffset; 126 } 127 e.baseHz = cvToFrequency(e.baseVOct); 128 129 float pw = params[SQUARE_PW_PARAM].getValue(); 130 if (inputs[SQUARE_PW_INPUT].isConnected()) { 131 pw *= clamp(inputs[SQUARE_PW_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); 132 } 133 pw *= 1.0f - 2.0f * e.square.minPulseWidth; 134 pw *= 0.5f; 135 pw += 0.5f; 136 e.square.setPulseWidth(e.squarePulseWidthSL.next(pw), _dcCorrection); 137 138 float saturation = params[SAW_SATURATION_PARAM].getValue(); 139 if (inputs[SAW_SATURATION_INPUT].isConnected()) { 140 saturation *= clamp(inputs[SAW_SATURATION_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 141 } 142 e.saw.setSaturation(e.sawSaturationSL.next(saturation) * 10.f); 143 144 float tsw = params[TRIANGLE_SAMPLE_PARAM].getValue() * Phasor::maxSampleWidth; 145 if (inputs[TRIANGLE_SAMPLE_INPUT].isConnected()) { 146 tsw *= clamp(inputs[TRIANGLE_SAMPLE_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 147 } 148 e.triangleSampleWidth = e.triangleSampleWidthSL.next(tsw); 149 e.triangle.setSampleWidth(e.triangleSampleWidth); 150 151 float sfb = params[SINE_FEEDBACK_PARAM].getValue(); 152 if (inputs[SINE_FEEDBACK_INPUT].isConnected()) { 153 sfb *= clamp(inputs[SINE_FEEDBACK_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 154 } 155 e.sineFeedback = e.sineFeedbackSL.next(sfb); 156 157 e.fmDepth = params[FM_DEPTH_PARAM].getValue(); 158 if (inputs[FM_DEPTH_INPUT].isConnected()) { 159 e.fmDepth *= clamp(inputs[FM_DEPTH_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 160 } 161 162 e.squarePhaseOffset = phaseOffset(c, params[SQUARE_PHASE_PARAM], inputs[SQUARE_PHASE_INPUT]); 163 e.sawPhaseOffset = phaseOffset(c, params[SAW_PHASE_PARAM], inputs[SAW_PHASE_INPUT]); 164 e.trianglePhaseOffset = phaseOffset(c, params[TRIANGLE_PHASE_PARAM], inputs[TRIANGLE_PHASE_INPUT]); 165 e.sinePhaseOffset = phaseOffset(c, params[SINE_PHASE_PARAM], inputs[SINE_PHASE_INPUT]); 166 167 e.squareMix = level(c, params[SQUARE_MIX_PARAM], inputs[SQUARE_MIX_INPUT]); 168 e.sawMix = level(c, params[SAW_MIX_PARAM], inputs[SAW_MIX_INPUT]); 169 e.triangleMix = level(c, params[TRIANGLE_MIX_PARAM], inputs[TRIANGLE_MIX_INPUT]); 170 e.sineMix = level(c, params[SINE_MIX_PARAM], inputs[SINE_MIX_INPUT]); 171 } 172 173 void XCO::processChannel(const ProcessArgs& args, int c) { 174 Engine& e = *_engines[c]; 175 176 if (e.syncTrigger.next(inputs[SYNC_INPUT].getPolyVoltage(c))) { 177 e.phasor.resetPhase(); 178 } 179 180 float frequency = e.baseHz; 181 Phasor::phase_delta_t phaseOffset = 0; 182 float fmd = e.fmDepthSL.next(e.fmDepth); 183 if (inputs[FM_INPUT].isConnected() && fmd > 0.01f) { 184 float fm = inputs[FM_INPUT].getPolyVoltage(c) * fmd; 185 if (_fmLinearMode) { 186 phaseOffset = Phasor::radiansToPhase(2.0f * fm); 187 } 188 else { 189 frequency = cvToFrequency(e.baseVOct + fm); 190 } 191 } 192 e.setFrequency(frequency); 193 194 const float oversampleWidth = 100.0f; 195 float mix, oMix; 196 if (frequency > _oversampleThreshold) { 197 if (frequency > _oversampleThreshold + oversampleWidth) { 198 mix = 0.0f; 199 oMix = 1.0f; 200 } 201 else { 202 oMix = (frequency - _oversampleThreshold) / oversampleWidth; 203 mix = 1.0f - oMix; 204 } 205 } 206 else { 207 mix = 1.0f; 208 oMix = 0.0f; 209 } 210 211 bool triangleSample = e.triangleSampleWidth > 0.001f; 212 bool squareActive = outputs[MIX_OUTPUT].isConnected() || outputs[SQUARE_OUTPUT].isConnected(); 213 bool sawActive = outputs[MIX_OUTPUT].isConnected() || outputs[SAW_OUTPUT].isConnected(); 214 bool triangleActive = outputs[MIX_OUTPUT].isConnected() || outputs[TRIANGLE_OUTPUT].isConnected(); 215 bool sineActive = outputs[MIX_OUTPUT].isConnected() || outputs[SINE_OUTPUT].isConnected(); 216 bool squareOversample = squareActive && oMix > 0.0f; 217 bool sawOversample = sawActive && oMix > 0.0f; 218 bool triangleOversample = triangleActive && (triangleSample || oMix > 0.0f); 219 bool squareNormal = squareActive && mix > 0.0f; 220 bool sawNormal = sawActive && mix > 0.0f; 221 bool triangleNormal = triangleActive && !triangleSample && mix > 0.0f; 222 float squareOut = 0.0f; 223 float sawOut = 0.0f; 224 float triangleOut = 0.0f; 225 float sineOut = 0.0f; 226 227 Phasor::phase_delta_t sineFeedbackOffset = 0; 228 if (sineActive) { 229 if (e.sineFeedback > 0.001f) { 230 sineFeedbackOffset = Phasor::radiansToPhase(e.sineFeedback * e.sineFeedbackDelayedSample); 231 if (e.sineOMix < 1.0f) { 232 e.sineOMix += sineOversampleMixIncrement; 233 } 234 } 235 else if (e.sineOMix > 0.0f) { 236 e.sineOMix -= sineOversampleMixIncrement; 237 } 238 } 239 240 if (squareOversample || sawOversample || triangleOversample || e.sineOMix > 0.0f) { 241 for (int i = 0; i < Engine::oversample; ++i) { 242 e.phasor.advancePhase(); 243 if (squareOversample) { 244 e.squareBuffer[i] = e.square.nextFromPhasor(e.phasor, e.squarePhaseOffset + phaseOffset); 245 } 246 if (sawOversample) { 247 e.sawBuffer[i] = e.saw.nextFromPhasor(e.phasor, e.sawPhaseOffset + phaseOffset); 248 } 249 if (triangleOversample) { 250 e.triangleBuffer[i] = e.triangle.nextFromPhasor(e.phasor, e.trianglePhaseOffset + phaseOffset); 251 } 252 if (e.sineOMix > 0.0f) { 253 e.sineBuffer[i] = e.sine.nextFromPhasor(e.phasor, sineFeedbackOffset + e.sinePhaseOffset + phaseOffset); 254 } 255 } 256 if (squareOversample) { 257 squareOut += oMix * amplitude * e.squareDecimator.next(e.squareBuffer); 258 } 259 if (sawOversample) { 260 sawOut += oMix * amplitude * e.sawDecimator.next(e.sawBuffer); 261 } 262 if (triangleOversample) { 263 triangleOut += amplitude * e.triangleDecimator.next(e.triangleBuffer); 264 if (!triangleSample) { 265 triangleOut *= oMix; 266 } 267 } 268 if (e.sineOMix > 0.0f) { 269 sineOut += amplitude * e.sineOMix * e.sineDecimator.next(e.sineBuffer); 270 } 271 } 272 else { 273 e.phasor.advancePhase(Engine::oversample); 274 } 275 276 if (squareNormal) { 277 squareOut += mix * amplitude * e.square.nextFromPhasor(e.phasor, e.squarePhaseOffset + phaseOffset); 278 } 279 if (sawNormal) { 280 sawOut += mix * amplitude * e.saw.nextFromPhasor(e.phasor, e.sawPhaseOffset + phaseOffset); 281 } 282 if (triangleNormal) { 283 triangleOut += mix * amplitude * e.triangle.nextFromPhasor(e.phasor, e.trianglePhaseOffset + phaseOffset); 284 } 285 if (e.sineOMix < 1.0f) { 286 sineOut += amplitude * (1.0f - e.sineOMix) * e.sine.nextFromPhasor(e.phasor, sineFeedbackOffset + e.sinePhaseOffset + phaseOffset); 287 } 288 289 outputs[SQUARE_OUTPUT].setChannels(_channels); 290 outputs[SQUARE_OUTPUT].setVoltage(squareOut, c); 291 outputs[SAW_OUTPUT].setChannels(_channels); 292 outputs[SAW_OUTPUT].setVoltage(sawOut, c); 293 outputs[TRIANGLE_OUTPUT].setChannels(_channels); 294 outputs[TRIANGLE_OUTPUT].setVoltage(triangleOut, c); 295 outputs[SINE_OUTPUT].setChannels(_channels); 296 outputs[SINE_OUTPUT].setVoltage(e.sineFeedbackDelayedSample = sineOut, c); 297 if (outputs[MIX_OUTPUT].isConnected()) { 298 float mix = e.squareMixSL.next(e.squareMix) * squareOut; 299 mix += e.sawMixSL.next(e.sawMix) * sawOut; 300 mix += e.triangleMixSL.next(e.triangleMix) * triangleOut; 301 mix += e.sineMixSL.next(e.sineMix) * sineOut; 302 303 switch (_clippingMode) { 304 case COMP_CLIPPING: { 305 Phasor::phase_t cycle = e.phasor._phase / Phasor::cyclePhase; 306 if (e.lastCycle != cycle) { 307 e.lastCycle = cycle; 308 e.mixScale = 1.0f / ((-e.minMix + e.maxMix) / 10.0f); 309 e.minMix = 0.0f; 310 e.maxMix = 0.0f; 311 } else if (mix < e.minMix) { 312 e.minMix = mix; 313 } else if (mix > e.maxMix) { 314 e.maxMix = mix; 315 } 316 if (e.mixScale < 1.0f) { 317 mix *= e.mixScale; 318 } 319 mix = clamp(mix, -12.0f, 12.0f); 320 break; 321 } 322 case SOFT_CLIPPING: { 323 mix = e.saturator.next(mix); 324 break; 325 } 326 case HARD_CLIPPING: { 327 mix = clamp(mix, -12.0f, 12.0f); 328 break; 329 } 330 case NO_CLIPPING:; 331 } 332 333 outputs[MIX_OUTPUT].setChannels(_channels); 334 outputs[MIX_OUTPUT].setVoltage(mix, c); 335 } 336 } 337 338 Phasor::phase_delta_t XCO::phaseOffset(int c, Param& param, Input& input) { 339 float v = param.getValue(); 340 if (input.isConnected()) { 341 v *= clamp(input.getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); 342 } 343 return -v * Phasor::cyclePhase / 2.0f; 344 } 345 346 float XCO::level(int c, Param& param, Input& input) { 347 float v = param.getValue(); 348 if (input.isConnected()) { 349 v *= clamp(input.getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 350 } 351 return v; 352 } 353 354 struct XCOWidget : BGModuleWidget { 355 static constexpr int hp = 20; 356 357 XCOWidget(XCO* module) { 358 setModule(module); 359 box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); 360 setPanel(box.size, "XCO"); 361 createScrews(); 362 363 // generated by svg_widgets.rb 364 auto frequencyParamPosition = Vec(40.0, 45.0); 365 auto fineParamPosition = Vec(47.0, 153.0); 366 auto slowParamPosition = Vec(112.0, 157.2); 367 auto fmDepthParamPosition = Vec(55.0, 194.0); 368 auto fmTypeParamPosition = Vec(101.5, 256.5); 369 auto squarePwParamPosition = Vec(147.0, 60.0); 370 auto squarePhaseParamPosition = Vec(147.0, 148.0); 371 auto squareMixParamPosition = Vec(147.0, 237.0); 372 auto sawSaturationParamPosition = Vec(187.0, 60.0); 373 auto sawPhaseParamPosition = Vec(187.0, 148.0); 374 auto sawMixParamPosition = Vec(187.0, 237.0); 375 auto triangleSampleParamPosition = Vec(227.0, 60.0); 376 auto trianglePhaseParamPosition = Vec(227.0, 148.0); 377 auto triangleMixParamPosition = Vec(227.0, 237.0); 378 auto sineFeedbackParamPosition = Vec(267.0, 60.0); 379 auto sinePhaseParamPosition = Vec(267.0, 148.0); 380 auto sineMixParamPosition = Vec(267.0, 237.0); 381 382 auto fmInputPosition = Vec(29.0, 251.0); 383 auto fmDepthInputPosition = Vec(62.0, 251.0); 384 auto squarePwInputPosition = Vec(143.0, 95.0); 385 auto squarePhaseInputPosition = Vec(143.0, 183.0); 386 auto squareMixInputPosition = Vec(143.0, 272.0); 387 auto sawSaturationInputPosition = Vec(183.0, 95.0); 388 auto sawPhaseInputPosition = Vec(183.0, 183.0); 389 auto sawMixInputPosition = Vec(183.0, 272.0); 390 auto triangleSampleInputPosition = Vec(223.0, 95.0); 391 auto trianglePhaseInputPosition = Vec(223.0, 183.0); 392 auto triangleMixInputPosition = Vec(223.0, 272.0); 393 auto sineFeedbackInputPosition = Vec(263.0, 95.0); 394 auto sinePhaseInputPosition = Vec(263.0, 183.0); 395 auto sineMixInputPosition = Vec(263.0, 272.0); 396 auto pitchInputPosition = Vec(17.0, 318.0); 397 auto syncInputPosition = Vec(50.0, 318.0); 398 399 auto squareOutputPosition = Vec(143.0, 318.0); 400 auto sawOutputPosition = Vec(183.0, 318.0); 401 auto triangleOutputPosition = Vec(223.0, 318.0); 402 auto sineOutputPosition = Vec(263.0, 318.0); 403 auto mixOutputPosition = Vec(103.0, 318.0); 404 // end generated by svg_widgets.rb 405 406 addParam(createParam<Knob68>(frequencyParamPosition, module, XCO::FREQUENCY_PARAM)); 407 addParam(createParam<Knob16>(fineParamPosition, module, XCO::FINE_PARAM)); 408 addParam(createParam<IndicatorButtonGreen9>(slowParamPosition, module, XCO::SLOW_PARAM)); 409 addParam(createParam<Knob38>(fmDepthParamPosition, module, XCO::FM_DEPTH_PARAM)); 410 addParam(createParam<SliderSwitch2State14>(fmTypeParamPosition, module, XCO::FM_TYPE_PARAM)); 411 addParam(createParam<Knob16>(squarePwParamPosition, module, XCO::SQUARE_PW_PARAM)); 412 addParam(createParam<Knob16>(squarePhaseParamPosition, module, XCO::SQUARE_PHASE_PARAM)); 413 addParam(createParam<Knob16>(squareMixParamPosition, module, XCO::SQUARE_MIX_PARAM)); 414 addParam(createParam<Knob16>(sawSaturationParamPosition, module, XCO::SAW_SATURATION_PARAM)); 415 addParam(createParam<Knob16>(sawPhaseParamPosition, module, XCO::SAW_PHASE_PARAM)); 416 addParam(createParam<Knob16>(sawMixParamPosition, module, XCO::SAW_MIX_PARAM)); 417 addParam(createParam<Knob16>(triangleSampleParamPosition, module, XCO::TRIANGLE_SAMPLE_PARAM)); 418 addParam(createParam<Knob16>(trianglePhaseParamPosition, module, XCO::TRIANGLE_PHASE_PARAM)); 419 addParam(createParam<Knob16>(triangleMixParamPosition, module, XCO::TRIANGLE_MIX_PARAM)); 420 addParam(createParam<Knob16>(sineFeedbackParamPosition, module, XCO::SINE_FEEDBACK_PARAM)); 421 addParam(createParam<Knob16>(sinePhaseParamPosition, module, XCO::SINE_PHASE_PARAM)); 422 addParam(createParam<Knob16>(sineMixParamPosition, module, XCO::SINE_MIX_PARAM)); 423 424 addInput(createInput<Port24>(fmInputPosition, module, XCO::FM_INPUT)); 425 addInput(createInput<Port24>(fmDepthInputPosition, module, XCO::FM_DEPTH_INPUT)); 426 addInput(createInput<Port24>(squarePwInputPosition, module, XCO::SQUARE_PW_INPUT)); 427 addInput(createInput<Port24>(squarePhaseInputPosition, module, XCO::SQUARE_PHASE_INPUT)); 428 addInput(createInput<Port24>(squareMixInputPosition, module, XCO::SQUARE_MIX_INPUT)); 429 addInput(createInput<Port24>(sawSaturationInputPosition, module, XCO::SAW_SATURATION_INPUT)); 430 addInput(createInput<Port24>(sawPhaseInputPosition, module, XCO::SAW_PHASE_INPUT)); 431 addInput(createInput<Port24>(sawMixInputPosition, module, XCO::SAW_MIX_INPUT)); 432 addInput(createInput<Port24>(triangleSampleInputPosition, module, XCO::TRIANGLE_SAMPLE_INPUT)); 433 addInput(createInput<Port24>(trianglePhaseInputPosition, module, XCO::TRIANGLE_PHASE_INPUT)); 434 addInput(createInput<Port24>(triangleMixInputPosition, module, XCO::TRIANGLE_MIX_INPUT)); 435 addInput(createInput<Port24>(sineFeedbackInputPosition, module, XCO::SINE_FEEDBACK_INPUT)); 436 addInput(createInput<Port24>(sinePhaseInputPosition, module, XCO::SINE_PHASE_INPUT)); 437 addInput(createInput<Port24>(sineMixInputPosition, module, XCO::SINE_MIX_INPUT)); 438 addInput(createInput<Port24>(pitchInputPosition, module, XCO::PITCH_INPUT)); 439 addInput(createInput<Port24>(syncInputPosition, module, XCO::SYNC_INPUT)); 440 441 addOutput(createOutput<Port24>(squareOutputPosition, module, XCO::SQUARE_OUTPUT)); 442 addOutput(createOutput<Port24>(sawOutputPosition, module, XCO::SAW_OUTPUT)); 443 addOutput(createOutput<Port24>(triangleOutputPosition, module, XCO::TRIANGLE_OUTPUT)); 444 addOutput(createOutput<Port24>(sineOutputPosition, module, XCO::SINE_OUTPUT)); 445 addOutput(createOutput<Port24>(mixOutputPosition, module, XCO::MIX_OUTPUT)); 446 } 447 448 void contextMenu(Menu* menu) override { 449 auto m = dynamic_cast<XCO*>(module); 450 assert(m); 451 452 menu->addChild(new BoolOptionMenuItem("DC offset correction", [m]() { return &m->_dcCorrection; })); 453 454 OptionsMenuItem* c = new OptionsMenuItem("Mix output processing"); 455 c->addItem(OptionMenuItem("Scaled to +/-5V", [m]() { return m->_clippingMode == XCO::COMP_CLIPPING; }, [m]() { m->_clippingMode = XCO::COMP_CLIPPING; })); 456 c->addItem(OptionMenuItem("Saturated", [m]() { return m->_clippingMode == XCO::SOFT_CLIPPING; }, [m]() { m->_clippingMode = XCO::SOFT_CLIPPING; })); 457 c->addItem(OptionMenuItem("Hard clipped", [m]() { return m->_clippingMode == XCO::HARD_CLIPPING; }, [m]() { m->_clippingMode = XCO::HARD_CLIPPING; })); 458 c->addItem(OptionMenuItem("None", [m]() { return m->_clippingMode == XCO::NO_CLIPPING; }, [m]() { m->_clippingMode = XCO::NO_CLIPPING; })); 459 OptionsMenuItem::addToMenu(c, menu); 460 } 461 }; 462 463 Model* modelXCO = bogaudio::createModel<XCO, XCOWidget>("Bogaudio-XCO", "XCO", "Oscillator", "Oscillator", "Polyphonic");