Programming a native Sega Genesis tracker.

For anything related to sound (YM2612, PSG, Z80, PCM...)

Moderator: BigEvilCorporation

MintyTheCat
Very interested
Posts: 484
Joined: Sat Mar 05, 2011 11:11 pm
Location: Berlin, Germany

Post by MintyTheCat » Sun Aug 24, 2014 9:49 am

Ok:

http://www.nanoloop.com/midi/

I do not see any src though - any ideas?

We just need something to parse and then map the params.
UMDK Fanboy

Qjimbo
Newbie
Posts: 4
Joined: Wed Jan 17, 2007 11:36 pm

Post by Qjimbo » Sat Aug 30, 2014 6:55 pm

MintyTheCat wrote:Ok:

http://www.nanoloop.com/midi/

I do not see any src though - any ideas?

We just need something to parse and then map the params.
In that case you'd probably want the source of mGB - the program that runs on the gameboy that parses the MIDI data:

https://code.google.com/p/arduinoboy/

This page states "Current Source code available upon request. (Its a bit of a mess without comments. ;)" so you should be able to get hold of that by asking.

MintyTheCat
Very interested
Posts: 484
Joined: Sat Mar 05, 2011 11:11 pm
Location: Berlin, Germany

Post by MintyTheCat » Mon Sep 01, 2014 7:10 am

Qjimbo wrote:
MintyTheCat wrote:Ok:

http://www.nanoloop.com/midi/

I do not see any src though - any ideas?

We just need something to parse and then map the params.
In that case you'd probably want the source of mGB - the program that runs on the gameboy that parses the MIDI data:

https://code.google.com/p/arduinoboy/

This page states "Current Source code available upon request. (Its a bit of a mess without comments. ;)" so you should be able to get hold of that by asking.
Hi,

I am trying to find out how to contact the Author but I am not able to find his E-Mail Address or some form of contact button.

Can anyone else find it?
UMDK Fanboy

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Mon Sep 01, 2014 7:21 pm

The source is included in the Arduinoboy 1_2_3 zip file under downloads. I think you'll be disappointed at the code. It's not really meant for a player of MIDI files - it's a handler of a MIDI stream from/to a MIDI instrument.

Here's some MIDI handling code I made for my upcoming Doom port. It's probably more useful for a MIDI player...

Code: Select all

void Mid_Unregister(int handle)
{
    int i;

    Mus_Stop();
    // music won't start playing until mus_playing set at this point

    num_tracks = 0;
}

int Mid_Register(void *mididata)
{
    uint8_t *mptr = (uint8_t *)mididata;
    int i, j;

    Mid_Unregister(1);
    // music won't start playing until mus_playing set at this point

    if (memcmp(&mptr[0], "MThd", 4))
        return 0;                           // doesn't start with MIDI header

    if (mptr[8] || (mptr[9] > 1))
        return 0;                           // not type 0 or type 1 MIDI file

    num_tracks = (mptr[10] << 8) | mptr[11];
    if (!num_tracks || (num_tracks > MAX_NUM_TRACKS))
        return 0;                           // too many tracks

    ticks_qn = (mptr[12] << 8) | mptr[13];

    for (i=0, j=0; i<num_tracks; )
    {
        j += (mptr[4] << 24) | (mptr[5] << 16) | (mptr[6] << 8) | mptr[7];
        j += 8;
        if (!memcmp(&mptr[j], "MTrk", 4))
        {
            tracks[i] = &mptr[j+8];
            i++;
        }
    }

    return 1;
}

