Big Mouth Billy Bass player that takes raw wavefiles and decision list text files from an SD card

Dependencies:   SDFileSystem mbed BillyBass

Files at this revision

API Documentation at this revision

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

action.hpp Show annotated file Show diff for this revision Revisions of this file
billybass.hpp Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
player.hpp Show annotated file Show diff for this revision Revisions of this file
song.hpp Show annotated file Show diff for this revision Revisions of this file
--- /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