Big Mouth Billy Bass automation library
player.hpp
- Committer:
- bikeNomad
- Date:
- 2013-06-20
- Revision:
- 7:dba9221acf48
- Parent:
- 6:ea8136eb6976
File content as of revision 7:dba9221acf48:
#ifndef __included_player_hpp
#define __included_player_hpp
#include "billybass.hpp"
extern AnalogOut speaker;
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;
Song *song;
Action *nextAction;
unsigned actionsDone;
SongPlayer() : playing(0)
, loading(0)
, fp(0)
, nChunks(0)
, chunksRemaining(0)
, song(0)
, actionsDone(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() + ANALOG_OUTPUT_BIAS));
}
bool startSong(Song *_song) {
song = _song;
nextAction = song->getActions();
timeInSong = 0.0;
actionsDone = 0;
fprintf(stderr, "starting %s: ", song->getSampleFileName());
if (fp) fclose(fp);
fp = fopen(song->getSampleFileName(), "rb");
if (!fp) goto on_error;
fprintf(stderr, "opened, ");
if (fseek(fp, 0, SEEK_END)) goto on_error;
fprintf(stderr, "seekend, ");
long fileSize = ftell(fp);
fprintf(stderr, "size=%d, ", fileSize);
if (fileSize < 0) goto on_error;
if (fseek(fp, 0, SEEK_SET)) goto on_error;
fprintf(stderr, "rewound, ");
chunksRemaining = nChunks = fileSize / BUFFER_SIZE;
loading = &buffer[0];
playing = &buffer[1];
fprintf(stderr, "chunks=%d expected=%f seconds\r\n", nChunks, nChunks * SECONDS_PER_CHUNK);
if (!loadNextChunk()) {
fprintf(stderr, "first chunk empty!\r\n");
goto on_error;
}
sampleTicker.attach_us(this, &SongPlayer::playNextSample, SAMPLE_PERIOD_USEC);
return true;
on_error:
if (fp) fclose(fp);
fp = 0;
return false;
}
// 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 (isDone())
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()) {
fprintf(stderr, "*");
timeInSong += SECONDS_PER_CHUNK;
return loadNextChunk();
} else {
return true;
}
}
void playEntireSong(Song *_song) {
if (!startSong(_song)) return;
Action* lastAction = song->getActions() + song->getNumActions();
while (!isDone()) {
while (nextAction < lastAction && nextAction->isPast(timeInSong)) {
nextAction->act();
fputc(nextAction->code, stderr);
actionsDone++;
nextAction++;
}
loadIfNecessary();
}
sampleTicker.detach();
song->myFish()->relax();
}
};
#endif
Ned Konz