native_midi/native_midi_common.c
author Ozkan Sezer <sezeroz@gmail.com>
Sat, 13 Oct 2018 23:02:04 +0300
branchSDL-1.2
changeset 908 6b860486ce24
parent 875 f067e333302e
permissions -rw-r--r--
Mix_InitMP3: unload dll if mpg123_init() fails.
     1 /*
     2   native_midi:  Hardware Midi support for the SDL_mixer library
     3   Copyright (C) 2000,2001  Florian 'Proff' Schulze <florian.proff.schulze@gmx.net>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 
    22 
    23 #include "native_midi_common.h"
    24 
    25 #include "../SDL_mixer.h"
    26 
    27 #include <stdlib.h>
    28 #include <string.h>
    29 #include <limits.h>
    30 
    31 
    32 /* The maximum number of midi tracks that we can handle 
    33 #define MIDI_TRACKS 32 */
    34 
    35 
    36 /* A single midi track as read from the midi file */
    37 typedef struct
    38 {
    39 	Uint8 *data;					/* MIDI message stream */
    40 	int len;						/* length of the track data */
    41 } MIDITrack;
    42 
    43 /* A midi file, stripped down to the absolute minimum - divison & track data */
    44 typedef struct
    45 {
    46 	int division;					/* number of pulses per quarter note (ppqn) */
    47     int nTracks;                    /* number of tracks */
    48 	MIDITrack *track;               /* tracks */
    49 } MIDIFile;
    50 
    51 
    52 /* Some macros that help us stay endianess-independant */
    53 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
    54 #define BE_SHORT(x) (x)
    55 #define BE_LONG(x) (x)
    56 #else
    57 #define BE_SHORT(x)	((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
    58 #define BE_LONG(x)	((((x)&0x0000FF)<<24) | \
    59 			 (((x)&0x00FF00)<<8) | \
    60 			 (((x)&0xFF0000)>>8) | \
    61 			 (((x)>>24)&0xFF))
    62 #endif
    63 
    64 
    65 
    66 /* Get Variable Length Quantity */
    67 static int GetVLQ(MIDITrack *track, int *currentPos)
    68 {
    69 	int l = 0;
    70 	Uint8 c;
    71 	while(1)
    72 	{
    73 		c = track->data[*currentPos];
    74 		(*currentPos)++;
    75 		l += (c & 0x7f);
    76 		if (!(c & 0x80)) 
    77 			return l;
    78 		l <<= 7;
    79 	}
    80 }
    81 
    82 /* Create a single MIDIEvent */
    83 static MIDIEvent *CreateEvent(Uint32 time, Uint8 event, Uint8 a, Uint8 b)
    84 {
    85 	MIDIEvent *newEvent;
    86 	
    87 	newEvent = calloc(1, sizeof(MIDIEvent));
    88 
    89 	if (newEvent)
    90 	{
    91 		newEvent->time = time;
    92 		newEvent->status = event;
    93 		newEvent->data[0] = a;
    94 		newEvent->data[1] = b;
    95 	}
    96 	else
    97 		Mix_SetError("Out of memory");
    98 	
    99 	return newEvent;
   100 }
   101 
   102 /* Convert a single midi track to a list of MIDIEvents */
   103 static MIDIEvent *MIDITracktoStream(MIDITrack *track)
   104 {
   105 	Uint32 atime = 0;
   106 	Uint32 len = 0;
   107 	Uint8 event,type,a,b;
   108 	Uint8 laststatus = 0;
   109 	Uint8 lastchan = 0;
   110 	int currentPos = 0;
   111 	int end = 0;
   112 	MIDIEvent *head = CreateEvent(0,0,0,0);	/* dummy event to make handling the list easier */
   113 	MIDIEvent *currentEvent = head;
   114 
   115 	while (!end)
   116 	{
   117 		if (currentPos >= track->len)
   118 			break; /* End of data stream reached */
   119 
   120 		atime += GetVLQ(track, &currentPos);
   121 		event = track->data[currentPos++];
   122 		
   123 		/* Handle SysEx seperatly */
   124 		if (((event>>4) & 0x0F) == MIDI_STATUS_SYSEX)
   125 		{
   126 			if (event == 0xFF)
   127 			{
   128 				type = track->data[currentPos];
   129 				currentPos++;
   130 				switch(type)
   131 				{
   132 					case 0x2f: /* End of data marker */
   133 						end = 1;
   134 					case 0x51: /* Tempo change */
   135 						/*
   136 						a=track->data[currentPos];
   137 						b=track->data[currentPos+1];
   138 						c=track->data[currentPos+2];
   139 						AddEvent(song, atime, MEVT_TEMPO, c, b, a);
   140 						*/
   141 						break;
   142 				}
   143 			}
   144 			else
   145 				type = 0;
   146 
   147 			len = GetVLQ(track, &currentPos);
   148 			
   149 			/* Create an event and attach the extra data, if any */
   150 			currentEvent->next = CreateEvent(atime, event, type, 0);
   151 			currentEvent = currentEvent->next;
   152 			if (NULL == currentEvent)
   153 			{
   154 				FreeMIDIEventList(head);
   155 				return NULL;
   156 			}
   157 			if (len)
   158 			{
   159 				currentEvent->extraLen = len;
   160 				currentEvent->extraData = malloc(len);
   161 				memcpy(currentEvent->extraData, &(track->data[currentPos]), len);
   162 				currentPos += len;
   163 			}
   164 		}
   165 		else
   166 		{
   167 			a = event;
   168 			if (a & 0x80) /* It's a status byte */
   169 			{
   170 				/* Extract channel and status information */
   171 				lastchan = a & 0x0F;
   172 				laststatus = (a>>4) & 0x0F;
   173 				
   174 				/* Read the next byte which should always be a data byte */
   175 				a = track->data[currentPos++] & 0x7F;
   176 			}
   177 			switch(laststatus)
   178 			{
   179 				case MIDI_STATUS_NOTE_OFF:
   180 				case MIDI_STATUS_NOTE_ON: /* Note on */
   181 				case MIDI_STATUS_AFTERTOUCH: /* Key Pressure */
   182 				case MIDI_STATUS_CONTROLLER: /* Control change */
   183 				case MIDI_STATUS_PITCH_WHEEL: /* Pitch wheel */
   184 					b = track->data[currentPos++] & 0x7F;
   185 					currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, b);
   186 					currentEvent = currentEvent->next;
   187 					if (NULL == currentEvent)
   188 					{
   189 						FreeMIDIEventList(head);
   190 						return NULL;
   191 					}
   192 					break;
   193 
   194 				case MIDI_STATUS_PROG_CHANGE: /* Program change */
   195 				case MIDI_STATUS_PRESSURE: /* Channel pressure */
   196 					a &= 0x7f;
   197 					currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, 0);
   198 					currentEvent = currentEvent->next;
   199 					if (NULL == currentEvent)
   200 					{
   201 						FreeMIDIEventList(head);
   202 						return NULL;
   203 					}
   204 					break;
   205 
   206 				default: /* Sysex already handled above */
   207 					break;
   208 			}
   209 		}
   210 	}
   211 	
   212 	currentEvent = head->next;
   213 	free(head);	/* release the dummy head event */
   214 	return currentEvent;
   215 }
   216 
   217 /*
   218  *  Convert a midi song, consisting of up to 32 tracks, to a list of MIDIEvents.
   219  *  To do so, first convert the tracks seperatly, then interweave the resulting
   220  *  MIDIEvent-Lists to one big list.
   221  */
   222 static MIDIEvent *MIDItoStream(MIDIFile *mididata)
   223 {
   224 	MIDIEvent **track;
   225 	MIDIEvent *head = CreateEvent(0,0,0,0);	/* dummy event to make handling the list easier */
   226 	MIDIEvent *currentEvent = head;
   227 	int trackID;
   228 
   229 	if (NULL == head)
   230 		return NULL;
   231 
   232 	track = (MIDIEvent**) calloc(1, sizeof(MIDIEvent*) * mididata->nTracks);
   233 	if (NULL == track)
   234 	{
   235 		free(head);
   236 		return NULL;
   237 	}
   238 	/* First, convert all tracks to MIDIEvent lists */
   239 	for (trackID = 0; trackID < mididata->nTracks; trackID++)
   240 		track[trackID] = MIDITracktoStream(&mididata->track[trackID]);
   241 
   242 	/* Now, merge the lists. */
   243 	/* TODO */
   244 	while(1)
   245 	{
   246 		Uint32 lowestTime = INT_MAX;
   247 		int currentTrackID = -1;
   248 		
   249 		/* Find the next event */
   250 		for (trackID = 0; trackID < mididata->nTracks; trackID++)
   251 		{
   252 			if (track[trackID] && (track[trackID]->time < lowestTime))
   253 			{
   254 				currentTrackID = trackID;
   255 				lowestTime = track[currentTrackID]->time;
   256 			}
   257 		}
   258 		
   259 		/* Check if we processes all events */
   260 		if (currentTrackID == -1)
   261 			break;
   262 		
   263 		currentEvent->next = track[currentTrackID];
   264 		track[currentTrackID] = track[currentTrackID]->next;
   265 
   266 		currentEvent = currentEvent->next;
   267 		
   268 		
   269 		lowestTime = 0;
   270 	}
   271 
   272 	/* Make sure the list is properly terminated */
   273 	currentEvent->next = 0;
   274 
   275 	currentEvent = head->next;
   276     free(track);
   277 	free(head);	/* release the dummy head event */
   278 	return currentEvent;
   279 }
   280 
   281 static int ReadMIDIFile(MIDIFile *mididata, SDL_RWops *rw)
   282 {
   283 	int i = 0;
   284 	Uint32	ID;
   285 	Uint32	size;
   286 	Uint16	format;
   287 	Uint16	tracks;
   288 	Uint16	division;
   289 
   290 	if (!mididata)
   291 		return 0;
   292 	if (!rw)
   293 		return 0;
   294 
   295 	/* Make sure this is really a MIDI file */
   296 	SDL_RWread(rw, &ID, 1, 4);
   297 	if (BE_LONG(ID) != 'MThd')
   298 		return 0;
   299 	
   300 	/* Header size must be 6 */
   301 	SDL_RWread(rw, &size, 1, 4);
   302 	size = BE_LONG(size);
   303 	if (size != 6)
   304 		return 0;
   305 	
   306 	/* We only support format 0 and 1, but not 2 */
   307 	SDL_RWread(rw, &format, 1, 2);
   308 	format = BE_SHORT(format);
   309 	if (format != 0 && format != 1)
   310 		return 0;
   311 	
   312 	SDL_RWread(rw, &tracks, 1, 2);
   313 	tracks = BE_SHORT(tracks);
   314 	mididata->nTracks = tracks;
   315     
   316     /* Allocate tracks */
   317     mididata->track = (MIDITrack*) calloc(1, sizeof(MIDITrack) * mididata->nTracks);
   318     if (NULL == mididata->track)
   319     {
   320         Mix_SetError("Out of memory");
   321         goto bail;
   322     }
   323     
   324 	/* Retrieve the PPQN value, needed for playback */
   325 	SDL_RWread(rw, &division, 1, 2);
   326 	mididata->division = BE_SHORT(division);
   327 	
   328 	
   329 	for (i=0; i<tracks; i++)
   330 	{
   331 		SDL_RWread(rw, &ID, 1, 4);	/* We might want to verify this is MTrk... */
   332 		SDL_RWread(rw, &size, 1, 4);
   333 		size = BE_LONG(size);
   334 		mididata->track[i].len = size;
   335 		mididata->track[i].data = malloc(size);
   336 		if (NULL == mididata->track[i].data)
   337 		{
   338 			Mix_SetError("Out of memory");
   339 			goto bail;
   340 		}
   341 		SDL_RWread(rw, mididata->track[i].data, 1, size);
   342 	}
   343 	return 1;
   344 
   345 bail:
   346 	for(;i >= 0; i--)
   347 	{
   348 		if (mididata->track[i].data)
   349 			free(mididata->track[i].data);
   350 	}
   351 
   352 	return 0;
   353 }
   354 
   355 MIDIEvent *CreateMIDIEventList(SDL_RWops *rw, Uint16 *division)
   356 {
   357 	MIDIFile *mididata = NULL;
   358 	MIDIEvent *eventList;
   359 	int trackID;
   360 	
   361 	mididata = calloc(1, sizeof(MIDIFile));
   362 	if (!mididata)
   363 		return NULL;
   364 
   365 	/* Open the file */
   366 	if ( rw != NULL )
   367 	{
   368 		/* Read in the data */
   369 		if ( ! ReadMIDIFile(mididata, rw))
   370 		{
   371 			free(mididata);
   372 			return NULL;
   373 		}
   374 	}
   375 	else
   376 	{
   377 		free(mididata);
   378 		return NULL;
   379 	}
   380 	
   381 	if (division)
   382 		*division = mididata->division;
   383 	
   384 	eventList = MIDItoStream(mididata);
   385 	if (eventList == NULL)
   386 	{
   387 		free(mididata);
   388 		return NULL;
   389 	}
   390 	for(trackID = 0; trackID < mididata->nTracks; trackID++)
   391 	{
   392 		if (mididata->track[trackID].data)
   393 			free(mididata->track[trackID].data);
   394 	}
   395 	free(mididata->track);
   396 	free(mididata);
   397 	
   398 	return eventList;
   399 }
   400 
   401 void FreeMIDIEventList(MIDIEvent *head)
   402 {
   403 	MIDIEvent *cur, *next;
   404 	
   405 	cur = head;
   406 
   407 	while (cur)
   408 	{
   409 		next = cur->next;
   410 		if (cur->extraData) 
   411 			free (cur->extraData);
   412 		free (cur);
   413 		cur = next;
   414 	}
   415 }