Adafruit_Learning_System_Gu.../M4_Eyes/user_fizzgig.cpp
2022-02-23 13:22:52 -05:00

207 lines
6.7 KiB
C++

// SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
#include "globals.h"
#include <Servo.h>
// Servo stuff
Servo myservo;
#define SERVO_MOUTH_OPEN 750 // Servo pulse microseconds
#define SERVO_MOUTH_CLOSED 1850
#define SERVO_PIN 3
#define BUTTON_PIN 2
// WAV player stuff
#define WAV_BUFFER_SIZE 256
static uint8_t wavBuf[2][WAV_BUFFER_SIZE];
static File wavFile;
static bool playing = false;
static int remainingBytesInChunk;
static uint8_t activeBuf;
static uint16_t bufIdx, bufEnd, nextBufEnd;
static bool startWav(char *filename);
static void wavOutCallback(void);
static uint32_t wavEventTime; // WAV start or end time, in ms
static const char *wav_path = "fizzgig";
static struct wavlist { // Linked list of WAV filenames
char *filename;
struct wavlist *next;
} *wavListStart = NULL, *wavListPtr = NULL;
#define MAX_WAV_FILES 20
void user_setup(void) {
File entry;
struct wavlist *wptr;
char filename[SD_MAX_FILENAME_SIZE+1];
// Scan wav_path for .wav files:
for(int i=0; i<MAX_WAV_FILES; i++) {
entry = arcada.openFileByIndex(wav_path, i, O_READ, "wav");
if(!entry) break;
// Found one, alloc new wavlist struct, try duplicating filename
if((wptr = (struct wavlist *)malloc(sizeof(struct wavlist)))) {
entry.getName(filename, SD_MAX_FILENAME_SIZE);
if((wptr->filename = strdup(filename))) {
// Alloc'd OK, add to linked list...
if(wavListPtr) { // List already started?
wavListPtr->next = wptr; // Point prior last item to new one
} else {
wavListStart = wptr; // Point list head to new item
}
wavListPtr = wptr; // Update last item to new one
} else {
free(wptr); // Alloc failed, delete interim stuff
}
}
entry.close();
}
if(wavListPtr) { // Any items in WAV list?
wavListPtr->next = wavListStart; // Point last item's next to list head (list is looped)
wavListPtr = wavListStart; // Update list pointer to head
}
}
void user_loop(void) {
if(playing) {
// While WAV is playing, wiggle servo between middle and open-mouth positions:
uint32_t elapsed = millis() - wavEventTime; // Time since audio start
uint16_t frac = elapsed % 500; // 0 to 499 = 0.5 sec
float n = 1.0 - ((float)abs(250 - frac) / 500.0); // Ramp 0.5, 1.0, 0.5 in 0.5 sec
myservo.writeMicroseconds((int)((float)SERVO_MOUTH_CLOSED + (float)(SERVO_MOUTH_OPEN - SERVO_MOUTH_CLOSED) * n));
// BUTTON_PIN button is ignored while sound is playing.
} else if(wavListPtr) {
// Not currently playing WAV. Check for button press on pin BUTTON_PIN.
pinMode(BUTTON_PIN, INPUT_PULLUP);
delayMicroseconds(20); // Avoid boop code interference
if(!digitalRead(BUTTON_PIN)) {
arcada.chdir(wav_path);
startWav(wavListPtr->filename);
wavListPtr = wavListPtr->next; // Will loop around from end to start of list
}
pinMode(BUTTON_PIN, INPUT);
if(myservo.attached()) { // If servo still active (from recent WAV playing)
myservo.writeMicroseconds(SERVO_MOUTH_CLOSED); // Make sure it's in closed position
// If it's been more than 1 sec since audio stopped,
// deactivate the servo to reduce power, heat & noise.
if((millis() - wavEventTime) > 1000) {
myservo.detach();
}
}
}
}
static uint16_t readWaveData(uint8_t *dst) {
if(remainingBytesInChunk <= 0) {
// Read next chunk
struct {
char id[4];
uint32_t size;
} header;
for(;;) {
if(wavFile.read(&header, 8) != 8) return 0;
if(!strncmp(header.id, "data", 4)) {
remainingBytesInChunk = header.size;
break;
}
if(!wavFile.seekCur(header.size)) { // If not "data" then skip
return 0; // Seek failed, return invalid count
}
}
}
int16_t bytesRead = wavFile.read(dst, min(WAV_BUFFER_SIZE, remainingBytesInChunk));
if(bytesRead > 0) remainingBytesInChunk -= bytesRead;
return bytesRead;
}
// Partially swiped from Wave Shield code.
// Is pared-down, handles 8-bit mono only to keep it simple.
static bool startWav(char *filename) {
wavFile = arcada.open(filename);
if(!wavFile) {
Serial.println("Failed to open WAV file");
return false;
}
union {
struct {
char id[4];
uint32_t size;
char data[4];
} riff; // riff chunk
struct {
uint16_t compress;
uint16_t channels;
uint32_t sampleRate;
uint32_t bytesPerSecond;
uint16_t blockAlign;
uint16_t bitsPerSample;
uint16_t extraBytes;
} fmt; // fmt data
} buf;
uint16_t size;
if((wavFile.read(&buf, 12) == 12)
&& !strncmp(buf.riff.id, "RIFF", 4)
&& !strncmp(buf.riff.data, "WAVE", 4)) {
// next chunk must be fmt, fmt chunk size must be 16 or 18
if((wavFile.read(&buf, 8) == 8)
&& !strncmp(buf.riff.id, "fmt ", 4)
&& (((size = buf.riff.size) == 16) || (size == 18))
&& (wavFile.read(&buf, size) == size)
&& ((size != 18) || (buf.fmt.extraBytes == 0))) {
if((buf.fmt.channels == 1) && (buf.fmt.bitsPerSample == 8)) {
Serial.printf("Samples/sec: %d\n", buf.fmt.sampleRate);
bufEnd = readWaveData(wavBuf[0]);
if(bufEnd > 0) {
// Initialize A/D, speaker and start timer
analogWriteResolution(8);
analogWrite(A0, 128);
analogWrite(A1, 128);
arcada.enableSpeaker(true);
wavEventTime = millis(); // WAV starting time
bufIdx = 0;
playing = true;
arcada.timerCallback(buf.fmt.sampleRate, wavOutCallback);
nextBufEnd = readWaveData(wavBuf[1]);
myservo.attach(SERVO_PIN);
}
return true;
} else {
Serial.println("Only 8-bit mono WAVs are supported");
}
} else {
Serial.println("WAV uses compression or other unrecognized setting");
}
} else {
Serial.println("Not WAV file");
}
wavFile.close();
return false;
}
static void wavOutCallback(void) {
uint8_t n = wavBuf[activeBuf][bufIdx];
analogWrite(A0, n);
analogWrite(A1, n);
if(++bufIdx >= bufEnd) {
if(nextBufEnd <= 0) {
arcada.timerStop();
arcada.enableSpeaker(false);
playing = false;
wavEventTime = millis(); // Same var now holds WAV end time
return;
}
bufIdx = 0;
bufEnd = nextBufEnd;
nextBufEnd = readWaveData(wavBuf[activeBuf]);
activeBuf = 1 - activeBuf;
}
}
#endif // 0