|
- #include "plugin.hpp"
-
-
- struct Quantizer : Module {
- enum ParamIds {
- NUM_PARAMS
- };
- enum InputIds {
- PITCH_INPUT,
- NUM_INPUTS
- };
- enum OutputIds {
- PITCH_OUTPUT,
- NUM_OUTPUTS
- };
- enum LightIds {
- NUM_LIGHTS
- };
-
- bool enabledNotes[12];
- // Intervals [i / 24, (i+1) / 24) V mapping to the closest enabled note
- int ranges[24];
- bool playingNotes[12];
-
- Quantizer() {
- config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
- onReset();
- }
-
- void onReset() override {
- for (int i = 0; i < 12; i++) {
- enabledNotes[i] = true;
- }
- updateRanges();
- }
-
- void onRandomize() override {
- for (int i = 0; i < 12; i++) {
- enabledNotes[i] = (random::uniform() < 0.5f);
- }
- updateRanges();
- }
-
- void process(const ProcessArgs &args) override {
- bool playingNotes[12] = {};
- int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
-
- for (int c = 0; c < channels; c++) {
- float pitch = inputs[PITCH_INPUT].getVoltage(c);
- int range = std::floor(pitch * 24);
- int octave = eucDiv(range, 24);
- range -= octave * 24;
- int note = ranges[range] + octave * 12;
- playingNotes[eucMod(note, 12)] = true;
- pitch = float(note) / 12;
- outputs[PITCH_OUTPUT].setVoltage(pitch, c);
- }
- outputs[PITCH_OUTPUT].setChannels(channels);
- std::memcpy(this->playingNotes, playingNotes, sizeof(playingNotes));
- }
-
- void updateRanges() {
- // Check if no notes are enabled
- bool anyEnabled = false;
- for (int note = 0; note < 12; note++) {
- if (enabledNotes[note]) {
- anyEnabled = true;
- break;
- }
- }
- // Find closest notes for each range
- for (int i = 0; i < 24; i++) {
- int closestNote = 0;
- int closestDist = INT_MAX;
- for (int note = -12; note <= 24; note++) {
- int dist = std::abs((i + 1) / 2 - note);
- // Ignore enabled state if no notes are enabled
- if (anyEnabled && !enabledNotes[eucMod(note, 12)]) {
- continue;
- }
- if (dist < closestDist) {
- closestNote = note;
- closestDist = dist;
- }
- else {
- // If dist increases, we won't find a better one.
- break;
- }
- }
- ranges[i] = closestNote;
- }
- }
-
- json_t *dataToJson() override {
- json_t *rootJ = json_object();
-
- json_t *enabledNotesJ = json_array();
- for (int i = 0; i < 12; i++) {
- json_array_insert_new(enabledNotesJ, i, json_boolean(enabledNotes[i]));
- }
- json_object_set_new(rootJ, "enabledNotes", enabledNotesJ);
-
- return rootJ;
- }
-
- void dataFromJson(json_t *rootJ) override {
- json_t *enabledNotesJ = json_object_get(rootJ, "enabledNotes");
- if (enabledNotesJ) {
- for (int i = 0; i < 12; i++) {
- json_t *enabledNoteJ = json_array_get(enabledNotesJ, i);
- if (enabledNoteJ)
- enabledNotes[i] = json_boolean_value(enabledNoteJ);
- }
- }
- updateRanges();
- }
- };
-
-
- struct QuantizerButton : OpaqueWidget {
- int note;
- Quantizer *module;
-
- void draw(const DrawArgs &args) override {
- const float margin = mm2px(1.5);
- Rect r = box.zeroPos().grow(Vec(margin, margin / 2).neg());
- nvgBeginPath(args.vg);
- nvgRect(args.vg, RECT_ARGS(r));
- if (module ? module->playingNotes[note] : (note == 0)) {
- nvgFillColor(args.vg, nvgRGB(0xff, 0xd7, 0x14));
- }
- else if (module ? module->enabledNotes[note] : true) {
- nvgFillColor(args.vg, nvgRGB(0x7f, 0x6b, 0x0a));
- }
- else {
- nvgFillColor(args.vg, nvgRGB(0x40, 0x36, 0x05));
- }
- nvgFill(args.vg);
- }
-
- void onDragStart(const event::DragStart &e) override {
- if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
- module->enabledNotes[note] ^= true;
- module->updateRanges();
- }
- OpaqueWidget::onDragStart(e);
- }
-
- void onDragEnter(const event::DragEnter &e) override {
- if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
- QuantizerButton *origin = dynamic_cast<QuantizerButton*>(e.origin);
- if (origin) {
- module->enabledNotes[note] = module->enabledNotes[origin->note];;
- module->updateRanges();
- }
- }
- OpaqueWidget::onDragEnter(e);
- }
- };
-
-
- struct QuantizerDisplay : OpaqueWidget {
- void setModule(Quantizer *module) {
- const float margin = mm2px(1.5) / 2;
- box.size = mm2px(Vec(15.24, 72.0));
- const int notes = 12;
- const float height = box.size.y - 2 * margin;
- for (int note = 0; note < notes; note++) {
- QuantizerButton *quantizerButton = new QuantizerButton();
- quantizerButton->box.pos = Vec(0, margin + height / notes * note);
- quantizerButton->box.size = Vec(box.size.x, height / notes);
- quantizerButton->module = module;
- quantizerButton->note = note;
- addChild(quantizerButton);
- }
- }
-
- void draw(const DrawArgs &args) override {
- // Background
- nvgBeginPath(args.vg);
- nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
- nvgFillColor(args.vg, nvgRGB(0, 0, 0));
- nvgFill(args.vg);
-
- OpaqueWidget::draw(args);
- }
- };
-
-
- struct QuantizerWidget : ModuleWidget {
- QuantizerWidget(Quantizer *module) {
- setModule(module);
- setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Quantizer.svg")));
-
- addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.62, 97.253)), module, Quantizer::PITCH_INPUT));
-
- addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.62, 112.253)), module, Quantizer::PITCH_OUTPUT));
-
- QuantizerDisplay *quantizerDisplay = createWidget<QuantizerDisplay>(mm2px(Vec(0.0, 14.585)));
- quantizerDisplay->setModule(module);
- addChild(quantizerDisplay);
- }
- };
-
-
- Model *modelQuantizer = createModel<Quantizer, QuantizerWidget>("Quantizer");
|