VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've build a MIDI (.MID files) player to test my MPU-401 softsynth, but it seems to process the .MID file wrong. I'm getting strange results. Anyone knows what's going wrong?

Main thread routine running the MID file ():

extern uint_64 timing_pos; //Current timing position for MIDI speed playback!

word MID_RUNNING = 0;

HEADER_CHNK header;

SDL_sem *MID_channel_Lock = NULL; //Our channel lock for counting running MIDI!

byte *MID_data[100]; //Tempo and music track!
TRACK_CHNK MID_tracks[100];

void handleMIDIChannel()
{
word channel;
channel = (word)getthreadparams(); //Gotten a channel?
playMIDIStream(channel,MID_data[channel], &header, &MID_tracks[channel]); //Play the MIDI stream!
SDL_SemWait(MID_channel_Lock);
dolog("MID", "Channel %i finished", channel); //We're finished!
--MID_RUNNING; //Done!
SDL_SemPost(MID_channel_Lock);
}

//All used locks!
extern SDL_sem *MIDLock;
extern SDL_sem *MID_timing_pos_Lock;
extern SDL_sem *MID_BPM_Lock;

void dosoundtest()
{
memset(&MID_data, 0, sizeof(MID_data)); //Init data!
memset(&MID_tracks, 0, sizeof(MID_tracks)); //Init tracks!

word numchannels = 0;
if ((numchannels = readMID("MPU.mid", &header, &MID_tracks[0], &MID_data[0], 100)))
{
//Initialise our device!
PORT_OUT_B(0x331, 0xFF); //Reset!
PORT_OUT_B(0x331, 0x3F); //Kick to UART mode!
updateMIDTimer(&header); //Update the timer!

//Create the semaphore for the threads!
MIDLock = SDL_CreateSemaphore(1);
MID_timing_pos_Lock = SDL_CreateSemaphore(1);
MID_BPM_Lock = SDL_CreateSemaphore(1);
MID_channel_Lock = SDL_CreateSemaphore(1);

//Now, start up all timers!

word i;
MID_RUNNING = numchannels; //Init to all running!
for (i = 0; i < numchannels; i++)
{
startThread(&handleMIDIChannel, "MIDI_STREAM", (void *)i, DEFAULT_PRIORITY); //Start a thread handling the output of the channel!
}

delay(10000); //Wait a bit to allow for initialisation (position 0) to run!

startTimers(1);
startTimers(0); //Start our timers!

Show last 27 lines
		byte running = 1; //Are we running?

for (;;) //Wait to end!
{
delay(1000000); //Wait 1sec intervals!
SDL_SemWait(MID_channel_Lock);
if (!MID_RUNNING)
{
running = 0; //Not running anymore!
}
SDL_SemPost(MID_channel_Lock);
if (!running) break; //Not running anymore? Start quitting!
}

//Destroy semaphore
SDL_DestroySemaphore(MIDLock);
SDL_DestroySemaphore(MID_timing_pos_Lock);
SDL_DestroySemaphore(MID_BPM_Lock);
SDL_DestroySemaphore(MID_channel_Lock);

removetimer("MID_tempotimer"); //Clean up!
freeMID(&MID_tracks[0], &MID_data[0], numchannels); //Free all channels!
halt();
exit(0);
sleep();
}
}

MID.c: MIDI file read support and player of a channel:

#include "headers/types.h"
#include "headers/support/zalloc.h" //Zalloc support!
#include "headers/hardware/ports.h" //MPU support!
#include "headers/emu/timers.h" //Timer support!
#include "headers/support/mid.h" //Our own typedefs!

#define MIDIHEADER_ID 0x6468544d
#define MIDIHEADER_TRACK_ID 0x6b72544d

word byteswap16(word value)
{
return ((value & 0xFF) << 8) | ((value & 0xFF00) >> 8); //Byteswap!
}

