Pokitto music player…. Part 1.

OK, as the Pokitto (as of winter 2018) does not yet have much in the way of in-game music playback that I like, I have taken it upon myself to attempt to create a tracker style player to use in my games. At this point in time, it doesn’t have a name, nor much of an idea of storage format, but I hope to keep a log of my progress and unlike most of my coding projects, it will be completed!

 

My first aim in this project as simply to get raw pcm data to play smoothly at the correct speed. The following code, manages to play 4 different sample rates remapped in real time to 44100mhz at the correct speed. The only downside, which might damage things later, is that there are a couple of floats in there.

 

#include "Pokitto.h"
#include "HWSound.h"
#include "snd.h"

Pokitto::Core mygame;
Pokitto::Display d;

typedef struct{
    bool playSample;                // should this sound be playing now?
    int soundPoint;                 // current position of sound
    const uint8_t *currentSound;    // currend sound array playing
    uint32_t currentSoundSize;      // length of current sound
    int volume;                     // volume of sound, best to be 63 or lower
    int speed;                      // how fast to play sound, 256 = 100%
    bool repeat;                    // 1 to repeat 0 not to
    int repeat_start;               // start point of repeat, default should be 0
    int repeat_end;                 // end point of repeate, default should be currentSoundSize
} sampletype;

sampletype snd[4];                  // up to 4 sounds at once?
int oldQuart;

int currentLine=0;
int mytick=0;
int pattern=0; 

Ticker soundPlay;
Ticker sounder1;


uint8_t playSound(int channel, const unsigned char *sound, uint32_t soundSize){

    // to resample current sample rate to 44100, cheap and probably wrong way
    float rate1 = 44100 / (float)sampleRates[channel];
    int speed = 256 / rate1; // playback speed is 

    snd[channel].currentSound = sound;                      // sound to play
    snd[channel].currentSoundSize = (soundSize<<8)/speed;   // length of sound array adjusted for speed change
    snd[channel].volume = 63;                               // volume, best kept below 64 as louder will cause clipping when playing multiple samples
    snd[channel].speed = speed;                             // recalculated above
    snd[channel].repeat = 0;                                // 1 to repead, 0 to not
    snd[channel].soundPoint = 0;                            // where the current sound is upto
    snd[channel].playSample = 1;                            // 1 to play this sound, 0 not to

    return channel;
}

uint8_t mixSound(int samplePos)
{
    int temp = 0;
    int ss[4];

    for(int s=0; s<4; s++){
        ss[s] = (snd[s].currentSound[(snd[s].soundPoint*snd[s].speed)>>8]*snd[s].volume>>8) * snd[s].playSample;

        ++snd[s].soundPoint;
        if(snd[s].soundPoint >= snd[s].currentSoundSize){
            if(snd[s].repeat){
                snd[s].soundPoint=0;
            }else{
                snd[s].playSample=0;
                snd[s].soundPoint=0;
            }
        }
     }    

    temp = (ss[0] + ss[1] + ss[2] + ss[3]) - ((ss[0] * ss[1] * ss[2] * ss[3])/256);
    
    return temp;
}


void updateAudioBuffer(){
    // when updating the audio buffer, I split the buffer into 4 and fill
    // a part that isn't being played, so there are no sync issues
    
    int quart = soundbufindex / 512;
    int sndOffset[]={1024,1536,0,512};
    
    // only update if the current playback pointer is nowhere near where we want to fill
    if(oldQuart != quart){
        oldQuart = quart;
        for(int t=0; t<=SBUFSIZE/4;){
            uint8_t sample = mixSound(t);
            soundbuf[t+sndOffset[quart]] = sample;
            t++;
        }
    }
}


int main ()
{
    mygame.begin();
    mygame.setFrameRate(255);

    // Init audio stream.
    pokPlayStream(); // activate stream
    mygame.sound.ampEnable(true);
    mygame.sound.playMusicStream();

    // check and update the sound buffer as often as we can
    soundPlay.attach(&updateAudioBuffer, 0.0001);
    
    d.color = 1;

    while (mygame.isRunning())
    {
        if (mygame.update())
        {
            if (mygame.buttons.leftBtn()){
                playSound(0, s_01, sizeof(s_01));
                d.printf("speed %d\n", snd[0].speed);
                d.printf("rate %d\n", sampleRates[0]);
            }      
            if (mygame.buttons.upBtn()){
                playSound(1, s_02, sizeof(s_02));
                d.printf("speed %d\n", snd[1].speed);
                d.printf("rate %d\n", sampleRates[1]);
            }      
            if (mygame.buttons.rightBtn()){
                playSound(2, s_03, sizeof(s_03));
                d.printf("speed %d\n", snd[2].speed);
                d.printf("rate %d\n", sampleRates[2]);
            }      
            if (mygame.buttons.downBtn()){
                playSound(3, s_04, sizeof(s_04));
                d.printf("speed %d\n", snd[3].speed);
                d.printf("rate %d\n", sampleRates[3]);
            }      
            
        }
    }
}


and of course some data to play!

snd.h

const char sampleRepeat[]={0,0,0}; // does each inetrument repeat?

const uint32_t sampleRates[]={44100, 22050, 16000, 96000}; // sample rate of sample

const unsigned char s_01[55340] = { // 44100
    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
    0x80, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
    0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x81,
 // data removed for space/size
};


const unsigned char s_02[27673] = { // 22050
 0x80, 0x7F, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x80, 0x80,
 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x7F, 0x81, 0x7F,
 0x82, 0x80, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x80, 0x81, 0x7F,
 // data removed for space/size
};


const unsigned char s_03[20081] = { // 16000
 0x80, 0x7F, 0x80, 0x82, 0x7F, 0x7F, 0x81, 0x80, 0x7F, 0x80, 0x80, 0x81,
 0x7F, 0x80, 0x80, 0x81, 0x7F, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x7F,
 0x7F, 0x82, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x7E, 0x80,

 // data removed for space/size
};