Big Mouth Billy Bass player that takes raw wavefiles and decision list text files from an SD card
Dependencies: SDFileSystem mbed BillyBass
Revision 5:5b846ef42702, committed 2013-06-15
- Comitter:
- bikeNomad
- Date:
- Sat Jun 15 03:32:20 2013 +0000
- Parent:
- 4:babc37764bd3
- Child:
- 6:e90a12ca056f
- Commit message:
- separated into multiple files
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/action.hpp Sat Jun 15 03:32:20 2013 +0000 @@ -0,0 +1,33 @@ +#ifndef __included_action_hpp +#define __included_action_hpp + +#include "billybass.hpp" + +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; + } +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/billybass.hpp Sat Jun 15 03:32:20 2013 +0000 @@ -0,0 +1,39 @@ +#ifndef __included_billybass_hpp +#define __included_billybass_hpp + +#include "mbed.h" +#include "SDFileSystem.h" +#include <list> +#include <cmath> + +struct Song; + +#define SD_NAME "sd" +#define SD_ROOT "/" SD_NAME +#define BASS_DIRECTORY SD_ROOT "/SD_Files" + +typedef int16_t Sample_t; // 16-bit raw, LE samples + +const size_t BUFFER_SIZE = 512; +const float SAMPLE_RATE_HZ = 8000.0; +#define MAX_BASENAME_LENGTH 30 +#define MAX_ACTION_LINE_LENGTH 100 +#define SERIAL_BAUD 115200 + +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; + +extern DigitalOut tail; // J3/2 +extern DigitalOut mouth; // J3/1 +extern DigitalOut head; // J3/3 +extern DigitalIn pushbutton; // J3/4 +extern PwmOut redLED; +extern PwmOut greenLED; +extern PwmOut blueLED; +extern AnalogOut speaker; // J10/11 +extern SDFileSystem sd; +extern Serial pc; +extern std::list<Song> songs; + +#endif
--- a/main.cpp Fri Jun 14 05:08:54 2013 +0000 +++ b/main.cpp Sat Jun 15 03:32:20 2013 +0000 @@ -1,20 +1,16 @@ -#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" +#include "billybass.hpp" +#include "song.hpp" +#include "player.hpp" +#include "action.hpp" // 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 +DigitalOut tail(PTA13); // J3/2 +DigitalOut mouth(PTC12); // J3/1 +DigitalOut head(PTC13); // J3/3 DigitalIn pushbutton(PTD5); // J3/4 @@ -25,259 +21,48 @@ // Analog: // GND J3/14 // VrefH J3/16 -AnalogOut speaker(PTE30); // J10/11 +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 +// 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, BASS_DIRECTORY "/%u_%u_%s", &num1, &num2, basename); - if (nItems != 3) { - pc.printf(" nItems=%d\r\n", nItems); - return false; - } - char *p = strrchr(basename, '.'); - if (!p) { - pc.printf(" no extension\r\n"); - return false; - } - strcpy(extension, p+1); - *p = 0; - if (num2 > 2) { - pc.printf(" num2=%d\r\n", num2); - return false; - } - if (strcasecmp("raw", extension)) { - pc.printf(" ext=%s\r\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; - - SongPlayer() : playing(0), loading(0), fp(0), nChunks(0), chunksRemaining(0) - {} - - // interrupt handler - void playNextSample(void) { - if (playing->samplesRemaining == 0) - swapBuffers(); - // NOTE bias of 0xC000 requires normalizing to 75% of full scale - speaker.write_u16(static_cast<uint16_t>(playing->getNextSample() + 0x8000) / 2); - } - - bool startSong(char const *name) { - pc.printf("starting %s: ", name); - if (fp) fclose(fp); - fp = fopen(name, "rb"); - pc.printf("opened, "); - if (!fp) return false; - pc.printf("seekend, "); - if (fseek(fp, 0, SEEK_END)) return false; - long fileSize = ftell(fp); - pc.printf("size=%d, ", fileSize); - if (fileSize < 0) return false; - if (fseek(fp, 0, SEEK_SET)) return false; - pc.printf("rewound, "); - chunksRemaining = nChunks = fileSize / BUFFER_SIZE; - loading = &buffer[0]; - playing = &buffer[1]; - pc.printf("chunks=%d expected=%f seconds\r\n", nChunks, nChunks * SECONDS_PER_CHUNK); - 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; - pc.baud(115200); + pc.baud(SERIAL_BAUD); // read the directory DIR *bassDir = opendir(BASS_DIRECTORY); - if (bassDir) { - while (dirent *dir = bassDir->readdir()) { + if (bassDir) + { + while (dirent *dir = bassDir->readdir()) + { char fn[ 60 ]; snprintf(fn, sizeof(fn), "%s/%s", BASS_DIRECTORY, dir->d_name); pc.printf(fn); // if this is a valid wave filename - if (Song::isValidWaveFileName(fn)) { + if (Song::isValidWaveFileName(fn)) + { pc.printf("\tvalid\r\n"); player.playEntireSong(fn); pc.printf("Song time: %f\r\n", player.timeInSong); - } else { + } + else + { pc.printf("\tnot valid\r\n"); } } - } else { + } + else + { pc.printf("Error opening " BASS_DIRECTORY); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/player.hpp Sat Jun 15 03:32:20 2013 +0000 @@ -0,0 +1,154 @@ +#ifndef __included_player_hpp +#define __included_player_hpp + +#include "billybass.hpp" + +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; + + SongPlayer() : playing(0) + , loading(0) + , fp(0) + , nChunks(0) + , chunksRemaining(0) + { + } + + // interrupt handler + void playNextSample(void) + { + if (playing->samplesRemaining == 0) + swapBuffers(); + // NOTE bias of 0xC000 requires normalizing to 75% of full scale + speaker.write_u16(static_cast<uint16_t>(playing->getNextSample() + 0x8000) / 2); + } + + bool startSong(char const *name) + { + pc.printf("starting %s: ", name); + if (fp) fclose(fp); + fp = fopen(name, "rb"); + pc.printf("opened, "); + if (!fp) return false; + + pc.printf("seekend, "); + if (fseek(fp, 0, SEEK_END)) return false; + + long fileSize = ftell(fp); + pc.printf("size=%d, ", fileSize); + if (fileSize < 0) return false; + + if (fseek(fp, 0, SEEK_SET)) return false; + + pc.printf("rewound, "); + chunksRemaining = nChunks = fileSize / BUFFER_SIZE; + loading = &buffer[0]; + playing = &buffer[1]; + pc.printf("chunks=%d expected=%f seconds\r\n", nChunks, nChunks * SECONDS_PER_CHUNK); + 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(); + } +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/song.hpp Sat Jun 15 03:32:20 2013 +0000 @@ -0,0 +1,83 @@ +#ifndef __included_song_hpp +#define __included_song_hpp + +#include "billybass.hpp" +#include "action.hpp" + +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, BASS_DIRECTORY "/%u_%u_%s", &num1, &num2, basename); + if (nItems != 3) + { + return false; + } + char *p = strrchr(basename, '.'); + if (!p) + { + return false; + } + strcpy(extension, p+1); + *p = 0; + if (num2 > 2) + { + return false; + } + if (strcasecmp("raw", 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); + } +}; + +#endif // __included_song_hpp