/ M4_Eyes / user_fizzgig.cpp
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