1
0
Fork 0

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.
This commit is contained in:
Teo-CD 2023-10-22 21:19:29 +01:00
parent caff96922e
commit a7c2856ef6
6 changed files with 124 additions and 6 deletions

View file

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

View file

@ -11,6 +11,7 @@
#include <Adafruit_PCD8544.h>
#include "Audio.h"
#include "Com.h"
#include "IDs.h"
#include "Pinout.h"

48
include/Speaker.h Normal file
View file

@ -0,0 +1,48 @@
//
// Created by Teo-CD on 22/10/23.
//
#ifndef JIN_BARBAPAPA_SPEAKER_H
#define JIN_BARBAPAPA_SPEAKER_H
#include <Arduino.h>
#include <Audio.h>
#include <SerialFlash.h>
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#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

View file

@ -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);

49
src/Speaker.cpp Normal file
View file

@ -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;
}

View file

@ -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) ? */