change to 16khz, add audio filter, refactor to use DMA

This commit is contained in:
zyphlar
2026-07-04 03:28:13 -07:00
parent 8a6d096635
commit 0697c3d6f3
4 changed files with 333 additions and 201 deletions
+206 -26
View File
@@ -2883,6 +2883,12 @@
(color 0 0 0 0)
(uuid "27e5f52e-0ae1-42be-b638-2ba1b684ea4c")
)
(junction
(at 302.26 162.56)
(diameter 0)
(color 0 0 0 0)
(uuid "493dddab-eea7-4bd3-80f3-98e4f109b241")
)
(junction
(at 339.09 76.18)
(diameter 0)
@@ -2947,16 +2953,6 @@
)
(uuid "0a315227-f5b7-4de4-9dc2-5a9da0d1e499")
)
(wire
(pts
(xy 318.69 162.56) (xy 320.04 162.56)
)
(stroke
(width 0)
(type default)
)
(uuid "105d0608-0c50-435d-ab00-ee5708be0d5c")
)
(wire
(pts
(xy 342.9 92.69) (xy 342.9 92.61)
@@ -3067,6 +3063,16 @@
)
(uuid "39ffb2c8-fe04-4380-a8d4-b2edce0fe6c0")
)
(wire
(pts
(xy 302.26 166.45) (xy 302.26 162.56)
)
(stroke
(width 0)
(type default)
)
(uuid "3e8b71b2-91ca-4a51-b4ed-10be32554bc2")
)
(wire
(pts
(xy 207.01 201.93) (xy 207.01 204.47)
@@ -3187,6 +3193,16 @@
)
(uuid "5c62cb6a-8252-484a-be0e-c05c74bf8a38")
)
(wire
(pts
(xy 314.88 162.56) (xy 320.04 162.56)
)
(stroke
(width 0)
(type default)
)
(uuid "650f524d-d388-4dcd-b479-9ecdf63dc629")
)
(wire
(pts
(xy 231.14 229.87) (xy 243.84 229.87)
@@ -3267,6 +3283,16 @@
)
(uuid "8f5e7827-d21e-468e-b028-9b178eb97914")
)
(wire
(pts
(xy 302.26 162.56) (xy 299.72 162.56)
)
(stroke
(width 0)
(type default)
)
(uuid "93b77e73-d20f-4fe2-bf42-a7c0d0018958")
)
(wire
(pts
(xy 330.2 135.89) (xy 330.2 135.97)
@@ -3307,6 +3333,16 @@
)
(uuid "a2cef328-0826-46a6-997f-fcedb293b343")
)
(wire
(pts
(xy 304.8 162.56) (xy 302.26 162.56)
)
(stroke
(width 0)
(type default)
)
(uuid "a80cbefa-0f08-40b2-9854-090a0f94a904")
)
(wire
(pts
(xy 207.01 204.47) (xy 223.52 204.47)
@@ -3547,6 +3583,16 @@
)
(uuid "02b9416a-d87a-410f-8104-ff4ee4156d8d")
)
(label "AUDIO_PWM"
(at 292.1 162.56 180)
(effects
(font
(size 1.27 1.27)
)
(justify right bottom)
)
(uuid "14dfac6d-2b8c-4d46-b75d-10037718af51")
)
(label "GND"
(at 334.02 82.55 0)
(effects
@@ -3587,6 +3633,16 @@
)
(uuid "20059635-02ae-4ce0-8d0b-fbe46035b4f2")
)
(label "GND"
(at 302.26 176.45 270)
(effects
(font
(size 1.27 1.27)
)
(justify right bottom)
)
(uuid "206b27d5-3a5c-4ff1-8a01-a0976b0bb04c")
)
(label "GND"
(at 223.52 209.55 180)
(effects
@@ -3727,16 +3783,6 @@
)
(uuid "663e9f4c-2079-4248-a551-ced079e7c1e8")
)
(label "AUDIO_PWM"
(at 308.69 162.56 180)
(effects
(font
(size 1.27 1.27)
)
(justify right bottom)
)
(uuid "67c49a5e-335d-40a3-a3b6-ba2bd352f99e")
)
(label "C_BTN1"
(at 223.52 201.93 180)
(effects
@@ -4067,6 +4113,74 @@
)
(uuid "fcb61f0a-8efc-4687-bfbd-16fb3032447e")
)
(symbol
(lib_id "Device:R")
(at 295.91 162.56 270)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "07a6ad63-d769-4a1d-a5f8-ac0dbcfcf37c")
(property "Reference" "R5"
(at 295.91 156.21 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "1K"
(at 295.91 158.75 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" "Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P1.90mm_Vertical"
(at 295.91 160.782 90)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" "~"
(at 295.91 162.56 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" "Resistor"
(at 295.91 162.56 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(pin "1"
(uuid "0c6ee5ba-b07d-4d0b-9f2e-a2f679d3f5d5")
)
(pin "2"
(uuid "20ea8652-17f4-4f08-affb-aeb74ca61b56")
)
(instances
(project "baby-mobile-v5"
(path "/3033f063-d051-4b2e-89f5-f1cfe4577b32"
(reference "R5")
(unit 1)
)
)
)
)
(symbol
(lib_id "Jumper:SolderJumper_2_Bridged")
(at 227.33 232.41 0)
@@ -4205,7 +4319,7 @@
)
(symbol
(lib_id "bm:C")
(at 313.69 162.56 270)
(at 309.88 162.56 270)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
@@ -4213,7 +4327,7 @@
(dnp no)
(uuid "16705866-bee7-4f26-a10e-6f7bfbff4c09")
(property "Reference" "C5"
(at 310.896 165.608 0)
(at 307.086 165.608 0)
(effects
(font
(size 1.27 1.27)
@@ -4221,7 +4335,7 @@
)
)
(property "Value" "0.1uF"
(at 313.944 167.64 0)
(at 310.134 167.64 0)
(effects
(font
(size 1.27 1.27)
@@ -4229,7 +4343,7 @@
)
)
(property "Footprint" "Capacitor_SMD:C_0805_2012Metric"
(at 313.69 162.56 0)
(at 309.88 162.56 0)
(effects
(font
(size 1.27 1.27)
@@ -4238,7 +4352,7 @@
)
)
(property "Datasheet" ""
(at 313.69 162.56 0)
(at 309.88 162.56 0)
(effects
(font
(size 1.27 1.27)
@@ -4247,7 +4361,7 @@
)
)
(property "Description" ""
(at 313.69 162.56 0)
(at 309.88 162.56 0)
(effects
(font
(size 1.27 1.27)
@@ -4335,6 +4449,72 @@
)
)
)
(symbol
(lib_id "bm:C")
(at 302.26 171.45 0)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(uuid "248137b6-5edd-42cd-819b-f475cf23c6e8")
(property "Reference" "C9"
(at 298.45 173.656 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "1nF"
(at 298.196 171.116 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" "Capacitor_SMD:C_0805_2012Metric"
(at 302.26 171.45 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 302.26 171.45 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 302.26 171.45 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(pin "1"
(uuid "299b06dd-f42f-47c1-98ae-d0f74816099b")
)
(pin "2"
(uuid "4eb980db-42f4-43fe-bdba-41a0fb426a69")
)
(instances
(project "baby-mobile-v5"
(path "/3033f063-d051-4b2e-89f5-f1cfe4577b32"
(reference "C9")
(unit 1)
)
)
)
)
(symbol
(lib_id "Jumper:SolderJumper_2_Bridged")
(at 227.33 204.47 0)
+117 -166
View File
@@ -149,7 +149,7 @@ const uint8_t BTN_PINS[] = {
// AUDIO CONFIG
// ============================================================
#define SAMPLE_RATE 8000
#define SAMPLE_RATE 16000
#define AUDIO_BUF_SIZE 512 // larger buffer = fewer SPI transactions
#define SILENCE 128 // 0x80 = center for unsigned 8-bit
@@ -205,10 +205,8 @@ uint32_t g_trackStart[MAX_TRACKS];
uint32_t g_trackLen[MAX_TRACKS];
uint8_t g_currentTrack = 0;
// Audio double-buffer
uint8_t g_audioBuf[2][AUDIO_BUF_SIZE];
volatile uint8_t g_activeBuf = 0;
volatile uint16_t g_bufPos = 0;
// Audio DMA buffers — uint16_t PWM duty-cycle values for EasyDMA
static uint16_t g_pwmBuf[2][AUDIO_BUF_SIZE];
volatile bool g_bufReady[2] = {false, false};
uint32_t g_nextReadAddr = 0;
uint32_t g_playEnd = 0;
@@ -633,6 +631,7 @@ void audioData_write_cb(uint16_t conn_handle, BLECharacteristic* chr,
}
void setupBLE() {
/* Note BLE requires a custom app to communicate, not regular bluetooth */
Bluefruit.begin();
Bluefruit.setName("BabyMobile");
Bluefruit.setTxPower(0); // 0 dBm — save power, short range is fine
@@ -676,127 +675,155 @@ void setupBLE() {
// ============================================================
// AUDIO PLAYBACK
// AUDIO PLAYBACK — nRF52840 PWM EasyDMA
// ============================================================
//
// NRF_PWM0 drives the audio pin directly via DMA — no per-sample ISR.
// PRESCALER = 0 → 16 MHz base clock
// COUNTERTOP = 500 → PWM carrier = 16 MHz / 500 = 32 kHz
// SEQ REFRESH = 1 → each sample plays 2 carrier cycles → 16 kHz sample rate
//
// Double-buffer: SEQ[0] / SEQ[1] auto-chain via SHORTS.
// ISR fires only on SEQEND (every 512 samples = 32 ms), sets g_bufReady[b]=false.
// loop() calls audioFillBuf() to reload the finished buffer.
// nRF52840 has hardware PWM (up to 4 instances, 16MHz base clock)
// We use the nrf_pwm peripheral directly for audio output
// and a TIMER for the sample rate interrupt.
#define PWM_COUNTERTOP 500 // 16 MHz / 500 = 32 kHz carrier
#define PWM_SILENCE 250 // midpoint of 0500 duty-cycle range
// For Arduino compatibility, we use analogWrite for the PWM
// and a software timer for sample feeding.
// micros() timestamp for next audio sample (loop-driven at 8kHz)
uint32_t g_nextSampleUs = 0;
// Software gain: 1=unity, 2=2x, etc. (clips at 0/255). Adjustable via 'u'/'d' serial.
// Software gain: 1=unity, 2=2x, etc. Adjustable via 'u'/'d' serial.
static uint8_t g_audioGain = 1;
// Called from loop() every 125µs while g_playing
void audioTick(void) {
// Output sample with software gain (stretches away from center 128)
int16_t s = (int16_t)g_audioBuf[g_activeBuf][g_bufPos] - 128;
// Read PCM from current source into g_pwmBuf[b], applying gain and scaling.
// Pads the tail with silence if fewer than AUDIO_BUF_SIZE bytes remain.
static void audioFillBuf(uint8_t b) {
uint8_t pcm[AUDIO_BUF_SIZE];
uint32_t toRead = 0;
if (g_nextReadAddr < g_playEnd) {
toRead = min((uint32_t)AUDIO_BUF_SIZE, g_playEnd - g_nextReadAddr);
#ifdef POC_INTERNAL_FLASH
g_pocFile.read(pcm, toRead);
#else
flashReadBytes(g_nextReadAddr, pcm, toRead);
#endif
g_nextReadAddr += toRead;
}
for (uint32_t i = 0; i < toRead; i++) {
int16_t s = (int16_t)pcm[i] - 128;
s *= g_audioGain;
if (s > 127) s = 127;
if (s < -128) s = -128;
analogWrite(PIN_AUDIO_PWM, (uint8_t)(s + 128));
g_bufPos++;
g_pwmBuf[b][i] = (uint16_t)((uint8_t)(s + 128)) * PWM_COUNTERTOP / 256;
}
for (uint32_t i = toRead; i < AUDIO_BUF_SIZE; i++) {
g_pwmBuf[b][i] = PWM_SILENCE;
}
g_bufReady[b] = true;
}
if (g_bufPos >= AUDIO_BUF_SIZE) {
g_bufReady[g_activeBuf] = false;
g_activeBuf ^= 1;
g_bufPos = 0;
if (!g_bufReady[g_activeBuf]) {
// Buffer underrun — stop cleanly
g_playing = false;
analogWrite(PIN_AUDIO_PWM, SILENCE);
// PWM0 ISR — fires every 32 ms (512 samples at 16 kHz).
// Just signals which buffer needs refilling; loop() does the actual I/O.
extern "C" void PWM0_IRQHandler() {
if (NRF_PWM0->EVENTS_SEQEND[0]) {
NRF_PWM0->EVENTS_SEQEND[0] = 0;
if (g_playing) {
g_bufReady[0] = false;
NRF_PWM0->TASKS_SEQSTART[1] = 1; // start buf 1 immediately
}
}
if (NRF_PWM0->EVENTS_SEQEND[1]) {
NRF_PWM0->EVENTS_SEQEND[1] = 0;
if (g_playing) {
g_bufReady[1] = false;
NRF_PWM0->TASKS_SEQSTART[0] = 1; // start buf 0 immediately
}
}
}
void audioInit() {
pinMode(PIN_AUDIO_PWM, OUTPUT);
analogWrite(PIN_AUDIO_PWM, SILENCE);
analogWriteResolution(8); // 8-bit PWM
// Silence both DMA buffers
for (int i = 0; i < AUDIO_BUF_SIZE; i++) {
g_pwmBuf[0][i] = PWM_SILENCE;
g_pwmBuf[1][i] = PWM_SILENCE;
}
// Configure NRF_PWM0 for EasyDMA sequence mode
NRF_PWM0->PSEL.OUT[0] = g_ADigitalPinMap[PIN_AUDIO_PWM];
NRF_PWM0->PSEL.OUT[1] = 0x80000000UL; // disconnected
NRF_PWM0->PSEL.OUT[2] = 0x80000000UL;
NRF_PWM0->PSEL.OUT[3] = 0x80000000UL;
NRF_PWM0->ENABLE = PWM_ENABLE_ENABLE_Enabled;
NRF_PWM0->MODE = PWM_MODE_UPDOWN_Up;
NRF_PWM0->PRESCALER = PWM_PRESCALER_PRESCALER_DIV_1; // 16 MHz
NRF_PWM0->COUNTERTOP = PWM_COUNTERTOP;
NRF_PWM0->LOOP = 0;
NRF_PWM0->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) |
(PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
NRF_PWM0->SEQ[0].PTR = (uint32_t)g_pwmBuf[0];
NRF_PWM0->SEQ[0].CNT = AUDIO_BUF_SIZE;
NRF_PWM0->SEQ[0].REFRESH = 1; // each value plays for 2 PWM periods
NRF_PWM0->SEQ[0].ENDDELAY = 0;
NRF_PWM0->SEQ[1].PTR = (uint32_t)g_pwmBuf[1];
NRF_PWM0->SEQ[1].CNT = AUDIO_BUF_SIZE;
NRF_PWM0->SEQ[1].REFRESH = 1;
NRF_PWM0->SEQ[1].ENDDELAY = 0;
NRF_PWM0->SHORTS = 0; // no auto-shorts; ISR handles sequence chaining
NRF_PWM0->INTENSET = PWM_INTENSET_SEQEND0_Msk | PWM_INTENSET_SEQEND1_Msk;
NVIC_SetPriority(PWM0_IRQn, 7);
NVIC_EnableIRQ(PWM0_IRQn);
// Start PWM outputting silence (amp is off, so this is silent)
NRF_PWM0->TASKS_SEQSTART[0] = 1;
}
void audioStart(uint8_t trackNum) {
Serial.print("Playing track");
Serial.print(trackNum);
Serial.print("Playing track ");
Serial.print(trackNum + 1);
Serial.print(" of ");
Serial.println(g_numTracks);
if (trackNum >= g_numTracks) return;
g_currentTrack = trackNum;
g_playEnd = g_trackLen[trackNum];
g_nextReadAddr = 0;
#ifdef POC_INTERNAL_FLASH
if (g_pocFile) g_pocFile.close();
char fname[16];
pocFilename(trackNum, fname);
if (!g_pocFile.open(fname, FILE_O_READ)) {
Serial.print("audioStart: cannot open ");
Serial.println(fname);
Serial.print("audioStart: cannot open "); Serial.println(fname);
return;
}
// Pre-fill buf 0
uint32_t toRead = min((uint32_t)AUDIO_BUF_SIZE, g_playEnd - g_nextReadAddr);
g_pocFile.read(g_audioBuf[0], toRead);
g_nextReadAddr += toRead;
g_bufReady[0] = true;
// Pre-fill buf 1
if (g_nextReadAddr < g_playEnd) {
toRead = min((uint32_t)AUDIO_BUF_SIZE, g_playEnd - g_nextReadAddr);
g_pocFile.read(g_audioBuf[1], toRead);
g_nextReadAddr += toRead;
g_bufReady[1] = true;
} else {
memset(g_audioBuf[1], SILENCE, AUDIO_BUF_SIZE);
g_bufReady[1] = true;
}
g_nextReadAddr = 0;
g_playEnd = g_trackLen[trackNum];
#else
uint32_t start = g_trackStart[trackNum];
g_playEnd = start + g_trackLen[trackNum];
g_nextReadAddr = start;
// Pre-fill both buffers
uint32_t toRead = min((uint32_t)AUDIO_BUF_SIZE, g_playEnd - g_nextReadAddr);
flashReadBytes(g_nextReadAddr, g_audioBuf[0], toRead);
g_nextReadAddr += toRead;
g_bufReady[0] = true;
if (g_nextReadAddr < g_playEnd) {
toRead = min((uint32_t)AUDIO_BUF_SIZE, g_playEnd - g_nextReadAddr);
flashReadBytes(g_nextReadAddr, g_audioBuf[1], toRead);
g_nextReadAddr += toRead;
g_bufReady[1] = true;
} else {
memset(g_audioBuf[1], SILENCE, AUDIO_BUF_SIZE);
g_bufReady[1] = true;
}
g_nextReadAddr = g_trackStart[trackNum];
g_playEnd = g_nextReadAddr + g_trackLen[trackNum];
#endif
g_activeBuf = 0;
g_bufPos = 0;
// Stop DMA cleanly, fill both buffers, restart
NRF_PWM0->TASKS_STOP = 1;
uint32_t t = millis();
while (!NRF_PWM0->EVENTS_STOPPED && millis() - t < 10) {}
NRF_PWM0->EVENTS_STOPPED = 0;
audioFillBuf(0);
audioFillBuf(1);
NRF_PWM0->SEQ[0].PTR = (uint32_t)g_pwmBuf[0];
NRF_PWM0->SEQ[1].PTR = (uint32_t)g_pwmBuf[1];
// Enable amplifier
digitalWrite(PIN_AMP_SD, HIGH);
delay(10);
g_playing = true;
g_nextSampleUs = micros();
Serial.print("Playing track ");
Serial.println(trackNum + 1);
NRF_PWM0->TASKS_SEQSTART[0] = 1;
}
void audioStop() {
g_playing = false;
analogWrite(PIN_AUDIO_PWM, SILENCE);
NRF_PWM0->TASKS_STOP = 1;
#ifdef POC_INTERNAL_FLASH
if (g_pocFile) g_pocFile.close();
@@ -804,7 +831,6 @@ void audioStop() {
// Disable amp
digitalWrite(PIN_AMP_SD, LOW);
Serial.println("Playback stopped");
}
@@ -856,16 +882,7 @@ uint8_t buttonRead() {
void waitButtonRelease(uint8_t btn) {
unsigned long deadline = millis() + 300;
while (buttonRead() == btn && millis() < deadline) {
// Keep audio running while waiting for button release
if (g_playing) {
uint32_t now = micros();
if ((int32_t)(now - g_nextSampleUs) >= 0) {
g_nextSampleUs += 125;
audioTick();
}
}
}
while (buttonRead() == btn && millis() < deadline) {}
}
@@ -982,59 +999,14 @@ void setup() {
audioInit();
#ifdef POC_INTERNAL_FLASH
// Boot test tone: 440Hz square wave for 1 second
// If you hear a beep, the amp/PWM chain is working.
Serial.println("Boot tone disabled...");
// digitalWrite(PIN_AMP_SD, HIGH);
delay(20);
for (int i = 0; i < 8000; i++) {
// 440Hz at 8kHz sample rate: 8000/440 ≈ 18 samples/cycle
analogWrite(PIN_AUDIO_PWM, (i % 18) < 9 ? 255 : 0);
delayMicroseconds(125);
}
analogWrite(PIN_AUDIO_PWM, SILENCE);
delay(20);
digitalWrite(PIN_AMP_SD, LOW);
Serial.println("Boot tone done");
// Hex dump track 0 + blocking playback diagnostic
// (after audioInit so amp/PWM are ready)
if (g_numTracks > 0) {
char diagName[16]; pocFilename(0, diagName);
File diagF(InternalFS);
if (diagF.open(diagName, FILE_O_READ)) {
uint32_t fsz = diagF.size();
Serial.print("Track0 size: "); Serial.println(fsz);
uint8_t hbuf[64];
int nr = diagF.read(hbuf, sizeof(hbuf));
diagF.close();
Serial.print("First "); Serial.print(nr); Serial.println(" bytes (hex):");
for (int i = 0; i < nr; i++) {
if (hbuf[i] < 0x10) Serial.print("0");
Serial.print(hbuf[i], HEX);
Serial.print(i % 16 == 15 ? "\n" : " ");
}
Serial.println();
} else {
Serial.println("Cannot open track0 for read!");
}
Serial.println("Diag: blocking play track0 for 3s...");
Serial.println("Diag: playing track0 for 3s...");
audioStart(0);
uint32_t diagEnd = millis() + 3000;
while (millis() < diagEnd && g_playing) {
uint32_t now = micros();
if ((int32_t)(now - g_nextSampleUs) >= 0) {
g_nextSampleUs += 125;
audioTick();
}
for (uint8_t b = 0; b < 2; b++) {
if (!g_bufReady[b] && g_nextReadAddr < g_playEnd) {
uint32_t toRead = min((uint32_t)AUDIO_BUF_SIZE, g_playEnd - g_nextReadAddr);
g_pocFile.read(g_audioBuf[b], toRead);
for (uint32_t i = toRead; i < AUDIO_BUF_SIZE; i++) g_audioBuf[b][i] = SILENCE;
g_nextReadAddr += toRead;
g_bufReady[b] = true;
audioFillBuf(b);
}
}
}
@@ -1212,43 +1184,22 @@ static void serUploadTick() {
#endif
void loop() {
// ---- Audio sample output (8kHz, loop-driven) ----
if (g_playing) {
g_lastActivity = millis(); // stay awake while audio is playing
uint32_t now = micros();
if ((int32_t)(now - g_nextSampleUs) >= 0) {
g_nextSampleUs += 125; // 1/8000s = 125µs
audioTick();
}
}
if (g_playing) g_lastActivity = millis();
// ---- Serial upload (POC mode) ----
#ifdef POC_INTERNAL_FLASH
serUploadTick();
#endif
// ---- Refill audio buffers ----
// ---- Refill audio buffers (ISR sets g_bufReady[b]=false when buffer done) ----
if (g_playing) {
for (uint8_t b = 0; b < 2; b++) {
if (!g_bufReady[b] && g_nextReadAddr < g_playEnd) {
uint32_t remaining = g_playEnd - g_nextReadAddr;
uint32_t toRead = min((uint32_t)AUDIO_BUF_SIZE, remaining);
#ifdef POC_INTERNAL_FLASH
g_pocFile.read(g_audioBuf[b], toRead);
#else
flashReadBytes(g_nextReadAddr, g_audioBuf[b], toRead);
#endif
// Pad with silence
for (uint32_t i = toRead; i < AUDIO_BUF_SIZE; i++) {
g_audioBuf[b][i] = SILENCE;
}
g_nextReadAddr += toRead;
g_bufReady[b] = true;
audioFillBuf(b);
}
}
// Track finished? Auto-advance and loop
// Track finished — both buffers drained and no data left
if (!g_bufReady[0] && !g_bufReady[1] && g_nextReadAddr >= g_playEnd) {
audioStop();
if (g_numTracks > 0) {
+6 -6
View File
@@ -44,12 +44,12 @@ def convert_to_pcm(path: str) -> bytes:
sample_rate = struct.unpack_from("<I", data, 24)[0]
bits_per_samp = struct.unpack_from("<H", data, 34)[0]
needs_convert = (audio_format != 1 or num_channels != 1
or sample_rate != 8000 or bits_per_samp != 8)
or sample_rate != 16000 or bits_per_samp != 8)
except Exception:
needs_convert = True
if not needs_convert:
print("WAV is already 8-bit mono 8kHz — stripping header")
print("WAV is already 8-bit mono 16kHz — stripping header")
return data[44:]
print("WAV needs resampling — converting via ffmpeg")
# fall through to ffmpeg conversion
@@ -64,14 +64,14 @@ def convert_to_pcm(path: str) -> bytes:
print(f"Converting {os.path.basename(path)} via ffmpeg...")
result = subprocess.run(
['ffmpeg', '-y', '-i', path,
'-ar', '8000', '-ac', '1', '-f', 'u8', '-acodec', 'pcm_u8', 'pipe:1'],
'-ar', '16000', '-ac', '1', '-f', 'u8', '-acodec', 'pcm_u8', 'pipe:1'],
capture_output=True
)
if result.returncode != 0:
sys.exit(f"ERROR: ffmpeg failed:\n{result.stderr.decode(errors='replace')[-400:]}")
pcm = result.stdout
duration = len(pcm) / 8000
duration = len(pcm) / 16000
print(f" Converted: {len(pcm):,} bytes ({duration:.1f}s)")
return pcm
@@ -133,10 +133,10 @@ def upload(port: str, track_num: int, pcm: bytes) -> None:
if "at=" in err_line:
accepted = int(err_line.split("at=")[1].split()[0])
safe = int(accepted * 0.9) # 10% headroom
max_s = safe / 8000
max_s = safe / 16000
print(f" LittleFS accepted {accepted} bytes before full.")
print(f" Safe clip length: ~{max_s:.1f}s")
print(f" Trim with: ffmpeg -i input.mp3 -t {max_s:.0f} -ar 8000 -ac 1 -f u8 out.raw")
print(f" Trim with: ffmpeg -i input.mp3 -t {max_s:.0f} -ar 16000 -ac 1 -f u8 out.raw")
sys.exit(1)
# Wait for OK
File diff suppressed because one or more lines are too long