uint_32 byteswap32(uint_32 value)
{
return (byteswap16(value & 0xFFFF) << 8) | byteswap16((value & 0xFFFF0000) >> 16); //
}

uint_32 activetempo = 500000; //Current tempo!

word readMID(char *filename, HEADER_CHNK *header, TRACK_CHNK *tracks, byte **channels, word maxchannels)
{
FILE *f;
TRACK_CHNK currenttrack;
TRACK_CHNK *curtracks = tracks;
word currenttrackn = 0; //Ammount of tracks loaded!
uint_32 tracklength;

byte *data;
f = fopen(filename, "rb"); //Try to open!
if (!f) return 0; //Error: file not found!
if (fread(header, 1, sizeof(*header), f) != sizeof(*header))
{
fclose(f);
return 0; //Error reading header!
}
if (header->Header != MIDIHEADER_ID)
{
fclose(f);
return 0; //Nothing!
}
if (byteswap32(header->header_length) != 6)
{
fclose(f);
return 0; //Nothing!
}
if (byteswap16(header->format)>1) //Not single/multiple tracks played simultaneously?
{
fclose(f);
return 0; //Not single track!
}
nexttrack: //Read the next track!
if (fread(&currenttrack, 1, sizeof(currenttrack), f) != sizeof(currenttrack)) //Error in track?
{
fclose(f);
return 0; //Invalid track!
}
if (currenttrack.Header != MIDIHEADER_TRACK_ID) //Not a track ID?
{
Show last 319 lines
		fclose(f);
return 0; //Invalid track header!
}
if (!currenttrack.length) //No length?
{
fclose(f);
return 0; //Invalid track length!
}
tracklength = byteswap32(currenttrack.length); //Calculate the length of the track!
data = zalloc(tracklength+sizeof(uint_32),"MIDI_DATA"); //Allocate data and cursor!
if (!data) //Ran out of memory?
{
fclose(f);
return 0; //Ran out of memory!
}
if (fread(data+sizeof(uint_32), 1, tracklength, f) != tracklength) //Error reading data?
{
fclose(f);
freez((void **)&data, tracklength+sizeof(uint_32), "MIDI_DATA");
return 0; //Error reading data!
}

++currenttrackn; //Increase the number of tracks loaded!
if (currenttrackn > maxchannels) //Limit broken?
{
freez((void **)&data, tracklength+sizeof(uint_32), "MIDI_DATA");
return 0; //Limit broken: we can't store the file!
}

channels[currenttrackn - 1] = data; //Safe the pointer to the data!
memcpy(tracks, &currenttrack, sizeof(currenttrack)); //Copy track information!
++tracks; //Next track!
if (/*(byteswap16(header->format) > 0) &&*/ (currenttrackn<byteswap16(header->n))) //Format 1? Take all tracks!
{
goto nexttrack; //Next track to check!
}

/*if (!feof(f)) //Not @EOF when required?
{
fclose(f);
freez((void **)data, byteswap32(currenttrack.length), "MIDI_DATA"); //Release current if there!
return 0; //Incomplete file!
}*/
fclose(f);
activetempo = 500000; //Default = 120BPM = 500000 microseconds/quarter note!
return currenttrackn; //Give the result: the ammount of tracks loaded!
}

void freeMID(TRACK_CHNK *tracks, byte **channels, word numchannels)
{
uint_32 channelnr;
for (channelnr = 0; channelnr < numchannels; channelnr++)
{
freez(&channels[channelnr], byteswap32(tracks[channelnr].length)+sizeof(uint_32), "MIDI_DATA"); //Try to free!
}
}

