Big Mouth Billy Bass automation library

Dependents:   BillyBass_with_SD

player.hpp

Committer:
bikeNomad
Date:
2013-06-20
Revision:
8:ad0c038ebfc1
Parent:
7:dba9221acf48

File content as of revision 8:ad0c038ebfc1:

#ifndef __included_player_hpp
#define __included_player_hpp

#include "billybass.hpp"

extern AnalogOut speaker;

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;
    Song *song;
    Action *nextAction;
    unsigned actionsDone;

    SongPlayer() : playing(0)
        , loading(0)
        , fp(0)
        , nChunks(0)
        , chunksRemaining(0)
        , song(0)
        , actionsDone(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() + ANALOG_OUTPUT_BIAS));
    }

    bool startSong(Song *_song) {
        song = _song;
        nextAction = song->getActions();
        timeInSong = 0.0;
        actionsDone = 0;

        fprintf(stderr, "starting %s: ", song->getSampleFileName());
        if (fp) fclose(fp);
        fp = fopen(song->getSampleFileName(), "rb");
        if (!fp) goto on_error;
        fprintf(stderr, "opened, ");

        if (fseek(fp, 0, SEEK_END)) goto on_error;
        fprintf(stderr, "seekend, ");

        long fileSize = ftell(fp);
        fprintf(stderr, "size=%d, ", fileSize);
        if (fileSize < 0) goto on_error;

        if (fseek(fp, 0, SEEK_SET)) goto on_error;

        fprintf(stderr, "rewound, ");
        chunksRemaining = nChunks = fileSize / BUFFER_SIZE;
        loading         = &buffer[0];
        playing         = &buffer[1];
        fprintf(stderr, "chunks=%d expected=%f seconds\r\n", nChunks, nChunks * SECONDS_PER_CHUNK);
        if (!loadNextChunk()) {
            fprintf(stderr, "first chunk empty!\r\n");
            goto on_error;
        }

        sampleTicker.attach_us(this, &SongPlayer::playNextSample, SAMPLE_PERIOD_USEC);
        return true;

on_error:
        if (fp) fclose(fp);
        fp = 0;
        return false;
    }

    // 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 (isDone())
            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()) {
            fprintf(stderr, "*");
            timeInSong += SECONDS_PER_CHUNK;
            return loadNextChunk();
        } else {
            return true;
        }
    }

    void playEntireSong(Song *_song) {
        if (!startSong(_song)) return;
        Action* lastAction = song->getActions() + song->getNumActions();
        while (!isDone()) {
            while (nextAction < lastAction && nextAction->isPast(timeInSong)) {
                nextAction->act();
                fputc(nextAction->code, stderr);
                actionsDone++;
                nextAction++;
            }
            loadIfNecessary();
        }
        sampleTicker.detach();
        song->myFish()->relax();
    }
};

#endif