Big Mouth Billy Bass automation library
Embed:
(wiki syntax)
Show/hide line numbers
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
Generated on Sun Jul 17 2022 08:00:03 by 1.7.2