Playing music

Here’s an update of Sensitive using my latest version of the music player, you can hear that the looping samples are going a but weird, I don’t know if there’s a trick to that or not.

sensitive.bin (208.7 KB)

I’ve cut the tune format down a little, resulting in a 10kb smaller file with the above tune, and the sample format now includes loop data, which is read directly from a converted .wav file.

What do you guys thing of the sound quality?

2 Likes

Sounds good :slight_smile: Amazing that there is such a long soundtrack in ROM!

I just need to create some sort of tracker so other people can use it :stuck_out_tongue:
There’s 27 samples and 50 patterns in that one, here’s a look at the track array…

const char my_pattern[]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,45,46,48,49};

const uint8_t tune[50][64][4][5]={
{
// Pattern:0
//      .----------------------[Note] Upper nibble is the note value.
//      |.---------------------[Octave] Lower nibble is the octave.
//      ||   .-----------------[Volume + 128] If first bit is set then volume is used.
//      ||   |    .------------[Instrument] Can use up to 255 instruments I suppose.
//      ||   |    |    .-------[Effect] Effects are based on standard MOD effects.
//      ||   |    |    |    .--[Effect Value] Values for above effects.
//      ||   |    |    |    |
    {{0x15,0xC0,0x02,0x0C,0x01},{0x00,0x00,0x00,0x0F,0x03},{0x00,0x00,0x00,0x0C,0x00},{0x00,0x00,0x00,0x0C,0x00}},
    {{0x00,0x00,0x00,0x0C,0x02},{0x00,0x00,0x00,0x0C,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x00,0x00,0x00,0x0C,0x03},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x00,0x00,0x00,0x0C,0x04},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x15,0xC0,0x03,0x0C,0x05},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x00,0x00,0x00,0x0C,0x06},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x00,0x00,0x00,0x0C,0x07},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x00,0x00,0x00,0x0C,0x08},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x15,0xC0,0x04,0x0C,0x09},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
    {{0x00,0x00,0x00,0x0C,0x0A},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00}},
// etc.

I think if I limit the number of samples to 15, I can probably merge the instrument and effect bytes and save a little more space…

2 Likes

Instead of just using raw uint8_ts,
you could make a struct to pack the data into so it’s easier to understand and manipulate.

E.g.

enum class Instrument : std::uint8_t
{
	Trumpet,
	Drum,
	Fish,
};

enum class MusicEffect : std::uint8_t
{
	None,
	SineWave,
	Tremolo,
};

enum class Note : std::uint8_t
{
	A, B, C, D, E, F, G,
};

class NoteAndOctave
{
private:
	static constexpr std::uint8_t noteMask = 0x0F;
	static constexpr std::uint8_t noteShift = 4;
	
	static constexpr std::uint8_t octaveMask = 0x0F;
	static constexpr std::uint8_t octaveShift = 0;
	
	constexpr std::uint8_t createValue(Note note, std::uint8_t octave)
	{	
		return static_cast<std::uint8_t>(((static_cast<std::uint8_t>(note) & noteMask) << noteShift) | ((octave & octaveMask) << octaveShift));
	}

private:
	std::uint8_t value;
	
public:
	NoteAndOctave() = default;
	
	constexpr NoteAndOctave(std::uint8_t value) :
		value(value)
	{
	}
	
	constexpr NoteAndOctave(Note note, std::uint8_t octave) :
		value(createValue(note, octave))
	{
	}
	
	constexpr Note getNote() const
	{
		return static_cast<Note>((this->value >> noteShift) & noteMask);
	}
	
	constexpr std::uint8_t getOctave() const
	{
		return static_cast<std::uint8_t>((this->value >> octaveShift) & octaveMask);
	}
};

class MusicThing
{
private:
	NoteAndOctave noteAndOctave;
	std::uint8_t volume;
	Instrument instrument;
	MusicEffect effect;
	std::uint8_t effectValue;
	
public:
	constexpr MusicThing(NoteAndOctave noteAndOctave, std::uint8_t volume, Instrument instrument, MusicEffect effect, std::uint8_t effectValue) :
		noteAndOctave(noteAndOctave), volume(volume), instrument(instrument), effect(effect), effectValue(effectValue)
	{
	}
	
