|
@@ -70,13 +70,13 @@ struct Elements : Module { |
|
|
NUM_LIGHTS |
|
|
NUM_LIGHTS |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
dsp::SampleRateConverter<2> inputSrc; |
|
|
|
|
|
dsp::SampleRateConverter<2> outputSrc; |
|
|
|
|
|
dsp::DoubleRingBuffer<dsp::Frame<2>, 256> inputBuffer; |
|
|
|
|
|
dsp::DoubleRingBuffer<dsp::Frame<2>, 256> outputBuffer; |
|
|
|
|
|
|
|
|
dsp::SampleRateConverter<16 * 2> inputSrc; |
|
|
|
|
|
dsp::SampleRateConverter<16 * 2> outputSrc; |
|
|
|
|
|
dsp::DoubleRingBuffer<dsp::Frame<16 * 2>, 256> inputBuffer; |
|
|
|
|
|
dsp::DoubleRingBuffer<dsp::Frame<16 * 2>, 256> outputBuffer; |
|
|
|
|
|
|
|
|
uint16_t reverb_buffer[32768] = {}; |
|
|
|
|
|
elements::Part* part; |
|
|
|
|
|
|
|
|
uint16_t reverb_buffers[16][32768] = {}; |
|
|
|
|
|
elements::Part* parts[16]; |
|
|
|
|
|
|
|
|
Elements() { |
|
|
Elements() { |
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
@@ -109,107 +109,144 @@ struct Elements : Module { |
|
|
configParam(SPACE_MOD_PARAM, -2.0, 2.0, 0.0, "Reverb space attenuverter"); |
|
|
configParam(SPACE_MOD_PARAM, -2.0, 2.0, 0.0, "Reverb space attenuverter"); |
|
|
configParam(PLAY_PARAM, 0.0, 1.0, 0.0, "Play"); |
|
|
configParam(PLAY_PARAM, 0.0, 1.0, 0.0, "Play"); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
for (int c = 0; c < 16; c++) { |
|
|
|
|
|
parts[c] = new elements::Part(); |
|
|
|
|
|
// In the Mutable Instruments code, Part doesn't initialize itself, so zero it here. |
|
|
|
|
|
std::memset(parts[c], 0, sizeof(*parts[c])); |
|
|
|
|
|
parts[c]->Init(reverb_buffers[c]); |
|
|
|
|
|
// Just some random numbers |
|
|
|
|
|
uint32_t seed[3] = {1, 2, 3}; |
|
|
|
|
|
parts[c]->Seed(seed, 3); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
~Elements() { |
|
|
~Elements() { |
|
|
delete part; |
|
|
|
|
|
|
|
|
for (int c = 0; c < 16; c++) { |
|
|
|
|
|
delete parts[c]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void onReset() override { |
|
|
|
|
|
setModel(0); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void process(const ProcessArgs& args) override { |
|
|
void process(const ProcessArgs& args) override { |
|
|
|
|
|
int channels = std::max(inputs[NOTE_INPUT].getChannels(), 1); |
|
|
|
|
|
|
|
|
// Get input |
|
|
// Get input |
|
|
if (!inputBuffer.full()) { |
|
|
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; |
|
|
|
|
|
|
|
|
dsp::Frame<16 * 2> inputFrame = {}; |
|
|
|
|
|
for (int c = 0; c < channels; c++) { |
|
|
|
|
|
inputFrame.samples[c * 2 + 0] = inputs[BLOW_INPUT].getPolyVoltage(c) / 5.0; |
|
|
|
|
|
inputFrame.samples[c * 2 + 1] = inputs[STRIKE_INPUT].getPolyVoltage(c) / 5.0; |
|
|
|
|
|
} |
|
|
inputBuffer.push(inputFrame); |
|
|
inputBuffer.push(inputFrame); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Render frames |
|
|
|
|
|
|
|
|
// Generate output if output buffer is empty |
|
|
if (outputBuffer.empty()) { |
|
|
if (outputBuffer.empty()) { |
|
|
float blow[16] = {}; |
|
|
|
|
|
float strike[16] = {}; |
|
|
|
|
|
float main[16]; |
|
|
|
|
|
float aux[16]; |
|
|
|
|
|
|
|
|
// blow[channel][bufferIndex] |
|
|
|
|
|
float blow[16][16] = {}; |
|
|
|
|
|
float strike[16][16] = {}; |
|
|
|
|
|
|
|
|
// Convert input buffer |
|
|
// Convert input buffer |
|
|
{ |
|
|
{ |
|
|
inputSrc.setRates(args.sampleRate, 32000); |
|
|
inputSrc.setRates(args.sampleRate, 32000); |
|
|
dsp::Frame<2> inputFrames[16]; |
|
|
|
|
|
|
|
|
inputSrc.setChannels(channels * 2); |
|
|
int inLen = inputBuffer.size(); |
|
|
int inLen = inputBuffer.size(); |
|
|
int outLen = 16; |
|
|
int outLen = 16; |
|
|
|
|
|
dsp::Frame<16 * 2> inputFrames[outLen]; |
|
|
inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); |
|
|
inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); |
|
|
inputBuffer.startIncr(inLen); |
|
|
inputBuffer.startIncr(inLen); |
|
|
|
|
|
|
|
|
for (int i = 0; i < outLen; i++) { |
|
|
|
|
|
blow[i] = inputFrames[i].samples[0]; |
|
|
|
|
|
strike[i] = inputFrames[i].samples[1]; |
|
|
|
|
|
|
|
|
for (int c = 0; c < channels; c++) { |
|
|
|
|
|
for (int i = 0; i < outLen; i++) { |
|
|
|
|
|
blow[c][i] = inputFrames[i].samples[c * 2 + 0]; |
|
|
|
|
|
strike[c][i] = inputFrames[i].samples[c * 2 + 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); |
|
|
|
|
|
|
|
|
// Process channels |
|
|
|
|
|
// main[channel][bufferIndex] |
|
|
|
|
|
float main[16][16]; |
|
|
|
|
|
float aux[16][16]; |
|
|
|
|
|
float gateLight = 0.f; |
|
|
|
|
|
float exciterLight = 0.f; |
|
|
|
|
|
float resonatorLight = 0.f; |
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < channels; c++) { |
|
|
|
|
|
// Set patch from parameters |
|
|
|
|
|
elements::Patch* p = parts[c]->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].getPolyVoltage(c) / 5.f, 0.f, 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].getPolyVoltage(c) / 5.f, 0.f, 2.f); |
|
|
|
|
|
|
|
|
|
|
|
// Get performance inputs |
|
|
|
|
|
elements::PerformanceState performance; |
|
|
|
|
|
performance.note = 12.f * inputs[NOTE_INPUT].getVoltage(c) + std::round(params[COARSE_PARAM].getValue()) + params[FINE_PARAM].getValue() + 69.f; |
|
|
|
|
|
performance.modulation = 3.3f * dsp::quarticBipolar(params[FM_PARAM].getValue()) * 49.5f * inputs[FM_INPUT].getPolyVoltage(c) / 5.f; |
|
|
|
|
|
performance.gate = params[PLAY_PARAM].getValue() >= 1.f || inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; |
|
|
|
|
|
performance.strength = clamp(1.f - inputs[STRENGTH_INPUT].getPolyVoltage(c) / 5.f, 0.f, 1.f); |
|
|
|
|
|
|
|
|
|
|
|
// Generate audio |
|
|
|
|
|
parts[c]->Process(performance, blow[c], strike[c], main[c], aux[c], 16); |
|
|
|
|
|
|
|
|
|
|
|
// Set lights based on first poly channel |
|
|
|
|
|
gateLight = std::max(gateLight, performance.gate ? 0.75f : 0.f); |
|
|
|
|
|
exciterLight = std::max(exciterLight, parts[c]->exciter_level()); |
|
|
|
|
|
resonatorLight = std::max(resonatorLight, parts[c]->resonator_level()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Set lights |
|
|
|
|
|
lights[GATE_LIGHT].setBrightness(gateLight); |
|
|
|
|
|
lights[EXCITER_LIGHT].setBrightness(exciterLight); |
|
|
|
|
|
lights[RESONATOR_LIGHT].setBrightness(resonatorLight); |
|
|
|
|
|
|
|
|
// Convert output buffer |
|
|
// 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]; |
|
|
|
|
|
|
|
|
dsp::Frame<16 * 2> outputFrames[16]; |
|
|
|
|
|
for (int c = 0; c < channels; c++) { |
|
|
|
|
|
for (int i = 0; i < 16; i++) { |
|
|
|
|
|
outputFrames[i].samples[c * 2 + 0] = main[c][i]; |
|
|
|
|
|
outputFrames[i].samples[c * 2 + 1] = aux[c][i]; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
outputSrc.setRates(32000, args.sampleRate); |
|
|
outputSrc.setRates(32000, args.sampleRate); |
|
|
|
|
|
outputSrc.setChannels(channels * 2); |
|
|
int inLen = 16; |
|
|
int inLen = 16; |
|
|
int outLen = outputBuffer.capacity(); |
|
|
int outLen = outputBuffer.capacity(); |
|
|
outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); |
|
|
outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); |
|
|
outputBuffer.endIncr(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 |
|
|
// Set output |
|
|
if (!outputBuffer.empty()) { |
|
|
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]); |
|
|
|
|
|
|
|
|
dsp::Frame<16 * 2> outputFrame = outputBuffer.shift(); |
|
|
|
|
|
for (int c = 0; c < channels; c++) { |
|
|
|
|
|
outputs[AUX_OUTPUT].setVoltage(5.f * outputFrame.samples[c * 2 + 0], c); |
|
|
|
|
|
outputs[MAIN_OUTPUT].setVoltage(5.f * outputFrame.samples[c * 2 + 1], c); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
outputs[AUX_OUTPUT].setChannels(channels); |
|
|
|
|
|
outputs[MAIN_OUTPUT].setChannels(channels); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
json_t* dataToJson() override { |
|
|
json_t* dataToJson() override { |
|
@@ -226,9 +263,10 @@ struct Elements : Module { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
int getModel() { |
|
|
int getModel() { |
|
|
if (part->easter_egg()) |
|
|
|
|
|
|
|
|
// Use the first channel's Part as the reference model |
|
|
|
|
|
if (parts[0]->easter_egg()) |
|
|
return -1; |
|
|
return -1; |
|
|
return (int) part->resonator_model(); |
|
|
|
|
|
|
|
|
return (int) parts[0]->resonator_model(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** Sets the resonator model. |
|
|
/** Sets the resonator model. |
|
@@ -236,11 +274,15 @@ struct Elements : Module { |
|
|
*/ |
|
|
*/ |
|
|
void setModel(int model) { |
|
|
void setModel(int model) { |
|
|
if (model < 0) { |
|
|
if (model < 0) { |
|
|
part->set_easter_egg(true); |
|
|
|
|
|
|
|
|
for (int c = 0; c < 16; c++) { |
|
|
|
|
|
parts[c]->set_easter_egg(true); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
|
part->set_easter_egg(false); |
|
|
|
|
|
part->set_resonator_model((elements::ResonatorModel) model); |
|
|
|
|
|
|
|
|
for (int c = 0; c < 16; c++) { |
|
|
|
|
|
parts[c]->set_easter_egg(false); |
|
|
|
|
|
parts[c]->set_resonator_model((elements::ResonatorModel) model); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|