static void Handle_MID(void)
{
    CacheClearLine(&mus_playing);

    // process music if playing
    if (mus_playing)
    {
        if (mus_playing < 0)
        {
            mus_playing = 0;                  // music now off
        }
        else
        {
            if (mus_playing > 1)
            {
                int i;

                mus_playing = 1;

                // purge the cache
                CacheControl(0); // cache disabled
                CacheControl(SH2_CCTL_CP | SH2_CCTL_CE); // cache purge and enable

                // start music from beginning
                for (i=0; i<num_tracks; i++)
                {
                    int d;
                    uint8_t t;

                    tptrs[i] = tracks[i]; // reset track pointers to start of tracks
                    // get initial delay
                    t = *tptrs[i]++;
                    d = t & 0x7F;
                    while (t & 0x80)
                    {
                        t = *tptrs[i]++;
                        d = (d << 7) | (t & 0x7F);
                    }
                    track_delays[i] = d;
                    tracks_done = 0;
                }
                us_qn = 500000;
                set_mus_samples(us_qn / ticks_qn / 45); // 45 = 1000000 / 22050
            }

            for (i=0; i<num_tracks; i++)
            {
                track_delays[i]--;
                if (track_delays[i] > 0)
                {
                    continue; // next track
                }

                do
                {
                    int d;
                    uint8_t d1;
                    uint8_t d2;
                    uint8_t t;
                    uint8_t channel;
                    uint8_t voice;
                    uint8_t note;
                    uint8_t volume;
                    uint8_t inst;

                    // get event or first byte of data
                    t = *tptrs[i]++;
                    if (t & 0x80)
                    {
                        // MIDI event
                        // get first byte of data
                        d1 = *tptrs[i]++;
                    }
                    else
                    {
                        // first data byte of running status
                        d1 = t;
                        t = running_status[i];
                    }
                    if (t < 0xF0)
                    {
                        running_status[i] = t; // save running status
                    }

                    channel = t & 15;
                    // handle event
                    switch ((t >> 4) & 7)
                    {
                    case 0:
                        // note off
                        //d1 = note
                        d2 = *tptrs[i]++; // velocity
                        note = d1 & 0x7F;
                        voice = mus_channel[channel].map[note] - 1;
                        if (voice < NUM_MUS_MIXERS)
                        {
                            mus_channel[channel].map[note] = 0;   // clear mapping
                            voice_avail |= 1 << voice;            // voice available
                            audVoice[voice].flags |= VCF_NOTEOFF; // voice releasing
                            audVoice[voice].fade = mus_channel[channel].vol;
                        }
                        break;
                    case 1:
                        // note on
                        //d1 = note
                        d2 = *tptrs[i]++; // velocity
                        if (d2)
                        {
                            note = d1 & 0x7F;
                            for (voice=0; voice<NUM_MUS_MIXERS; voice++)
                            {
                                if (voice_avail & (1 << voice))
                                    break; // found free voice
                            }
                            if (voice < NUM_MUS_MIXERS)
                            {
                                voice_avail &= ~(1 << voice); // in use
                                mus_channel[channel].map[note] = voice + 1;
                                if (channel != 10)
                                {
                                    // melodic channel
                                    audVoice[voice].chan = channel; // back link for pitch wheel
                                    audVoice[voice].note = note;
                                    audVoice[voice].flags = VCF_ACTIVE|VCF_NOTEON; // enable voice
                                }
                                else
                                {
                                    // percussion channel
                                    audVoice[voice].chan = channel; // back link for pitch wheel
                                    audVoice[voice].note = note + 100; // instrument (should be + 128 but for inst file)
                                    audVoice[voice].flags = VCF_ACTIVE|VCF_NOTEON; // enable voice
                                }
                            }
                        }
                        else
                        {
                            // note on with velocity 0 is the same as note off!
                            note = d1 & 0x7F;
                            voice = mus_channel[channel].map[note] - 1;
                            if (voice < NUM_MUS_MIXERS)
                            {
                                mus_channel[channel].map[note] = 0;   // clear mapping
                                voice_avail |= 1 << voice;            // voice available
                                audVoice[voice].flags |= VCF_NOTEOFF; // voice releasing
                                audVoice[voice].fade = mus_channel[channel].vol;
                            }
                        }
                        break;
                    case 2:
                        // polyphonic key pressure
                        //d1 = pressure
                        d2 = *tptrs[i]++; // note

                        break;
                    case 3:
                        // controller
                        //d1 = index
                        d2 = *tptrs[i]++; // value
                        switch(d1 & 0x7F)
                        {
                        case 7:
                            // set channel volume
                            mus_channel[channel].vol = d2;
                            break;
                        case 10:
                            // set channel pan
                            mus_channel[channel].pan = d2;
                            break;
                        }
                        break;
                    case 4:
                        // instrument change
                        //d1 = instrument
                        mus_channel[channel].instrument = d1 & 0x7F;
                        break;
                    case 5:
                        // channel pressure
                        //d1 = pressure

                        break;
                    case 6:
                        // pitch bend
                        //d1 = LSB
                        d2 = *tptrs[i]++; // MSB
                        //d = (((d2 & 0x7F) << 7) | (d1 & 0x7F)) - 8192; // pitch bend: -8192 to 8191
                        mus_channel[channel].pitch = pitch_table[d2]; // fix me!
                        break;
                    case 7:
                        // system exclusive
                        if (((t & 15) == 0) || ((t & 15) == 7))
                        {
                            //d1 = length
                            while (d1--)
                            {
                                d2 = *tptrs[i]++; // data[]
                            }
                        }
                        else if ((t & 15) == 1)
                        {
                            //d1 = LSB
                            d2 = *tptrs[i]++; // MSB

                            //printf("MIDI Time Code\n");
                        }
                        else if ((t & 15) == 15)
                        {
                            //printf("Meta-event\n");
                            if (d1 == 0x2F)
                            {
                                //printf("Track end\n");
                            }
                            else if (d1 == 0x51)
                            {
                                d2 = *tptrs[i]++; // 3
                                d = *tptrs[i]++; // tthi
                                d = (d << 8) | *tptrs[i]++; // ttmd
                                d = (d << 8) | *tptrs[i]++; // ttlo
                                us_qn = d;
                                set_mus_samples(us_qn / ticks_qn / 45);
                                //printf("Tempo meta-event\n");
                            }
                            else
                            {
                                // len data[]
                                d1 = *tptrs[i]++; // length
                                while (d1--)
                                {
                                    d2 = *tptrs[i]++; // data[]
                                }
                                //printf("Meta-event\n");
                            }
                        }
                        break;
                    }
                    if ((t == 0xFF) && (d1 == 0x2F))
                    {
                        // end of track
                        track_delays[i] = 0x7FFFFFFF;
                        tracks_done++;
                        if (tracks_done == num_tracks)
                        {
                            // song done
                            memset(mus_channel, 0, sizeof(mus_channel));
                            for (voice=0; voice<NUM_MUS_MIXERS; voice++)
                            {
                              audVoice[voice].flags = 0;
                              audVoice[voice].mixer = 0xFF;
                              stop_ins(voice);
                            }
                            voice_avail = 0x00FFFFFF;
                            if (mus_looping)
                            {
                                mus_playing = 2; // start from beginning
                            }
                            else
                            {
                                mus_playing = 0; // stop
                            }
                            return;
                        }
                    }
                    else
                    {
                        // get next delay
                        t = *tptrs[i]++;
                        d = t & 0x7F;
                        while (t & 0x80)
                        {
                            t = *tptrs[i]++;
                            d = (d << 7) | (t & 0x7F);
                        }
                        track_delays[i] = d;
                    }

                } while (track_delays[i] <= 0);
            }
        }
    }
}
Handle_MID() is called once each period, where the period is set by the number of samples per buffer - I'm using the audio DMA as a timer for the score processing. You'd need to change that to using the FM timer instead.