	constexpr Note getNote() const
	{
		return this->noteAndOctave.getNote();
	}
	
	constexpr std::uint8_t getOctave() const
	{
		return this->noteAndOctave.getOctave();
	}
	
	constexpr Instrument getInstrument() const
	{
		return this->instrument;
	}
	
	constexpr MusicEffect getEffect() const
	{
		return this->effect;
	}
	
	constexpr std::uint8_t getEffectValue() const
	{
		return this->effectValue;
	}
};

Then you could declare a sound part like:

{ { Note::A, 5 }, 192, Instrument::Drum, MusicEffect::Tremolo, 0x01 }
2 Likes

2 Likes

You know, if I strip some of the unfinished features out from my music player, I bet it could work well. If say I forget trying to add any of the special effect etc. and simple have notes volumes and a fixed set of instruments, it could work well.

Yeah, the audio quality sounds very promising in your system. How much it would increase the rom size (the code and the default instruments)? How much is affects to cpu performance?

If there was the ability to add own instrument samples, how much would the rom cost be per instrument (minimum)?

The way it stands now, if my most recent PR got added (I haven’t checked) then some of the code is already in pokittolib. Its effect on speed seems to me very little. I’ve been using Sensitive to test the changes (which used direct bitmaps so is already running quite slow) doesn’t seem to slow down at all when the audio quality is set to 44100.
As for size, that depends entirely on the samples. Currently I’m using samples taken directly from old protracker mod files without compression, so some of the sample sets are up to 150+kb, but with a handful of fixed instruments, piano, guitar, drum etc. I bet we can keep the size very small, it can handle ANY sample rate, even non-standard rates and looping works well now, so it’s possible that an instrument can be really short.
For the tune data, 1byte per note (with octave) + 1 byte per instrument (+ volume?) for maybe 4 channels, we can probably keep that size down also.

sensitive.bin (215.8 KB)
Above is my latest build of sensitive, which is as far as the music player is right now. it’s updating the buffer in the playback interrupt with no ill effect on the gameplay.

The linker is able to drop out the code that is not used. So it probably will take ROM space only after it is taken into use in a game.

That’s impressive!

I suppose some sort of REAL speed test might be needed though.

Btw. are you using any extra RAM buffers or just feeding the mixed samples to the PokittoLib sound buffer?

Very little RAM is being used…

typedef struct{
    bool playSample;
    int soundPoint;
    const uint8_t *currentSound;
    uint32_t currentSoundSize;
    int volume;
    int speed;
    int repeat;
}sampletype;

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

The samples are pulled straight from ROM, as is the tune data.

uint8_t mixSound()
{
    int temp = 0;
    signed int ss[4];
    for(int s=0; s<4; s++){
        snd[s].soundPoint++;

        int currentPos = (snd[s].soundPoint*snd[s].speed)>>8;

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

        ss[s] = snd[s].currentSound[currentPos] -128;
        ss[s] *= snd[s].volume;
        ss[s] = ss[s]>>8;
        ss[s] *= snd[s].playSample; // will be 1 or 0, if not playing, will silence

     }
    temp = (ss[0] + ss[1] + ss[2] + ss[3])/4;
    return temp +128;
}


inline void pokSoundBufferedIRQ() {

#if POK_AUD_TRACKER
           uint32_t output = mixSound();
#else
           uint32_t output = soundbuf[soundbufindex+=Pokitto::streamon];
#endif

           if (soundbufindex==SBUFSIZE){
                soundbufindex=0;
           }

           //if (p==sizeof(beat_11025_raw)) p=0;
           //soundbuf[soundbufindex++] = output;
           //uint32_t t_on = (uint32_t)(((obj->pwm->MATCHREL0)*output)>>8); //cut out float
           //obj->pwm->MATCHREL1 = t_on;
           output *= discrete_vol_multipliers[discrete_vol];
           output >>= 8;
           dac_write(output);

           //setDRAMpoint(pixx, pixy);
}

