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
Ned Konz