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.

295 lines
8.3KB

  1. // Lots of this is poached from Strum's Mental Chord and Bogaudio's Retone!
  2. #include "dsp/digital.hpp"
  3. #include <iostream>
  4. #include <cmath>
  5. #include <sstream>
  6. #include <iomanip>
  7. #include "RJModules.hpp"
  8. namespace rack_plugin_RJModules {
  9. // Displays
  10. struct StringDisplayWidget : TransparentWidget {
  11. std::string *value;
  12. std::shared_ptr<Font> font;
  13. StringDisplayWidget() {
  14. font = Font::load(assetPlugin(plugin, "res/Pokemon.ttf"));
  15. };
  16. void draw(NVGcontext *vg) override
  17. {
  18. // Background
  19. NVGcolor backgroundColor = nvgRGB(0xC0, 0xC0, 0xC0);
  20. nvgBeginPath(vg);
  21. nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
  22. nvgFillColor(vg, backgroundColor);
  23. nvgFill(vg);
  24. // text
  25. nvgFontSize(vg, 24);
  26. nvgFontFaceId(vg, font->handle);
  27. nvgTextLetterSpacing(vg, 2.5);
  28. std::stringstream to_display;
  29. to_display << std::setw(3) << *value;
  30. Vec textPos = Vec(16.0f, 33.0f);
  31. NVGcolor textColor = nvgRGB(0x00, 0x00, 0x00);
  32. nvgFillColor(vg, textColor);
  33. nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
  34. }
  35. };
  36. struct LargeSnapKnob : RoundHugeBlackKnob
  37. {
  38. LargeSnapKnob()
  39. {
  40. minAngle = -0.83 * M_PI;
  41. maxAngle = 0.83 * M_PI;
  42. snap = true;
  43. }
  44. };
  45. // Main
  46. struct Chord : Module {
  47. enum ParamIds {
  48. CHORD_PARAM,
  49. SHAPE_PARAM,
  50. NUM_PARAMS
  51. };
  52. enum InputIds {
  53. CHORD_CV_INPUT,
  54. SHAPE_CV_INPUT,
  55. NUM_INPUTS
  56. };
  57. enum OutputIds {
  58. ROOT_OUTPUT,
  59. THREE_OUTPUT,
  60. FIVE_OUTPUT,
  61. SEVEN_OUTPUT,
  62. NINE_OUTPUT,
  63. NUM_OUTPUTS
  64. };
  65. enum LightIds {
  66. NUM_LIGHTS
  67. };
  68. std::string chord_name = "Hello!";
  69. // Pitchies
  70. float referenceFrequency = 261.626; // C4; frequency at which Rack 1v/octave CVs are zero.
  71. float referenceSemitone = 60.0; // C4; value of C4 in semitones is arbitrary here, so have it match midi note numbers when rounded to integer.
  72. float twelfthRootTwo = 1.0594630943592953;
  73. float logTwelfthRootTwo = logf(1.0594630943592953);
  74. int referencePitch = 0;
  75. int referenceOctave = 4;
  76. float frequencyToSemitone(float frequency) {
  77. return logf(frequency / referenceFrequency) / logTwelfthRootTwo + referenceSemitone;
  78. }
  79. float semitoneToFrequency(float semitone) {
  80. return powf(twelfthRootTwo, semitone - referenceSemitone) * referenceFrequency;
  81. }
  82. float frequencyToCV(float frequency) {
  83. return log2f(frequency / referenceFrequency);
  84. }
  85. float cvToFrequency(float cv) {
  86. return powf(2.0, cv) * referenceFrequency;
  87. }
  88. float cvToSemitone(float cv) {
  89. return frequencyToSemitone(cvToFrequency(cv));
  90. }
  91. float semitoneToCV(float semitone) {
  92. return frequencyToCV(semitoneToFrequency(semitone));
  93. }
  94. Chord() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  95. void step() override;
  96. };
  97. void Chord::step() {
  98. float offset_raw = (params[CHORD_PARAM].value) * 12 - 6 + (inputs[CHORD_CV_INPUT].value) / 1.5;
  99. float pitch_offset = round(offset_raw) / 12;
  100. float root = 1.0*1 + pitch_offset;
  101. float _input_pitch = params[CHORD_PARAM].value * clamp(inputs[CHORD_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f);;
  102. float _pitch = (int) _input_pitch % (int) 12;
  103. float _octave = int(_input_pitch / 12);
  104. float _shape = params[SHAPE_PARAM].value * clamp(inputs[SHAPE_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f);;
  105. float _three_interval;
  106. float _five_interval;
  107. float _seven_interval;
  108. char* shape = NULL;
  109. // via https://en.wikibooks.org/wiki/Music_Theory/Chords
  110. switch ((int) _shape) {
  111. case 0: {
  112. // Maj
  113. shape = "Maj";
  114. _three_interval = 4;
  115. _five_interval = 7;
  116. _seven_interval = 11;
  117. break;
  118. }
  119. case 1: {
  120. // Min
  121. shape = "Min";
  122. _three_interval = 3;
  123. _five_interval = 7;
  124. _seven_interval = 10;
  125. break;
  126. }
  127. case 2: {
  128. // Dim
  129. shape = "Dim";
  130. _three_interval = 3;
  131. _five_interval = 6;
  132. _seven_interval = 10;
  133. break;
  134. }
  135. case 3: {
  136. shape = "Aug";
  137. _three_interval = 4;
  138. _five_interval = 8;
  139. _seven_interval = 12;
  140. break;
  141. }
  142. }
  143. float _root_frequency = semitoneToFrequency(referenceSemitone + 12 * (_octave - referenceOctave) + (_pitch - referencePitch));
  144. float _root_cv = frequencyToCV(_root_frequency);
  145. float _third_frequency = semitoneToFrequency(referenceSemitone + 12 * (_octave - referenceOctave) + (_pitch + _three_interval - referencePitch));
  146. float _third_cv = frequencyToCV(_third_frequency);
  147. float _fifth_frequency = semitoneToFrequency(referenceSemitone + 12 * (_octave - referenceOctave) + (_pitch + _five_interval - referencePitch));
  148. float _fifth_cv = frequencyToCV(_fifth_frequency);
  149. float _seventh_frequency = semitoneToFrequency(referenceSemitone + 12 * (_octave - referenceOctave) + (_pitch + _seven_interval - referencePitch));
  150. float _seventh_cv = frequencyToCV(_seventh_frequency);
  151. outputs[ROOT_OUTPUT].value = _root_cv;
  152. outputs[THREE_OUTPUT].value = _third_cv;
  153. outputs[FIVE_OUTPUT].value = _fifth_cv;
  154. outputs[SEVEN_OUTPUT].value = _seventh_cv;
  155. char* pitch = NULL;
  156. char* sharpFlat = NULL;
  157. switch ((int) _pitch) {
  158. case 0: {
  159. pitch = "C";
  160. break;
  161. }
  162. case 1: {
  163. pitch = "C#";
  164. sharpFlat = "#";
  165. break;
  166. }
  167. case 2: {
  168. pitch = "D";
  169. break;
  170. }
  171. case 3: {
  172. pitch = "D#";
  173. sharpFlat = "#";
  174. break;
  175. }
  176. case 4: {
  177. pitch = "E";
  178. break;
  179. }
  180. case 5: {
  181. pitch = "F";
  182. break;
  183. }
  184. case 6: {
  185. pitch = "F#";
  186. sharpFlat = "#";
  187. break;
  188. }
  189. case 7: {
  190. pitch = "G";
  191. break;
  192. }
  193. case 8: {
  194. pitch = "G#";
  195. sharpFlat = "#";
  196. break;
  197. }
  198. case 9: {
  199. pitch = "A";
  200. break;
  201. }
  202. case 10: {
  203. pitch = "A#";
  204. sharpFlat = "#";
  205. break;
  206. }
  207. case 11: {
  208. pitch = "B";
  209. break;
  210. }
  211. }
  212. chord_name = std::string(pitch) + std::to_string((int)_octave) + std::string(shape);
  213. }
  214. struct ChordWidget: ModuleWidget {
  215. ChordWidget(Chord *module);
  216. };
  217. ChordWidget::ChordWidget(Chord *module) : ModuleWidget(module) {
  218. box.size = Vec(15*10, 380);
  219. {
  220. SVGPanel *panel = new SVGPanel();
  221. panel->box.size = box.size;
  222. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Chord.svg")));
  223. addChild(panel);
  224. }
  225. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  226. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0)));
  227. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  228. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365)));
  229. addParam(ParamWidget::create<LargeSnapKnob>(Vec(47, 143), module, Chord::CHORD_PARAM, 0.0, 59.0, 24.0));
  230. addParam(ParamWidget::create<LargeSnapKnob>(Vec(47, 228), module, Chord::SHAPE_PARAM, 0.0, 3.0, 0.0));
  231. addInput(Port::create<PJ301MPort>(Vec(22, 190), Port::INPUT, module, Chord::CHORD_CV_INPUT));
  232. addInput(Port::create<PJ301MPort>(Vec(22, 270), Port::INPUT, module, Chord::SHAPE_CV_INPUT));
  233. addOutput(Port::create<PJ301MPort>(Vec(16, 319), Port::OUTPUT, module, Chord::ROOT_OUTPUT));
  234. addOutput(Port::create<PJ301MPort>(Vec(48, 319), Port::OUTPUT, module, Chord::THREE_OUTPUT));
  235. addOutput(Port::create<PJ301MPort>(Vec(81, 319), Port::OUTPUT, module, Chord::FIVE_OUTPUT));
  236. addOutput(Port::create<PJ301MPort>(Vec(114, 319), Port::OUTPUT, module, Chord::SEVEN_OUTPUT));
  237. StringDisplayWidget *display = new StringDisplayWidget();
  238. display->box.pos = Vec(28, 70);
  239. display->box.size = Vec(100, 40);
  240. display->value = &module->chord_name;
  241. addChild(display);
  242. }
  243. } // namespace rack_plugin_RJModules
  244. using namespace rack_plugin_RJModules;
  245. RACK_PLUGIN_MODEL_INIT(RJModules, Chord) {
  246. Model *modelChord = Model::create<Chord, ChordWidget>("RJModules", "Chord", "[GEN] Chord", LFO_TAG);
  247. return modelChord;
  248. }