Browse Source

Make all methods class-inline.

tags/v1.0.1
Andrew Belt 6 years ago
parent
commit
2b302795ad
13 changed files with 976 additions and 1017 deletions
  1. +17
    -19
      src/Blinds.cpp
  2. +84
    -85
      src/Braids.cpp
  3. +38
    -41
      src/Branches.cpp
  4. +158
    -164
      src/Clouds.cpp
  5. +133
    -138
      src/Elements.cpp
  6. +151
    -156
      src/Frames.cpp
  7. +29
    -30
      src/Kinks.cpp
  8. +22
    -23
      src/Links.cpp
  9. +148
    -152
      src/Rings.cpp
  10. +21
    -23
      src/Shades.cpp
  11. +101
    -106
      src/Tides.cpp
  12. +20
    -22
      src/Veils.cpp
  13. +54
    -58
      src/Warps.cpp

+ 17
- 19
src/Blinds.cpp View File

@@ -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 {


+ 84
- 85
src/Braids.cpp View File

@@ -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[] = {


+ 38
- 41
src/Branches.cpp View File

@@ -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);


+ 158
- 164
src/Clouds.cpp View File

@@ -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);


+ 133
- 138
src/Elements.cpp View File

@@ -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;


+ 151
- 156
src/Frames.cpp View File

@@ -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")));


+ 29
- 30
src/Kinks.cpp View File

@@ -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 {


+ 22
- 23
src/Links.cpp View File

@@ -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 {


+ 148
- 152
src/Rings.cpp View File

@@ -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);


+ 21
- 23
src/Shades.cpp View File

@@ -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 {


+ 101
- 106
src/Tides.cpp View File

@@ -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;


+ 20
- 22
src/Veils.cpp View File

@@ -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 {


+ 54
- 58
src/Warps.cpp View File

@@ -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);


Loading…
Cancel
Save