1 //
2 // Copyright(C) 1993-1996 Id Software, Inc.
3 // Copyright(C) 2005-2014 Simon Howard
4 // Copyright(C) 2006 Ben Ryves 2006
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (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 // mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com
17 // Use to convert a MUS file into a single track, type 0 MIDI file.
18
19 #include <stdio.h>
20
21 #include "doomtype.h"
22 #include "i_swap.h"
23
24 #include "memio.h"
25 #include "mus2mid.h"
26
27 #define NUM_CHANNELS 16
28
29 #define MIDI_PERCUSSION_CHAN 9
30 #define MUS_PERCUSSION_CHAN 15
31
32 // MUS event codes
33 typedef enum
34 {
35 mus_releasekey = 0x00,
36 mus_presskey = 0x10,
37 mus_pitchwheel = 0x20,
38 mus_systemevent = 0x30,
39 mus_changecontroller = 0x40,
40 mus_scoreend = 0x60
41 } musevent;
42
43 // MIDI event codes
44 typedef enum
45 {
46 midi_releasekey = 0x80,
47 midi_presskey = 0x90,
48 midi_aftertouchkey = 0xA0,
49 midi_changecontroller = 0xB0,
50 midi_changepatch = 0xC0,
51 midi_aftertouchchannel = 0xD0,
52 midi_pitchwheel = 0xE0
53 } midievent;
54
55 // Structure to hold MUS file header
56 typedef PACKED_STRUCT (
57 {
58 byte id[4];
59 unsigned short scorelength;
60 unsigned short scorestart;
61 unsigned short primarychannels;
62 unsigned short secondarychannels;
63 unsigned short instrumentcount;
64 }) musheader;
65
66 // Standard MIDI type 0 header + track header
67 static const byte midiheader[] =
68 {
69 'M', 'T', 'h', 'd', // Main header
70 0x00, 0x00, 0x00, 0x06, // Header size
71 0x00, 0x00, // MIDI type (0)
72 0x00, 0x01, // Number of tracks
73 0x00, 0x46, // Resolution
74 'M', 'T', 'r', 'k', // Start of track
75 0x00, 0x00, 0x00, 0x00 // Placeholder for track length
76 };
77
78 // Cached channel velocities
79 static byte channelvelocities[] =
80 {
81 127, 127, 127, 127, 127, 127, 127, 127,
82 127, 127, 127, 127, 127, 127, 127, 127
83 };
84
85 // Timestamps between sequences of MUS events
86
87 static unsigned int queuedtime = 0;
88
89 // Counter for the length of the track
90
91 static unsigned int tracksize;
92
93 static const byte controller_map[] =
94 {
95 0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D,
96 0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79
97 };
98
99 static int channel_map[NUM_CHANNELS];
100
101 // Write timestamp to a MIDI file.
102
WriteTime(unsigned int time,MEMFILE * midioutput)103 static boolean WriteTime(unsigned int time, MEMFILE *midioutput)
104 {
105 unsigned int buffer = time & 0x7F;
106 byte writeval;
107
108 while ((time >>= 7) != 0)
109 {
110 buffer <<= 8;
111 buffer |= ((time & 0x7F) | 0x80);
112 }
113
114 for (;;)
115 {
116 writeval = (byte)(buffer & 0xFF);
117
118 if (mem_fwrite(&writeval, 1, 1, midioutput) != 1)
119 {
120 return true;
121 }
122
123 ++tracksize;
124
125 if ((buffer & 0x80) != 0)
126 {
127 buffer >>= 8;
128 }
129 else
130 {
131 queuedtime = 0;
132 return false;
133 }
134 }
135 }
136
137
138 // Write the end of track marker
WriteEndTrack(MEMFILE * midioutput)139 static boolean WriteEndTrack(MEMFILE *midioutput)
140 {
141 byte endtrack[] = {0xFF, 0x2F, 0x00};
142
143 if (WriteTime(queuedtime, midioutput))
144 {
145 return true;
146 }
147
148 if (mem_fwrite(endtrack, 1, 3, midioutput) != 3)
149 {
150 return true;
151 }
152
153 tracksize += 3;
154 return false;
155 }
156
157 // Write a key press event
WritePressKey(byte channel,byte key,byte velocity,MEMFILE * midioutput)158 static boolean WritePressKey(byte channel, byte key,
159 byte velocity, MEMFILE *midioutput)
160 {
161 byte working = midi_presskey | channel;
162
163 if (WriteTime(queuedtime, midioutput))
164 {
165 return true;
166 }
167
168 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
169 {
170 return true;
171 }
172
173 working = key & 0x7F;
174
175 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
176 {
177 return true;
178 }
179
180 working = velocity & 0x7F;
181
182 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
183 {
184 return true;
185 }
186
187 tracksize += 3;
188
189 return false;
190 }
191
192 // Write a key release event
WriteReleaseKey(byte channel,byte key,MEMFILE * midioutput)193 static boolean WriteReleaseKey(byte channel, byte key,
194 MEMFILE *midioutput)
195 {
196 byte working = midi_releasekey | channel;
197
198 if (WriteTime(queuedtime, midioutput))
199 {
200 return true;
201 }
202
203 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
204 {
205 return true;
206 }
207
208 working = key & 0x7F;
209
210 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
211 {
212 return true;
213 }
214
215 working = 0;
216
217 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
218 {
219 return true;
220 }
221
222 tracksize += 3;
223
224 return false;
225 }
226
227 // Write a pitch wheel/bend event
WritePitchWheel(byte channel,short wheel,MEMFILE * midioutput)228 static boolean WritePitchWheel(byte channel, short wheel,
229 MEMFILE *midioutput)
230 {
231 byte working = midi_pitchwheel | channel;
232
233 if (WriteTime(queuedtime, midioutput))
234 {
235 return true;
236 }
237
238 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
239 {
240 return true;
241 }
242
243 working = wheel & 0x7F;
244
245 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
246 {
247 return true;
248 }
249
250 working = (wheel >> 7) & 0x7F;
251
252 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
253 {
254 return true;
255 }
256
257 tracksize += 3;
258 return false;
259 }
260
261 // Write a patch change event
WriteChangePatch(byte channel,byte patch,MEMFILE * midioutput)262 static boolean WriteChangePatch(byte channel, byte patch,
263 MEMFILE *midioutput)
264 {
265 byte working = midi_changepatch | channel;
266
267 if (WriteTime(queuedtime, midioutput))
268 {
269 return true;
270 }
271
272 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
273 {
274 return true;
275 }
276
277 working = patch & 0x7F;
278
279 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
280 {
281 return true;
282 }
283
284 tracksize += 2;
285
286 return false;
287 }
288
289 // Write a valued controller change event
290
WriteChangeController_Valued(byte channel,byte control,byte value,MEMFILE * midioutput)291 static boolean WriteChangeController_Valued(byte channel,
292 byte control,
293 byte value,
294 MEMFILE *midioutput)
295 {
296 byte working = midi_changecontroller | channel;
297
298 if (WriteTime(queuedtime, midioutput))
299 {
300 return true;
301 }
302
303 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
304 {
305 return true;
306 }
307
308 working = control & 0x7F;
309
310 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
311 {
312 return true;
313 }
314
315 // Quirk in vanilla DOOM? MUS controller values should be
316 // 7-bit, not 8-bit.
317
318 working = value;// & 0x7F;
319
320 // Fix on said quirk to stop MIDI players from complaining that
321 // the value is out of range:
322
323 if (working & 0x80)
324 {
325 working = 0x7F;
326 }
327
328 if (mem_fwrite(&working, 1, 1, midioutput) != 1)
329 {
330 return true;
331 }
332
333 tracksize += 3;
334
335 return false;
336 }
337
338 // Write a valueless controller change event
WriteChangeController_Valueless(byte channel,byte control,MEMFILE * midioutput)339 static boolean WriteChangeController_Valueless(byte channel,
340 byte control,
341 MEMFILE *midioutput)
342 {
343 return WriteChangeController_Valued(channel, control, 0,
344 midioutput);
345 }
346
347 // Allocate a free MIDI channel.
348
AllocateMIDIChannel(void)349 static int AllocateMIDIChannel(void)
350 {
351 int result;
352 int max;
353 int i;
354
355 // Find the current highest-allocated channel.
356
357 max = -1;
358
359 for (i=0; i<NUM_CHANNELS; ++i)
360 {
361 if (channel_map[i] > max)
362 {
363 max = channel_map[i];
364 }
365 }
366
367 // max is now equal to the highest-allocated MIDI channel. We can
368 // now allocate the next available channel. This also works if
369 // no channels are currently allocated (max=-1)
370
371 result = max + 1;
372
373 // Don't allocate the MIDI percussion channel!
374
375 if (result == MIDI_PERCUSSION_CHAN)
376 {
377 ++result;
378 }
379
380 return result;
381 }
382
383 // Given a MUS channel number, get the MIDI channel number to use
384 // in the outputted file.
385
GetMIDIChannel(int mus_channel,MEMFILE * midioutput)386 static int GetMIDIChannel(int mus_channel, MEMFILE *midioutput)
387 {
388 // Find the MIDI channel to use for this MUS channel.
389 // MUS channel 15 is the percusssion channel.
390
391 if (mus_channel == MUS_PERCUSSION_CHAN)
392 {
393 return MIDI_PERCUSSION_CHAN;
394 }
395 else
396 {
397 // If a MIDI channel hasn't been allocated for this MUS channel
398 // yet, allocate the next free MIDI channel.
399
400 if (channel_map[mus_channel] == -1)
401 {
402 channel_map[mus_channel] = AllocateMIDIChannel();
403
404 // First time using the channel, send an "all notes off"
405 // event. This fixes "The D_DDTBLU disease" described here:
406 // https://www.doomworld.com/vb/source-ports/66802-the
407 WriteChangeController_Valueless(channel_map[mus_channel], 0x7b,
408 midioutput);
409 }
410
411 return channel_map[mus_channel];
412 }
413 }
414
ReadMusHeader(MEMFILE * file,musheader * header)415 static boolean ReadMusHeader(MEMFILE *file, musheader *header)
416 {
417 boolean result;
418
419 result = mem_fread(&header->id, sizeof(byte), 4, file) == 4
420 && mem_fread(&header->scorelength, sizeof(short), 1, file) == 1
421 && mem_fread(&header->scorestart, sizeof(short), 1, file) == 1
422 && mem_fread(&header->primarychannels, sizeof(short), 1, file) == 1
423 && mem_fread(&header->secondarychannels, sizeof(short), 1, file) == 1
424 && mem_fread(&header->instrumentcount, sizeof(short), 1, file) == 1;
425
426 if (result)
427 {
428 header->scorelength = SHORT(header->scorelength);
429 header->scorestart = SHORT(header->scorestart);
430 header->primarychannels = SHORT(header->primarychannels);
431 header->secondarychannels = SHORT(header->secondarychannels);
432 header->instrumentcount = SHORT(header->instrumentcount);
433 }
434
435 return result;
436 }
437
438
439 // Read a MUS file from a stream (musinput) and output a MIDI file to
440 // a stream (midioutput).
441 //
442 // Returns 0 on success or 1 on failure.
443
mus2mid(MEMFILE * musinput,MEMFILE * midioutput)444 boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput)
445 {
446 // Header for the MUS file
447 musheader musfileheader;
448
449 // Descriptor for the current MUS event
450 byte eventdescriptor;
451 int channel; // Channel number
452 musevent event;
453
454
455 // Bunch of vars read from MUS lump
456 byte key;
457 byte controllernumber;
458 byte controllervalue;
459
460 // Buffer used for MIDI track size record
461 byte tracksizebuffer[4];
462
463 // Flag for when the score end marker is hit.
464 int hitscoreend = 0;
465
466 // Temp working byte
467 byte working;
468 // Used in building up time delays
469 unsigned int timedelay;
470
471 // Initialise channel map to mark all channels as unused.
472
473 for (channel=0; channel<NUM_CHANNELS; ++channel)
474 {
475 channel_map[channel] = -1;
476 }
477
478 // Grab the header
479
480 if (!ReadMusHeader(musinput, &musfileheader))
481 {
482 return true;
483 }
484
485 // [crispy] enable MUS format header check
486 #define CHECK_MUS_HEADER
487 #ifdef CHECK_MUS_HEADER
488 // Check MUS header
489 if (musfileheader.id[0] != 'M'
490 || musfileheader.id[1] != 'U'
491 || musfileheader.id[2] != 'S'
492 || musfileheader.id[3] != 0x1A)
493 {
494 return true;
495 }
496 #endif
497
498 // Seek to where the data is held
499 if (mem_fseek(musinput, (long)musfileheader.scorestart,
500 MEM_SEEK_SET) != 0)
501 {
502 return true;
503 }
504
505 // So, we can assume the MUS file is faintly legit. Let's start
506 // writing MIDI data...
507
508 mem_fwrite(midiheader, 1, sizeof(midiheader), midioutput);
509 tracksize = 0;
510
511 // Now, process the MUS file:
512 while (!hitscoreend)
513 {
514 // Handle a block of events:
515
516 while (!hitscoreend)
517 {
518 // Fetch channel number and event code:
519
520 if (mem_fread(&eventdescriptor, 1, 1, musinput) != 1)
521 {
522 return true;
523 }
524
525 channel = GetMIDIChannel(eventdescriptor & 0x0F, midioutput);
526 event = eventdescriptor & 0x70;
527
528 switch (event)
529 {
530 case mus_releasekey:
531 if (mem_fread(&key, 1, 1, musinput) != 1)
532 {
533 return true;
534 }
535
536 if (WriteReleaseKey(channel, key, midioutput))
537 {
538 return true;
539 }
540
541 break;
542
543 case mus_presskey:
544 if (mem_fread(&key, 1, 1, musinput) != 1)
545 {
546 return true;
547 }
548
549 if (key & 0x80)
550 {
551 if (mem_fread(&channelvelocities[channel], 1, 1, musinput) != 1)
552 {
553 return true;
554 }
555
556 channelvelocities[channel] &= 0x7F;
557 }
558
559 if (WritePressKey(channel, key,
560 channelvelocities[channel], midioutput))
561 {
562 return true;
563 }
564
565 break;
566
567 case mus_pitchwheel:
568 if (mem_fread(&key, 1, 1, musinput) != 1)
569 {
570 break;
571 }
572 if (WritePitchWheel(channel, (short)(key * 64), midioutput))
573 {
574 return true;
575 }
576
577 break;
578
579 case mus_systemevent:
580 if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
581 {
582 return true;
583 }
584 if (controllernumber < 10 || controllernumber > 14)
585 {
586 return true;
587 }
588
589 if (WriteChangeController_Valueless(channel,
590 controller_map[controllernumber],
591 midioutput))
592 {
593 return true;
594 }
595
596 break;
597
598 case mus_changecontroller:
599 if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
600 {
601 return true;
602 }
603
604 if (mem_fread(&controllervalue, 1, 1, musinput) != 1)
605 {
606 return true;
607 }
608
609 if (controllernumber == 0)
610 {
611 if (WriteChangePatch(channel, controllervalue,
612 midioutput))
613 {
614 return true;
615 }
616 }
617 else
618 {
619 if (controllernumber < 1 || controllernumber > 9)
620 {
621 return true;
622 }
623
624 if (WriteChangeController_Valued(channel,
625 controller_map[controllernumber],
626 controllervalue,
627 midioutput))
628 {
629 return true;
630 }
631 }
632
633 break;
634
635 case mus_scoreend:
636 hitscoreend = 1;
637 break;
638
639 default:
640 return true;
641 break;
642 }
643
644 if (eventdescriptor & 0x80)
645 {
646 break;
647 }
648 }
649 // Now we need to read the time code:
650 if (!hitscoreend)
651 {
652 timedelay = 0;
653 for (;;)
654 {
655 if (mem_fread(&working, 1, 1, musinput) != 1)
656 {
657 return true;
658 }
659
660 timedelay = timedelay * 128 + (working & 0x7F);
661 if ((working & 0x80) == 0)
662 {
663 break;
664 }
665 }
666 queuedtime += timedelay;
667 }
668 }
669
670 // End of track
671 if (WriteEndTrack(midioutput))
672 {
673 return true;
674 }
675
676 // Write the track size into the stream
677 if (mem_fseek(midioutput, 18, MEM_SEEK_SET))
678 {
679 return true;
680 }
681
682 tracksizebuffer[0] = (tracksize >> 24) & 0xff;
683 tracksizebuffer[1] = (tracksize >> 16) & 0xff;
684 tracksizebuffer[2] = (tracksize >> 8) & 0xff;
685 tracksizebuffer[3] = tracksize & 0xff;
686
687 if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4)
688 {
689 return true;
690 }
691
692 return false;
693 }
694
695 #ifdef STANDALONE
696
697 #include "m_misc.h"
698 #include "z_zone.h"
699
main(int argc,char * argv[])700 int main(int argc, char *argv[])
701 {
702 MEMFILE *src, *dst;
703 byte *infile;
704 long infile_len;
705 void *outfile;
706 size_t outfile_len;
707
708 if (argc != 3)
709 {
710 printf("Usage: %s <musfile> <midfile>\n", argv[0]);
711 exit(-1);
712 }
713
714 Z_Init();
715
716 infile_len = M_ReadFile(argv[1], &infile);
717
718 src = mem_fopen_read(infile, infile_len);
719 dst = mem_fopen_write();
720
721 if (mus2mid(src, dst))
722 {
723 fprintf(stderr, "mus2mid() failed\n");
724 exit(-1);
725 }
726
727 // Write result to output file:
728
729 mem_get_buf(dst, &outfile, &outfile_len);
730
731 M_WriteFile(argv[2], outfile, outfile_len);
732
733 return 0;
734 }
735
736 #endif
737
738