| @@ -28,24 +28,18 @@ struct ADSR : Module { | |||||
| SchmittTrigger trigger; | SchmittTrigger trigger; | ||||
| float lights[4] = {}; | float lights[4] = {}; | ||||
| ADSR(); | |||||
| ADSR() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||||
| trigger.setThresholds(0.0, 1.0); | |||||
| } | |||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| ADSR::ADSR() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| trigger.setThresholds(0.0, 1.0); | |||||
| } | |||||
| void ADSR::step() { | void ADSR::step() { | ||||
| float attack = clampf(params[ATTACK_INPUT] + getf(inputs[ATTACK_INPUT]) / 10.0, 0.0, 1.0); | |||||
| float decay = clampf(params[DECAY_PARAM] + getf(inputs[DECAY_INPUT]) / 10.0, 0.0, 1.0); | |||||
| float sustain = clampf(params[SUSTAIN_PARAM] + getf(inputs[SUSTAIN_INPUT]) / 10.0, 0.0, 1.0); | |||||
| float release = clampf(params[RELEASE_PARAM] + getf(inputs[RELEASE_PARAM]) / 10.0, 0.0, 1.0); | |||||
| float attack = clampf(params[ATTACK_INPUT].value + inputs[ATTACK_INPUT].value / 10.0, 0.0, 1.0); | |||||
| float decay = clampf(params[DECAY_PARAM].value + inputs[DECAY_INPUT].value / 10.0, 0.0, 1.0); | |||||
| float sustain = clampf(params[SUSTAIN_PARAM].value + inputs[SUSTAIN_INPUT].value / 10.0, 0.0, 1.0); | |||||
| float release = clampf(params[RELEASE_PARAM].value + inputs[RELEASE_PARAM].value / 10.0, 0.0, 1.0); | |||||
| // Lights | // Lights | ||||
| lights[0] = 2.0*attack - 1.0; | lights[0] = 2.0*attack - 1.0; | ||||
| @@ -54,8 +48,8 @@ void ADSR::step() { | |||||
| lights[3] = 2.0*release - 1.0; | lights[3] = 2.0*release - 1.0; | ||||
| // Gate and trigger | // Gate and trigger | ||||
| bool gated = getf(inputs[GATE_INPUT]) >= 1.0; | |||||
| if (trigger.process(getf(inputs[TRIG_INPUT]))) | |||||
| bool gated = inputs[GATE_INPUT].value >= 1.0; | |||||
| if (trigger.process(inputs[TRIG_INPUT].value)) | |||||
| decaying = false; | decaying = false; | ||||
| const float base = 20000.0; | const float base = 20000.0; | ||||
| @@ -63,7 +57,12 @@ void ADSR::step() { | |||||
| if (gated) { | if (gated) { | ||||
| if (decaying) { | if (decaying) { | ||||
| // Decay | // Decay | ||||
| env += powf(base, 1 - decay) / maxTime * (sustain - env) / gSampleRate; | |||||
| if (decay < 1e-4) { | |||||
| env = sustain; | |||||
| } | |||||
| else { | |||||
| env += powf(base, 1 - decay) / maxTime * (sustain - env) / gSampleRate; | |||||
| } | |||||
| } | } | ||||
| else { | else { | ||||
| // Attack | // Attack | ||||
| @@ -82,11 +81,16 @@ void ADSR::step() { | |||||
| } | } | ||||
| else { | else { | ||||
| // Release | // Release | ||||
| env += powf(base, 1 - release) / maxTime * (0.0 - env) / gSampleRate; | |||||
| if (release < 1e-4) { | |||||
| env = 0.0; | |||||
| } | |||||
| else { | |||||
| env += powf(base, 1 - release) / maxTime * (0.0 - env) / gSampleRate; | |||||
| } | |||||
| decaying = false; | decaying = false; | ||||
| } | } | ||||
| setf(outputs[ENVELOPE_OUTPUT], 10.0 * env); | |||||
| outputs[ENVELOPE_OUTPUT].value = 10.0 * env; | |||||
| } | } | ||||
| @@ -31,26 +31,20 @@ struct Delay : Module { | |||||
| RCFilter lowpassFilter; | RCFilter lowpassFilter; | ||||
| RCFilter highpassFilter; | RCFilter highpassFilter; | ||||
| Delay(); | |||||
| Delay() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| Delay::Delay() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| void Delay::step() { | void Delay::step() { | ||||
| // Get input to delay block | // Get input to delay block | ||||
| float in = getf(inputs[IN_INPUT]); | |||||
| float feedback = clampf(params[FEEDBACK_PARAM] + getf(inputs[FEEDBACK_INPUT]) / 10.0, 0.0, 0.99); | |||||
| float in = inputs[IN_INPUT].value; | |||||
| float feedback = clampf(params[FEEDBACK_PARAM].value + inputs[FEEDBACK_INPUT].value / 10.0, 0.0, 0.99); | |||||
| float dry = in + lastWet * feedback; | float dry = in + lastWet * feedback; | ||||
| // Compute delay time in seconds | // Compute delay time in seconds | ||||
| float delay = 1e-3 * powf(10.0 / 1e-3, clampf(params[TIME_PARAM] + getf(inputs[TIME_INPUT]) / 10.0, 0.0, 1.0)); | |||||
| float delay = 1e-3 * powf(10.0 / 1e-3, clampf(params[TIME_PARAM].value + inputs[TIME_INPUT].value / 10.0, 0.0, 1.0)); | |||||
| // Number of delay samples | // Number of delay samples | ||||
| float index = delay * gSampleRate; | float index = delay * gSampleRate; | ||||
| @@ -93,7 +87,7 @@ void Delay::step() { | |||||
| // Apply color to delay wet output | // Apply color to delay wet output | ||||
| // TODO Make it sound better | // TODO Make it sound better | ||||
| float color = clampf(params[COLOR_PARAM] + getf(inputs[COLOR_INPUT]) / 10.0, 0.0, 1.0); | |||||
| float color = clampf(params[COLOR_PARAM].value + inputs[COLOR_INPUT].value / 10.0, 0.0, 1.0); | |||||
| float lowpassFreq = 10000.0 * powf(10.0, clampf(2.0*color, 0.0, 1.0)); | float lowpassFreq = 10000.0 * powf(10.0, clampf(2.0*color, 0.0, 1.0)); | ||||
| lowpassFilter.setCutoff(lowpassFreq / gSampleRate); | lowpassFilter.setCutoff(lowpassFreq / gSampleRate); | ||||
| lowpassFilter.process(wet); | lowpassFilter.process(wet); | ||||
| @@ -105,9 +99,9 @@ void Delay::step() { | |||||
| lastWet = wet; | lastWet = wet; | ||||
| float mix = clampf(params[MIX_PARAM] + getf(inputs[MIX_INPUT]) / 10.0, 0.0, 1.0); | |||||
| float mix = clampf(params[MIX_PARAM].value + inputs[MIX_INPUT].value / 10.0, 0.0, 1.0); | |||||
| float out = crossf(in, wet, mix); | float out = crossf(in, wet, mix); | ||||
| setf(outputs[OUT_OUTPUT], out); | |||||
| outputs[OUT_OUTPUT].value = out; | |||||
| } | } | ||||
| @@ -40,6 +40,5 @@ struct ScopeWidget : ModuleWidget { | |||||
| struct SEQ3Widget : ModuleWidget { | struct SEQ3Widget : ModuleWidget { | ||||
| SEQ3Widget(); | SEQ3Widget(); | ||||
| json_t *toJsonData(); | |||||
| void fromJsonData(json_t *root); | |||||
| Menu *createContextMenu(); | |||||
| }; | }; | ||||
| @@ -39,6 +39,13 @@ struct SEQ3 : Module { | |||||
| bool gateState[8] = {}; | bool gateState[8] = {}; | ||||
| float stepLights[8] = {}; | float stepLights[8] = {}; | ||||
| enum GateMode { | |||||
| TRIGGER, | |||||
| RETRIGGER, | |||||
| CONTINUOUS, | |||||
| }; | |||||
| GateMode gateMode = TRIGGER; | |||||
| // Lights | // Lights | ||||
| float runningLight = 0.0; | float runningLight = 0.0; | ||||
| float resetLight = 0.0; | float resetLight = 0.0; | ||||
| @@ -46,12 +53,13 @@ struct SEQ3 : Module { | |||||
| float rowLights[3] = {}; | float rowLights[3] = {}; | ||||
| float gateLights[8] = {}; | float gateLights[8] = {}; | ||||
| SEQ3(); | |||||
| SEQ3() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| json_t *toJson() { | json_t *toJson() { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| // gates | |||||
| json_t *gatesJ = json_array(); | json_t *gatesJ = json_array(); | ||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| json_t *gateJ = json_integer((int) gateState[i]); | json_t *gateJ = json_integer((int) gateState[i]); | ||||
| @@ -59,15 +67,28 @@ struct SEQ3 : Module { | |||||
| } | } | ||||
| json_object_set_new(rootJ, "gates", gatesJ); | json_object_set_new(rootJ, "gates", gatesJ); | ||||
| // gateMode | |||||
| json_t *gateModeJ = json_integer((int) gateMode); | |||||
| json_object_set_new(rootJ, "gateMode", gateModeJ); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| void fromJson(json_t *rootJ) { | void fromJson(json_t *rootJ) { | ||||
| // gates | |||||
| json_t *gatesJ = json_object_get(rootJ, "gates"); | json_t *gatesJ = json_object_get(rootJ, "gates"); | ||||
| for (int i = 0; i < 8; i++) { | |||||
| json_t *gateJ = json_array_get(gatesJ, i); | |||||
| gateState[i] = !!json_integer_value(gateJ); | |||||
| if (gatesJ) { | |||||
| for (int i = 0; i < 8; i++) { | |||||
| json_t *gateJ = json_array_get(gatesJ, i); | |||||
| if (gateJ) | |||||
| gateState[i] = !!json_integer_value(gateJ); | |||||
| } | |||||
| } | } | ||||
| // gateMode | |||||
| json_t *gateModeJ = json_object_get(rootJ, "gateMode"); | |||||
| if (gateModeJ) | |||||
| gateMode = (GateMode)json_integer_value(gateModeJ); | |||||
| } | } | ||||
| void initialize() { | void initialize() { | ||||
| @@ -84,16 +105,10 @@ struct SEQ3 : Module { | |||||
| }; | }; | ||||
| SEQ3::SEQ3() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| void SEQ3::step() { | void SEQ3::step() { | ||||
| const float lightLambda = 0.075; | const float lightLambda = 0.075; | ||||
| // Run | // Run | ||||
| if (runningTrigger.process(params[RUN_PARAM])) { | |||||
| if (runningTrigger.process(params[RUN_PARAM].value)) { | |||||
| running = !running; | running = !running; | ||||
| } | } | ||||
| runningLight = running ? 1.0 : 0.0; | runningLight = running ? 1.0 : 0.0; | ||||
| @@ -101,16 +116,16 @@ void SEQ3::step() { | |||||
| bool nextStep = false; | bool nextStep = false; | ||||
| if (running) { | if (running) { | ||||
| if (inputs[EXT_CLOCK_INPUT]) { | |||||
| if (inputs[EXT_CLOCK_INPUT].active) { | |||||
| // External clock | // External clock | ||||
| if (clockTrigger.process(*inputs[EXT_CLOCK_INPUT])) { | |||||
| if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { | |||||
| phase = 0.0; | phase = 0.0; | ||||
| nextStep = true; | nextStep = true; | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| // Internal clock | // Internal clock | ||||
| float clockTime = powf(2.0, params[CLOCK_PARAM] + getf(inputs[CLOCK_INPUT])); | |||||
| float clockTime = powf(2.0, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value); | |||||
| phase += clockTime / gSampleRate; | phase += clockTime / gSampleRate; | ||||
| if (phase >= 1.0) { | if (phase >= 1.0) { | ||||
| phase -= 1.0; | phase -= 1.0; | ||||
| @@ -120,7 +135,7 @@ void SEQ3::step() { | |||||
| } | } | ||||
| // Reset | // Reset | ||||
| if (resetTrigger.process(params[RESET_PARAM] + getf(inputs[RESET_INPUT]))) { | |||||
| if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { | |||||
| phase = 0.0; | phase = 0.0; | ||||
| index = 999; | index = 999; | ||||
| nextStep = true; | nextStep = true; | ||||
| @@ -129,7 +144,7 @@ void SEQ3::step() { | |||||
| if (nextStep) { | if (nextStep) { | ||||
| // Advance step | // Advance step | ||||
| int numSteps = clampi(roundf(params[STEPS_PARAM] + getf(inputs[STEPS_INPUT])), 1, 8); | |||||
| int numSteps = clampi(roundf(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value), 1, 8); | |||||
| index += 1; | index += 1; | ||||
| if (index >= numSteps) { | if (index >= numSteps) { | ||||
| index = 0; | index = 0; | ||||
| @@ -141,24 +156,31 @@ void SEQ3::step() { | |||||
| // Gate buttons | // Gate buttons | ||||
| for (int i = 0; i < 8; i++) { | for (int i = 0; i < 8; i++) { | ||||
| if (gateTriggers[i].process(params[GATE_PARAM + i])) { | |||||
| if (gateTriggers[i].process(params[GATE_PARAM + i].value)) { | |||||
| gateState[i] = !gateState[i]; | gateState[i] = !gateState[i]; | ||||
| } | } | ||||
| float gate = (i == index && gateState[i] >= 1.0) ? 10.0 : 0.0; | float gate = (i == index && gateState[i] >= 1.0) ? 10.0 : 0.0; | ||||
| setf(outputs[GATE_OUTPUT + i], gate); | |||||
| outputs[GATE_OUTPUT + i].value = gate; | |||||
| stepLights[i] -= stepLights[i] / lightLambda / gSampleRate; | stepLights[i] -= stepLights[i] / lightLambda / gSampleRate; | ||||
| gateLights[i] = (gateState[i] >= 1.0) ? 1.0 - stepLights[i] : stepLights[i]; | gateLights[i] = (gateState[i] >= 1.0) ? 1.0 - stepLights[i] : stepLights[i]; | ||||
| } | } | ||||
| // Rows | // Rows | ||||
| float row1 = params[ROW1_PARAM + index]; | |||||
| float row2 = params[ROW2_PARAM + index]; | |||||
| float row3 = params[ROW3_PARAM + index]; | |||||
| float gates = (gateState[index] >= 1.0) ? 10.0 : 0.0; | |||||
| setf(outputs[ROW1_OUTPUT], row1); | |||||
| setf(outputs[ROW2_OUTPUT], row2); | |||||
| setf(outputs[ROW3_OUTPUT], row3); | |||||
| setf(outputs[GATES_OUTPUT], gates); | |||||
| float row1 = params[ROW1_PARAM + index].value; | |||||
| float row2 = params[ROW2_PARAM + index].value; | |||||
| float row3 = params[ROW3_PARAM + index].value; | |||||
| bool gatesOn = gateState[index]; | |||||
| if (gateMode == TRIGGER) | |||||
| gatesOn = gatesOn && nextStep; | |||||
| else if (gateMode == RETRIGGER) | |||||
| gatesOn = gatesOn && !nextStep; | |||||
| float gates = gatesOn ? 10.0 : 0.0; | |||||
| // Outputs | |||||
| outputs[ROW1_OUTPUT].value = row1; | |||||
| outputs[ROW2_OUTPUT].value = row2; | |||||
| outputs[ROW3_OUTPUT].value = row3; | |||||
| outputs[GATES_OUTPUT].value = gates; | |||||
| gatesLight = (gateState[index] >= 1.0) ? 1.0 : 0.0; | gatesLight = (gateState[index] >= 1.0) ? 1.0 : 0.0; | ||||
| rowLights[0] = row1; | rowLights[0] = row1; | ||||
| rowLights[1] = row2; | rowLights[1] = row2; | ||||
| @@ -213,3 +235,48 @@ SEQ3Widget::SEQ3Widget() { | |||||
| addOutput(createOutput<PJ301MPort>(Vec(portX[i]-1, 308-1), module, SEQ3::GATE_OUTPUT + i)); | addOutput(createOutput<PJ301MPort>(Vec(portX[i]-1, 308-1), module, SEQ3::GATE_OUTPUT + i)); | ||||
| } | } | ||||
| } | } | ||||
| struct SEQ3GateModeItem : MenuItem { | |||||
| SEQ3 *seq3; | |||||
| SEQ3::GateMode gateMode; | |||||
| void onAction() { | |||||
| seq3->gateMode = gateMode; | |||||
| } | |||||
| void step() { | |||||
| rightText = (seq3->gateMode == gateMode) ? "âś”" : ""; | |||||
| } | |||||
| }; | |||||
| Menu *SEQ3Widget::createContextMenu() { | |||||
| Menu *menu = ModuleWidget::createContextMenu(); | |||||
| MenuLabel *spacerLabel = new MenuLabel(); | |||||
| menu->pushChild(spacerLabel); | |||||
| SEQ3 *seq3 = dynamic_cast<SEQ3*>(module); | |||||
| assert(seq3); | |||||
| MenuLabel *modeLabel = new MenuLabel(); | |||||
| modeLabel->text = "Gate Mode"; | |||||
| menu->pushChild(modeLabel); | |||||
| SEQ3GateModeItem *triggerItem = new SEQ3GateModeItem(); | |||||
| triggerItem->text = "Trigger"; | |||||
| triggerItem->seq3 = seq3; | |||||
| triggerItem->gateMode = SEQ3::TRIGGER; | |||||
| menu->pushChild(triggerItem); | |||||
| SEQ3GateModeItem *retriggerItem = new SEQ3GateModeItem(); | |||||
| retriggerItem->text = "Retrigger"; | |||||
| retriggerItem->seq3 = seq3; | |||||
| retriggerItem->gateMode = SEQ3::RETRIGGER; | |||||
| menu->pushChild(retriggerItem); | |||||
| SEQ3GateModeItem *continuousItem = new SEQ3GateModeItem(); | |||||
| continuousItem->text = "Continuous"; | |||||
| continuousItem->seq3 = seq3; | |||||
| continuousItem->gateMode = SEQ3::CONTINUOUS; | |||||
| menu->pushChild(continuousItem); | |||||
| return menu; | |||||
| } | |||||
| @@ -38,7 +38,7 @@ struct Scope : Module { | |||||
| float lights[4] = {}; | float lights[4] = {}; | ||||
| SchmittTrigger resetTrigger; | SchmittTrigger resetTrigger; | ||||
| Scope(); | |||||
| Scope() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| json_t *toJson() { | json_t *toJson() { | ||||
| @@ -65,36 +65,30 @@ struct Scope : Module { | |||||
| }; | }; | ||||
| Scope::Scope() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| void Scope::step() { | void Scope::step() { | ||||
| // Modes | // Modes | ||||
| if (sumTrigger.process(params[MODE_PARAM])) { | |||||
| if (sumTrigger.process(params[MODE_PARAM].value)) { | |||||
| sum = !sum; | sum = !sum; | ||||
| } | } | ||||
| lights[0] = sum ? 0.0 : 1.0; | lights[0] = sum ? 0.0 : 1.0; | ||||
| lights[1] = sum ? 1.0 : 0.0; | lights[1] = sum ? 1.0 : 0.0; | ||||
| if (extTrigger.process(params[EXT_PARAM])) { | |||||
| if (extTrigger.process(params[EXT_PARAM].value)) { | |||||
| ext = !ext; | ext = !ext; | ||||
| } | } | ||||
| lights[2] = ext ? 0.0 : 1.0; | lights[2] = ext ? 0.0 : 1.0; | ||||
| lights[3] = ext ? 1.0 : 0.0; | lights[3] = ext ? 1.0 : 0.0; | ||||
| // Compute time | // Compute time | ||||
| float deltaTime = powf(2.0, params[TIME_PARAM]); | |||||
| float deltaTime = powf(2.0, params[TIME_PARAM].value); | |||||
| int frameCount = (int)ceilf(deltaTime * gSampleRate); | int frameCount = (int)ceilf(deltaTime * gSampleRate); | ||||
| // Add frame to buffer | // Add frame to buffer | ||||
| if (bufferIndex < BUFFER_SIZE) { | if (bufferIndex < BUFFER_SIZE) { | ||||
| if (++frameIndex > frameCount) { | if (++frameIndex > frameCount) { | ||||
| frameIndex = 0; | frameIndex = 0; | ||||
| bufferX[bufferIndex] = getf(inputs[X_INPUT]); | |||||
| bufferY[bufferIndex] = getf(inputs[Y_INPUT]); | |||||
| bufferX[bufferIndex] = inputs[X_INPUT].value; | |||||
| bufferY[bufferIndex] = inputs[Y_INPUT].value; | |||||
| bufferIndex++; | bufferIndex++; | ||||
| } | } | ||||
| } | } | ||||
| @@ -102,7 +96,7 @@ void Scope::step() { | |||||
| // Are we waiting on the next trigger? | // Are we waiting on the next trigger? | ||||
| if (bufferIndex >= BUFFER_SIZE) { | if (bufferIndex >= BUFFER_SIZE) { | ||||
| // Trigger immediately if external but nothing plugged in | // Trigger immediately if external but nothing plugged in | ||||
| if (ext && !inputs[TRIG_INPUT]) { | |||||
| if (ext && !inputs[TRIG_INPUT].active) { | |||||
| bufferIndex = 0; frameIndex = 0; return; | bufferIndex = 0; frameIndex = 0; return; | ||||
| } | } | ||||
| @@ -113,8 +107,8 @@ void Scope::step() { | |||||
| frameIndex++; | frameIndex++; | ||||
| // Must go below 0.1V to trigger | // Must go below 0.1V to trigger | ||||
| resetTrigger.setThresholds(params[TRIG_PARAM] - 0.1, params[TRIG_PARAM]); | |||||
| float gate = ext ? getf(inputs[TRIG_INPUT]) : getf(inputs[X_INPUT]); | |||||
| resetTrigger.setThresholds(params[TRIG_PARAM].value - 0.1, params[TRIG_PARAM].value); | |||||
| float gate = ext ? inputs[TRIG_INPUT].value : inputs[X_INPUT].value; | |||||
| // Reset if triggered | // Reset if triggered | ||||
| float holdTime = 0.1; | float holdTime = 0.1; | ||||
| @@ -232,10 +226,10 @@ struct ScopeDisplay : TransparentWidget { | |||||
| } | } | ||||
| void draw(NVGcontext *vg) { | void draw(NVGcontext *vg) { | ||||
| float gainX = powf(2.0, roundf(module->params[Scope::X_SCALE_PARAM])) / 12.0; | |||||
| float gainY = powf(2.0, roundf(module->params[Scope::Y_SCALE_PARAM])) / 12.0; | |||||
| float posX = module->params[Scope::X_POS_PARAM]; | |||||
| float posY = module->params[Scope::Y_POS_PARAM]; | |||||
| float gainX = powf(2.0, roundf(module->params[Scope::X_SCALE_PARAM].value)) / 12.0; | |||||
| float gainY = powf(2.0, roundf(module->params[Scope::Y_SCALE_PARAM].value)) / 12.0; | |||||
| float posX = module->params[Scope::X_POS_PARAM].value; | |||||
| float posY = module->params[Scope::Y_POS_PARAM].value; | |||||
| // Draw waveforms | // Draw waveforms | ||||
| if (module->sum) { | if (module->sum) { | ||||
| @@ -249,18 +243,18 @@ struct ScopeDisplay : TransparentWidget { | |||||
| } | } | ||||
| else { | else { | ||||
| // Y | // Y | ||||
| if (module->inputs[Scope::Y_INPUT]) { | |||||
| if (module->inputs[Scope::Y_INPUT].active) { | |||||
| nvgStrokeColor(vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0)); | nvgStrokeColor(vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0)); | ||||
| drawWaveform(vg, module->bufferY, gainY, posY); | drawWaveform(vg, module->bufferY, gainY, posY); | ||||
| } | } | ||||
| // X | // X | ||||
| if (module->inputs[Scope::X_INPUT]) { | |||||
| if (module->inputs[Scope::X_INPUT].active) { | |||||
| nvgStrokeColor(vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0)); | nvgStrokeColor(vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0)); | ||||
| drawWaveform(vg, module->bufferX, gainX, posX); | drawWaveform(vg, module->bufferX, gainX, posX); | ||||
| } | } | ||||
| } | } | ||||
| drawTrig(vg, module->params[Scope::TRIG_PARAM], gainX, posX); | |||||
| drawTrig(vg, module->params[Scope::TRIG_PARAM].value, gainX, posX); | |||||
| // Calculate and draw stats | // Calculate and draw stats | ||||
| if (++frame >= 4) { | if (++frame >= 4) { | ||||
| @@ -22,25 +22,19 @@ struct VCA : Module { | |||||
| NUM_OUTPUTS | NUM_OUTPUTS | ||||
| }; | }; | ||||
| VCA(); | |||||
| VCA() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| VCA::VCA() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| static void stepChannel(const float *in, float level, const float *lin, const float *exp, float *out) { | |||||
| float v = getf(in) * level; | |||||
| if (lin) | |||||
| v *= clampf(*lin / 10.0, 0.0, 1.0); | |||||
| static void stepChannel(Input &in, Param &level, Input &lin, Input &exp, Output &out) { | |||||
| float v = in.value * level.value; | |||||
| if (lin.active) | |||||
| v *= clampf(lin.value / 10.0, 0.0, 1.0); | |||||
| const float expBase = 50.0; | const float expBase = 50.0; | ||||
| if (exp) | |||||
| v *= rescalef(powf(expBase, clampf(*exp / 10.0, 0.0, 1.0)), 1.0, expBase, 0.0, 1.0); | |||||
| setf(out, v); | |||||
| if (exp.active) | |||||
| v *= rescalef(powf(expBase, clampf(exp.value / 10.0, 0.0, 1.0)), 1.0, expBase, 0.0, 1.0); | |||||
| out.value = v; | |||||
| } | } | ||||
| void VCA::step() { | void VCA::step() { | ||||
| @@ -102,32 +102,26 @@ struct VCF : Module { | |||||
| LadderFilter filter; | LadderFilter filter; | ||||
| VCF(); | |||||
| VCF() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| VCF::VCF() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| void VCF::step() { | void VCF::step() { | ||||
| float input = getf(inputs[IN_INPUT]) / 5.0; | |||||
| float drive = params[DRIVE_PARAM] + getf(inputs[DRIVE_INPUT]) / 10.0; | |||||
| float input = inputs[IN_INPUT].value / 5.0; | |||||
| float drive = params[DRIVE_PARAM].value + inputs[DRIVE_INPUT].value / 10.0; | |||||
| float gain = powf(100.0, drive); | float gain = powf(100.0, drive); | ||||
| input *= gain; | input *= gain; | ||||
| // Add -60dB noise to bootstrap self-oscillation | // Add -60dB noise to bootstrap self-oscillation | ||||
| input += 1.0e-6 * (2.0*randomf() - 1.0); | input += 1.0e-6 * (2.0*randomf() - 1.0); | ||||
| // Set resonance | // Set resonance | ||||
| float res = params[RES_PARAM] + getf(inputs[RES_INPUT]) / 5.0; | |||||
| float res = params[RES_PARAM].value + inputs[RES_INPUT].value / 5.0; | |||||
| res = 5.5 * clampf(res, 0.0, 1.0); | res = 5.5 * clampf(res, 0.0, 1.0); | ||||
| filter.resonance = res; | filter.resonance = res; | ||||
| // Set cutoff frequency | // Set cutoff frequency | ||||
| float cutoffExp = params[FREQ_PARAM] + params[FREQ_CV_PARAM] * getf(inputs[FREQ_INPUT]) / 5.0; | |||||
| float cutoffExp = params[FREQ_PARAM].value + params[FREQ_CV_PARAM].value * inputs[FREQ_INPUT].value / 5.0; | |||||
| cutoffExp = clampf(cutoffExp, 0.0, 1.0); | cutoffExp = clampf(cutoffExp, 0.0, 1.0); | ||||
| const float minCutoff = 15.0; | const float minCutoff = 15.0; | ||||
| const float maxCutoff = 8400.0; | const float maxCutoff = 8400.0; | ||||
| @@ -136,9 +130,9 @@ void VCF::step() { | |||||
| // Push a sample to the state filter | // Push a sample to the state filter | ||||
| filter.process(input, 1.0/gSampleRate); | filter.process(input, 1.0/gSampleRate); | ||||
| // Extract outputs | |||||
| setf(outputs[LPF_OUTPUT], 5.0 * filter.state[3]); | |||||
| setf(outputs[HPF_OUTPUT], 5.0 * (input - filter.state[3])); | |||||
| // Set outputs | |||||
| outputs[LPF_OUTPUT].value = 5.0 * filter.state[3]; | |||||
| outputs[HPF_OUTPUT].value = 5.0 * (input - filter.state[3]); | |||||
| } | } | ||||
| @@ -27,28 +27,22 @@ struct VCMixer : Module { | |||||
| NUM_OUTPUTS | NUM_OUTPUTS | ||||
| }; | }; | ||||
| VCMixer(); | |||||
| VCMixer() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| VCMixer::VCMixer() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| void VCMixer::step() { | void VCMixer::step() { | ||||
| float ch1 = getf(inputs[CH1_INPUT]) * params[CH1_PARAM] * clampf(getf(inputs[CH1_CV_INPUT], 10.0) / 10.0, 0.0, 1.0); | |||||
| float ch2 = getf(inputs[CH2_INPUT]) * params[CH2_PARAM] * clampf(getf(inputs[CH2_CV_INPUT], 10.0) / 10.0, 0.0, 1.0); | |||||
| float ch3 = getf(inputs[CH3_INPUT]) * params[CH3_PARAM] * clampf(getf(inputs[CH3_CV_INPUT], 10.0) / 10.0, 0.0, 1.0); | |||||
| float mix = (ch1 + ch2 + ch3) * params[MIX_PARAM] * getf(inputs[MIX_CV_INPUT], 10.0) / 10.0; | |||||
| float ch1 = inputs[CH1_INPUT].value * params[CH1_PARAM].value * clampf(inputs[CH1_CV_INPUT].normalize(10.0) / 10.0, 0.0, 1.0); | |||||
| float ch2 = inputs[CH2_INPUT].value * params[CH2_PARAM].value * clampf(inputs[CH2_CV_INPUT].normalize(10.0) / 10.0, 0.0, 1.0); | |||||
| float ch3 = inputs[CH3_INPUT].value * params[CH3_PARAM].value * clampf(inputs[CH3_CV_INPUT].normalize(10.0) / 10.0, 0.0, 1.0); | |||||
| float cv = inputs[MIX_CV_INPUT].normalize(10.0); | |||||
| float mix = (ch1 + ch2 + ch3) * params[MIX_PARAM].value * cv / 10.0; | |||||
| setf(outputs[CH1_OUTPUT], ch1); | |||||
| setf(outputs[CH2_OUTPUT], ch2); | |||||
| setf(outputs[CH3_OUTPUT], ch3); | |||||
| setf(outputs[MIX_OUTPUT], mix); | |||||
| outputs[CH1_OUTPUT].value = ch1; | |||||
| outputs[CH2_OUTPUT].value = ch2; | |||||
| outputs[CH3_OUTPUT].value = ch3; | |||||
| outputs[MIX_OUTPUT].value = mix; | |||||
| } | } | ||||
| @@ -51,20 +51,15 @@ struct VCO : Module { | |||||
| float pitchSlew = 0.0; | float pitchSlew = 0.0; | ||||
| int pitchSlewIndex = 0; | int pitchSlewIndex = 0; | ||||
| VCO(); | |||||
| VCO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} | |||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| VCO::VCO() { | |||||
| params.resize(NUM_PARAMS); | |||||
| inputs.resize(NUM_INPUTS); | |||||
| outputs.resize(NUM_OUTPUTS); | |||||
| } | |||||
| void VCO::step() { | void VCO::step() { | ||||
| bool analog = params[MODE_PARAM] < 1.0; | |||||
| bool analog = params[MODE_PARAM].value < 1.0; | |||||
| // TODO Soft sync features | // TODO Soft sync features | ||||
| bool soft = params[SYNC_PARAM] < 1.0; | |||||
| bool soft = params[SYNC_PARAM].value < 1.0; | |||||
| if (analog) { | if (analog) { | ||||
| // Adjust pitch slew | // Adjust pitch slew | ||||
| @@ -76,7 +71,7 @@ void VCO::step() { | |||||
| } | } | ||||
| // Compute frequency | // Compute frequency | ||||
| float pitch = params[FREQ_PARAM]; | |||||
| float pitch = params[FREQ_PARAM].value; | |||||
| if (analog) { | if (analog) { | ||||
| // Apply pitch slew | // Apply pitch slew | ||||
| const float pitchSlewAmount = 3.0; | const float pitchSlewAmount = 3.0; | ||||
| @@ -86,16 +81,16 @@ void VCO::step() { | |||||
| // Quantize coarse knob if digital mode | // Quantize coarse knob if digital mode | ||||
| pitch = roundf(pitch); | pitch = roundf(pitch); | ||||
| } | } | ||||
| pitch += 12.0 * getf(inputs[PITCH_INPUT]); | |||||
| pitch += 3.0 * quadraticBipolar(params[FINE_PARAM]); | |||||
| if (inputs[FM_INPUT]) { | |||||
| pitch += quadraticBipolar(params[FM_PARAM]) * 12.0 * *inputs[FM_INPUT]; | |||||
| pitch += 12.0 * inputs[PITCH_INPUT].value; | |||||
| pitch += 3.0 * quadraticBipolar(params[FINE_PARAM].value); | |||||
| if (inputs[FM_INPUT].active) { | |||||
| pitch += quadraticBipolar(params[FM_PARAM].value) * 12.0 * inputs[FM_INPUT].value; | |||||
| } | } | ||||
| float freq = 261.626 * powf(2.0, pitch / 12.0); | float freq = 261.626 * powf(2.0, pitch / 12.0); | ||||
| // Pulse width | // Pulse width | ||||
| const float pwMin = 0.01; | const float pwMin = 0.01; | ||||
| float pw = clampf(params[PW_PARAM] + params[PW_CV_PARAM] * getf(inputs[PW_INPUT]) / 10.0, pwMin, 1.0 - pwMin); | |||||
| float pw = clampf(params[PW_PARAM].value + params[PW_CV_PARAM].value * inputs[PW_INPUT].value / 10.0, pwMin, 1.0 - pwMin); | |||||
| // Advance phase | // Advance phase | ||||
| float deltaPhase = clampf(freq / gSampleRate, 1e-6, 0.5); | float deltaPhase = clampf(freq / gSampleRate, 1e-6, 0.5); | ||||
| @@ -103,8 +98,8 @@ void VCO::step() { | |||||
| // Detect sync | // Detect sync | ||||
| int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE) | int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE) | ||||
| float syncCrossing = 0.0; // Offset that sync occurs [0.0, 1.0) | float syncCrossing = 0.0; // Offset that sync occurs [0.0, 1.0) | ||||
| if (inputs[SYNC_INPUT]) { | |||||
| float sync = *inputs[SYNC_INPUT] - 0.01; | |||||
| if (inputs[SYNC_INPUT].active) { | |||||
| float sync = inputs[SYNC_INPUT].value - 0.01; | |||||
| if (sync > 0.0 && lastSync <= 0.0) { | if (sync > 0.0 && lastSync <= 0.0) { | ||||
| float deltaSync = sync - lastSync; | float deltaSync = sync - lastSync; | ||||
| syncCrossing = 1.0 - sync / deltaSync; | syncCrossing = 1.0 - sync / deltaSync; | ||||
| @@ -137,26 +132,26 @@ void VCO::step() { | |||||
| } | } | ||||
| } | } | ||||
| if (outputs[SIN_OUTPUT]) { | |||||
| if (outputs[SIN_OUTPUT].active) { | |||||
| if (analog) | if (analog) | ||||
| // Quadratic approximation of sine, slightly richer harmonics | // Quadratic approximation of sine, slightly richer harmonics | ||||
| sin[i] = 1.08 * ((phase < 0.25) ? (-1.0 + (4*phase)*(4*phase)) : (phase < 0.75) ? (1.0 - (4*phase-2)*(4*phase-2)) : (-1.0 + (4*phase-4)*(4*phase-4))); | sin[i] = 1.08 * ((phase < 0.25) ? (-1.0 + (4*phase)*(4*phase)) : (phase < 0.75) ? (1.0 - (4*phase-2)*(4*phase-2)) : (-1.0 + (4*phase-4)*(4*phase-4))); | ||||
| else | else | ||||
| sin[i] = -cosf(2*M_PI * phase); | sin[i] = -cosf(2*M_PI * phase); | ||||
| } | } | ||||
| if (outputs[TRI_OUTPUT]) { | |||||
| if (outputs[TRI_OUTPUT].active) { | |||||
| if (analog) | if (analog) | ||||
| tri[i] = 1.35 * interpf(triTable, phase * 2047.0); | tri[i] = 1.35 * interpf(triTable, phase * 2047.0); | ||||
| else | else | ||||
| tri[i] = (phase < 0.5) ? (-1.0 + 4.0*phase) : (1.0 - 4.0*(phase - 0.5)); | tri[i] = (phase < 0.5) ? (-1.0 + 4.0*phase) : (1.0 - 4.0*(phase - 0.5)); | ||||
| } | } | ||||
| if (outputs[SAW_OUTPUT]) { | |||||
| if (outputs[SAW_OUTPUT].active) { | |||||
| if (analog) | if (analog) | ||||
| saw[i] = 1.5 * interpf(sawTable, phase * 2047.0); | saw[i] = 1.5 * interpf(sawTable, phase * 2047.0); | ||||
| else | else | ||||
| saw[i] = -1.0 + 2.0*phase; | saw[i] = -1.0 + 2.0*phase; | ||||
| } | } | ||||
| if (outputs[SQR_OUTPUT]) { | |||||
| if (outputs[SQR_OUTPUT].active) { | |||||
| sqr[i] = (phase < 1.0 - pw) ? -1.0 : 1.0; | sqr[i] = (phase < 1.0 - pw) ? -1.0 : 1.0; | ||||
| if (analog) { | if (analog) { | ||||
| // Simply filter here | // Simply filter here | ||||
| @@ -171,14 +166,14 @@ void VCO::step() { | |||||
| } | } | ||||
| // Set output | // Set output | ||||
| if (outputs[SIN_OUTPUT]) | |||||
| *outputs[SIN_OUTPUT] = 5.0 * sinDecimator.process(sin); | |||||
| if (outputs[TRI_OUTPUT]) | |||||
| *outputs[TRI_OUTPUT] = 5.0 * triDecimator.process(tri); | |||||
| if (outputs[SAW_OUTPUT]) | |||||
| *outputs[SAW_OUTPUT] = 5.0 * sawDecimator.process(saw); | |||||
| if (outputs[SQR_OUTPUT]) | |||||
| *outputs[SQR_OUTPUT] = 5.0 * sqrDecimator.process(sqr); | |||||
| if (outputs[SIN_OUTPUT].active) | |||||
| outputs[SIN_OUTPUT].value = 5.0 * sinDecimator.process(sin); | |||||
| if (outputs[TRI_OUTPUT].active) | |||||
| outputs[TRI_OUTPUT].value = 5.0 * triDecimator.process(tri); | |||||
| if (outputs[SAW_OUTPUT].active) | |||||
| outputs[SAW_OUTPUT].value = 5.0 * sawDecimator.process(saw); | |||||
| if (outputs[SQR_OUTPUT].active) | |||||
| outputs[SQR_OUTPUT].value = 5.0 * sqrDecimator.process(sqr); | |||||
| lights[0] = rescalef(pitch, -48.0, 48.0, -1.0, 1.0); | lights[0] = rescalef(pitch, -48.0, 48.0, -1.0, 1.0); | ||||
| } | } | ||||