Big Mouth Billy Bass player that takes raw wavefiles and decision list text files from an SD card
Dependencies: SDFileSystem mbed BillyBass
main.cpp@3:00b4c1aadd30, 2013-06-07 (annotated)
- Committer:
- bikeNomad
- Date:
- Fri Jun 07 04:00:57 2013 +0000
- Revision:
- 3:00b4c1aadd30
- Parent:
- 2:f96989f2b8a1
- Child:
- 4:babc37764bd3
added song playing
Who changed what in which revision?
| User | Revision | Line number | New contents of line |
|---|---|---|---|
| bikeNomad | 0:0944c3654ded | 1 | #include "mbed.h" |
| bikeNomad | 0:0944c3654ded | 2 | #include "SDFileSystem.h" |
| bikeNomad | 0:0944c3654ded | 3 | #include <list> |
| bikeNomad | 0:0944c3654ded | 4 | #include <cmath> |
| bikeNomad | 0:0944c3654ded | 5 | |
| bikeNomad | 0:0944c3654ded | 6 | #define SD_NAME "sd" |
| bikeNomad | 0:0944c3654ded | 7 | #define SD_ROOT "/" SD_NAME |
| bikeNomad | 2:f96989f2b8a1 | 8 | #define BASS_DIRECTORY SD_ROOT "/SD_Files" |
| bikeNomad | 0:0944c3654ded | 9 | |
| bikeNomad | 0:0944c3654ded | 10 | // Power: |
| bikeNomad | 0:0944c3654ded | 11 | // Power GND J9/14 |
| bikeNomad | 0:0944c3654ded | 12 | // Vin (6V) J9/16 |
| bikeNomad | 0:0944c3654ded | 13 | |
| bikeNomad | 0:0944c3654ded | 14 | // Digital: |
| bikeNomad | 0:0944c3654ded | 15 | DigitalOut tail(PTA13); // J3/2 |
| bikeNomad | 0:0944c3654ded | 16 | DigitalOut mouth(PTC12); // J3/1 |
| bikeNomad | 0:0944c3654ded | 17 | DigitalOut head(PTC13); // J3/3 |
| bikeNomad | 0:0944c3654ded | 18 | |
| bikeNomad | 0:0944c3654ded | 19 | DigitalIn pushbutton(PTD5); // J3/4 |
| bikeNomad | 0:0944c3654ded | 20 | |
| bikeNomad | 0:0944c3654ded | 21 | PwmOut redLED(LED_RED); |
| bikeNomad | 0:0944c3654ded | 22 | PwmOut greenLED(LED_GREEN); |
| bikeNomad | 0:0944c3654ded | 23 | PwmOut blueLED(LED_BLUE); |
| bikeNomad | 0:0944c3654ded | 24 | |
| bikeNomad | 0:0944c3654ded | 25 | // Analog: |
| bikeNomad | 0:0944c3654ded | 26 | // GND J3/14 |
| bikeNomad | 0:0944c3654ded | 27 | // VrefH J3/16 |
| bikeNomad | 0:0944c3654ded | 28 | AnalogOut speaker(PTE30); // J10/11 |
| bikeNomad | 0:0944c3654ded | 29 | |
| bikeNomad | 0:0944c3654ded | 30 | // PTD0 D10 – Used for CS of SPI |
| bikeNomad | 0:0944c3654ded | 31 | // PTD2 D11 – Used for MOSI of SPI |
| bikeNomad | 0:0944c3654ded | 32 | // PTD3 D12 – Used for MISO of SPI |
| bikeNomad | 0:0944c3654ded | 33 | // PTC5 J1/9 Used for SCLK of SPI |
| bikeNomad | 0:0944c3654ded | 34 | |
| bikeNomad | 0:0944c3654ded | 35 | // MOSI, MISO, SCLK, CS, name |
| bikeNomad | 0:0944c3654ded | 36 | SDFileSystem sd(PTD2, PTD3, PTC5, PTD0, SD_NAME); |
| bikeNomad | 0:0944c3654ded | 37 | Serial pc(USBTX, USBRX); |
| bikeNomad | 0:0944c3654ded | 38 | |
| bikeNomad | 0:0944c3654ded | 39 | typedef int16_t Sample_t; // 16-bit raw, LE samples |
| bikeNomad | 0:0944c3654ded | 40 | |
| bikeNomad | 0:0944c3654ded | 41 | const size_t BUFFER_SIZE = 512; |
| bikeNomad | 0:0944c3654ded | 42 | const float SAMPLE_RATE_HZ = 8000.0; |
| bikeNomad | 1:f3f439154ab4 | 43 | const unsigned SAMPLE_PERIOD_USEC = (unsigned)(1.0e6/SAMPLE_RATE_HZ); |
| bikeNomad | 0:0944c3654ded | 44 | const size_t SAMPLES_PER_BUFFER = BUFFER_SIZE / sizeof(Sample_t); |
| bikeNomad | 0:0944c3654ded | 45 | const float SECONDS_PER_CHUNK = SAMPLES_PER_BUFFER / SAMPLE_RATE_HZ; |
| bikeNomad | 0:0944c3654ded | 46 | |
| bikeNomad | 0:0944c3654ded | 47 | struct Action { |
| bikeNomad | 0:0944c3654ded | 48 | float actionTime; |
| bikeNomad | 0:0944c3654ded | 49 | bool desiredState; |
| bikeNomad | 0:0944c3654ded | 50 | DigitalOut *output; |
| bikeNomad | 0:0944c3654ded | 51 | |
| bikeNomad | 0:0944c3654ded | 52 | bool operator < (Action const &other) const { |
| bikeNomad | 0:0944c3654ded | 53 | return actionTime < other.actionTime; |
| bikeNomad | 0:0944c3654ded | 54 | } |
| bikeNomad | 0:0944c3654ded | 55 | |
| bikeNomad | 0:0944c3654ded | 56 | bool isValid() { |
| bikeNomad | 0:0944c3654ded | 57 | return actionTime >= 0.0 && output != 0; |
| bikeNomad | 0:0944c3654ded | 58 | } |
| bikeNomad | 0:0944c3654ded | 59 | |
| bikeNomad | 0:0944c3654ded | 60 | Action() : actionTime(-1.0), desiredState(false), output(0) { |
| bikeNomad | 0:0944c3654ded | 61 | } |
| bikeNomad | 0:0944c3654ded | 62 | |
| bikeNomad | 0:0944c3654ded | 63 | bool parseLine(char const *line) { |
| bikeNomad | 0:0944c3654ded | 64 | // TODO |
| bikeNomad | 0:0944c3654ded | 65 | return true; |
| bikeNomad | 0:0944c3654ded | 66 | } |
| bikeNomad | 0:0944c3654ded | 67 | }; |
| bikeNomad | 0:0944c3654ded | 68 | |
| bikeNomad | 0:0944c3654ded | 69 | #define MAX_BASENAME_LENGTH 30 |
| bikeNomad | 0:0944c3654ded | 70 | #define MAX_ACTION_LINE_LENGTH 100 |
| bikeNomad | 0:0944c3654ded | 71 | |
| bikeNomad | 0:0944c3654ded | 72 | struct Song { |
| bikeNomad | 0:0944c3654ded | 73 | char basename[ MAX_BASENAME_LENGTH + 1 ]; |
| bikeNomad | 0:0944c3654ded | 74 | unsigned sequenceNumber; |
| bikeNomad | 0:0944c3654ded | 75 | unsigned whichFish; |
| bikeNomad | 0:0944c3654ded | 76 | std::list<Action> actions; |
| bikeNomad | 0:0944c3654ded | 77 | |
| bikeNomad | 0:0944c3654ded | 78 | Song() : sequenceNumber(0), whichFish(3) { |
| bikeNomad | 0:0944c3654ded | 79 | basename[0] = 0; |
| bikeNomad | 0:0944c3654ded | 80 | } |
| bikeNomad | 0:0944c3654ded | 81 | |
| bikeNomad | 0:0944c3654ded | 82 | bool readWaveFile(char const *_waveFileName) { |
| bikeNomad | 0:0944c3654ded | 83 | if (!parseFilename(_waveFileName, sequenceNumber, whichFish, basename)) return false; |
| bikeNomad | 0:0944c3654ded | 84 | char txtFileName[ FILENAME_MAX ]; |
| bikeNomad | 0:0944c3654ded | 85 | sprintf(txtFileName, "%u_%u_%s.txt", sequenceNumber, whichFish, basename); |
| bikeNomad | 0:0944c3654ded | 86 | FILE *txtfile = fopen(txtFileName, "rt"); |
| bikeNomad | 0:0944c3654ded | 87 | if (!txtfile) return false; // TODO |
| bikeNomad | 0:0944c3654ded | 88 | // read actions from file |
| bikeNomad | 0:0944c3654ded | 89 | char actionLine[ MAX_ACTION_LINE_LENGTH + 1 ]; |
| bikeNomad | 0:0944c3654ded | 90 | while (fgets(actionLine, sizeof(actionLine), txtfile)) { |
| bikeNomad | 0:0944c3654ded | 91 | Action action; |
| bikeNomad | 0:0944c3654ded | 92 | if (action.parseLine(actionLine)) { |
| bikeNomad | 0:0944c3654ded | 93 | actions.push_back(action); |
| bikeNomad | 0:0944c3654ded | 94 | } |
| bikeNomad | 0:0944c3654ded | 95 | } |
| bikeNomad | 0:0944c3654ded | 96 | return true; |
| bikeNomad | 0:0944c3654ded | 97 | } |
| bikeNomad | 0:0944c3654ded | 98 | |
| bikeNomad | 0:0944c3654ded | 99 | static bool parseFilename(char const *_name, unsigned &_num1, unsigned &_num2, char *_basename) { |
| bikeNomad | 0:0944c3654ded | 100 | char basename[ MAX_BASENAME_LENGTH + 1 ]; |
| bikeNomad | 0:0944c3654ded | 101 | unsigned num1, num2; |
| bikeNomad | 0:0944c3654ded | 102 | char extension[ 4 ]; |
| bikeNomad | 3:00b4c1aadd30 | 103 | int nItems = sscanf(_name, "%u_%u_%s", &num1, &num2, basename); |
| bikeNomad | 3:00b4c1aadd30 | 104 | if (nItems != 3) { pc.printf(" nItems=%d\n", nItems); return false; } |
| bikeNomad | 3:00b4c1aadd30 | 105 | char *p = strrchr(basename, '.'); |
| bikeNomad | 3:00b4c1aadd30 | 106 | if (!p) { pc.printf(" no extension\n"); return false;} |
| bikeNomad | 3:00b4c1aadd30 | 107 | strcpy(extension, p+1); |
| bikeNomad | 3:00b4c1aadd30 | 108 | *p = 0; |
| bikeNomad | 3:00b4c1aadd30 | 109 | if (num2 > 2) { pc.printf(" num2=%d\n", num2); return false; } |
| bikeNomad | 3:00b4c1aadd30 | 110 | if (strcasecmp("raw", extension)) { pc.printf(" ext=%s\n", extension); return false; } |
| bikeNomad | 0:0944c3654ded | 111 | _num1 = num1; |
| bikeNomad | 0:0944c3654ded | 112 | _num2 = num2; |
| bikeNomad | 0:0944c3654ded | 113 | strcpy(_basename, basename); |
| bikeNomad | 0:0944c3654ded | 114 | return true; |
| bikeNomad | 0:0944c3654ded | 115 | } |
| bikeNomad | 0:0944c3654ded | 116 | |
| bikeNomad | 0:0944c3654ded | 117 | // return true if filename is of proper format |
| bikeNomad | 0:0944c3654ded | 118 | // <num>_<num>_<name>.raw |
| bikeNomad | 0:0944c3654ded | 119 | // and there is a corresponding .txt file |
| bikeNomad | 0:0944c3654ded | 120 | static bool isValidWaveFileName(char const *_name) { |
| bikeNomad | 0:0944c3654ded | 121 | unsigned int num1, num2; |
| bikeNomad | 0:0944c3654ded | 122 | char basename[ 31 ]; |
| bikeNomad | 0:0944c3654ded | 123 | return parseFilename(_name, num1, num2, basename); |
| bikeNomad | 0:0944c3654ded | 124 | } |
| bikeNomad | 0:0944c3654ded | 125 | }; |
| bikeNomad | 0:0944c3654ded | 126 | |
| bikeNomad | 0:0944c3654ded | 127 | class SongPlayer; |
| bikeNomad | 0:0944c3654ded | 128 | |
| bikeNomad | 0:0944c3654ded | 129 | struct SampleBuffer { |
| bikeNomad | 0:0944c3654ded | 130 | Sample_t volatile buf[ SAMPLES_PER_BUFFER ]; |
| bikeNomad | 0:0944c3654ded | 131 | size_t volatile samplesRemaining; |
| bikeNomad | 0:0944c3654ded | 132 | Sample_t volatile * volatile nextSample; |
| bikeNomad | 0:0944c3654ded | 133 | |
| bikeNomad | 0:0944c3654ded | 134 | bool isDone() { |
| bikeNomad | 0:0944c3654ded | 135 | return !samplesRemaining; |
| bikeNomad | 0:0944c3654ded | 136 | } |
| bikeNomad | 0:0944c3654ded | 137 | |
| bikeNomad | 0:0944c3654ded | 138 | float remainingDuration() { |
| bikeNomad | 0:0944c3654ded | 139 | return samplesRemaining / SAMPLE_RATE_HZ; |
| bikeNomad | 0:0944c3654ded | 140 | } |
| bikeNomad | 0:0944c3654ded | 141 | |
| bikeNomad | 0:0944c3654ded | 142 | // return true if we read any samples |
| bikeNomad | 0:0944c3654ded | 143 | bool loadFrom(FILE *fp) { |
| bikeNomad | 0:0944c3654ded | 144 | samplesRemaining = fread((void *)buf, sizeof(Sample_t), SAMPLES_PER_BUFFER, fp); |
| bikeNomad | 0:0944c3654ded | 145 | nextSample = buf; |
| bikeNomad | 0:0944c3654ded | 146 | return samplesRemaining > 0; |
| bikeNomad | 0:0944c3654ded | 147 | } |
| bikeNomad | 0:0944c3654ded | 148 | |
| bikeNomad | 1:f3f439154ab4 | 149 | Sample_t getNextSample() { |
| bikeNomad | 1:f3f439154ab4 | 150 | --samplesRemaining; |
| bikeNomad | 1:f3f439154ab4 | 151 | return *++nextSample; |
| bikeNomad | 1:f3f439154ab4 | 152 | } |
| bikeNomad | 1:f3f439154ab4 | 153 | |
| bikeNomad | 0:0944c3654ded | 154 | SampleBuffer() : samplesRemaining(0), nextSample(buf) { } |
| bikeNomad | 0:0944c3654ded | 155 | }; |
| bikeNomad | 0:0944c3654ded | 156 | |
| bikeNomad | 0:0944c3654ded | 157 | struct SongPlayer { |
| bikeNomad | 0:0944c3654ded | 158 | SampleBuffer * volatile playing; |
| bikeNomad | 0:0944c3654ded | 159 | SampleBuffer * volatile loading; |
| bikeNomad | 0:0944c3654ded | 160 | SampleBuffer buffer[2]; |
| bikeNomad | 0:0944c3654ded | 161 | FILE *fp; |
| bikeNomad | 0:0944c3654ded | 162 | size_t nChunks; |
| bikeNomad | 0:0944c3654ded | 163 | size_t volatile chunksRemaining; |
| bikeNomad | 3:00b4c1aadd30 | 164 | float timeInSong; |
| bikeNomad | 1:f3f439154ab4 | 165 | Ticker sampleTicker; |
| bikeNomad | 1:f3f439154ab4 | 166 | |
| bikeNomad | 1:f3f439154ab4 | 167 | // interrupt handler |
| bikeNomad | 1:f3f439154ab4 | 168 | void playNextSample(void) { |
| bikeNomad | 1:f3f439154ab4 | 169 | if (!playing->samplesRemaining) |
| bikeNomad | 1:f3f439154ab4 | 170 | swapBuffers(); |
| bikeNomad | 1:f3f439154ab4 | 171 | // TODO change bias to 0xC000 (requires normalizing to 75% of full scale) |
| bikeNomad | 1:f3f439154ab4 | 172 | speaker.write_u16(static_cast<uint16_t>(playing->getNextSample() + 0x8000)); |
| bikeNomad | 1:f3f439154ab4 | 173 | } |
| bikeNomad | 0:0944c3654ded | 174 | |
| bikeNomad | 0:0944c3654ded | 175 | bool startSong(char const *name) { |
| bikeNomad | 0:0944c3654ded | 176 | if (fp) fclose(fp); |
| bikeNomad | 0:0944c3654ded | 177 | fp = fopen(name, "rb"); |
| bikeNomad | 0:0944c3654ded | 178 | if (!fp) return false; |
| bikeNomad | 0:0944c3654ded | 179 | if (fseek(fp, 0, SEEK_END)) return false; |
| bikeNomad | 0:0944c3654ded | 180 | long fileSize = ftell(fp); |
| bikeNomad | 0:0944c3654ded | 181 | if (fileSize < 0) return false; |
| bikeNomad | 0:0944c3654ded | 182 | if (fseek(fp, 0, SEEK_SET)) return false; |
| bikeNomad | 0:0944c3654ded | 183 | chunksRemaining = nChunks = fileSize / BUFFER_SIZE; |
| bikeNomad | 0:0944c3654ded | 184 | loading = &buffer[0]; |
| bikeNomad | 0:0944c3654ded | 185 | playing = &buffer[1]; |
| bikeNomad | 1:f3f439154ab4 | 186 | if (! loadNextChunk()) |
| bikeNomad | 1:f3f439154ab4 | 187 | return false; |
| bikeNomad | 3:00b4c1aadd30 | 188 | timeInSong = 0.0; |
| bikeNomad | 1:f3f439154ab4 | 189 | sampleTicker.attach_us(this, &SongPlayer::playNextSample, SAMPLE_PERIOD_USEC); |
| bikeNomad | 1:f3f439154ab4 | 190 | return true; |
| bikeNomad | 0:0944c3654ded | 191 | } |
| bikeNomad | 0:0944c3654ded | 192 | |
| bikeNomad | 0:0944c3654ded | 193 | // swap loading/playing buffers; |
| bikeNomad | 0:0944c3654ded | 194 | // decrement chunksRemaining |
| bikeNomad | 0:0944c3654ded | 195 | void swapBuffers() { |
| bikeNomad | 0:0944c3654ded | 196 | SampleBuffer * volatile tmp = playing; |
| bikeNomad | 0:0944c3654ded | 197 | if (tmp == buffer + 0) |
| bikeNomad | 0:0944c3654ded | 198 | playing = buffer + 1; |
| bikeNomad | 0:0944c3654ded | 199 | else |
| bikeNomad | 0:0944c3654ded | 200 | playing = buffer + 0; |
| bikeNomad | 0:0944c3654ded | 201 | loading = tmp; |
| bikeNomad | 0:0944c3654ded | 202 | if (chunksRemaining) |
| bikeNomad | 0:0944c3654ded | 203 | chunksRemaining--; |
| bikeNomad | 0:0944c3654ded | 204 | } |
| bikeNomad | 0:0944c3654ded | 205 | |
| bikeNomad | 0:0944c3654ded | 206 | // get next chunk of file into *loading |
| bikeNomad | 0:0944c3654ded | 207 | // to prepare for eventual swap. |
| bikeNomad | 0:0944c3654ded | 208 | // returns true if more samples remain |
| bikeNomad | 0:0944c3654ded | 209 | bool loadNextChunk() { |
| bikeNomad | 0:0944c3654ded | 210 | if (! chunksRemaining) return false; |
| bikeNomad | 0:0944c3654ded | 211 | bool notDone = loading->loadFrom(fp); |
| bikeNomad | 0:0944c3654ded | 212 | return notDone; |
| bikeNomad | 0:0944c3654ded | 213 | } |
| bikeNomad | 0:0944c3654ded | 214 | |
| bikeNomad | 0:0944c3654ded | 215 | bool isDone() { |
| bikeNomad | 0:0944c3654ded | 216 | return !chunksRemaining; |
| bikeNomad | 0:0944c3654ded | 217 | } |
| bikeNomad | 0:0944c3654ded | 218 | |
| bikeNomad | 1:f3f439154ab4 | 219 | // look at loading buffer; load only if necessary. |
| bikeNomad | 1:f3f439154ab4 | 220 | bool loadIfNecessary() { |
| bikeNomad | 1:f3f439154ab4 | 221 | if (loading->isDone()) { |
| bikeNomad | 3:00b4c1aadd30 | 222 | timeInSong += SECONDS_PER_CHUNK; |
| bikeNomad | 1:f3f439154ab4 | 223 | return loadNextChunk(); |
| bikeNomad | 1:f3f439154ab4 | 224 | } else return true; |
| bikeNomad | 1:f3f439154ab4 | 225 | } |
| bikeNomad | 3:00b4c1aadd30 | 226 | |
| bikeNomad | 3:00b4c1aadd30 | 227 | void playEntireSong(char const *name) { |
| bikeNomad | 3:00b4c1aadd30 | 228 | if (!startSong(name)) return; |
| bikeNomad | 3:00b4c1aadd30 | 229 | while (!isDone()) { |
| bikeNomad | 3:00b4c1aadd30 | 230 | loadIfNecessary(); |
| bikeNomad | 3:00b4c1aadd30 | 231 | } |
| bikeNomad | 3:00b4c1aadd30 | 232 | sampleTicker.detach(); |
| bikeNomad | 3:00b4c1aadd30 | 233 | } |
| bikeNomad | 0:0944c3654ded | 234 | }; |
| bikeNomad | 0:0944c3654ded | 235 | |
| bikeNomad | 0:0944c3654ded | 236 | std::list<Song> songs; |
| bikeNomad | 0:0944c3654ded | 237 | |
| bikeNomad | 0:0944c3654ded | 238 | int main() |
| bikeNomad | 0:0944c3654ded | 239 | { |
| bikeNomad | 3:00b4c1aadd30 | 240 | SongPlayer player; |
| bikeNomad | 3:00b4c1aadd30 | 241 | |
| bikeNomad | 0:0944c3654ded | 242 | // read the directory |
| bikeNomad | 0:0944c3654ded | 243 | DIR *bassDir = opendir(BASS_DIRECTORY); |
| bikeNomad | 0:0944c3654ded | 244 | if (bassDir) { |
| bikeNomad | 0:0944c3654ded | 245 | while (dirent *dir = bassDir->readdir()) { |
| bikeNomad | 0:0944c3654ded | 246 | pc.printf("%s", dir->d_name); |
| bikeNomad | 0:0944c3654ded | 247 | // if this is a valid wave filename |
| bikeNomad | 0:0944c3654ded | 248 | if (Song::isValidWaveFileName(dir->d_name)) { |
| bikeNomad | 0:0944c3654ded | 249 | pc.printf("\tvalid\r\n"); |
| bikeNomad | 3:00b4c1aadd30 | 250 | player.playEntireSong(dir->d_name); |
| bikeNomad | 3:00b4c1aadd30 | 251 | pc.printf("Song time: %f\r\n", player.timeInSong); |
| bikeNomad | 0:0944c3654ded | 252 | } else { |
| bikeNomad | 0:0944c3654ded | 253 | pc.printf("\tnot valid\r\n"); |
| bikeNomad | 0:0944c3654ded | 254 | } |
| bikeNomad | 0:0944c3654ded | 255 | } |
| bikeNomad | 0:0944c3654ded | 256 | } else { |
| bikeNomad | 0:0944c3654ded | 257 | pc.printf("Error opening " BASS_DIRECTORY); |
| bikeNomad | 0:0944c3654ded | 258 | } |
| bikeNomad | 0:0944c3654ded | 259 | } |
Ned Konz