Playing music

open-source

#1

Before anyone uses this code, it has became clear to me that some people prefer clarification on licencing when source is open.
I will make the same statement here as I do when anyone asks, that is this.
If my source code is available, then I genuinely do not care what you do with it. If I intended to restrict its use in any way, then I simply would not release the code. That being said, the closest license that I know about is MIT. If anyone is not happy with “I have said it’s free so it is”, then go with MIT. Change it, sell it, forget about it, I really don’t care.
In this case specifically I have asked for help and actually want people to change any use my code, I don’t think I can be clearer.

I have mad a reasonable start at a tracker based music player, specifically for one game, but I’m going to need a little help refining what I have to be more reusable.

https://os.mbed.com/users/spinal/code/music_player/
the tune in the example is based on Mod_Sensitive.xm (37.5 KB)
file, if anyone wants to hear what it should sound like :-P.

It works about as well as I expected, but isn’t much use to other people in its current state.

I assume there are far more elegant ways to do things like the following for example…

            switch(instrument){
                case 1:
                    playSound(t, s_01, sizeof(s_01), volume, speed, sampleRepeat[0]);
                break;
                case 2:
                    playSound(t, s_02, sizeof(s_02), volume, speed, sampleRepeat[1]);
                break;
                case 3:
                    playSound(t, s_03, sizeof(s_03), volume, speed, sampleRepeat[2]);
                break;
                case 4:
                    playSound(t, s_04, sizeof(s_04), volume, speed, sampleRepeat[3]);
                break;
            }

The music format (if you can call it that yet) is as follows -

