From 5816f4d62a4125a2bf1af0b7443e70072dcedd39 Mon Sep 17 00:00:00 2001 From: Dooho Yi Date: Sun, 13 Jun 2021 00:32:37 +0900 Subject: [PATCH] added 'q' - the sequencer. --- q/.gitignore | 1 + q/include/README | 39 +++ q/lib/ESP32-audioI2S | 1 + q/lib/README | 46 +++ q/platformio.ini | 19 ++ q/src/main.cpp | 733 +++++++++++++++++++++++++++++++++++++++++++ q/test/README | 11 + 7 files changed, 850 insertions(+) create mode 100644 q/.gitignore create mode 100644 q/include/README create mode 160000 q/lib/ESP32-audioI2S create mode 100644 q/lib/README create mode 100644 q/platformio.ini create mode 100644 q/src/main.cpp create mode 100644 q/test/README diff --git a/q/.gitignore b/q/.gitignore new file mode 100644 index 0000000..03f4a3c --- /dev/null +++ b/q/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/q/include/README b/q/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/q/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/q/lib/ESP32-audioI2S b/q/lib/ESP32-audioI2S new file mode 160000 index 0000000..e5e7497 --- /dev/null +++ b/q/lib/ESP32-audioI2S @@ -0,0 +1 @@ +Subproject commit e5e7497cc468cb1814e490a721c6d32384561b88 diff --git a/q/lib/README b/q/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/q/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/q/platformio.ini b/q/platformio.ini new file mode 100644 index 0000000..d93d430 --- /dev/null +++ b/q/platformio.ini @@ -0,0 +1,19 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = + adafruit/Adafruit SSD1306@^2.4.5 + adafruit/Adafruit BusIO@^1.7.3 + arkhipenko/TaskScheduler@^3.3.0 +upload_speed = 921600 ; 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 diff --git a/q/src/main.cpp b/q/src/main.cpp new file mode 100644 index 0000000..d4c4e17 --- /dev/null +++ b/q/src/main.cpp @@ -0,0 +1,733 @@ +// +// wirelessly connected cloud (based on ESP-NOW, a kind of LPWAN?) +// + +// +// 0set performance 'Georidugi'/Distancing +// @ 2021 Jun 15 ~ 17 +// + +// +// 2021 june +// +// esp32 based sampler + 'note' sequence generator! +// + +//======================== +// +#define MY_GROUP_ID (70000) +#define MY_ID (MY_GROUP_ID + 1) +#define MY_SIGN ("Q") +// +//======================== + +//===================== +// +// 'HAVE_CLIENT' +// --> i have a client. enable the client task. +// +// 'DISABLE_AP' +// --> (questioning)... +// +// 'REPLICATE_NOTE_REQ' (+ N_SEC_BLOCKING_NOTE_REQ) +// --> for supporting wider area with simple esp_now protocol, +// all receipents will replicate NOTE msg. when they are newly appeared. +// + then, network would be flooded by infinite duplicating msg., +// unless they stop reacting to 'known' req. for some seconds. (e.g. 3 seconds) +// +// 'GEN_NOTE_REQ' +// --> this will generate 'note' msg. +// +//==================== +// +#define DISABLE_AP +#define GEN_NOTE_REQ + +//======================== +// +#define LED_PERIOD (11111) +#define LED_ONTIME (1) +#define LED_GAPTIME (222) +// +#define SCREEN_PERIOD (200) //200ms = 5hz +// +#define WIFI_CHANNEL 5 +// +// 'MONITORING_SERIAL' +// +// --> sometimes, the 'Serial' is in use (for example, 'osc' node) +// then, use 'Serial1' - D4/GPIO2/TDX1 @ nodemcu (this is TX only.) +// +// --> otherwise, MONITORING_SERIAL == Serial. +// +#if defined(SERIAL_SWAP) +#define MONITORING_SERIAL (Serial1) +#else +#define MONITORING_SERIAL (Serial) +#endif +// +//======================= + +//======================== +#if defined(ARDUINO_FEATHER_ESP32) // featheresp32 +#define LED_PIN 13 +#elif defined(ARDUINO_ESP32_DEV) // esp32dev (MakePython ESP32 => Have NO LED) +#define LED_PIN 2 +#else +#define LED_PIN 2 +#endif +//======================= + +//arduino +#include + +//post & addresses +#include "../../post.h" + +//espnow +#include +#include +AddressLibrary lib; + +//task +#include +Scheduler runner; + +//screen +#include +#include +#define MAKEPYTHON_ESP32_SDA 4 +#define MAKEPYTHON_ESP32_SCL 5 +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +//-*-*-*-*-*-*-*-*-*-*-*-*- +// my tasks +// audio & sd & filesystem +#include "Audio.h" +#include "SPI.h" +#include "SD.h" +#include "FS.h" +//sdcard +#define SD_CS 22 +#define SPI_MOSI 23 +#define SPI_MISO 19 +#define SPI_SCK 18 +//digital i/o used (for) makerfabs audio v2.0 +#define I2S_DOUT 27 +#define I2S_BCLK 26 +#define I2S_LRC 25 +Audio audio; + +//buttons +const int pin_vol_up = 39; +const int pin_vol_down = 36; +const int pin_mute = 35; +const int pin_previous = 15; +const int pin_pause = 33; +const int pin_next = 2; + +//sample # +int sample_now = 0; //0~999 +extern Task sample_player_start_task; +extern Task sample_player_stop_task; + +//repeat +#define NEW_NOTE_TIMEOUT (3000) +static unsigned long new_note_time = (-1*NEW_NOTE_TIMEOUT); + +//screen task +#if defined(GEN_NOTE_REQ) +String screen_cmd = "XXX..composing..XXX"; +#else +String screen_cmd = "(((..listening..)))"; +#endif +String screen_filename = "***.wav"; + +// +extern Task screen_cmd_notify_task; +bool cmd_notify = false; +void screen_cmd_notify() { + if (screen_cmd_notify_task.isFirstIteration()) cmd_notify = true; + else if (screen_cmd_notify_task.isLastIteration()) cmd_notify = false; + else cmd_notify = !cmd_notify; +} +Task screen_cmd_notify_task(500, 10, &screen_cmd_notify, &runner, false); + +// +extern Task screen_req_notify_task; +bool req_notify = false; +void screen_req_notify() { + if (screen_req_notify_task.isFirstIteration()) req_notify = true; + else if (screen_req_notify_task.isLastIteration()) req_notify = false; + else req_notify = !req_notify; +} +Task screen_req_notify_task(500, 10, &screen_req_notify, &runner, false); + +// +extern Task screen_task; +void screen() { + +#if defined(GEN_NOTE_REQ) + + // button job! + static int btn_vol_up = 0; + static int btn_vol_down = 0; + static int btn_mute = 0; + static int btn_previous = 0; + static int btn_pause = 0; + static int btn_next = 0; + // + int a; + static int song_request = 1; + // + a = digitalRead(pin_vol_up); + if(btn_vol_up != a) { + btn_vol_up = a; + if(a == 0) { + // 'vol_up' button pressed. + + song_request = song_request - 1; + if (song_request < 1) song_request = 1; + } + } + // + a = digitalRead(pin_vol_down); + if(btn_vol_down != a) { + btn_vol_down = a; + if(a == 0) { + // 'vol_down' button pressed. + + song_request = song_request + 1; + if (song_request > 999) song_request = 999; + } + } + // + a = digitalRead(pin_mute); + if(btn_mute != a) { + btn_mute = a; + if(a == 0) { + // 'mute' button pressed. + } + } + // + a = digitalRead(pin_previous); + if(btn_previous != a) { + btn_previous = a; + if(a == 0) { + // 'previous' button pressed. + } + } + // + a = digitalRead(pin_pause); + if(btn_pause != a) { + btn_pause = a; + if(a == 0) { + // 'pause' button pressed. + + // create a NOTE req. and send it out. + Note note_composed = { + 10001, // int32_t id; + song_request, // float pitch; + 127, // float velocity; + 1, // float onoff; + 0, // float x1; + 0, // float x2; + 0, // float x3; + 0, // float x4; + 0 // float ps; + }; + // + uint8_t frm_size = sizeof(Note) + 2; + uint8_t frm[frm_size]; + frm[0] = '['; + memcpy(frm + 1, (uint8_t *) ¬e_composed, sizeof(Note)); + frm[frm_size - 1] = ']'; + // + esp_now_send(NULL, frm, frm_size); // to all peers in the list. + // + MONITORING_SERIAL.print("# posting a req.# ==> "); + MONITORING_SERIAL.println(note_composed.to_string()); + // + + //+ play start for myself + sample_now = song_request; + sample_player_start_task.restartDelayed(10); + new_note_time = millis(); // also, block for some time. + + //+ fancy stuff + screen_cmd_notify_task.restart(); + screen_req_notify_task.restart(); + + //+ automatically increase 'song_request' + song_request = song_request + 1; + if (song_request > 999) song_request = 999; + + } + } + // + a = digitalRead(pin_next); + if(btn_next != a) { + btn_next = a; + if(a == 0) { + // 'next' button pressed. + } + } + +#endif + + //clear screen + a + int line_step = 12; + int line = 0; + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + display.setTextSize(1); + + //line1 - mode line (playing / stopped) + notify mark + display.setCursor(0, line); + if (audio.isRunning()) display.println("= playing ==="); + else display.println("* stopped !:."); + if (cmd_notify) { + display.setCursor(120, line); + display.println("*"); + } + line += line_step; + + //line2 - filename + display.setCursor(0, line); + display.println(screen_filename.c_str()); + line += line_step; + + //line3 - rf. last msg. + display.setCursor(0, line); + display.println(screen_cmd.c_str()); + line += line_step; + + display.setCursor(0, line); + display.setTextSize(2); + // + char filename[14] = "/NNN.wav"; + int note = song_request; + int nnn = (note % 1000); // 0~999 + int nn = (note % 100); // 0~99 + filename[1] = '0' + (nnn / 100); // N__.WAV + filename[2] = '0' + (nn / 10); // _N_.WAV + filename[3] = '0' + (nn % 10); // __N.WAV + // + display.println(filename); + line += line_step; + display.setTextSize(1); + line += 8; + + //line5 - song req. tx. notify. (GEN_NOTE_REQ) + display.setCursor(0, line); + if (req_notify) { + display.setCursor(25, line); + display.println("~~ d[+=+]b ~~ >>>"); + } + line += line_step; + + // + display.display(); + // +} +Task screen_task(SCREEN_PERIOD, TASK_FOREVER, &screen, &runner, true); + +//on 'start' +void sample_player_start() +{ + //filename buffer - 8.3 naming convension! 8+1+3+1 = 13 + // + '/' root symbol 13+1 = 14 (ESP32 specific?) + char filename[14] = "/NNN.wav"; + //search for the sound file + int note = sample_now; + int nnn = (note % 1000); // 0~999 + int nn = (note % 100); // 0~99 + filename[1] = '0' + (nnn / 100); // N__.WAV + filename[2] = '0' + (nn / 10); // _N_.WAV + filename[3] = '0' + (nn % 10); // __N.WAV + //TEST + Serial.println(filename); + screen_filename = String(filename); + bool test = SD.exists(filename); + if (!test) { + Serial.println("... does not exist."); + screen_filename = screen_filename + "_!NEXIST!"; + return; + } + //start the player! + audio.connecttoSD(filename); + delay(10); +} +Task sample_player_start_task(0, TASK_ONCE, &sample_player_start, &runner, false); + +//on 'stop' +void sample_player_stop() { + //filename buffer - 8.3 naming convension! 8+1+3+1 = 13 + // + '/' root symbol 13+1 = 14 (ESP32 specific?) + char filename[14] = "/NNN.wav"; + //search for the sound file + int note = sample_now; + int nnn = (note % 1000); // 0~999 + int nn = (note % 100); // 0~99 + filename[1] = '0' + (nnn / 100); // N__.WAV + filename[2] = '0' + (nn / 10); // _N_.WAV + filename[3] = '0' + (nn % 10); // __N.WAV + //TEST + Serial.println(filename); + screen_filename = String(filename); + bool test = SD.exists(filename); + if (!test) { + Serial.println("... does not exist."); + screen_filename = screen_filename + "_!NEXIST!"; + return; + } + //stop the player. + audio.stopSong(); +} +Task sample_player_stop_task(0, TASK_ONCE, &sample_player_stop, &runner, false); + +// +#if defined(REPLICATE_NOTE_REQ) +Note note_now = { + -1, // int32_t id; + -1, // float pitch; + -1, // float velocity; + -1, // float onoff; + -1, // float x1; + -1, // float x2; + -1, // float x3; + -1, // float x4; + -1 // float ps; +}; +void repeat() { + // + uint8_t frm_size = sizeof(Note) + 2; + uint8_t frm[frm_size]; + frm[0] = '['; + memcpy(frm + 1, (uint8_t *) ¬e_now, sizeof(Note)); + frm[frm_size - 1] = ']'; + // + esp_now_send(NULL, frm, frm_size); // to all peers in the list. + // + MONITORING_SERIAL.print("repeat! ==> "); + MONITORING_SERIAL.println(note_now.to_string()); +} +Task repeat_task(0, TASK_ONCE, &repeat, &runner, false); +#endif +//*-*-*-*-*-*-*-*-*-*-*-*-* + +// +extern Task hello_task; +static int hello_delay = 0; +void hello() { + // + byte mac[6]; + WiFi.macAddress(mac); + uint32_t mac32 = (((((mac[2] << 8) + mac[3]) << 8) + mac[4]) << 8) + mac[5]; + // + Hello hello(String(MY_SIGN), MY_ID, mac32); // the most basic 'hello' + // and you can append some floats + static int count = 0; + count++; + hello.h1 = (count % 1000); + hello.h2 = sample_now; + // hello.h3 = 0; + // hello.h4 = 0; + // + uint8_t frm_size = sizeof(Hello) + 2; + uint8_t frm[frm_size]; + frm[0] = '{'; + memcpy(frm + 1, (uint8_t *) &hello, sizeof(Hello)); + frm[frm_size - 1] = '}'; + // + esp_now_send(NULL, frm, frm_size); // to all peers in the list. + // + // MONITORING_SERIAL.write(frm, frm_size); + // MONITORING_SERIAL.println(" ==(esp_now_send/0)==> "); + // + if (hello_delay > 0) { + if (hello_delay < 100) hello_delay = 100; + hello_task.restartDelayed(hello_delay); + } + + // //TEST + // Note n = { + // 10001, // int32_t id; + // 1, // float pitch; + // 127, // float velocity; + // 1, // float onoff; + // 0, // float x1; + // 0, // float x2; + // 0, // float x3; + // 0, // float x4; + // 5000 // float ps; + // }; + // note_now = n; + // repeat_task.restart(); +} +Task hello_task(0, TASK_ONCE, &hello, &runner, false); + +//task #0 : blink led +extern Task blink_task; +void blink() { + // + static int count = 0; + count++; + // + switch (count % 4) { + case 0: + digitalWrite(LED_PIN, LOW); // first ON + blink_task.delay(LED_ONTIME); + break; + case 1: + digitalWrite(LED_PIN, HIGH); // first OFF + blink_task.delay(LED_GAPTIME); + break; + case 2: + digitalWrite(LED_PIN, LOW); // second ON + blink_task.delay(LED_ONTIME); + break; + case 3: + digitalWrite(LED_PIN, HIGH); // second OFF + blink_task.delay(LED_PERIOD - 2* LED_ONTIME - LED_GAPTIME); + break; + } +} +Task blink_task(0, TASK_FOREVER, &blink, &runner, false); // makepython esp32 has NO led => disabled. + +// on 'Note' +void onNoteHandler(Note & n) { + //is it for me? + if (n.id == MY_GROUP_ID || n.id == MY_ID) { + // +#if defined(GEN_NOTE_REQ) +#else + screen_cmd = n.to_string(); +#endif + screen_cmd_notify_task.restart(); + // + if (n.onoff == 1) { + sample_now = n.pitch; + sample_player_start_task.restartDelayed(10); + } else if (n.onoff == 0) { + sample_now = n.pitch; + sample_player_stop_task.restartDelayed(10); + } + // + } +} + +// on 'receive' +void onDataReceive(const uint8_t * mac, const uint8_t *incomingData, int32_t len) { + + // + // MONITORING_SERIAL.write(incomingData, len); + + // +#if defined(HAVE_CLIENT) + Serial.write(incomingData, len); // we pass it over to the client. +#endif + + // open => identify => use. + if (incomingData[0] == '{' && incomingData[len - 1] == '}' && len == (sizeof(Hello) + 2)) { + Hello hello(""); + memcpy((uint8_t *) &hello, incomingData + 1, sizeof(Hello)); + // + MONITORING_SERIAL.println(hello.to_string()); + // + } + + // open => identify => use. + if (incomingData[0] == '[' && incomingData[len - 1] == ']' && len == (sizeof(Note) + 2)) { + Note note; + memcpy((uint8_t *) ¬e, incomingData + 1, sizeof(Note)); + onNoteHandler(note); + + //is it for me? + if (note.id == MY_GROUP_ID || note.id == MY_ID) { + hello_delay = note.ps; + if (hello_delay > 0 && hello_task.isEnabled() == false) { + hello_task.restart(); + } + } + + MONITORING_SERIAL.println(note.to_string()); + + #if defined(REPLICATE_NOTE_REQ) + if (millis() - new_note_time > NEW_NOTE_TIMEOUT) { + note_now = note; + repeat_task.restart(); + new_note_time = millis(); + } + #endif + + } +} + +// on 'sent' +void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sendStatus) { + if (sendStatus != 0) MONITORING_SERIAL.println("Delivery failed!"); +} + +// SD TEST +void printDirectory(File dir, int numTabs) { + // char filename[256] = ""; + while(true) { + File entry = dir.openNextFile(); + if (!entry) { + // no more files + break; + } + for (uint8_t i=0; i \"" + String(MY_SIGN) + "\""); + Serial.println("- mac address: " + WiFi.macAddress() + ", channel: " + String(WIFI_CHANNEL)); +#if defined(HAVE_CLIENT) + Serial.println("- ======== 'HAVE_CLIENT' ========"); +#endif +#if defined(SERIAL_SWAP) + Serial.println("- ======== 'SERIAL_SWAP' ========"); +#endif +#if defined(DISABLE_AP) + Serial.println("- ======== 'DISABLE_AP' ========"); +#endif +#if defined(HAVE_CLIENT_I2C) + Serial.println("- ======== 'HAVE_CLIENT_I2C' ========"); +#endif +#if defined(REPLICATE_NOTE_REQ) + Serial.println("- ======== 'REPLICATE_NOTE_REQ' ========"); +#endif + Serial.println("-"); + + //wifi + WiFiMode_t node_type = WIFI_AP_STA; +#if defined(DISABLE_AP) + // system_phy_set_max_tpw(0); + WiFi.setTxPower(WIFI_POWER_MINUS_1dBm); // Set WiFi RF power output to lowest level + node_type = WIFI_STA; +#endif + WiFi.mode(node_type); + + //esp-now + if (esp_now_init() != 0) { + Serial.println("Error initializing ESP-NOW"); + return; + } + esp_now_register_send_cb(onDataSent); + esp_now_register_recv_cb(onDataReceive); + // + // Serial.println("- ! (esp_now_add_peer) ==> add a 'broadcast peer' (FF:FF:FF:FF:FF:FF)."); + // uint8_t broadcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + // + // // + // esp_now_peer_info_t peerInfo; + // memcpy(peerInfo.peer_addr, broadcastmac, 6); + // peerInfo.channel = 0; + // peerInfo.encrypt = false; + // esp_now_add_peer(&peerInfo); + + AddressBook * book = lib.getBookByTitle("audioooo"); + for (int idx = 0; idx < book->list.size(); idx++) { + Serial.println("- ! (esp_now_add_peer) ==> add a '" + book->list[idx].name + "'."); + esp_now_peer_info_t peerInfo; + memcpy(peerInfo.peer_addr, book->list[idx].mac, 6); + peerInfo.channel = 0; + peerInfo.encrypt = false; + esp_now_add_peer(&peerInfo); + } + // + Serial.println("-"); + Serial.println("\".-.-.-. :)\""); + Serial.println(); +} + +void loop() { + // + audio.loop(); + // + runner.execute(); + // +} diff --git a/q/test/README b/q/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/q/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html