Browse Source

Macro Oscillator 2: Make polyphonic. Use separate "invisible" parameters for LPG response and decay, so they can be MIDI-mapped and generally behave better. Clean up parameter quantities.

tags/v1.1.0
Andrew Belt 5 years ago
parent
commit
7063288df2
1 changed files with 125 additions and 66 deletions
  1. +125
    -66
      src/Plaits.cpp

+ 125
- 66
src/Plaits.cpp View File

@@ -19,6 +19,8 @@ struct Plaits : Module {
TIMBRE_CV_PARAM, TIMBRE_CV_PARAM,
FREQ_CV_PARAM, FREQ_CV_PARAM,
MORPH_CV_PARAM, MORPH_CV_PARAM,
LPG_COLOR_PARAM,
LPG_DECAY_PARAM,
NUM_PARAMS NUM_PARAMS
}; };
enum InputIds { enum InputIds {
@@ -42,16 +44,14 @@ struct Plaits : Module {
NUM_LIGHTS NUM_LIGHTS
}; };


plaits::Voice voice;
plaits::Voice voice[16];
plaits::Patch patch = {}; plaits::Patch patch = {};
plaits::Modulations modulations = {};
char shared_buffer[16384] = {};
char shared_buffer[16][16384] = {};
float triPhase = 0.f; float triPhase = 0.f;


dsp::SampleRateConverter<2> outputSrc;
dsp::DoubleRingBuffer<dsp::Frame<2>, 256> outputBuffer;
dsp::SampleRateConverter<16 * 2> outputSrc;
dsp::DoubleRingBuffer<dsp::Frame<16 * 2>, 256> outputBuffer;
bool lowCpu = false; bool lowCpu = false;
bool lpg = false;


dsp::BooleanTrigger model1Trigger; dsp::BooleanTrigger model1Trigger;
dsp::BooleanTrigger model2Trigger; dsp::BooleanTrigger model2Trigger;
@@ -60,16 +60,20 @@ struct Plaits : Module {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam(MODEL1_PARAM, 0.0, 1.0, 0.0, "Model selection 1"); configParam(MODEL1_PARAM, 0.0, 1.0, 0.0, "Model selection 1");
configParam(MODEL2_PARAM, 0.0, 1.0, 0.0, "Model selection 2"); configParam(MODEL2_PARAM, 0.0, 1.0, 0.0, "Model selection 2");
configParam(FREQ_PARAM, -4.0, 4.0, 0.0, "Coarse frequency adjustment");
configParam(HARMONICS_PARAM, 0.0, 1.0, 0.5, "Harmonics");
configParam(TIMBRE_PARAM, 0.0, 1.0, 0.5, "Timbre");
configParam(MORPH_PARAM, 0.0, 1.0, 0.5, "Morph");
configParam(FREQ_PARAM, -4.0, 4.0, 0.0, "Frequency", " semitones", 0.f, 12.f);
configParam(HARMONICS_PARAM, 0.0, 1.0, 0.5, "Harmonics", "%", 0.f, 100.f);
configParam(TIMBRE_PARAM, 0.0, 1.0, 0.5, "Timbre", "%", 0.f, 100.f);
configParam(LPG_COLOR_PARAM, 0.0, 1.0, 0.5, "Lowpass gate response", "%", 0.f, 100.f);
configParam(MORPH_PARAM, 0.0, 1.0, 0.5, "Morph", "%", 0.f, 100.f);
configParam(LPG_DECAY_PARAM, 0.0, 1.0, 0.5, "Lowpass gate decay", "%", 0.f, 100.f);
configParam(TIMBRE_CV_PARAM, -1.0, 1.0, 0.0, "Timbre CV"); configParam(TIMBRE_CV_PARAM, -1.0, 1.0, 0.0, "Timbre CV");
configParam(FREQ_CV_PARAM, -1.0, 1.0, 0.0, "Frequency CV"); configParam(FREQ_CV_PARAM, -1.0, 1.0, 0.0, "Frequency CV");
configParam(MORPH_CV_PARAM, -1.0, 1.0, 0.0, "Morph CV"); configParam(MORPH_CV_PARAM, -1.0, 1.0, 0.0, "Morph CV");


stmlib::BufferAllocator allocator(shared_buffer, sizeof(shared_buffer));
voice.Init(&allocator);
for (int i = 0; i < 16; i++) {
stmlib::BufferAllocator allocator(shared_buffer[i], sizeof(shared_buffer[i]));
voice[i].Init(&allocator);
}


onReset(); onReset();
} }
@@ -89,8 +93,6 @@ struct Plaits : Module {


json_object_set_new(rootJ, "lowCpu", json_boolean(lowCpu)); json_object_set_new(rootJ, "lowCpu", json_boolean(lowCpu));
json_object_set_new(rootJ, "model", json_integer(patch.engine)); json_object_set_new(rootJ, "model", json_integer(patch.engine));
json_object_set_new(rootJ, "lpgColor", json_real(patch.lpg_colour));
json_object_set_new(rootJ, "decay", json_real(patch.decay));


return rootJ; return rootJ;
} }
@@ -104,16 +106,20 @@ struct Plaits : Module {
if (modelJ) if (modelJ)
patch.engine = json_integer_value(modelJ); patch.engine = json_integer_value(modelJ);


// Legacy <=1.0.2
json_t *lpgColorJ = json_object_get(rootJ, "lpgColor"); json_t *lpgColorJ = json_object_get(rootJ, "lpgColor");
if (lpgColorJ) if (lpgColorJ)
patch.lpg_colour = json_number_value(lpgColorJ);
params[LPG_COLOR_PARAM].setValue(json_number_value(lpgColorJ));


// Legacy <=1.0.2
json_t *decayJ = json_object_get(rootJ, "decay"); json_t *decayJ = json_object_get(rootJ, "decay");
if (decayJ) if (decayJ)
patch.decay = json_number_value(decayJ);
params[LPG_DECAY_PARAM].setValue(json_number_value(decayJ));
} }


