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.

278 lines
7.6KB

  1. #include "plugin.hpp"
  2. struct Quantizer : Module {
  3. enum ParamIds {
  4. OFFSET_PARAM, // TODO
  5. NUM_PARAMS
  6. };
  7. enum InputIds {
  8. PITCH_INPUT,
  9. NUM_INPUTS
  10. };
  11. enum OutputIds {
  12. PITCH_OUTPUT,
  13. NUM_OUTPUTS
  14. };
  15. enum LightIds {
  16. NUM_LIGHTS
  17. };
  18. bool enabledNotes[12];
  19. // Intervals [i / 24, (i+1) / 24) V mapping to the closest enabled note
  20. int ranges[24];
  21. bool playingNotes[12];
  22. Quantizer() {
  23. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  24. configParam(OFFSET_PARAM, -1.f, 1.f, 0.f, "Pre-offset", " semitones", 0.f, 12.f);
  25. configInput(PITCH_INPUT, "1V/octave pitch");
  26. configOutput(PITCH_OUTPUT, "Pitch");
  27. configBypass(PITCH_INPUT, PITCH_OUTPUT);
  28. onReset();
  29. }
  30. void onReset() override {
  31. for (int i = 0; i < 12; i++) {
  32. enabledNotes[i] = true;
  33. }
  34. updateRanges();
  35. }
  36. void onRandomize() override {
  37. for (int i = 0; i < 12; i++) {
  38. enabledNotes[i] = (random::uniform() < 0.5f);
  39. }
  40. updateRanges();
  41. }
  42. void process(const ProcessArgs& args) override {
  43. bool playingNotes[12] = {};
  44. int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
  45. float offsetParam = params[OFFSET_PARAM].getValue();
  46. for (int c = 0; c < channels; c++) {
  47. float pitch = inputs[PITCH_INPUT].getVoltage(c);
  48. pitch += offsetParam;
  49. int range = std::floor(pitch * 24);
  50. int octave = eucDiv(range, 24);
  51. range -= octave * 24;
  52. int note = ranges[range] + octave * 12;
  53. playingNotes[eucMod(note, 12)] = true;
  54. pitch = float(note) / 12;
  55. outputs[PITCH_OUTPUT].setVoltage(pitch, c);
  56. }
  57. outputs[PITCH_OUTPUT].setChannels(channels);
  58. std::memcpy(this->playingNotes, playingNotes, sizeof(playingNotes));
  59. }
  60. void updateRanges() {
  61. // Check if no notes are enabled
  62. bool anyEnabled = false;
  63. for (int note = 0; note < 12; note++) {
  64. if (enabledNotes[note]) {
  65. anyEnabled = true;
  66. break;
  67. }
  68. }
  69. // Find closest notes for each range
  70. for (int i = 0; i < 24; i++) {
  71. int closestNote = 0;
  72. int closestDist = INT_MAX;
  73. for (int note = -12; note <= 24; note++) {
  74. int dist = std::abs((i + 1) / 2 - note);
  75. // Ignore enabled state if no notes are enabled
  76. if (anyEnabled && !enabledNotes[eucMod(note, 12)]) {
  77. continue;
  78. }
  79. if (dist < closestDist) {
  80. closestNote = note;
  81. closestDist = dist;
  82. }
  83. else {
  84. // If dist increases, we won't find a better one.
  85. break;
  86. }
  87. }
  88. ranges[i] = closestNote;
  89. }
  90. }
  91. void rotateNotes(int delta) {
  92. delta = eucMod(-delta, 12);
  93. std::rotate(&enabledNotes[0], &enabledNotes[delta], &enabledNotes[12]);
  94. updateRanges();
  95. }
  96. json_t* dataToJson() override {
  97. json_t* rootJ = json_object();
  98. json_t* enabledNotesJ = json_array();
  99. for (int i = 0; i < 12; i++) {
  100. json_array_insert_new(enabledNotesJ, i, json_boolean(enabledNotes[i]));
  101. }
  102. json_object_set_new(rootJ, "enabledNotes", enabledNotesJ);
  103. return rootJ;
  104. }
  105. void dataFromJson(json_t* rootJ) override {
  106. json_t* enabledNotesJ = json_object_get(rootJ, "enabledNotes");
  107. if (enabledNotesJ) {
  108. for (int i = 0; i < 12; i++) {
  109. json_t* enabledNoteJ = json_array_get(enabledNotesJ, i);
  110. if (enabledNoteJ)
  111. enabledNotes[i] = json_boolean_value(enabledNoteJ);
  112. }
  113. }
  114. updateRanges();
  115. }
  116. };
  117. struct QuantizerButton : OpaqueWidget {
  118. int note;
  119. Quantizer* module;
  120. void drawLayer(const DrawArgs& args, int layer) override {
  121. if (layer != 1)
  122. return;
  123. Rect r = box.zeroPos();
  124. const float margin = mm2px(1.0);
  125. Rect rMargin = r.grow(Vec(margin, margin));
  126. nvgBeginPath(args.vg);
  127. nvgRect(args.vg, RECT_ARGS(rMargin));
  128. nvgFillColor(args.vg, nvgRGB(0x12, 0x12, 0x12));
  129. nvgFill(args.vg);
  130. nvgBeginPath(args.vg);
  131. nvgRect(args.vg, RECT_ARGS(r));
  132. if (module ? module->playingNotes[note] : (note == 0)) {
  133. nvgFillColor(args.vg, SCHEME_YELLOW);
  134. }
  135. else if (module ? module->enabledNotes[note] : true) {
  136. nvgFillColor(args.vg, nvgRGB(0x7f, 0x6b, 0x0a));
  137. }
  138. else {
  139. nvgFillColor(args.vg, nvgRGB(0x40, 0x40, 0x40));
  140. }
  141. nvgFill(args.vg);
  142. }
  143. void onDragStart(const event::DragStart& e) override {
  144. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  145. module->enabledNotes[note] ^= true;
  146. module->updateRanges();
  147. }
  148. OpaqueWidget::onDragStart(e);
  149. }
  150. void onDragEnter(const event::DragEnter& e) override {
  151. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  152. QuantizerButton* origin = dynamic_cast<QuantizerButton*>(e.origin);
  153. if (origin) {
  154. module->enabledNotes[note] = module->enabledNotes[origin->note];;
  155. module->updateRanges();
  156. }
  157. }
  158. OpaqueWidget::onDragEnter(e);
  159. }
  160. };
  161. struct QuantizerDisplay : LedDisplay {
  162. void setModule(Quantizer* module) {
  163. std::vector<Vec> noteAbsPositions = {
  164. mm2px(Vec(2.242, 60.54)),
  165. mm2px(Vec(2.242, 58.416)),
  166. mm2px(Vec(2.242, 52.043)),
  167. mm2px(Vec(2.242, 49.919)),
  168. mm2px(Vec(2.242, 45.67)),
  169. mm2px(Vec(2.242, 39.298)),
  170. mm2px(Vec(2.242, 37.173)),
  171. mm2px(Vec(2.242, 30.801)),
  172. mm2px(Vec(2.242, 28.677)),
  173. mm2px(Vec(2.242, 22.304)),
  174. mm2px(Vec(2.242, 20.18)),
  175. mm2px(Vec(2.242, 15.931)),
  176. };
  177. std::vector<Vec> noteSizes = {
  178. mm2px(Vec(10.734, 5.644)),
  179. mm2px(Vec(8.231, 3.52)),
  180. mm2px(Vec(10.734, 7.769)),
  181. mm2px(Vec(8.231, 3.52)),
  182. mm2px(Vec(10.734, 5.644)),
  183. mm2px(Vec(10.734, 5.644)),
  184. mm2px(Vec(8.231, 3.52)),
  185. mm2px(Vec(10.734, 7.769)),
  186. mm2px(Vec(8.231, 3.52)),
  187. mm2px(Vec(10.734, 7.768)),
  188. mm2px(Vec(8.231, 3.52)),
  189. mm2px(Vec(10.734, 5.644)),
  190. };
  191. // White notes
  192. static const std::vector<int> whiteNotes = {0, 2, 4, 5, 7, 9, 11};
  193. for (int note : whiteNotes) {
  194. QuantizerButton* quantizerButton = new QuantizerButton();
  195. quantizerButton->box.pos = noteAbsPositions[note] - box.pos;
  196. quantizerButton->box.size = noteSizes[note];
  197. quantizerButton->module = module;
  198. quantizerButton->note = note;
  199. addChild(quantizerButton);
  200. }
  201. // Black notes
  202. static const std::vector<int> blackNotes = {1, 3, 6, 8, 10};
  203. for (int note : blackNotes) {
  204. QuantizerButton* quantizerButton = new QuantizerButton();
  205. quantizerButton->box.pos = noteAbsPositions[note] - box.pos;
  206. quantizerButton->box.size = noteSizes[note];
  207. quantizerButton->module = module;
  208. quantizerButton->note = note;
  209. addChild(quantizerButton);
  210. }
  211. }
  212. };
  213. struct QuantizerWidget : ModuleWidget {
  214. QuantizerWidget(Quantizer* module) {
  215. setModule(module);
  216. setPanel(createPanel(asset::plugin(pluginInstance, "res/Quantizer.svg"), asset::plugin(pluginInstance, "res/Quantizer-dark.svg")));
  217. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  218. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  219. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  220. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  221. addParam(createParamCentered<RoundBlackKnob>(mm2px(Vec(7.62, 80.551)), module, Quantizer::OFFSET_PARAM));
  222. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 96.859)), module, Quantizer::PITCH_INPUT));
  223. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 113.115)), module, Quantizer::PITCH_OUTPUT));
  224. QuantizerDisplay* quantizerDisplay = createWidget<QuantizerDisplay>(mm2px(Vec(0.0, 13.039)));
  225. quantizerDisplay->box.size = mm2px(Vec(15.24, 55.88));
  226. quantizerDisplay->setModule(module);
  227. addChild(quantizerDisplay);
  228. }
  229. void appendContextMenu(Menu* menu) override {
  230. Quantizer* module = getModule<Quantizer>();
  231. menu->addChild(new MenuSeparator);
  232. menu->addChild(createMenuItem("Shift notes up", "", [=]() {
  233. module->rotateNotes(1);
  234. }));
  235. menu->addChild(createMenuItem("Shift notes down", "", [=]() {
  236. module->rotateNotes(-1);
  237. }));
  238. }
  239. };
  240. Model* modelQuantizer = createModel<Quantizer, QuantizerWidget>("Quantizer");