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.

217 lines
5.9KB

  1. //***********************************************************************************************
  2. //MidiFile module for VCV Rack by Marc Boulé
  3. //
  4. //Based on code from the Fundamental and AudibleInstruments plugins by Andrew Belt
  5. //and graphics from the Component Library by Wes Milholen
  6. //Also based on Midifile, a C++ MIDI file parsing library by Craig Stuart Sapp
  7. //See ./LICENSE.txt for all licenses
  8. //See ./res/fonts/ for font licenses
  9. //
  10. //Module concept by Marc Boulé
  11. //***********************************************************************************************
  12. /* temporary notes
  13. https://github.com/craigsapp/midifile
  14. Dekstop (callback mechanism and file opening):
  15. https://github.com/dekstop/vcvrackplugins_dekstop/blob/master/src/Recorder.cpp
  16. VCVRack-Simple (file opening):
  17. https://github.com/IohannRabeson/VCVRack-Simple/commit/2d33e97d2e344d2926548a0b9f11f1c15ee4ca3c
  18. */
  19. #include "ImpromptuModular.hpp"
  20. #include "midifile/MidiFile.h"
  21. #include "osdialog.h"
  22. #include <iostream>
  23. using namespace std;
  24. using namespace smf;
  25. //*****************************************************************************
  26. namespace rack_plugin_ImpromptuModular {
  27. struct MidiFileModule : Module {
  28. enum ParamIds {
  29. LOADMIDI_PARAM,
  30. NUM_PARAMS
  31. };
  32. enum InputIds {
  33. NUM_INPUTS
  34. };
  35. enum OutputIds {
  36. NUM_OUTPUTS
  37. };
  38. enum LightIds {
  39. ENUMS(LOADMIDI_LIGHT, 2),
  40. NUM_LIGHTS
  41. };
  42. // Need to save, with reset
  43. // none
  44. // Need to save, no reset
  45. int panelTheme;
  46. string lastPath;// TODO: save also the filename so that it can automatically be reloaded when Rack starts?
  47. // No need to save, with reset
  48. // none
  49. // No need to save, no reset
  50. MidiFile midifile;
  51. bool fileLoaded;
  52. MidiFileModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  53. // Need to save, no reset
  54. panelTheme = 0;
  55. lastPath = "";
  56. // No need to save, no reset
  57. fileLoaded = false;
  58. onReset();
  59. }
  60. // widgets are not yet created when module is created (and when onReset() is called by constructor)
  61. // onReset() is also called when right-click initialization of module
  62. void onReset() override {
  63. }
  64. // widgets randomized before onRandomize() is called
  65. void onRandomize() override {
  66. }
  67. json_t *toJson() override {
  68. json_t *rootJ = json_object();
  69. // TODO // Need to save (reset or not)
  70. return rootJ;
  71. }
  72. // widgets loaded before this fromJson() is called
  73. void fromJson(json_t *rootJ) override {
  74. // TODO // Need to save (reset or not)
  75. // No need to save, with reset
  76. // none
  77. }
  78. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  79. void step() override {
  80. lights[LOADMIDI_LIGHT + 0].value = fileLoaded ? 1.0f : 0.0f;
  81. lights[LOADMIDI_LIGHT + 1].value = !fileLoaded ? 1.0f : 0.0f;
  82. }// step()
  83. void loadMidiFile() {
  84. osdialog_filters *filters = osdialog_filters_parse("Midi File (.mid):mid;Text File (.txt):txt");
  85. string dir = lastPath.empty() ? assetLocal("") : stringDirectory(lastPath);
  86. char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  87. if (path) {
  88. lastPath = path;
  89. //lastFilename = stringFilename(path);
  90. if (midifile.read(path)) {
  91. fileLoaded = true;
  92. midifile.doTimeAnalysis();
  93. midifile.linkNotePairs();
  94. int tracks = midifile.getTrackCount();
  95. cout << "TPQ: " << midifile.getTicksPerQuarterNote() << endl;
  96. if (tracks > 1) cout << "TRACKS: " << tracks << endl;
  97. for (int track=0; track<tracks; track++) {
  98. if (tracks > 1) cout << "\nTrack " << track << endl;
  99. cout << "Tick\tSeconds\tDur\tMessage" << endl;
  100. for (int event=0; event<midifile[track].size(); event++) {
  101. cout << dec << midifile[track][event].tick;
  102. cout << '\t' << dec << midifile[track][event].seconds;
  103. cout << '\t';
  104. if (midifile[track][event].isNoteOn())
  105. cout << midifile[track][event].getDurationInSeconds();
  106. cout << '\t' << hex;
  107. for (unsigned int i=0; i<midifile[track][event].size(); i++)
  108. cout << (int)midifile[track][event][i] << ' ';
  109. cout << endl;
  110. }
  111. }
  112. }
  113. else
  114. fileLoaded = false;
  115. free(path);
  116. }
  117. osdialog_filters_free(filters);
  118. }
  119. };// MidiFileModule : module
  120. struct MidiFileWidget : ModuleWidget {
  121. struct LoadMidiPushButton : IMBigPushButton {
  122. MidiFileModule *moduleL = nullptr;
  123. void onChange(EventChange &e) override {
  124. if (value > 0.0 && moduleL != nullptr) {
  125. moduleL->loadMidiFile();
  126. }
  127. IMBigPushButton::onChange(e);
  128. }
  129. };
  130. MidiFileWidget(MidiFileModule *module) : ModuleWidget(module) {
  131. // Main panel from Inkscape
  132. DynamicSVGPanel* panel = new DynamicSVGPanel();
  133. panel->mode = &module->panelTheme;
  134. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/MidiFile.svg")));
  135. //panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/MidiFile_dark.svg")));
  136. box.size = panel->box.size;
  137. addChild(panel);
  138. // Screws
  139. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  140. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  141. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 0), &module->panelTheme));
  142. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 365), &module->panelTheme));
  143. // main load button
  144. LoadMidiPushButton* midiButton = createDynamicParam<LoadMidiPushButton>(Vec(100, 100), module, MidiFileModule::LOADMIDI_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme);
  145. midiButton->moduleL = module;
  146. addParam(midiButton);
  147. // load light
  148. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(Vec(100, 200), module, MidiFileModule::LOADMIDI_LIGHT + 0));
  149. }
  150. };
  151. } // namespace rack_plugin_ImpromptuModular
  152. using namespace rack_plugin_ImpromptuModular;
  153. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, MidiFile) {
  154. Model *modelMidiFile = Model::create<MidiFileModule, MidiFileWidget>("Impromptu Modular", "Midi-File", "UTIL - Midi-File", MIDI_TAG);
  155. return modelMidiFile;
  156. }
  157. /*CHANGE LOG
  158. 0.6.10:
  159. created
  160. */