forked from LenShustek/miditones
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmidi2tones.c
1664 lines (1530 loc) · 65.8 KB
/
midi2tones.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*********************************************************************************************
*
* MIDI2TONES: Convert a MIDI file into a simple bytestream of notes
*
* This is a fork of MIDITONES as it stood on September 27, 2016
* Copyright (c) 2011,2013,2015,2016, Len Shustek
* https://github.com/LenShustek/miditones
*
* The purpose of the fork was to add an alternate output format.
*
* MIDI2TONES converts a MIDI music file into a much simplified stream of commands, so that
* the music can easily be played on a small microcontroller-based synthesizer that has
* only simple tone generators. This is on GitHub at
* https://github.com/MLXXXp/midi2tones
*
* This was written for the "Playtune" series of Arduino and Teensy microcontroller
* synthesizers. See the separate documentation for the various Playtune players at
* https://github.com/LenShustek/arduino-playtune
* https://github.com/LenShustek/ATtiny-playtune
* https://github.com/LenShustek/Playtune_poll
* https://github.com/LenShustek/Playtune_samp
* and also the ArduboyPlaytune library derived from arduino-playtune
* https://github.com/Arduboy/ArduboyPlaytune
*
* MIDI2TONES may also prove useful for other simple music synthesizers.
*
* Volume ("velocity") and instrument information in the MIDI file can either be
* discarded or kept. All the tracks are processed and merged into a single time-ordered
* stream of "note on", "note off", "change instrument" and "delay" commands.
*
* An alternate output format can be specified, which consists of a single monotonic
* stream of frequency/duration pairs of 16 bit values. The specified frequency can
* also include a flag to indicate that the note is to be played at a higher volume,
* if the velocity of the MIDI note is above a certain value.
* This format is suitable for use with the ArduboyTones library, which is on GitHub at
* https://github.com/MLXXXp/ArduboyTones
*
* The output can be either a C-language source code fragment that initializes an
* array with the command bytestream, or a binary file with the bytestream itself.
*
* MIDI2TONES is written in standard ANSI C and is meant to be executed from the
* command line. There is no GUI interface.
*
* The MIDI file format is complicated, and this has not been tested on all of its
* variations. In particular we have tested only format type "1", which seems
* to be what most of them are. Let me know if you find MIDI files that it
* won't digest and I'll see if I can fix it.
*
* There is a companion program in the same repository called Miditones_scroll that
* can convert the Playtune bytestream generated by MIDI2TONES into a piano-player
* like listing for debugging or annotation. See the documentation in the
* beginning of its source code.
*
*
* ***** The MIDI2TONES command line *****
*
* To convert a MIDI file called "chopin.mid" into a command bytestream, execute
*
* midi2tones chopin
*
* It will create a file in the same directory called "chopin.c" which contains
* the C-language statement to intiialize an array called "score" with the bytestream.
*
*
* The general form for command line execution is this:
*
* midi2tones <options> <basefilename>
*
* Options must be specified individually, each with its own "-" lead-in, and separated
* with spaces. A forward slash "/" can be used instead of a dash "-" for option lead-ins.
*
* The <basefilename> is the base name, without an extension, for the input and
* output files. It can contain directory path information, or not.
*
* The input file is <basefilename>.mid The output filename(s)
* are the base file name with .c, .bin, and/or .log extensions.
*
*
* The following commonly-used command-line options can be specified:
*
* -on Generate output format "n".
* Two formats are available:
* 1: The Playtune format (which is the default if this option isn't given).
* 2: The frequency/duration pair format, as used by ArduboyTones.
*
* -v Add velocity (volume) information to the output bytestream.
*
* -vn For the alternate format, "n" specifies the minimum velocity value that will
* produce a high volume tone. Without this option all tones will be
* normal volume.
*
* -i Add instrument change commands to the output bytestream.
*
* -pt Translate notes in the MIDI percussion track to note numbers 128..255
* and assign them to a tone generator as usual.
*
* -d Generate a self-describing file header that says which optional bytestream
* fields are present. This is highly recommended if you are using later
* Playtune players that can check the header to know what data to expect.
*
* -b Generate a binary file with the name <basefilename>.bin, instead of a
* C-language source file with the name <basefilename>.c.
*
* -tn Generate the bytestream so that at most "n" tone generators are used.
* The default is 6 tone generators, and the maximum is 16. The program
* will report how many notes had to be discarded because there weren't
* enough tone generators.
*
*
* The following are lesser-used command-line options:
*
* -p Only parse the MIDI file, and don't generate an output file.
* Tracks are processed sequentially instead of being merged into chronological order.
* This is mostly useful for debugging MIDI file parsing problems.
*
* -lp Log input file parsing information to the <basefilename>.log file.
*
* -lg Log output bytestream generation information to the <basefilename>.log file.
*
* -nx Put about "x" items on each line of the C file output.
*
* -sn Use bytestream generation strategy "n".
* Two strategies are currently implemented:
* 1: Favor track 1 notes instead of all tracks equally.
* 2: Try to keep each track to its own tone generator.
*
* -cn Only process the channel numbers whose bits are on in the number "n".
* For example, -c3 means "only process channels 0 and 1". In addition to decimal,
* "n" can be also specified in hex using a 0x prefix or octal with a 0 prefix.
* For the alternate output format, only the lowest bit will be used to specify
* the single channel to be processed, and without this option channel 0 will
* be used.
*
* -kn Change the musical key of the output by n chromatic notes.
* -k-12 goes one octave down, -k12 goes one octave up, etc.
*
* -pi Ignore notes in the MIDI percussion track 9 (also called 10 by some).
*
* -dp Generate IDE-dependent C code to define PROGMEM.
*
* -fx For the alternate output format, instead of using defined note names,
* output actual frequency values in decimal format depending on "x":
* -fa: For high volume notes use format "<freq>+TONE_HIGH_VOLUME".
* -fb: For high volume notes just add 0x8000 to the frequency value.
*
* -r Terminate the output file with a "restart" command instead of a "stop" command.
*
* -h Give command-line help.
*
*
* ***** The score bytestream *****
*
* The generated bytestream is a series of commands that turn notes on and off,
* maybe change instruments, and begin delays until the next note change.
* Here are the details, with numbers shown in hexadecimal.
*
* If the high-order bit of the byte is 1, then it is one of the following commands:
*
* 9t nn [vv]
* Start playing note nn on tone generator t, replacing any previous note.
* Generators are numbered starting with 0. The note numbers are the MIDI
* numbers for the chromatic scale, with decimal 69 being Middle A (440 Hz).
* If the -v option was given, a second byte is added to indicate note volume.
*
* 8t Stop playing the note on tone generator t.
*
* Ct ii Change tone generator t to play instrument ii from now on. This will only
* be generated if the -i option was given.
*
* F0 End of score; stop playing.
*
* E0 End of score; start playing again from the beginning. Will be generated if
* the -r option was given.
*
* If the high-order bit of the byte is 0, it is a command to delay for a while until
* the next note change. The other 7 bits and the 8 bits of the following byte are
* interpreted as a 15-bit big-endian integer that is the number of milliseconds to
* wait before processing the next command. For example,
*
* 07 D0
*
* would cause a delay of 0x07d0 = 2000 decimal millisconds, or 2 seconds. Any tones
* that were playing before the delay command will continue to play.
*
* If the -d option is specified, the bytestream begins with a little header that tells
* what optional information will be in the data. This makes the file more self-describing,
* and allows music players to adapt to different kinds of files. The later Playtune
* players do that. The header looks like this:
*
* 'Pt' Two ascii characters that signal the presence of the header
* nn The length (in one byte) of the entire header, 6..255
* ff1 A byte of flag bits, three of which are currently defined:
* 80 velocity information is present
* 40 instrument change information is present
* 20 translated percussion notes are present
* ff2 Another byte of flags, currently undefined
* tt The number (in one byte) of tone generators actually used in this music.
*
* Any subsequent header bytes covered by the count, if present, are currently undefined
* and should be ignored by players.
*
* ***** The alternate frequency/duration pair output format *****
*
* The generated stream is a series of frequency/duration value pairs. The frequency
* is in Hz and the duration is in milliseconds. Each value is 16 bits. For a binary
* file the values are stored high byte first. The ArduboyTones player supports
* frequencies from 16 Hz to 32767 Hz but MIDI2TONES converts MIDI note numbers in the
* range from note 12 (16.352 Hz rounded to 16 Hz) to note 127 (12543.9 Hz rounded
* to 12544 Hz).
*
* Periods of silence are represented by a frequency/duration pair with a frequency
* value of 0.
*
* Since the output is monotonic, only one MIDI channel is processed. The lowest bit
* set in the -cn option's mask will indicate the channel to be used. If the -cn option
* isn't given, channel 0 will be used.
*
* Tones can be specified to play at either normal or high volume. High volume is
* indicated by setting the high bit of the frequency value (i.e. adding 0x8000 to the
* desired frequency). A note will be set to high volume if the -vn option is used and
* the MIDI velocity of the note is equal to or greater than the option value.
*
* For the C output format, frequencies will be output as note names, as defined in the
* ArduboyTones library's ArduboyTonesPitches.h file. If the -f option is given,
* the actual frequency, in decimal, will be used instead. Durations will be output
* in decimal.
*
* Output files are terminated with a single 16 bit value of 0x8000 to indicate
* end of score - stop playing. A file can instead be terminated with 0x8001 to indicate
* end of score - start playing again from the beginning, which is specified using the
* -r option.
*
* Len Shustek, 4 Feb 2011 and later.
* Frequency/duration pair output format and other changes:
* Scott Allen, 27 Sept 2016 and later.
*********************************************************************************************/
/*********************************************************************************************
* The MIT License (MIT)
* Original work Copyright (c) 2011,2013,2015,2016, Len Shustek
* Modified work Copyright (c) 2016, Scott Allen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*********************************************************************************************/
/*
* Change log
* 19 January 2011, L.Shustek, V1.0
* -Initial release.
* 26 February 2011, L. Shustek, V1.1
* -Expand the documentation generated in the output file.
* -End the binary output file with an "end of score" command.
* -Fix bug: Some "stop note" commands were generated too early.
* 04 March 2011, L. Shustek, V1.2
* -Minor error message rewording.
* 13 June 2011, L. Shustek, V1.3
* -Add -s2 strategy to try to keep each track on its own tone generator
* for when there are separate speakers. This obviously works only when
* each track is monophonic. (Suggested by Michal Pustejovsky)
* 20 November 2011, L. Shustek, V1.4
* -Add -cn option to mask which channels (tracks) to process
* -Add -kn option to change key
* Both of these are in support of music-playing on my Tesla Coil.
* 05 December 2011, L. Shustek, V1.5
* -Fix command line parsing error for option -s1
* -Display the commandline in the C file output
* -Change to decimal instead of hex for note numbers in the C file output
* 06 August 2013, L. Shustek, V1.6
* -Changed to allow compilation and execution in 64-bit environments
* by using C99 standard intN_t and uintN_t types for MIDI structures,
* and formatting specifications like "PRId32" instead of "ld".
* 04 April 2015, L. Shustek, V1.7
* -Made friendlier to other compilers: import source of strlcpy and strlcat,
* fixed various type mismatches that the LCC compiler didn't fret about.
* Generate "const" for data initialization for compatibility with Arduino IDE v1.6.x.
* 23 January 2016, D. Blackketter, V1.8
* -Fix warnings and errors building on Mac OS X via "gcc miditones.c"
* 25 January 2016, D. Blackketter, Paul Stoffregen, V1.9
* -Merge in velocity output option from Arduino/Teensy Audio Library
* 26 June 2016, L. Shustek, V1.10
* -Fix overflow problem in calculating long delays. (Thanks go to Tiago Rocha.)
* In the process I discover and work around an LCC 32-bit compiler bug.
* 14 August 2016: L. Shustek, V1.11
* -Fix our interpretation of MIDI "running status": it applies only to MIDI events
* (8x through Ex), not, as we thought, also to Sysex (Fx) or Meta (FF) events.
* -Improve parsing of text events for the log.
* -Change log file note and patch numbers, etc., to decimal.
* -Document a summary of the MIDI file format so I don't have to keep looking it up.
* -Add -pi and -pt options to ignore or translate the MIDI percussion track 9.
* -Remove string.h for more portability; add strlength().
* -Add -i option for recording instrument types in the bytestream.
* -Add -d option for generating a file description header.
* -Add -dp option to make generating the PROGMEM definition optional
* -Add -n option to specify number of items per output line
* -Do better error checking on options
* -Reformat option help
* 26 September 2016, Scott Allen, V1.12
* - Fix spelling and minor formatting errors
* - Fix -p option parsing and handling, which broke when -pi and -pt were added
* - Fix handling of the -nx option to count more accurately
* - Give a proper error message for missing base name
* - Include the header and terminator in the score byte count
*
* **** MIDITONES forked and renamed MIDI2TONES ****
*
* 27 September 2016, Scott Allen, V1.0.0
* -Add alternate frequency/duration pair output format and options to support it
* -Add -r option to terminate output with "restart" instead of "stop"
* -Allow hex and octal entry, in addition to decimal, for -cn option
* -Prevent unnecessary "note off" commands from being generated by delaying
* them until we see if a "note on" is generated before the next wait.
* Ported from MIDITONES V1.14
*/
#define VERSION "1.0.0"
/*--------------------------------------------------------------------------------------------
A CONCISE SUMMARY OF MIDI FILE FORMAT
L. Shustek, 16 July 2016.
Gleaned from http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html
Notation:
<xxx> is 1-4 bytes of 7-bit data, concatenated into one 7- to 28-bit number. The high bit of the last byte is 0.
lower case letters are hex digits. If preceeded by 0, only low 7 bits are used.
"xx" are ascii text characters
{xxx}... means indefinite repeat of xxx
A MIDI file is: header_chunk {track_chunk}...
header_chunk
"MThd" 00000006 ffff nnnn dddd
track_chunk
"MTrk" llllllll {<deltatime> track_event}...
"running status" track_event
0x to 7x: assume a missing 8n to En event code which is the same as the last MIDI-event track_event
MIDI-event track_event
8n 0kk 0vv note off, channel n, note kk, velocity vv
9n 0kk 0vv note on, channel n, note kk, velocity vv
An 0kk 0vv key pressure, channel n, note kk, pressure vv
Bn 0cc 0vv control value change, channel n, controller cc, new value vv
Cn 0pp program patch (instrument) change, channel n, new program pp
Dn 0vv channel pressure, channel n, pressure vv
En 0ll 0mm pitch wheel change, value llmm
Note that channel 9 (called 10 by some programs) is used for percussion, particularly notes 35 to 81.
Sysex event track_event
F0 0ii {0dd}... F7 system-dependent data for manufacture ii. See www.gweep.net/~prefect/eng/reference/protocol/midispec.html
F2 0ll 0mm song position pointer
F3 0ss song select
F6 tune request
F7 end of system-dependent data
F8 timing clock sync
FA start playing
FB continue playing
FC stop playing
FE active sensing (hearbeat)
Meta event track_event
FF 00 02 ssss specify sequence number
FF 01 <len> "xx"... arbitrary text
FF 02 <len> "xx"... copyright notice
FF 03 <len> "xx"... sequence or track name
FF 04 <len> "xx"... instrument name
FF 05 <len> "xx"... lyric to be sung
FF 06 <len> "xx"... name of marked point in the score
FF 07 <len> "xx"... description of cue point in the score
FF 20 01 0c default channel for subsequent events without a channel is c
FF 2F 00 end of track
FF 51 03 tttttt set tempo in microseconds per quarter-note
FF 54 05 hhmmssfrff set SMPTE time to start the track
FF 58 04 nnddccbb set time signature
FF 59 02 sfmi set key signature
FF 7F <len> data sequencer-specific data
--------------------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>
#include <time.h>
#include <inttypes.h>
/*********** MIDI file header formats *****************/
struct midi_header {
int8_t MThd[4];
uint32_t header_size;
uint16_t format_type;
uint16_t number_of_tracks;
uint16_t time_division;
};
struct track_header {
int8_t MTrk[4];
uint32_t track_size;
};
/*********** Global variables ******************/
#define MAX_TONEGENS 16 /* max tone generators: tones we can play simultaneously */
#define DEFAULT_TONEGENS 6 /* default number of tone generators */
#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */
#define PERCUSSION_TRACK 9 /* the track MIDI uses for percussion sounds */
/* for alternate output format: */
#define TONE_HIGH_VOLUME 0x8000 /* add to frequency for high volume */
#define TONES_END 0x8000 /* end marker for stop playing */
#define TONES_REPEAT 0x8001 /* end marker for restart playing */
bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem,
velocityoutput, instrumentoutput, percussion_ignore, percussion_translate, do_header,
alt_out, gen_restart, freq_style_a, freq_style_b, option_n;
FILE *infile, *outfile, *logfile;
uint8_t *buffer, *hdrptr;
unsigned long buflen;
int num_tracks;
int tracks_done = 0;
int outfile_maxitems = 26;
int outfile_itemcount = 0;
int num_tonegens = DEFAULT_TONEGENS;
int num_tonegens_used = 0;
int instrument_changes = 0;
int note_on_commands = 0;
unsigned channel_mask = 0xffff; // bit mask of channels to process
int keyshift = 0; // optional chromatic note shift for output file
long int outfile_bytecount = 0;
unsigned int ticks_per_beat = 240;
unsigned long timenow = 0;
unsigned long tempo; /* current tempo in usec/qnote */
int velocity_threshold = 128; /* alt out format: minimum velocity for high volume */
int pending_note; /* alt out format: note number awaiting output */
int pending_velocity; /* alt out format: velocity of note awaiting output */
int alt_out_channel; /* alt out format: MIDI channel used */
struct tonegen_status { /* current status of a tone generator */
bool playing; /* is it playing? */
bool stopnote_pending; /* do we need to stop this generator before the next wait? */
int track; /* if so, which track is the note from? */
int note; /* what note is playing? */
int instrument; /* what instrument? */
} tonegen[MAX_TONEGENS] = {
{
0}
};
struct track_status { /* current processing point of a MIDI track */
uint8_t *trkptr; /* ptr to the next note change */
uint8_t *trkend; /* ptr past the end of the track */
unsigned long time; /* what time we're at in the score */
unsigned long tempo; /* the tempo last set, in usec per qnote */
unsigned int preferred_tonegen; /* for strategy2, try to use this generator */
unsigned char cmd; /* CMD_xxxx next to do */
unsigned char note; /* for which note */
unsigned char chan; /* from which channel it was */
unsigned char velocity; /* the current volume */
unsigned char last_event; /* the last event, for MIDI's "running status" */
bool tonegens[MAX_TONEGENS]; /* which tone generators our notes are playing on */
} track[MAX_TRACKS] = {
{
0}
};
int midi_chan_instrument[16] = {
0
}; /* which instrument is currently being played on each channel */
/* output bytestream commands, which are also stored in track_status.cmd */
#define CMD_PLAYNOTE 0x90 /* play a note: low nibble is generator #, note is next byte */
#define CMD_STOPNOTE 0x80 /* stop a note: low nibble is generator # */
#define CMD_INSTRUMENT 0xc0 /* change instrument; low nibble is generator #, instrument is next byte */
#define CMD_RESTART 0xe0 /* restart the score from the beginning */
#define CMD_STOP 0xf0 /* stop playing */
/* if CMD < 0x80, then the other 7 bits and the next byte are a 15-bit number of msec to delay */
/* these other commands stored in the track_status.com */
#define CMD_TEMPO 0xFE /* tempo in usec per quarter note ("beat") */
#define CMD_TRACKDONE 0xFF /* no more data left in this track */
struct file_hdr_t { /* what the optional file header looks like */
char id1; // 'P'
char id2; // 't'
unsigned char hdr_length; // length of whole file header
unsigned char f1; // flag byte 1
unsigned char f2; // flag byte 2
unsigned char num_tgens; // how many tone generators are used by this score
} file_header = {
'P', 't', sizeof (struct file_hdr_t), 0, 0, MAX_TONEGENS};
#define HDR_F1_VOLUME_PRESENT 0x80
#define HDR_F1_INSTRUMENTS_PRESENT 0x40
#define HDR_F1_PERCUSSION_PRESENT 0x20
long int file_header_num_tgens_position;
const char note_name[][3] = {
"C", "CS", "D", "DS", "E", "F", "FS", "G", "GS", "A", "AS", "B" };
/* MIDI note frequencies (notes below number 12 not supported) */
const uint16_t note_freq[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - 11 */
16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, /* 12 - 23 */
33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, /* 24 - 35 */
65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, /* 36 - 47 */
131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, /* 48 - 59 */
262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, /* 60 - 71 */
523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, /* 72 - 83 */
1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, /* 84 - 95 */
2093, 2218, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, /* 96 - 107 */
4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902, /* 108 - 119 */
8372, 8870, 9397, 9956,10548,11175,11840,12544 /* 120 - 127 */
};
/************** command-line processing *******************/
void SayUsage (char *programName) {
static char *usage[] = {
"Convert MIDI files to an Arduino PLAYTUNE bytestream,",
"or a frequency/duration pairs stream suitable for ArduboyTones",
"",
"Use: midi2tones <options> <basefilename>",
" input file will be <basefilename>.mid",
" output file will be <basefilename>.bin or .c",
" log file will be <basefilename>.log",
"",
"Commonly-used options:",
" -o1 generate Playtune output format (the default)",
" -o2 generate the frequency/duration pair output format",
" -v include velocity data",
" -vn for alternate format: n is the minimum velocity for high volume notes",
" -i include instrument change commands",
" -pt translate notes in the percussion track to notes 129 to 255",
" -d include a self-describing file header",
" -b generate binary file output instead of C source text",
" -tn use at most n tone generators (default is 6, max is 16)",
"",
" The best options for later Playtune music players are: -v -i -pt -d",
"",
"Lesser-used command-line options:",
" -p parse only, don't generate bytestream",
" -lp log input parsing",
" -lg log output generation",
" -nx put about x items on each line of the C file output",
" -s1 strategy 1: favor track 1",
" -s2 strategy 2: try to assign tracks to specific tone generators",
" -cn mask for which tracks to process, e.g. -c3 for only 0 and 1",
" -kn key shift in chromatic notes, positive or negative",
" -pi ignore notes in the percussion track (9)",
" -fa for alternate format: high volume notes as \"<freq>+TONE_HIGH_VOLUME\"",
" -fb for alternate format: high volume notes as a single decimal value",
" -r terminate output file with \"restart\" instead of \"stop\" command",
" -dp define PROGMEM in output C code",
NULL
};
int i = 0;
while (usage[i] != NULL)
fprintf (stderr, "%s\n", usage[i++]);
}
int HandleOptions (int argc, char *argv[]) {
/* returns the index of the first argument that is not an option; i.e.
does not start with a dash or a slash*/
int i, nch, firstnonoption = 0;
/* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */
for (i = 1; i < argc; i++) {
if (argv[i][0] == '/' || argv[i][0] == '-') {
switch (toupper (argv[i][1])) {
case 'H':
case '?':
SayUsage (argv[0]);
exit (1);
case 'O':
if (argv[i][2] == '1')
alt_out = false;
else if (argv[i][2] == '2') {
alt_out = true;
printf ("Generating frequency/duration pair output format.\n");
}
else
goto opterror;
if (argv[i][3] != '\0')
goto opterror;
break;
case 'L':
if (toupper (argv[i][2]) == 'G')
loggen = true;
else if (toupper (argv[i][2]) == 'P')
logparse = true;
else
goto opterror;
if (argv[i][3] != '\0')
goto opterror;
break;
case 'P':
if (argv[i][2] == '\0') {
parseonly = true;
break;
}
else if (toupper (argv[i][2]) == 'I')
percussion_ignore = true;
else if (toupper (argv[i][2]) == 'T')
percussion_translate = true;
else
goto opterror;
if (argv[i][3] != '\0')
goto opterror;
break;
case 'B':
binaryoutput = true;
if (argv[i][2] != '\0')
goto opterror;
break;
case 'V':
velocityoutput = true;
if (argv[i][2] == '\0')
break;
if (sscanf (&argv[i][2], "%d%n", &velocity_threshold, &nch) != 1
|| velocity_threshold < 0 || velocity_threshold > 127)
goto opterror;
printf ("Using velocity >= %d for high volume.\n", velocity_threshold);
if (argv[i][2 + nch] != '\0')
goto opterror;
break;
case 'I':
instrumentoutput = true;
if (argv[i][2] != '\0')
goto opterror;
break;
case 'S':
if (argv[i][2] == '1')
strategy1 = true;
else if (argv[i][2] == '2')
strategy2 = true;
else
goto opterror;
if (argv[i][3] != '\0')
goto opterror;
break;
case 'T':
if (sscanf (&argv[i][2], "%d%n", &num_tonegens, &nch) != 1
|| num_tonegens < 1 || num_tonegens > MAX_TONEGENS)
goto opterror;
printf ("Using %d tone generators.\n", num_tonegens);
if (argv[i][2 + nch] != '\0')
goto opterror;
break;
case 'N':
if (sscanf (&argv[i][2], "%d%n", &outfile_maxitems, &nch) != 1 || outfile_maxitems < 1)
goto opterror;
if (argv[i][2 + nch] != '\0')
goto opterror;
option_n = true;
break;
case 'C':
if (sscanf (&argv[i][2], "%i%n", &channel_mask, &nch) != 1
|| channel_mask == 0 || channel_mask > 0xffff)
goto opterror;
printf ("Channel (track) mask is %04X.\n", channel_mask);
if (argv[i][2 + nch] != '\0')
goto opterror;
break;
case 'K':
if (sscanf (&argv[i][2], "%d%n", &keyshift, &nch) != 1 || keyshift < -100
|| keyshift > 100)
goto opterror;
printf ("Using keyshift %d.\n", keyshift);
if (argv[i][2 + nch] != '\0')
goto opterror;
break;
case 'D':
if (argv[i][2] == '\0') {
do_header = true;
break;
}
if (toupper (argv[i][2]) == 'P')
define_progmem = true;
else
goto opterror;
if (argv[i][3] != '\0')
goto opterror;
break;
case 'F':
if (toupper (argv[i][2]) == 'A')
freq_style_a = true;
else if (toupper (argv[i][2]) == 'B')
freq_style_b = true;
else
goto opterror;
if (argv[i][3] != '\0')
goto opterror;
break;
case 'R':
gen_restart = true;
if (argv[i][2] != '\0')
goto opterror;
break;
/* add more option switches here */
opterror:
default:
fprintf (stderr, "\n*** unknown option: %s\n\n", argv[i]);
SayUsage (argv[0]);
exit (4);
}
} else {
firstnonoption = i;
break;
}
}
return firstnonoption;
}
void print_command_line (int argc, char *argv[]) {
int i;
fprintf (outfile, "// command line: ");
for (i = 0; i < argc; i++)
fprintf (outfile, "%s ", argv[i]);
fprintf (outfile, "\n");
}
/**************** utility routines **********************/
/* portable string length */
int strlength (const char *str) {
int i;
for (i = 0; str[i] != '\0'; ++i);
return i;
}
/* safe string copy */
size_t miditones_strlcpy (char *dst, const char *src, size_t siz) {
char *d = dst;
const char *s = src;
size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0) {
while (--n != 0) {
if ((*d++ = *s++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++);
}
return (s - src - 1); /* count does not include NUL */
}
/* safe string concatenation */
size_t miditones_strlcat (char *dst, const char *src, size_t siz) {
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return (dlen + strlength (s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return (dlen + (s - src)); /* count does not include NUL */
}
/* match a constant character sequence */
int charcmp (const char *buf, const char *match) {
int len, i;
len = strlength (match);
for (i = 0; i < len; ++i)
if (buf[i] != match[i])
return 0;
return 1;
}
/* announce a fatal MIDI file format error */
void midi_error (char *msg, unsigned char *bufptr) {
unsigned char *ptr;
fprintf (stderr, "---> MIDI file error at position %04X (%d): %s\n",
(uint16_t) (bufptr - buffer), (uint16_t) (bufptr - buffer), msg);
/* print some bytes surrounding the error */
ptr = bufptr - 16;
if (ptr < buffer)
ptr = buffer;
for (; ptr <= bufptr + 16 && ptr < buffer + buflen; ++ptr)
fprintf (stderr, ptr == bufptr ? " [%02X] " : "%02X ", *ptr);
fprintf (stderr, "\n");
exit (8);
}
/* check that we have a specified number of bytes left in the buffer */
void chk_bufdata (unsigned char *ptr, unsigned long int len) {
if ((unsigned) (ptr + len - buffer) > buflen)
midi_error ("data missing", ptr);
}
/* fetch big-endian numbers */
uint16_t rev_short (uint16_t val) {
return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
}
uint32_t rev_long (uint32_t val) {
return (((rev_short ((uint16_t) val) & 0xffff) << 16) |
(rev_short ((uint16_t) (val >> 16)) & 0xffff));
}
/* write a big-endian 16 bit number to the output file */
void output_word (uint16_t val) {
putc ((unsigned char) (val >> 8), outfile);
putc ((unsigned char) (val & 0xff), outfile);
outfile_bytecount += 2;
}
/* account for new items in the non-binary output file
and generate a newline every so often. */
void outfile_items (int n) {
if (!alt_out)
outfile_bytecount += n;
else
outfile_bytecount += (n * 2);
outfile_itemcount += n;
if (!binaryoutput && outfile_itemcount >= outfile_maxitems) {
fprintf (outfile, "\n");
outfile_itemcount = 0;
}
}
/************** process the MIDI file header *****************/
void process_header (void) {
struct midi_header *hdr;
unsigned int time_division;
chk_bufdata (hdrptr, sizeof (struct midi_header));
hdr = (struct midi_header *) hdrptr;
if (!charcmp ((char *) hdr->MThd, "MThd"))
midi_error ("Missing 'MThd'", hdrptr);
num_tracks = rev_short (hdr->number_of_tracks);
time_division = rev_short (hdr->time_division);
if (time_division < 0x8000)
ticks_per_beat = time_division;
else
ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ *(time_division & 0xff); /* ticks/SMTE frame */
if (logparse) {
fprintf (logfile, "Header size %" PRId32 "\n", rev_long (hdr->header_size));
fprintf (logfile, "Format type %d\n", rev_short (hdr->format_type));
fprintf (logfile, "Number of tracks %d\n", num_tracks);
fprintf (logfile, "Time division %04X\n", time_division);
fprintf (logfile, "Ticks/beat = %d\n", ticks_per_beat);
}
hdrptr += rev_long (hdr->header_size) + 8; /* point past header to track header, presumably. */
return;
}
/**************** Process a MIDI track header *******************/
void start_track (int tracknum) {
struct track_header *hdr;
unsigned long tracklen;
chk_bufdata (hdrptr, sizeof (struct track_header));
hdr = (struct track_header *) hdrptr;
if (!charcmp ((char *) (hdr->MTrk), "MTrk"))
midi_error ("Missing 'MTrk'", hdrptr);
tracklen = rev_long (hdr->track_size);
if (logparse)
fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen);
hdrptr += sizeof (struct track_header); /* point past header */
chk_bufdata (hdrptr, tracklen);
track[tracknum].trkptr = hdrptr;
hdrptr += tracklen; /* point to the start of the next track */
track[tracknum].trkend = hdrptr; /* the point past the end of the track */
}
/* Get a MIDI-style variable-length integer */
unsigned long get_varlen (uint8_t ** ptr) {
/* Get a 1-4 byte variable-length value and adjust the pointer past it.
These are a succession of 7-bit values with a MSB bit of zero marking the end */
unsigned long val;
int i, byte;
val = 0;
for (i = 0; i < 4; ++i) {
byte = *(*ptr)++;
val = (val << 7) | (byte & 0x7f);
if (!(byte & 0x80))
return val;
}
return val;
}
/*************** Process the MIDI track data ***************************/
/* Skip in the track for the next "note on", "note off" or "set tempo" command,
then record that information in the track status block and return. */
void find_note (int tracknum) {
unsigned long int delta_time;
int event, chan;
int i;
int note, velocity, controller, pressure, pitchbend, instrument;
int meta_cmd, meta_length;
unsigned long int sysex_length;
struct track_status *t;
char *tag;
/* process events */
t = &track[tracknum]; /* our track status structure */
while (t->trkptr < t->trkend) {
delta_time = get_varlen (&t->trkptr);
if (logparse) {
fprintf (logfile, "trk %d ", tracknum);
if (delta_time) {
fprintf (logfile, "delta time %4ld, ", delta_time);
} else {
fprintf (logfile, " ");
}
}
t->time += delta_time;
if (*t->trkptr < 0x80)
event = t->last_event; /* using "running status": same event as before */
else { /* otherwise get new "status" (event type) */
event = *t->trkptr++;
}
if (event == 0xff) { /* meta-event */
meta_cmd = *t->trkptr++;
meta_length = *t->trkptr++;
switch (meta_cmd) {
case 0x00:
if (logparse)
fprintf (logfile, "sequence number %d\n", rev_short (*(unsigned short *) t->trkptr));
break;
case 0x01:
tag = "description";
goto show_text;
case 0x02:
tag = "copyright";
goto show_text;
case 0x03:
tag = "track name";
if (tracknum == 0 && !parseonly && !binaryoutput) {
/* Incredibly, MIDI has no standard for recording the name of the piece!
Track 0's "trackname" is often used for that so we output it to the C file as documentation. */
fprintf (outfile, "// ");
for (i = 0; i < meta_length; ++i) {