Big Mouth Billy Bass player that takes raw wavefiles and decision list text files from an SD card
Dependencies: SDFileSystem mbed BillyBass
main.cpp@2:f96989f2b8a1, 2013-06-07 (annotated)
- Committer:
- bikeNomad
- Date:
- Fri Jun 07 03:36:00 2013 +0000
- Revision:
- 2:f96989f2b8a1
- Parent:
- 1:f3f439154ab4
- Child:
- 3:00b4c1aadd30
changed root of SD to /SD_Files
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 | 0:0944c3654ded | 103 | int nItems = sscanf(_name, "%u_%u_%30[^_.].%3[a-zA-Z]", &num1, &num2, basename, extension); |
bikeNomad | 0:0944c3654ded | 104 | if (nItems != 4) return false; |
bikeNomad | 0:0944c3654ded | 105 | if (num2 > 2) return false; |
bikeNomad | 0:0944c3654ded | 106 | if (strcasecmp("raw", extension)) return false; |
bikeNomad | 0:0944c3654ded | 107 | _num1 = num1; |
bikeNomad | 0:0944c3654ded | 108 | _num2 = num2; |
bikeNomad | 0:0944c3654ded | 109 | strcpy(_basename, basename); |
bikeNomad | 0:0944c3654ded | 110 | return true; |
bikeNomad | 0:0944c3654ded | 111 | } |
bikeNomad | 0:0944c3654ded | 112 | |
bikeNomad | 0:0944c3654ded | 113 | // return true if filename is of proper format |
bikeNomad | 0:0944c3654ded | 114 | // <num>_<num>_<name>.raw |
bikeNomad | 0:0944c3654ded | 115 | // and there is a corresponding .txt file |
bikeNomad | 0:0944c3654ded | 116 | static bool isValidWaveFileName(char const *_name) { |
bikeNomad | 0:0944c3654ded | 117 | unsigned int num1, num2; |
bikeNomad | 0:0944c3654ded | 118 | char basename[ 31 ]; |
bikeNomad | 0:0944c3654ded | 119 | return parseFilename(_name, num1, num2, basename); |
bikeNomad | 0:0944c3654ded | 120 | } |
bikeNomad | 0:0944c3654ded | 121 | }; |
bikeNomad | 0:0944c3654ded | 122 | |
bikeNomad | 0:0944c3654ded | 123 | class SongPlayer; |
bikeNomad | 0:0944c3654ded | 124 | |
bikeNomad | 0:0944c3654ded | 125 | struct SampleBuffer { |
bikeNomad | 0:0944c3654ded | 126 | Sample_t volatile buf[ SAMPLES_PER_BUFFER ]; |
bikeNomad | 0:0944c3654ded | 127 | size_t volatile samplesRemaining; |
bikeNomad | 0:0944c3654ded | 128 | Sample_t volatile * volatile nextSample; |
bikeNomad | 0:0944c3654ded | 129 | |
bikeNomad | 0:0944c3654ded | 130 | bool isDone() { |
bikeNomad | 0:0944c3654ded | 131 | return !samplesRemaining; |
bikeNomad | 0:0944c3654ded | 132 | } |
bikeNomad | 0:0944c3654ded | 133 | |
bikeNomad | 0:0944c3654ded | 134 | float remainingDuration() { |
bikeNomad | 0:0944c3654ded | 135 | return samplesRemaining / SAMPLE_RATE_HZ; |
bikeNomad | 0:0944c3654ded | 136 | } |
bikeNomad | 0:0944c3654ded | 137 | |
bikeNomad | 0:0944c3654ded | 138 | // return true if we read any samples |
bikeNomad | 0:0944c3654ded | 139 | bool loadFrom(FILE *fp) { |
bikeNomad | 0:0944c3654ded | 140 | samplesRemaining = fread((void *)buf, sizeof(Sample_t), SAMPLES_PER_BUFFER, fp); |
bikeNomad | 0:0944c3654ded | 141 | nextSample = buf; |
bikeNomad | 0:0944c3654ded | 142 | return samplesRemaining > 0; |
bikeNomad | 0:0944c3654ded | 143 | } |
bikeNomad | 0:0944c3654ded | 144 | |
bikeNomad | 1:f3f439154ab4 | 145 | Sample_t getNextSample() { |
bikeNomad | 1:f3f439154ab4 | 146 | --samplesRemaining; |
bikeNomad | 1:f3f439154ab4 | 147 | return *++nextSample; |
bikeNomad | 1:f3f439154ab4 | 148 | } |
bikeNomad | 1:f3f439154ab4 | 149 | |
bikeNomad | 0:0944c3654ded | 150 | SampleBuffer() : samplesRemaining(0), nextSample(buf) { } |
bikeNomad | 0:0944c3654ded | 151 | }; |
bikeNomad | 0:0944c3654ded | 152 | |
bikeNomad | 0:0944c3654ded | 153 | struct SongPlayer { |
bikeNomad | 0:0944c3654ded | 154 | SampleBuffer * volatile playing; |
bikeNomad | 0:0944c3654ded | 155 | SampleBuffer * volatile loading; |
bikeNomad | 0:0944c3654ded | 156 | SampleBuffer buffer[2]; |
bikeNomad | 0:0944c3654ded | 157 | FILE *fp; |
bikeNomad | 0:0944c3654ded | 158 | size_t nChunks; |
bikeNomad | 0:0944c3654ded | 159 | size_t volatile chunksRemaining; |
bikeNomad | 1:f3f439154ab4 | 160 | Ticker sampleTicker; |
bikeNomad | 1:f3f439154ab4 | 161 | |
bikeNomad | 1:f3f439154ab4 | 162 | // interrupt handler |
bikeNomad | 1:f3f439154ab4 | 163 | void playNextSample(void) { |
bikeNomad | 1:f3f439154ab4 | 164 | if (!playing->samplesRemaining) |
bikeNomad | 1:f3f439154ab4 | 165 | swapBuffers(); |
bikeNomad | 1:f3f439154ab4 | 166 | // TODO change bias to 0xC000 (requires normalizing to 75% of full scale) |
bikeNomad | 1:f3f439154ab4 | 167 | speaker.write_u16(static_cast<uint16_t>(playing->getNextSample() + 0x8000)); |
bikeNomad | 1:f3f439154ab4 | 168 | } |
bikeNomad | 0:0944c3654ded | 169 | |
bikeNomad | 0:0944c3654ded | 170 | bool startSong(char const *name) { |
bikeNomad | 0:0944c3654ded | 171 | if (fp) fclose(fp); |
bikeNomad | 0:0944c3654ded | 172 | fp = fopen(name, "rb"); |
bikeNomad | 0:0944c3654ded | 173 | if (!fp) return false; |
bikeNomad | 0:0944c3654ded | 174 | if (fseek(fp, 0, SEEK_END)) return false; |
bikeNomad | 0:0944c3654ded | 175 | long fileSize = ftell(fp); |
bikeNomad | 0:0944c3654ded | 176 | if (fileSize < 0) return false; |
bikeNomad | 0:0944c3654ded | 177 | if (fseek(fp, 0, SEEK_SET)) return false; |
bikeNomad | 0:0944c3654ded | 178 | chunksRemaining = nChunks = fileSize / BUFFER_SIZE; |
bikeNomad | 0:0944c3654ded | 179 | loading = &buffer[0]; |
bikeNomad | 0:0944c3654ded | 180 | playing = &buffer[1]; |
bikeNomad | 1:f3f439154ab4 | 181 | if (! loadNextChunk()) |
bikeNomad | 1:f3f439154ab4 | 182 | return false; |
bikeNomad | 1:f3f439154ab4 | 183 | sampleTicker.attach_us(this, &SongPlayer::playNextSample, SAMPLE_PERIOD_USEC); |
bikeNomad | 1:f3f439154ab4 | 184 | return true; |
bikeNomad | 0:0944c3654ded | 185 | } |
bikeNomad | 0:0944c3654ded | 186 | |
bikeNomad | 0:0944c3654ded | 187 | // swap loading/playing buffers; |
bikeNomad | 0:0944c3654ded | 188 | // decrement chunksRemaining |
bikeNomad | 0:0944c3654ded | 189 | void swapBuffers() { |
bikeNomad | 0:0944c3654ded | 190 | SampleBuffer * volatile tmp = playing; |
bikeNomad | 0:0944c3654ded | 191 | if (tmp == buffer + 0) |
bikeNomad | 0:0944c3654ded | 192 | playing = buffer + 1; |
bikeNomad | 0:0944c3654ded | 193 | else |
bikeNomad | 0:0944c3654ded | 194 | playing = buffer + 0; |
bikeNomad | 0:0944c3654ded | 195 | loading = tmp; |
bikeNomad | 0:0944c3654ded | 196 | if (chunksRemaining) |
bikeNomad | 0:0944c3654ded | 197 | chunksRemaining--; |
bikeNomad | 0:0944c3654ded | 198 | } |
bikeNomad | 0:0944c3654ded | 199 | |
bikeNomad | 0:0944c3654ded | 200 | // get next chunk of file into *loading |
bikeNomad | 0:0944c3654ded | 201 | // to prepare for eventual swap. |
bikeNomad | 0:0944c3654ded | 202 | // returns true if more samples remain |
bikeNomad | 0:0944c3654ded | 203 | bool loadNextChunk() { |
bikeNomad | 0:0944c3654ded | 204 | if (! chunksRemaining) return false; |
bikeNomad | 0:0944c3654ded | 205 | bool notDone = loading->loadFrom(fp); |
bikeNomad | 0:0944c3654ded | 206 | return notDone; |
bikeNomad | 0:0944c3654ded | 207 | } |
bikeNomad | 0:0944c3654ded | 208 | |
bikeNomad | 0:0944c3654ded | 209 | bool isDone() { |
bikeNomad | 0:0944c3654ded | 210 | return !chunksRemaining; |
bikeNomad | 0:0944c3654ded | 211 | } |
bikeNomad | 0:0944c3654ded | 212 | |
bikeNomad | 1:f3f439154ab4 | 213 | // look at loading buffer; load only if necessary. |
bikeNomad | 1:f3f439154ab4 | 214 | bool loadIfNecessary() { |
bikeNomad | 1:f3f439154ab4 | 215 | if (loading->isDone()) { |
bikeNomad | 1:f3f439154ab4 | 216 | return loadNextChunk(); |
bikeNomad | 1:f3f439154ab4 | 217 | } else return true; |
bikeNomad | 1:f3f439154ab4 | 218 | } |
bikeNomad | 0:0944c3654ded | 219 | }; |
bikeNomad | 0:0944c3654ded | 220 | |
bikeNomad | 0:0944c3654ded | 221 | std::list<Song> songs; |
bikeNomad | 0:0944c3654ded | 222 | |
bikeNomad | 0:0944c3654ded | 223 | int main() |
bikeNomad | 0:0944c3654ded | 224 | { |
bikeNomad | 0:0944c3654ded | 225 | // read the directory |
bikeNomad | 0:0944c3654ded | 226 | DIR *bassDir = opendir(BASS_DIRECTORY); |
bikeNomad | 0:0944c3654ded | 227 | if (bassDir) { |
bikeNomad | 0:0944c3654ded | 228 | while (dirent *dir = bassDir->readdir()) { |
bikeNomad | 0:0944c3654ded | 229 | pc.printf("%s", dir->d_name); |
bikeNomad | 0:0944c3654ded | 230 | // if this is a valid wave filename |
bikeNomad | 0:0944c3654ded | 231 | if (Song::isValidWaveFileName(dir->d_name)) { |
bikeNomad | 0:0944c3654ded | 232 | pc.printf("\tvalid\r\n"); |
bikeNomad | 0:0944c3654ded | 233 | } else { |
bikeNomad | 0:0944c3654ded | 234 | pc.printf("\tnot valid\r\n"); |
bikeNomad | 0:0944c3654ded | 235 | } |
bikeNomad | 0:0944c3654ded | 236 | } |
bikeNomad | 0:0944c3654ded | 237 | } else { |
bikeNomad | 0:0944c3654ded | 238 | pc.printf("Error opening " BASS_DIRECTORY); |
bikeNomad | 0:0944c3654ded | 239 | } |
bikeNomad | 0:0944c3654ded | 240 | |
bikeNomad | 0:0944c3654ded | 241 | for(;;) |
bikeNomad | 0:0944c3654ded | 242 | ; |
bikeNomad | 0:0944c3654ded | 243 | } |