| @@ -54,28 +54,26 @@ struct Blinds : Module { | |||
| configParam(Blinds::MOD3_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Blinds::MOD4_PARAM, -1.0, 1.0, 0.0); | |||
| } | |||
| void process(const ProcessArgs &args) override; | |||
| }; | |||
| void Blinds::process(const ProcessArgs &args) { | |||
| float out = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| float g = params[GAIN1_PARAM + i].getValue(); | |||
| g += params[MOD1_PARAM + i].getValue() * inputs[CV1_INPUT + i].getVoltage() / 5.0; | |||
| g = clamp(g, -2.0f, 2.0f); | |||
| lights[CV1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, g), args.sampleTime); | |||
| lights[CV1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -g), args.sampleTime); | |||
| out += g * inputs[IN1_INPUT + i].getNormalVoltage(5.0); | |||
| lights[OUT1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, out / 5.0), args.sampleTime); | |||
| lights[OUT1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -out / 5.0), args.sampleTime); | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(out); | |||
| out = 0.0; | |||
| void process(const ProcessArgs &args) { | |||
| float out = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| float g = params[GAIN1_PARAM + i].getValue(); | |||
| g += params[MOD1_PARAM + i].getValue() * inputs[CV1_INPUT + i].getVoltage() / 5.0; | |||
| g = clamp(g, -2.0f, 2.0f); | |||
| lights[CV1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, g), args.sampleTime); | |||
| lights[CV1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -g), args.sampleTime); | |||
| out += g * inputs[IN1_INPUT + i].getNormalVoltage(5.0); | |||
| lights[OUT1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, out / 5.0), args.sampleTime); | |||
| lights[OUT1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -out / 5.0), args.sampleTime); | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(out); | |||
| out = 0.0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| struct BlindsWidget : ModuleWidget { | |||
| @@ -61,8 +61,90 @@ struct Braids : Module { | |||
| settings.vco_drift = 0; | |||
| settings.signature = 0; | |||
| } | |||
| void process(const ProcessArgs &args) override; | |||
| void setShape(int shape); | |||
| void process(const ProcessArgs &args) { | |||
| // Trigger | |||
| bool trig = inputs[TRIG_INPUT].getVoltage() >= 1.0; | |||
| if (!lastTrig && trig) { | |||
| osc.Strike(); | |||
| } | |||
| lastTrig = trig; | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| float fm = params[FM_PARAM].getValue() * inputs[FM_INPUT].getVoltage(); | |||
| // Set shape | |||
| int shape = roundf(params[SHAPE_PARAM].getValue() * braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META); | |||
| if (settings.meta_modulation) { | |||
| shape += roundf(fm / 10.0 * braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META); | |||
| } | |||
| settings.shape = clamp(shape, 0, braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META); | |||
| // Setup oscillator from settings | |||
| osc.set_shape((braids::MacroOscillatorShape) settings.shape); | |||
| // Set timbre/modulation | |||
| float timbre = params[TIMBRE_PARAM].getValue() + params[MODULATION_PARAM].getValue() * inputs[TIMBRE_INPUT].getVoltage() / 5.0; | |||
| float modulation = params[COLOR_PARAM].getValue() + inputs[COLOR_INPUT].getVoltage() / 5.0; | |||
| int16_t param1 = rescale(clamp(timbre, 0.0f, 1.0f), 0.0f, 1.0f, 0, INT16_MAX); | |||
| int16_t param2 = rescale(clamp(modulation, 0.0f, 1.0f), 0.0f, 1.0f, 0, INT16_MAX); | |||
| osc.set_parameters(param1, param2); | |||
| // Set pitch | |||
| float pitchV = inputs[PITCH_INPUT].getVoltage() + params[COARSE_PARAM].getValue() + params[FINE_PARAM].getValue() / 12.0; | |||
| if (!settings.meta_modulation) | |||
| pitchV += fm; | |||
| if (lowCpu) | |||
| pitchV += log2f(96000.f * args.sampleTime); | |||
| int32_t pitch = (pitchV * 12.0 + 60) * 128; | |||
| pitch += jitter_source.Render(settings.vco_drift); | |||
| pitch = clamp(pitch, 0, 16383); | |||
| osc.set_pitch(pitch); | |||
| // TODO: add a sync input buffer (must be sample rate converted) | |||
| uint8_t sync_buffer[24] = {}; | |||
| int16_t render_buffer[24]; | |||
| osc.Render(sync_buffer, render_buffer, 24); | |||
| // Signature waveshaping, decimation (not yet supported), and bit reduction (not yet supported) | |||
| uint16_t signature = settings.signature * settings.signature * 4095; | |||
| for (size_t i = 0; i < 24; i++) { | |||
| const int16_t bit_mask = 0xffff; | |||
| int16_t sample = render_buffer[i] & bit_mask; | |||
| int16_t warped = ws.Transform(sample); | |||
| render_buffer[i] = stmlib::Mix(sample, warped, signature); | |||
| } | |||
| if (lowCpu) { | |||
| for (int i = 0; i < 24; i++) { | |||
| dsp::Frame<1> f; | |||
| f.samples[0] = render_buffer[i] / 32768.0; | |||
| outputBuffer.push(f); | |||
| } | |||
| } | |||
| else { | |||
| // Sample rate convert | |||
| dsp::Frame<1> in[24]; | |||
| for (int i = 0; i < 24; i++) { | |||
| in[i].samples[0] = render_buffer[i] / 32768.0; | |||
| } | |||
| src.setRates(96000, args.sampleRate); | |||
| int inLen = 24; | |||
| int outLen = outputBuffer.capacity(); | |||
| src.process(in, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| } | |||
| // Output | |||
| if (!outputBuffer.empty()) { | |||
| dsp::Frame<1> f = outputBuffer.shift(); | |||
| outputs[OUT_OUTPUT].setVoltage(5.0 * f.samples[0]); | |||
| } | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -99,89 +181,6 @@ struct Braids : Module { | |||
| }; | |||
| void Braids::process(const ProcessArgs &args) { | |||
| // Trigger | |||
| bool trig = inputs[TRIG_INPUT].getVoltage() >= 1.0; | |||
| if (!lastTrig && trig) { | |||
| osc.Strike(); | |||
| } | |||
| lastTrig = trig; | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| float fm = params[FM_PARAM].getValue() * inputs[FM_INPUT].getVoltage(); | |||
| // Set shape | |||
| int shape = roundf(params[SHAPE_PARAM].getValue() * braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META); | |||
| if (settings.meta_modulation) { | |||
| shape += roundf(fm / 10.0 * braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META); | |||
| } | |||
| settings.shape = clamp(shape, 0, braids::MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META); | |||
| // Setup oscillator from settings | |||
| osc.set_shape((braids::MacroOscillatorShape) settings.shape); | |||
| // Set timbre/modulation | |||
| float timbre = params[TIMBRE_PARAM].getValue() + params[MODULATION_PARAM].getValue() * inputs[TIMBRE_INPUT].getVoltage() / 5.0; | |||
| float modulation = params[COLOR_PARAM].getValue() + inputs[COLOR_INPUT].getVoltage() / 5.0; | |||
| int16_t param1 = rescale(clamp(timbre, 0.0f, 1.0f), 0.0f, 1.0f, 0, INT16_MAX); | |||
| int16_t param2 = rescale(clamp(modulation, 0.0f, 1.0f), 0.0f, 1.0f, 0, INT16_MAX); | |||
| osc.set_parameters(param1, param2); | |||
| // Set pitch | |||
| float pitchV = inputs[PITCH_INPUT].getVoltage() + params[COARSE_PARAM].getValue() + params[FINE_PARAM].getValue() / 12.0; | |||
| if (!settings.meta_modulation) | |||
| pitchV += fm; | |||
| if (lowCpu) | |||
| pitchV += log2f(96000.f * args.sampleTime); | |||
| int32_t pitch = (pitchV * 12.0 + 60) * 128; | |||
| pitch += jitter_source.Render(settings.vco_drift); | |||
| pitch = clamp(pitch, 0, 16383); | |||
| osc.set_pitch(pitch); | |||
| // TODO: add a sync input buffer (must be sample rate converted) | |||
| uint8_t sync_buffer[24] = {}; | |||
| int16_t render_buffer[24]; | |||
| osc.Render(sync_buffer, render_buffer, 24); | |||
| // Signature waveshaping, decimation (not yet supported), and bit reduction (not yet supported) | |||
| uint16_t signature = settings.signature * settings.signature * 4095; | |||
| for (size_t i = 0; i < 24; i++) { | |||
| const int16_t bit_mask = 0xffff; | |||
| int16_t sample = render_buffer[i] & bit_mask; | |||
| int16_t warped = ws.Transform(sample); | |||
| render_buffer[i] = stmlib::Mix(sample, warped, signature); | |||
| } | |||
| if (lowCpu) { | |||
| for (int i = 0; i < 24; i++) { | |||
| dsp::Frame<1> f; | |||
| f.samples[0] = render_buffer[i] / 32768.0; | |||
| outputBuffer.push(f); | |||
| } | |||
| } | |||
| else { | |||
| // Sample rate convert | |||
| dsp::Frame<1> in[24]; | |||
| for (int i = 0; i < 24; i++) { | |||
| in[i].samples[0] = render_buffer[i] / 32768.0; | |||
| } | |||
| src.setRates(96000, args.sampleRate); | |||
| int inLen = 24; | |||
| int outLen = outputBuffer.capacity(); | |||
| src.process(in, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| } | |||
| // Output | |||
| if (!outputBuffer.empty()) { | |||
| dsp::Frame<1> f = outputBuffer.shift(); | |||
| outputs[OUT_OUTPUT].setVoltage(5.0 * f.samples[0]); | |||
| } | |||
| } | |||
| static const char *algo_values[] = { | |||
| @@ -65,7 +65,44 @@ struct Branches : Module { | |||
| } | |||
| } | |||
| void process(const ProcessArgs &args) override; | |||
| void process(const ProcessArgs &args) { | |||
| float gate = 0.0; | |||
| for (int i = 0; i < 2; i++) { | |||
| // mode button | |||
| if (modeTriggers[i].process(params[MODE1_PARAM + i].getValue())) | |||
| modes[i] = !modes[i]; | |||
| if (inputs[IN1_INPUT + i].isConnected()) | |||
| gate = inputs[IN1_INPUT + i].getVoltage(); | |||
| if (gateTriggers[i].process(gate)) { | |||
| // trigger | |||
| float r = random::uniform(); | |||
| float threshold = clamp(params[THRESHOLD1_PARAM + i].getValue() + inputs[P1_INPUT + i].getVoltage() / 10.f, 0.f, 1.f); | |||
| bool toss = (r < threshold); | |||
| if (!modes[i]) { | |||
| // direct modes | |||
| outcomes[i] = toss; | |||
| } | |||
| else { | |||
| // toggle modes | |||
| outcomes[i] = (outcomes[i] != toss); | |||
| } | |||
| if (!outcomes[i]) | |||
| lights[STATE1_POS_LIGHT + 2*i].value = 1.0; | |||
| else | |||
| lights[STATE1_NEG_LIGHT + 2*i].value = 1.0; | |||
| } | |||
| lights[STATE1_POS_LIGHT + 2*i].value *= 1.0 - args.sampleTime * 15.0; | |||
| lights[STATE1_NEG_LIGHT + 2*i].value *= 1.0 - args.sampleTime * 15.0; | |||
| lights[MODE1_LIGHT + i].value = modes[i] ? 1.0 : 0.0; | |||
| outputs[OUT1A_OUTPUT + i].setVoltage(outcomes[i] ? 0.0 : gate); | |||
| outputs[OUT1B_OUTPUT + i].setVoltage(outcomes[i] ? gate : 0.0); | |||
| } | |||
| } | |||
| void onReset() override { | |||
| for (int i = 0; i < 2; i++) { | |||
| @@ -76,46 +113,6 @@ struct Branches : Module { | |||
| }; | |||
| void Branches::process(const ProcessArgs &args) { | |||
| float gate = 0.0; | |||
| for (int i = 0; i < 2; i++) { | |||
| // mode button | |||
| if (modeTriggers[i].process(params[MODE1_PARAM + i].getValue())) | |||
| modes[i] = !modes[i]; | |||
| if (inputs[IN1_INPUT + i].isConnected()) | |||
| gate = inputs[IN1_INPUT + i].getVoltage(); | |||
| if (gateTriggers[i].process(gate)) { | |||
| // trigger | |||
| float r = random::uniform(); | |||
| float threshold = clamp(params[THRESHOLD1_PARAM + i].getValue() + inputs[P1_INPUT + i].getVoltage() / 10.f, 0.f, 1.f); | |||
| bool toss = (r < threshold); | |||
| if (!modes[i]) { | |||
| // direct modes | |||
| outcomes[i] = toss; | |||
| } | |||
| else { | |||
| // toggle modes | |||
| outcomes[i] = (outcomes[i] != toss); | |||
| } | |||
| if (!outcomes[i]) | |||
| lights[STATE1_POS_LIGHT + 2*i].value = 1.0; | |||
| else | |||
| lights[STATE1_NEG_LIGHT + 2*i].value = 1.0; | |||
| } | |||
| lights[STATE1_POS_LIGHT + 2*i].value *= 1.0 - args.sampleTime * 15.0; | |||
| lights[STATE1_NEG_LIGHT + 2*i].value *= 1.0 - args.sampleTime * 15.0; | |||
| lights[MODE1_LIGHT + i].value = modes[i] ? 1.0 : 0.0; | |||
| outputs[OUT1A_OUTPUT + i].setVoltage(outcomes[i] ? 0.0 : gate); | |||
| outputs[OUT1B_OUTPUT + i].setVoltage(outcomes[i] ? gate : 0.0); | |||
| } | |||
| } | |||
| struct BranchesWidget : ModuleWidget { | |||
| BranchesWidget(Branches *module) { | |||
| setModule(module); | |||
| @@ -65,9 +65,164 @@ struct Clouds : Module { | |||
| clouds::PlaybackMode playback; | |||
| int quality = 0; | |||
| Clouds(); | |||
| ~Clouds(); | |||
| void process(const ProcessArgs &args) override; | |||
| Clouds() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Clouds::POSITION_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::SIZE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::PITCH_PARAM, -2.0, 2.0, 0.0); | |||
| configParam(Clouds::IN_GAIN_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::DENSITY_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::TEXTURE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::BLEND_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::SPREAD_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::FEEDBACK_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::REVERB_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::FREEZE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::MODE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::LOAD_PARAM, 0.0, 1.0, 0.0); | |||
| const int memLen = 118784; | |||
| const int ccmLen = 65536 - 128; | |||
| block_mem = new uint8_t[memLen](); | |||
| block_ccm = new uint8_t[ccmLen](); | |||
| processor = new clouds::GranularProcessor(); | |||
| memset(processor, 0, sizeof(*processor)); | |||
| processor->Init(block_mem, memLen, block_ccm, ccmLen); | |||
| onReset(); | |||
| } | |||
| ~Clouds() { | |||
| delete processor; | |||
| delete[] block_mem; | |||
| delete[] block_ccm; | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| // Get input | |||
| dsp::Frame<2> inputFrame = {}; | |||
| if (!inputBuffer.full()) { | |||
| inputFrame.samples[0] = inputs[IN_L_INPUT].getVoltage() * params[IN_GAIN_PARAM].getValue() / 5.0; | |||
| inputFrame.samples[1] = inputs[IN_R_INPUT].isConnected() ? inputs[IN_R_INPUT].getVoltage() * params[IN_GAIN_PARAM].getValue() / 5.0 : inputFrame.samples[0]; | |||
| inputBuffer.push(inputFrame); | |||
| } | |||
| if (freezeTrigger.process(params[FREEZE_PARAM].getValue())) { | |||
| freeze ^= true; | |||
| } | |||
| if (blendTrigger.process(params[MODE_PARAM].getValue())) { | |||
| blendMode = (blendMode + 1) % 4; | |||
| } | |||
| // Trigger | |||
| if (inputs[TRIG_INPUT].getVoltage() >= 1.0) { | |||
| triggered = true; | |||
| } | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| clouds::ShortFrame input[32] = {}; | |||
| // Convert input buffer | |||
| { | |||
| inputSrc.setRates(args.sampleRate, 32000); | |||
| dsp::Frame<2> inputFrames[32]; | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = 32; | |||
| inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| // We might not fill all of the input buffer if there is a deficiency, but this cannot be avoided due to imprecisions between the input and output SRC. | |||
| for (int i = 0; i < outLen; i++) { | |||
| input[i].l = clamp(inputFrames[i].samples[0] * 32767.0f, -32768.0f, 32767.0f); | |||
| input[i].r = clamp(inputFrames[i].samples[1] * 32767.0f, -32768.0f, 32767.0f); | |||
| } | |||
| } | |||
| // Set up processor | |||
| processor->set_playback_mode(playback); | |||
| processor->set_quality(quality); | |||
| processor->Prepare(); | |||
| clouds::Parameters *p = processor->mutable_parameters(); | |||
| p->trigger = triggered; | |||
| p->gate = triggered; | |||
| p->freeze = freeze || (inputs[FREEZE_INPUT].getVoltage() >= 1.0); | |||
| p->position = clamp(params[POSITION_PARAM].getValue() + inputs[POSITION_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->size = clamp(params[SIZE_PARAM].getValue() + inputs[SIZE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->pitch = clamp((params[PITCH_PARAM].getValue() + inputs[PITCH_INPUT].getVoltage()) * 12.0f, -48.0f, 48.0f); | |||
| p->density = clamp(params[DENSITY_PARAM].getValue() + inputs[DENSITY_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->texture = clamp(params[TEXTURE_PARAM].getValue() + inputs[TEXTURE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->dry_wet = params[BLEND_PARAM].getValue(); | |||
| p->stereo_spread = params[SPREAD_PARAM].getValue(); | |||
| p->feedback = params[FEEDBACK_PARAM].getValue(); | |||
| // TODO | |||
| // Why doesn't dry audio get reverbed? | |||
| p->reverb = params[REVERB_PARAM].getValue(); | |||
| float blend = inputs[BLEND_INPUT].getVoltage() / 5.0f; | |||
| switch (blendMode) { | |||
| case 0: | |||
| p->dry_wet += blend; | |||
| p->dry_wet = clamp(p->dry_wet, 0.0f, 1.0f); | |||
| break; | |||
| case 1: | |||
| p->stereo_spread += blend; | |||
| p->stereo_spread = clamp(p->stereo_spread, 0.0f, 1.0f); | |||
| break; | |||
| case 2: | |||
| p->feedback += blend; | |||
| p->feedback = clamp(p->feedback, 0.0f, 1.0f); | |||
| break; | |||
| case 3: | |||
| p->reverb += blend; | |||
| p->reverb = clamp(p->reverb, 0.0f, 1.0f); | |||
| break; | |||
| } | |||
| clouds::ShortFrame output[32]; | |||
| processor->Process(input, output, 32); | |||
| // Convert output buffer | |||
| { | |||
| dsp::Frame<2> outputFrames[32]; | |||
| for (int i = 0; i < 32; i++) { | |||
| outputFrames[i].samples[0] = output[i].l / 32768.0; | |||
| outputFrames[i].samples[1] = output[i].r / 32768.0; | |||
| } | |||
| outputSrc.setRates(32000, args.sampleRate); | |||
| int inLen = 32; | |||
| int outLen = outputBuffer.capacity(); | |||
| outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| triggered = false; | |||
| } | |||
| // Set output | |||
| dsp::Frame<2> outputFrame = {}; | |||
| if (!outputBuffer.empty()) { | |||
| outputFrame = outputBuffer.shift(); | |||
| outputs[OUT_L_OUTPUT].setVoltage(5.0 * outputFrame.samples[0]); | |||
| outputs[OUT_R_OUTPUT].setVoltage(5.0 * outputFrame.samples[1]); | |||
| } | |||
| // Lights | |||
| clouds::Parameters *p = processor->mutable_parameters(); | |||
| dsp::VuMeter vuMeter; | |||
| vuMeter.dBInterval = 6.0; | |||
| dsp::Frame<2> lightFrame = p->freeze ? outputFrame : inputFrame; | |||
| vuMeter.setValue(fmaxf(fabsf(lightFrame.samples[0]), fabsf(lightFrame.samples[1]))); | |||
| lights[FREEZE_LIGHT].setBrightness(p->freeze ? 0.75 : 0.0); | |||
| lights[MIX_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(3), args.sampleTime); | |||
| lights[PAN_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(2), args.sampleTime); | |||
| lights[FEEDBACK_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(1), args.sampleTime); | |||
| lights[REVERB_GREEN_LIGHT].setBrightness(0.0); | |||
| lights[MIX_RED_LIGHT].setBrightness(0.0); | |||
| lights[PAN_RED_LIGHT].setBrightness(0.0); | |||
| lights[FEEDBACK_RED_LIGHT].setSmoothBrightness(vuMeter.getBrightness(1), args.sampleTime); | |||
| lights[REVERB_RED_LIGHT].setSmoothBrightness(vuMeter.getBrightness(0), args.sampleTime); | |||
| } | |||
| void onReset() override { | |||
| freeze = false; | |||
| @@ -105,167 +260,6 @@ struct Clouds : Module { | |||
| }; | |||
| Clouds::Clouds() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Clouds::POSITION_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::SIZE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::PITCH_PARAM, -2.0, 2.0, 0.0); | |||
| configParam(Clouds::IN_GAIN_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::DENSITY_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::TEXTURE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::BLEND_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Clouds::SPREAD_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::FEEDBACK_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::REVERB_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::FREEZE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::MODE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Clouds::LOAD_PARAM, 0.0, 1.0, 0.0); | |||
| const int memLen = 118784; | |||
| const int ccmLen = 65536 - 128; | |||
| block_mem = new uint8_t[memLen](); | |||
| block_ccm = new uint8_t[ccmLen](); | |||
| processor = new clouds::GranularProcessor(); | |||
| memset(processor, 0, sizeof(*processor)); | |||
| processor->Init(block_mem, memLen, block_ccm, ccmLen); | |||
| onReset(); | |||
| } | |||
| Clouds::~Clouds() { | |||
| delete processor; | |||
| delete[] block_mem; | |||
| delete[] block_ccm; | |||
| } | |||
| void Clouds::process(const ProcessArgs &args) { | |||
| // Get input | |||
| dsp::Frame<2> inputFrame = {}; | |||
| if (!inputBuffer.full()) { | |||
| inputFrame.samples[0] = inputs[IN_L_INPUT].getVoltage() * params[IN_GAIN_PARAM].getValue() / 5.0; | |||
| inputFrame.samples[1] = inputs[IN_R_INPUT].isConnected() ? inputs[IN_R_INPUT].getVoltage() * params[IN_GAIN_PARAM].getValue() / 5.0 : inputFrame.samples[0]; | |||
| inputBuffer.push(inputFrame); | |||
| } | |||
| if (freezeTrigger.process(params[FREEZE_PARAM].getValue())) { | |||
| freeze ^= true; | |||
| } | |||
| if (blendTrigger.process(params[MODE_PARAM].getValue())) { | |||
| blendMode = (blendMode + 1) % 4; | |||
| } | |||
| // Trigger | |||
| if (inputs[TRIG_INPUT].getVoltage() >= 1.0) { | |||
| triggered = true; | |||
| } | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| clouds::ShortFrame input[32] = {}; | |||
| // Convert input buffer | |||
| { | |||
| inputSrc.setRates(args.sampleRate, 32000); | |||
| dsp::Frame<2> inputFrames[32]; | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = 32; | |||
| inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| // We might not fill all of the input buffer if there is a deficiency, but this cannot be avoided due to imprecisions between the input and output SRC. | |||
| for (int i = 0; i < outLen; i++) { | |||
| input[i].l = clamp(inputFrames[i].samples[0] * 32767.0f, -32768.0f, 32767.0f); | |||
| input[i].r = clamp(inputFrames[i].samples[1] * 32767.0f, -32768.0f, 32767.0f); | |||
| } | |||
| } | |||
| // Set up processor | |||
| processor->set_playback_mode(playback); | |||
| processor->set_quality(quality); | |||
| processor->Prepare(); | |||
| clouds::Parameters *p = processor->mutable_parameters(); | |||
| p->trigger = triggered; | |||
| p->gate = triggered; | |||
| p->freeze = freeze || (inputs[FREEZE_INPUT].getVoltage() >= 1.0); | |||
| p->position = clamp(params[POSITION_PARAM].getValue() + inputs[POSITION_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->size = clamp(params[SIZE_PARAM].getValue() + inputs[SIZE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->pitch = clamp((params[PITCH_PARAM].getValue() + inputs[PITCH_INPUT].getVoltage()) * 12.0f, -48.0f, 48.0f); | |||
| p->density = clamp(params[DENSITY_PARAM].getValue() + inputs[DENSITY_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->texture = clamp(params[TEXTURE_PARAM].getValue() + inputs[TEXTURE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->dry_wet = params[BLEND_PARAM].getValue(); | |||
| p->stereo_spread = params[SPREAD_PARAM].getValue(); | |||
| p->feedback = params[FEEDBACK_PARAM].getValue(); | |||
| // TODO | |||
| // Why doesn't dry audio get reverbed? | |||
| p->reverb = params[REVERB_PARAM].getValue(); | |||
| float blend = inputs[BLEND_INPUT].getVoltage() / 5.0f; | |||
| switch (blendMode) { | |||
| case 0: | |||
| p->dry_wet += blend; | |||
| p->dry_wet = clamp(p->dry_wet, 0.0f, 1.0f); | |||
| break; | |||
| case 1: | |||
| p->stereo_spread += blend; | |||
| p->stereo_spread = clamp(p->stereo_spread, 0.0f, 1.0f); | |||
| break; | |||
| case 2: | |||
| p->feedback += blend; | |||
| p->feedback = clamp(p->feedback, 0.0f, 1.0f); | |||
| break; | |||
| case 3: | |||
| p->reverb += blend; | |||
| p->reverb = clamp(p->reverb, 0.0f, 1.0f); | |||
| break; | |||
| } | |||
| clouds::ShortFrame output[32]; | |||
| processor->Process(input, output, 32); | |||
| // Convert output buffer | |||
| { | |||
| dsp::Frame<2> outputFrames[32]; | |||
| for (int i = 0; i < 32; i++) { | |||
| outputFrames[i].samples[0] = output[i].l / 32768.0; | |||
| outputFrames[i].samples[1] = output[i].r / 32768.0; | |||
| } | |||
| outputSrc.setRates(32000, args.sampleRate); | |||
| int inLen = 32; | |||
| int outLen = outputBuffer.capacity(); | |||
| outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| triggered = false; | |||
| } | |||
| // Set output | |||
| dsp::Frame<2> outputFrame = {}; | |||
| if (!outputBuffer.empty()) { | |||
| outputFrame = outputBuffer.shift(); | |||
| outputs[OUT_L_OUTPUT].setVoltage(5.0 * outputFrame.samples[0]); | |||
| outputs[OUT_R_OUTPUT].setVoltage(5.0 * outputFrame.samples[1]); | |||
| } | |||
| // Lights | |||
| clouds::Parameters *p = processor->mutable_parameters(); | |||
| dsp::VuMeter vuMeter; | |||
| vuMeter.dBInterval = 6.0; | |||
| dsp::Frame<2> lightFrame = p->freeze ? outputFrame : inputFrame; | |||
| vuMeter.setValue(fmaxf(fabsf(lightFrame.samples[0]), fabsf(lightFrame.samples[1]))); | |||
| lights[FREEZE_LIGHT].setBrightness(p->freeze ? 0.75 : 0.0); | |||
| lights[MIX_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(3), args.sampleTime); | |||
| lights[PAN_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(2), args.sampleTime); | |||
| lights[FEEDBACK_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(1), args.sampleTime); | |||
| lights[REVERB_GREEN_LIGHT].setBrightness(0.0); | |||
| lights[MIX_RED_LIGHT].setBrightness(0.0); | |||
| lights[PAN_RED_LIGHT].setBrightness(0.0); | |||
| lights[FEEDBACK_RED_LIGHT].setSmoothBrightness(vuMeter.getBrightness(1), args.sampleTime); | |||
| lights[REVERB_RED_LIGHT].setSmoothBrightness(vuMeter.getBrightness(0), args.sampleTime); | |||
| } | |||
| struct FreezeLight : YellowLight { | |||
| FreezeLight() { | |||
| box.size = Vec(28-6, 28-6); | |||
| @@ -78,9 +78,139 @@ struct Elements : Module { | |||
| uint16_t reverb_buffer[32768] = {}; | |||
| elements::Part *part; | |||
| Elements(); | |||
| ~Elements(); | |||
| void process(const ProcessArgs &args) override; | |||
| Elements() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Elements::CONTOUR_PARAM, 0.0, 1.0, 1.0); | |||
| configParam(Elements::BOW_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Elements::BLOW_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Elements::STRIKE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::COARSE_PARAM, -30.0, 30.0, 0.0); | |||
| configParam(Elements::FINE_PARAM, -2.0, 2.0, 0.0); | |||
| configParam(Elements::FM_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::FLOW_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::MALLET_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::GEOMETRY_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::BRIGHTNESS_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::BOW_TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::BLOW_TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::STRIKE_TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::DAMPING_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::POSITION_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::SPACE_PARAM, 0.0, 2.0, 0.0); | |||
| configParam(Elements::BOW_TIMBRE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::FLOW_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::BLOW_TIMBRE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::MALLET_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::STRIKE_TIMBRE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::DAMPING_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::GEOMETRY_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::POSITION_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::BRIGHTNESS_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::SPACE_MOD_PARAM, -2.0, 2.0, 0.0); | |||
| configParam(Elements::PLAY_PARAM, 0.0, 1.0, 0.0); | |||
| part = new elements::Part(); | |||
| // In the Mutable Instruments code, Part doesn't initialize itself, so zero it here. | |||
| memset(part, 0, sizeof(*part)); | |||
| part->Init(reverb_buffer); | |||
| // Just some random numbers | |||
| uint32_t seed[3] = {1, 2, 3}; | |||
| part->Seed(seed, 3); | |||
| } | |||
| ~Elements() { | |||
| delete part; | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| // Get input | |||
| if (!inputBuffer.full()) { | |||
| dsp::Frame<2> inputFrame; | |||
| inputFrame.samples[0] = inputs[BLOW_INPUT].getVoltage() / 5.0; | |||
| inputFrame.samples[1] = inputs[STRIKE_INPUT].getVoltage() / 5.0; | |||
| inputBuffer.push(inputFrame); | |||
| } | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| float blow[16] = {}; | |||
| float strike[16] = {}; | |||
| float main[16]; | |||
| float aux[16]; | |||
| // Convert input buffer | |||
| { | |||
| inputSrc.setRates(args.sampleRate, 32000); | |||
| dsp::Frame<2> inputFrames[16]; | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = 16; | |||
| inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| for (int i = 0; i < outLen; i++) { | |||
| blow[i] = inputFrames[i].samples[0]; | |||
| strike[i] = inputFrames[i].samples[1]; | |||
| } | |||
| } | |||
| // Set patch from parameters | |||
| elements::Patch* p = part->mutable_patch(); | |||
| p->exciter_envelope_shape = params[CONTOUR_PARAM].getValue(); | |||
| p->exciter_bow_level = params[BOW_PARAM].getValue(); | |||
| p->exciter_blow_level = params[BLOW_PARAM].getValue(); | |||
| p->exciter_strike_level = params[STRIKE_PARAM].getValue(); | |||
| #define BIND(_p, _m, _i) clamp(params[_p].getValue() + 3.3f*dsp::quadraticBipolar(params[_m].getValue())*inputs[_i].getVoltage()/5.0f, 0.0f, 0.9995f) | |||
| p->exciter_bow_timbre = BIND(BOW_TIMBRE_PARAM, BOW_TIMBRE_MOD_PARAM, BOW_TIMBRE_MOD_INPUT); | |||
| p->exciter_blow_meta = BIND(FLOW_PARAM, FLOW_MOD_PARAM, FLOW_MOD_INPUT); | |||
| p->exciter_blow_timbre = BIND(BLOW_TIMBRE_PARAM, BLOW_TIMBRE_MOD_PARAM, BLOW_TIMBRE_MOD_INPUT); | |||
| p->exciter_strike_meta = BIND(MALLET_PARAM, MALLET_MOD_PARAM, MALLET_MOD_INPUT); | |||
| p->exciter_strike_timbre = BIND(STRIKE_TIMBRE_PARAM, STRIKE_TIMBRE_MOD_PARAM, STRIKE_TIMBRE_MOD_INPUT); | |||
| p->resonator_geometry = BIND(GEOMETRY_PARAM, GEOMETRY_MOD_PARAM, GEOMETRY_MOD_INPUT); | |||
| p->resonator_brightness = BIND(BRIGHTNESS_PARAM, BRIGHTNESS_MOD_PARAM, BRIGHTNESS_MOD_INPUT); | |||
| p->resonator_damping = BIND(DAMPING_PARAM, DAMPING_MOD_PARAM, DAMPING_MOD_INPUT); | |||
| p->resonator_position = BIND(POSITION_PARAM, POSITION_MOD_PARAM, POSITION_MOD_INPUT); | |||
| p->space = clamp(params[SPACE_PARAM].getValue() + params[SPACE_MOD_PARAM].getValue()*inputs[SPACE_MOD_INPUT].getVoltage()/5.0f, 0.0f, 2.0f); | |||
| // Get performance inputs | |||
| elements::PerformanceState performance; | |||
| performance.note = 12.0*inputs[NOTE_INPUT].getVoltage() + roundf(params[COARSE_PARAM].getValue()) + params[FINE_PARAM].getValue() + 69.0; | |||
| performance.modulation = 3.3*dsp::quarticBipolar(params[FM_PARAM].getValue()) * 49.5 * inputs[FM_INPUT].getVoltage()/5.0; | |||
| performance.gate = params[PLAY_PARAM].getValue() >= 1.0 || inputs[GATE_INPUT].getVoltage() >= 1.0; | |||
| performance.strength = clamp(1.0 - inputs[STRENGTH_INPUT].getVoltage()/5.0f, 0.0f, 1.0f); | |||
| // Generate audio | |||
| part->Process(performance, blow, strike, main, aux, 16); | |||
| // Convert output buffer | |||
| { | |||
| dsp::Frame<2> outputFrames[16]; | |||
| for (int i = 0; i < 16; i++) { | |||
| outputFrames[i].samples[0] = main[i]; | |||
| outputFrames[i].samples[1] = aux[i]; | |||
| } | |||
| outputSrc.setRates(32000, args.sampleRate); | |||
| int inLen = 16; | |||
| int outLen = outputBuffer.capacity(); | |||
| outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| // Set lights | |||
| lights[GATE_LIGHT].setBrightness(performance.gate ? 0.75 : 0.0); | |||
| lights[EXCITER_LIGHT].setBrightness(part->exciter_level()); | |||
| lights[RESONATOR_LIGHT].setBrightness(part->resonator_level()); | |||
| } | |||
| // Set output | |||
| if (!outputBuffer.empty()) { | |||
| dsp::Frame<2> outputFrame = outputBuffer.shift(); | |||
| outputs[AUX_OUTPUT].setVoltage(5.0 * outputFrame.samples[0]); | |||
| outputs[MAIN_OUTPUT].setVoltage(5.0 * outputFrame.samples[1]); | |||
| } | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -105,141 +235,6 @@ struct Elements : Module { | |||
| }; | |||
| Elements::Elements() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Elements::CONTOUR_PARAM, 0.0, 1.0, 1.0); | |||
| configParam(Elements::BOW_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Elements::BLOW_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Elements::STRIKE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::COARSE_PARAM, -30.0, 30.0, 0.0); | |||
| configParam(Elements::FINE_PARAM, -2.0, 2.0, 0.0); | |||
| configParam(Elements::FM_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::FLOW_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::MALLET_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::GEOMETRY_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::BRIGHTNESS_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::BOW_TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::BLOW_TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::STRIKE_TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::DAMPING_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::POSITION_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Elements::SPACE_PARAM, 0.0, 2.0, 0.0); | |||
| configParam(Elements::BOW_TIMBRE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::FLOW_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::BLOW_TIMBRE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::MALLET_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::STRIKE_TIMBRE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::DAMPING_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::GEOMETRY_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::POSITION_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::BRIGHTNESS_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Elements::SPACE_MOD_PARAM, -2.0, 2.0, 0.0); | |||
| configParam(Elements::PLAY_PARAM, 0.0, 1.0, 0.0); | |||
| part = new elements::Part(); | |||
| // In the Mutable Instruments code, Part doesn't initialize itself, so zero it here. | |||
| memset(part, 0, sizeof(*part)); | |||
| part->Init(reverb_buffer); | |||
| // Just some random numbers | |||
| uint32_t seed[3] = {1, 2, 3}; | |||
| part->Seed(seed, 3); | |||
| } | |||
| Elements::~Elements() { | |||
| delete part; | |||
| } | |||
| void Elements::process(const ProcessArgs &args) { | |||
| // Get input | |||
| if (!inputBuffer.full()) { | |||
| dsp::Frame<2> inputFrame; | |||
| inputFrame.samples[0] = inputs[BLOW_INPUT].getVoltage() / 5.0; | |||
| inputFrame.samples[1] = inputs[STRIKE_INPUT].getVoltage() / 5.0; | |||
| inputBuffer.push(inputFrame); | |||
| } | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| float blow[16] = {}; | |||
| float strike[16] = {}; | |||
| float main[16]; | |||
| float aux[16]; | |||
| // Convert input buffer | |||
| { | |||
| inputSrc.setRates(args.sampleRate, 32000); | |||
| dsp::Frame<2> inputFrames[16]; | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = 16; | |||
| inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| for (int i = 0; i < outLen; i++) { | |||
| blow[i] = inputFrames[i].samples[0]; | |||
| strike[i] = inputFrames[i].samples[1]; | |||
| } | |||
| } | |||
| // Set patch from parameters | |||
| elements::Patch* p = part->mutable_patch(); | |||
| p->exciter_envelope_shape = params[CONTOUR_PARAM].getValue(); | |||
| p->exciter_bow_level = params[BOW_PARAM].getValue(); | |||
| p->exciter_blow_level = params[BLOW_PARAM].getValue(); | |||
| p->exciter_strike_level = params[STRIKE_PARAM].getValue(); | |||
| #define BIND(_p, _m, _i) clamp(params[_p].getValue() + 3.3f*dsp::quadraticBipolar(params[_m].getValue())*inputs[_i].getVoltage()/5.0f, 0.0f, 0.9995f) | |||
| p->exciter_bow_timbre = BIND(BOW_TIMBRE_PARAM, BOW_TIMBRE_MOD_PARAM, BOW_TIMBRE_MOD_INPUT); | |||
| p->exciter_blow_meta = BIND(FLOW_PARAM, FLOW_MOD_PARAM, FLOW_MOD_INPUT); | |||
| p->exciter_blow_timbre = BIND(BLOW_TIMBRE_PARAM, BLOW_TIMBRE_MOD_PARAM, BLOW_TIMBRE_MOD_INPUT); | |||
| p->exciter_strike_meta = BIND(MALLET_PARAM, MALLET_MOD_PARAM, MALLET_MOD_INPUT); | |||
| p->exciter_strike_timbre = BIND(STRIKE_TIMBRE_PARAM, STRIKE_TIMBRE_MOD_PARAM, STRIKE_TIMBRE_MOD_INPUT); | |||
| p->resonator_geometry = BIND(GEOMETRY_PARAM, GEOMETRY_MOD_PARAM, GEOMETRY_MOD_INPUT); | |||
| p->resonator_brightness = BIND(BRIGHTNESS_PARAM, BRIGHTNESS_MOD_PARAM, BRIGHTNESS_MOD_INPUT); | |||
| p->resonator_damping = BIND(DAMPING_PARAM, DAMPING_MOD_PARAM, DAMPING_MOD_INPUT); | |||
| p->resonator_position = BIND(POSITION_PARAM, POSITION_MOD_PARAM, POSITION_MOD_INPUT); | |||
| p->space = clamp(params[SPACE_PARAM].getValue() + params[SPACE_MOD_PARAM].getValue()*inputs[SPACE_MOD_INPUT].getVoltage()/5.0f, 0.0f, 2.0f); | |||
| // Get performance inputs | |||
| elements::PerformanceState performance; | |||
| performance.note = 12.0*inputs[NOTE_INPUT].getVoltage() + roundf(params[COARSE_PARAM].getValue()) + params[FINE_PARAM].getValue() + 69.0; | |||
| performance.modulation = 3.3*dsp::quarticBipolar(params[FM_PARAM].getValue()) * 49.5 * inputs[FM_INPUT].getVoltage()/5.0; | |||
| performance.gate = params[PLAY_PARAM].getValue() >= 1.0 || inputs[GATE_INPUT].getVoltage() >= 1.0; | |||
| performance.strength = clamp(1.0 - inputs[STRENGTH_INPUT].getVoltage()/5.0f, 0.0f, 1.0f); | |||
| // Generate audio | |||
| part->Process(performance, blow, strike, main, aux, 16); | |||
| // Convert output buffer | |||
| { | |||
| dsp::Frame<2> outputFrames[16]; | |||
| for (int i = 0; i < 16; i++) { | |||
| outputFrames[i].samples[0] = main[i]; | |||
| outputFrames[i].samples[1] = aux[i]; | |||
| } | |||
| outputSrc.setRates(32000, args.sampleRate); | |||
| int inLen = 16; | |||
| int outLen = outputBuffer.capacity(); | |||
| outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| // Set lights | |||
| lights[GATE_LIGHT].setBrightness(performance.gate ? 0.75 : 0.0); | |||
| lights[EXCITER_LIGHT].setBrightness(part->exciter_level()); | |||
| lights[RESONATOR_LIGHT].setBrightness(part->resonator_level()); | |||
| } | |||
| // Set output | |||
| if (!outputBuffer.empty()) { | |||
| dsp::Frame<2> outputFrame = outputBuffer.shift(); | |||
| outputs[AUX_OUTPUT].setVoltage(5.0 * outputFrame.samples[0]); | |||
| outputs[MAIN_OUTPUT].setVoltage(5.0 * outputFrame.samples[1]); | |||
| } | |||
| } | |||
| struct ElementsModalItem : MenuItem { | |||
| Elements *elements; | |||
| int model; | |||
| @@ -49,8 +49,157 @@ struct Frames : Module { | |||
| dsp::SchmittTrigger addTrigger; | |||
| dsp::SchmittTrigger delTrigger; | |||
| Frames(); | |||
| void process(const ProcessArgs &args) override; | |||
| Frames() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Frames::GAIN1_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::GAIN2_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::GAIN3_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::GAIN4_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::FRAME_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::MODULATION_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Frames::ADD_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::DEL_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::OFFSET_PARAM, 0.0, 1.0, 0.0); | |||
| memset(&keyframer, 0, sizeof(keyframer)); | |||
| keyframer.Init(); | |||
| memset(&poly_lfo, 0, sizeof(poly_lfo)); | |||
| poly_lfo.Init(); | |||
| onReset(); | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| // Set gain and timestamp knobs | |||
| uint16_t controls[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| controls[i] = params[GAIN1_PARAM + i].getValue() * 65535.0; | |||
| } | |||
| int32_t timestamp = params[FRAME_PARAM].getValue() * 65535.0; | |||
| int32_t timestampMod = timestamp + params[MODULATION_PARAM].getValue() * inputs[FRAME_INPUT].getVoltage() / 10.0 * 65535.0; | |||
| timestamp = clamp(timestamp, 0, 65535); | |||
| timestampMod = clamp(timestampMod, 0, 65535); | |||
| int16_t nearestIndex = -1; | |||
| if (!poly_lfo_mode) { | |||
| nearestIndex = keyframer.FindNearestKeyframe(timestamp, 2048); | |||
| } | |||
| // Render, handle buttons | |||
| if (poly_lfo_mode) { | |||
| if (controls[0] != lastControls[0]) | |||
| poly_lfo.set_shape(controls[0]); | |||
| if (controls[1] != lastControls[1]) | |||
| poly_lfo.set_shape_spread(controls[1]); | |||
| if (controls[2] != lastControls[2]) | |||
| poly_lfo.set_spread(controls[2]); | |||
| if (controls[3] != lastControls[3]) | |||
| poly_lfo.set_coupling(controls[3]); | |||
| poly_lfo.Render(timestampMod); | |||
| } | |||
| else { | |||
| for (int i = 0; i < 4; i++) { | |||
| if (controls[i] != lastControls[i]) { | |||
| // Update recently moved control | |||
| if (keyframer.num_keyframes() == 0) { | |||
| keyframer.set_immediate(i, controls[i]); | |||
| } | |||
| if (nearestIndex >= 0) { | |||
| frames::Keyframe *nearestKeyframe = keyframer.mutable_keyframe(nearestIndex); | |||
| nearestKeyframe->values[i] = controls[i]; | |||
| } | |||
| } | |||
| } | |||
| if (addTrigger.process(params[ADD_PARAM].getValue())) { | |||
| if (nearestIndex < 0) { | |||
| keyframer.AddKeyframe(timestamp, controls); | |||
| } | |||
| } | |||
| if (delTrigger.process(params[DEL_PARAM].getValue())) { | |||
| if (nearestIndex >= 0) { | |||
| int32_t nearestTimestamp = keyframer.keyframe(nearestIndex).timestamp; | |||
| keyframer.RemoveKeyframe(nearestTimestamp); | |||
| } | |||
| } | |||
| keyframer.Evaluate(timestampMod); | |||
| } | |||
| // Get gains | |||
| float gains[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| if (poly_lfo_mode) { | |||
| // gains[i] = poly_lfo.level(i) / 255.0; | |||
| gains[i] = poly_lfo.level16(i) / 65535.0; | |||
| } | |||
| else { | |||
| float lin = keyframer.level(i) / 65535.0; | |||
| gains[i] = lin; | |||
| } | |||
| // Simulate SSM2164 | |||
| if (keyframer.mutable_settings(i)->response > 0) { | |||
| const float expBase = 200.0; | |||
| float expGain = rescale(powf(expBase, gains[i]), 1.0f, expBase, 0.0f, 1.0f); | |||
| gains[i] = crossfade(gains[i], expGain, keyframer.mutable_settings(i)->response / 255.0f); | |||
| } | |||
| } | |||
| // Update last controls | |||
| for (int i = 0; i < 4; i++) { | |||
| lastControls[i] = controls[i]; | |||
| } | |||
| // Get inputs | |||
| float all = ((int)params[OFFSET_PARAM].getValue() == 1) ? 10.0 : 0.0; | |||
| if (inputs[ALL_INPUT].isConnected()) { | |||
| all = inputs[ALL_INPUT].getVoltage(); | |||
| } | |||
| float ins[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| ins[i] = inputs[IN1_INPUT + i].getNormalVoltage(all) * gains[i]; | |||
| } | |||
| // Set outputs | |||
| float mix = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(ins[i]); | |||
| } | |||
| else { | |||
| mix += ins[i]; | |||
| } | |||
| } | |||
| outputs[MIX_OUTPUT].setVoltage(clamp(mix / 2.0, -10.0f, 10.0f)); | |||
| // Set lights | |||
| for (int i = 0; i < 4; i++) { | |||
| lights[GAIN1_LIGHT + i].setBrightness(gains[i]); | |||
| } | |||
| if (poly_lfo_mode) { | |||
| lights[EDIT_LIGHT].value = (poly_lfo.level(0) > 128 ? 1.0 : 0.0); | |||
| } | |||
| else { | |||
| lights[EDIT_LIGHT].value = (nearestIndex >= 0 ? 1.0 : 0.0); | |||
| } | |||
| // Set frame light colors | |||
| const uint8_t *colors; | |||
| if (poly_lfo_mode) { | |||
| colors = poly_lfo.color(); | |||
| } | |||
| else { | |||
| colors = keyframer.color(); | |||
| } | |||
| for (int i = 0; i < 3; i++) { | |||
| float c = colors[i] / 255.0; | |||
| c = 1.0 - (1.0 - c) * 1.25; | |||
| lights[FRAME_LIGHT + i].setBrightness(c); | |||
| } | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -130,160 +279,6 @@ struct Frames : Module { | |||
| }; | |||
| Frames::Frames() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Frames::GAIN1_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::GAIN2_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::GAIN3_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::GAIN4_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::FRAME_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::MODULATION_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Frames::ADD_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::DEL_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Frames::OFFSET_PARAM, 0.0, 1.0, 0.0); | |||
| memset(&keyframer, 0, sizeof(keyframer)); | |||
| keyframer.Init(); | |||
| memset(&poly_lfo, 0, sizeof(poly_lfo)); | |||
| poly_lfo.Init(); | |||
| onReset(); | |||
| } | |||
| void Frames::process(const ProcessArgs &args) { | |||
| // Set gain and timestamp knobs | |||
| uint16_t controls[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| controls[i] = params[GAIN1_PARAM + i].getValue() * 65535.0; | |||
| } | |||
| int32_t timestamp = params[FRAME_PARAM].getValue() * 65535.0; | |||
| int32_t timestampMod = timestamp + params[MODULATION_PARAM].getValue() * inputs[FRAME_INPUT].getVoltage() / 10.0 * 65535.0; | |||
| timestamp = clamp(timestamp, 0, 65535); | |||
| timestampMod = clamp(timestampMod, 0, 65535); | |||
| int16_t nearestIndex = -1; | |||
| if (!poly_lfo_mode) { | |||
| nearestIndex = keyframer.FindNearestKeyframe(timestamp, 2048); | |||
| } | |||
| // Render, handle buttons | |||
| if (poly_lfo_mode) { | |||
| if (controls[0] != lastControls[0]) | |||
| poly_lfo.set_shape(controls[0]); | |||
| if (controls[1] != lastControls[1]) | |||
| poly_lfo.set_shape_spread(controls[1]); | |||
| if (controls[2] != lastControls[2]) | |||
| poly_lfo.set_spread(controls[2]); | |||
| if (controls[3] != lastControls[3]) | |||
| poly_lfo.set_coupling(controls[3]); | |||
| poly_lfo.Render(timestampMod); | |||
| } | |||
| else { | |||
| for (int i = 0; i < 4; i++) { | |||
| if (controls[i] != lastControls[i]) { | |||
| // Update recently moved control | |||
| if (keyframer.num_keyframes() == 0) { | |||
| keyframer.set_immediate(i, controls[i]); | |||
| } | |||
| if (nearestIndex >= 0) { | |||
| frames::Keyframe *nearestKeyframe = keyframer.mutable_keyframe(nearestIndex); | |||
| nearestKeyframe->values[i] = controls[i]; | |||
| } | |||
| } | |||
| } | |||
| if (addTrigger.process(params[ADD_PARAM].getValue())) { | |||
| if (nearestIndex < 0) { | |||
| keyframer.AddKeyframe(timestamp, controls); | |||
| } | |||
| } | |||
| if (delTrigger.process(params[DEL_PARAM].getValue())) { | |||
| if (nearestIndex >= 0) { | |||
| int32_t nearestTimestamp = keyframer.keyframe(nearestIndex).timestamp; | |||
| keyframer.RemoveKeyframe(nearestTimestamp); | |||
| } | |||
| } | |||
| keyframer.Evaluate(timestampMod); | |||
| } | |||
| // Get gains | |||
| float gains[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| if (poly_lfo_mode) { | |||
| // gains[i] = poly_lfo.level(i) / 255.0; | |||
| gains[i] = poly_lfo.level16(i) / 65535.0; | |||
| } | |||
| else { | |||
| float lin = keyframer.level(i) / 65535.0; | |||
| gains[i] = lin; | |||
| } | |||
| // Simulate SSM2164 | |||
| if (keyframer.mutable_settings(i)->response > 0) { | |||
| const float expBase = 200.0; | |||
| float expGain = rescale(powf(expBase, gains[i]), 1.0f, expBase, 0.0f, 1.0f); | |||
| gains[i] = crossfade(gains[i], expGain, keyframer.mutable_settings(i)->response / 255.0f); | |||
| } | |||
| } | |||
| // Update last controls | |||
| for (int i = 0; i < 4; i++) { | |||
| lastControls[i] = controls[i]; | |||
| } | |||
| // Get inputs | |||
| float all = ((int)params[OFFSET_PARAM].getValue() == 1) ? 10.0 : 0.0; | |||
| if (inputs[ALL_INPUT].isConnected()) { | |||
| all = inputs[ALL_INPUT].getVoltage(); | |||
| } | |||
| float ins[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| ins[i] = inputs[IN1_INPUT + i].getNormalVoltage(all) * gains[i]; | |||
| } | |||
| // Set outputs | |||
| float mix = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(ins[i]); | |||
| } | |||
| else { | |||
| mix += ins[i]; | |||
| } | |||
| } | |||
| outputs[MIX_OUTPUT].setVoltage(clamp(mix / 2.0, -10.0f, 10.0f)); | |||
| // Set lights | |||
| for (int i = 0; i < 4; i++) { | |||
| lights[GAIN1_LIGHT + i].setBrightness(gains[i]); | |||
| } | |||
| if (poly_lfo_mode) { | |||
| lights[EDIT_LIGHT].value = (poly_lfo.level(0) > 128 ? 1.0 : 0.0); | |||
| } | |||
| else { | |||
| lights[EDIT_LIGHT].value = (nearestIndex >= 0 ? 1.0 : 0.0); | |||
| } | |||
| // Set frame light colors | |||
| const uint8_t *colors; | |||
| if (poly_lfo_mode) { | |||
| colors = poly_lfo.color(); | |||
| } | |||
| else { | |||
| colors = keyframer.color(); | |||
| } | |||
| for (int i = 0; i < 3; i++) { | |||
| float c = colors[i] / 255.0; | |||
| c = 1.0 - (1.0 - c) * 1.25; | |||
| lights[FRAME_LIGHT + i].setBrightness(c); | |||
| } | |||
| } | |||
| struct CKSSRot : SVGSwitch { | |||
| CKSSRot() { | |||
| addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CKSS_rot_0.svg"))); | |||
| @@ -34,38 +34,37 @@ struct Kinks : Module { | |||
| float sample = 0.0; | |||
| Kinks() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);} | |||
| void process(const ProcessArgs &args) override; | |||
| }; | |||
| void Kinks::process(const ProcessArgs &args) { | |||
| // Gaussian noise generator | |||
| float noise = 2.0 * random::normal(); | |||
| // S&H | |||
| if (trigger.process(inputs[TRIG_INPUT].getVoltage() / 0.7)) { | |||
| sample = inputs[SH_INPUT].getNormalVoltage(noise); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| } | |||
| // lights | |||
| lights[SIGN_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inputs[SIGN_INPUT].getVoltage() / 5.0), args.sampleTime); | |||
| lights[SIGN_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inputs[SIGN_INPUT].getVoltage() / 5.0), args.sampleTime); | |||
| float logicSum = inputs[LOGIC_A_INPUT].getVoltage() + inputs[LOGIC_B_INPUT].getVoltage(); | |||
| lights[LOGIC_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, logicSum / 5.0), args.sampleTime); | |||
| lights[LOGIC_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -logicSum / 5.0), args.sampleTime); | |||
| lights[SH_POS_LIGHT].setBrightness(fmaxf(0.0, sample / 5.0)); | |||
| lights[SH_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample / 5.0)); | |||
| // outputs | |||
| outputs[INVERT_OUTPUT].setVoltage(-inputs[SIGN_INPUT].getVoltage()); | |||
| outputs[HALF_RECTIFY_OUTPUT].setVoltage(fmaxf(0.0, inputs[SIGN_INPUT].getVoltage())); | |||
| outputs[FULL_RECTIFY_OUTPUT].setVoltage(fabsf(inputs[SIGN_INPUT].getVoltage())); | |||
| outputs[MAX_OUTPUT].setVoltage(fmaxf(inputs[LOGIC_A_INPUT].getVoltage(), inputs[LOGIC_B_INPUT].getVoltage())); | |||
| outputs[MIN_OUTPUT].setVoltage(fminf(inputs[LOGIC_A_INPUT].getVoltage(), inputs[LOGIC_B_INPUT].getVoltage())); | |||
| outputs[NOISE_OUTPUT].setVoltage(noise); | |||
| outputs[SH_OUTPUT].setVoltage(sample); | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| // Gaussian noise generator | |||
| float noise = 2.0 * random::normal(); | |||
| // S&H | |||
| if (trigger.process(inputs[TRIG_INPUT].getVoltage() / 0.7)) { | |||
| sample = inputs[SH_INPUT].getNormalVoltage(noise); | |||
| } | |||
| // lights | |||
| lights[SIGN_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inputs[SIGN_INPUT].getVoltage() / 5.0), args.sampleTime); | |||
| lights[SIGN_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inputs[SIGN_INPUT].getVoltage() / 5.0), args.sampleTime); | |||
| float logicSum = inputs[LOGIC_A_INPUT].getVoltage() + inputs[LOGIC_B_INPUT].getVoltage(); | |||
| lights[LOGIC_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, logicSum / 5.0), args.sampleTime); | |||
| lights[LOGIC_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -logicSum / 5.0), args.sampleTime); | |||
| lights[SH_POS_LIGHT].setBrightness(fmaxf(0.0, sample / 5.0)); | |||
| lights[SH_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample / 5.0)); | |||
| // outputs | |||
| outputs[INVERT_OUTPUT].setVoltage(-inputs[SIGN_INPUT].getVoltage()); | |||
| outputs[HALF_RECTIFY_OUTPUT].setVoltage(fmaxf(0.0, inputs[SIGN_INPUT].getVoltage())); | |||
| outputs[FULL_RECTIFY_OUTPUT].setVoltage(fabsf(inputs[SIGN_INPUT].getVoltage())); | |||
| outputs[MAX_OUTPUT].setVoltage(fmaxf(inputs[LOGIC_A_INPUT].getVoltage(), inputs[LOGIC_B_INPUT].getVoltage())); | |||
| outputs[MIN_OUTPUT].setVoltage(fminf(inputs[LOGIC_A_INPUT].getVoltage(), inputs[LOGIC_B_INPUT].getVoltage())); | |||
| outputs[NOISE_OUTPUT].setVoltage(noise); | |||
| outputs[SH_OUTPUT].setVoltage(sample); | |||
| } | |||
| }; | |||
| struct KinksWidget : ModuleWidget { | |||
| @@ -31,30 +31,29 @@ struct Links : Module { | |||
| }; | |||
| Links() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);} | |||
| void process(const ProcessArgs &args) override; | |||
| }; | |||
| void Links::process(const ProcessArgs &args) { | |||
| float inA = inputs[A1_INPUT].getVoltage(); | |||
| float inB = inputs[B1_INPUT].getVoltage() + inputs[B2_INPUT].getVoltage(); | |||
| float inC = inputs[C1_INPUT].getVoltage() + inputs[C2_INPUT].getVoltage() + inputs[C3_INPUT].getVoltage(); | |||
| outputs[A1_OUTPUT].setVoltage(inA); | |||
| outputs[A2_OUTPUT].setVoltage(inA); | |||
| outputs[A3_OUTPUT].setVoltage(inA); | |||
| outputs[B1_OUTPUT].setVoltage(inB); | |||
| outputs[B2_OUTPUT].setVoltage(inB); | |||
| outputs[C1_OUTPUT].setVoltage(inC); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| } | |||
| lights[A_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inA / 5.0), args.sampleTime); | |||
| lights[A_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inA / 5.0), args.sampleTime); | |||
| lights[B_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inB / 5.0), args.sampleTime); | |||
| lights[B_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inB / 5.0), args.sampleTime); | |||
| lights[C_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inC / 5.0), args.sampleTime); | |||
| lights[C_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inC / 5.0), args.sampleTime); | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| float inA = inputs[A1_INPUT].getVoltage(); | |||
| float inB = inputs[B1_INPUT].getVoltage() + inputs[B2_INPUT].getVoltage(); | |||
| float inC = inputs[C1_INPUT].getVoltage() + inputs[C2_INPUT].getVoltage() + inputs[C3_INPUT].getVoltage(); | |||
| outputs[A1_OUTPUT].setVoltage(inA); | |||
| outputs[A2_OUTPUT].setVoltage(inA); | |||
| outputs[A3_OUTPUT].setVoltage(inA); | |||
| outputs[B1_OUTPUT].setVoltage(inB); | |||
| outputs[B2_OUTPUT].setVoltage(inB); | |||
| outputs[C1_OUTPUT].setVoltage(inC); | |||
| lights[A_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inA / 5.0), args.sampleTime); | |||
| lights[A_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inA / 5.0), args.sampleTime); | |||
| lights[B_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inB / 5.0), args.sampleTime); | |||
| lights[B_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inB / 5.0), args.sampleTime); | |||
| lights[C_POS_LIGHT].setSmoothBrightness(fmaxf(0.0, inC / 5.0), args.sampleTime); | |||
| lights[C_NEG_LIGHT].setSmoothBrightness(fmaxf(0.0, -inC / 5.0), args.sampleTime); | |||
| } | |||
| }; | |||
| struct LinksWidget : ModuleWidget { | |||
| @@ -63,8 +63,154 @@ struct Rings : Module { | |||
| rings::ResonatorModel resonatorModel = rings::RESONATOR_MODEL_MODAL; | |||
| bool easterEgg = false; | |||
| Rings(); | |||
| void process(const ProcessArgs &args) override; | |||
| Rings() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Rings::POLYPHONY_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Rings::RESONATOR_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Rings::FREQUENCY_PARAM, 0.0, 60.0, 30.0); | |||
| configParam(Rings::STRUCTURE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::BRIGHTNESS_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::DAMPING_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::POSITION_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::BRIGHTNESS_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::FREQUENCY_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::DAMPING_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::STRUCTURE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::POSITION_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| memset(&strummer, 0, sizeof(strummer)); | |||
| memset(&part, 0, sizeof(part)); | |||
| memset(&string_synth, 0, sizeof(string_synth)); | |||
| strummer.Init(0.01, 44100.0 / 24); | |||
| part.Init(reverb_buffer); | |||
| string_synth.Init(reverb_buffer); | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| // TODO | |||
| // "Normalized to a pulse/burst generator that reacts to note changes on the V/OCT input." | |||
| // Get input | |||
| if (!inputBuffer.full()) { | |||
| dsp::Frame<1> f; | |||
| f.samples[0] = inputs[IN_INPUT].getVoltage() / 5.0; | |||
| inputBuffer.push(f); | |||
| } | |||
| if (!strum) { | |||
| strum = inputs[STRUM_INPUT].getVoltage() >= 1.0; | |||
| } | |||
| // Polyphony / model | |||
| if (polyphonyTrigger.process(params[POLYPHONY_PARAM].getValue())) { | |||
| polyphonyMode = (polyphonyMode + 1) % 3; | |||
| } | |||
| lights[POLYPHONY_GREEN_LIGHT].value = (polyphonyMode == 0 || polyphonyMode == 1) ? 1.0 : 0.0; | |||
| lights[POLYPHONY_RED_LIGHT].value = (polyphonyMode == 1 || polyphonyMode == 2) ? 1.0 : 0.0; | |||
| if (modelTrigger.process(params[RESONATOR_PARAM].getValue())) { | |||
| resonatorModel = (rings::ResonatorModel) ((resonatorModel + 1) % 3); | |||
| } | |||
| int modelColor = resonatorModel % 3; | |||
| lights[RESONATOR_GREEN_LIGHT].value = (modelColor == 0 || modelColor == 1) ? 1.0 : 0.0; | |||
| lights[RESONATOR_RED_LIGHT].value = (modelColor == 1 || modelColor == 2) ? 1.0 : 0.0; | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| float in[24] = {}; | |||
| // Convert input buffer | |||
| { | |||
| inputSrc.setRates(args.sampleRate, 48000); | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = 24; | |||
| inputSrc.process(inputBuffer.startData(), &inLen, (dsp::Frame<1>*) in, &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| } | |||
| // Polyphony | |||
| int polyphony = 1 << polyphonyMode; | |||
| if (part.polyphony() != polyphony) | |||
| part.set_polyphony(polyphony); | |||
| // Model | |||
| if (easterEgg) | |||
| string_synth.set_fx((rings::FxType) resonatorModel); | |||
| else | |||
| part.set_model(resonatorModel); | |||
| // Patch | |||
| rings::Patch patch; | |||
| float structure = params[STRUCTURE_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[STRUCTURE_MOD_PARAM].getValue())*inputs[STRUCTURE_MOD_INPUT].getVoltage()/5.0; | |||
| patch.structure = clamp(structure, 0.0f, 0.9995f); | |||
| patch.brightness = clamp(params[BRIGHTNESS_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[BRIGHTNESS_MOD_PARAM].getValue())*inputs[BRIGHTNESS_MOD_INPUT].getVoltage()/5.0, 0.0f, 1.0f); | |||
| patch.damping = clamp(params[DAMPING_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[DAMPING_MOD_PARAM].getValue())*inputs[DAMPING_MOD_INPUT].getVoltage()/5.0, 0.0f, 0.9995f); | |||
| patch.position = clamp(params[POSITION_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[POSITION_MOD_PARAM].getValue())*inputs[POSITION_MOD_INPUT].getVoltage()/5.0, 0.0f, 0.9995f); | |||
| // Performance | |||
| rings::PerformanceState performance_state; | |||
| performance_state.note = 12.0*inputs[PITCH_INPUT].getNormalVoltage(1/12.0); | |||
| float transpose = params[FREQUENCY_PARAM].getValue(); | |||
| // Quantize transpose if pitch input is connected | |||
| if (inputs[PITCH_INPUT].isConnected()) { | |||
| transpose = roundf(transpose); | |||
| } | |||
| performance_state.tonic = 12.0 + clamp(transpose, 0.0f, 60.0f); | |||
| performance_state.fm = clamp(48.0 * 3.3*dsp::quarticBipolar(params[FREQUENCY_MOD_PARAM].getValue()) * inputs[FREQUENCY_MOD_INPUT].getNormalVoltage(1.0)/5.0, -48.0f, 48.0f); | |||
| performance_state.internal_exciter = !inputs[IN_INPUT].isConnected(); | |||
| performance_state.internal_strum = !inputs[STRUM_INPUT].isConnected(); | |||
| performance_state.internal_note = !inputs[PITCH_INPUT].isConnected(); | |||
| // TODO | |||
| // "Normalized to a step detector on the V/OCT input and a transient detector on the IN input." | |||
| performance_state.strum = strum && !lastStrum; | |||
| lastStrum = strum; | |||
| strum = false; | |||
| performance_state.chord = clamp((int) roundf(structure * (rings::kNumChords - 1)), 0, rings::kNumChords - 1); | |||
| // Process audio | |||
| float out[24]; | |||
| float aux[24]; | |||
| if (easterEgg) { | |||
| strummer.Process(NULL, 24, &performance_state); | |||
| string_synth.Process(performance_state, patch, in, out, aux, 24); | |||
| } | |||
| else { | |||
| strummer.Process(in, 24, &performance_state); | |||
| part.Process(performance_state, patch, in, out, aux, 24); | |||
| } | |||
| // Convert output buffer | |||
| { | |||
| dsp::Frame<2> outputFrames[24]; | |||
| for (int i = 0; i < 24; i++) { | |||
| outputFrames[i].samples[0] = out[i]; | |||
| outputFrames[i].samples[1] = aux[i]; | |||
| } | |||
| outputSrc.setRates(48000, args.sampleRate); | |||
| int inLen = 24; | |||
| int outLen = outputBuffer.capacity(); | |||
| outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| } | |||
| // Set output | |||
| if (!outputBuffer.empty()) { | |||
| dsp::Frame<2> outputFrame = outputBuffer.shift(); | |||
| // "Note that you need to insert a jack into each output to split the signals: when only one jack is inserted, both signals are mixed together." | |||
| if (outputs[ODD_OUTPUT].isConnected() && outputs[EVEN_OUTPUT].isConnected()) { | |||
| outputs[ODD_OUTPUT].setVoltage(clamp(outputFrame.samples[0], -1.0, 1.0)*5.0); | |||
| outputs[EVEN_OUTPUT].setVoltage(clamp(outputFrame.samples[1], -1.0, 1.0)*5.0); | |||
| } | |||
| else { | |||
| float v = clamp(outputFrame.samples[0] + outputFrame.samples[1], -1.0, 1.0)*5.0; | |||
| outputs[ODD_OUTPUT].setVoltage(v); | |||
| outputs[EVEN_OUTPUT].setVoltage(v); | |||
| } | |||
| } | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -105,156 +251,6 @@ struct Rings : Module { | |||
| }; | |||
| Rings::Rings() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Rings::POLYPHONY_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Rings::RESONATOR_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Rings::FREQUENCY_PARAM, 0.0, 60.0, 30.0); | |||
| configParam(Rings::STRUCTURE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::BRIGHTNESS_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::DAMPING_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::POSITION_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Rings::BRIGHTNESS_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::FREQUENCY_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::DAMPING_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::STRUCTURE_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Rings::POSITION_MOD_PARAM, -1.0, 1.0, 0.0); | |||
| memset(&strummer, 0, sizeof(strummer)); | |||
| memset(&part, 0, sizeof(part)); | |||
| memset(&string_synth, 0, sizeof(string_synth)); | |||
| strummer.Init(0.01, 44100.0 / 24); | |||
| part.Init(reverb_buffer); | |||
| string_synth.Init(reverb_buffer); | |||
| } | |||
| void Rings::process(const ProcessArgs &args) { | |||
| // TODO | |||
| // "Normalized to a pulse/burst generator that reacts to note changes on the V/OCT input." | |||
| // Get input | |||
| if (!inputBuffer.full()) { | |||
| dsp::Frame<1> f; | |||
| f.samples[0] = inputs[IN_INPUT].getVoltage() / 5.0; | |||
| inputBuffer.push(f); | |||
| } | |||
| if (!strum) { | |||
| strum = inputs[STRUM_INPUT].getVoltage() >= 1.0; | |||
| } | |||
| // Polyphony / model | |||
| if (polyphonyTrigger.process(params[POLYPHONY_PARAM].getValue())) { | |||
| polyphonyMode = (polyphonyMode + 1) % 3; | |||
| } | |||
| lights[POLYPHONY_GREEN_LIGHT].value = (polyphonyMode == 0 || polyphonyMode == 1) ? 1.0 : 0.0; | |||
| lights[POLYPHONY_RED_LIGHT].value = (polyphonyMode == 1 || polyphonyMode == 2) ? 1.0 : 0.0; | |||
| if (modelTrigger.process(params[RESONATOR_PARAM].getValue())) { | |||
| resonatorModel = (rings::ResonatorModel) ((resonatorModel + 1) % 3); | |||
| } | |||
| int modelColor = resonatorModel % 3; | |||
| lights[RESONATOR_GREEN_LIGHT].value = (modelColor == 0 || modelColor == 1) ? 1.0 : 0.0; | |||
| lights[RESONATOR_RED_LIGHT].value = (modelColor == 1 || modelColor == 2) ? 1.0 : 0.0; | |||
| // Render frames | |||
| if (outputBuffer.empty()) { | |||
| float in[24] = {}; | |||
| // Convert input buffer | |||
| { | |||
| inputSrc.setRates(args.sampleRate, 48000); | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = 24; | |||
| inputSrc.process(inputBuffer.startData(), &inLen, (dsp::Frame<1>*) in, &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| } | |||
| // Polyphony | |||
| int polyphony = 1 << polyphonyMode; | |||
| if (part.polyphony() != polyphony) | |||
| part.set_polyphony(polyphony); | |||
| // Model | |||
| if (easterEgg) | |||
| string_synth.set_fx((rings::FxType) resonatorModel); | |||
| else | |||
| part.set_model(resonatorModel); | |||
| // Patch | |||
| rings::Patch patch; | |||
| float structure = params[STRUCTURE_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[STRUCTURE_MOD_PARAM].getValue())*inputs[STRUCTURE_MOD_INPUT].getVoltage()/5.0; | |||
| patch.structure = clamp(structure, 0.0f, 0.9995f); | |||
| patch.brightness = clamp(params[BRIGHTNESS_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[BRIGHTNESS_MOD_PARAM].getValue())*inputs[BRIGHTNESS_MOD_INPUT].getVoltage()/5.0, 0.0f, 1.0f); | |||
| patch.damping = clamp(params[DAMPING_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[DAMPING_MOD_PARAM].getValue())*inputs[DAMPING_MOD_INPUT].getVoltage()/5.0, 0.0f, 0.9995f); | |||
| patch.position = clamp(params[POSITION_PARAM].getValue() + 3.3*dsp::quadraticBipolar(params[POSITION_MOD_PARAM].getValue())*inputs[POSITION_MOD_INPUT].getVoltage()/5.0, 0.0f, 0.9995f); | |||
| // Performance | |||
| rings::PerformanceState performance_state; | |||
| performance_state.note = 12.0*inputs[PITCH_INPUT].getNormalVoltage(1/12.0); | |||
| float transpose = params[FREQUENCY_PARAM].getValue(); | |||
| // Quantize transpose if pitch input is connected | |||
| if (inputs[PITCH_INPUT].isConnected()) { | |||
| transpose = roundf(transpose); | |||
| } | |||
| performance_state.tonic = 12.0 + clamp(transpose, 0.0f, 60.0f); | |||
| performance_state.fm = clamp(48.0 * 3.3*dsp::quarticBipolar(params[FREQUENCY_MOD_PARAM].getValue()) * inputs[FREQUENCY_MOD_INPUT].getNormalVoltage(1.0)/5.0, -48.0f, 48.0f); | |||
| performance_state.internal_exciter = !inputs[IN_INPUT].isConnected(); | |||
| performance_state.internal_strum = !inputs[STRUM_INPUT].isConnected(); | |||
| performance_state.internal_note = !inputs[PITCH_INPUT].isConnected(); | |||
| // TODO | |||
| // "Normalized to a step detector on the V/OCT input and a transient detector on the IN input." | |||
| performance_state.strum = strum && !lastStrum; | |||
| lastStrum = strum; | |||
| strum = false; | |||
| performance_state.chord = clamp((int) roundf(structure * (rings::kNumChords - 1)), 0, rings::kNumChords - 1); | |||
| // Process audio | |||
| float out[24]; | |||
| float aux[24]; | |||
| if (easterEgg) { | |||
| strummer.Process(NULL, 24, &performance_state); | |||
| string_synth.Process(performance_state, patch, in, out, aux, 24); | |||
| } | |||
| else { | |||
| strummer.Process(in, 24, &performance_state); | |||
| part.Process(performance_state, patch, in, out, aux, 24); | |||
| } | |||
| // Convert output buffer | |||
| { | |||
| dsp::Frame<2> outputFrames[24]; | |||
| for (int i = 0; i < 24; i++) { | |||
| outputFrames[i].samples[0] = out[i]; | |||
| outputFrames[i].samples[1] = aux[i]; | |||
| } | |||
| outputSrc.setRates(48000, args.sampleRate); | |||
| int inLen = 24; | |||
| int outLen = outputBuffer.capacity(); | |||
| outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
| outputBuffer.endIncr(outLen); | |||
| } | |||
| } | |||
| // Set output | |||
| if (!outputBuffer.empty()) { | |||
| dsp::Frame<2> outputFrame = outputBuffer.shift(); | |||
| // "Note that you need to insert a jack into each output to split the signals: when only one jack is inserted, both signals are mixed together." | |||
| if (outputs[ODD_OUTPUT].isConnected() && outputs[EVEN_OUTPUT].isConnected()) { | |||
| outputs[ODD_OUTPUT].setVoltage(clamp(outputFrame.samples[0], -1.0, 1.0)*5.0); | |||
| outputs[EVEN_OUTPUT].setVoltage(clamp(outputFrame.samples[1], -1.0, 1.0)*5.0); | |||
| } | |||
| else { | |||
| float v = clamp(outputFrame.samples[0] + outputFrame.samples[1], -1.0, 1.0)*5.0; | |||
| outputs[ODD_OUTPUT].setVoltage(v); | |||
| outputs[EVEN_OUTPUT].setVoltage(v); | |||
| } | |||
| } | |||
| } | |||
| struct RingsWidget : ModuleWidget { | |||
| RingsWidget(Rings *module) { | |||
| setModule(module); | |||
| @@ -39,32 +39,30 @@ struct Shades : Module { | |||
| configParam(Shades::MODE2_PARAM, 0.0, 1.0, 1.0); | |||
| configParam(Shades::MODE3_PARAM, 0.0, 1.0, 1.0); | |||
| } | |||
| void process(const ProcessArgs &args) override; | |||
| }; | |||
| void Shades::process(const ProcessArgs &args) { | |||
| float out = 0.0; | |||
| for (int i = 0; i < 3; i++) { | |||
| float in = inputs[IN1_INPUT + i].normalize(5.0); | |||
| if ((int)params[MODE1_PARAM + i].getValue() == 1) { | |||
| // attenuverter | |||
| in *= 2.0 * params[GAIN1_PARAM + i].getValue() - 1.0; | |||
| } | |||
| else { | |||
| // attenuator | |||
| in *= params[GAIN1_PARAM + i].getValue(); | |||
| } | |||
| out += in; | |||
| lights[OUT1_POS_LIGHT + 2*i].setBrightnessSmooth(fmaxf(0.0, out / 5.0)); | |||
| lights[OUT1_NEG_LIGHT + 2*i].setBrightnessSmooth(fmaxf(0.0, -out / 5.0)); | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(out); | |||
| out = 0.0; | |||
| void process(const ProcessArgs &args) { | |||
| float out = 0.0; | |||
| for (int i = 0; i < 3; i++) { | |||
| float in = inputs[IN1_INPUT + i].normalize(5.0); | |||
| if ((int)params[MODE1_PARAM + i].getValue() == 1) { | |||
| // attenuverter | |||
| in *= 2.0 * params[GAIN1_PARAM + i].getValue() - 1.0; | |||
| } | |||
| else { | |||
| // attenuator | |||
| in *= params[GAIN1_PARAM + i].getValue(); | |||
| } | |||
| out += in; | |||
| lights[OUT1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, out / 5.0), args.sampleTime); | |||
| lights[OUT1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -out / 5.0), args.sampleTime); | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(out); | |||
| out = 0.0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| struct ShadesWidget : ModuleWidget { | |||
| @@ -50,9 +50,108 @@ struct Tides : Module { | |||
| dsp::SchmittTrigger modeTrigger; | |||
| dsp::SchmittTrigger rangeTrigger; | |||
| Tides(); | |||
| void process(const ProcessArgs &args) override; | |||
| Tides() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Tides::MODE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Tides::RANGE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Tides::FREQUENCY_PARAM, -48.0, 48.0, 0.0); | |||
| configParam(Tides::FM_PARAM, -12.0, 12.0, 0.0); | |||
| configParam(Tides::SHAPE_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Tides::SLOPE_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Tides::SMOOTHNESS_PARAM, -1.0, 1.0, 0.0); | |||
| memset(&generator, 0, sizeof(generator)); | |||
| generator.Init(); | |||
| generator.set_sync(false); | |||
| onReset(); | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| tides::GeneratorMode mode = generator.mode(); | |||
| if (modeTrigger.process(params[MODE_PARAM].getValue())) { | |||
| mode = (tides::GeneratorMode) (((int)mode - 1 + 3) % 3); | |||
| generator.set_mode(mode); | |||
| } | |||
| lights[MODE_GREEN_LIGHT].value = (mode == 2) ? 1.0 : 0.0; | |||
| lights[MODE_RED_LIGHT].value = (mode == 0) ? 1.0 : 0.0; | |||
| tides::GeneratorRange range = generator.range(); | |||
| if (rangeTrigger.process(params[RANGE_PARAM].getValue())) { | |||
| range = (tides::GeneratorRange) (((int)range - 1 + 3) % 3); | |||
| generator.set_range(range); | |||
| } | |||
| lights[RANGE_GREEN_LIGHT].value = (range == 2) ? 1.0 : 0.0; | |||
| lights[RANGE_RED_LIGHT].value = (range == 0) ? 1.0 : 0.0; | |||
| // Buffer loop | |||
| if (++frame >= 16) { | |||
| frame = 0; | |||
| // Pitch | |||
| float pitch = params[FREQUENCY_PARAM].getValue(); | |||
| pitch += 12.0 * inputs[PITCH_INPUT].getVoltage(); | |||
| pitch += params[FM_PARAM].getValue() * inputs[FM_INPUT].getNormalVoltage(0.1) / 5.0; | |||
| pitch += 60.0; | |||
| // Scale to the global sample rate | |||
| pitch += log2f(48000.0 / args.sampleRate) * 12.0; | |||
| generator.set_pitch((int) clamp(pitch * 0x80, (float) -0x8000, (float) 0x7fff)); | |||
| // Slope, smoothness, pitch | |||
| int16_t shape = clamp(params[SHAPE_PARAM].getValue() + inputs[SHAPE_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f) * 0x7fff; | |||
| int16_t slope = clamp(params[SLOPE_PARAM].getValue() + inputs[SLOPE_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f) * 0x7fff; | |||
| int16_t smoothness = clamp(params[SMOOTHNESS_PARAM].getValue() + inputs[SMOOTHNESS_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f) * 0x7fff; | |||
| generator.set_shape(shape); | |||
| generator.set_slope(slope); | |||
| generator.set_smoothness(smoothness); | |||
| // Sync | |||
| // Slight deviation from spec here. | |||
| // Instead of toggling sync by holding the range button, just enable it if the clock port is plugged in. | |||
| generator.set_sync(inputs[CLOCK_INPUT].isConnected() && !sheep); | |||
| // Generator | |||
| generator.Process(sheep); | |||
| } | |||
| // Level | |||
| uint16_t level = clamp(inputs[LEVEL_INPUT].getNormalVoltage(8.0) / 8.0f, 0.0f, 1.0f) * 0xffff; | |||
| if (level < 32) | |||
| level = 0; | |||
| uint8_t gate = 0; | |||
| if (inputs[FREEZE_INPUT].getVoltage() >= 0.7) | |||
| gate |= tides::CONTROL_FREEZE; | |||
| if (inputs[TRIG_INPUT].getVoltage() >= 0.7) | |||
| gate |= tides::CONTROL_GATE; | |||
| if (inputs[CLOCK_INPUT].getVoltage() >= 0.7) | |||
| gate |= tides::CONTROL_CLOCK; | |||
| if (!(lastGate & tides::CONTROL_CLOCK) && (gate & tides::CONTROL_CLOCK)) | |||
| gate |= tides::CONTROL_GATE_RISING; | |||
| if (!(lastGate & tides::CONTROL_GATE) && (gate & tides::CONTROL_GATE)) | |||
| gate |= tides::CONTROL_GATE_RISING; | |||
| if ((lastGate & tides::CONTROL_GATE) && !(gate & tides::CONTROL_GATE)) | |||
| gate |= tides::CONTROL_GATE_FALLING; | |||
| lastGate = gate; | |||
| const tides::GeneratorSample& sample = generator.Process(gate); | |||
| uint32_t uni = sample.unipolar; | |||
| int32_t bi = sample.bipolar; | |||
| uni = uni * level >> 16; | |||
| bi = -bi * level >> 16; | |||
| float unif = (float) uni / 0xffff; | |||
| float bif = (float) bi / 0x8000; | |||
| outputs[HIGH_OUTPUT].setVoltage(sample.flags & tides::FLAG_END_OF_ATTACK ? 0.0 : 5.0); | |||
| outputs[LOW_OUTPUT].setVoltage(sample.flags & tides::FLAG_END_OF_RELEASE ? 0.0 : 5.0); | |||
| outputs[UNI_OUTPUT].setVoltage(unif * 8.0); | |||
| outputs[BI_OUTPUT].setVoltage(bif * 5.0); | |||
| if (sample.flags & tides::FLAG_END_OF_ATTACK) | |||
| unif *= -1.0; | |||
| lights[PHASE_GREEN_LIGHT].setSmoothBrightness(fmaxf(0.0, unif), args.sampleTime); | |||
| lights[PHASE_RED_LIGHT].setSmoothBrightness(fmaxf(0.0, -unif), args.sampleTime); | |||
| } | |||
| void onReset() override { | |||
| generator.set_range(tides::GENERATOR_RANGE_MEDIUM); | |||
| @@ -94,110 +193,6 @@ struct Tides : Module { | |||
| }; | |||
| Tides::Tides() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Tides::MODE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Tides::RANGE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Tides::FREQUENCY_PARAM, -48.0, 48.0, 0.0); | |||
| configParam(Tides::FM_PARAM, -12.0, 12.0, 0.0); | |||
| configParam(Tides::SHAPE_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Tides::SLOPE_PARAM, -1.0, 1.0, 0.0); | |||
| configParam(Tides::SMOOTHNESS_PARAM, -1.0, 1.0, 0.0); | |||
| memset(&generator, 0, sizeof(generator)); | |||
| generator.Init(); | |||
| generator.set_sync(false); | |||
| onReset(); | |||
| } | |||
| void Tides::process(const ProcessArgs &args) { | |||
| tides::GeneratorMode mode = generator.mode(); | |||
| if (modeTrigger.process(params[MODE_PARAM].getValue())) { | |||
| mode = (tides::GeneratorMode) (((int)mode - 1 + 3) % 3); | |||
| generator.set_mode(mode); | |||
| } | |||
| lights[MODE_GREEN_LIGHT].value = (mode == 2) ? 1.0 : 0.0; | |||
| lights[MODE_RED_LIGHT].value = (mode == 0) ? 1.0 : 0.0; | |||
| tides::GeneratorRange range = generator.range(); | |||
| if (rangeTrigger.process(params[RANGE_PARAM].getValue())) { | |||
| range = (tides::GeneratorRange) (((int)range - 1 + 3) % 3); | |||
| generator.set_range(range); | |||
| } | |||
| lights[RANGE_GREEN_LIGHT].value = (range == 2) ? 1.0 : 0.0; | |||
| lights[RANGE_RED_LIGHT].value = (range == 0) ? 1.0 : 0.0; | |||
| // Buffer loop | |||
| if (++frame >= 16) { | |||
| frame = 0; | |||
| // Pitch | |||
| float pitch = params[FREQUENCY_PARAM].getValue(); | |||
| pitch += 12.0 * inputs[PITCH_INPUT].getVoltage(); | |||
| pitch += params[FM_PARAM].getValue() * inputs[FM_INPUT].getNormalVoltage(0.1) / 5.0; | |||
| pitch += 60.0; | |||
| // Scale to the global sample rate | |||
| pitch += log2f(48000.0 / args.sampleRate) * 12.0; | |||
| generator.set_pitch((int) clamp(pitch * 0x80, (float) -0x8000, (float) 0x7fff)); | |||
| // Slope, smoothness, pitch | |||
| int16_t shape = clamp(params[SHAPE_PARAM].getValue() + inputs[SHAPE_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f) * 0x7fff; | |||
| int16_t slope = clamp(params[SLOPE_PARAM].getValue() + inputs[SLOPE_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f) * 0x7fff; | |||
| int16_t smoothness = clamp(params[SMOOTHNESS_PARAM].getValue() + inputs[SMOOTHNESS_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f) * 0x7fff; | |||
| generator.set_shape(shape); | |||
| generator.set_slope(slope); | |||
| generator.set_smoothness(smoothness); | |||
| // Sync | |||
| // Slight deviation from spec here. | |||
| // Instead of toggling sync by holding the range button, just enable it if the clock port is plugged in. | |||
| generator.set_sync(inputs[CLOCK_INPUT].isConnected() && !sheep); | |||
| // Generator | |||
| generator.Process(sheep); | |||
| } | |||
| // Level | |||
| uint16_t level = clamp(inputs[LEVEL_INPUT].getNormalVoltage(8.0) / 8.0f, 0.0f, 1.0f) * 0xffff; | |||
| if (level < 32) | |||
| level = 0; | |||
| uint8_t gate = 0; | |||
| if (inputs[FREEZE_INPUT].getVoltage() >= 0.7) | |||
| gate |= tides::CONTROL_FREEZE; | |||
| if (inputs[TRIG_INPUT].getVoltage() >= 0.7) | |||
| gate |= tides::CONTROL_GATE; | |||
| if (inputs[CLOCK_INPUT].getVoltage() >= 0.7) | |||
| gate |= tides::CONTROL_CLOCK; | |||
| if (!(lastGate & tides::CONTROL_CLOCK) && (gate & tides::CONTROL_CLOCK)) | |||
| gate |= tides::CONTROL_GATE_RISING; | |||
| if (!(lastGate & tides::CONTROL_GATE) && (gate & tides::CONTROL_GATE)) | |||
| gate |= tides::CONTROL_GATE_RISING; | |||
| if ((lastGate & tides::CONTROL_GATE) && !(gate & tides::CONTROL_GATE)) | |||
| gate |= tides::CONTROL_GATE_FALLING; | |||
| lastGate = gate; | |||
| const tides::GeneratorSample& sample = generator.Process(gate); | |||
| uint32_t uni = sample.unipolar; | |||
| int32_t bi = sample.bipolar; | |||
| uni = uni * level >> 16; | |||
| bi = -bi * level >> 16; | |||
| float unif = (float) uni / 0xffff; | |||
| float bif = (float) bi / 0x8000; | |||
| outputs[HIGH_OUTPUT].setVoltage(sample.flags & tides::FLAG_END_OF_ATTACK ? 0.0 : 5.0); | |||
| outputs[LOW_OUTPUT].setVoltage(sample.flags & tides::FLAG_END_OF_RELEASE ? 0.0 : 5.0); | |||
| outputs[UNI_OUTPUT].setVoltage(unif * 8.0); | |||
| outputs[BI_OUTPUT].setVoltage(bif * 5.0); | |||
| if (sample.flags & tides::FLAG_END_OF_ATTACK) | |||
| unif *= -1.0; | |||
| lights[PHASE_GREEN_LIGHT].setSmoothBrightness(fmaxf(0.0, unif), args.sampleTime); | |||
| lights[PHASE_RED_LIGHT].setSmoothBrightness(fmaxf(0.0, -unif), args.sampleTime); | |||
| } | |||
| struct TidesWidget : ModuleWidget { | |||
| SvgPanel *tidesPanel; | |||
| SvgPanel *sheepPanel; | |||
| @@ -50,31 +50,29 @@ struct Veils : Module { | |||
| configParam(Veils::RESPONSE3_PARAM, 0.0, 1.0, 1.0); | |||
| configParam(Veils::RESPONSE4_PARAM, 0.0, 1.0, 1.0); | |||
| } | |||
| void process(const ProcessArgs &args) override; | |||
| }; | |||
| void Veils::process(const ProcessArgs &args) { | |||
| float out = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| float in = inputs[IN1_INPUT + i].getVoltage() * params[GAIN1_PARAM + i].getValue(); | |||
| if (inputs[CV1_INPUT + i].isConnected()) { | |||
| float linear = fmaxf(inputs[CV1_INPUT + i].getVoltage() / 5.0, 0.0); | |||
| linear = clamp(linear, 0.0f, 2.0f); | |||
| const float base = 200.0; | |||
| float exponential = rescale(powf(base, linear / 2.0f), 1.0f, base, 0.0f, 10.0f); | |||
| in *= crossfade(exponential, linear, params[RESPONSE1_PARAM + i].getValue()); | |||
| } | |||
| out += in; | |||
| lights[OUT1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, out / 5.0), args.sampleTime); | |||
| lights[OUT1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -out / 5.0), args.sampleTime); | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(out); | |||
| out = 0.0; | |||
| void process(const ProcessArgs &args) { | |||
| float out = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| float in = inputs[IN1_INPUT + i].getVoltage() * params[GAIN1_PARAM + i].getValue(); | |||
| if (inputs[CV1_INPUT + i].isConnected()) { | |||
| float linear = fmaxf(inputs[CV1_INPUT + i].getVoltage() / 5.0, 0.0); | |||
| linear = clamp(linear, 0.0f, 2.0f); | |||
| const float base = 200.0; | |||
| float exponential = rescale(powf(base, linear / 2.0f), 1.0f, base, 0.0f, 10.0f); | |||
| in *= crossfade(exponential, linear, params[RESPONSE1_PARAM + i].getValue()); | |||
| } | |||
| out += in; | |||
| lights[OUT1_POS_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, out / 5.0), args.sampleTime); | |||
| lights[OUT1_NEG_LIGHT + 2*i].setSmoothBrightness(fmaxf(0.0, -out / 5.0), args.sampleTime); | |||
| if (outputs[OUT1_OUTPUT + i].isConnected()) { | |||
| outputs[OUT1_OUTPUT + i].setVoltage(out); | |||
| out = 0.0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| struct VeilsWidget : ModuleWidget { | |||
| @@ -38,8 +38,60 @@ struct Warps : Module { | |||
| warps::ShortFrame outputFrames[60] = {}; | |||
| dsp::SchmittTrigger stateTrigger; | |||
| Warps(); | |||
| void process(const ProcessArgs &args) override; | |||
| Warps() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Warps::ALGORITHM_PARAM, 0.0, 8.0, 0.0); | |||
| configParam(Warps::TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Warps::STATE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Warps::LEVEL1_PARAM, 0.0, 1.0, 1.0); | |||
| configParam(Warps::LEVEL2_PARAM, 0.0, 1.0, 1.0); | |||
| memset(&modulator, 0, sizeof(modulator)); | |||
| modulator.Init(96000.0f); | |||
| } | |||
| void process(const ProcessArgs &args) { | |||
| // State trigger | |||
| warps::Parameters *p = modulator.mutable_parameters(); | |||
| if (stateTrigger.process(params[STATE_PARAM].getValue())) { | |||
| p->carrier_shape = (p->carrier_shape + 1) % 4; | |||
| } | |||
| lights[CARRIER_GREEN_LIGHT].value = (p->carrier_shape == 1 || p->carrier_shape == 2) ? 1.0 : 0.0; | |||
| lights[CARRIER_RED_LIGHT].value = (p->carrier_shape == 2 || p->carrier_shape == 3) ? 1.0 : 0.0; | |||
| // Buffer loop | |||
| if (++frame >= 60) { | |||
| frame = 0; | |||
| p->channel_drive[0] = clamp(params[LEVEL1_PARAM].getValue() + inputs[LEVEL1_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->channel_drive[1] = clamp(params[LEVEL2_PARAM].getValue() + inputs[LEVEL2_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->modulation_algorithm = clamp(params[ALGORITHM_PARAM].getValue() / 8.0f + inputs[ALGORITHM_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| { | |||
| // TODO | |||
| // Use the correct light color | |||
| NVGcolor algorithmColor = nvgHSL(p->modulation_algorithm, 0.3, 0.4); | |||
| lights[ALGORITHM_LIGHT + 0].setBrightness(algorithmColor.r); | |||
| lights[ALGORITHM_LIGHT + 1].setBrightness(algorithmColor.g); | |||
| lights[ALGORITHM_LIGHT + 2].setBrightness(algorithmColor.b); | |||
| } | |||
| p->modulation_parameter = clamp(params[TIMBRE_PARAM].getValue() + inputs[TIMBRE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->frequency_shift_pot = params[ALGORITHM_PARAM].getValue() / 8.0; | |||
| p->frequency_shift_cv = clamp(inputs[ALGORITHM_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f); | |||
| p->phase_shift = p->modulation_algorithm; | |||
| p->note = 60.0 * params[LEVEL1_PARAM].getValue() + 12.0 * inputs[LEVEL1_INPUT].getNormalVoltage(2.0) + 12.0; | |||
| p->note += log2f(96000.0f * args.sampleTime) * 12.0f; | |||
| modulator.Process(inputFrames, outputFrames, 60); | |||
| } | |||
| inputFrames[frame].l = clamp((int) (inputs[CARRIER_INPUT].getVoltage() / 16.0 * 0x8000), -0x8000, 0x7fff); | |||
| inputFrames[frame].r = clamp((int) (inputs[MODULATOR_INPUT].getVoltage() / 16.0 * 0x8000), -0x8000, 0x7fff); | |||
| outputs[MODULATOR_OUTPUT].setVoltage((float)outputFrames[frame].l / 0x8000 * 5.0); | |||
| outputs[AUX_OUTPUT].setVoltage((float)outputFrames[frame].r / 0x8000 * 5.0); | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -68,62 +120,6 @@ struct Warps : Module { | |||
| }; | |||
| Warps::Warps() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configParam(Warps::ALGORITHM_PARAM, 0.0, 8.0, 0.0); | |||
| configParam(Warps::TIMBRE_PARAM, 0.0, 1.0, 0.5); | |||
| configParam(Warps::STATE_PARAM, 0.0, 1.0, 0.0); | |||
| configParam(Warps::LEVEL1_PARAM, 0.0, 1.0, 1.0); | |||
| configParam(Warps::LEVEL2_PARAM, 0.0, 1.0, 1.0); | |||
| memset(&modulator, 0, sizeof(modulator)); | |||
| modulator.Init(96000.0f); | |||
| } | |||
| void Warps::process(const ProcessArgs &args) { | |||
| // State trigger | |||
| warps::Parameters *p = modulator.mutable_parameters(); | |||
| if (stateTrigger.process(params[STATE_PARAM].getValue())) { | |||
| p->carrier_shape = (p->carrier_shape + 1) % 4; | |||
| } | |||
| lights[CARRIER_GREEN_LIGHT].value = (p->carrier_shape == 1 || p->carrier_shape == 2) ? 1.0 : 0.0; | |||
| lights[CARRIER_RED_LIGHT].value = (p->carrier_shape == 2 || p->carrier_shape == 3) ? 1.0 : 0.0; | |||
| // Buffer loop | |||
| if (++frame >= 60) { | |||
| frame = 0; | |||
| p->channel_drive[0] = clamp(params[LEVEL1_PARAM].getValue() + inputs[LEVEL1_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->channel_drive[1] = clamp(params[LEVEL2_PARAM].getValue() + inputs[LEVEL2_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->modulation_algorithm = clamp(params[ALGORITHM_PARAM].getValue() / 8.0f + inputs[ALGORITHM_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| { | |||
| // TODO | |||
| // Use the correct light color | |||
| NVGcolor algorithmColor = nvgHSL(p->modulation_algorithm, 0.3, 0.4); | |||
| lights[ALGORITHM_LIGHT + 0].setBrightness(algorithmColor.r); | |||
| lights[ALGORITHM_LIGHT + 1].setBrightness(algorithmColor.g); | |||
| lights[ALGORITHM_LIGHT + 2].setBrightness(algorithmColor.b); | |||
| } | |||
| p->modulation_parameter = clamp(params[TIMBRE_PARAM].getValue() + inputs[TIMBRE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); | |||
| p->frequency_shift_pot = params[ALGORITHM_PARAM].getValue() / 8.0; | |||
| p->frequency_shift_cv = clamp(inputs[ALGORITHM_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f); | |||
| p->phase_shift = p->modulation_algorithm; | |||
| p->note = 60.0 * params[LEVEL1_PARAM].getValue() + 12.0 * inputs[LEVEL1_INPUT].getNormalVoltage(2.0) + 12.0; | |||
| p->note += log2f(96000.0f * args.sampleTime) * 12.0f; | |||
| modulator.Process(inputFrames, outputFrames, 60); | |||
| } | |||
| inputFrames[frame].l = clamp((int) (inputs[CARRIER_INPUT].getVoltage() / 16.0 * 0x8000), -0x8000, 0x7fff); | |||
| inputFrames[frame].r = clamp((int) (inputs[MODULATOR_INPUT].getVoltage() / 16.0 * 0x8000), -0x8000, 0x7fff); | |||
| outputs[MODULATOR_OUTPUT].setVoltage((float)outputFrames[frame].l / 0x8000 * 5.0); | |||
| outputs[AUX_OUTPUT].setVoltage((float)outputFrames[frame].r / 0x8000 * 5.0); | |||
| } | |||
| struct AlgorithmLight : RedGreenBlueLight { | |||
| AlgorithmLight() { | |||
| box.size = Vec(71, 71); | |||