From 8c6b3b1029d25a8d827630c60b95f7542df8e51b Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:59:38 +1000 Subject: [PATCH] redid commit e0e011e and also added Vendor ESP32-audioI2S into lib/ with ES8311 APLL + set-once I2S edits --- examples/companion_radio/main.cpp | 6 +- .../ui-new/Audiobookplayerscreen.h | 64 ++++++++++++++++++- examples/companion_radio/ui-new/UITask.cpp | 4 +- examples/companion_radio/ui-new/UITask.h | 2 + variants/lilygo_tdeck_max/platformio.ini | 2 - 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 119e75b5..8c4281ba 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -2841,6 +2841,7 @@ void loop() { digitalWrite(41, LOW); #endif notifTonePlaying = false; + ui_task.setNotifAudioActive(false); notifFadingOut = false; notifMuted = false; notifDurationMs = 0; @@ -2943,9 +2944,10 @@ void loop() { } #endif - // Ramp volume up to max - audio->setVolume(21); + // Ramp volume up to playback level + audio->setVolume(17); notifTonePlaying = true; + ui_task.setNotifAudioActive(true); notifFadingOut = false; notifMuted = false; notifStartMs = millis(); diff --git a/examples/companion_radio/ui-new/Audiobookplayerscreen.h b/examples/companion_radio/ui-new/Audiobookplayerscreen.h index e355c8ae..98561651 100644 --- a/examples/companion_radio/ui-new/Audiobookplayerscreen.h +++ b/examples/companion_radio/ui-new/Audiobookplayerscreen.h @@ -376,8 +376,61 @@ private: return String(AB_BOOKMARK_FOLDER) + "/" + base + ".bmk"; } + // True when the current folder is the music library (/audiobooks/music or any + // subfolder). Music files are played without bookmarks: no position is saved + // or restored, and any stale bookmark for the file is deleted when it opens. + bool isMusicPath() const { + String musicRoot = String(AUDIOBOOKS_FOLDER) + "/music"; + return _currentPath == musicRoot || _currentPath.startsWith(musicRoot + "/"); + } + + // Compute WAV playback duration (seconds) from the file header by reading the + // fmt chunk's byte rate and the data chunk's size. Returns 0 if the header + // can't be parsed (caller falls back to the during-playback bitrate estimate). + uint32_t parseWavDurationSec(File& f) { + if (!f) return 0; + f.seek(0); + uint8_t hdr[12]; + if (f.read(hdr, 12) != 12) return 0; + if (memcmp(hdr, "RIFF", 4) != 0 || memcmp(hdr + 8, "WAVE", 4) != 0) return 0; + + uint32_t byteRate = 0; + uint32_t dataSize = 0; + while (f.available() >= 8) { + uint8_t ch[8]; + if (f.read(ch, 8) != 8) break; + uint32_t sz = (uint32_t)ch[4] | ((uint32_t)ch[5] << 8) | + ((uint32_t)ch[6] << 16) | ((uint32_t)ch[7] << 24); + if (memcmp(ch, "fmt ", 4) == 0) { + uint8_t fmt[16]; + uint32_t toRead = sz < 16 ? sz : 16; + if ((uint32_t)f.read(fmt, toRead) != toRead) break; + // byteRate is bytes 8-11 of the fmt body (after format, channels, rate) + byteRate = (uint32_t)fmt[8] | ((uint32_t)fmt[9] << 8) | + ((uint32_t)fmt[10] << 16) | ((uint32_t)fmt[11] << 24); + if (sz > toRead) f.seek(f.position() + (sz - toRead)); + if (sz & 1) f.seek(f.position() + 1); // chunks are word-aligned + } else if (memcmp(ch, "data", 4) == 0) { + dataSize = sz; + break; // have what we need + } else { + f.seek(f.position() + sz + (sz & 1)); // skip other chunks (word-aligned) + } + } + if (byteRate == 0 || dataSize == 0) return 0; + return dataSize / byteRate; + } + void loadBookmark() { String path = getBookmarkPath(_currentFile); + if (isMusicPath()) { + // Music files don't use bookmarks — delete any stale one and start at 0. + if (SD.exists(path.c_str())) { + SD.remove(path.c_str()); + digitalWrite(SDCARD_CS, HIGH); + } + return; + } File f = SD.open(path.c_str(), FILE_READ); if (!f) return; @@ -394,6 +447,7 @@ private: void saveBookmark() { if (!_bookOpen || _currentFile.length() == 0) return; + if (isMusicPath()) return; // Music files don't use bookmarks if (!SD.exists(AB_BOOKMARK_FOLDER)) { SD.mkdir(AB_BOOKMARK_FOLDER); @@ -973,6 +1027,11 @@ private: int dot = base.lastIndexOf('.'); if (dot > 0) base = base.substring(0, dot); strncpy(_metadata.title, base.c_str(), M4B_MAX_TITLE - 1); + // WAV: exact duration is in the header, so show it at open. + if (lower.endsWith(".wav")) { + uint32_t wavSec = parseWavDurationSec(file); + if (wavSec > 0) _durationSec = wavSec; + } } file.close(); } @@ -1626,7 +1685,10 @@ public: // auto-refresh once per minute (for time/progress updates). Key presses // always trigger immediate refresh regardless of this interval. // When paused or stopped, refresh every 5s as normal. - if (_isPlaying && !_isPaused) return 60000; // 1 min between auto-refreshes + if (_isPlaying && !_isPaused) { + if (_durationSec == 0) return 1000; // refresh quickly until duration resolves + return 60000; // then back to once-a-minute (avoids stutter) + } return 5000; } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 41514849..40e255b4 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2019,7 +2019,9 @@ if (curr) curr->poll(); // Defer display refresh while BLE is actively transferring contacts. // E-ink partial update blocks for ~820ms, stalling the BLE send queue // and adding ~1.6s of dead time to a full contact sync. - if (_serial != NULL && _serial->hasPendingData()) { + if (_notifAudioActive) { + _next_refresh = millis() + 200; // Defer e-ink refresh during notif tone (SPI bus contention) + } else if (_serial != NULL && _serial->hasPendingData()) { _next_refresh = millis() + 500; // Re-check in 500ms } else { // Sync dark mode with prefs (settings toggle takes effect here) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 691dd93a..f3b15230 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -51,6 +51,7 @@ class UITask : public AbstractUITask { GenericVibration vibration; #endif unsigned long _next_refresh, _auto_off; + bool _notifAudioActive = false; // True while a notification tone plays; defers e-ink refresh (SPI bus contention) unsigned long _kb_flash_off_at; // Keyboard flash turn-off timer #ifdef HAS_4G_MODEM bool _incomingCallRinging; // Currently ringing (incoming call) @@ -221,6 +222,7 @@ public: #endif void showAlert(const char* text, int duration_millis) override; void forceRefresh() override { _next_refresh = 100; } + void setNotifAudioActive(bool active) { _notifAudioActive = active; } void showBootHint(bool immediate = false); // Show navigation hint overlay on first boot void dismissBootHint(); // Dismiss hint and save preference bool isHintActive() const { return _hintActive; } diff --git a/variants/lilygo_tdeck_max/platformio.ini b/variants/lilygo_tdeck_max/platformio.ini index 70a57908..dd824229 100644 --- a/variants/lilygo_tdeck_max/platformio.ini +++ b/variants/lilygo_tdeck_max/platformio.ini @@ -183,7 +183,6 @@ build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} lib_deps = ${LilyGo_TDeck_Pro_Max.lib_deps} densaugeo/base64 @ ~1.4.0 - https://github.com/schreibfaul1/ESP32-audioI2S.git#2.0.6 bitbank2/JPEGDEC ; MAX + WiFi companion (WiFi app bridging — no BLE, higher contact limit) @@ -212,7 +211,6 @@ build_src_filter = ${LilyGo_TDeck_Pro_Max.build_src_filter} lib_deps = ${LilyGo_TDeck_Pro_Max.lib_deps} densaugeo/base64 @ ~1.4.0 - https://github.com/schreibfaul1/ESP32-audioI2S.git#2.0.6 bitbank2/JPEGDEC ; MAX standalone (no BLE/WiFi — maximum battery life, LoRa mesh only)