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);
}
}
Ned Konz