You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.3KB

  1. #include "plugin.hpp"
  2. struct Quantizer : Module {
  3. enum ParamIds {
  4. NUM_PARAMS
  5. };
  6. enum InputIds {
  7. PITCH_INPUT,
  8. NUM_INPUTS
  9. };
  10. enum OutputIds {
  11. PITCH_OUTPUT,
  12. NUM_OUTPUTS
  13. };
  14. enum LightIds {
  15. NUM_LIGHTS
  16. };
  17. bool enabledNotes[12];
  18. // Intervals [i / 24, (i+1) / 24) V mapping to the closest enabled note
  19. int ranges[24];
  20. bool playingNotes[12];
  21. Quantizer() {
  22. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  23. onReset();
  24. }
  25. void onReset() override {
  26. for (int i = 0; i < 12; i++) {
  27. enabledNotes[i] = true;
  28. }
  29. updateRanges();
  30. }
  31. void onRandomize() override {
  32. for (int i = 0; i < 12; i++) {
  33. enabledNotes[i] = (random::uniform() < 0.5f);
  34. }
  35. updateRanges();
  36. }
  37. void process(const ProcessArgs& args) override {
  38. bool playingNotes[12] = {};
  39. int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
  40. for (int c = 0; c < channels; c++) {
  41. float pitch = inputs[PITCH_INPUT].getVoltage(c);
  42. int range = std::floor(pitch * 24);
  43. int octave = eucDiv(range, 24);
  44. range -= octave * 24;
  45. int note = ranges[range] + octave * 12;
  46. playingNotes[eucMod(note, 12)] = true;
  47. pitch = float(note) / 12;
  48. outputs[PITCH_OUTPUT].setVoltage(pitch, c);
  49. }
  50. outputs[PITCH_OUTPUT].setChannels(channels);
  51. std::memcpy(this->playingNotes, playingNotes, sizeof(playingNotes));
  52. }
  53. void updateRanges() {
  54. // Check if no notes are enabled
  55. bool anyEnabled = false;
  56. for (int note = 0; note < 12; note++) {
  57. if (enabledNotes[note]) {
  58. anyEnabled = true;
  59. break;
  60. }
  61. }
  62. // Find closest notes for each range
  63. for (int i = 0; i < 24; i++) {
  64. int closestNote = 0;
  65. int closestDist = INT_MAX;
  66. for (int note = -12; note <= 24; note++) {
  67. int dist = std::abs((i + 1) / 2 - note);
  68. // Ignore enabled state if no notes are enabled
  69. if (anyEnabled && !enabledNotes[eucMod(note, 12)]) {
  70. continue;
  71. }
  72. if (dist < closestDist) {
  73. closestNote = note;
  74. closestDist = dist;
  75. }
  76. else {
  77. // If dist increases, we won't find a better one.
  78. break;
  79. }
  80. }
  81. ranges[i] = closestNote;
  82. }
  83. }
  84. json_t* dataToJson() override {
  85. json_t* rootJ = json_object();
  86. json_t* enabledNotesJ = json_array();
  87. for (int i = 0; i < 12; i++) {
  88. json_array_insert_new(enabledNotesJ, i, json_boolean(enabledNotes[i]));
  89. }
  90. json_object_set_new(rootJ, "enabledNotes", enabledNotesJ);
  91. return rootJ;
  92. }
  93. void dataFromJson(json_t* rootJ) override {
  94. json_t* enabledNotesJ = json_object_get(rootJ, "enabledNotes");
  95. if (enabledNotesJ) {
  96. for (int i = 0; i < 12; i++) {
  97. json_t* enabledNoteJ = json_array_get(enabledNotesJ, i);
  98. if (enabledNoteJ)
  99. enabledNotes[i] = json_boolean_value(enabledNoteJ);
  100. }
  101. }
  102. updateRanges();
  103. }
  104. };
  105. struct QuantizerButton : OpaqueWidget {
  106. int note;
  107. Quantizer* module;
  108. void draw(const DrawArgs& args) override {
  109. const float margin = mm2px(1.5);
  110. Rect r = box.zeroPos().grow(Vec(margin, margin / 2).neg());
  111. nvgBeginPath(args.vg);
  112. nvgRect(args.vg, RECT_ARGS(r));
  113. if (module ? module->playingNotes[note] : (note == 0)) {
  114. nvgFillColor(args.vg, nvgRGB(0xff, 0xd7, 0x14));
  115. }
  116. else if (module ? module->enabledNotes[note] : true) {
  117. nvgFillColor(args.vg, nvgRGB(0x7f, 0x6b, 0x0a));
  118. }
  119. else {
  120. nvgFillColor(args.vg, nvgRGB(0x40, 0x36, 0x05));
  121. }
  122. nvgFill(args.vg);
  123. }
  124. void onDragStart(const event::DragStart& e) override {
  125. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  126. module->enabledNotes[note] ^= true;
  127. module->updateRanges();
  128. }
  129. OpaqueWidget::onDragStart(e);
  130. }
  131. void onDragEnter(const event::DragEnter& e) override {
  132. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  133. QuantizerButton* origin = dynamic_cast<QuantizerButton*>(e.origin);
  134. if (origin) {
  135. module->enabledNotes[note] = module->enabledNotes[origin->note];;
  136. module->updateRanges();
  137. }
  138. }
  139. OpaqueWidget::onDragEnter(e);
  140. }
  141. };
  142. struct QuantizerDisplay : OpaqueWidget {
  143. void setModule(Quantizer* module) {
  144. const float margin = mm2px(1.5) / 2;
  145. box.size = mm2px(Vec(15.24, 72.0));
  146. const int notes = 12;
  147. const float height = box.size.y - 2 * margin;
  148. for (int note = 0; note < notes; note++) {
  149. QuantizerButton* quantizerButton = new QuantizerButton();
  150. quantizerButton->box.pos = Vec(0, margin + height / notes * note);
  151. quantizerButton->box.size = Vec(box.size.x, height / notes);
  152. quantizerButton->module = module;
  153. quantizerButton->note = note;
  154. addChild(quantizerButton);
  155. }
  156. }
  157. void draw(const DrawArgs& args) override {
  158. // Background
  159. nvgBeginPath(args.vg);
  160. nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
  161. nvgFillColor(args.vg, nvgRGB(0, 0, 0));
  162. nvgFill(args.vg);
  163. OpaqueWidget::draw(args);
  164. }
  165. };
  166. struct QuantizerWidget : ModuleWidget {
  167. QuantizerWidget(Quantizer* module) {
  168. setModule(module);
  169. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Quantizer.svg")));
  170. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  171. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  172. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.62, 97.253)), module, Quantizer::PITCH_INPUT));
  173. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.62, 112.253)), module, Quantizer::PITCH_OUTPUT));
  174. QuantizerDisplay* quantizerDisplay = createWidget<QuantizerDisplay>(mm2px(Vec(0.0, 14.585)));
  175. quantizerDisplay->setModule(module);
  176. addChild(quantizerDisplay);
  177. }
  178. };
  179. Model* modelQuantizer = createModel<Quantizer, QuantizerWidget>("Quantizer");