Big Mouth Billy Bass automation library

Dependents:   BillyBass_with_SD

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