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.

161 lines
4.8KB

  1. #include "aepelzen.hpp"
  2. #include "dsp/digital.hpp"
  3. #include <math.h>
  4. #define NUM_CHANNELS 4
  5. struct Erwin : Module {
  6. enum ParamIds {
  7. CHANNEL_TRANSPOSE_PARAM,
  8. NOTE_PARAM = CHANNEL_TRANSPOSE_PARAM + NUM_CHANNELS,
  9. NUM_PARAMS = NOTE_PARAM + 12
  10. };
  11. enum InputIds {
  12. TRANSPOSE_INPUT,
  13. SEMI_INPUT,
  14. IN_INPUT,
  15. NUM_INPUTS = IN_INPUT + 4
  16. };
  17. enum OutputIds {
  18. OUT_OUTPUT,
  19. NUM_OUTPUTS = OUT_OUTPUT + 4
  20. };
  21. enum LightIds {
  22. NOTE_LIGHT,
  23. NUM_LIGHTS = NOTE_LIGHT + 12
  24. };
  25. Erwin() : Module( NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { reset(); };
  26. void step() override;
  27. json_t* toJson() override;
  28. void fromJson(json_t *rootJ) override;
  29. //float ratios[13] = {1.0, 16.0/15, 9.0/8, 6.0/5, 5.0/4, 4.0/3, 7.0/5, 3.0/2, 8.0/5, 5.0/3, 16.0/9, 15.0/8, 2.0};
  30. bool noteState[12] = {};
  31. int octave;
  32. int transposeOctave = 0;
  33. int transposeSemi = 0;
  34. float freq;
  35. SchmittTrigger noteTriggers[12];
  36. };
  37. json_t* Erwin::toJson() {
  38. json_t *rootJ = json_object();
  39. // Note values
  40. json_t *gatesJ = json_array();
  41. for (int i = 0; i < 12; i++) {
  42. json_t *gateJ = json_boolean(noteState[i]);
  43. json_array_append_new(gatesJ, gateJ);
  44. }
  45. json_object_set_new(rootJ, "notes", gatesJ);
  46. return rootJ;
  47. }
  48. void Erwin::fromJson(json_t *rootJ) {
  49. // Note values
  50. json_t *gatesJ = json_object_get(rootJ, "notes");
  51. for (int i = 0; i < 12; i++) {
  52. json_t *gateJ = json_array_get(gatesJ, i);
  53. noteState[i] = !!json_boolean_value(gateJ);
  54. }
  55. }
  56. void Erwin::step() {
  57. for(int y=0;y<NUM_CHANNELS;y++) {
  58. octave = trunc(inputs[IN_INPUT+y].value);
  59. freq = inputs[IN_INPUT+y].value - octave;
  60. //limit to 4 octaves
  61. transposeOctave = clampi((int)round(inputs[TRANSPOSE_INPUT].value / 3.0) + (int)round(params[CHANNEL_TRANSPOSE_PARAM + y].value),-4, 4);
  62. //limit to 1 octave
  63. transposeSemi = (int)round(inputs[SEMI_INPUT].value * 1.2);
  64. int semi = (int)(round(freq * 12));
  65. //find last matching note in scale
  66. int lastValidIndex = 0;
  67. for(int i=semi;i>=0;i--) {
  68. if(noteState[i]) {
  69. lastValidIndex = i;
  70. break;
  71. }
  72. }
  73. if(transposeSemi) {
  74. lastValidIndex = (lastValidIndex + transposeSemi);
  75. }
  76. outputs[OUT_OUTPUT + y].value = octave + lastValidIndex * 1/12.0 + transposeOctave;
  77. }
  78. // Note buttons
  79. for (int i = 0; i < 12; i++) {
  80. if (noteTriggers[i].process(params[NOTE_PARAM + i].value)) {
  81. noteState[i] = !noteState[i];
  82. }
  83. lights[NOTE_LIGHT + i].value = (noteState[i] >= 1.0) ? 0.7 : 0;
  84. }
  85. }
  86. template <typename BASE>
  87. struct MuteLight : BASE {
  88. MuteLight() {
  89. this->box.size = mm2px(Vec(6.0, 6.0));
  90. }
  91. };
  92. struct ErwinWidget : ModuleWidget {
  93. ErwinWidget(Erwin *module);
  94. };
  95. ErwinWidget::ErwinWidget(Erwin *module) : ModuleWidget(module) {
  96. box.size = Vec(15*8, 380);
  97. {
  98. SVGPanel *panel = new SVGPanel();
  99. panel->box.size = box.size;
  100. panel->setBackground(SVG::load(assetPlugin(plugin,"res/Erwin.svg")));
  101. addChild(panel);
  102. }
  103. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  104. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0)));
  105. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  106. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365)));
  107. addInput(Port::create<PJ301MPort>(Vec(22.5, 42), Port::INPUT, module, Erwin::TRANSPOSE_INPUT));
  108. addInput(Port::create<PJ301MPort>(Vec(76, 42), Port::INPUT, module, Erwin::SEMI_INPUT));
  109. for(int i=0;i<4;i++) {
  110. addOutput(Port::create<PJ301MPort>(Vec(76, 235 + i*30), Port::OUTPUT, module, Erwin::OUT_OUTPUT + i));
  111. addInput(Port::create<PJ301MPort>(Vec(76, 100 +i*30), Port::INPUT, module, Erwin::IN_INPUT + i));
  112. }
  113. addParam(ParamWidget::create<Trimpot>(Vec(16, 90), module, Erwin::CHANNEL_TRANSPOSE_PARAM, -4, 4, 0));
  114. addParam(ParamWidget::create<Trimpot>(Vec(38, 90), module, Erwin::CHANNEL_TRANSPOSE_PARAM + 1, -4, 4, 0));
  115. addParam(ParamWidget::create<Trimpot>(Vec(16, 117.5), module, Erwin::CHANNEL_TRANSPOSE_PARAM + 2, -4, 4, 0));
  116. addParam(ParamWidget::create<Trimpot>(Vec(38, 117.5), module, Erwin::CHANNEL_TRANSPOSE_PARAM + 3, -4, 4, 0));
  117. //Note buttons
  118. int white=0;
  119. int black = 0;
  120. for(int i=0; i<12; i++) {
  121. if (i == 1 || i == 3 || i == 6 || i == 8 || i == 10 ) {
  122. addParam(ParamWidget::create<LEDBezel>(Vec(10,311.5 - black*30), module, Erwin::NOTE_PARAM + i, 0.0, 1.0, 0.0));
  123. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(12, 313.5 - black*30), module, Erwin::NOTE_LIGHT+i));
  124. black++;
  125. }
  126. else {
  127. if(i == 4)
  128. black++;
  129. addParam(ParamWidget::create<LEDBezel>(Vec(35,326.5 - white*30), module, Erwin::NOTE_PARAM + i, 0.0, 1.0, 0.0));
  130. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(37, 328.5 - white*30), module, Erwin::NOTE_LIGHT+i));
  131. white++;
  132. }
  133. }
  134. }
  135. Model *modelErwin = Model::create<Erwin, ErwinWidget>("Aepelzens Modules", "Erwin", "Erwin", UTILITY_TAG);