Browse Source

Added Sampling Modulator prototype

tags/v2.1.0
hemmer 3 years ago
parent
commit
98e2fddef0
5 changed files with 1342 additions and 0 deletions
  1. +12
    -0
      plugin.json
  2. +1063
    -0
      res/SamplingModulator.svg
  3. +265
    -0
      src/SamplingModulator.cpp
  4. +1
    -0
      src/plugin.cpp
  5. +1
    -0
      src/plugin.hpp

+ 12
- 0
plugin.json View File

@@ -151,6 +151,18 @@
"Hardware clone",
"Synth voice"
]
},
{
"slug": "SamplingModulator",
"name": "Sampling Modulator",
"description": "Multi-function module that lies somewhere between a VCO, a Sample & Hold, and an 8 step trigger sequencer",
"manualUrl": "https://www.befaco.org/sampling-modulator/",
"modularGridUrl": "https://www.modulargrid.net/e/befaco-sampling-modulator-",
"tags": [
"Sample and hold",
"Oscillator",
"Clock generator"
]
}
]
}

+ 1063
- 0
res/SamplingModulator.svg
File diff suppressed because it is too large
View File


+ 265
- 0
src/SamplingModulator.cpp View File

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

+ 1
- 0
src/plugin.cpp View File

@@ -17,4 +17,5 @@ void init(rack::Plugin *p) {
p->addModel(modelHexmixVCA);
p->addModel(modelChoppingKinky);
p->addModel(modelKickall);
p->addModel(modelSamplingModulator);
}

+ 1
- 0
src/plugin.hpp View File

@@ -17,6 +17,7 @@ extern Model *modelPercall;
extern Model *modelHexmixVCA;
extern Model *modelChoppingKinky;
extern Model *modelKickall;
extern Model *modelSamplingModulator;


struct Knurlie : SvgScrew {


Loading…
Cancel
Save