void process(const ProcessArgs &args) override { void process(const ProcessArgs &args) override {
int channels = std::max(inputs[NOTE_INPUT].getChannels(), 1);

if (outputBuffer.empty()) { if (outputBuffer.empty()) {
const int blockSize = 12; const int blockSize = 12;


@@ -136,74 +142,91 @@ struct Plaits : Module {
} }


// Model lights // Model lights
int activeEngine = voice.active_engine();
// Pulse light at 2 Hz
triPhase += 2.f * args.sampleTime * blockSize; triPhase += 2.f * args.sampleTime * blockSize;
if (triPhase >= 1.f) if (triPhase >= 1.f)
triPhase -= 1.f; triPhase -= 1.f;
float tri = (triPhase < 0.5f) ? triPhase * 2.f : (1.f - triPhase) * 2.f; float tri = (triPhase < 0.5f) ? triPhase * 2.f : (1.f - triPhase) * 2.f;


for (int i = 0; i < 8; i++) {
lights[MODEL_LIGHT + 2*i + 0].setBrightness((activeEngine == i) ? 1.f : (patch.engine == i) ? tri : 0.f);
lights[MODEL_LIGHT + 2*i + 1].setBrightness((activeEngine == i + 8) ? 1.f : (patch.engine == i + 8) ? tri : 0.f);
// Get active engines of all voice channels
bool activeEngines[16] = {};
bool pulse = false;
for (int c = 0; c < channels; c++) {
int activeEngine = voice[c].active_engine();
activeEngines[activeEngine] = true;
// Pulse the light if at least one voice is using a different engine.
if (activeEngine != patch.engine)
pulse = true;
}

// Set model lights
for (int i = 0; i < 16; i++) {
// Transpose the [light][color] table
int lightId = (i % 8) * 2 + (i / 8);
float brightness = activeEngines[i];
if (patch.engine == i && pulse)
brightness = tri;
lights[MODEL_LIGHT + lightId].setBrightness(brightness);
} }


// Calculate pitch for lowCpu mode if needed // Calculate pitch for lowCpu mode if needed
float pitch = params[FREQ_PARAM].getValue(); float pitch = params[FREQ_PARAM].getValue();
if (lowCpu) if (lowCpu)
pitch += log2f(48000.f * args.sampleTime);
pitch += std::log2(48000.f * args.sampleTime);
// Update patch // Update patch
patch.note = 60.f + pitch * 12.f; patch.note = 60.f + pitch * 12.f;
patch.harmonics = params[HARMONICS_PARAM].getValue(); patch.harmonics = params[HARMONICS_PARAM].getValue();
if (!lpg) {
patch.timbre = params[TIMBRE_PARAM].getValue();
patch.morph = params[MORPH_PARAM].getValue();
}
else {
patch.lpg_colour = params[TIMBRE_PARAM].getValue();
patch.decay = params[MORPH_PARAM].getValue();
}
patch.timbre = params[TIMBRE_PARAM].getValue();
patch.morph = params[MORPH_PARAM].getValue();
patch.lpg_colour = params[LPG_COLOR_PARAM].getValue();
patch.decay = params[LPG_DECAY_PARAM].getValue();
patch.frequency_modulation_amount = params[FREQ_CV_PARAM].getValue(); patch.frequency_modulation_amount = params[FREQ_CV_PARAM].getValue();
patch.timbre_modulation_amount = params[TIMBRE_CV_PARAM].getValue(); patch.timbre_modulation_amount = params[TIMBRE_CV_PARAM].getValue();
patch.morph_modulation_amount = params[MORPH_CV_PARAM].getValue(); patch.morph_modulation_amount = params[MORPH_CV_PARAM].getValue();


// Update modulations
modulations.engine = inputs[ENGINE_INPUT].getVoltage() / 5.f;
modulations.note = inputs[NOTE_INPUT].getVoltage() * 12.f;
modulations.frequency = inputs[FREQ_INPUT].getVoltage() * 6.f;
modulations.harmonics = inputs[HARMONICS_INPUT].getVoltage() / 5.f;
modulations.timbre = inputs[TIMBRE_INPUT].getVoltage() / 8.f;
modulations.morph = inputs[MORPH_INPUT].getVoltage() / 8.f;
// Triggers at around 0.7 V
modulations.trigger = inputs[TRIGGER_INPUT].getVoltage() / 3.f;
modulations.level = inputs[LEVEL_INPUT].getVoltage() / 8.f;

modulations.frequency_patched = inputs[FREQ_INPUT].isConnected();
modulations.timbre_patched = inputs[TIMBRE_INPUT].isConnected();
modulations.morph_patched = inputs[MORPH_INPUT].isConnected();
modulations.trigger_patched = inputs[TRIGGER_INPUT].isConnected();
modulations.level_patched = inputs[LEVEL_INPUT].isConnected();

// Render frames
plaits::Voice::Frame output[blockSize];
voice.Render(patch, modulations, output, blockSize);

// Convert output to frames
dsp::Frame<2> outputFrames[blockSize];
for (int i = 0; i < blockSize; i++) {
outputFrames[i].samples[0] = output[i].out / 32768.f;
outputFrames[i].samples[1] = output[i].aux / 32768.f;
// Render output buffer for each voice
dsp::Frame<16 * 2> outputFrames[blockSize];
for (int c = 0; c < channels; c++) {
// Construct modulations
plaits::Modulations modulations;
modulations.engine = inputs[ENGINE_INPUT].getPolyVoltage(c) / 5.f;
modulations.note = inputs[NOTE_INPUT].getVoltage(c) * 12.f;
modulations.frequency = inputs[FREQ_INPUT].getPolyVoltage(c) * 6.f;
modulations.harmonics = inputs[HARMONICS_INPUT].getPolyVoltage(c) / 5.f;
modulations.timbre = inputs[TIMBRE_INPUT].getPolyVoltage(c) / 8.f;
modulations.morph = inputs[MORPH_INPUT].getPolyVoltage(c) / 8.f;
// Triggers at around 0.7 V
modulations.trigger = inputs[TRIGGER_INPUT].getPolyVoltage(c) / 3.f;
modulations.level = inputs[LEVEL_INPUT].getPolyVoltage(c) / 8.f;

modulations.frequency_patched = inputs[FREQ_INPUT].isConnected();
modulations.timbre_patched = inputs[TIMBRE_INPUT].isConnected();
modulations.morph_patched = inputs[MORPH_INPUT].isConnected();
modulations.trigger_patched = inputs[TRIGGER_INPUT].isConnected();
modulations.level_patched = inputs[LEVEL_INPUT].isConnected();

// Render frames
plaits::Voice::Frame output[blockSize];
voice[c].Render(patch, modulations, output, blockSize);

// Convert output to frames
for (int i = 0; i < blockSize; i++) {
outputFrames[i].samples[c * 2 + 0] = output[i].out / 32768.f;
outputFrames[i].samples[c * 2 + 1] = output[i].aux / 32768.f;
}
} }


// Convert output // Convert output
if (lowCpu) { if (lowCpu) {
int len = std::min((int) outputBuffer.capacity(), blockSize); int len = std::min((int) outputBuffer.capacity(), blockSize);
memcpy(outputBuffer.endData(), outputFrames, len * sizeof(dsp::Frame<2>));
std::memcpy(outputBuffer.endData(), outputFrames, len * sizeof(outputFrames[0]));
outputBuffer.endIncr(len); outputBuffer.endIncr(len);
} }
else { else {
outputSrc.setRates(48000, args.sampleRate);
outputSrc.setRates(48000, (int) args.sampleRate);
int inLen = blockSize; int inLen = blockSize;
int outLen = outputBuffer.capacity(); int outLen = outputBuffer.capacity();
outputSrc.setChannels(channels * 2);
outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen);
outputBuffer.endIncr(outLen); outputBuffer.endIncr(outLen);
} }
@@ -211,11 +234,15 @@ struct Plaits : Module {


// Set output // Set output
if (!outputBuffer.empty()) { if (!outputBuffer.empty()) {
dsp::Frame<2> outputFrame = outputBuffer.shift();
// Inverting op-amp on outputs
outputs[OUT_OUTPUT].setVoltage(-outputFrame.samples[0] * 5.f);
outputs[AUX_OUTPUT].setVoltage(-outputFrame.samples[1] * 5.f);
dsp::Frame<16 * 2> outputFrame = outputBuffer.shift();
for (int c = 0; c < channels; c++) {
// Inverting op-amp on outputs
outputs[OUT_OUTPUT].setVoltage(-outputFrame.samples[c * 2 + 0] * 5.f, c);
outputs[AUX_OUTPUT].setVoltage(-outputFrame.samples[c * 2 + 1] * 5.f, c);
}
} }
outputs[OUT_OUTPUT].setChannels(channels);
outputs[AUX_OUTPUT].setChannels(channels);
} }
}; };


