From c83935931326670e2f91c02fd8485a0420331989 Mon Sep 17 00:00:00 2001 From: Dooho Yi Date: Fri, 3 Jan 2020 10:24:42 +0900 Subject: [PATCH] die hard. + i tried so eagerly to achieve somewhat stability. yet, to be frustrated. + nothing gained. wanna go back. + but just wanna leave some trace. --- osc/src/main.cpp | 60 +++- postman-monitor/src/main.cpp | 2 +- sampler/lib/Audio_SdFat/play_sd_wav.h | 2 +- sampler/lib/SdFat-beta | 1 + sampler/platformio.ini | 2 +- sampler/src/main.cpp | 418 +++++++++++++++++--------- src/main.cpp | 4 +- 7 files changed, 336 insertions(+), 153 deletions(-) create mode 160000 sampler/lib/SdFat-beta diff --git a/osc/src/main.cpp b/osc/src/main.cpp index 15b4f1c..4257096 100644 --- a/osc/src/main.cpp +++ b/osc/src/main.cpp @@ -6,23 +6,26 @@ // // -// COSMO40 @ Incheon w/ Factory2 // RTA @ Seoul w/ Post Territory Ujeongguk // // -// 2019 12 11 +// 2020 1 2 // // (part-2) teensy35 : 'client:osc' (osc over slip --> mesh post) // -// especially, MIDI-like. -// collect following OSC msg. +// collect following MIDI-like. OSC msg. +// + extra. direction msg. ('/timing/offset', '/timing/loop', '/timing/interval') // // "/note/onoff" // "/note/velocity" -// "/note/key" +// "/note/pitch" +// + +// "/timing/offset" +// "/timing/loop" +// "/timing/interval" // -// and build a MIDI-like letter post. +// and build one letter post. // and give it to the postman // @@ -33,13 +36,22 @@ // : [123456789012345678901234567890] // 'MIDI' letter frame // : [123456789012345678901234567890] -// : [KKKVVVG.......................] +// : [KKKVVVGOOOOOLIIIII............] // : KKK - Key // .substring(1, 4); // : VVV - Velocity (volume/amp.) // .substring(4, 7); // : G - Gate (note on/off) // .substring(7, 8); +// : O - timing offset (plz start after this milli-sec) +// .substring(8, 13); +// : L - looping mode (plz do no/auto/manual-looping) +// .substring(13, 14); +// L == 1 -> no-looping. play once. +// L == 2 -> auto-restart when the playback ends. +// L == 3 -> restart after 'timing interval' milli-sec. +// : I - looping interval (only valid for 'manual-looping') +// .substring(14, 19); ==> check 'special cases' first. // //arduino @@ -91,6 +103,37 @@ void setup() { SLIPSerial.begin(57600); } +// +static int t_offset = 0; +static int t_loop = 0; +static int t_interval = 0; +void timingnote(OSCMessage& msg, int offset) { + // matches will happen in the order. that the bundle is packed. + // (1) --> /offset + if (msg.fullMatch("/offset", offset)) { + // + t_offset = 0; + t_loop = 0; + t_interval = 0; + // + t_offset = msg.getInt(0); + if (t_offset < 0) t_offset = 0; + if (t_offset > 99999) t_offset = 99999; + } + // (2) --> /loop + if (msg.fullMatch("/loop", offset)) { + t_loop = msg.getInt(0); + if (t_loop < 1) t_loop = 1; + if (t_loop > 9) t_loop = 9; + } + // (3) --> /interval + if (msg.fullMatch("/interval", offset)) { + // + t_interval = msg.getInt(0); + if (t_interval < 0) t_interval = 0; + if (t_interval > 99999) t_interval = 99999; + } +} // void midinote(OSCMessage& msg, int offset) { // matches will happen in the order. that the bundle is packed. @@ -120,7 +163,7 @@ void midinote(OSCMessage& msg, int offset) { if (pitch > 127) pitch = 127; // // while (new_letter != false) {}; // <-- sort of semaphore.. but it doesn't work yet.. buggy. - sprintf(letter_outro, "[%03d%03d%01d.......................]", pitch, velocity, onoff); + sprintf(letter_outro, "[%03d%03d%01d%05d%01d%05d............]", pitch, velocity, onoff, t_offset, t_loop, t_interval); new_letter = true; } } @@ -140,6 +183,7 @@ void loop() { } if(!bundleIN.hasError()) { bundleIN.route("/note", midinote); + bundleIN.route("/timing", timingnote); } // else { // str = "error! : " + String(bundleIN.getError()); diff --git a/postman-monitor/src/main.cpp b/postman-monitor/src/main.cpp index 5135502..bea8963 100644 --- a/postman-monitor/src/main.cpp +++ b/postman-monitor/src/main.cpp @@ -89,7 +89,7 @@ void register_nodes(){ #define MESH_PASSWORD "cc*vvvv/kkk" #define MESH_PORT 5555 #define MESH_CHANNEL 5 -#define LONELY_TO_DIE (1000) +#define LONELY_TO_DIE (1000) //======================= // diff --git a/sampler/lib/Audio_SdFat/play_sd_wav.h b/sampler/lib/Audio_SdFat/play_sd_wav.h index 2136fae..7e0b36f 100644 --- a/sampler/lib/Audio_SdFat/play_sd_wav.h +++ b/sampler/lib/Audio_SdFat/play_sd_wav.h @@ -43,7 +43,7 @@ public: uint32_t lengthMillis(void); virtual void update(void); private: - File wavfile; + FsFile wavfile; bool consume(uint32_t size); bool parse_format(void); uint32_t header[10]; // temporary storage of wav header data diff --git a/sampler/lib/SdFat-beta b/sampler/lib/SdFat-beta new file mode 160000 index 0000000..172c685 --- /dev/null +++ b/sampler/lib/SdFat-beta @@ -0,0 +1 @@ +Subproject commit 172c6856246738f538a1e48746c32318408dac5c diff --git a/sampler/platformio.ini b/sampler/platformio.ini index 3e51ed2..af4d6a9 100644 --- a/sampler/platformio.ini +++ b/sampler/platformio.ini @@ -15,7 +15,7 @@ default_envs = teensy35 lib_ignore = Audio, SD lib_deps = 721 ; TaskScheduler - 322 ; SdFat +; 322 ; SdFat 401 ; Adafruit SleepyDog Library [env:teensy35] diff --git a/sampler/src/main.cpp b/sampler/src/main.cpp index 8894c90..b261b66 100644 --- a/sampler/src/main.cpp +++ b/sampler/src/main.cpp @@ -6,22 +6,13 @@ // // -// COSMO40 @ Incheon w/ Factory2 // RTA @ Seoul w/ Post Territory Ujeongguk // -// -// 2019 12 11 // // (part-3) teensy35 : 'client:sampler' (mesh post --> play sounds) // -// -// 2019 12 29 -// -// multiple sound playback -> 4 voices -- TESTING -// - //-------------------- // // 'ANALOG_REF_EXTERNAL_3P3V' @@ -31,21 +22,57 @@ // teensy35 was okay since they are stronger (5V compatible I/O) // // #define ANALOG_REF_EXTERNAL_3P3V +// +// 'LED_INDICATOR' +// --> this will enable red LED on/off according to the file playback status. +// +#define LED_INDICATOR +// +// 'USE_SD' +// --> a original sd card driver.. +// +// #define USE_SD +// +// 'USE_SDFATSDIO' +// --> a faster sd card driver.. +// +// #define USE_SDFATSDIO +// +// 'USE_SDFATBETA' +// --> a faster sd card driver.. +// +#define USE_SDFATBETA //-------------------- //watchdog #include //teensy audio +#if defined(USE_SD) +#include +#include +#include +#include +#include +#define xFile File +// +#elif defined(USE_SDFATSDIO) #include #include -SdFatSdioEX SD; +SdFatSdio SD; +// SdFatSdioEX SD; #include - -//teensy 3.5 with SD card -#define SDCARD_CS_PIN BUILTIN_SDCARD -#define SDCARD_MOSI_PIN 11 // not actually used -#define SDCARD_SCK_PIN 13 // not actually used +#define xFile File +// +#elif defined(USE_SDFATBETA) +#include +#include +#include +SdFs SD; +#define xFile FsFile +// SdExFat SD; +// #define xFile ExFile +#endif // GUItool: begin automatically generated code AudioPlaySdWav playSdWav1; //xy=183,90 @@ -83,8 +110,54 @@ AudioConnection patchCord17(mixer2, 0, dacs1, 1); AudioConnection patchCord18(mixer1, 0, dacs1, 0); // GUItool: end automatically generated code +//threads +#include +//following class is from --> https://github.com/ftrias/TeensyThreads/blob/master/examples/Runnable/ +class Runnable { +private: +protected: + virtual void runTarget(void *arg) = 0; +public: + virtual ~Runnable(){ + } + + static void runThread(void *arg) { + Runnable *_runnable = static_cast (arg); + _runnable->runTarget(arg); + } +}; + +//NOTE: using TeensyThreads == nightmare of 'volatile' problems. +// ==> wrap all the variables that is used to comm. between threads into a 'global' & 'volatile' object.. +class vVoice { +public: + int note_now; + int velocity_now; + int start_offset; + bool note_on_req; + bool note_off_req; + bool is_playing; + + vVoice() { + note_now = 0; + velocity_now = 0; + start_offset = 0; + note_on_req = false; + note_off_req = false; + is_playing = false; + } + + vVoice(const vVoice &vv) { + note_now = vv.note_now; + velocity_now = vv.velocity_now; + start_offset = vv.start_offset; + note_on_req = vv.note_on_req; + note_off_req = vv.note_off_req; + is_playing = vv.is_playing; + } +}; // -class Voice { +class Voice : public Runnable { //private //teensy audio @@ -92,83 +165,105 @@ class Voice { AudioAmplifier& ampL; AudioAmplifier& ampR; - // a filename buffer - char filename[13]; + //teensythreads + std::thread* thrd; + +protected: + //teensythreads + void runTarget(void *arg) { + + // a filename buffer + char filename[13] = "NN.WAV"; + + // + while(1) { + { + Threads::Scope m(mx); + + // 'note off' request + if (vv.note_off_req == true) { + vv.note_off_req = false; + player.stop(); + vv.note_now = 0; // declare that [i'm free.] + vv.is_playing = false; + } + // 'note on' request + else if (vv.note_on_req == true) { + vv.note_on_req = false; + // set filename to play... + int nn = (vv.note_now % 100); // 0~99 + filename[0] = '0' + (nn / 10); // [N]N.WAV + filename[1] = '0' + (nn % 10); // N[N].WAV + // the filename to play is... + Serial.println(filename); + // go! (re-triggering) + // threads.delay(vv.start_offset); // <-- so.. this also means 'yield' so.. sth. unexpected happening here? so, we cannot do this? -> SAD. + AudioNoInterrupts(); // maybe .. 'AudioNoInterrupts' helps for the stability? + // apply gains + int val = vv.velocity_now; + if (val < 0) val = 0; + float gg = (float)val / 127; // allowing +gain for values over 127. + ampL.gain(gg); + ampR.gain(gg); + bool res = player.play(filename); + Serial.print("player.play -> "); Serial.println(res); // maybe.. meaningless? play() is kinda blocking call... -> SAD + AudioInterrupts(); + // --> we just believe that this 'file' is existing & available. NO additional checking. + // threads.delay(10); + // --> let's wait a bit before exit, to give more room to work for background workers(==filesystem|audio-interrupts) + // --> if we get too fast 'player.play' twice, then the system might get broken/stalled. ? + vv.is_playing = true; + } + } + + // + threads.yield(); + // threads.delay(5); + } + } public: - // - int note_now; - int velocity_now; + //mutex + Threads::Mutex& mx; // BETTER hav only 1 mutex. for all? so, only 1 change can happen at a time. + + //variables for Voice + volatile vVoice& vv; // - Voice(AudioPlaySdWav& player_, AudioAmplifier& ampL_, AudioAmplifier& ampR_) - : player(player_) - , ampL(ampL_) - , ampR(ampR_) - { - //initializations - note_now = 0; - velocity_now = 0; - strcpy(filename, "NN.WAV"); + Voice(AudioPlaySdWav& player_, AudioAmplifier& ampL_, AudioAmplifier& ampR_, Threads::Mutex& mx_, volatile vVoice& vv_) + : player(player_), ampL(ampL_), ampR(ampR_), mx(mx_), vv(vv_) { } - // - void noteOn(int note) { - // present my 'note' -> 'occupied'. - note_now = note; - // set filename to play... - int nn = (note % 100); // 0~99 - filename[0] = '0' + (nn / 10); // [N]N.WAV - filename[1] = '0' + (nn % 10); // N[N].WAV - // the filename to play is... - Serial.println(filename); - // go! (re-triggering) - // if (player.isPlaying()) player.stop(); - player.play(filename); - Serial.println("1"); - // --> we just believe that this 'file' is existing & available. NO additional checking. - delay(10); - Serial.println("2"); - // --> let's wait a bit before exit, to give more room to work for background workers(==filesystem|audio-interrupts) - // --> if we get too fast 'player.play' twice, then the system might get broken/stalled. ? - } - // - void noteOff() { - player.stop(); - delay(10); // wait to close file? - // present my 'note' -> 'free'. - note_now = 0; - } - // - void setVelocity(int val) { - if (val < 0) val = 0; - float vv = (float)val / 127; // allowing +gain for values over 127. - ampL.gain(vv); - ampR.gain(vv); - } - // - bool isPlaying() { - return player.isPlaying(); + //threading + void start() { + thrd = new std::thread(&Runnable::runThread, this); } }; // voice banks #include #include -static Voice __voice_1(playSdWav1, amp1, amp2); -static Voice __voice_2(playSdWav2, amp3, amp4); -static Voice __voice_3(playSdWav3, amp5, amp6); -static Voice __voice_4(playSdWav4, amp7, amp8); +//NOTE: stronger locking? there's only 1 locking mutex. +Threads::Mutex __mx; +//NOTE: using TeensyThreads == nightmare of 'volatile' problems. +// ==> wrap all the variables that is used to comm. between threads into a 'global' & 'volatile' object.. +static volatile vVoice __vv1; +static volatile vVoice __vv2; +// static volatile vVoice __vv3; +// static volatile vVoice __vv4; +// ==> and link global volatile objects to the actual objects like : +static Voice __voice_1(playSdWav1, amp1, amp2, __mx, __vv1); +static Voice __voice_2(playSdWav2, amp3, amp4, __mx, __vv2); +// static Voice __voice_3(playSdWav3, amp5, amp6, __mx, __vv3); +// static Voice __voice_4(playSdWav4, amp7, amp8, __mx, __vv4); static std::vector poly_bank; static std::deque< std::pair > poly_queue; -//task -#include -Scheduler runner; // polyphonics static int note_sched = 0; static int velocity_sched = 0; +static int offset_sched = 0; void scheduleNoteOn() { //filename buffer - 8.3 naming convension! 8+1+3+1 = 13 @@ -197,9 +292,13 @@ void scheduleNoteOn() // (1) re-trigger (stop-and-restart) is_already = true; Voice& v = poly_bank[poly_queue[idx].first]; - v.noteOff(); - v.noteOn(note); - v.setVelocity(velocity_sched); + { + Threads::Scope m(v.mx); + v.vv.note_now = note; + v.vv.velocity_now = velocity_sched; + v.vv.start_offset = offset_sched; + v.vv.note_on_req = true; + } break; // (2) do nothing (just let it play till end) // is_already = true; @@ -216,30 +315,40 @@ void scheduleNoteOn() //fine, is there idle voice? bool is_found_idle = false; for (uint32_t idx = 0; idx < poly_bank.size(); idx++) { - if (poly_bank[idx].note_now == 0) { - //cool, got one. - is_found_idle = true; - //play start-up - Voice& v = poly_bank[idx]; - v.noteOn(note); - v.setVelocity(velocity_sched); - //leave a record : (# of voice bank, playing note #) - poly_queue.push_back(std::pair(idx, note)); - break; + Voice& v = poly_bank[idx]; + { + Threads::Scope m(v.mx); + if (v.vv.note_now == 0) { + //cool, got one. + is_found_idle = true; + //play start-up + v.vv.note_now = note; + v.vv.velocity_now = velocity_sched; + v.vv.start_offset = offset_sched; + v.vv.note_on_req = true; + //leave a record : (# of voice bank, playing note #) + poly_queue.push_back(std::pair(idx, note)); + break; + } } } //oh, no idle one! if (is_found_idle == false) { //then, who's the oldest? int oldest = poly_queue.front().first; - poly_bank[oldest].noteOff(); + //poly_bank[oldest].noteOff(); poly_queue.pop_front(); // int newentry = oldest; // Voice& v = poly_bank[newentry]; - v.noteOn(note); - v.setVelocity(velocity_sched); + { + Threads::Scope m(v.mx); + v.vv.note_now = note; + v.vv.velocity_now = velocity_sched; + v.vv.start_offset = offset_sched; + v.vv.note_on_req = true; + } //leave a record : (# of voice bank, playing note #) poly_queue.push_back(std::pair(newentry, note)); } @@ -259,8 +368,6 @@ void scheduleNoteOn() Serial.println(); } // -Task scheduleNoteOn_task(0, TASK_ONCE, scheduleNoteOn); -// void scheduleNoteOff() { for (auto it = poly_queue.begin(); it != poly_queue.end(); ++it) { //is this meaningful, btw? @@ -268,7 +375,11 @@ void scheduleNoteOff() { //okay. we've got that. Serial.println("okay. we've got that."); //a record : (# of voice bank, playing note #) - poly_bank[(*it).first].noteOff(); // stop the bank + Voice& v = poly_bank[(*it).first]; + { + Threads::Scope m(v.mx); + v.vv.note_off_req = true; // stop the bank + } poly_queue.erase(it); // remove the record break; } @@ -285,33 +396,38 @@ void scheduleNoteOff() { } Serial.println(); } -// -Task scheduleNoteOff_task(0, TASK_ONCE, scheduleNoteOff); // void playcheck() { - bool is_nosound = true; - for (uint32_t idx = 0; idx < poly_bank.size(); idx++) { - if (poly_bank[idx].isPlaying()) { - is_nosound = false; + while(1) { +#if defined(LED_INDICATOR) + bool is_nosound = true; + for (uint32_t idx = 0; idx < poly_bank.size(); idx++) { + Voice& v = poly_bank[idx]; + { + Threads::Scope m(v.mx); + if (v.vv.is_playing) is_nosound = false; + } } - } - if (is_nosound) { - //mark the indicator : LOW: OFF - digitalWrite(13, LOW); - } else { - //mark the indicator : HIGH: ON - digitalWrite(13, HIGH); - } - // // - // Serial.print("AM_max:"); - // Serial.println(AudioMemoryUsageMax()); + if (is_nosound) { + //mark the indicator : LOW: OFF + digitalWrite(13, LOW); + } else { + //mark the indicator : HIGH: ON + digitalWrite(13, HIGH); + } +#endif - //watchdog - Watchdog.reset(); + // // + // Serial.print("AM_max:"); + // Serial.println(AudioMemoryUsageMax()); + + //watchdog + Watchdog.reset(); + + threads.delay(100); + } } -// -Task playcheck_task(100, TASK_FOREVER, playcheck, &runner, true); //i2c #include @@ -340,71 +456,91 @@ void receiveEvent(int numBytes) { // 'MIDI' letter frame // : [123456789012345678901234567890] - // : [KKKVVVG.......................] + // : [KKKVVVGOOOOOLIIIII............] // : KKK - Key // .substring(1, 4); // : VVV - Velocity (volume/amp.) // .substring(4, 7); // : G - Gate (note on/off) // .substring(7, 8); + // : O - timing offset (plz start after this milli-sec) + // .substring(8, 13); + // : L - looping mode (plz do no/auto/manual-looping) + // .substring(13, 14); + // L == 1 -> no-looping. play once. + // L == 2 -> auto-restart when the playback ends. + // L == 3 -> restart after 'timing interval' milli-sec. + // : I - looping interval (only valid for 'manual-looping') + // .substring(14, 19); ==> check 'special cases' first. String str_key = msg.substring(1, 4); String str_velocity = msg.substring(4, 7); String str_gate = msg.substring(7, 8); - // Serial.println(str_key); - // Serial.println(str_velocity); - // Serial.println(str_gate); + String str_offset = msg.substring(8, 13); + String str_loop = msg.substring(13, 14); + String str_interval = msg.substring(14, 19); // int key = str_key.toInt(); int velocity = str_velocity.toInt(); // 0 ~ 127 int gate = str_gate.toInt(); + int offset = str_offset.toInt(); + int loop = str_loop.toInt(); + int interval = str_interval.toInt(); // if (gate == 0) { note_sched = key; - scheduleNoteOff_task.restart(); + scheduleNoteOff(); } else { note_sched = key; velocity_sched = velocity; - scheduleNoteOn_task.restart(); + offset_sched = offset; + scheduleNoteOn(); } } } // SD TEST -void printDirectory(File dir, int numTabs) { +void printDirectory(xFile dir, int numTabs) { + char filename[256] = ""; while(true) { - File entry = dir.openNextFile(); + xFile entry = dir.openNextFile(); if (!entry) { - // no more files - Serial.println("**nomorefiles**"); break; } for (uint8_t i=0; i=========== // (1) the backbone AP -#if 1 +#if 0 #define DISABLE_I2C_REQ #define SET_CONTAINSROOT // (2) osc client (the ROOT) @@ -72,7 +72,7 @@ #define SET_ROOT #define SET_CONTAINSROOT // (3) sampler client -#elif 0 +#elif 1 #define DISABLE_AP #define DISABLE_I2C_REQ //