Big Mouth Billy Bass automation library

Dependents:   BillyBass_with_SD

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers player.hpp Source File

player.hpp

00001 #ifndef __included_player_hpp
00002 #define __included_player_hpp
00003 
00004 #include "billybass.hpp"
00005 
00006 extern AnalogOut speaker;
00007 
00008 class SongPlayer;
00009 
00010 struct SampleBuffer {
00011     Sample_t volatile buf[ SAMPLES_PER_BUFFER ];
00012     size_t volatile samplesRemaining;
00013     Sample_t volatile * volatile nextSample;
00014 
00015     bool isDone() {
00016         return !samplesRemaining;
00017     }
00018 
00019     float remainingDuration() {
00020         return samplesRemaining / SAMPLE_RATE_HZ;
00021     }
00022 
00023     // return true if we read any samples
00024     bool loadFrom(FILE *fp) {
00025         samplesRemaining = fread((void*)buf, sizeof(Sample_t), SAMPLES_PER_BUFFER, fp);
00026         nextSample       = buf;
00027         return samplesRemaining > 0;
00028     }
00029 
00030     Sample_t getNextSample() {
00031         --samplesRemaining;
00032         return *++nextSample;
00033     }
00034 
00035     SampleBuffer() : samplesRemaining(0)
00036         , nextSample(buf) {
00037     }
00038 };
00039 
00040 struct SongPlayer {
00041     SampleBuffer * volatile playing;
00042     SampleBuffer * volatile loading;
00043     SampleBuffer buffer[2];
00044     FILE *fp;
00045     size_t nChunks;
00046     size_t volatile chunksRemaining;
00047     float timeInSong;
00048     Ticker sampleTicker;
00049     Song *song;
00050     Action *nextAction;
00051     unsigned actionsDone;
00052 
00053     SongPlayer() : playing(0)
00054         , loading(0)
00055         , fp(0)
00056         , nChunks(0)
00057         , chunksRemaining(0)
00058         , song(0)
00059         , actionsDone(0) {
00060     }
00061 
00062     // interrupt handler
00063     void playNextSample(void) {
00064         if (playing->samplesRemaining == 0)
00065             swapBuffers();
00066         // NOTE bias of 0xC000 requires normalizing to 75% of full scale
00067         speaker.write_u16(static_cast<uint16_t>(playing->getNextSample() + ANALOG_OUTPUT_BIAS));
00068     }
00069 
00070     bool startSong(Song *_song) {
00071         song = _song;
00072         nextAction = song->getActions();
00073         timeInSong = 0.0;
00074         actionsDone = 0;
00075 
00076         fprintf(stderr, "starting %s: ", song->getSampleFileName());
00077         if (fp) fclose(fp);
00078         fp = fopen(song->getSampleFileName(), "rb");
00079         if (!fp) goto on_error;
00080         fprintf(stderr, "opened, ");
00081 
00082         if (fseek(fp, 0, SEEK_END)) goto on_error;
00083         fprintf(stderr, "seekend, ");
00084 
00085         long fileSize = ftell(fp);
00086         fprintf(stderr, "size=%d, ", fileSize);
00087         if (fileSize < 0) goto on_error;
00088 
00089         if (fseek(fp, 0, SEEK_SET)) goto on_error;
00090 
00091         fprintf(stderr, "rewound, ");
00092         chunksRemaining = nChunks = fileSize / BUFFER_SIZE;
00093         loading         = &buffer[0];
00094         playing         = &buffer[1];
00095         fprintf(stderr, "chunks=%d expected=%f seconds\r\n", nChunks, nChunks * SECONDS_PER_CHUNK);
00096         if (!loadNextChunk()) {
00097             fprintf(stderr, "first chunk empty!\r\n");
00098             goto on_error;
00099         }
00100 
00101         sampleTicker.attach_us(this, &SongPlayer::playNextSample, SAMPLE_PERIOD_USEC);
00102         return true;
00103 
00104 on_error:
00105         if (fp) fclose(fp);
00106         fp = 0;
00107         return false;
00108     }
00109 
00110     // swap loading/playing buffers;
00111     // decrement chunksRemaining
00112     void swapBuffers() {
00113         SampleBuffer * volatile tmp = playing;
00114         if (tmp == buffer + 0)
00115             playing = buffer + 1;
00116         else
00117             playing = buffer + 0;
00118         loading = tmp;
00119         if (chunksRemaining)
00120             chunksRemaining--;
00121     }
00122 
00123     // get next chunk of file into *loading
00124     // to prepare for eventual swap.
00125     // returns true if more samples remain
00126     bool loadNextChunk() {
00127         if (isDone())
00128             return false;
00129         bool notDone = loading->loadFrom(fp);
00130         return notDone;
00131     }
00132 
00133     bool isDone() {
00134         return !chunksRemaining;
00135     }
00136 
00137     // look at loading buffer; load only if necessary.
00138     bool loadIfNecessary() {
00139         if (loading->isDone()) {
00140             fprintf(stderr, "*");
00141             timeInSong += SECONDS_PER_CHUNK;
00142             return loadNextChunk();
00143         } else {
00144             return true;
00145         }
00146     }
00147 
00148     void playEntireSong(Song *_song) {
00149         if (!startSong(_song)) return;
00150         Action* lastAction = song->getActions() + song->getNumActions();
00151         while (!isDone()) {
00152             while (nextAction < lastAction && nextAction->isPast(timeInSong)) {
00153                 nextAction->act();
00154                 fputc(nextAction->code, stderr);
00155                 actionsDone++;
00156                 nextAction++;
00157             }
00158             loadIfNecessary();
00159         }
00160         sampleTicker.detach();
00161         song->myFish()->relax();
00162     }
00163 };
00164 
00165 #endif