user_fizzgig.cpp
1 // SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 #if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!) 6 7 #include "globals.h" 8 #include <Servo.h> 9 10 // Servo stuff 11 Servo myservo; 12 #define SERVO_MOUTH_OPEN 750 // Servo pulse microseconds 13 #define SERVO_MOUTH_CLOSED 1850 14 #define SERVO_PIN 3 15 16 #define BUTTON_PIN 2 17 18 // WAV player stuff 19 #define WAV_BUFFER_SIZE 256 20 static uint8_t wavBuf[2][WAV_BUFFER_SIZE]; 21 static File wavFile; 22 static bool playing = false; 23 static int remainingBytesInChunk; 24 static uint8_t activeBuf; 25 static uint16_t bufIdx, bufEnd, nextBufEnd; 26 static bool startWav(char *filename); 27 static void wavOutCallback(void); 28 static uint32_t wavEventTime; // WAV start or end time, in ms 29 static const char *wav_path = "fizzgig"; 30 static struct wavlist { // Linked list of WAV filenames 31 char *filename; 32 struct wavlist *next; 33 } *wavListStart = NULL, *wavListPtr = NULL; 34 #define MAX_WAV_FILES 20 35 36 void user_setup(void) { 37 File entry; 38 struct wavlist *wptr; 39 char filename[SD_MAX_FILENAME_SIZE+1]; 40 // Scan wav_path for .wav files: 41 for(int i=0; i<MAX_WAV_FILES; i++) { 42 entry = arcada.openFileByIndex(wav_path, i, O_READ, "wav"); 43 if(!entry) break; 44 // Found one, alloc new wavlist struct, try duplicating filename 45 if((wptr = (struct wavlist *)malloc(sizeof(struct wavlist)))) { 46 entry.getName(filename, SD_MAX_FILENAME_SIZE); 47 if((wptr->filename = strdup(filename))) { 48 // Alloc'd OK, add to linked list... 49 if(wavListPtr) { // List already started? 50 wavListPtr->next = wptr; // Point prior last item to new one 51 } else { 52 wavListStart = wptr; // Point list head to new item 53 } 54 wavListPtr = wptr; // Update last item to new one 55 } else { 56 free(wptr); // Alloc failed, delete interim stuff 57 } 58 } 59 entry.close(); 60 } 61 if(wavListPtr) { // Any items in WAV list? 62 wavListPtr->next = wavListStart; // Point last item's next to list head (list is looped) 63 wavListPtr = wavListStart; // Update list pointer to head 64 } 65 } 66 67 void user_loop(void) { 68 if(playing) { 69 // While WAV is playing, wiggle servo between middle and open-mouth positions: 70 uint32_t elapsed = millis() - wavEventTime; // Time since audio start 71 uint16_t frac = elapsed % 500; // 0 to 499 = 0.5 sec 72 float n = 1.0 - ((float)abs(250 - frac) / 500.0); // Ramp 0.5, 1.0, 0.5 in 0.5 sec 73 myservo.writeMicroseconds((int)((float)SERVO_MOUTH_CLOSED + (float)(SERVO_MOUTH_OPEN - SERVO_MOUTH_CLOSED) * n)); 74 // BUTTON_PIN button is ignored while sound is playing. 75 } else if(wavListPtr) { 76 // Not currently playing WAV. Check for button press on pin BUTTON_PIN. 77 pinMode(BUTTON_PIN, INPUT_PULLUP); 78 delayMicroseconds(20); // Avoid boop code interference 79 if(!digitalRead(BUTTON_PIN)) { 80 arcada.chdir(wav_path); 81 startWav(wavListPtr->filename); 82 wavListPtr = wavListPtr->next; // Will loop around from end to start of list 83 } 84 pinMode(BUTTON_PIN, INPUT); 85 if(myservo.attached()) { // If servo still active (from recent WAV playing) 86 myservo.writeMicroseconds(SERVO_MOUTH_CLOSED); // Make sure it's in closed position 87 // If it's been more than 1 sec since audio stopped, 88 // deactivate the servo to reduce power, heat & noise. 89 if((millis() - wavEventTime) > 1000) { 90 myservo.detach(); 91 } 92 } 93 } 94 } 95 96 static uint16_t readWaveData(uint8_t *dst) { 97 if(remainingBytesInChunk <= 0) { 98 // Read next chunk 99 struct { 100 char id[4]; 101 uint32_t size; 102 } header; 103 for(;;) { 104 if(wavFile.read(&header, 8) != 8) return 0; 105 if(!strncmp(header.id, "data", 4)) { 106 remainingBytesInChunk = header.size; 107 break; 108 } 109 if(!wavFile.seekCur(header.size)) { // If not "data" then skip 110 return 0; // Seek failed, return invalid count 111 } 112 } 113 } 114 115 int16_t bytesRead = wavFile.read(dst, min(WAV_BUFFER_SIZE, remainingBytesInChunk)); 116 if(bytesRead > 0) remainingBytesInChunk -= bytesRead; 117 return bytesRead; 118 } 119 120 // Partially swiped from Wave Shield code. 121 // Is pared-down, handles 8-bit mono only to keep it simple. 122 static bool startWav(char *filename) { 123 wavFile = arcada.open(filename); 124 if(!wavFile) { 125 Serial.println("Failed to open WAV file"); 126 return false; 127 } 128 129 union { 130 struct { 131 char id[4]; 132 uint32_t size; 133 char data[4]; 134 } riff; // riff chunk 135 struct { 136 uint16_t compress; 137 uint16_t channels; 138 uint32_t sampleRate; 139 uint32_t bytesPerSecond; 140 uint16_t blockAlign; 141 uint16_t bitsPerSample; 142 uint16_t extraBytes; 143 } fmt; // fmt data 144 } buf; 145 146 uint16_t size; 147 if((wavFile.read(&buf, 12) == 12) 148 && !strncmp(buf.riff.id, "RIFF", 4) 149 && !strncmp(buf.riff.data, "WAVE", 4)) { 150 // next chunk must be fmt, fmt chunk size must be 16 or 18 151 if((wavFile.read(&buf, 8) == 8) 152 && !strncmp(buf.riff.id, "fmt ", 4) 153 && (((size = buf.riff.size) == 16) || (size == 18)) 154 && (wavFile.read(&buf, size) == size) 155 && ((size != 18) || (buf.fmt.extraBytes == 0))) { 156 if((buf.fmt.channels == 1) && (buf.fmt.bitsPerSample == 8)) { 157 Serial.printf("Samples/sec: %d\n", buf.fmt.sampleRate); 158 bufEnd = readWaveData(wavBuf[0]); 159 if(bufEnd > 0) { 160 // Initialize A/D, speaker and start timer 161 analogWriteResolution(8); 162 analogWrite(A0, 128); 163 analogWrite(A1, 128); 164 arcada.enableSpeaker(true); 165 wavEventTime = millis(); // WAV starting time 166 bufIdx = 0; 167 playing = true; 168 arcada.timerCallback(buf.fmt.sampleRate, wavOutCallback); 169 nextBufEnd = readWaveData(wavBuf[1]); 170 myservo.attach(SERVO_PIN); 171 } 172 return true; 173 } else { 174 Serial.println("Only 8-bit mono WAVs are supported"); 175 } 176 } else { 177 Serial.println("WAV uses compression or other unrecognized setting"); 178 } 179 } else { 180 Serial.println("Not WAV file"); 181 } 182 183 wavFile.close(); 184 return false; 185 } 186 187 static void wavOutCallback(void) { 188 uint8_t n = wavBuf[activeBuf][bufIdx]; 189 analogWrite(A0, n); 190 analogWrite(A1, n); 191 192 if(++bufIdx >= bufEnd) { 193 if(nextBufEnd <= 0) { 194 arcada.timerStop(); 195 arcada.enableSpeaker(false); 196 playing = false; 197 wavEventTime = millis(); // Same var now holds WAV end time 198 return; 199 } 200 bufIdx = 0; 201 bufEnd = nextBufEnd; 202 nextBufEnd = readWaveData(wavBuf[activeBuf]); 203 activeBuf = 1 - activeBuf; 204 } 205 } 206 207 #endif // 0