Implement advanced gestures (1=next, 2=pause, 3=prev, 4=vol) and ESP32-audioI2S MP3/WAV playback
This commit is contained in:
@@ -1,186 +1,94 @@
|
|||||||
#include "Audio_Player.h"
|
#include "Audio_Player.h"
|
||||||
#include <ESP_I2S.h>
|
#include "Audio.h"
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
I2SClass i2s;
|
Audio audio;
|
||||||
TaskHandle_t audioTaskHandle = NULL;
|
TaskHandle_t audioTaskHandle = NULL;
|
||||||
File audioFile;
|
volatile bool isAudioTaskRunning = false;
|
||||||
volatile bool isAudioPlaying = false;
|
uint8_t currentVolume = 12; // 0 to 21 range
|
||||||
char currentPlayPath[128];
|
|
||||||
|
|
||||||
uint16_t Wav_Count = 0;
|
uint16_t Audio_Count = 0;
|
||||||
char Wav_Names[100][100];
|
char Audio_Names[100][100];
|
||||||
|
|
||||||
void Search_Wavs(const char* directory) {
|
// FreeRTOS background task on Core 0 to run ESP32-audioI2S decode loop
|
||||||
Wav_Count = Folder_retrieval(directory, ".wav", Wav_Names, 100);
|
void audioLoopTask(void* parameter) {
|
||||||
|
isAudioTaskRunning = true;
|
||||||
|
while (isAudioTaskRunning) {
|
||||||
|
audio.loop();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1)); // Yield to let other RTOS tasks run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Parse WAV header and skip meta chunks (like LIST) to find the "fmt " and "data" segments
|
|
||||||
bool parseWavHeader(File& file, uint32_t& sampleRate, uint16_t& channels, uint16_t& bitsPerSample, uint32_t& dataStartOffset, uint32_t& dataSize) {
|
|
||||||
char id[4];
|
|
||||||
|
|
||||||
// Read RIFF header
|
|
||||||
file.seek(0);
|
|
||||||
if (file.readBytes(id, 4) != 4 || memcmp(id, "RIFF", 4) != 0) {
|
|
||||||
printf("WAV Header Error: RIFF signature not found!\r\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.seek(8);
|
|
||||||
if (file.readBytes(id, 4) != 4 || memcmp(id, "WAVE", 4) != 0) {
|
|
||||||
printf("WAV Header Error: WAVE signature not found!\r\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t offset = 12;
|
|
||||||
bool foundFmt = false;
|
|
||||||
bool foundData = false;
|
|
||||||
|
|
||||||
while (offset < file.size() && (!foundFmt || !foundData)) {
|
|
||||||
file.seek(offset);
|
|
||||||
if (file.readBytes(id, 4) != 4) break;
|
|
||||||
|
|
||||||
uint32_t chunkSize;
|
|
||||||
if (file.read((uint8_t*)&chunkSize, 4) != 4) break;
|
|
||||||
|
|
||||||
offset += 8; // move past chunk ID and Size field
|
|
||||||
|
|
||||||
if (memcmp(id, "fmt ", 4) == 0) {
|
|
||||||
uint16_t audioFormat;
|
|
||||||
file.read((uint8_t*)&audioFormat, 2);
|
|
||||||
if (audioFormat != 1) { // 1 = PCM (uncompressed)
|
|
||||||
printf("WAV Header Error: Only uncompressed PCM WAV is supported!\r\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
file.read((uint8_t*)&channels, 2);
|
|
||||||
file.read((uint8_t*)&sampleRate, 4);
|
|
||||||
file.seek(offset + 8); // skip byte rate and block align
|
|
||||||
file.read((uint8_t*)&bitsPerSample, 2);
|
|
||||||
foundFmt = true;
|
|
||||||
}
|
|
||||||
else if (memcmp(id, "data", 4) == 0) {
|
|
||||||
dataStartOffset = offset;
|
|
||||||
dataSize = chunkSize;
|
|
||||||
foundData = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return foundFmt && foundData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void audioPlayTask(void* parameter) {
|
|
||||||
uint32_t sampleRate = 44100;
|
|
||||||
uint16_t channels = 2;
|
|
||||||
uint16_t bitsPerSample = 16;
|
|
||||||
uint32_t dataStartOffset = 44;
|
|
||||||
uint32_t dataSize = 0;
|
|
||||||
|
|
||||||
audioFile = SD_MMC.open(currentPlayPath, "r");
|
|
||||||
if (!audioFile) {
|
|
||||||
printf("Failed to open audio file: %s\r\n", currentPlayPath);
|
|
||||||
isAudioPlaying = false;
|
|
||||||
audioTaskHandle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parseWavHeader(audioFile, sampleRate, channels, bitsPerSample, dataStartOffset, dataSize)) {
|
|
||||||
printf("Failed to parse WAV header\r\n");
|
|
||||||
audioFile.close();
|
|
||||||
isAudioPlaying = false;
|
|
||||||
audioTaskHandle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("WAV File: Rate=%d Hz, Channels=%d, Bits=%d, Size=%d bytes\r\n",
|
|
||||||
sampleRate, channels, bitsPerSample, dataSize);
|
|
||||||
|
|
||||||
// Configure I2S based on WAV parameters
|
|
||||||
i2s_data_bit_width_t bitWidth = I2S_DATA_BIT_WIDTH_16BIT;
|
|
||||||
if (bitsPerSample == 8) bitWidth = I2S_DATA_BIT_WIDTH_8BIT;
|
|
||||||
else if (bitsPerSample == 24) bitWidth = I2S_DATA_BIT_WIDTH_24BIT;
|
|
||||||
else if (bitsPerSample == 32) bitWidth = I2S_DATA_BIT_WIDTH_32BIT;
|
|
||||||
|
|
||||||
i2s_slot_mode_t slotMode = (channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
|
||||||
|
|
||||||
// Set standard pins
|
|
||||||
i2s.setPins(I2S_BCLK, I2S_LRCK, I2S_DIN, -1, -1);
|
|
||||||
|
|
||||||
// Stop any active driver configuration
|
|
||||||
i2s.end();
|
|
||||||
|
|
||||||
if (!i2s.begin(I2S_MODE_STD, sampleRate, bitWidth, slotMode)) {
|
|
||||||
printf("Failed to initialize I2S!\r\n");
|
|
||||||
audioFile.close();
|
|
||||||
isAudioPlaying = false;
|
|
||||||
audioTaskHandle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek past the parsed headers to raw data
|
|
||||||
audioFile.seek(dataStartOffset);
|
|
||||||
|
|
||||||
const size_t bufferSize = 1024;
|
|
||||||
uint8_t buffer[bufferSize];
|
|
||||||
uint32_t bytesRead = 0;
|
|
||||||
uint32_t totalBytesWritten = 0;
|
|
||||||
|
|
||||||
while (isAudioPlaying && totalBytesWritten < dataSize) {
|
|
||||||
size_t toRead = std::min(bufferSize, (size_t)(dataSize - totalBytesWritten));
|
|
||||||
bytesRead = audioFile.read(buffer, toRead);
|
|
||||||
if (bytesRead == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t written = i2s.write(buffer, bytesRead);
|
|
||||||
if (written == 0) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
totalBytesWritten += written;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Playback done. Written %d bytes.\r\n", totalBytesWritten);
|
|
||||||
audioFile.close();
|
|
||||||
i2s.end();
|
|
||||||
isAudioPlaying = false;
|
|
||||||
audioTaskHandle = NULL;
|
audioTaskHandle = NULL;
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void playWav(const char* filePath) {
|
void initAudioPlayer() {
|
||||||
if (isAudioPlaying) {
|
audio.setPinout(I2S_BCLK, I2S_LRCK, I2S_DIN);
|
||||||
stopWav();
|
audio.setVolume(currentVolume);
|
||||||
}
|
|
||||||
strncpy(currentPlayPath, filePath, sizeof(currentPlayPath) - 1);
|
|
||||||
currentPlayPath[sizeof(currentPlayPath) - 1] = '\0';
|
|
||||||
isAudioPlaying = true;
|
|
||||||
|
|
||||||
|
// Start the background loop task on Core 0
|
||||||
|
if (audioTaskHandle == NULL) {
|
||||||
xTaskCreatePinnedToCore(
|
xTaskCreatePinnedToCore(
|
||||||
audioPlayTask,
|
audioLoopTask,
|
||||||
"audioPlayTask",
|
"audioLoopTask",
|
||||||
4096,
|
8192, // Stack size (ESP32-audioI2S requires a larger stack size for decoding)
|
||||||
NULL,
|
NULL,
|
||||||
5,
|
5, // Priority
|
||||||
&audioTaskHandle,
|
&audioTaskHandle,
|
||||||
0
|
0 // Core 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopWav() {
|
|
||||||
if (isAudioPlaying) {
|
|
||||||
isAudioPlaying = false;
|
|
||||||
while (audioTaskHandle != NULL) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWavPlaying() {
|
void playAudio(const char* filePath) {
|
||||||
return isAudioPlaying;
|
printf("AudioPlayer: Connecting to FS for file: %s\r\n", filePath);
|
||||||
|
audio.connecttoFS(SD_MMC, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopAudio() {
|
||||||
|
audio.stopSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pauseResumeAudio() {
|
||||||
|
audio.pauseResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAudioPlaying() {
|
||||||
|
return audio.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPlayerVolume(uint8_t volume) {
|
||||||
|
if (volume > 21) volume = 21;
|
||||||
|
currentVolume = volume;
|
||||||
|
audio.setVolume(currentVolume);
|
||||||
|
printf("AudioPlayer: Volume set to %d / 21\r\n", currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getPlayerVolume() {
|
||||||
|
return currentVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Search_Audio(const char* directory) {
|
||||||
|
File Path = SD_MMC.open(directory);
|
||||||
|
if (!Path) {
|
||||||
|
printf("Path: <%s> does not exist\r\n", directory);
|
||||||
|
Audio_Count = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t fileCount = 0;
|
||||||
|
File file = Path.openNextFile();
|
||||||
|
while (file && fileCount < 100) {
|
||||||
|
if (!file.isDirectory()) {
|
||||||
|
const char* name = file.name();
|
||||||
|
// Match WAV or MP3 files
|
||||||
|
if (strstr(name, ".wav") || strstr(name, ".mp3") || strstr(name, ".WAV") || strstr(name, ".MP3")) {
|
||||||
|
strncpy(Audio_Names[fileCount], name, sizeof(Audio_Names[fileCount]) - 1);
|
||||||
|
Audio_Names[fileCount][sizeof(Audio_Names[fileCount]) - 1] = '\0';
|
||||||
|
fileCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file = Path.openNextFile();
|
||||||
|
}
|
||||||
|
Path.close();
|
||||||
|
Audio_Count = fileCount;
|
||||||
|
printf("Found %d audio files (.mp3/.wav)\r\n", Audio_Count);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
#define I2S_LRCK 2
|
#define I2S_LRCK 2
|
||||||
#define I2S_DIN 4
|
#define I2S_DIN 4
|
||||||
|
|
||||||
void playWav(const char* filePath);
|
void initAudioPlayer();
|
||||||
void stopWav();
|
void playAudio(const char* filePath);
|
||||||
bool isWavPlaying();
|
void stopAudio();
|
||||||
|
void pauseResumeAudio();
|
||||||
void Search_Wavs(const char* directory);
|
bool isAudioPlaying();
|
||||||
extern uint16_t Wav_Count;
|
void setPlayerVolume(uint8_t volume);
|
||||||
extern char Wav_Names[100][100];
|
uint8_t getPlayerVolume();
|
||||||
|
|
||||||
|
void Search_Audio(const char* directory);
|
||||||
|
extern uint16_t Audio_Count;
|
||||||
|
extern char Audio_Names[100][100];
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ uint32_t lastLedPulseTime = 0;
|
|||||||
uint8_t pulseBrightness = 0;
|
uint8_t pulseBrightness = 0;
|
||||||
bool pulseDirection = true;
|
bool pulseDirection = true;
|
||||||
|
|
||||||
|
// Click gesture timers
|
||||||
|
uint32_t lastButtonReleaseTime = 0;
|
||||||
|
uint32_t buttonPressStartTime = 0;
|
||||||
|
uint8_t clickCount = 0;
|
||||||
|
bool isButtonPressed = false;
|
||||||
|
bool longPressTriggered = false;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
@@ -55,12 +62,15 @@ void setup() {
|
|||||||
|
|
||||||
// Retrieve media files from SD Card
|
// Retrieve media files from SD Card
|
||||||
Search_Images("/");
|
Search_Images("/");
|
||||||
Search_Wavs("/");
|
Search_Audio("/");
|
||||||
|
|
||||||
|
// Initialize ESP32-audioI2S player
|
||||||
|
initAudioPlayer();
|
||||||
|
|
||||||
printf("Initialization complete!\r\n");
|
printf("Initialization complete!\r\n");
|
||||||
printf("Found %d PNG images and %d WAV soundbites.\r\n", Image_Count, Wav_Count);
|
printf("Found %d PNG images and %d Audio files (.mp3/.wav).\r\n", Image_Count, Audio_Count);
|
||||||
|
|
||||||
if (Image_Count == 0 || Wav_Count == 0) {
|
if (Image_Count == 0 || Audio_Count == 0) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
currentState = STATE_ERROR;
|
currentState = STATE_ERROR;
|
||||||
printf("ERROR: No media found on microSD card!\r\n");
|
printf("ERROR: No media found on microSD card!\r\n");
|
||||||
@@ -72,13 +82,13 @@ void setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void playCurrentPair() {
|
void playCurrentPair() {
|
||||||
if (Image_Count == 0 && Wav_Count == 0) {
|
if (Image_Count == 0 && Audio_Count == 0) {
|
||||||
printf("Cannot play: missing PNG images and WAV soundbites!\r\n");
|
printf("Cannot play: missing PNG images and Audio files!\r\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop current playing audio if any
|
// Stop current playing audio if any
|
||||||
stopWav();
|
stopAudio();
|
||||||
|
|
||||||
printf("Playing item index %d:\r\n", currentIndex);
|
printf("Playing item index %d:\r\n", currentIndex);
|
||||||
|
|
||||||
@@ -88,12 +98,76 @@ void playCurrentPair() {
|
|||||||
Display_Image_By_Index("/", pngIndex);
|
Display_Image_By_Index("/", pngIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play the soundbite
|
// Play the audio bite
|
||||||
if (Wav_Count > 0) {
|
if (Audio_Count > 0) {
|
||||||
uint16_t wavIndex = currentIndex % Wav_Count;
|
uint16_t audioIndex = currentIndex % Audio_Count;
|
||||||
String soundPath = String("/") + Wav_Names[wavIndex];
|
String soundPath = String("/") + Audio_Names[audioIndex];
|
||||||
printf("Playing sound: %s\r\n", soundPath.c_str());
|
printf("Playing audio: %s\r\n", soundPath.c_str());
|
||||||
playWav(soundPath.c_str());
|
playAudio(soundPath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSinglePress() {
|
||||||
|
printf("Gesture: Single Click - Next Slide\r\n");
|
||||||
|
if (!hasError) {
|
||||||
|
if (currentState == STATE_AUTOPLAY) {
|
||||||
|
currentState = STATE_IDLE;
|
||||||
|
printf("Autoplay Disabled.\r\n");
|
||||||
|
}
|
||||||
|
currentIndex = (currentIndex + 1) % std::max(Image_Count, Audio_Count);
|
||||||
|
playCurrentPair();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleDoublePress() {
|
||||||
|
printf("Gesture: Double Click - Pause / Resume Audio\r\n");
|
||||||
|
if (!hasError) {
|
||||||
|
pauseResumeAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTriplePress() {
|
||||||
|
printf("Gesture: Triple Click - Previous Slide\r\n");
|
||||||
|
if (!hasError) {
|
||||||
|
if (currentState == STATE_AUTOPLAY) {
|
||||||
|
currentState = STATE_IDLE;
|
||||||
|
printf("Autoplay Disabled.\r\n");
|
||||||
|
}
|
||||||
|
uint16_t totalMedia = std::max(Image_Count, Audio_Count);
|
||||||
|
currentIndex = (currentIndex + totalMedia - 1) % totalMedia;
|
||||||
|
playCurrentPair();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleQuadruplePress() {
|
||||||
|
if (!hasError) {
|
||||||
|
uint8_t nextVol = getPlayerVolume() + 4;
|
||||||
|
if (nextVol > 20) nextVol = 4; // Loop back
|
||||||
|
setPlayerVolume(nextVol);
|
||||||
|
|
||||||
|
// Quick flash NeoPixel white to indicate volume change
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
neopixelWrite(PIN_NEOPIXEL, 64, 64, 64);
|
||||||
|
delay(50);
|
||||||
|
neopixelWrite(PIN_NEOPIXEL, 0, 0, 0);
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleLongPress() {
|
||||||
|
printf("Gesture: Long Press - Toggle Autoplay Slideshow\r\n");
|
||||||
|
if (!hasError) {
|
||||||
|
if (currentState == STATE_AUTOPLAY) {
|
||||||
|
currentState = STATE_IDLE;
|
||||||
|
printf("Autoplay Disabled. Manual Mode active.\r\n");
|
||||||
|
stopAudio();
|
||||||
|
} else {
|
||||||
|
currentState = STATE_AUTOPLAY;
|
||||||
|
printf("Autoplay Enabled! Cycling slides every %d ms.\r\n", slideShowInterval);
|
||||||
|
lastSlideTime = millis();
|
||||||
|
playCurrentPair();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +195,7 @@ void updateLEDStatus() {
|
|||||||
neopixelWrite(PIN_NEOPIXEL, 0, 0, 0);
|
neopixelWrite(PIN_NEOPIXEL, 0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isWavPlaying()) {
|
else if (isAudioPlaying()) {
|
||||||
// Pulsing Green
|
// Pulsing Green
|
||||||
neopixelWrite(PIN_NEOPIXEL, 0, pulseBrightness, 0);
|
neopixelWrite(PIN_NEOPIXEL, 0, pulseBrightness, 0);
|
||||||
}
|
}
|
||||||
@@ -137,62 +211,54 @@ void updateLEDStatus() {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
bool currentButtonReading = digitalRead(BOOT_KEY_PIN);
|
bool currentButtonReading = digitalRead(BOOT_KEY_PIN);
|
||||||
static uint32_t buttonPressStartTime = 0;
|
uint32_t now = millis();
|
||||||
static bool buttonWasHeld = false;
|
|
||||||
|
|
||||||
// Detect changes in button state
|
// Detect state transitions for debouncing and gesture capturing
|
||||||
if (currentButtonReading != lastButtonState) {
|
if (currentButtonReading == LOW && lastButtonState == HIGH) {
|
||||||
lastDebounceTime = millis();
|
// Button Pressed down
|
||||||
if (currentButtonReading == LOW) {
|
lastDebounceTime = now;
|
||||||
buttonPressStartTime = millis();
|
buttonPressStartTime = now;
|
||||||
buttonWasHeld = false;
|
isButtonPressed = true;
|
||||||
|
longPressTriggered = false;
|
||||||
|
}
|
||||||
|
else if (currentButtonReading == LOW && lastButtonState == LOW && isButtonPressed) {
|
||||||
|
// Button is held down
|
||||||
|
if (!longPressTriggered && (now - buttonPressStartTime > 1500)) {
|
||||||
|
longPressTriggered = true;
|
||||||
|
handleLongPress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (currentButtonReading == HIGH && lastButtonState == LOW) {
|
||||||
|
// Button Released
|
||||||
|
isButtonPressed = false;
|
||||||
|
uint32_t pressDuration = now - buttonPressStartTime;
|
||||||
|
|
||||||
|
if (!longPressTriggered && pressDuration > 50) { // Debounced release
|
||||||
|
clickCount++;
|
||||||
|
lastButtonReleaseTime = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((millis() - lastDebounceTime) > debounceDelay) {
|
// Process click gestures after 300ms timeout has elapsed since last release
|
||||||
if (currentButtonReading == LOW) {
|
if (clickCount > 0 && !isButtonPressed && (now - lastButtonReleaseTime > 300)) {
|
||||||
// Button is being held down. Check if it exceeds long-press threshold (1.5 seconds)
|
if (clickCount == 1) {
|
||||||
if (!buttonWasHeld && (millis() - buttonPressStartTime > 1500)) {
|
handleSinglePress();
|
||||||
buttonWasHeld = true; // Mark as held to prevent repeated triggers
|
} else if (clickCount == 2) {
|
||||||
if (!hasError) {
|
handleDoublePress();
|
||||||
if (currentState == STATE_AUTOPLAY) {
|
} else if (clickCount == 3) {
|
||||||
currentState = STATE_IDLE;
|
handleTriplePress();
|
||||||
printf("Autoplay Disabled. Manual Mode active.\r\n");
|
} else if (clickCount >= 4) {
|
||||||
stopWav();
|
handleQuadruplePress();
|
||||||
} else {
|
|
||||||
currentState = STATE_AUTOPLAY;
|
|
||||||
printf("Autoplay Enabled! Cycling slides every %d ms.\r\n", slideShowInterval);
|
|
||||||
lastSlideTime = millis();
|
|
||||||
playCurrentPair();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Button was released
|
|
||||||
if (lastButtonState == LOW && !buttonWasHeld) {
|
|
||||||
// Single press trigger
|
|
||||||
printf("Single press detected!\r\n");
|
|
||||||
if (!hasError) {
|
|
||||||
if (currentState == STATE_AUTOPLAY) {
|
|
||||||
currentState = STATE_IDLE;
|
|
||||||
printf("Autoplay Disabled via manual navigation.\r\n");
|
|
||||||
}
|
|
||||||
playCurrentPair();
|
|
||||||
currentIndex = (currentIndex + 1) % std::max(Image_Count, Wav_Count);
|
|
||||||
} else {
|
|
||||||
printf("System in error state (missing files). Can't cycle.\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buttonWasHeld = false;
|
|
||||||
}
|
}
|
||||||
|
clickCount = 0; // Reset counter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle slideshow auto-advance in Autoplay mode
|
// Handle slideshow auto-advance in Autoplay mode
|
||||||
if (currentState == STATE_AUTOPLAY && !hasError) {
|
if (currentState == STATE_AUTOPLAY && !hasError) {
|
||||||
// Advance if the audio finished playing and 5 seconds has elapsed
|
// Only advance when the audio stops playing and slideShowInterval (5s) has passed
|
||||||
if (!isWavPlaying() && (millis() - lastSlideTime > slideShowInterval)) {
|
if (!isAudioPlaying() && (now - lastSlideTime > slideShowInterval)) {
|
||||||
lastSlideTime = millis();
|
lastSlideTime = now;
|
||||||
currentIndex = (currentIndex + 1) % std::max(Image_Count, Wav_Count);
|
currentIndex = (currentIndex + 1) % std::max(Image_Count, Audio_Count);
|
||||||
playCurrentPair();
|
playCurrentPair();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user