Big Mouth Billy Bass player that takes raw wavefiles and decision list text files from an SD card
Dependencies: SDFileSystem mbed BillyBass
main.cpp
- Committer:
- bikeNomad
- Date:
- 2013-06-07
- Revision:
- 3:00b4c1aadd30
- Parent:
- 2:f96989f2b8a1
- Child:
- 4:babc37764bd3
File content as of revision 3:00b4c1aadd30:
#include "mbed.h" #include "SDFileSystem.h" #include <list> #include <cmath> #define SD_NAME "sd" #define SD_ROOT "/" SD_NAME #define BASS_DIRECTORY SD_ROOT "/SD_Files" // Power: // Power GND J9/14 // Vin (6V) J9/16 // Digital: DigitalOut tail(PTA13); // J3/2 DigitalOut mouth(PTC12); // J3/1 DigitalOut head(PTC13); // J3/3 DigitalIn pushbutton(PTD5); // J3/4 PwmOut redLED(LED_RED); PwmOut greenLED(LED_GREEN); PwmOut blueLED(LED_BLUE); // Analog: // GND J3/14 // VrefH J3/16 AnalogOut speaker(PTE30); // J10/11 // PTD0 D10 – Used for CS of SPI // PTD2 D11 – Used for MOSI of SPI // PTD3 D12 – Used for MISO of SPI // PTC5 J1/9 Used for SCLK of SPI // MOSI, MISO, SCLK, CS, name SDFileSystem sd(PTD2, PTD3, PTC5, PTD0, SD_NAME); Serial pc(USBTX, USBRX); typedef int16_t Sample_t; // 16-bit raw, LE samples const size_t BUFFER_SIZE = 512; const float SAMPLE_RATE_HZ = 8000.0; const unsigned SAMPLE_PERIOD_USEC = (unsigned)(1.0e6/SAMPLE_RATE_HZ); const size_t SAMPLES_PER_BUFFER = BUFFER_SIZE / sizeof(Sample_t); const float SECONDS_PER_CHUNK = SAMPLES_PER_BUFFER / SAMPLE_RATE_HZ; struct Action { float actionTime; bool desiredState; DigitalOut *output; bool operator < (Action const &other) const { return actionTime < other.actionTime; } bool isValid() { return actionTime >= 0.0 && output != 0; } Action() : actionTime(-1.0), desiredState(false), output(0) { } bool parseLine(char const *line) { // TODO return true; } }; #define MAX_BASENAME_LENGTH 30 #define MAX_ACTION_LINE_LENGTH 100 struct Song { char basename[ MAX_BASENAME_LENGTH + 1 ]; unsigned sequenceNumber; unsigned whichFish; std::list<Action> actions; Song() : sequenceNumber(0), whichFish(3) { basename[0] = 0; } bool readWaveFile(char const *_waveFileName) { if (!parseFilename(_waveFileName, sequenceNumber, whichFish, basename)) return false; char txtFileName[ FILENAME_MAX ]; sprintf(txtFileName, "%u_%u_%s.txt", sequenceNumber, whichFish, basename); FILE *txtfile = fopen(txtFileName, "rt"); if (!txtfile) return false; // TODO // read actions from file char actionLine[ MAX_ACTION_LINE_LENGTH + 1 ]; while (fgets(actionLine, sizeof(actionLine), txtfile)) { Action action; if (action.parseLine(actionLine)) { actions.push_back(action); } } return true; } static bool parseFilename(char const *_name, unsigned &_num1, unsigned &_num2, char *_basename) { char basename[ MAX_BASENAME_LENGTH + 1 ]; unsigned num1, num2; char extension[ 4 ]; int nItems = sscanf(_name, "%u_%u_%s", &num1, &num2, basename); if (nItems != 3) { pc.printf(" nItems=%d\n", nItems); return false; } char *p = strrchr(basename, '.'); if (!p) { pc.printf(" no extension\n"); return false;} strcpy(extension, p+1); *p = 0; if (num2 > 2) { pc.printf(" num2=%d\n", num2); return false; } if (strcasecmp("raw", extension)) { pc.printf(" ext=%s\n", extension); return false; } _num1 = num1; _num2 = num2; strcpy(_basename, basename); return true; } // return true if filename is of proper format // <num>_<num>_<name>.raw // and there is a corresponding .txt file static bool isValidWaveFileName(char const *_name) { unsigned int num1, num2; char basename[ 31 ]; return parseFilename(_name, num1, num2, basename); } }; class SongPlayer; struct SampleBuffer { Sample_t volatile buf[ SAMPLES_PER_BUFFER ]; size_t volatile samplesRemaining; Sample_t volatile * volatile nextSample; bool isDone() { return !samplesRemaining; } float remainingDuration() { return samplesRemaining / SAMPLE_RATE_HZ; } // return true if we read any samples bool loadFrom(FILE *fp) { samplesRemaining = fread((void *)buf, sizeof(Sample_t), SAMPLES_PER_BUFFER, fp); nextSample = buf; return samplesRemaining > 0; } Sample_t getNextSample() { --samplesRemaining; return *++nextSample; } SampleBuffer() : samplesRemaining(0), nextSample(buf) { } }; struct SongPlayer { SampleBuffer * volatile playing; SampleBuffer * volatile loading; SampleBuffer buffer[2]; FILE *fp; size_t nChunks; size_t volatile chunksRemaining; float timeInSong; Ticker sampleTicker; // interrupt handler void playNextSample(void) { if (!playing->samplesRemaining) swapBuffers(); // TODO change bias to 0xC000 (requires normalizing to 75% of full scale) speaker.write_u16(static_cast<uint16_t>(playing->getNextSample() + 0x8000)); } bool startSong(char const *name) { if (fp) fclose(fp); fp = fopen(name, "rb"); if (!fp) return false; if (fseek(fp, 0, SEEK_END)) return false; long fileSize = ftell(fp); if (fileSize < 0) return false; if (fseek(fp, 0, SEEK_SET)) return false; chunksRemaining = nChunks = fileSize / BUFFER_SIZE; loading = &buffer[0]; playing = &buffer[1]; if (! loadNextChunk()) return false; timeInSong = 0.0; sampleTicker.attach_us(this, &SongPlayer::playNextSample, SAMPLE_PERIOD_USEC); return true; } // swap loading/playing buffers; // decrement chunksRemaining void swapBuffers() { SampleBuffer * volatile tmp = playing; if (tmp == buffer + 0) playing = buffer + 1; else playing = buffer + 0; loading = tmp; if (chunksRemaining) chunksRemaining--; } // get next chunk of file into *loading // to prepare for eventual swap. // returns true if more samples remain bool loadNextChunk() { if (! chunksRemaining) return false; bool notDone = loading->loadFrom(fp); return notDone; } bool isDone() { return !chunksRemaining; } // look at loading buffer; load only if necessary. bool loadIfNecessary() { if (loading->isDone()) { timeInSong += SECONDS_PER_CHUNK; return loadNextChunk(); } else return true; } void playEntireSong(char const *name) { if (!startSong(name)) return; while (!isDone()) { loadIfNecessary(); } sampleTicker.detach(); } }; std::list<Song> songs; int main() { SongPlayer player; // read the directory DIR *bassDir = opendir(BASS_DIRECTORY); if (bassDir) { while (dirent *dir = bassDir->readdir()) { pc.printf("%s", dir->d_name); // if this is a valid wave filename if (Song::isValidWaveFileName(dir->d_name)) { pc.printf("\tvalid\r\n"); player.playEntireSong(dir->d_name); pc.printf("Song time: %f\r\n", player.timeInSong); } else { pc.printf("\tnot valid\r\n"); } } } else { pc.printf("Error opening " BASS_DIRECTORY); } }