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.
Programming a native Sega Genesis tracker.
Moderator: BigEvilCorporation
-
- Very interested
- Posts: 484
- Joined: Sat Mar 05, 2011 11:11 pm
- Location: Berlin, Germany
In that case you'd probably want the source of mGB - the program that runs on the gameboy that parses the MIDI data: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.
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.
-
- Very interested
- Posts: 484
- Joined: Sat Mar 05, 2011 11:11 pm
- Location: Berlin, Germany
Hi,Qjimbo wrote:In that case you'd probably want the source of mGB - the program that runs on the gameboy that parses the MIDI data: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.
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.
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
-
- Very interested
- Posts: 2984
- Joined: Fri Aug 17, 2007 9:33 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...
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.
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:
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:
That's the "full" pitch bend selector in the comment.
Changes in pressure aren't handled yet:
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.
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);
}
}
}
}
Code: Select all
us_qn = 500000;
set_mus_samples(us_qn / ticks_qn / 45); // 45 = 1000000 / 22050
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");
}
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;
Changes in pressure aren't handled yet:
Code: Select all
case 5:
// channel pressure
//d1 = pressure
break;
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.