Big Mouth Billy Bass automation library
player.hpp
- Committer:
- bikeNomad
- Date:
- 2013-06-17
- Revision:
- 0:84aaade0de8f
- Child:
- 2:eaba75af0f0d
File content as of revision 0:84aaade0de8f:
#ifndef __included_player_hpp #define __included_player_hpp #include "billybass.hpp" 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(); } }; #endif