|
|
@@ -0,0 +1,265 @@ |
|
|
|
#include "plugin.hpp" |
|
|
|
|
|
|
|
|
|
|
|
struct SamplingModulator : Module { |
|
|
|
|
|
|
|
const static int numSteps = 8; |
|
|
|
|
|
|
|
enum ParamIds { |
|
|
|
RATE_PARAM, |
|
|
|
FINE_PARAM, |
|
|
|
INT_EXT_PARAM, |
|
|
|
ENUMS(STEP_PARAM, numSteps), |
|
|
|
NUM_PARAMS |
|
|
|
}; |
|
|
|
enum InputIds { |
|
|
|
SYNC_INPUT, |
|
|
|
VOCT_INPUT, |
|
|
|
HOLD_INPUT, |
|
|
|
IN_INPUT, |
|
|
|
NUM_INPUTS |
|
|
|
}; |
|
|
|
enum OutputIds { |
|
|
|
CLOCK_OUTPUT, |
|
|
|
TRIGG_OUTPUT, |
|
|
|
OUT_OUTPUT, |
|
|
|
NUM_OUTPUTS |
|
|
|
}; |
|
|
|
enum LightIds { |
|
|
|
ENUMS(STEP_LIGHT, numSteps), |
|
|
|
NUM_LIGHTS |
|
|
|
}; |
|
|
|
|
|
|
|
enum StepState { |
|
|
|
STATE_RESET, |
|
|
|
STATE_OFF, |
|
|
|
STATE_ON |
|
|
|
}; |
|
|
|
|
|
|
|
enum ClockMode { |
|
|
|
CLOCK_EXTERNAL, |
|
|
|
CLOCK_INTERNAL |
|
|
|
}; |
|
|
|
|
|
|
|
struct ClockTypeParam : ParamQuantity { |
|
|
|
std::string getDisplayValueString() override { |
|
|
|
if (module != nullptr && paramId == INT_EXT_PARAM) { |
|
|
|
return (module->params[INT_EXT_PARAM].getValue() == CLOCK_EXTERNAL) ? "External" : "Internal"; |
|
|
|
} |
|
|
|
else { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct StepTypeParam : ParamQuantity { |
|
|
|
std::string getDisplayValueString() override { |
|
|
|
if (module != nullptr && STEP_PARAM <= paramId && STEP_PARAM < STEP_PARAM_LAST) { |
|
|
|
StepState stepState = (StepState) module->params[paramId].getValue(); |
|
|
|
|
|
|
|
if (stepState == STATE_RESET) { |
|
|
|
return "Reset"; |
|
|
|
} |
|
|
|
else if (stepState == STATE_OFF) { |
|
|
|
return "Off"; |
|
|
|
} |
|
|
|
else { |
|
|
|
return "On"; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
int numEffectiveSteps = numSteps; |
|
|
|
int currentStep = 0; |
|
|
|
StepState stepStates[numSteps]; |
|
|
|
float triggerTime = 0; |
|
|
|
bool triggerActive = false; |
|
|
|
dsp::SchmittTrigger holdDetector; |
|
|
|
dsp::SchmittTrigger clock; |
|
|
|
dsp::MinBlepGenerator<16, 32> squareMinBlep; |
|
|
|
dsp::MinBlepGenerator<16, 32> triggMinBlep; |
|
|
|
dsp::MinBlepGenerator<16, 32> holdMinBlep; |
|
|
|
bool applyMinBlep = true; |
|
|
|
|
|
|
|
float stepPhase = 0.f; |
|
|
|
float heldValue = 0.f; |
|
|
|
/** Whether we are past the pulse width already */ |
|
|
|
bool halfPhase = false; |
|
|
|
|
|
|
|
SamplingModulator() { |
|
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
|
configParam(RATE_PARAM, 0.0f, 1.f, 0.f, "Rate"); |
|
|
|
configParam(FINE_PARAM, 0.f, 1.f, 0.f, "Fine tune"); |
|
|
|
configParam<ClockTypeParam>(INT_EXT_PARAM, 0.f, 1.f, CLOCK_INTERNAL, "Clock"); |
|
|
|
|
|
|
|
for (int i = 0; i < numSteps; i++) { |
|
|
|
configParam<StepTypeParam>(STEP_PARAM + i, 0.f, 2.f, STATE_ON, "Step " + std::to_string(i + 1)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void process(const ProcessArgs& args) override { |
|
|
|
bool advanceStep = false; |
|
|
|
holdDetector.process(rescale(inputs[HOLD_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f)); |
|
|
|
|
|
|
|
if (params[INT_EXT_PARAM].getValue() == CLOCK_EXTERNAL) { |
|
|
|
// if external mode, the SYNC/EXT. CLOCK input acts as a clock |
|
|
|
advanceStep = clock.process(rescale(inputs[SYNC_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f)); |
|
|
|
} else { |
|
|
|
// if internal mode, the SYNC/EXT. CLOCK input acts as a sync |
|
|
|
if (clock.process(rescale(inputs[SYNC_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) { |
|
|
|
currentStep = 0; |
|
|
|
stepPhase = 0.f; |
|
|
|
halfPhase = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
for (int i = 0; i < numSteps; i++) { |
|
|
|
stepStates[i] = (StepState) params[STEP_PARAM + i].getValue(); |
|
|
|
} |
|
|
|
numEffectiveSteps = 8; |
|
|
|
for (int i = 0; i < numSteps; i++) { |
|
|
|
if (stepStates[i] == STATE_RESET) { |
|
|
|
numEffectiveSteps = i; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const float pitch = 16.f * params[RATE_PARAM].getValue() + params[FINE_PARAM].getValue() + inputs[VOCT_INPUT].getVoltage(); |
|
|
|
const float frequency = 0.1 * simd::pow(2.f, pitch); |
|
|
|
|
|
|
|
float oldPhase = stepPhase; |
|
|
|
float deltaPhase = clamp(args.sampleTime * frequency, 1e-6f, 0.5f); |
|
|
|
stepPhase += deltaPhase; |
|
|
|
|
|
|
|
if (!halfPhase && stepPhase >= 0.5) { |
|
|
|
float crossing = -(stepPhase - 0.5) / deltaPhase; |
|
|
|
squareMinBlep.insertDiscontinuity(crossing, -2.f); |
|
|
|
halfPhase = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (stepPhase >= 1.0f) { |
|
|
|
stepPhase -= 1.0f; |
|
|
|
float crossing = -stepPhase / deltaPhase; |
|
|
|
squareMinBlep.insertDiscontinuity(crossing, +2.f); |
|
|
|
|
|
|
|
halfPhase = false; |
|
|
|
|
|
|
|
if (params[INT_EXT_PARAM].getValue() == CLOCK_INTERNAL) { |
|
|
|
advanceStep = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (triggerActive) { |
|
|
|
triggerTime -= args.sampleTime; |
|
|
|
if (triggerTime < 0) { |
|
|
|
triggMinBlep.insertDiscontinuity(triggerTime, -2.f); |
|
|
|
triggerTime = 0.; |
|
|
|
triggerActive = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (advanceStep) { |
|
|
|
// TODO: what does reset on first step do? |
|
|
|
currentStep = (currentStep + 1) % std::max(1, numEffectiveSteps); |
|
|
|
|
|
|
|
if (stepStates[currentStep] == STATE_ON) { |
|
|
|
|
|
|
|
float oldHeldValue = heldValue; |
|
|
|
heldValue = inputs[IN_INPUT].getVoltage();; |
|
|
|
triggerTime = 1e-3; |
|
|
|
triggerActive = true; |
|
|
|
|
|
|
|
float crossing = -(oldPhase + deltaPhase - 1.0) / deltaPhase; |
|
|
|
// TODO: i guess should only be on if clock is internal? |
|
|
|
triggMinBlep.insertDiscontinuity(crossing, +2.f); |
|
|
|
holdMinBlep.insertDiscontinuity(crossing, heldValue - oldHeldValue); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
float output = heldValue + holdMinBlep.process() * applyMinBlep; |
|
|
|
outputs[OUT_OUTPUT].setVoltage(output); |
|
|
|
|
|
|
|
// TODO: could calculate DC offset correction based on number of active bits |
|
|
|
float triggerOut = triggerActive ? +1.f : -1.f; |
|
|
|
triggerOut += triggMinBlep.process() * applyMinBlep; |
|
|
|
outputs[TRIGG_OUTPUT].setVoltage(5.f * triggerOut); |
|
|
|
|
|
|
|
float square = (stepPhase < 0.5) ? 1.f : -1.f; |
|
|
|
square += squareMinBlep.process() * applyMinBlep; |
|
|
|
outputs[CLOCK_OUTPUT].setVoltage(5.f * square); |
|
|
|
|
|
|
|
for (int i = 0; i < numSteps; i++) { |
|
|
|
lights[STEP_LIGHT + i].setBrightness(currentStep == i); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
struct SamplingModulatorWidget : ModuleWidget { |
|
|
|
SamplingModulatorWidget(SamplingModulator* module) { |
|
|
|
setModule(module); |
|
|
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SamplingModulator.svg"))); |
|
|
|
|
|
|
|
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); |
|
|
|
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); |
|
|
|
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
|
|
|
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
|
|
|
|
|
|
|
addParam(createParamCentered<Davies1900hWhiteKnob>(mm2px(Vec(9.72, 38.019)), module, SamplingModulator::RATE_PARAM)); |
|
|
|
addParam(createParamCentered<Davies1900hWhiteKnob>(mm2px(Vec(30.921, 38.019)), module, SamplingModulator::FINE_PARAM)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(20.313, 52.642)), module, SamplingModulator::INT_EXT_PARAM)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(8.319, 57.761)), module, SamplingModulator::STEP_PARAM + 0)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(8.319, 71.758)), module, SamplingModulator::STEP_PARAM + 1)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(8.319, 85.769)), module, SamplingModulator::STEP_PARAM + 2)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(8.319, 99.804)), module, SamplingModulator::STEP_PARAM + 3)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(32.326, 57.761)), module, SamplingModulator::STEP_PARAM + 4)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(32.326, 71.758)), module, SamplingModulator::STEP_PARAM + 5)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(32.326, 85.769)), module, SamplingModulator::STEP_PARAM + 6)); |
|
|
|
addParam(createParamCentered<BefacoSwitch>(mm2px(Vec(32.326, 99.804)), module, SamplingModulator::STEP_PARAM + 7)); |
|
|
|
|
|
|
|
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.426, 16.737)), module, SamplingModulator::SYNC_INPUT)); |
|
|
|
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(20.313, 28.175)), module, SamplingModulator::VOCT_INPUT)); |
|
|
|
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(20.342, 111.762)), module, SamplingModulator::HOLD_INPUT)); |
|
|
|
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.426, 114.484)), module, SamplingModulator::IN_INPUT)); |
|
|
|
|
|
|
|
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(20.313, 14.417)), module, SamplingModulator::CLOCK_OUTPUT)); |
|
|
|
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(33.224, 16.737)), module, SamplingModulator::TRIGG_OUTPUT)); |
|
|
|
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(33.224, 114.484)), module, SamplingModulator::OUT_OUTPUT)); |
|
|
|
|
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(16.921, 62.208)), module, SamplingModulator::STEP_LIGHT + 0)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(16.921, 73.011)), module, SamplingModulator::STEP_LIGHT + 1)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(16.921, 83.814)), module, SamplingModulator::STEP_LIGHT + 2)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(16.921, 94.617)), module, SamplingModulator::STEP_LIGHT + 3)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(23.722, 62.208)), module, SamplingModulator::STEP_LIGHT + 4)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(23.722, 73.011)), module, SamplingModulator::STEP_LIGHT + 5)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(23.722, 83.814)), module, SamplingModulator::STEP_LIGHT + 6)); |
|
|
|
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(23.722, 94.617)), module, SamplingModulator::STEP_LIGHT + 7)); |
|
|
|
} |
|
|
|
|
|
|
|
struct MinBLEPMenuItem : MenuItem { |
|
|
|
SamplingModulator* module; |
|
|
|
void onAction(const event::Action& e) override { |
|
|
|
module->applyMinBlep ^= true; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
void appendContextMenu(Menu* menu) override { |
|
|
|
SamplingModulator* module = dynamic_cast<SamplingModulator*>(this->module); |
|
|
|
assert(module); |
|
|
|
|
|
|
|
menu->addChild(new MenuSeparator()); |
|
|
|
|
|
|
|
MinBLEPMenuItem* minBlepItem = createMenuItem<MinBLEPMenuItem>("Apply minBlep", CHECKMARK(module->applyMinBlep)); |
|
|
|
minBlepItem->module = module; |
|
|
|
menu->addChild(minBlepItem); |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Model* modelSamplingModulator = createModel<SamplingModulator, SamplingModulatorWidget>("SamplingModulator"); |