Mercurial > SDL_sound_CoreAudio
annotate decoders/timidity/readmidi.c @ 474:c66080364dff
Most decoders now report total sample play time, now. Technically, this
breaks binary compatibility with the 1.0 branch, since it extends the
Sound_Sample struct, but most (all?) programs are just passing pointers
allocated by SDL_sound around, and might be okay.
Source-level compatibility is not broken...yet! :)
--ryan.
-------- Original Message --------
Subject: SDL_sound patch: Finding total length of time of sound file.
Date: Sun, 26 Jan 2003 09:31:17 -0800 (PST)
Hi Ryan,
I am working with Eric Wing and helping him modify
SDL_sound. AS part of our efforts in improving and
enhancing SDL_sound, we like to submit this patch. We
modified the codecs to find the total time of a sound
file. Below is the explanation of the patch. The
patch is appended as an attachment to this email.
* MOTIVATION:
We needed the ability to get the total play time of a
sample (And we noticed that we're not the only ones).
Since SDL_sound blocks direct access to the specific
decoders, there is no way for a user to know this
information short of decoding the whole thing.
Because of this, we believe this will be a useful
addition, even though the accuracy may not be perfect
(subject to each decoder) or the information may not
always be available.
* CONTRIBUTORS:
Wesley Leong (modified the majority of the codecs and
verified the results)
Eric Wing (showed everyone how to do modify codec,
modified mikmod)
Wang Lam (modified a handful of codecs, researched
into specs and int overflow)
Ahilan Anantha (modified a few codecs and helped with
integer math)
* GENERAL ISSUES:
We chose the value to be milliseconds as an Sint32.
Milliseconds because that's what Sound_Seek takes as a
parameter and -1 to allow for instances/codecs where
the value could not be determined. We are
not sure if this is the final convention you want, so
we are willing to work with you on this.
We also expect the total_time field to be set on open
and never again modified by SDL_sound. Users may
access it directly much like the sample buffer and
buffer_size. We thought about recomputing the time
on DecodeAll, but since users may seek or decode small
chunks first, not all the data may be there. So this
is better done by the user. This may be good
information to document.
Currently, all the main codecs are implemented except
for QuickTime.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Sat, 08 May 2004 08:19:50 +0000 |
parents | 2d887640d300 |
children |
rev | line source |
---|---|
199 | 1 /* |
2 | |
3 TiMidity -- Experimental MIDI to WAVE converter | |
4 Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi> | |
5 | |
6 This program is free software; you can redistribute it and/or modify | |
7 it under the terms of the GNU General Public License as published by | |
8 the Free Software Foundation; either version 2 of the License, or | |
9 (at your option) any later version. | |
10 | |
11 This program is distributed in the hope that it will be useful, | |
12 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 GNU General Public License for more details. | |
15 | |
16 You should have received a copy of the GNU General Public License | |
17 along with this program; if not, write to the Free Software | |
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
19 | |
20 */ | |
21 | |
22 #if HAVE_CONFIG_H | |
23 # include <config.h> | |
24 #endif | |
25 | |
26 #include <stdio.h> | |
27 #include <stdlib.h> | |
28 #include <string.h> | |
29 | |
30 #include "SDL_sound.h" | |
31 | |
32 #define __SDL_SOUND_INTERNAL__ | |
33 #include "SDL_sound_internal.h" | |
34 | |
35 #include "timidity.h" | |
36 #include "common.h" | |
37 #include "instrum.h" | |
38 #include "playmidi.h" | |
39 | |
40 /* Computes how many (fractional) samples one MIDI delta-time unit contains */ | |
41 static void compute_sample_increment(MidiSong *song, Sint32 tempo, | |
42 Sint32 divisions) | |
43 { | |
44 double a; | |
45 a = (double) (tempo) * (double) (song->rate) * (65536.0/1000000.0) / | |
46 (double)(divisions); | |
47 | |
48 song->sample_correction = (Sint32)(a) & 0xFFFF; | |
49 song->sample_increment = (Sint32)(a) >> 16; | |
50 | |
51 SNDDBG(("Samples per delta-t: %d (correction %d)", | |
52 song->sample_increment, song->sample_correction)); | |
53 } | |
54 | |
55 /* Read variable-length number (7 bits per byte, MSB first) */ | |
56 static Sint32 getvl(SDL_RWops *rw) | |
57 { | |
58 Sint32 l=0; | |
59 Uint8 c; | |
60 for (;;) | |
61 { | |
62 SDL_RWread(rw, &c, 1, 1); | |
63 l += (c & 0x7f); | |
64 if (!(c & 0x80)) return l; | |
65 l<<=7; | |
66 } | |
67 } | |
68 | |
69 /* Print a string from the file, followed by a newline. Any non-ASCII | |
70 or unprintable characters will be converted to periods. */ | |
71 static int dumpstring(SDL_RWops *rw, Sint32 len, char *label) | |
72 { | |
73 signed char *s=safe_malloc(len+1); | |
74 if (len != (Sint32) SDL_RWread(rw, s, 1, len)) | |
75 { | |
76 free(s); | |
77 return -1; | |
78 } | |
79 s[len]='\0'; | |
80 while (len--) | |
81 { | |
82 if (s[len]<32) | |
83 s[len]='.'; | |
84 } | |
85 SNDDBG(("%s%s", label, s)); | |
86 free(s); | |
87 return 0; | |
88 } | |
89 | |
90 #define MIDIEVENT(at,t,ch,pa,pb) \ | |
91 new=safe_malloc(sizeof(MidiEventList)); \ | |
92 new->event.time=at; new->event.type=t; new->event.channel=ch; \ | |
93 new->event.a=pa; new->event.b=pb; new->next=0;\ | |
94 return new; | |
95 | |
96 #define MAGIC_EOT ((MidiEventList *)(-1)) | |
97 | |
98 /* Read a MIDI event, returning a freshly allocated element that can | |
99 be linked to the event list */ | |
100 static MidiEventList *read_midi_event(MidiSong *song) | |
101 { | |
102 static Uint8 laststatus, lastchan; | |
103 static Uint8 nrpn=0, rpn_msb[16], rpn_lsb[16]; /* one per channel */ | |
104 Uint8 me, type, a,b,c; | |
105 Sint32 len; | |
106 MidiEventList *new; | |
107 | |
108 for (;;) | |
109 { | |
110 song->at += getvl(song->rw); | |
111 if (SDL_RWread(song->rw, &me, 1, 1) != 1) | |
112 { | |
113 SNDDBG(("read_midi_event: SDL_RWread() failure\n")); | |
114 return 0; | |
115 } | |
116 | |
117 if(me==0xF0 || me == 0xF7) /* SysEx event */ | |
118 { | |
119 len=getvl(song->rw); | |
120 SDL_RWseek(song->rw, len, SEEK_CUR); | |
121 } | |
122 else if(me==0xFF) /* Meta event */ | |
123 { | |
124 SDL_RWread(song->rw, &type, 1, 1); | |
125 len=getvl(song->rw); | |
126 if (type>0 && type<16) | |
127 { | |
128 static char *label[]={ | |
129 "Text event: ", "Text: ", "Copyright: ", "Track name: ", | |
130 "Instrument: ", "Lyric: ", "Marker: ", "Cue point: "}; | |
131 dumpstring(song->rw, len, label[(type>7) ? 0 : type]); | |
132 } | |
133 else | |
134 switch(type) | |
135 { | |
136 case 0x2F: /* End of Track */ | |
137 return MAGIC_EOT; | |
138 | |
139 case 0x51: /* Tempo */ | |
140 SDL_RWread(song->rw, &a, 1, 1); | |
141 SDL_RWread(song->rw, &b, 1, 1); | |
142 SDL_RWread(song->rw, &c, 1, 1); | |
143 MIDIEVENT(song->at, ME_TEMPO, c, a, b); | |
144 | |
145 default: | |
146 SNDDBG(("(Meta event type 0x%02x, length %d)\n", type, len)); | |
147 SDL_RWseek(song->rw, len, SEEK_CUR); | |
148 break; | |
149 } | |
150 } | |
151 else | |
152 { | |
153 a=me; | |
154 if (a & 0x80) /* status byte */ | |
155 { | |
156 lastchan=a & 0x0F; | |
157 laststatus=(a>>4) & 0x07; | |
158 SDL_RWread(song->rw, &a, 1, 1); | |
159 a &= 0x7F; | |
160 } | |
161 switch(laststatus) | |
162 { | |
163 case 0: /* Note off */ | |
164 SDL_RWread(song->rw, &b, 1, 1); | |
165 b &= 0x7F; | |
166 MIDIEVENT(song->at, ME_NOTEOFF, lastchan, a,b); | |
167 | |
168 case 1: /* Note on */ | |
169 SDL_RWread(song->rw, &b, 1, 1); | |
170 b &= 0x7F; | |
171 MIDIEVENT(song->at, ME_NOTEON, lastchan, a,b); | |
172 | |
173 case 2: /* Key Pressure */ | |
174 SDL_RWread(song->rw, &b, 1, 1); | |
175 b &= 0x7F; | |
176 MIDIEVENT(song->at, ME_KEYPRESSURE, lastchan, a, b); | |
177 | |
178 case 3: /* Control change */ | |
179 SDL_RWread(song->rw, &b, 1, 1); | |
180 b &= 0x7F; | |
181 { | |
182 int control=255; | |
183 switch(a) | |
184 { | |
185 case 7: control=ME_MAINVOLUME; break; | |
186 case 10: control=ME_PAN; break; | |
187 case 11: control=ME_EXPRESSION; break; | |
188 case 64: control=ME_SUSTAIN; break; | |
189 case 120: control=ME_ALL_SOUNDS_OFF; break; | |
190 case 121: control=ME_RESET_CONTROLLERS; break; | |
191 case 123: control=ME_ALL_NOTES_OFF; break; | |
192 | |
193 /* These should be the SCC-1 tone bank switch | |
194 commands. I don't know why there are two, or | |
195 why the latter only allows switching to bank 0. | |
196 Also, some MIDI files use 0 as some sort of | |
197 continuous controller. This will cause lots of | |
198 warnings about undefined tone banks. */ | |
199 case 0: control=ME_TONE_BANK; break; | |
200 case 32: | |
201 if (b!=0) | |
202 SNDDBG(("(Strange: tone bank change 0x20%02x)\n", b)); | |
203 else | |
204 control=ME_TONE_BANK; | |
205 break; | |
206 | |
207 case 100: nrpn=0; rpn_msb[lastchan]=b; break; | |
208 case 101: nrpn=0; rpn_lsb[lastchan]=b; break; | |
209 case 99: nrpn=1; rpn_msb[lastchan]=b; break; | |
210 case 98: nrpn=1; rpn_lsb[lastchan]=b; break; | |
211 | |
212 case 6: | |
213 if (nrpn) | |
214 { | |
215 SNDDBG(("(Data entry (MSB) for NRPN %02x,%02x: %d)\n", | |
216 rpn_msb[lastchan], rpn_lsb[lastchan], b)); | |
217 break; | |
218 } | |
219 | |
220 switch((rpn_msb[lastchan]<<8) | rpn_lsb[lastchan]) | |
221 { | |
222 case 0x0000: /* Pitch bend sensitivity */ | |
223 control=ME_PITCH_SENS; | |
224 break; | |
225 | |
226 case 0x7F7F: /* RPN reset */ | |
227 /* reset pitch bend sensitivity to 2 */ | |
228 MIDIEVENT(song->at, ME_PITCH_SENS, lastchan, 2, 0); | |
229 | |
230 default: | |
231 SNDDBG(("(Data entry (MSB) for RPN %02x,%02x: %d)\n", | |
232 rpn_msb[lastchan], rpn_lsb[lastchan], b)); | |
233 break; | |
234 } | |
235 break; | |
236 | |
237 default: | |
238 SNDDBG(("(Control %d: %d)\n", a, b)); | |
239 break; | |
240 } | |
241 if (control != 255) | |
242 { | |
243 MIDIEVENT(song->at, control, lastchan, b, 0); | |
244 } | |
245 } | |
246 break; | |
247 | |
248 case 4: /* Program change */ | |
249 a &= 0x7f; | |
250 MIDIEVENT(song->at, ME_PROGRAM, lastchan, a, 0); | |
251 | |
252 case 5: /* Channel pressure - NOT IMPLEMENTED */ | |
253 break; | |
254 | |
255 case 6: /* Pitch wheel */ | |
256 SDL_RWread(song->rw, &b, 1, 1); | |
257 b &= 0x7F; | |
258 MIDIEVENT(song->at, ME_PITCHWHEEL, lastchan, a, b); | |
259 | |
260 default: | |
261 SNDDBG(("*** Can't happen: status 0x%02X, channel 0x%02X\n", | |
262 laststatus, lastchan)); | |
263 break; | |
264 } | |
265 } | |
266 } | |
267 | |
268 return new; | |
269 } | |
270 | |
271 #undef MIDIEVENT | |
272 | |
273 /* Read a midi track into the linked list, either merging with any previous | |
274 tracks or appending to them. */ | |
275 static int read_track(MidiSong *song, int append) | |
276 { | |
277 MidiEventList *meep; | |
278 MidiEventList *next, *new; | |
279 Sint32 len; | |
280 char tmp[4]; | |
281 | |
282 meep = song->evlist; | |
283 if (append && meep) | |
284 { | |
285 /* find the last event in the list */ | |
286 for (; meep->next; meep=meep->next) | |
287 ; | |
288 song->at = meep->event.time; | |
289 } | |
290 else | |
291 song->at=0; | |
292 | |
293 /* Check the formalities */ | |
294 | |
295 if (SDL_RWread(song->rw, tmp, 1, 4) != 4 || SDL_RWread(song->rw, &len, 4, 1) != 1) | |
296 { | |
297 SNDDBG(("Can't read track header.\n")); | |
298 return -1; | |
299 } | |
300 len=SDL_SwapBE32(len); | |
301 if (memcmp(tmp, "MTrk", 4)) | |
302 { | |
303 SNDDBG(("Corrupt MIDI file.\n")); | |
304 return -2; | |
305 } | |
306 | |
307 for (;;) | |
308 { | |
309 if (!(new=read_midi_event(song))) /* Some kind of error */ | |
310 return -2; | |
311 | |
312 if (new==MAGIC_EOT) /* End-of-track Hack. */ | |
313 { | |
314 return 0; | |
315 } | |
316 | |
317 next=meep->next; | |
318 while (next && (next->event.time < new->event.time)) | |
319 { | |
320 meep=next; | |
321 next=meep->next; | |
322 } | |
323 | |
324 new->next=next; | |
325 meep->next=new; | |
326 | |
327 song->event_count++; /* Count the event. (About one?) */ | |
328 meep=new; | |
329 } | |
330 } | |
331 | |
332 /* Free the linked event list from memory. */ | |
333 static void free_midi_list(MidiSong *song) | |
334 { | |
335 MidiEventList *meep, *next; | |
336 if (!(meep = song->evlist)) return; | |
337 while (meep) | |
338 { | |
339 next=meep->next; | |
340 free(meep); | |
341 meep=next; | |
342 } | |
343 song->evlist=0; | |
344 } | |
345 | |
346 /* Allocate an array of MidiEvents and fill it from the linked list of | |
347 events, marking used instruments for loading. Convert event times to | |
348 samples: handle tempo changes. Strip unnecessary events from the list. | |
349 Free the linked list. */ | |
350 static MidiEvent *groom_list(MidiSong *song, Sint32 divisions,Sint32 *eventsp, | |
351 Sint32 *samplesp) | |
352 { | |
353 MidiEvent *groomed_list, *lp; | |
354 MidiEventList *meep; | |
355 Sint32 i, our_event_count, tempo, skip_this_event, new_value; | |
356 Sint32 sample_cum, samples_to_do, at, st, dt, counting_time; | |
357 | |
358 int current_bank[16], current_set[16], current_program[16]; | |
359 /* Or should each bank have its own current program? */ | |
360 | |
361 for (i=0; i<16; i++) | |
362 { | |
363 current_bank[i]=0; | |
364 current_set[i]=0; | |
365 current_program[i]=song->default_program; | |
366 } | |
367 | |
368 tempo=500000; | |
369 compute_sample_increment(song, tempo, divisions); | |
370 | |
371 /* This may allocate a bit more than we need */ | |
372 groomed_list=lp=safe_malloc(sizeof(MidiEvent) * (song->event_count+1)); | |
373 meep=song->evlist; | |
374 | |
375 our_event_count=0; | |
376 st=at=sample_cum=0; | |
377 counting_time=2; /* We strip any silence before the first NOTE ON. */ | |
378 | |
379 for (i = 0; i < song->event_count; i++) | |
380 { | |
381 skip_this_event=0; | |
382 | |
383 if (meep->event.type==ME_TEMPO) | |
384 { | |
385 tempo= | |
386 meep->event.channel + meep->event.b * 256 + meep->event.a * 65536; | |
387 compute_sample_increment(song, tempo, divisions); | |
388 skip_this_event=1; | |
389 } | |
390 else switch (meep->event.type) | |
391 { | |
392 case ME_PROGRAM: | |
393 if (ISDRUMCHANNEL(song, meep->event.channel)) | |
394 { | |
395 if (song->drumset[meep->event.a]) /* Is this a defined drumset? */ | |
396 new_value=meep->event.a; | |
397 else | |
398 { | |
399 SNDDBG(("Drum set %d is undefined\n", meep->event.a)); | |
400 new_value=meep->event.a=0; | |
401 } | |
402 if (current_set[meep->event.channel] != new_value) | |
403 current_set[meep->event.channel]=new_value; | |
404 else | |
405 skip_this_event=1; | |
406 } | |
407 else | |
408 { | |
409 new_value=meep->event.a; | |
410 if ((current_program[meep->event.channel] != SPECIAL_PROGRAM) | |
411 && (current_program[meep->event.channel] != new_value)) | |
412 current_program[meep->event.channel] = new_value; | |
413 else | |
414 skip_this_event=1; | |
415 } | |
416 break; | |
417 | |
418 case ME_NOTEON: | |
419 if (counting_time) | |
420 counting_time=1; | |
421 if (ISDRUMCHANNEL(song, meep->event.channel)) | |
422 { | |
423 /* Mark this instrument to be loaded */ | |
424 if (!(song->drumset[current_set[meep->event.channel]] | |
425 ->instrument[meep->event.a])) | |
426 song->drumset[current_set[meep->event.channel]] | |
427 ->instrument[meep->event.a] = MAGIC_LOAD_INSTRUMENT; | |
428 } | |
429 else | |
430 { | |
431 if (current_program[meep->event.channel]==SPECIAL_PROGRAM) | |
432 break; | |
433 /* Mark this instrument to be loaded */ | |
434 if (!(song->tonebank[current_bank[meep->event.channel]] | |
435 ->instrument[current_program[meep->event.channel]])) | |
436 song->tonebank[current_bank[meep->event.channel]] | |
437 ->instrument[current_program[meep->event.channel]] = | |
438 MAGIC_LOAD_INSTRUMENT; | |
439 } | |
440 break; | |
441 | |
442 case ME_TONE_BANK: | |
443 if (ISDRUMCHANNEL(song, meep->event.channel)) | |
444 { | |
445 skip_this_event=1; | |
446 break; | |
447 } | |
448 if (song->tonebank[meep->event.a]) /* Is this a defined tone bank? */ | |
449 new_value=meep->event.a; | |
450 else | |
451 { | |
452 SNDDBG(("Tone bank %d is undefined\n", meep->event.a)); | |
453 new_value=meep->event.a=0; | |
454 } | |
455 if (current_bank[meep->event.channel]!=new_value) | |
456 current_bank[meep->event.channel]=new_value; | |
457 else | |
458 skip_this_event=1; | |
459 break; | |
460 } | |
461 | |
462 /* Recompute time in samples*/ | |
463 if ((dt=meep->event.time - at) && !counting_time) | |
464 { | |
465 samples_to_do = song->sample_increment * dt; | |
466 sample_cum += song->sample_correction * dt; | |
467 if (sample_cum & 0xFFFF0000) | |
468 { | |
469 samples_to_do += ((sample_cum >> 16) & 0xFFFF); | |
470 sample_cum &= 0x0000FFFF; | |
471 } | |
472 st += samples_to_do; | |
473 } | |
474 else if (counting_time==1) counting_time=0; | |
475 if (!skip_this_event) | |
476 { | |
477 /* Add the event to the list */ | |
478 *lp=meep->event; | |
479 lp->time=st; | |
480 lp++; | |
481 our_event_count++; | |
482 } | |
483 at=meep->event.time; | |
484 meep=meep->next; | |
485 } | |
486 /* Add an End-of-Track event */ | |
487 lp->time=st; | |
488 lp->type=ME_EOT; | |
489 our_event_count++; | |
490 free_midi_list(song); | |
474
c66080364dff
Most decoders now report total sample play time, now. Technically, this
Ryan C. Gordon <icculus@icculus.org>
parents:
199
diff
changeset
|
491 |
199 | 492 *eventsp=our_event_count; |
493 *samplesp=st; | |
494 return groomed_list; | |
495 } | |
496 | |
497 MidiEvent *read_midi_file(MidiSong *song, Sint32 *count, Sint32 *sp) | |
498 { | |
499 Sint32 len, divisions; | |
500 Sint16 format, tracks, divisions_tmp; | |
501 int i; | |
502 char tmp[4]; | |
503 | |
504 song->event_count=0; | |
505 song->at=0; | |
506 song->evlist=0; | |
507 | |
508 if (SDL_RWread(song->rw, tmp, 1, 4) != 4 || SDL_RWread(song->rw, &len, 4, 1) != 1) | |
509 { | |
510 SNDDBG(("Not a MIDI file!\n")); | |
511 return 0; | |
512 } | |
513 len=SDL_SwapBE32(len); | |
514 if (memcmp(tmp, "MThd", 4) || len < 6) | |
515 { | |
516 SNDDBG(("Not a MIDI file!\n")); | |
517 return 0; | |
518 } | |
519 | |
520 SDL_RWread(song->rw, &format, 2, 1); | |
521 SDL_RWread(song->rw, &tracks, 2, 1); | |
522 SDL_RWread(song->rw, &divisions_tmp, 2, 1); | |
523 format=SDL_SwapBE16(format); | |
524 tracks=SDL_SwapBE16(tracks); | |
525 divisions_tmp=SDL_SwapBE16(divisions_tmp); | |
526 | |
527 if (divisions_tmp<0) | |
528 { | |
529 /* SMPTE time -- totally untested. Got a MIDI file that uses this? */ | |
530 divisions= | |
531 (Sint32)(-(divisions_tmp/256)) * (Sint32)(divisions_tmp & 0xFF); | |
532 } | |
533 else divisions=(Sint32)(divisions_tmp); | |
534 | |
535 if (len > 6) | |
536 { | |
537 SNDDBG(("MIDI file header size %u bytes", len)); | |
538 SDL_RWseek(song->rw, len-6, SEEK_CUR); /* skip the excess */ | |
539 } | |
540 if (format<0 || format >2) | |
541 { | |
542 SNDDBG(("Unknown MIDI file format %d\n", format)); | |
543 return 0; | |
544 } | |
545 SNDDBG(("Format: %d Tracks: %d Divisions: %d\n", | |
546 format, tracks, divisions)); | |
547 | |
548 /* Put a do-nothing event first in the list for easier processing */ | |
549 song->evlist=safe_malloc(sizeof(MidiEventList)); | |
550 song->evlist->event.time=0; | |
551 song->evlist->event.type=ME_NONE; | |
552 song->evlist->next=0; | |
553 song->event_count++; | |
554 | |
555 switch(format) | |
556 { | |
557 case 0: | |
558 if (read_track(song, 0)) | |
559 { | |
560 free_midi_list(song); | |
561 return 0; | |
562 } | |
563 break; | |
564 | |
565 case 1: | |
566 for (i=0; i<tracks; i++) | |
567 if (read_track(song, 0)) | |
568 { | |
569 free_midi_list(song); | |
570 return 0; | |
571 } | |
572 break; | |
573 | |
574 case 2: /* We simply play the tracks sequentially */ | |
575 for (i=0; i<tracks; i++) | |
576 if (read_track(song, 1)) | |
577 { | |
578 free_midi_list(song); | |
579 return 0; | |
580 } | |
581 break; | |
582 } | |
583 return groom_list(song, divisions, count, sp); | |
584 } |