const char tune[22][64][4][4]={ { {{0 ,0 ,128,0 },{0 ,0 ,128,0 },{0 ,0 ,128,0 },{1 ,5 ,160,3 }},

4 columns with 64 rows, this example has 22 patterns.
1st number is the note to be played, starting at 1 with C, 0 being no note
2nd is the octave, rage from 0 to 8
3rd is volume, currently 0 doesn’t change volume, 128 or above changes the volume to number-128, with a recommended max of 63 to keep the mixing OK.
4th is the instrument number.

each instrument can have it’s own sample rate and can be loopable. I intended to at a start and end loop point, I haven’t done that yet.

one timer is used to fill the audio buffer and a second used to time the music playback.

Also needed would be some soft of tracker software, probably cross-platform I guess, I haven’t even thought much about that part.


#2

Hi,

I have also recently been struggling with the same issues as you, but on smaller scale (in PGP). I use a very simple “tracker” format where there is only a note and an instrument, i.e. {50, 1}.

I do not have own player but I am using PokittoLib API in Synth_songfuncs.cpp. It is quite versatile. It supports at least:

  • 3 channels
  • Array of blocks. Each block can have 64 notes and instrument indices. Each block can be in any channel.
  • The song can be looped to a certain block or ended after a certain block.
  • Instrument array. There are also default instruments to test with.
  • global tempo to set

Now, that I got the PGP release out, i will make a simple example of using that.


#3

There’s a lot of suggestions I’d like to make, but first I need to know are you able to use C++11 (i.e. with PlatformIO+VSCode, PlatformIO+Atom or EmBitz), or are you still stuck using the online IDE?


Edit:
I’m confused about how updateAudioBuffer is supposed to work.
t runs from 0 to 512 inclusive (I think that <= is supposed to be a <),
and on each iteration of the loop, you pass t to mixSound,
which doesn’t actually use t, it just mixes the data in your 4 sample arrays.

Also, why are the sound offsets in the order { (512 * 2), (512 * 3), (512 * 0), (512 * 1) }?
Wouldn’t it make more sense to just do arrange them in ascending order and then do index + (quart * 512)?


#4

Lots of very good questions friend, some of which may not have logical Answers. The offsets for the buffer were my brains way of making sure I didn’t overlap the playing position when filling in my previous attempt, it wasn’t using a timer previously so accuracy wasn’t great.


#5

Your quart guard should already prevent that.
soundbufindex / 512 will reduce to a value between 0 and 4 (inclusive),
so (512 * quart) would evaluate to 0, 512, 1024 or 1536, and there should be no overlap.
Effectively sndOffset is already doing that, but rotated.
i.e. { 1024, 1536, 0, 512 } is { 0, 512, 1024, 1536 } rotated left twice.

What about the other questions?
Most importantly, would C++11 code be acceptable?
If it is, there are some really useful changes that can be made.


#6

I’m also confused about how the adjusted sound size is supposed to work.

float rate1 = 44100 / (float)sampleRates[channel];
speed = speed / rate1;
snd[channel].currentSoundSize = (soundSize<<8)/speed;

It seems to overrun the container for certain values. E.g.

rate1 = 44100.0f / 22417.0f = 1.9672569924610786456706963465227f
speed = 256.0f / 1.9672...f = 130.13043083900226757369614512472f
speed = static_cast<int>(130.1304...f) = 130
intermediateResult = 718 << 8 = 183808
currentSoundSize = 183808 / 130 = 1413

1413 is larger than 718, so soundPoint will eventually overspill the sound data.

(Apart from that I’m pretty close to having a much more organised rewrite.)


#7

I know almost nothing about c standards, if you think c++11 is a good thing to accommodate then go for it. I have been using the online compiler mostly for how easy it is to update pokittolib.
As for variable sizes, I didn’t think them through fully, so yeah, some of them might be wrong .


#8

C++ standards.
The C standards committee and the C++ standards committee are two different entities.

It’s the best thing to happen to the language since it was first standardised.
(C++20 will be the second best.)

If you’re using the online compiler then you won’t be able to use C++11 features.


I’ve done a big rewrite. It’s not fully working yet and I have no idea if I’ve broken anything or not, but it’s making noises and it looks pretty, so it’s a start at least:

(By the way, you haven’t licensed your code, so I decided to just stick the MIT licence on for now.)


#9

@spinal can you please drop the open-source tag until you add a license, or add a license now? It’s confusing, I’ve been looking for the license but it’s absent.


#10

I have added to the PokittoLib a simple example how to use the Synth_songfuncs.cpp API for playing music. The PR is here: https://github.com/pokitto/PokittoLib/pull/64.

The code is as follows:

#include "Pokitto.h"
#include "Synth.h"

Pokitto::Core game;
Pokitto::Display disp;
Pokitto::Sound snd;
Pokitto::Buttons btn;

// Fanfare 1
// The array item consists of the note number and the instrument number, e.g. {29,1}. The instrument number 0
// means no instrument, so the previous note is still playing.
const uint8_t Fanfare[][2] =
{
    {39, 1},{255, 0},  // A short note
    {41, 1},{255, 0},
    {42, 1},{255, 0},
    {43, 1},{255, 0},{255, 0},{255, 0}, // a long note
    {45, 1},{255, 0},
    {43, 1},{255, 0},{255, 0},{255, 0},
    {255, 2}  // Pause until the next note.
};

int main()
{
    game.begin();

    // *** Initialize the music player

    // Reset the song data. This contains most of the important song data like
    // - the block sequence,
    // - the current note and instrument streams (arrays)
    // - the current position in the block sequence, and
    // - the song end and loop block indices.
    emptySong();

    // Initialize all the blocks. Each block can contain 64 notes and instrument indices.
    emptyBlocks();

    // Only one simultaneous sound
    track1on = true; track2on = false; track3on = false;

    // Make some instruments to test with.
    makeSampleInstruments();

    // Enable the internal audio amplifier.
    snd.ampEnable(1);

    // Change the tempo.
    uint32_t tempo = 45;
    samplespertick = (float)((60.0f/(float)tempo)*POK_AUD_FREQ)/16;

    // SEt the instrument 2 to be silent. That can be used as pause.
    patch[2].wave = WOFF;

    // Start playing!
    playing = true;

    // Store the fanfare to the block 0.
    int32_t blockNum = 0;
    int32_t songLen = sizeof(Fanfare) / sizeof(Fanfare[0]);
    for(int32_t i=0; i<songLen && i<PATTERNLENGTH;i++)
    {
        block[blockNum].notenumber[i] = Fanfare[i][0];
        block[blockNum].instrument[i] = Fanfare[i][1];
    }

    while (game.isRunning())
    {
        if (game.update())
            disp.print("Playing music...");

    }
    return 0;
}

#11

Instead of explaining in a comment that the first value is the note and the second value is the instrument,
why not just bake that meaning into the code?

struct Sound
{
	uint8_t noteNumber;
	uint8_t instrumentNumber;
};

// ...

size_t blockIndex = 0;
size_t songLen = (sizeof(Fanfare) / sizeof(Fanfare[0]));
for(size_t index = 0; ((index < songLen) && (index < PATTERNLENGTH)); ++index)
{
	block[blockIndex].noteNumber[index] = Fanfare[index].noteNumber;
	block[blockIndex].instrumentNumber[index] = Fanfare[index].instrumentNumber;
}

#12

Yes, that could be improved. Just go ahead if you like.


#13

I’ve popped a comment in the first post about license and that’s the best anyone will get on the situation. Now let’s forget the politics and get back to creating a useful music example.


#14

the sound speed is later >>8, I can’t quite remember my thinking here, but it made sense at the time.

ss[s] = (snd[s].currentSound[(snd[s].soundPoint*snd[s].speed)>>8]*snd[s].volume>>8) * snd[s].playSample;

[edit]
I just tried listening to my example with headphones, it’s awful :stuck_out_tongue:


#15

Got the following when building with embitz…

..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\PlayableSound.h|32|warning: returning reference to temporary [-Wreturn-local-addr]|
..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\SoundPlayer.h|58|error: enclosing class of constexpr non-static member function 'std::size_t SoundPlayer::getChannelCount() const' is not a literal type|
..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\SoundPlayer.h|12|note: 'SoundPlayer' is not literal because:|
..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\SoundPlayer.h|12|note:   'SoundPlayer' has a non-trivial destructor|
..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\SoundPlayer.h|50|error: 'callback' was not declared in this scope|
..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\SoundPlayer.h|40|  required from here|
..\Mine\SpinalMusicPlayerRedux-master\MusicPlayer\PlayableSound.h|77|warning: unused variable 'repeatInfo' [-Wunused-variable]|

I know my pokittolib is a little outdated, could it be something to do with that?


#16

I can’t until my open PR is merged to the actual library.
I’ll give Jonne a nudge about this later.

It’s not politics, it’s good habit and/or good ‘manners’ to have a licence on the code.
MIT or CC0 are usually your best options for “I don’t care”.
(I lean towards MIT more because it specifically mentions ‘software’ and CC0 doesn’t.)

The first error is definitely my fault, it’s supposed to be:

constexpr const SoundRepeatInfo & getRepeatInfo() const
{
	return this->repeatInfo;
}

I fixed that on my repo.

I think part of the reason it was working on PlatformIO is because the variable isn’t even used.
It’s something I forgot to remove when I was splitting the functions into ‘update’ and ‘mix’.
I’ve removed it now.

I sometimes forget to read the warning messages because a handful eminate from the Pokitto library,
so I get used to there always being some warnings when building.

callback is supposed to come from mbed and Pokitto.h should be including that ‘somewhere’.

You could try adding #include <mbed.h> to that file.
If you can’t get it working I’ll look tomorrow to see if I can get it building on EmBitz.


#17

I’ll merge the PR when I wske up, didn’t notice


#18

6am? You’re up early.
(Says the person clearly awake at 4am.)


#19

No my kid woke me up. I went back to sleep :wink: