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

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

music: make the HMI decoder closer to the global coding style.

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