byte consumeStream(byte *stream, TRACK_CHNK *track, byte *result)
{
byte *streamdata = stream + sizeof(uint_32); //Start of the data!
uint_32 *streampos = (uint_32 *)stream; //Position!
if (!memprotect(streampos, 4, "MIDI_DATA")) return 0; //Error: Invalid stream!
if (*streampos >= byteswap32(track->length)) return 0; //End of stream reached!
if (!memprotect(&streamdata[*streampos], 1, "MIDI_DATA")) return 0; //Error: Invalid data!
*result = streamdata[*streampos]; //Read the data!
++(*streampos); //Increase pointer in the stream!
return 1; //Consumed!
}

byte peekStream(byte *stream, TRACK_CHNK *track, byte *result)
{
byte *streamdata = stream + sizeof(uint_32); //Start of the data!
uint_32 *streampos = (uint_32 *)stream; //Position!
if (!memprotect(streampos, 4, "MIDI_DATA")) return 0; //Error: Invalid stream!
if (*streampos >= byteswap32(track->length)) return 0; //End of stream reached!
if (!memprotect(&streamdata[*streampos], 1, "MIDI_DATA")) return 0; //Error: Invalid data!
*result = streamdata[*streampos]; //Read the data!
return 1; //Consumed!
}

byte read_VLV(byte *midi_stream, TRACK_CHNK *track, uint_32 *result)
{
uint_32 temp = 0;
byte curdata;
if (!consumeStream(midi_stream, track, &curdata)) return 0; //Read first VLV failed?
for (;;) //Process/read the VLV!
{
temp |= (curdata & 0x7F); //Add to length!
if (!(curdata & 0x80)) break; //No byte to follow?
temp <<= 7; //Make some room for the next byte!
if (!consumeStream(midi_stream, track, &curdata)) return 0; //Read VLV failed?
}
*result = temp; //Give the result!
return 1; //OK!
}

float calcfreq(uint_32 tempo, HEADER_CHNK *header)
{
float PPQN, speed;
byte frames;
byte subframes; //Pulses per quarter note!
word division;
division = byteswap16(header->division); //Byte swap!

if (division & 0x8000) //SMTPE?
{
frames = (float)((division >> 8)&0x7F); //Frames!
subframes = (float)(division & 0xFF); //Subframes!
speed = frames; //Default: we're the frames!
if (subframes)
{
//We don't use the tempo: our rate is fixed!
frames *= subframes; //The result in subframes/second!
}
speed = frames; //Use (sub)frames!
}
else
{
//PPQN method!
PPQN = (float)division; //Read PPQN!
speed = (float)tempo; //Convert to speed!
speed /= 1000000.0f; //Divide by 1 second!
speed /= PPQN; //Divide to get the ticks per second!
speed = 1.0f / speed; //Ammount per second!
speed *= 0.5f; //Divide by 2 for slower speed!
}

//We're counting in ticks!
return speed; //ticks per second!
}

//The protective semaphore for our flags!
SDL_sem *MID_timing_pos_Lock = NULL; //Timing position lock!
SDL_sem *MID_BPM_Lock = NULL; //BPM/Active tempo lock!

uint_64 timing_pos = 0; //Current timing position!
float BPM = 0.0f; //No BPM by default!

void updateMIDTimer(HEADER_CHNK *header) //Request an update of our timer!
{
addtimer(calcfreq(activetempo, header), (Handler)&timing_pos, "MID_tempotimer", 0, 2, MID_timing_pos_Lock); //Add a counter timer!
}

//The protective semaphore for the hardware!
SDL_sem *MIDLock = NULL;

#define MIDI_ERROR(position) {error = position; goto abortMIDI;}

