redid commit e0e011e and also added Vendor ESP32-audioI2S into lib/ with ES8311 APLL + set-once I2S edits

This commit is contained in:
pelgraine
2026-06-03 17:59:38 +10:00
parent 2ffdb5e090
commit 8c6b3b1029
5 changed files with 72 additions and 6 deletions
+4 -2
View File
@@ -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();
@@ -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;
}
+3 -1
View File
@@ -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)
+2
View File
@@ -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; }
-2
View File
@@ -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)