First post, by superfury
I'm currently testing my MPU emulation (which is the Dosbox mpu.cpp as the 'front-end' and my own MIDI emulation (everything producing sound and processing the MIDI commands send to the output) as the 'backend'. I'm currently trying to get my .mid file playback correctly. I do get sound, but it seems to go very fast and crashes in the end:
#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 0x6b72544dword 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 = 0x500000; //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(¤ttrack, 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?{
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,"MIDI_DATA"); //Allocate data!if (!data) //Ran out of memory?{fclose(f);return 0; //Ran out of memory!}if (fread(data, 1, tracklength, f) != tracklength) //Error reading data?{fclose(f);freez((void **)&data, tracklength, "MIDI_DATA");return 0; //Error reading data!}++currenttrackn; //Increase the number of tracks loaded!if (currenttrackn > maxchannels) //Limit broken?{freez((void **)&data, tracklength, "MIDI_DATA");return 0; //Limit broken: we can't store the file!}channels[currenttrackn - 1] = data; //Safe the pointer to the data!memcpy(tracks, ¤ttrack, 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), "MIDI_DATA"); //Try to free!}}byte consumeStream(byte **stream, byte *result){if (!memprotect(*stream, 1, "MIDI_DATA")) return 0; //Error: EOS!*result = **stream; //Read!++(*stream); //Increase pointer in the stream!return 1; //Consumed!}byte peekStream(byte **stream, byte *result){if (!memprotect(*stream, 1, "MIDI_DATA")) return 0; //Error: EOS!*result = *(*stream); //Consume a byte!return 1; //Consumed!}byte read_VLV(byte **midi_stream, uint_32 *result){uint_32 temp = 0;byte curdata;if (!consumeStream(midi_stream, &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, &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){byte *curstream = midi_stream; //Copy the stream!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(&curstream, &delta_time)) return; //Read VLV time index!play_pos += delta_time; //Add the delta time to the playing position!for (;;){//LockSDL_SemWait(MID_timing_pos_Lock);if (timing_pos >= play_pos){//UnlockSDL_SemPost(MID_timing_pos_Lock);break; //Arrived? Play!}//UnlockSDL_SemPost(MID_timing_pos_Lock);delay(0); //Wait for our tick!}if (!peekStream(&curstream, &curdata)){return; //Failed to peek!}if (curdata == 0xFF) //System?{if (!consumeStream(&curstream, &curdata)) return; //EOS!if (!consumeStream(&curstream, &meta_type)) return; //Meta type failed? Give error!if (!read_VLV(&curstream, &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?//LockSDL_SemWait(MID_BPM_Lock);removetimer("MID_tempotimer"); //Remove old timer!if (!consumeStream(&curstream, &curdata)) return; //Tempo 1/3 failed?activetempo = curdata; //Final byte!activetempo <<= 8;if (!consumeStream(&curstream, &curdata)) return; //Tempo 2/3 failed?activetempo |= curdata; //Final byte!activetempo <<= 8;if (!consumeStream(&curstream, &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);//UnlockSDL_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(&curstream, &curdata)) return; //Skip failed?}break;}}else //Hardware?{//LockSDL_SemWait(MIDLock);if (curdata & 0x80) //Starting a new command?{dolog("MID", "Status@Channel %i=%02X", channel, curdata);if (!consumeStream(&curstream, &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(&curstream, &length)) MIDI_ERROR(2) //Error: unexpected EOS!for (; length--;) //Transmit the packet!{if (!consumeStream(&curstream, &curdata)) MIDI_ERROR(3+length) //EOS!PORT_OUT_B(0x330, curdata); //Send the byte!}break;case 0x1:case 0x3://1 byte follows!if (!consumeStream(&curstream, &curdata)) MIDI_ERROR(2) //EOS!PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!break;case 0x2://2 bytes follow!if (!consumeStream(&curstream, &curdata)) MIDI_ERROR(2) //EOS!PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!if (!consumeStream(&curstream, &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(&curstream, &curdata)) MIDI_ERROR(2) //EOS!PORT_OUT_B(0x330, curdata); //Passthrough to MIDI!if (!consumeStream(&curstream, &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 followsif (!consumeStream(&curstream, &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(&curstream, &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://Unlockif (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!}}}
The loader:
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); //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;
...
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!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 semaphoreSDL_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();}
What happens:
- The loader does the normal 'CPU' work.
- The midi channel processes a channel and send it to the MPU-401.
- The Dosbox mpu.cpp sends command and data bytes it to my emulated MIDI processing layer (Dosbox itself sends it to Windows/Unix etc.), since it's in UART mode.
- My MIDI processing layer processes command and sets them a command at a time to my playback engine.
- My MIDI playback engine handles everything to generate the sound:
- 1. Process the command and execute it.
- 2. Voice sound thread: Make the correct sound using the SoundFont backend and envelopes.
Anyone knows if the above parser is correct?
BTW, the delay(us) function delays the current thread. PORT_OUT_B(port,data) sends data to the MPU-401.
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io