Guest User

Arduino-Roomba Working Music Script

a guest
Oct 13th, 2025
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.58 KB | None | 0 0
  1. // src/main.cpp
  2. // playPhrase supports a single REST token (use "REST" or "R" or nullptr in names[])
  3. // Uses SoftwareSerial on D10/D11 and DD pulses (default).
  4.  
  5. #include <Arduino.h>
  6. #include <SoftwareSerial.h>
  7.  
  8. #define USE_HARDWARE_SERIAL false
  9.  
  10. const uint8_t RX_PIN = 10;
  11. const uint8_t TX_PIN = 11;
  12. const uint8_t DD_PIN = 8;
  13.  
  14. static SoftwareSerial roombaSoft(RX_PIN, TX_PIN);
  15. #define RoombaSerial roombaSoft
  16.  
  17. // ---------- Serial helpers ----------
  18. void sendRaw(const uint8_t *buf, size_t len) {
  19. RoombaSerial.write(buf, len);
  20. delay(5);
  21. }
  22. void sendByte(uint8_t b) {
  23. RoombaSerial.write(b);
  24. delay(3);
  25. }
  26.  
  27. // ---------- Roomba init ----------
  28. void wakeRoomba() {
  29. #if !USE_HARDWARE_SERIAL
  30. digitalWrite(DD_PIN, LOW);
  31. delay(500);
  32. digitalWrite(DD_PIN, HIGH);
  33. delay(2000);
  34. #endif
  35. }
  36. void set19200_viaDD() {
  37. #if !USE_HARDWARE_SERIAL
  38. for (int i = 0; i < 3; ++i) {
  39. digitalWrite(DD_PIN, LOW);
  40. delay(200);
  41. digitalWrite(DD_PIN, HIGH);
  42. delay(200);
  43. }
  44. delay(200);
  45. #endif
  46. }
  47. void enterSafeMode() {
  48. sendByte(128); // START
  49. delay(20);
  50. sendByte(130); // CONTROL (safe)
  51. delay(50);
  52. }
  53.  
  54. // ---------- Single-note helper ----------
  55. uint8_t secondsTo64ths(float seconds) {
  56. if (seconds <= 0.0f) return 1;
  57. int v = (int)round(seconds * 64.0f);
  58. if (v < 1) v = 1;
  59. if (v > 255) v = 255;
  60. return (uint8_t)v;
  61. }
  62. void defineSong0_andPlay(uint8_t midiNote, uint8_t dur64) {
  63. uint8_t buf[5] = {140, 0, 1, midiNote, dur64};
  64. sendRaw(buf, sizeof(buf));
  65. uint8_t playCmd[2] = {141, 0};
  66. sendRaw(playCmd, 2);
  67. }
  68. void playSound(uint8_t midiNote, float seconds) {
  69. if (midiNote < 31) midiNote = 31;
  70. if (midiNote > 127) midiNote = 127;
  71. uint8_t dur = secondsTo64ths(seconds);
  72. defineSong0_andPlay(midiNote, dur);
  73. delay((uint32_t)(seconds * 1000.0f) + 30);
  74. }
  75.  
  76. // ---------- Note name parser ----------
  77. uint8_t noteNameToMidi(const char *name) {
  78. if (!name || !name[0]) return 0;
  79. int baseSemitone = -999;
  80. char letter = name[0];
  81. if (letter >= 'a' && letter <= 'g') letter -= 'a' - 'A';
  82.  
  83. switch (letter) {
  84. case 'C': baseSemitone = 0; break;
  85. case 'D': baseSemitone = 2; break;
  86. case 'E': baseSemitone = 4; break;
  87. case 'F': baseSemitone = 5; break;
  88. case 'G': baseSemitone = 7; break;
  89. case 'A': baseSemitone = 9; break;
  90. case 'B': baseSemitone = 11; break;
  91. default: return 0;
  92. }
  93.  
  94. int idx = 1;
  95. int accidental = 0;
  96. if (name[idx] == '#' || name[idx] == 'β™―') { accidental = 1; idx++; }
  97. else if (name[idx] == 'b' || name[idx] == 'β™­') { accidental = -1; idx++; }
  98.  
  99. int semitone = baseSemitone + accidental;
  100. bool neg = false;
  101. if (name[idx] == '-') { neg = true; idx++; }
  102. if (!(name[idx] >= '0' && name[idx] <= '9')) return 0;
  103.  
  104. int octave = 0;
  105. while (name[idx] >= '0' && name[idx] <= '9') {
  106. octave = octave * 10 + (name[idx] - '0');
  107. idx++;
  108. }
  109. if (neg) octave = -octave;
  110.  
  111. while (semitone < 0) { semitone += 12; octave -= 1; }
  112. while (semitone >= 12) { semitone -= 12; octave += 1; }
  113.  
  114. int midi = 12 * (octave + 1) + semitone;
  115. if (midi < 0 || midi > 127) return 0;
  116. return (uint8_t)midi;
  117. }
  118. void playSoundName(const char *name, float seconds) {
  119. uint8_t midi = noteNameToMidi(name);
  120. if (midi == 0) return;
  121. playSound(midi, seconds);
  122. }
  123.  
  124. // ---------- BPM / beat utils ----------
  125. static int GLOBAL_BPM = 120;
  126. inline float secondsPerQuarter() { return 60.0f / (float)GLOBAL_BPM; }
  127. inline float beatsToSeconds(float beats) { return beats * secondsPerQuarter(); }
  128. void setBPM(int bpm) { if (bpm < 20) bpm = 20; if (bpm > 300) bpm = 300; GLOBAL_BPM = bpm; }
  129.  
  130. // short lowercase beat names
  131. static const float whole = 4.0f;
  132. static const float half = 2.0f;
  133. static const float quarter = 1.0f;
  134. static const float eighth = 0.5f;
  135. static const float sixteenth = 0.25f;
  136. static const float thirtysecond = 0.125f;
  137. static const float dotted_quarter = quarter + eighth;
  138.  
  139. inline float dotted(float beat) { return beat * 1.5f; }
  140. inline float triplet(float beat) { return beat * (2.0f/3.0f); }
  141.  
  142. // ---------- Utility: isRestName ----------
  143. static bool isRestName(const char *name) {
  144. if (!name) return true; // nullptr -> treat as rest
  145. if (name[0] == '\0') return true; // empty string -> rest
  146. // Accept "REST", "Rest", "rest", "R", "r"
  147. if (name[0] == 'R' || name[0] == 'r') {
  148. if (name[1] == '\0') return true; // "R"
  149. // check "REST" prefix
  150. if ( (name[1] == 'E' || name[1] == 'e') &&
  151. (name[2] == 'S' || name[2] == 's') &&
  152. (name[3] == 'T' || name[3] == 't') &&
  153. name[4] == '\0') return true;
  154. }
  155. return false;
  156. }
  157.  
  158. // ---------- playPhrase with REST support ----------
  159. void playPhrase(const char *names[], const float beats[], size_t count) {
  160. if (count == 0) return;
  161.  
  162. const size_t MAX_SONG_NOTES = 16;
  163. size_t idx = 0;
  164. uint8_t songSlot = 0;
  165.  
  166. // buffer for building a chunk in memory
  167. uint8_t buf[3 + MAX_SONG_NOTES * 2];
  168.  
  169. while (idx < count) {
  170. // Build a chunk up to MAX_SONG_NOTES or until we hit a REST that must be handled.
  171. size_t chunkStart = idx;
  172. size_t chunkNotes = 0;
  173. float chunkSeconds = 0.0f;
  174.  
  175. // fill chunk until we hit REST or hit MAX_SONG_NOTES
  176. while (idx < count && chunkNotes < MAX_SONG_NOTES) {
  177. const char *nm = names[idx];
  178. float b = beats[idx];
  179.  
  180. if (isRestName(nm)) {
  181. // REST encountered: break so we can flush existing chunk, then wait the rest
  182. break;
  183. }
  184.  
  185. // normal note: add to chunk
  186. float s = beatsToSeconds(b);
  187. uint8_t dur64 = secondsTo64ths(s);
  188. uint8_t midi = noteNameToMidi(nm);
  189. if (midi == 0) midi = 60; // fallback to C4 if parse fails
  190.  
  191. // place into buf later after we know chunkNotes
  192. chunkNotes++;
  193. chunkSeconds += s;
  194. idx++;
  195. }
  196.  
  197. // If chunk has notes, send them as a single song and play
  198. if (chunkNotes > 0) {
  199. buf[0] = 140; // SONG opcode
  200. buf[1] = songSlot; // slot
  201. buf[2] = (uint8_t)chunkNotes;
  202. // second pass to fill note/duration pairs
  203. for (size_t j = 0; j < chunkNotes; ++j) {
  204. const char *nm = names[chunkStart + j];
  205. float s = beatsToSeconds(beats[chunkStart + j]);
  206. uint8_t dur64 = secondsTo64ths(s);
  207. uint8_t midi = noteNameToMidi(nm);
  208. if (midi == 0) midi = 60;
  209. buf[3 + j*2 + 0] = midi;
  210. buf[3 + j*2 + 1] = dur64;
  211. }
  212. // send definition and play
  213. sendRaw(buf, 3 + chunkNotes*2);
  214. uint8_t playCmd[2] = {141, songSlot};
  215. sendRaw(playCmd, 2);
  216. // wait chunk duration
  217. delay((uint32_t)(chunkSeconds * 1000.0f) + 20);
  218.  
  219. // advance song slot
  220. songSlot++;
  221. if (songSlot >= 15) songSlot = 0;
  222. }
  223.  
  224. // If next is REST, handle it: may be multiple consecutive rests -> sum durations
  225. if (idx < count && isRestName(names[idx])) {
  226. float restSec = 0.0f;
  227. while (idx < count && isRestName(names[idx])) {
  228. restSec += beatsToSeconds(beats[idx]);
  229. idx++;
  230. }
  231. // perform the silence (just delay)
  232. delay((uint32_t)(restSec * 1000.0f) + 2);
  233. // then continue building next chunk
  234. }
  235. // else loop continues to build the next chunk
  236. }
  237. }
  238.  
  239. // ---------- Song functions (moved from setup to avoid redeclaration) ----------
  240.  
  241. void playErrorSound() {
  242. const char *names[] = {"G4", "D4", "A#4", "REST", "A4"};
  243. const float beats[] = {dotted_quarter, eighth, quarter, thirtysecond, dotted_quarter};
  244. playPhrase(names, beats, sizeof(names)/sizeof(names[0]));
  245. }
  246.  
  247. void playToneSurprise() {
  248. const char *names[] = {"REST", "A#4", "A4", "REST", "F4", "REST", "C5"};
  249. const float beats[] = {quarter, eighth, eighth, eighth, dotted_quarter, quarter, quarter};
  250. playPhrase(names, beats, sizeof(names)/sizeof(names[0]));
  251. }
  252.  
  253. void playToneFull() {
  254. const char *names[] = {"A4", "REST", "F4", "A#4", "A4", "REST", "A#4", "A4", "F4"};
  255. const float beats[] = {quarter, eighth, eighth, quarter, quarter, eighth, eighth, quarter, dotted_quarter};
  256. playPhrase(names, beats, sizeof(names)/sizeof(names[0]));
  257. }
  258.  
  259. // ---------- Example usage ----------
  260. void setup() {
  261. Serial.begin(115200);
  262. while (!Serial) { delay(5); }
  263.  
  264. pinMode(DD_PIN, OUTPUT);
  265. digitalWrite(DD_PIN, HIGH);
  266.  
  267. delay(1000);
  268.  
  269. wakeRoomba();
  270. set19200_viaDD();
  271.  
  272. RoombaSerial.begin(19200);
  273. delay(50);
  274.  
  275. enterSafeMode();
  276.  
  277. Serial.println(F("Ready β€” playPhrase supports REST."));
  278.  
  279. setBPM(187);
  280.  
  281. // call the separated song functions
  282. playErrorSound();
  283. playToneSurprise();
  284. playToneFull();
  285. }
  286.  
  287. void loop() {
  288. // nothing
  289. }
Advertisement
Add Comment
Please, Sign In to add comment