From a7c2856ef67b0eb33c8e3402306cc8dd847fed71 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 22 Oct 2023 21:19:29 +0100 Subject: [PATCH] Speaker: Introduce audio playback Introduce the Speaker class to play audio from the SD card when detecting tags. The only audio supported is WAV. Audio playback is handled via interrupts, so it might try to read from the SD card at the same time as the LCD class is trying to read new frames. Update the LCD animation code to temporarily disable audio interrupts while reading from the SD card. --- README.md | 10 +++++----- include/LCD.h | 1 + include/Speaker.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++ src/LCD.cpp | 9 +++++++++ src/Speaker.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 13 ++++++++++++- 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 include/Speaker.h create mode 100644 src/Speaker.cpp diff --git a/README.md b/README.md index 2362bbd..96300fd 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ se synchroniser avec le firmware et potentiellement le mettre à jour. - [x] LCD (base) - [x] LCD (animations) - [ ] LCD (UI) - - [ ] Audio (sons de figurines) + - [x] Audio (sons de figurines) - [ ] Audio (sons d'UI) - [ ] Audio (gain ?) - [ ] Communication avec logiciel @@ -81,7 +81,7 @@ détaillés ici. ## Structure de fichiers -Les fichiers sur la carte SD sont structurés comme suit : +Les fichiers sur la carte SD sont structurés comme suit (**notez les majuscules**) : ``` / ├── RANDONNEE/ @@ -91,16 +91,16 @@ Les fichiers sur la carte SD sont structurés comme suit : │ │ ├── 02 │ │ ├── .. │ │ └── XX -│ └── audio.wav +│ └── ENTRY.WAV ├── RESTO/ │ ├── ANIM/ │ │ └── .. -│ └── audio.wav +│ └── ENTRY.WAV ├── .../ └── SYS/ ├── ANIM*/ ├── ../ - └── *.wav + └── *.WAV ``` Tous les fichiers liés à une figurine en particulier doivent être placés dans un diff --git a/include/LCD.h b/include/LCD.h index b3111a1..e84c51c 100644 --- a/include/LCD.h +++ b/include/LCD.h @@ -11,6 +11,7 @@ #include +#include "Audio.h" #include "Com.h" #include "IDs.h" #include "Pinout.h" diff --git a/include/Speaker.h b/include/Speaker.h new file mode 100644 index 0000000..722965a --- /dev/null +++ b/include/Speaker.h @@ -0,0 +1,48 @@ +// +// Created by Teo-CD on 22/10/23. +// + +#ifndef JIN_BARBAPAPA_SPEAKER_H +#define JIN_BARBAPAPA_SPEAKER_H + +#include +#include +#include +#include +#include +#include + +#include "Com.h" +#include "IDs.h" +#include "Pinout.h" + +static constexpr uint8_t audioMemReservation = 10; + +class Speaker { +public: + Speaker() : sd(SD) {}; + static void init(); + + /** Play the entry sound for a specific tag ID. */ + void playNewSound(int8_t ID); + + /** Check if a sound and playing an disable the amp if not anymore. */ + bool checkPlayingAndDisable(); +private: + SDClass &sd; + + /* + * Below are members needed to set up the audio pipeline. + * We need an input, output, and to connect them to one another. + * We also need some specially reserved memory to store the data to playback. + */ + static audio_block_t audioMemory[audioMemReservation]; + static bool memoryInitialized; + AudioPlaySdWav sdWavSource; + AudioOutputI2S i2sOut; + AudioConnection connectLeftChannels{sdWavSource, 0, i2sOut, 0}; + AudioConnection connectRightChannels{sdWavSource, 1, i2sOut, 1}; +}; + + +#endif //JIN_BARBAPAPA_SPEAKER_H diff --git a/src/LCD.cpp b/src/LCD.cpp index 0b9e478..9c08678 100644 --- a/src/LCD.cpp +++ b/src/LCD.cpp @@ -47,14 +47,23 @@ bool LCD::checkAndDisplay() { if (!animDir || millis() - lastFrameTime < frameTarget) return false; + /* + * Audio playback is controlled by interrupts, so it can happen anytime. + * Prevent a potential conflict when reading a frame of animation from the + * SD card and being interrupted by the audio library, which will try to + * read audio data from the SD card at the same time. + */ + AudioNoInterrupts(); File frame = animDir.openNextFile(); if (!frame) { /* There are no frames anymore, the animation is complete. */ animDir.close(); + AudioInterrupts(); return false; } frame.read(bitmap, 528); frame.close(); + AudioInterrupts(); lcd.clearDisplay(); lcd.drawBitmap(0, 0, bitmap, 84, 48, 1); diff --git a/src/Speaker.cpp b/src/Speaker.cpp new file mode 100644 index 0000000..d361f2e --- /dev/null +++ b/src/Speaker.cpp @@ -0,0 +1,49 @@ +// +// Created by Teo-CD on 22/10/23. +// + +#include "Speaker.h" + +DMAMEM audio_block_t Speaker::audioMemory[audioMemReservation]; +bool Speaker::memoryInitialized = false; + +void Speaker::init() { + if (memoryInitialized) + return; + AudioStream::initialize_memory(audioMemory, audioMemReservation); + memoryInitialized = true; +} + +void Speaker::playNewSound(int8_t ID) { + char filePath[20]; + uint8_t rawID = getRawId(ID); + + if (!SD.mediaPresent()) { + Com::sendComment("SD Card not present, cannot play sound"); + return; + } + if (!isValidId(ID)) { + Com::sendComment("Unknown ID for sound : %d", rawID); + return; + } + + if (sdWavSource.isPlaying()) + sdWavSource.stop(); + + snprintf(filePath, 20, "%s/ENTRY.WAV", + (isIdCharacter(ID) ? charDirs : promoDirs)[rawID]); + + if (!sd.exists(filePath)) { + Com::sendComment("File %s not present, cannot play sound.", filePath); + return; + } + digitalWrite(pin_Audio_Amp_enable, HIGH); + sdWavSource.play(filePath); +} + +bool Speaker::checkPlayingAndDisable() { + bool isPlaying = sdWavSource.isPlaying(); + if (!isPlaying) + digitalWrite(pin_Audio_Amp_enable, LOW); + return isPlaying; +} diff --git a/src/main.cpp b/src/main.cpp index 6615fef..4214db5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Com.h" #include "LCD.h" #include "RFID.h" +#include "Speaker.h" __attribute__((noreturn)) int main() { pinMode(pin_DBG_LED_1, OUTPUT); @@ -40,7 +41,6 @@ __attribute__((noreturn)) int main() { Serial.begin(115200); Com::sendComment("System is powered up, running set-up."); - /* TODO: Setups once module structure is up. */ RFID rfid; digitalWrite(pin_NFC1_RST, HIGH); rfid.init(); @@ -52,17 +52,28 @@ __attribute__((noreturn)) int main() { digitalWrite(pin_LCD_RST, HIGH); lcd.init(); + /* Don't enable the amp here, only do it when playing to save some power. */ + Speaker::init(); + Speaker speaker; + + Com::sendComment("All modules initialized, entering main loop."); + /* Main loop */ while (true) { int8_t tagEvent; /* Display first, in case the RFID communication delays it too much. */ lcd.checkAndDisplay(); + speaker.checkPlayingAndDisable(); tagEvent = rfid.checkTags(); if (tagEvent) { + tagEvent = 4; Com::sendFigUpdate(tagEvent); + /* Start the audio first because of the possible WAV parsing delay. */ + speaker.playNewSound(tagEvent); lcd.startNewAnim(tagEvent); + } /* TODO: Drop delay, WFE+timer interrupt(s) ? */