@@ -241,6 +268,8 @@ static const std::string modelLabels[16] = {




struct PlaitsWidget : ModuleWidget { struct PlaitsWidget : ModuleWidget {
bool lpgMode = false;

PlaitsWidget(Plaits *module) { PlaitsWidget(Plaits *module) {
setModule(module); setModule(module);
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Plaits.svg"))); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Plaits.svg")));
@@ -260,6 +289,13 @@ struct PlaitsWidget : ModuleWidget {
addParam(createParam<Trimpot>(mm2px(Vec(27.2245, 77.60705)), module, Plaits::FREQ_CV_PARAM)); addParam(createParam<Trimpot>(mm2px(Vec(27.2245, 77.60705)), module, Plaits::FREQ_CV_PARAM));
addParam(createParam<Trimpot>(mm2px(Vec(46.56189, 77.60705)), module, Plaits::MORPH_CV_PARAM)); addParam(createParam<Trimpot>(mm2px(Vec(46.56189, 77.60705)), module, Plaits::MORPH_CV_PARAM));


ParamWidget* lpgColorParam = createParam<Rogan1PSBlue>(mm2px(Vec(4.04171, 49.6562)), module, Plaits::LPG_COLOR_PARAM);
lpgColorParam->hide();
addParam(lpgColorParam);
ParamWidget* decayParam = createParam<Rogan1PSBlue>(mm2px(Vec(42.71716, 49.6562)), module, Plaits::LPG_DECAY_PARAM);
decayParam->hide();
addParam(decayParam);

addInput(createInput<PJ301MPort>(mm2px(Vec(3.31381, 92.48067)), module, Plaits::ENGINE_INPUT)); addInput(createInput<PJ301MPort>(mm2px(Vec(3.31381, 92.48067)), module, Plaits::ENGINE_INPUT));
addInput(createInput<PJ301MPort>(mm2px(Vec(14.75983, 92.48067)), module, Plaits::TIMBRE_INPUT)); addInput(createInput<PJ301MPort>(mm2px(Vec(14.75983, 92.48067)), module, Plaits::TIMBRE_INPUT));
addInput(createInput<PJ301MPort>(mm2px(Vec(26.20655, 92.48067)), module, Plaits::FREQ_INPUT)); addInput(createInput<PJ301MPort>(mm2px(Vec(26.20655, 92.48067)), module, Plaits::FREQ_INPUT));
@@ -292,10 +328,10 @@ struct PlaitsWidget : ModuleWidget {
} }
}; };