void playMIDIStream(word channel, byte *midi_stream, HEADER_CHNK *header, TRACK_CHNK *track)
{
byte curdata;

//Metadata event!
byte meta_type;
uint_32 length; //Our metadata variable length!

uint_64 play_pos = 0; //Current play position!

uint_32 delta_time; //Delta time!

byte last_command = 0; //Last executed command!

int_32 error = 0; //Default: no error!
for (;;) //Playing?
{
//First, timing information and timing itself!
if (!read_VLV(midi_stream, track, &delta_time)) return; //Read VLV time index!
play_pos += delta_time; //Add the delta time to the playing position!
for (;;)
{
//Lock
SDL_SemWait(MID_timing_pos_Lock);
if (timing_pos >= play_pos)
{
//Unlock
SDL_SemPost(MID_timing_pos_Lock);
break; //Arrived? Play!
}
//Unlock
SDL_SemPost(MID_timing_pos_Lock);
delay(0); //Wait for our tick!
}

if (!peekStream(midi_stream,track, &curdata))
{
return; //Failed to peek!
}
if (curdata == 0xFF) //System?
{
if (!consumeStream(midi_stream, track, &curdata)) return; //EOS!
if (!consumeStream(midi_stream, track, &meta_type)) return; //Meta type failed? Give error!
if (!read_VLV(midi_stream, track, &length)) return; //Error: unexpected EOS!
switch (meta_type) //What event?
{
case 0x2F: //EOT?
dolog("MID", "channel %i: EOT!", channel); //EOT reached!
return; //End of track reached: done!
case 0x51: //Set tempo?
//Lock
SDL_SemWait(MID_BPM_Lock);
removetimer("MID_tempotimer"); //Remove old timer!

if (!consumeStream(midi_stream, track, &curdata)) return; //Tempo 1/3 failed?
activetempo = curdata; //Final byte!
activetempo <<= 8;
if (!consumeStream(midi_stream, track, &curdata)) return; //Tempo 2/3 failed?
activetempo |= curdata; //Final byte!
activetempo <<= 8;
if (!consumeStream(midi_stream, track, &curdata)) return; //Tempo 3/3 failed?
activetempo |= curdata; //Final byte!
//Tempo = us per quarter note!

updateMIDTimer(header);

dolog("MID", "channel %i: Set Tempo:%06X!", channel, activetempo);

//Unlock
SDL_SemPost(MID_BPM_Lock);
break;
default: //Unrecognised meta event? Skip it!
dolog("MID", "Unrecognised meta type: %02X@Channel %i; Data length: %i", meta_type, channel,length); //Log the unrecognised metadata type!
for (; length--;) //Process length bytes!
{
if (!consumeStream(midi_stream, track, &curdata)) return; //Skip failed?
}
break;
}
}
else //Hardware?
{
//Lock
SDL_SemWait(MIDLock);

if (curdata & 0x80) //Starting a new command?
{
dolog("MID", "Status@Channel %i=%02X", channel, curdata);
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(1) //EOS!
last_command = curdata; //Save the last command!
if (last_command != 0xF7) //Escaped continue isn't sent!
{
PORT_OUT_B(0x330, last_command); //Send the command!
}
}
else
{
dolog("MID", "Continued status@Channel %i: %02X=>%02X",channel, last_command, curdata);
if (last_command != 0xF7) //Escaped continue isn't used last?
{
PORT_OUT_B(0x330, last_command); //Repeat the status bytes: we don't know what the other channels do!
}
}

//Process the data for the command!
switch ((last_command >> 4) & 0xF) //What command to send data for?
{
case 0xF: //Special?
switch (last_command & 0xF) //What subcommand are we sending?
{
case 0x0: //System exclusive?
case 0x7: //Escaped continue?
if (!read_VLV(midi_stream, track, &length)) MIDI_ERROR(2) //Error: unexpected EOS!
for (; length--;) //Transmit the packet!
{
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(3 + length) //EOS!
PORT_OUT_B(0x330, curdata); //Send the byte!
}
break;
case 0x1:
case 0x3:
//1 byte follows!
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(2) //EOS!
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
break;
case 0x2:
//2 bytes follow!
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(2) //EOS!
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(3) //EOS!
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
break;
default: //Unknown special instruction?
break; //Single byte instruction?
}
break;
case 0x8: //Note off?
case 0x9: //Note on?
case 0xA: //Aftertouch?
case 0xB: //Control change?
case 0xE: //Pitch bend?
//2 bytes follow!
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(2) //EOS!
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(3) //EOS!
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
break;
case 0xC: //Program change?
case 0xD: //Channel pressure/aftertouch?
//1 byte follows
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(2) //EOS!
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
break;
default: //Unknown data? We're sending directly to the hardware! We shouldn't be here!
if (!consumeStream(midi_stream, track, &curdata)) MIDI_ERROR(2) //EOS!
dolog("MID", "Warning: Unknown data detected@channel %i: passthrough to MIDI device: %02X!",channel,curdata);
PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!
break;
}
abortMIDI:
//Unlock
if (error)
{
dolog("MID", "channel %i: Error @position %i during MID processing! Unexpected EOS? Last command: %02X, Current data: %02X",channel,error,last_command,curdata);
SDL_SemPost(MIDLock); //Finish up!
return; //Abort on error!
}
SDL_SemPost(MIDLock); //Finish: prepare for next command!
}
}
}

