Big Mouth Billy Bass automation library
player.hpp
- Committer:
- bikeNomad
- Date:
- 2013-06-18
- Revision:
- 3:6c91a6232c4a
- Parent:
- 2:eaba75af0f0d
- Child:
- 4:f009306756b3
File content as of revision 3:6c91a6232c4a:
#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; Song::actions_t::iterator 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) { if (song) delete song; song = _song; nextAction = song->getActions().begin(); timeInSong = 0.0; actionsDone = 0; fprintf(stderr, "starting %s: ", song->getSampleFileName()); if (fp) fclose(fp); fp = fopen(song->getSampleFileName(), "rb"); fprintf(stderr, "opened, "); if (!fp) return false; fprintf(stderr, "seekend, "); if (fseek(fp, 0, SEEK_END)) return false; long fileSize = ftell(fp); fprintf(stderr, "size=%d, ", fileSize); if (fileSize < 0) return false; if (fseek(fp, 0, SEEK_SET)) return false; 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"); 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 (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; while (!isDone()) { while (nextAction != song->getActions().end() && nextAction->actIfPast(timeInSong)) { fprintf(stderr, "."); actionsDone++; nextAction++; } loadIfNecessary(); } sampleTicker.detach(); song->myFish()->relax(); } }; #endif