1/* TinyMidiLoader - v0.7 - Minimalistic midi parsing library - https://github.com/schellingb/TinySoundFont
  2                                     no warranty implied; use at your own risk
  3   Do this:
  4      #define TML_IMPLEMENTATION
  5   before you include this file in *one* C or C++ file to create the implementation.
  6   // i.e. it should look like this:
  7   #include ...
  8   #include ...
  9   #define TML_IMPLEMENTATION
 10   #include "tml.h"
 11
 12   [OPTIONAL] #define TML_NO_STDIO to remove stdio dependency
 13   [OPTIONAL] #define TML_MALLOC, TML_REALLOC, and TML_FREE to avoid stdlib.h
 14   [OPTIONAL] #define TML_MEMCPY to avoid string.h
 15
 16   LICENSE (ZLIB)
 17
 18   Copyright (C) 2017, 2018, 2020 Bernhard Schelling
 19
 20   This software is provided 'as-is', without any express or implied
 21   warranty.  In no event will the authors be held liable for any damages
 22   arising from the use of this software.
 23
 24   Permission is granted to anyone to use this software for any purpose,
 25   including commercial applications, and to alter it and redistribute it
 26   freely, subject to the following restrictions:
 27
 28   1. The origin of this software must not be misrepresented; you must not
 29      claim that you wrote the original software. If you use this software
 30      in a product, an acknowledgment in the product documentation would be
 31      appreciated but is not required.
 32   2. Altered source versions must be plainly marked as such, and must not be
 33      misrepresented as being the original software.
 34   3. This notice may not be removed or altered from any source distribution.
 35
 36*/
 37
 38#ifndef TML_INCLUDE_TML_INL
 39#define TML_INCLUDE_TML_INL
 40
 41#ifdef __cplusplus
 42extern "C" {
 43#endif
 44
 45// Define this if you want the API functions to be static
 46#ifdef TML_STATIC
 47#define TMLDEF static
 48#else
 49#define TMLDEF extern
 50#endif
 51
 52// Channel message type
 53enum TMLMessageType
 54{
 55	TML_NOTE_OFF = 0x80, TML_NOTE_ON = 0x90, TML_KEY_PRESSURE = 0xA0, TML_CONTROL_CHANGE = 0xB0, TML_PROGRAM_CHANGE = 0xC0, TML_CHANNEL_PRESSURE = 0xD0, TML_PITCH_BEND = 0xE0, TML_SET_TEMPO = 0x51
 56};
 57
 58// Midi controller numbers
 59enum TMLController
 60{
 61	TML_BANK_SELECT_MSB,      TML_MODULATIONWHEEL_MSB, TML_BREATH_MSB,       TML_FOOT_MSB = 4,      TML_PORTAMENTO_TIME_MSB,   TML_DATA_ENTRY_MSB, TML_VOLUME_MSB,
 62	TML_BALANCE_MSB,          TML_PAN_MSB = 10,        TML_EXPRESSION_MSB,   TML_EFFECTS1_MSB,      TML_EFFECTS2_MSB,          TML_GPC1_MSB = 16, TML_GPC2_MSB, TML_GPC3_MSB, TML_GPC4_MSB,
 63	TML_BANK_SELECT_LSB = 32, TML_MODULATIONWHEEL_LSB, TML_BREATH_LSB,       TML_FOOT_LSB = 36,     TML_PORTAMENTO_TIME_LSB,   TML_DATA_ENTRY_LSB, TML_VOLUME_LSB,
 64	TML_BALANCE_LSB,          TML_PAN_LSB = 42,        TML_EXPRESSION_LSB,   TML_EFFECTS1_LSB,      TML_EFFECTS2_LSB,          TML_GPC1_LSB = 48, TML_GPC2_LSB, TML_GPC3_LSB, TML_GPC4_LSB,
 65	TML_SUSTAIN_SWITCH = 64,  TML_PORTAMENTO_SWITCH,   TML_SOSTENUTO_SWITCH, TML_SOFT_PEDAL_SWITCH, TML_LEGATO_SWITCH,         TML_HOLD2_SWITCH,
 66	TML_SOUND_CTRL1,          TML_SOUND_CTRL2,         TML_SOUND_CTRL3,      TML_SOUND_CTRL4,       TML_SOUND_CTRL5,           TML_SOUND_CTRL6,
 67	TML_SOUND_CTRL7,          TML_SOUND_CTRL8,         TML_SOUND_CTRL9,      TML_SOUND_CTRL10,      TML_GPC5, TML_GPC6,        TML_GPC7, TML_GPC8,
 68	TML_PORTAMENTO_CTRL,      TML_FX_REVERB = 91,      TML_FX_TREMOLO,       TML_FX_CHORUS,         TML_FX_CELESTE_DETUNE,     TML_FX_PHASER,
 69	TML_DATA_ENTRY_INCR,      TML_DATA_ENTRY_DECR,     TML_NRPN_LSB,         TML_NRPN_MSB,          TML_RPN_LSB,               TML_RPN_MSB,
 70	TML_ALL_SOUND_OFF = 120,  TML_ALL_CTRL_OFF,        TML_LOCAL_CONTROL,    TML_ALL_NOTES_OFF,     TML_OMNI_OFF, TML_OMNI_ON, TML_POLY_OFF, TML_POLY_ON
 71};
 72
 73// A single MIDI message linked to the next message in time
 74typedef struct tml_message
 75{
 76	// Time of the message in milliseconds
 77	unsigned int time;
 78
 79	// Type (see TMLMessageType) and channel number
 80	unsigned char type, channel;
 81
 82	// 2 byte of parameter data based on the type:
 83	// - key, velocity for TML_NOTE_ON and TML_NOTE_OFF messages
 84	// - key, key_pressure for TML_KEY_PRESSURE messages
 85	// - control, control_value for TML_CONTROL_CHANGE messages (see TMLController)
 86	// - program for TML_PROGRAM_CHANGE messages
 87	// - channel_pressure for TML_CHANNEL_PRESSURE messages
 88	// - pitch_bend for TML_PITCH_BEND messages
 89	union
 90	{
 91		#ifdef _MSC_VER
 92		#pragma warning(push)
 93		#pragma warning(disable:4201) //nonstandard extension used: nameless struct/union
 94		#elif defined(__GNUC__)
 95		#pragma GCC diagnostic push
 96		#pragma GCC diagnostic ignored "-Wpedantic" //ISO C++ prohibits anonymous structs
 97		#endif
 98
 99		struct { union { char key, control, program, channel_pressure; }; union { char velocity, key_pressure, control_value; }; };
100		struct { unsigned short pitch_bend; };
101
102		#ifdef _MSC_VER
103		#pragma warning( pop )
104		#elif defined(__GNUC__)
105		#pragma GCC diagnostic pop
106		#endif
107	};
108
109	// The pointer to the next message in time following this event
110	struct tml_message* next;
111} tml_message;
112
113// The load functions will return a pointer to a struct tml_message.
114// Normally the linked list gets traversed by following the next pointers.
115// Make sure to keep the pointer to the first message to free the memory.
116// On error the tml_load* functions will return NULL most likely due to an
117// invalid MIDI stream (or if the file did not exist in tml_load_filename).
118
119#ifndef TML_NO_STDIO
120// Directly load a MIDI file from a .mid file path
121TMLDEF tml_message* tml_load_filename(const char* filename);
122#endif
123
124// Load a MIDI file from a block of memory
125TMLDEF tml_message* tml_load_memory(const void* buffer, int size);
126
127// Get infos about this loaded MIDI file, returns the note count
128// NULL can be passed for any output value pointer if not needed.
129//   used_channels:   Will be set to how many channels play notes
130//                    (i.e. 1 if channel 15 is used but no other)
131//   used_programs:   Will be set to how many different programs are used
132//   total_notes:     Will be set to the total number of note on messages
133//   time_first_note: Will be set to the time of the first note on message
134//   time_length:     Will be set to the total time in milliseconds
135TMLDEF int tml_get_info(tml_message* first_message, int* used_channels, int* used_programs, int* total_notes, unsigned int* time_first_note, unsigned int* time_length);
136
137// Read the tempo (microseconds per quarter note) value from a message with the type TML_SET_TEMPO
138TMLDEF int tml_get_tempo_value(tml_message* set_tempo_message);
139
140// Free all the memory of the linked message list (can also call free() manually)
141TMLDEF void tml_free(tml_message* f);
142
143// Stream structure for the generic loading
144struct tml_stream
145{
146	// Custom data given to the functions as the first parameter
147	void* data;
148
149	// Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes)
150	int (*read)(void* data, void* ptr, unsigned int size);
151};
152
153// Generic Midi loading method using the stream structure above
154TMLDEF tml_message* tml_load(struct tml_stream* stream);
155
156// If this library is used together with TinySoundFont, tsf_stream (equivalent to tml_stream) can also be used
157struct tsf_stream;
158TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream);
159
160#ifdef __cplusplus
161}
162#endif
163
164// end header
165// ---------------------------------------------------------------------------------------------------------
166#endif //TML_INCLUDE_TML_INL
167
168#ifdef TML_IMPLEMENTATION
169
170#if !defined(TML_MALLOC) || !defined(TML_FREE) || !defined(TML_REALLOC)
171#  include <stdlib.h>
172#  define TML_MALLOC  malloc
173#  define TML_FREE    free
174#  define TML_REALLOC realloc
175#endif
176
177#if !defined(TML_MEMCPY)
178#  include <string.h>
179#  define TML_MEMCPY  memcpy
180#endif
181
182#ifndef TML_NO_STDIO
183#  include <stdio.h>
184#endif
185
186#define TML_NULL 0
187
188////crash on errors and warnings to find broken midi files while debugging
189//#define TML_ERROR(msg) *(int*)0 = 0xbad;
190//#define TML_WARN(msg)  *(int*)0 = 0xf00d;
191
192////print errors and warnings
193//#define TML_ERROR(msg) printf("ERROR: %s\n", msg);
194//#define TML_WARN(msg)  printf("WARNING: %s\n", msg);
195
196#ifndef TML_ERROR
197#define TML_ERROR(msg)
198#endif
199
200#ifndef TML_WARN
201#define TML_WARN(msg)
202#endif
203
204#ifdef __cplusplus
205extern "C" {
206#endif
207
208#ifndef TML_NO_STDIO
209static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); }
210TMLDEF tml_message* tml_load_filename(const char* filename)
211{
212	struct tml_message* res;
213	struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_stdio_read };
214	#if __STDC_WANT_SECURE_LIB__
215	FILE* f = TML_NULL; fopen_s(&f, filename, "rb");
216	#else
217	FILE* f = fopen(filename, "rb");
218	#endif
219	if (!f) { TML_ERROR("File not found"); return 0; }
220	stream.data = f;
221	res = tml_load(&stream);
222	fclose(f);
223	return res;
224}
225#endif
226
227struct tml_stream_memory { const char* buffer; unsigned int total, pos; };
228static int tml_stream_memory_read(struct tml_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TML_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; }
229TMLDEF struct tml_message* tml_load_memory(const void* buffer, int size)
230{
231	struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_memory_read };
232	struct tml_stream_memory f = { 0, 0, 0 };
233	f.buffer = (const char*)buffer;
234	f.total = size;
235	stream.data = &f;
236	return tml_load(&stream);
237}
238
239struct tml_track
240{
241	unsigned int Idx, End, Ticks;
242};
243
244struct tml_tempomsg
245{
246	unsigned int time;
247	unsigned char type, Tempo[3];
248	tml_message* next;
249};
250
251struct tml_parser
252{
253	unsigned char *buf, *buf_end; 
254	int last_status, message_array_size, message_count;
255};
256
257enum TMLSystemType
258{
259	TML_TEXT  = 0x01, TML_COPYRIGHT    = 0x02, TML_TRACK_NAME     = 0x03, TML_INST_NAME     = 0x04, TML_LYRIC           = 0x05, TML_MARKER       = 0x06, TML_CUE_POINT = 0x07,
260	TML_EOT   = 0x2f, TML_SMPTE_OFFSET = 0x54, TML_TIME_SIGNATURE = 0x58, TML_KEY_SIGNATURE = 0x59, TML_SEQUENCER_EVENT = 0x7f,
261	TML_SYSEX = 0xf0, TML_TIME_CODE    = 0xf1, TML_SONG_POSITION  = 0xf2, TML_SONG_SELECT   = 0xf3, TML_TUNE_REQUEST    = 0xf6, TML_EOX          = 0xf7, TML_SYNC      = 0xf8,
262	TML_TICK  = 0xf9, TML_START        = 0xfa, TML_CONTINUE       = 0xfb, TML_STOP          = 0xfc, TML_ACTIVE_SENSING  = 0xfe, TML_SYSTEM_RESET = 0xff
263};
264
265static int tml_readbyte(struct tml_parser* p)
266{
267	return (p->buf == p->buf_end ? -1 : *(p->buf++));
268}
269
270static int tml_readvariablelength(struct tml_parser* p)
271{
272	unsigned int res = 0, i = 0;
273	unsigned char c;
274	for (; i != 4; i++)
275	{
276		if (p->buf == p->buf_end) { TML_WARN("Unexpected end of file"); return -1; }
277		c = *(p->buf++);
278		if (c & 0x80) res = ((res | (c & 0x7F)) << 7);
279		else return (int)(res | c);
280	}
281	TML_WARN("Invalid variable length byte count"); return -1;
282}
283
284static int tml_parsemessage(tml_message** f, struct tml_parser* p)
285{
286	int deltatime = tml_readvariablelength(p), status = tml_readbyte(p);
287	tml_message* evt;
288
289	if (deltatime & 0xFFF00000) deltatime = 0; //throw away delays that are insanely high for malformatted midis
290	if (status < 0) { TML_WARN("Unexpected end of file"); return -1; }
291	if ((status & 0x80) == 0)
292	{
293		// Invalid, use same status as before
294		if ((p->last_status & 0x80) == 0) { TML_WARN("Undefined status and invalid running status"); return -1; }
295		p->buf--;
296		status = p->last_status;
297	}
298	else p->last_status = status;
299
300	if (p->message_array_size == p->message_count)
301	{
302		//start allocated memory size of message array at 64, double each time until 8192, then add 1024 entries until done
303		p->message_array_size += (!p->message_array_size ? 64 : (p->message_array_size > 4096 ? 1024 : p->message_array_size));
304		*f = (tml_message*)TML_REALLOC(*f, p->message_array_size * sizeof(tml_message));
305		if (!*f) { TML_ERROR("Out of memory"); return -1; }
306	}
307	evt = *f + p->message_count;
308
309	//check what message we have
310	if ((status == TML_SYSEX) || (status == TML_EOX)) //sysex
311	{
312		//sysex messages are not handled
313		p->buf += tml_readvariablelength(p);
314		if (p->buf > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; }
315		evt->type = 0;
316	}
317	else if (status == 0xFF) //meta events
318	{
319		int meta_type = tml_readbyte(p), buflen = tml_readvariablelength(p);
320		unsigned char* metadata = p->buf;
321		if (meta_type < 0) { TML_WARN("Unexpected end of file"); return -1; }
322		if (buflen > 0 && (p->buf += buflen) > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; }
323
324		switch (meta_type)
325		{
326			case TML_EOT:
327				if (buflen != 0) { TML_WARN("Invalid length for EndOfTrack event"); return -1; }
328				if (!deltatime) return TML_EOT; //no need to store this message
329				evt->type = TML_EOT;
330				break;
331
332			case TML_SET_TEMPO:
333				if (buflen != 3) { TML_WARN("Invalid length for SetTempo meta event"); return -1; }
334				evt->type = TML_SET_TEMPO;
335				((struct tml_tempomsg*)evt)->Tempo[0] = metadata[0];
336				((struct tml_tempomsg*)evt)->Tempo[1] = metadata[1];
337				((struct tml_tempomsg*)evt)->Tempo[2] = metadata[2];
338				break;
339
340			default:
341				evt->type = 0;
342		}
343	}
344	else //channel message
345	{
346		int param; 
347		if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
348		evt->key = (param & 0x7f);
349		evt->channel = (status & 0x0f);
350		switch (evt->type = (status & 0xf0))
351		{
352			case TML_NOTE_OFF:
353			case TML_NOTE_ON:
354			case TML_KEY_PRESSURE:
355			case TML_CONTROL_CHANGE:
356				if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
357				evt->velocity = (param & 0x7f);
358				break;
359
360			case TML_PITCH_BEND:
361				if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
362				evt->pitch_bend = ((param & 0x7f) << 7) | evt->key;
363				break;
364
365			case TML_PROGRAM_CHANGE:
366			case TML_CHANNEL_PRESSURE:
367				evt->velocity = 0;
368				break;
369
370			default: //ignore system/manufacture messages
371				evt->type = 0;
372				break;
373		}
374	}
375
376	if (deltatime || evt->type)
377	{
378		evt->time = deltatime;
379		p->message_count++;
380	}
381	return evt->type;
382}
383
384TMLDEF tml_message* tml_load(struct tml_stream* stream)
385{
386	int num_tracks, division, trackbufsize = 0;
387	unsigned char midi_header[14], *trackbuf = TML_NULL;
388	struct tml_message* messages = TML_NULL;
389	struct tml_track *tracks, *t, *tracksEnd;
390	struct tml_parser p = { TML_NULL, TML_NULL, 0, 0, 0 };
391
392	// Parse MIDI header
393	if (stream->read(stream->data, midi_header, 14) != 14) { TML_ERROR("Unexpected end of file"); return messages; }
394	if (midi_header[0] != 'M' || midi_header[1] != 'T' || midi_header[2] != 'h' || midi_header[3] != 'd' ||
395	    midi_header[7] != 6   || midi_header[9] >  2) { TML_ERROR("Doesn't look like a MIDI file: invalid MThd header"); return messages; }
396	if (midi_header[12] & 0x80) { TML_ERROR("File uses unsupported SMPTE timing"); return messages; }
397	num_tracks = (int)(midi_header[10] << 8) | midi_header[11];
398	division = (int)(midi_header[12] << 8) | midi_header[13]; //division is ticks per beat (quarter-note)
399	if (num_tracks <= 0 && division <= 0) { TML_ERROR("Doesn't look like a MIDI file: invalid track or division values"); return messages; }
400
401	// Allocate temporary tracks array for parsing
402	tracks = (struct tml_track*)TML_MALLOC(sizeof(struct tml_track) * num_tracks);
403	tracksEnd = &tracks[num_tracks];
404	for (t = tracks; t != tracksEnd; t++) t->Idx = t->End = t->Ticks = 0;
405
406	// Read all messages for all tracks
407	for (t = tracks; t != tracksEnd; t++)
408	{
409		unsigned char track_header[8];
410		int track_length;
411		if (stream->read(stream->data, track_header, 8) != 8) { TML_WARN("Unexpected end of file"); break; }
412		if (track_header[0] != 'M' || track_header[1] != 'T' || track_header[2] != 'r' || track_header[3] != 'k')
413			{ TML_WARN("Invalid MTrk header"); break; }
414
415		// Get size of track data and read into buffer (allocate bigger buffer if needed)
416		track_length = track_header[7] | (track_header[6] << 8) | (track_header[5] << 16) | (track_header[4] << 24);
417		if (track_length < 0) { TML_WARN("Invalid MTrk header"); break; }
418		if (trackbufsize < track_length) { TML_FREE(trackbuf); trackbuf = (unsigned char*)TML_MALLOC(trackbufsize = track_length); }
419		if (stream->read(stream->data, trackbuf, track_length) != track_length) { TML_WARN("Unexpected end of file"); break; }
420
421		t->Idx = p.message_count;
422		for (p.buf_end = (p.buf = trackbuf) + track_length; p.buf != p.buf_end;)
423		{
424			int type = tml_parsemessage(&messages, &p);
425			if (type == TML_EOT || type < 0) break; //file end or illegal data encountered
426		}
427		if (p.buf != p.buf_end) { TML_WARN( "Track length did not match data length"); }
428		t->End = p.message_count;
429	}
430	TML_FREE(trackbuf);
431
432	// Change message time signature from delta ticks to actual msec values and link messages ordered by time
433	if (p.message_count)
434	{
435		tml_message *PrevMessage = TML_NULL, *Msg, *MsgEnd, Swap;
436		unsigned int ticks = 0, tempo_ticks = 0; //tick counter and value at last tempo change
437		int step_smallest, msec, tempo_msec = 0; //msec value at last tempo change
438		double ticks2time = 500000 / (1000.0 * division); //milliseconds per tick
439
440		// Loop through all messages over all tracks ordered by time
441		for (step_smallest = 0; step_smallest != 0x7fffffff; ticks += step_smallest)
442		{
443			step_smallest = 0x7fffffff;
444			msec = tempo_msec + (int)((ticks - tempo_ticks) * ticks2time);
445			for (t = tracks; t != tracksEnd; t++)
446			{
447				if (t->Idx == t->End) continue;
448				for (Msg = &messages[t->Idx], MsgEnd = &messages[t->End]; Msg != MsgEnd && t->Ticks + Msg->time == ticks; Msg++, t->Idx++)
449				{
450					t->Ticks += Msg->time;
451					if (Msg->type == TML_SET_TEMPO)
452					{
453						unsigned char* Tempo = ((struct tml_tempomsg*)Msg)->Tempo;
454						ticks2time = ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2])/(1000.0 * division);
455						tempo_msec = msec;
456						tempo_ticks = ticks;
457					}
458					if (Msg->type)
459					{
460						Msg->time = msec;
461						if (PrevMessage) { PrevMessage->next = Msg; PrevMessage = Msg; }
462						else { Swap = *Msg; *Msg = *messages; *messages = Swap; PrevMessage = messages; }
463					}
464				}
465				if (Msg != MsgEnd && t->Ticks + Msg->time > ticks)
466				{
467					int step = (int)(t->Ticks + Msg->time - ticks);
468					if (step < step_smallest) step_smallest = step;
469				}
470			}
471		}
472		if (PrevMessage) PrevMessage->next = TML_NULL;
473		else p.message_count = 0;
474	}
475	TML_FREE(tracks);
476
477	if (p.message_count == 0)
478	{
479		TML_FREE(messages);
480		messages = TML_NULL;
481	}
482
483	return messages;
484}
485
486TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream)
487{
488	return tml_load((struct tml_stream*)stream);
489}
490
491TMLDEF int tml_get_info(tml_message* Msg, int* out_used_channels, int* out_used_programs, int* out_total_notes, unsigned int* out_time_first_note, unsigned int* out_time_length)
492{
493	int used_programs = 0, used_channels = 0, total_notes = 0;
494	unsigned int time_first_note = 0xffffffff, time_length = 0;
495	unsigned char channels[16] = { 0 }, programs[128] = { 0 };
496	for (;Msg; Msg = Msg->next)
497	{
498		time_length = Msg->time;
499		if (Msg->type == TML_PROGRAM_CHANGE && !programs[(int)Msg->program]) { programs[(int)Msg->program] = 1; used_programs++; }
500		if (Msg->type != TML_NOTE_ON) continue;
501		if (time_first_note == 0xffffffff) time_first_note = time_length;
502		if (!channels[Msg->channel]) { channels[Msg->channel] = 1; used_channels++; }
503		total_notes++;
504	}
505	if (time_first_note == 0xffffffff) time_first_note = 0;
506	if (out_used_channels  ) *out_used_channels   = used_channels;
507	if (out_used_programs  ) *out_used_programs   = used_programs;
508	if (out_total_notes    ) *out_total_notes     = total_notes;
509	if (out_time_first_note) *out_time_first_note = time_first_note;
510	if (out_time_length    ) *out_time_length     = time_length;
511	return total_notes;
512}
513
514TMLDEF int tml_get_tempo_value(tml_message* msg)
515{
516	unsigned char* Tempo;
517	if (!msg || msg->type != TML_SET_TEMPO) return 0;
518	Tempo = ((struct tml_tempomsg*)msg)->Tempo;
519	return ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2]);
520}
521
522TMLDEF void tml_free(tml_message* f)
523{
524	TML_FREE(f);
525}
526
527#ifdef __cplusplus
528}
529#endif
530
531#endif //TML_IMPLEMENTATION