Looks very neat and simple. Is the speed parameter related to the note pitch?

yes, I have a list of speeds,

const int note_speed[]={
16,17,18,19,20,21,23,24,25,27,29,30, // C, C#, D, D#, E, F, F#, G, G#, A, A#, B,
32,34,36,38,40,43,45,48,51,54,57,60,
64,68,72,76,81,85,91,96,102,108,114,121,
128,136,144,152,161,171,181,192,203,215,228,242,
256,271,287,304,323,342,362,384,406,431,456,483,
512,542,575,609,645,683,724,767,813,861,912,967,
1024,1085,1149,1218,1290,1367,1448,1534,1625,1722,1825,1933,
2048,2170,2299,2435,2580,2734,2896,3068,3251,3444,3649,3866,
4096,4339,4598,4871,5161,5467,5793,6137,6502,6889,7298,7732
};

// new playsound function
uint8_t playSound(int channel, const unsigned char *sound, int volume = 255, int speed=255, int offset=0){
    int dataOffset = 14 + offset;


    // get sound length
    uint32_t soundSize = 0;
    for(int t=0; t<4; t++){
        soundSize <<= 8;
        soundSize |= sound[t] & 0xFF;
    }

    int sampleRate = 0;
    for(int t=0; t<2; t++){
        sampleRate <<= 8;
        sampleRate |= sound[t+4] & 0xFF;
    }

    // get repeat start
    int repeatStart = 0;
    for(int t=0; t<4; t++){
        repeatStart <<= 8;
        repeatStart |= sound[t+6] & 0xFF;
    }

    // get repeat end
    int repeatEnd = 0;
    for(int t=0; t<4; t++){
        repeatEnd <<= 8;
        repeatEnd |= sound[t+10] & 0xFF;
    }

    // cheat, if there is a looping sample, make the size the length of the loop
    if(repeatEnd != 0){
        soundSize = repeatEnd;
        // also cheat for start loop
        if(repeatStart == 0){
           repeatStart = 1;
        }
    }

    float spd = (POK_AUD_FREQ / (float)sampleRate);

    Pokitto::snd[channel].currentSound = &sound[dataOffset];          // sound to play
    Pokitto::snd[channel].volume = volume;               // volume
    Pokitto::snd[channel].speed = (speed/spd);           // recalculated above
    Pokitto::snd[channel].currentSoundSize = soundSize;  // length of sound array
    Pokitto::snd[channel].soundPoint = 0;                // where the current sound is upto
    Pokitto::snd[channel].repeat = repeatStart * 256 / Pokitto::snd[channel].speed;          // repeat point
    Pokitto::snd[channel].playSample = 1;                // trigger sample playing

    return channel;
}

I created this from a list of note frequencies I found online and recalculated them into ‘precentages’ out of 255 (instead of 100) then used a multiply and divide to remove the need for floats.

later today, I’ll update a music player example and upload it to show whats going on.

As the mixSound() is called very often it might be worthwhile to do a separate if-branch for the silence (playSample==0) at the very beginning of the for block. In “silent” sample case you could just store zero to the ss[] buffer and increase and check the soundPoint counter, and skip the rest of the calculations.

Moved the discussion over here from another thread…

Btw. I cannot find your PR in PokittoLib GitHub

I suppose this can handle sound effects too? I mean e.g. 3 channels for music and 1 channel for effects?

Eventually, we should support 4-bit samples too but lets do first things first.

Also, you might not know Jonne’s fix to the DAC audio(?). I do not know if it affects to your audio system but for streaming audio, it was essential. The fix is here in my PR ( Pokitto::dac_write() change only): https://github.com/pokitto/PokittoLib/pull/76/commits/531e507fd352121acd17c7b673d672b9c1cd4c9a

It will be about 2 months ago I think.

[edit] maybe not.
I’m trying to add to a new clone of pokittolib, but now I’m having trouble with mixMode screen mode.
MusicPlayer\main.cpp|223|undefined reference to `Pokitto::Display::scanType’|
Which is odd, because that has a) been working for ages and b) is defined, I can see it in the various source files.

I think I managed a PR. I hate github.

1 Like