playMIDIstream plays a MIDI stream, readMID reads a .MID file stream into memory for playback. freeMID releases a loaded .MID file stream from memory. PORT_OUT_B(port,data) sends data to the UART (MPU-401).

Anyone knows what's going wrong? Does my player have errors?

Edit: I've changed the container to include size limits instead of only memory protection. This way, data streams that are continuous in memory will be played correctly (instead of the streams starting the next stream track(s) when continuous in memory).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 1 of 5, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

Sure. How would you describe it does not work? Do you think problem is more in the loading the .MID file or playing it?

Can you give a sample .MID file so we have the same data to play? (Link is fine, or the file itself)

It seems there's plenty of other stuff required, so I can't just copy-paste your code and compile it.
For what environment is it for? Linux/Windows? It appears to require libsdl at least.

It would be nice to simulate this in an environment that would just load the file and simulate timer ticks while writing debug prints about events and delta times.

Reply 2 of 5, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm running it within my own x86 emulator (currently emulating an incomplete 80(1)86). I'm including the above code within my emulator, which uses PORT_OUT_B to send it to my emulated MPU-401. The MPU receiving the PORT_OUT_B itself is a copy of the Dosbox MPU-401. This passes the commands to my own MIDI synth. My MIDI synth is a simple synth using SF2 files (SF2 soundfonts only). So in turn, the Dosbox MPU-401 passes the data to my MIDI parser, which parses the MIDI commands into packets containing 1 MIDI command at a time. When a packet is fully filled, it's passed to my MIDI synth, which renders the sound to SDL (just a 44.1KHz audio stream) using the soundfont.

I'm currently trying to play runescape-the terrible tower.mid from http://www.moparscape.org/smf/index.php?topic=19.0 .

This is the log I get:

The attachment MID_20150430_1932.zip is no longer available

The synth gives me junk sounds (although the sound itself seems fine, only messed up notes).

MID.log contains the commands executed by the above MIDI player.
MPU.log contains the commands actually executed, after parsed into executable blocks (complete MIDI commands), by my MIDI synth. Or, at least, it tries to execute them when it can.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 5, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just used a simple passthrough from my MIDI synth to Windows synth instead of it trying to do the synth itself. It seems to give correct output now. So the problem is in my MIDI synth, and not in my player?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 4 of 5, by superfury

User metadata
Rank l33t++
Rank
l33t++

Here's my current MIDI synth. Since the Windows synth plays the song just fine, I think the problem is with my MIDI synth.

The attachment x86EMU_MIDI_20150430_2019.zip is no longer available

- mididevice.c contains the Main rendering of the synth (combines with below support files).
- sf2.c contains the soundfont loader/parser.
- adsr.c contains the ADSR envelope generator.

Can you see what's going wrong (uncomment the DIRECT_MIDI define to enable passthrough to windows itself)?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io