struct PlaitsLPGItem : MenuItem {
Plaits *module;
struct PlaitsLpgModeItem : MenuItem {
PlaitsWidget *moduleWidget;
void onAction(const event::Action &e) override { void onAction(const event::Action &e) override {
module->lpg ^= true;
moduleWidget->setLpgMode(!moduleWidget->getLpgMode());
} }
}; };


@@ -311,8 +347,8 @@ struct PlaitsWidget : ModuleWidget {
PlaitsLowCpuItem *lowCpuItem = createMenuItem<PlaitsLowCpuItem>("Low CPU", CHECKMARK(module->lowCpu)); PlaitsLowCpuItem *lowCpuItem = createMenuItem<PlaitsLowCpuItem>("Low CPU", CHECKMARK(module->lowCpu));
lowCpuItem->module = module; lowCpuItem->module = module;
menu->addChild(lowCpuItem); menu->addChild(lowCpuItem);
PlaitsLPGItem *lpgItem = createMenuItem<PlaitsLPGItem>("Edit LPG response/decay", CHECKMARK(module->lpg));
lpgItem->module = module;
PlaitsLpgModeItem *lpgItem = createMenuItem<PlaitsLpgModeItem>("Edit LPG response/decay", CHECKMARK(getLpgMode()));
lpgItem->moduleWidget = this;
menu->addChild(lpgItem); menu->addChild(lpgItem);


menu->addChild(new MenuSeparator); menu->addChild(new MenuSeparator);
@@ -324,6 +360,29 @@ struct PlaitsWidget : ModuleWidget {
menu->addChild(modelItem); menu->addChild(modelItem);
} }
} }

void setLpgMode(bool lpgMode) {
// ModuleWidget::getParam() doesn't work if the ModuleWidget doesn't have a module.
if (!module)
return;
if (lpgMode) {
getParam(Plaits::MORPH_PARAM)->hide();
getParam(Plaits::TIMBRE_PARAM)->hide();
getParam(Plaits::LPG_DECAY_PARAM)->show();
getParam(Plaits::LPG_COLOR_PARAM)->show();
}
else {
getParam(Plaits::MORPH_PARAM)->show();
getParam(Plaits::TIMBRE_PARAM)->show();
getParam(Plaits::LPG_DECAY_PARAM)->hide();
getParam(Plaits::LPG_COLOR_PARAM)->hide();
}
this->lpgMode = lpgMode;
}

bool getLpgMode() {
return this->lpgMode;
}
}; };






Loading…
Cancel
Save