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:
- 1:f3f439154ab4
- Parent:
- 0:0944c3654ded
- Child:
- 2:f96989f2b8a1
File content as of revision 1:f3f439154ab4:
#include "mbed.h"
#include "SDFileSystem.h"
#include <list>
#include <cmath>
#define SD_NAME "sd"
#define SD_ROOT "/" SD_NAME
#define BASS_DIRECTORY SD_ROOT
// 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_%30[^_.].%3[a-zA-Z]", &num1, &num2, basename, extension);
if (nItems != 4) return false;
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);
}
};
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;
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;
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()) {
return loadNextChunk();
} else return true;
}
};
std::list<Song> songs;
int main()
{
// 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");
} else {
pc.printf("\tnot valid\r\n");
}
}
} else {
pc.printf("Error opening " BASS_DIRECTORY);
}
for(;;)
;
}
Ned Konz