Big Mouth Billy Bass automation library
player.hpp
- Committer:
- bikeNomad
- Date:
- 2013-06-20
- Revision:
- 8:ad0c038ebfc1
- Parent:
- 7:dba9221acf48
File content as of revision 8:ad0c038ebfc1:
#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