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.

969 lines
22KB

  1. #include "modular80.hpp"
  2. #include <stdio.h>
  3. #include <cfloat>
  4. #include <thread>
  5. #include <algorithm>
  6. #include <sys/stat.h>
  7. #include "dsp/digital.hpp"
  8. #include "dsp/vumeter.hpp"
  9. #include "dsp/samplerate.hpp"
  10. #include "dsp/ringbuffer.hpp"
  11. #include "osdialog.h"
  12. namespace rack_plugin_modular80 {
  13. #define DR_WAV_IMPLEMENTATION
  14. #include "dep/dr_libs/dr_wav.h"
  15. #define MAX_BANK_SIZE 2147483648l // 2GB max per bank, 32GB total (16 banks)
  16. #define MAX_NUM_BANKS 16
  17. #define MAX_DIR_DEPTH 1
  18. class FileScanner {
  19. public:
  20. FileScanner() :
  21. scanDepth(0),
  22. bankCount(0),
  23. bankSize(0)
  24. {}
  25. ~FileScanner() {};
  26. void reset() {
  27. bankCount = 0;
  28. bankSize = 0;
  29. scanDepth = 0;
  30. banks.clear();
  31. }
  32. static bool isSupportedAudioFormat(std::string& path) {
  33. const std::string tmpF = stringLowercase(path);
  34. return (stringEndsWith(tmpF, ".wav") || stringEndsWith(tmpF, ".raw"));
  35. }
  36. void scan(std::string& root, const bool sort = false, const bool filter = true) {
  37. std::vector<std::string> files;
  38. std::vector<std::string> entries;
  39. entries = systemListEntries(root);
  40. if (sort) {
  41. std::sort(entries.begin(), entries.end());
  42. }
  43. for (std::string &entry : entries) {
  44. if (systemIsDirectory(entry)) {
  45. if (stringStartsWith(entry, "SPOTL") ||
  46. stringStartsWith(entry, "TRASH") ||
  47. stringStartsWith(entry, "__MACOSX")) {
  48. continue;
  49. }
  50. if (bankCount > MAX_NUM_BANKS) {
  51. warn("Max number of banks reached. Ignoring subdirectories.");
  52. return;
  53. }
  54. if (scanDepth++ > MAX_DIR_DEPTH) {
  55. warn("Directory has too many subdirectories: %s", entry.c_str());
  56. continue;
  57. };
  58. scan(entry, sort, filter);
  59. } else {
  60. struct stat statbuf;
  61. if (stat(entry.c_str(), &statbuf)) {
  62. warn("Failed to get file stats: %s", entry.c_str());
  63. continue;
  64. }
  65. bankSize += (intmax_t)statbuf.st_size;
  66. if (bankSize > MAX_BANK_SIZE) {
  67. warn("Bank size limit reached. Ignoring file: %s", entry.c_str());
  68. continue;
  69. } else {
  70. files.push_back(entry);
  71. }
  72. }
  73. }
  74. if (filter) {
  75. for (std::vector<std::string>::iterator it = files.begin(); it != files.end(); /* */) {
  76. if (!isSupportedAudioFormat(*it)) it = files.erase(it);
  77. else ++it;
  78. }
  79. }
  80. if (!files.empty()) {
  81. bankSize = 0;
  82. bankCount++;
  83. banks.push_back(files);
  84. }
  85. scanDepth--;
  86. }
  87. int scanDepth;
  88. int bankCount;
  89. intmax_t bankSize;
  90. std::vector< std::vector<std::string> > banks;
  91. };
  92. // Base class
  93. class AudioObject {
  94. public:
  95. AudioObject() :
  96. filePath(),
  97. currentPos(0),
  98. channels(0),
  99. sampleRate(0),
  100. bytesPerSample(2),
  101. totalSamples(0),
  102. samples(nullptr),
  103. peak(0.0f) {};
  104. virtual ~AudioObject() {};
  105. virtual bool load(const std::string &path) = 0;
  106. std::string filePath;
  107. unsigned long currentPos;
  108. unsigned int channels;
  109. unsigned int sampleRate;
  110. unsigned int bytesPerSample;
  111. drwav_uint64 totalSamples;
  112. float *samples;
  113. float peak;
  114. };
  115. class WavAudioObject : public AudioObject {
  116. public:
  117. WavAudioObject() : AudioObject() {
  118. bytesPerSample = 4;
  119. };
  120. ~WavAudioObject() {
  121. if (samples) {
  122. drwav_free(samples);
  123. }
  124. };
  125. bool load(const std::string &path) override {
  126. filePath = path;
  127. samples = drwav_open_file_and_read_f32(
  128. filePath.c_str(), &channels, &sampleRate, &totalSamples
  129. );
  130. if (samples) {
  131. for (size_t i = 0; i < totalSamples; ++i) {
  132. if (samples[i] > peak) peak = samples[i];
  133. }
  134. }
  135. return (samples != NULL);
  136. }
  137. };
  138. class RawAudioObject : public AudioObject {
  139. public:
  140. RawAudioObject() : AudioObject() {
  141. channels = 1;
  142. sampleRate = 44100;
  143. bytesPerSample = 2;
  144. };
  145. ~RawAudioObject() {
  146. if (samples) {
  147. free(samples);
  148. }
  149. }
  150. bool load(const std::string &path) override {
  151. filePath = path;
  152. FILE *wav = fopen(filePath.c_str(), "rb");
  153. if (wav) {
  154. fseek(wav, 0, SEEK_END);
  155. const long fsize = ftell(wav);
  156. rewind(wav);
  157. int16_t *rawSamples = (int16_t*)malloc(sizeof(int16_t) * fsize/bytesPerSample);
  158. if (rawSamples) {
  159. const long samplesRead = fread(rawSamples, (size_t)sizeof(int16_t), fsize/bytesPerSample, wav);
  160. fclose(wav);
  161. if (samplesRead != fsize/(int)bytesPerSample) { warn("Failed to read entire file"); }
  162. totalSamples = samplesRead;
  163. samples = (float*)malloc(sizeof(float) * totalSamples);
  164. for (size_t i = 0; i < totalSamples; ++i) {
  165. samples[i] = static_cast<float>(rawSamples[i]);
  166. if (samples[i] > peak) peak = samples[i];
  167. }
  168. } else {
  169. fatal("Failed to allocate memory");
  170. }
  171. free(rawSamples);
  172. } else {
  173. fatal("Failed to load file: %s", filePath.c_str());
  174. }
  175. return (samples != NULL);
  176. }
  177. };
  178. class AudioPlayer {
  179. public:
  180. AudioPlayer() :
  181. startPos(0)
  182. {};
  183. ~AudioPlayer() {};
  184. void load(std::shared_ptr<AudioObject> object) {
  185. audio = std::move(object);
  186. }
  187. void skipTo(unsigned long pos) {
  188. if (audio) {
  189. audio->currentPos = pos;
  190. }
  191. }
  192. float play(unsigned int channel) {
  193. float sample(0.0f);
  194. if (audio) {
  195. if (channel < audio->channels) {
  196. if (audio->currentPos < (audio->totalSamples/audio->channels) ||
  197. ((audio->currentPos + channel) < audio->totalSamples)) {
  198. sample = audio->samples[channel + audio->currentPos];
  199. }
  200. }
  201. }
  202. return sample;
  203. }
  204. void advance(bool repeat = false) {
  205. if (audio) {
  206. const unsigned long nextPos = audio->currentPos + audio->channels;
  207. const unsigned long maxPos = audio->totalSamples/audio->channels;
  208. if (nextPos >= maxPos) {
  209. if (repeat) {
  210. audio->currentPos = startPos;
  211. } else {
  212. audio->currentPos = maxPos;
  213. }
  214. } else {
  215. audio->currentPos = nextPos;
  216. }
  217. }
  218. }
  219. void resetTo(unsigned long pos) {
  220. if (audio) {
  221. startPos = pos;
  222. audio->currentPos = startPos;
  223. }
  224. }
  225. bool ready() {
  226. if (audio) {
  227. return audio->totalSamples > 0;
  228. } else {
  229. return false;
  230. }
  231. }
  232. void reset() {
  233. if (audio) {
  234. audio.reset();
  235. }
  236. }
  237. std::shared_ptr<AudioObject> object() {
  238. return audio;
  239. }
  240. private:
  241. std::shared_ptr<AudioObject> audio;
  242. long startPos;
  243. };
  244. struct RadioMusic : Module {
  245. enum ParamIds {
  246. CHANNEL_PARAM,
  247. START_PARAM,
  248. RESET_PARAM,
  249. NUM_PARAMS
  250. };
  251. enum InputIds {
  252. STATION_INPUT,
  253. START_INPUT,
  254. RESET_INPUT,
  255. NUM_INPUTS
  256. };
  257. enum OutputIds {
  258. OUT_OUTPUT,
  259. NUM_OUTPUTS
  260. };
  261. enum LightIds {
  262. RESET_LIGHT,
  263. LED_0_LIGHT,
  264. LED_1_LIGHT,
  265. LED_2_LIGHT,
  266. LED_3_LIGHT,
  267. NUM_LIGHTS
  268. };
  269. RadioMusic() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS)
  270. {
  271. currentPlayer = &audioPlayer1;
  272. previousPlayer = &audioPlayer2;
  273. init();
  274. }
  275. void step() override;
  276. void reset() override;
  277. void init();
  278. void threadedScan();
  279. void scanAudioFiles();
  280. void threadedLoad();
  281. void loadAudioFiles();
  282. void resetCurrentPlayer(float start);
  283. std::string rootDir;
  284. bool loadFiles;
  285. bool scanFiles;
  286. bool selectBank;
  287. // Settings
  288. bool loopingEnabled;
  289. bool enableCrossfade;
  290. bool sortFiles;
  291. bool allowAllFiles;
  292. json_t *toJson() override {
  293. json_t *rootJ = json_object();
  294. // Option: Loop Samples
  295. json_t *loopingJ = json_boolean(loopingEnabled);
  296. json_object_set_new(rootJ, "loopingEnabled", loopingJ);
  297. // Option: Enable Crossfade
  298. json_t *crossfadeJ = json_boolean(enableCrossfade);
  299. json_object_set_new(rootJ, "enableCrossfade", crossfadeJ);
  300. // Option: Sort Files
  301. json_t *sortJ = json_boolean(sortFiles);
  302. json_object_set_new(rootJ, "sortFiles", sortJ);
  303. // Option: Allow All Files
  304. json_t *filesJ = json_boolean(allowAllFiles);
  305. json_object_set_new(rootJ, "allowAllFiles", filesJ);
  306. // Internal state: rootDir
  307. json_t *rootDirJ = json_string(rootDir.c_str());
  308. json_object_set_new(rootJ, "rootDir", rootDirJ);
  309. // Internal state: currentBank
  310. json_t *bankJ = json_integer(currentBank);
  311. json_object_set_new(rootJ, "currentBank", bankJ);
  312. return rootJ;
  313. }
  314. void fromJson(json_t *rootJ) override {
  315. // Option: Loop Samples
  316. json_t *loopingJ = json_object_get(rootJ, "loopingEnabled");
  317. if (loopingJ) loopingEnabled = json_boolean_value(loopingJ);
  318. // Option: Enable Crossfade
  319. json_t *crossfadeJ = json_object_get(rootJ, "enableCrossfade");
  320. if (crossfadeJ) enableCrossfade = json_boolean_value(crossfadeJ);
  321. // Option: Sort Files
  322. json_t *sortJ = json_object_get(rootJ, "sortFiles");
  323. if (sortJ) sortFiles = json_boolean_value(sortJ);
  324. // Option: Allow All Files
  325. json_t *filesJ = json_object_get(rootJ, "allowAllFiles");
  326. if (filesJ) allowAllFiles = json_boolean_value(filesJ);
  327. // Internal state: rootDir
  328. json_t *rootDirJ = json_object_get(rootJ, "rootDir");
  329. if (rootDirJ) rootDir = json_string_value(rootDirJ);
  330. // Internal state: currentBank
  331. json_t *bankJ = json_object_get(rootJ, "currentBank");
  332. if (bankJ) currentBank = json_integer_value(bankJ);
  333. scanFiles = true;
  334. }
  335. private:
  336. AudioPlayer audioPlayer1;
  337. AudioPlayer audioPlayer2;
  338. AudioPlayer *currentPlayer;
  339. AudioPlayer *previousPlayer;
  340. std::vector<std::shared_ptr<AudioObject>> objects;
  341. SchmittTrigger rstButtonTrigger;
  342. SchmittTrigger rstInputTrigger;
  343. int prevIndex;
  344. unsigned long tick;
  345. unsigned long elapsedMs;
  346. bool crossfade;
  347. bool fadeout;
  348. float fadeOutGain;
  349. float xfadeGain1;
  350. float xfadeGain2;
  351. bool flashResetLed;
  352. unsigned long ledTimerMs;
  353. VUMeter vumeter;
  354. SampleRateConverter<1> outputSrc;
  355. DoubleRingBuffer<Frame<1>, 256> outputBuffer;
  356. bool ready;
  357. int currentBank;
  358. FileScanner scanner;
  359. static const int BLOCK_SIZE = 16;
  360. float block[BLOCK_SIZE];
  361. };
  362. void RadioMusic::reset() {
  363. init();
  364. }
  365. void RadioMusic::init() {
  366. prevIndex = -1;
  367. tick = 0;
  368. elapsedMs = 0;
  369. crossfade = false;
  370. fadeout = false;
  371. flashResetLed = false;
  372. ledTimerMs = 0;
  373. rootDir = "";
  374. loadFiles = false;
  375. scanFiles = false;
  376. selectBank = 0;
  377. ready = false;
  378. fadeOutGain = 1.0f;
  379. xfadeGain1 = 0.0f;
  380. xfadeGain2 = 1.0f;
  381. // Settings
  382. loopingEnabled = true;
  383. enableCrossfade = true;
  384. sortFiles = false;
  385. allowAllFiles = false;
  386. // Persistent
  387. rootDir = "";
  388. currentBank = 0;
  389. // Internal state
  390. scanner.banks.clear();
  391. if (currentPlayer->object()) {
  392. currentPlayer->reset();
  393. }
  394. if (previousPlayer->object()) {
  395. previousPlayer->reset();
  396. }
  397. for (size_t i = 0; i < NUM_LIGHTS; i++) {
  398. lights[RESET_LIGHT + i].value = 0.0f;
  399. }
  400. }
  401. void RadioMusic::threadedScan() {
  402. if (rootDir.empty()) {
  403. warn("No root directory defined. Scan failed.");
  404. return;
  405. }
  406. scanner.reset();
  407. scanner.scan(rootDir, sortFiles, !allowAllFiles);
  408. if (scanner.banks.size() == 0) {
  409. return;
  410. }
  411. loadFiles = true;
  412. }
  413. void RadioMusic::scanAudioFiles() {
  414. std::thread worker(&RadioMusic::threadedScan, this);
  415. worker.detach();
  416. }
  417. void RadioMusic::threadedLoad() {
  418. if (scanner.banks.empty()) {
  419. warn("No banks available. Failed to load audio files.");
  420. return;
  421. }
  422. objects.clear();
  423. currentBank = clamp(currentBank, 0, (int)scanner.banks.size()-1);
  424. const std::vector<std::string> files = scanner.banks[currentBank];
  425. for (unsigned int i = 0; i < files.size(); ++i) {
  426. std::shared_ptr<AudioObject> object;
  427. // Quickly determine file type
  428. drwav wav;
  429. if (drwav_init_file(&wav, files[i].c_str())) {
  430. object = std::make_shared<WavAudioObject>();
  431. } else {
  432. object = std::make_shared<RawAudioObject>();
  433. }
  434. drwav_uninit(&wav);
  435. // Actually load files
  436. if (object->load(files[i])) {
  437. objects.push_back(std::move(object));
  438. } else {
  439. warn("Failed to load object %d %s", i, files[i].c_str());
  440. }
  441. }
  442. prevIndex = -1; // Force channel change detection upon loading files
  443. elapsedMs = 0; // Reset station to beginning
  444. ready = true;
  445. }
  446. void RadioMusic::loadAudioFiles() {
  447. std::thread worker(&RadioMusic::threadedLoad, this);
  448. worker.detach();
  449. }
  450. void RadioMusic::resetCurrentPlayer(float start) {
  451. const unsigned int channels = currentPlayer->object()->channels;
  452. unsigned long pos = static_cast<int>(start * (currentPlayer->object()->totalSamples / channels));
  453. if (pos >= channels) { pos -= channels; }
  454. pos = pos % (currentPlayer->object()->totalSamples / channels);
  455. currentPlayer->resetTo(pos);
  456. }
  457. void RadioMusic::step() {
  458. if (rootDir.empty()) {
  459. // No files loaded yet. Idle.
  460. return;
  461. }
  462. if (scanFiles) {
  463. scanAudioFiles();
  464. scanFiles = false;
  465. }
  466. if (loadFiles) {
  467. // Disable channel switching and resetting while loading files.
  468. ready = false;
  469. loadAudioFiles();
  470. loadFiles = false;
  471. }
  472. // Bank selection mode
  473. if (selectBank) {
  474. // Bank is selected via Reset button
  475. if (rstButtonTrigger.process(params[RESET_PARAM].value)) {
  476. currentBank++;
  477. currentBank %= scanner.banks.size();
  478. }
  479. // Show bank selection in LED bar
  480. lights[LED_0_LIGHT].value = (1 && (currentBank & 1));
  481. lights[LED_1_LIGHT].value = (1 && (currentBank & 2));
  482. lights[LED_2_LIGHT].value = (1 && (currentBank & 4));
  483. lights[LED_3_LIGHT].value = (1 && (currentBank & 8));
  484. lights[RESET_LIGHT].value = 1.0f;
  485. }
  486. // Keep track of milliseconds of elapsed time
  487. if (tick++ % (static_cast<int>(engineGetSampleRate())/1000) == 0) {
  488. elapsedMs++;
  489. ledTimerMs++;
  490. }
  491. // Start knob & input
  492. const float start = clamp(params[START_PARAM].value + inputs[START_INPUT].value/5.0f, 0.0f, 1.0f);
  493. if (ready && (rstButtonTrigger.process(params[RESET_PARAM].value) ||
  494. (inputs[RESET_INPUT].active && rstInputTrigger.process(inputs[RESET_INPUT].value)))) {
  495. fadeOutGain = 1.0f;
  496. if (enableCrossfade) {
  497. fadeout = true;
  498. } else {
  499. resetCurrentPlayer(start);
  500. }
  501. flashResetLed = true;
  502. }
  503. // Channel knob & input
  504. const float channel = clamp(params[CHANNEL_PARAM].value + inputs[STATION_INPUT].value/5.0f, 0.0f, 1.0f);
  505. const int index = \
  506. clamp(static_cast<int>(rescale(channel, 0.0f, 1.0f, 0.0f, static_cast<float>(objects.size()))),
  507. 0, objects.size() - 1);
  508. // Channel switch detection
  509. if (ready && index != prevIndex) {
  510. AudioPlayer *tmp;
  511. tmp = previousPlayer;
  512. previousPlayer = currentPlayer;
  513. currentPlayer = tmp;
  514. if (index < (int)objects.size()) {
  515. currentPlayer->load(objects[index]);
  516. unsigned long pos = objects[index]->currentPos + \
  517. (currentPlayer->object()->channels * elapsedMs * objects[index]->sampleRate) / 1000;
  518. pos = pos % (objects[index]->totalSamples / objects[index]->channels);
  519. currentPlayer->skipTo(pos);
  520. elapsedMs = 0;
  521. }
  522. xfadeGain1 = 0.0f;
  523. xfadeGain2 = 1.0f;
  524. crossfade = enableCrossfade;
  525. // Different number of channels while crossfading leads to audible artifacts.
  526. if (previousPlayer->object()) {
  527. if (currentPlayer->object()->channels != previousPlayer->object()->channels) {
  528. crossfade = false;
  529. }
  530. }
  531. flashResetLed = true;
  532. }
  533. prevIndex = index;
  534. // Reset LED
  535. if (flashResetLed) {
  536. static int initTimer(true);
  537. static int timerStart(0);
  538. if (initTimer) {
  539. timerStart = ledTimerMs;
  540. initTimer = false;
  541. }
  542. lights[RESET_LIGHT].value = 1.0f;
  543. if ((ledTimerMs - timerStart) > 50) { // 50ms flash time
  544. initTimer = true;
  545. ledTimerMs = 0;
  546. flashResetLed = false;
  547. }
  548. }
  549. if (!flashResetLed && !selectBank) {
  550. lights[RESET_LIGHT].value = 0.0f;
  551. }
  552. // Audio processing
  553. if (outputBuffer.empty()) {
  554. // Nothing to play if no audio objects are loaded into players.
  555. if (!currentPlayer->object() && !previousPlayer->object()) {
  556. return;
  557. }
  558. for (int i = 0; i < BLOCK_SIZE; i++) {
  559. float output(0.0f);
  560. // Crossfade?
  561. if (crossfade) {
  562. xfadeGain1 = rack::crossfade(xfadeGain1, 1.0f, 0.005); // 0.005 = ~25ms
  563. xfadeGain2 = rack::crossfade(xfadeGain2, 0.0f, 0.005); // 0.005 = ~25ms
  564. for (size_t channel = 0; channel < currentPlayer->object()->channels; channel++) {
  565. const float currSample = currentPlayer->play(channel);
  566. const float prevSample = previousPlayer->play(channel);
  567. const float out = currSample * xfadeGain1 + prevSample * xfadeGain2;
  568. output += 5.0f * out / currentPlayer->object()->peak;
  569. }
  570. output /= currentPlayer->object()->channels;
  571. currentPlayer->advance(loopingEnabled);
  572. previousPlayer->advance(loopingEnabled);
  573. if (isNear(xfadeGain1+0.005, 1.0f) || isNear(xfadeGain2, 0.0f)) {
  574. crossfade = false;
  575. }
  576. }
  577. // Fade out (before resetting)?
  578. else if (fadeout)
  579. {
  580. fadeOutGain = rack::crossfade(fadeOutGain, 0.0f, 0.05); // 0.05 = ~5ms
  581. for (size_t channel = 0; channel < currentPlayer->object()->channels; channel++) {
  582. const float sample = currentPlayer->play(channel);
  583. const float out = sample * fadeOutGain;
  584. output += 5.0f * out / currentPlayer->object()->peak;
  585. }
  586. output /= currentPlayer->object()->channels;
  587. currentPlayer->advance(loopingEnabled);
  588. if (isNear(fadeOutGain, 0.0f)) {
  589. resetCurrentPlayer(start);
  590. fadeout = false;
  591. }
  592. }
  593. else // No fading
  594. {
  595. for (size_t channel = 0; channel < currentPlayer->object()->channels; channel++) {
  596. const float out = currentPlayer->play(channel);
  597. output += 5.0f * out / currentPlayer->object()->peak;
  598. }
  599. output /= currentPlayer->object()->channels;
  600. currentPlayer->advance(loopingEnabled);
  601. }
  602. block[i] = output;
  603. }
  604. // Sample rate conversion to match Rack engine sample rate.
  605. outputSrc.setRates(currentPlayer->object()->sampleRate, engineGetSampleRate());
  606. int inLen = BLOCK_SIZE;
  607. int outLen = outputBuffer.capacity();
  608. Frame<1> frame[BLOCK_SIZE];
  609. for (int i = 0; i < BLOCK_SIZE; i++) {
  610. frame[i].samples[0] = block[i];
  611. }
  612. outputSrc.process(frame, &inLen, outputBuffer.endData(), &outLen);
  613. outputBuffer.endIncr(outLen);
  614. }
  615. // Output processing & metering
  616. if (!outputBuffer.empty()) {
  617. Frame<1> frame = outputBuffer.shift();
  618. outputs[OUT_OUTPUT].value = frame.samples[0];
  619. // Disable VU Meter in Bank Selection mode.
  620. if (!selectBank) {
  621. for (int i = 0; i < 4; i++){
  622. vumeter.setValue(frame.samples[0]/5.0f);
  623. lights[LED_3_LIGHT - i].setBrightnessSmooth(vumeter.getBrightness(i));
  624. }
  625. }
  626. }
  627. }
  628. struct RadioMusicWidget : ModuleWidget {
  629. RadioMusicWidget(RadioMusic *module);
  630. Menu *createContextMenu() override;
  631. };
  632. RadioMusicWidget::RadioMusicWidget(RadioMusic *module) : ModuleWidget(module) {
  633. setPanel(SVG::load(assetPlugin(plugin, "res/Radio.svg")));
  634. addChild(Widget::create<ScrewSilver>(Vec(14, 0)));
  635. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(6, 33), module, RadioMusic::LED_0_LIGHT));
  636. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(19, 33), module, RadioMusic::LED_1_LIGHT));
  637. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(32, 33), module, RadioMusic::LED_2_LIGHT));
  638. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(45, 33), module, RadioMusic::LED_3_LIGHT));
  639. addParam(ParamWidget::create<Davies1900hBlackKnob>(Vec(12, 49), module, RadioMusic::CHANNEL_PARAM, 0.0f, 1.0f, 0.0f));
  640. addParam(ParamWidget::create<Davies1900hBlackKnob>(Vec(12, 131), module, RadioMusic::START_PARAM, 0.0f, 1.0f, 0.0f));
  641. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(44, 188), module, RadioMusic::RESET_LIGHT));
  642. addParam(ParamWidget::create<PB61303>(Vec(25, 202), module, RadioMusic::RESET_PARAM, 0, 1, 0));
  643. addInput(Port::create<PJ301MPort>(Vec(3, 274), Port::INPUT, module, RadioMusic::STATION_INPUT));
  644. addInput(Port::create<PJ301MPort>(Vec(32, 274), Port::INPUT, module, RadioMusic::START_INPUT));
  645. addInput(Port::create<PJ301MPort>(Vec(3, 318), Port::INPUT, module, RadioMusic::RESET_INPUT));
  646. addOutput(Port::create<PJ301MPort>(Vec(32, 318), Port::OUTPUT, module, RadioMusic::OUT_OUTPUT));
  647. addChild(Widget::create<ScrewSilver>(Vec(14, 365)));
  648. }
  649. struct RadioMusicDirDialogItem : MenuItem {
  650. RadioMusic *rm;
  651. void onAction(EventAction &e) override {
  652. const std::string dir = \
  653. rm->rootDir.empty() ? assetLocal("") : rm->rootDir;
  654. char *path = osdialog_file(OSDIALOG_OPEN_DIR, dir.c_str(), NULL, NULL);
  655. if (path) {
  656. rm->rootDir = std::string(path);
  657. rm->scanFiles = true;
  658. free(path);
  659. }
  660. }
  661. };
  662. struct RadioMusicSelectBankItem : MenuItem {
  663. RadioMusic *rm;
  664. void onAction(EventAction &e) override {
  665. rm->selectBank = !rm->selectBank;
  666. if (rm->selectBank == false) {
  667. rm->loadFiles = true;
  668. }
  669. }
  670. void step() override {
  671. text = (rm->selectBank != true) ? "Enter Bank Select Mode" : "Exit Bank Select Mode";
  672. rightText = (rm->selectBank == true) ? "✔" : "";
  673. }
  674. };
  675. struct RadioMusicLoopingEnabledItem : MenuItem {
  676. RadioMusic *rm;
  677. void onAction(EventAction &e) override {
  678. rm->loopingEnabled = !rm->loopingEnabled;
  679. }
  680. void step() override {
  681. rightText = (rm->loopingEnabled == true) ? "✔" : "";
  682. }
  683. };
  684. struct RadioMusicCrossfadeItem : MenuItem {
  685. RadioMusic *rm;
  686. void onAction(EventAction &e) override {
  687. rm->enableCrossfade = !rm->enableCrossfade;
  688. }
  689. void step() override {
  690. rightText = (rm->enableCrossfade == true) ? "✔" : "";
  691. }
  692. };
  693. struct RadioMusicFileSortItem : MenuItem {
  694. RadioMusic *rm;
  695. void onAction(EventAction &e) override {
  696. rm->sortFiles = !rm->sortFiles;
  697. }
  698. void step() override {
  699. rightText = (rm->sortFiles == true) ? "✔" : "";
  700. }
  701. };
  702. struct RadioMusicFilesAllowedItem : MenuItem {
  703. RadioMusic *rm;
  704. void onAction(EventAction &e) override {
  705. rm->allowAllFiles = !rm->allowAllFiles;
  706. }
  707. void step() override {
  708. rightText = (rm->allowAllFiles == true) ? "✔" : "";
  709. }
  710. };
  711. Menu *RadioMusicWidget::createContextMenu() {
  712. Menu *menu = ModuleWidget::createContextMenu();
  713. MenuLabel *spacerLabel = new MenuLabel();
  714. menu->addChild(spacerLabel);
  715. RadioMusic *rm = dynamic_cast<RadioMusic*>(module);
  716. assert(rm);
  717. RadioMusicDirDialogItem *rootDirItem = new RadioMusicDirDialogItem();
  718. rootDirItem->text = "Set Root Directory";
  719. rootDirItem->rm = rm;
  720. menu->addChild(rootDirItem);
  721. RadioMusicSelectBankItem *selectBankItem = new RadioMusicSelectBankItem();
  722. selectBankItem->text = "";
  723. selectBankItem->rm = rm;
  724. menu->addChild(selectBankItem);
  725. MenuLabel *spacerLabel2 = new MenuLabel();
  726. menu->addChild(spacerLabel2);
  727. RadioMusicLoopingEnabledItem *loopingEnabledItem = new RadioMusicLoopingEnabledItem();
  728. loopingEnabledItem->text = "Enable Looping";
  729. loopingEnabledItem->rm = rm;
  730. menu->addChild(loopingEnabledItem);
  731. RadioMusicCrossfadeItem *crossfadeItem = new RadioMusicCrossfadeItem();
  732. crossfadeItem->text = "Enable Crossfade";
  733. crossfadeItem->rm = rm;
  734. menu->addChild(crossfadeItem);
  735. RadioMusicFileSortItem *fileSortItem = new RadioMusicFileSortItem();
  736. fileSortItem->text = "Sort Files";
  737. fileSortItem->rm = rm;
  738. menu->addChild(fileSortItem);
  739. RadioMusicFilesAllowedItem *filesAllowedItem = new RadioMusicFilesAllowedItem();
  740. filesAllowedItem->text = "Allow All Files";
  741. filesAllowedItem->rm = rm;
  742. menu->addChild(filesAllowedItem);
  743. return menu;
  744. }
  745. } // namespace rack_plugin_modular80
  746. using namespace rack_plugin_modular80;
  747. RACK_PLUGIN_MODEL_INIT(modular80, RadioMusic) {
  748. Model *modelRadioMusic = Model::create<RadioMusic, RadioMusicWidget>("modular80", "Radio Music", "Radio Music", SAMPLER_TAG);
  749. return modelRadioMusic;
  750. }