Big Mouth Billy Bass player that takes raw wavefiles and decision list text files from an SD card
Dependencies: SDFileSystem mbed BillyBass
Diff: main.cpp
- Revision:
- 5:5b846ef42702
- Parent:
- 4:babc37764bd3
- Child:
- 6:e90a12ca056f
--- 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); } }