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