Code: Select all

                us_qn = 500000;
                set_mus_samples(us_qn / ticks_qn / 45); // 45 = 1000000 / 22050
That's the default timing for MIDI - 500,000 us per unit divided by the number of ticks per unit (set in the header as read by Register_MID()). That period is then divided by the us per sample at a playback rate of 22050 to get the number of samples that yields the correct tick rate.

The us per unit can be changed by a system tempo message:

Code: Select all

                            else if (d1 == 0x51)
                            {
                                d2 = *tptrs[i]++; // 3
                                d = *tptrs[i]++; // tthi
                                d = (d << 8) | *tptrs[i]++; // ttmd
                                d = (d << 8) | *tptrs[i]++; // ttlo
                                us_qn = d;
                                set_mus_samples(us_qn / ticks_qn / 45);
                                //printf("Tempo meta-event\n");
                            }
To alter these for the FM timer, you need to divide by however many us there are in a timer tick instead of 45.

You'll also note that the code only uses the MSB of the pitch bending at the moment:

Code: Select all

                    case 6:
                        // pitch bend
                        //d1 = LSB
                        d2 = *tptrs[i]++; // MSB
                        //d = (((d2 & 0x7F) << 7) | (d1 & 0x7F)) - 8192; // pitch bend: -8192 to 8191
                        mus_channel[channel].pitch = pitch_table[d2]; // fix me!
                        break;
That's the "full" pitch bend selector in the comment.

Changes in pressure aren't handled yet:

Code: Select all

                    case 5:
                        // channel pressure
                        //d1 = pressure

                        break;
Anywho, you get the idea... everything useful for most type 0 and 1 MIDI files are parsed even if not used. If you have any other questions, just ask.

Note - the vast majority of MIDI files are type 1, with the majority of the remaining files being type 0. I've never seen a type 2 MIDI file in the wild, and looking at type 2 files, I don't care to support them. So don't bug me about adding support for type 2 files.

Post Reply