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
//