source: abuse/trunk/src/sdlport/hmi.cpp @ 494

Last change on this file since 494 was 494, checked in by Sam Hocevar, 10 years ago

style: remove trailing spaces, fix copyright statements.

  • Property svn:keywords set to Id
File size: 9.4 KB
Line 
1// Load Abuse HMI files and covert them to standard Midi format
2//
3// HMI files differ from Midi files in the following ways:
4// - there is a header giving offsets to the tracks and various other information (unknown)
5// - note-on events include the duration of the note instead of dedicated note-off events
6// - additional 0xFE event with variable length, purpose unknown
7//
8// This converter does the bare minimum to get Abuse HMI files to convert. The bpm and
9// header information is fixed and not read from the file (except the number of tracks).
10// HMI files make use of running status notation, the converted files don't.
11
12#include <malloc.h>
13#include <stdlib.h>
14#include <string.h>
15#include <stdio.h>
16
17
18#define MAX_NOTE_OFF_EVENTS 30
19
20typedef struct
21{
22    unsigned int time;
23    unsigned char command;
24    unsigned char note;
25}
26midi_note_off_event_t;
27
28midi_note_off_event_t note_off_events[MAX_NOTE_OFF_EVENTS];
29
30
31
32unsigned int get_int_from_buffer(unsigned char* buffer)
33{
34    return (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) + (buffer[0]);
35}
36
37
38void write_big_endian_number(unsigned int le, unsigned char* buffer)
39{
40    buffer[3] = (le & 0x000000FF);
41    buffer[2] = (le & 0x0000FF00) >> 8;
42    buffer[1] = (le & 0x00FF0000) >> 16;
43    buffer[0] = (le & 0xFF000000) >> 24;
44}
45
46
47int compare_times(const void* a, const void* b)
48{
49    if (((midi_note_off_event_t*)a)->time < ((midi_note_off_event_t*)b)->time)
50        return -1;
51    else if (((midi_note_off_event_t*)a)->time > ((midi_note_off_event_t*)b)->time)
52        return 1;
53    else
54        return 0;
55}
56
57
58// Variable length number code from: http://www.chriswareham.demon.co.uk/midifiles/variable_length.html
59
60unsigned int read_time_value(unsigned char* &buffer)
61{
62    unsigned int value;
63    unsigned char c;
64
65    if ((value = *buffer++) & 0x80)
66    {
67        value &= 0x7F;
68        do
69        {
70            value = (value << 7) + ((c = *buffer++) & 0x7F);
71        }
72        while (c & 0x80);
73    }
74
75    return value;
76}
77
78
79void write_time_value(unsigned int time, unsigned char* &buffer)
80{
81    unsigned int value_buffer = time & 0x7F;
82
83    while (time >>= 7)
84    {
85        value_buffer <<= 8;
86        value_buffer |= ((time & 0x7F) | 0x80);
87    }
88
89    while (1)
90    {
91        *buffer++ = value_buffer;
92        if (value_buffer & 0x80)
93            value_buffer >>= 8;
94        else
95            break;
96    }
97}
98
99
100void remember_note_off_event(unsigned int time, unsigned char command, unsigned char note)
101{
102    int i;
103    for (i = 0; i < MAX_NOTE_OFF_EVENTS; i++)
104    {
105        if (note_off_events[i].time == 0xFFFFFFFF)
106        {
107            note_off_events[i].time = time;
108            note_off_events[i].command = command;
109            note_off_events[i].note = note;
110            break;
111        }
112    }
113
114    // Sort the note off array by the time
115    qsort(note_off_events, MAX_NOTE_OFF_EVENTS, sizeof(midi_note_off_event_t), compare_times);
116}
117
118
119void check_for_note_off_events(unsigned int &current_time, unsigned int &last_time, unsigned char* &buffer)
120{
121    int i;
122    for (i = 0; i < MAX_NOTE_OFF_EVENTS; i++)
123    {
124        if (note_off_events[i].time == 0xFFFFFFFF)
125            break;
126
127        if (note_off_events[i].time < current_time)
128        {
129            // Add event
130            write_time_value(note_off_events[i].time - last_time, buffer);
131            last_time = note_off_events[i].time;
132
133            *buffer++ = note_off_events[i].command;
134            *buffer++ = note_off_events[i].note;
135            *buffer++ = 0x00;
136
137            // Remove event from queue
138            note_off_events[i].time = 0xFFFFFFFF;
139        }
140    }
141
142    // Sort the note off array by the time
143    qsort(note_off_events, MAX_NOTE_OFF_EVENTS, sizeof(midi_note_off_event_t), compare_times);
144}
145
146
147void convert_hmi_track(unsigned char* input, unsigned int input_size, unsigned char* &output)
148{
149    int done = 0;
150    unsigned char current_command = 0;
151    unsigned char current_value = 0;
152    unsigned int current_time = 0;
153    unsigned int last_time = 0;
154    unsigned char* start_of_buffer = output;
155    unsigned char* start_of_input = input;
156
157    memset(note_off_events, 0xFF, sizeof(midi_note_off_event_t) * MAX_NOTE_OFF_EVENTS);
158
159    // Midi data offset is at 0x57 from track start
160    input += input[0x57];
161
162    // Write track header, leave length as zero for now
163    unsigned char track_header[] = { 0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00};
164    memcpy(output, track_header, 8);
165    output += 8;
166
167    while (!done)
168    {
169        // Read variable length time
170        current_time += read_time_value(input);
171
172        // Next comes either a command (>= 0x80) or data (running status)
173        current_value = *input++;
174        if (current_value >= 0x80)
175        {
176            // Is command, make current, increase data pointer
177            current_command = current_value;
178            current_value = *input++;
179        }
180
181        // Check if note off events have to be inserted here
182        check_for_note_off_events(current_time, last_time, output);
183
184        if (current_command != 0xFE)
185        {
186            // Write variable length time to output
187            write_time_value(current_time - last_time, output);
188            last_time = current_time;
189        }
190
191        // Write command, no running status in output
192        if (current_command != 0xFE)
193            *output++ = current_command;
194
195        switch (current_command & 0xF0)
196        {
197            // 1 data byte
198        case 0xC0: // Program change
199        case 0xD0: // Channel aftertouch
200            *output++ = current_value;
201            break;
202
203            // 2 data bytes
204        case 0x80: // Note off, does not occur in HMI
205        case 0xA0: // Aftertouch
206        case 0xB0: // Controller
207        case 0xE0: // Pitch bend
208            *output++ = current_value;
209            *output++ = *input++;
210            break;
211
212            // 3 data bytes
213        case 0x90: // Note on, non-standard, HMI files specify the duration as a third param
214            *output++ = current_value;
215            *output++ = *input++;
216            remember_note_off_event(current_time + read_time_value(input), current_command, current_value);
217            break;
218
219        case 0xF0: // Meta event
220            if (current_command == 0xFE)
221            {
222                // HMI specific event, variable length depending on type
223                switch (current_value)
224                {
225                case 0x10:
226                    input += 2;
227                    input += *input;
228                    input += 5;
229                    break;
230                case 0x14:
231                    input += 2;
232                    break;
233                case 0x15:
234                    input += 6;
235                    break;
236                }
237            }
238            else
239            {
240                // Only process end marker
241                *output++ = current_value;
242                *output++ = *input++;
243                done = 1;
244            }
245            break;
246
247        default:
248            // error?
249            break;
250        }
251
252        if ((unsigned int)(input - start_of_input) >= input_size)
253            break;
254    }
255
256    // Write end marker if necessary
257    if (done != 1)
258    {
259        unsigned char end_marker[] = { 0x00, 0xFF, 0x2F, 0x00 };
260        memcpy(output, end_marker, 4);
261        output += 4;
262    }
263
264    // Update header with length of track
265    write_big_endian_number((unsigned int)(output - start_of_buffer - 8), &start_of_buffer[4]);
266}
267
268
269unsigned char* load_hmi(char* filename, unsigned int &data_size)
270{
271    unsigned char* input_buffer;
272    unsigned char* output_buffer;
273
274    FILE* hmifile = fopen(filename, "rb");
275
276    if (hmifile == NULL)
277        return NULL;
278
279    fseek(hmifile, 0, SEEK_END);
280    int buffersize = ftell(hmifile);
281    fseek(hmifile, 0, SEEK_SET);
282
283    input_buffer = (unsigned char*)malloc(buffersize);
284    fread(input_buffer, 1, buffersize, hmifile);
285    fclose(hmifile);
286
287    output_buffer = (unsigned char*)malloc(buffersize * 10); // Midi files can be larger than HMI files
288    unsigned char* output_buffer_ptr = output_buffer;
289
290    // Offset to tracks is at 0x113
291    unsigned int offset_tracks = get_int_from_buffer(&input_buffer[0xE8]);
292    unsigned int next_offset = get_int_from_buffer(&input_buffer[0xF4]);
293
294    unsigned char num_tracks = (next_offset - offset_tracks) / sizeof(int);
295
296    // Write Midi file header
297    unsigned char midi_header[] = { 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, (num_tracks + 1), 0x00, 0xC0 };
298    memcpy(output_buffer_ptr, midi_header, 14);
299    output_buffer_ptr += 14;
300
301    // Write additional first track with bpm info
302    unsigned char bpm_track[] = { 0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0xFF, 0x51, 0x03, 0x18, 0x7F, 0xFF, 0x00, 0xFF, 0x2F, 0x00 };
303    memcpy(output_buffer_ptr, bpm_track, sizeof(bpm_track));
304    output_buffer_ptr += sizeof(bpm_track);
305
306    int i;
307    for (i = 0; i < num_tracks; i++)
308    {
309        unsigned int trackposition = get_int_from_buffer(&input_buffer[offset_tracks + i * (sizeof(int))]);
310        unsigned int tracksize;
311        if (i == num_tracks - 1)
312            tracksize = buffersize - trackposition;
313        else
314            tracksize = get_int_from_buffer(&input_buffer[offset_tracks + (i + 1) * (sizeof(int))]) - trackposition;
315
316        convert_hmi_track(&input_buffer[trackposition], tracksize, output_buffer_ptr);
317    }
318
319    data_size = (unsigned int)(output_buffer_ptr - output_buffer);
320    output_buffer = (unsigned char*)realloc(output_buffer, data_size);
321
322    free(input_buffer);
323
324    return output_buffer;
325}
326
Note: See TracBrowser for help on using the repository browser.