Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // src/main.cpp
- // playPhrase supports a single REST token (use "REST" or "R" or nullptr in names[])
- // Uses SoftwareSerial on D10/D11 and DD pulses (default).
- #include <Arduino.h>
- #include <SoftwareSerial.h>
- #define USE_HARDWARE_SERIAL false
- const uint8_t RX_PIN = 10;
- const uint8_t TX_PIN = 11;
- const uint8_t DD_PIN = 8;
- static SoftwareSerial roombaSoft(RX_PIN, TX_PIN);
- #define RoombaSerial roombaSoft
- // ---------- Serial helpers ----------
- void sendRaw(const uint8_t *buf, size_t len) {
- RoombaSerial.write(buf, len);
- delay(5);
- }
- void sendByte(uint8_t b) {
- RoombaSerial.write(b);
- delay(3);
- }
- // ---------- Roomba init ----------
- void wakeRoomba() {
- #if !USE_HARDWARE_SERIAL
- digitalWrite(DD_PIN, LOW);
- delay(500);
- digitalWrite(DD_PIN, HIGH);
- delay(2000);
- #endif
- }
- void set19200_viaDD() {
- #if !USE_HARDWARE_SERIAL
- for (int i = 0; i < 3; ++i) {
- digitalWrite(DD_PIN, LOW);
- delay(200);
- digitalWrite(DD_PIN, HIGH);
- delay(200);
- }
- delay(200);
- #endif
- }
- void enterSafeMode() {
- sendByte(128); // START
- delay(20);
- sendByte(130); // CONTROL (safe)
- delay(50);
- }
- // ---------- Single-note helper ----------
- uint8_t secondsTo64ths(float seconds) {
- if (seconds <= 0.0f) return 1;
- int v = (int)round(seconds * 64.0f);
- if (v < 1) v = 1;
- if (v > 255) v = 255;
- return (uint8_t)v;
- }
- void defineSong0_andPlay(uint8_t midiNote, uint8_t dur64) {
- uint8_t buf[5] = {140, 0, 1, midiNote, dur64};
- sendRaw(buf, sizeof(buf));
- uint8_t playCmd[2] = {141, 0};
- sendRaw(playCmd, 2);
- }
- void playSound(uint8_t midiNote, float seconds) {
- if (midiNote < 31) midiNote = 31;
- if (midiNote > 127) midiNote = 127;
- uint8_t dur = secondsTo64ths(seconds);
- defineSong0_andPlay(midiNote, dur);
- delay((uint32_t)(seconds * 1000.0f) + 30);
- }
- // ---------- Note name parser ----------
- uint8_t noteNameToMidi(const char *name) {
- if (!name || !name[0]) return 0;
- int baseSemitone = -999;
- char letter = name[0];
- if (letter >= 'a' && letter <= 'g') letter -= 'a' - 'A';
- switch (letter) {
- case 'C': baseSemitone = 0; break;
- case 'D': baseSemitone = 2; break;
- case 'E': baseSemitone = 4; break;
- case 'F': baseSemitone = 5; break;
- case 'G': baseSemitone = 7; break;
- case 'A': baseSemitone = 9; break;
- case 'B': baseSemitone = 11; break;
- default: return 0;
- }
- int idx = 1;
- int accidental = 0;
- if (name[idx] == '#' || name[idx] == 'β―') { accidental = 1; idx++; }
- else if (name[idx] == 'b' || name[idx] == 'β') { accidental = -1; idx++; }
- int semitone = baseSemitone + accidental;
- bool neg = false;
- if (name[idx] == '-') { neg = true; idx++; }
- if (!(name[idx] >= '0' && name[idx] <= '9')) return 0;
- int octave = 0;
- while (name[idx] >= '0' && name[idx] <= '9') {
- octave = octave * 10 + (name[idx] - '0');
- idx++;
- }
- if (neg) octave = -octave;
- while (semitone < 0) { semitone += 12; octave -= 1; }
- while (semitone >= 12) { semitone -= 12; octave += 1; }
- int midi = 12 * (octave + 1) + semitone;
- if (midi < 0 || midi > 127) return 0;
- return (uint8_t)midi;
- }
- void playSoundName(const char *name, float seconds) {
- uint8_t midi = noteNameToMidi(name);
- if (midi == 0) return;
- playSound(midi, seconds);
- }
- // ---------- BPM / beat utils ----------
- static int GLOBAL_BPM = 120;
- inline float secondsPerQuarter() { return 60.0f / (float)GLOBAL_BPM; }
- inline float beatsToSeconds(float beats) { return beats * secondsPerQuarter(); }
- void setBPM(int bpm) { if (bpm < 20) bpm = 20; if (bpm > 300) bpm = 300; GLOBAL_BPM = bpm; }
- // short lowercase beat names
- static const float whole = 4.0f;
- static const float half = 2.0f;
- static const float quarter = 1.0f;
- static const float eighth = 0.5f;
- static const float sixteenth = 0.25f;
- static const float thirtysecond = 0.125f;
- static const float dotted_quarter = quarter + eighth;
- inline float dotted(float beat) { return beat * 1.5f; }
- inline float triplet(float beat) { return beat * (2.0f/3.0f); }
- // ---------- Utility: isRestName ----------
- static bool isRestName(const char *name) {
- if (!name) return true; // nullptr -> treat as rest
- if (name[0] == '\0') return true; // empty string -> rest
- // Accept "REST", "Rest", "rest", "R", "r"
- if (name[0] == 'R' || name[0] == 'r') {
- if (name[1] == '\0') return true; // "R"
- // check "REST" prefix
- if ( (name[1] == 'E' || name[1] == 'e') &&
- (name[2] == 'S' || name[2] == 's') &&
- (name[3] == 'T' || name[3] == 't') &&
- name[4] == '\0') return true;
- }
- return false;
- }
- // ---------- playPhrase with REST support ----------
- void playPhrase(const char *names[], const float beats[], size_t count) {
- if (count == 0) return;
- const size_t MAX_SONG_NOTES = 16;
- size_t idx = 0;
- uint8_t songSlot = 0;
- // buffer for building a chunk in memory
- uint8_t buf[3 + MAX_SONG_NOTES * 2];
- while (idx < count) {
- // Build a chunk up to MAX_SONG_NOTES or until we hit a REST that must be handled.
- size_t chunkStart = idx;
- size_t chunkNotes = 0;
- float chunkSeconds = 0.0f;
- // fill chunk until we hit REST or hit MAX_SONG_NOTES
- while (idx < count && chunkNotes < MAX_SONG_NOTES) {
- const char *nm = names[idx];
- float b = beats[idx];
- if (isRestName(nm)) {
- // REST encountered: break so we can flush existing chunk, then wait the rest
- break;
- }
- // normal note: add to chunk
- float s = beatsToSeconds(b);
- uint8_t dur64 = secondsTo64ths(s);
- uint8_t midi = noteNameToMidi(nm);
- if (midi == 0) midi = 60; // fallback to C4 if parse fails
- // place into buf later after we know chunkNotes
- chunkNotes++;
- chunkSeconds += s;
- idx++;
- }
- // If chunk has notes, send them as a single song and play
- if (chunkNotes > 0) {
- buf[0] = 140; // SONG opcode
- buf[1] = songSlot; // slot
- buf[2] = (uint8_t)chunkNotes;
- // second pass to fill note/duration pairs
- for (size_t j = 0; j < chunkNotes; ++j) {
- const char *nm = names[chunkStart + j];
- float s = beatsToSeconds(beats[chunkStart + j]);
- uint8_t dur64 = secondsTo64ths(s);
- uint8_t midi = noteNameToMidi(nm);
- if (midi == 0) midi = 60;
- buf[3 + j*2 + 0] = midi;
- buf[3 + j*2 + 1] = dur64;
- }
- // send definition and play
- sendRaw(buf, 3 + chunkNotes*2);
- uint8_t playCmd[2] = {141, songSlot};
- sendRaw(playCmd, 2);
- // wait chunk duration
- delay((uint32_t)(chunkSeconds * 1000.0f) + 20);
- // advance song slot
- songSlot++;
- if (songSlot >= 15) songSlot = 0;
- }
- // If next is REST, handle it: may be multiple consecutive rests -> sum durations
- if (idx < count && isRestName(names[idx])) {
- float restSec = 0.0f;
- while (idx < count && isRestName(names[idx])) {
- restSec += beatsToSeconds(beats[idx]);
- idx++;
- }
- // perform the silence (just delay)
- delay((uint32_t)(restSec * 1000.0f) + 2);
- // then continue building next chunk
- }
- // else loop continues to build the next chunk
- }
- }
- // ---------- Song functions (moved from setup to avoid redeclaration) ----------
- void playErrorSound() {
- const char *names[] = {"G4", "D4", "A#4", "REST", "A4"};
- const float beats[] = {dotted_quarter, eighth, quarter, thirtysecond, dotted_quarter};
- playPhrase(names, beats, sizeof(names)/sizeof(names[0]));
- }
- void playToneSurprise() {
- const char *names[] = {"REST", "A#4", "A4", "REST", "F4", "REST", "C5"};
- const float beats[] = {quarter, eighth, eighth, eighth, dotted_quarter, quarter, quarter};
- playPhrase(names, beats, sizeof(names)/sizeof(names[0]));
- }
- void playToneFull() {
- const char *names[] = {"A4", "REST", "F4", "A#4", "A4", "REST", "A#4", "A4", "F4"};
- const float beats[] = {quarter, eighth, eighth, quarter, quarter, eighth, eighth, quarter, dotted_quarter};
- playPhrase(names, beats, sizeof(names)/sizeof(names[0]));
- }
- // ---------- Example usage ----------
- void setup() {
- Serial.begin(115200);
- while (!Serial) { delay(5); }
- pinMode(DD_PIN, OUTPUT);
- digitalWrite(DD_PIN, HIGH);
- delay(1000);
- wakeRoomba();
- set19200_viaDD();
- RoombaSerial.begin(19200);
- delay(50);
- enterSafeMode();
- Serial.println(F("Ready β playPhrase supports REST."));
- setBPM(187);
- // call the separated song functions
- playErrorSound();
- playToneSurprise();
- playToneFull();
- }
- void loop() {
- // nothing
- }
Advertisement
Add Comment
Please, Sign In to add comment