diff --git a/README.txt b/README.txt index 4fab864..71bc5dc 100644 --- a/README.txt +++ b/README.txt @@ -64,7 +64,7 @@ MIDITONES may also prove useful for other simple music synthesizers. There are various forks of this code, and of the Playtune players, on Githib. - + *** THE PROGRAM MIDITONES is written in standard ANSI C and is meant to be executed from the @@ -104,8 +104,8 @@ If the user specifies the full .mid filename, the .mid or .MID extension will be dropped and the remaining name will be used as . - The input file is .mid, and the output filename(s) - are the base file name with .c, .h, .bin, and/or .log extensions. + The input file is .mid, and the output filename(s) are + the base file name with .c, .h, .bin, .asm, .inc, and/or .log extensions. The following commonly-used command-line options can be specified: @@ -122,7 +122,8 @@ Playtune players that can check the header to know what data to expect. -b Generate a binary file with the name .bin, instead of a - C-language source file with the name .c. + C-language source file with the name .c. This is useful + to create a file that can be input to Miditones_scroll. -t=n 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 @@ -140,6 +141,9 @@ -dp Generate Arduino IDE-dependent C code that uses PROGMEM for the bytestream. + -asm1802 Generate assembler code for the Cosmac Elf 1802 microprocessor + -asm6502 Generate assembler code for the MOS technology 6502 microprocessor + -k=n Change the musical key of the output by n chromatic notes. -k=-12 goes one octave down, -k=12 goes one octave up, etc. @@ -256,4 +260,4 @@ Any subsequent header bytes included in the length are currently undefined and should be ignored by players. - Len Shustek, 2011 to 2021; see the change log. + Len Shustek, 2011 to 2025; see the change log. diff --git a/miditones.c b/miditones.c index 9844af1..3d2fd1a 100644 --- a/miditones.c +++ b/miditones.c @@ -66,6 +66,7 @@ MIDITONES may also prove useful for other simple music synthesizers. There are various forks of this code, and of the Playtune players, on Githib. + *** THE PROGRAM @@ -106,8 +107,8 @@ If the user specifies the full .mid filename, the .mid or .MID extension will be dropped and the remaining name will be used as . - The input file is .mid, and the output filename(s) - are the base file name with .c, .h, .bin, and/or .log extensions. + The input file is .mid, and the output filename(s) are + the base file name with .c, .h, .bin, .asm, .inc, and/or .log extensions. The following commonly-used command-line options can be specified: @@ -124,7 +125,8 @@ Playtune players that can check the header to know what data to expect. -b Generate a binary file with the name .bin, instead of a - C-language source file with the name .c. + C-language source file with the name .c. This is useful + to create a file that can be input to Miditones_scroll. -t=n 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 @@ -142,6 +144,9 @@ -dp Generate Arduino IDE-dependent C code that uses PROGMEM for the bytestream. + -asm1802 Generate assembler code for the Cosmac Elf 1802 microprocessor + -asm6502 Generate assembler code for the MOS technology 6502 microprocessor + -k=n Change the musical key of the output by n chromatic notes. -k=-12 goes one octave down, -k=12 goes one octave up, etc. @@ -258,11 +263,11 @@ Any subsequent header bytes included in the length are currently undefined and should be ignored by players. - Len Shustek, 2011 to 2021; see the change log. + Len Shustek, 2011 to 2025; see the change log. *---------------------------------------------------------------------------------------- * The MIT License (MIT) -* Copyright (c) 2011,2013,2015,2016,2019,2021 Len Shustek +* Copyright (c) 2011,2013,2015,2016,2019,2021,2025 Len Shustek * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -394,10 +399,13 @@ (Thanks to Jonathan Oakley for providing an example of the problem.) -But sometimes, to reduce the number of tone generators, it's helpful to eliminate identical notes. So add a -noduplicates option to do just that. +10 April 2025, Len Shustek, V2.5 + -Put the length of the song as a comment at the end of the generated source code. + -Add -asm1802 and -asm6502 options, adopting changes from GitHub user fourstix. future version ideas - -Perhaps elide "note off/note on" event sequences for the same note that + -Perhaps elide "note off/note on" event sequences for the same note that become adjacent because of -delaymin. Does that happen much, or at all? -Allow the flexibility to specify note timing on a track-by-track or @@ -409,7 +417,7 @@ future version ideas channel 8 // organ options -attacktime=1000 -sustainlevel=80% -releasetime=100 -notemin=200 */ -#define VERSION "2.4" +#define VERSION "2.5" /*-------------------------------------------------------------------------------------------- @@ -601,7 +609,10 @@ struct track_header { bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem, volume_output, instrumentoutput, percussion_ignore, percussion_translate, do_header, - gen_restart, scorename, showskipped, noduplicates; + gen_restart, scorename, showskipped, noduplicates, asm_output; +char *comment = "//"; // default start of comment +char *hex = "0x"; // default start of hex constant +char *byteop = ""; // what assembler psuedo-op indicates a byte constant FILE *infile, *outfile, *logfile; uint8_t *buffer, *hdrptr; unsigned long buflen; @@ -708,7 +719,7 @@ void SayUsage(char *programName) { "", "Use: miditones ", " input file will be .mid", - " output file will be .bin or .c or .h", + " output file will be .bin, .c, .h, .asm, or .inc", " log file will be .log", "", "Commonly-used options:", @@ -733,6 +744,8 @@ void SayUsage(char *programName) { " -r terminate output file with \"restart\" instead of \"stop\" command", " -s1 strategy 1: favor track 1", " -s2 strategy 2: try to assign tracks to specific tone generators", + " -asm1802 generate assembler code for the 1802 microprocessor", + " -asm6502 generate assembler code for the 6502 microprocessor", " -showskipped display information about each note that had to be skipped", " -noduplicates remove identical notes playing on different channels", " -delaymin=x minimum delay is x msec, to save bytestream space", @@ -771,6 +784,10 @@ int HandleOptions (int argc, char *argv[]) { else if (opt_key(arg, "pi")) percussion_ignore = true; else if (opt_key(arg, "pt")) percussion_translate = true; else if (opt_key(arg, "r")) gen_restart = true; + else if (opt_key(arg, "asm1802")) { + asm_output = true; comment = ";"; byteop = " db "; hex = "$"; outfile_maxitems = 16; } + else if (opt_key(arg, "asm6502")) { + asm_output = true; comment = ";"; byteop = " .byte "; hex = "$"; outfile_maxitems = 16; } else if (opt_int(arg, "delaymin", &tempint, 1, 1000)) delaymin_usec = tempint * 1000; else if (opt_int(arg, "releasetime", &tempint, 0, INT_MAX)) releasetime_usec = tempint * 1000; else if (opt_int(arg, "notemin", &tempint, 0, INT_MAX)) notemin_usec = tempint * 1000; @@ -801,7 +818,7 @@ int HandleOptions (int argc, char *argv[]) { return firstnonoption; } void print_command_line (FILE *file, int argc, char *argv[]) { - fprintf (file, "// command line: "); + fprintf (file, "%s command line: ", comment); for (int i = 0; i < argc; i++) fprintf (file, "%s ", argv[i]); fprintf (file, "\n"); } @@ -888,12 +905,15 @@ uint32_t rev_long (uint32_t val) { /* account for new items in the non-binary output file and generate a newline every so often. */ -void outfile_items (int n) { +void outfile_items(int n) { outfile_bytecount += n; outfile_itemcount += n; - if (!binaryoutput && outfile_itemcount >= outfile_maxitems) { - fprintf (outfile, "\n"); - outfile_itemcount = 0; } } + if (!binaryoutput) { + if (outfile_itemcount >= outfile_maxitems) { + if (asm_output) fprintf(outfile, "\n%s", byteop); + else fprintf(outfile, ",\n"); + outfile_itemcount = 0; } + else fprintf(outfile, ", "); } } //******* structures for recording track, channel, and tone generator status @@ -1076,8 +1096,13 @@ void remove_queue_entry(int ndx) { // remove the oldest queue entry putc(CMD_INSTRUMENT | tgnum, outfile); putc(tg->note.instrument, outfile); outfile_bytecount += 2; } + else if (asm_output) { + fprintf(outfile, "%s%02X", hex, CMD_INSTRUMENT | tgnum); + outfile_items(1); + fprintf(outfile, "%3d", tg->note.instrument); + outfile_items(1); } else { - fprintf(outfile, "0x%02X,%d, ", CMD_INSTRUMENT | tgnum, tg->note.instrument); + fprintf(outfile, "0x%02X,%d", CMD_INSTRUMENT | tgnum, tg->note.instrument); outfile_items(2); } } } if (loggen) fprintf(logfile, " play tgen %d %s\n", tgnum, describe(&q->note)); tg->playing = true; @@ -1093,12 +1118,20 @@ void remove_queue_entry(int ndx) { // remove the oldest queue entry if (volume_output) { putc(tg->note.volume, outfile); outfile_bytecount +=1; } } + else if (asm_output) { + fprintf(outfile, "%s%02X", hex, CMD_PLAYNOTE | tgnum); + outfile_items(1); + fprintf(outfile, "%3d", tg->note.note); + outfile_items(1); + if (volume_output) { + fprintf(outfile, "%3d", tg->note.volume); + outfile_items(1); } } else { if (volume_output == 0) { - fprintf(outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, tg->note.note); + fprintf(outfile, "0x%02X,%d", CMD_PLAYNOTE | tgnum, tg->note.note); outfile_items(2); } else { - fprintf(outfile, "0x%02X,%d,%d, ", CMD_PLAYNOTE | tgnum, tg->note.note, tg->note.volume); + fprintf(outfile, "0x%02X,%d,%d", CMD_PLAYNOTE | tgnum, tg->note.note, tg->note.volume); outfile_items(3); } } } else { if (loggen) fprintf(logfile, " *** at %lu.%03lu msec no free generator; skipping %s\n", @@ -1117,8 +1150,13 @@ void generate_delay(unsigned long delta_msec) { // output a delay command putc((byte)(delta_msec >> 8), outfile); putc((byte)(delta_msec & 0xff), outfile); outfile_bytecount += 2; } + else if (asm_output) { + fprintf(outfile, "%s%02X", hex, (byte)(delta_msec >> 8)); + outfile_items(1); + fprintf(outfile, "%s%02X", hex, (byte)(delta_msec & 0xff)); + outfile_items(1); } else { - fprintf(outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); + fprintf(outfile, "%ld,%ld", delta_msec >> 8, delta_msec & 0xff); outfile_items(2); } } } // output all queue elements which are at the oldest time or at most "delaymin" later @@ -1156,7 +1194,7 @@ void pull_queue(void) { putc(CMD_STOPNOTE | tgnum, outfile); outfile_bytecount += 1; } else { - fprintf(outfile, "0x%02X, ", CMD_STOPNOTE | tgnum); + fprintf(outfile, "%s%02X", hex, CMD_STOPNOTE | tgnum); outfile_items(1); } if (loggen) fprintf(logfile, " stop tgen %d %s\n", tgnum, describe(&tg->note)); tg->stopnote_pending = false; @@ -1356,7 +1394,7 @@ void find_next_note (int tracknum) { tag = "copyright"; goto show_text; case 0x03: tag = "track name"; - if (tracknum == 0 && !parseonly && !binaryoutput) { + if (tracknum == 0 && !parseonly && !binaryoutput && !asm_output) { /* 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, "// "); @@ -1622,8 +1660,9 @@ void process_track_data(void) { putc(gen_restart ? CMD_RESTART : CMD_STOP, outfile); outfile_bytecount +=1; } else { - fprintf(outfile, "0x%02X};", gen_restart ? CMD_RESTART : CMD_STOP); - outfile_items(1); + fprintf(outfile, "%s%02X", hex, gen_restart ? CMD_RESTART : CMD_STOP); + ++outfile_bytecount; + if (!asm_output) fprintf(outfile, "};"); fprintf(outfile, "\n"); } } @@ -1647,7 +1686,7 @@ int main (int argc, char *argv[]) { SayUsage (argv[0]); exit (4); } filebasename = argv[argno]; - + // strip off trailing .mid or .MID extension if provided by user basenamelen = strlength(filebasename); if (basenamelen > 4 && @@ -1696,7 +1735,7 @@ int main (int argc, char *argv[]) { miditones_strlcat (filename, ".bin", MAXPATH); outfile = fopen (filename, "wb"); } else { - miditones_strlcat (filename, scorename ? ".h" : ".c", MAXPATH); + miditones_strlcat (filename, asm_output ? (scorename ? ".inc" : ".asm") : (scorename ? ".h" : ".c"), MAXPATH); outfile = fopen (filename, "w"); } if (!outfile) { fprintf (stderr, "Unable to open output file %s\n", filename); @@ -1708,27 +1747,29 @@ int main (int argc, char *argv[]) { if (!binaryoutput) { /* create header of C file that initializes score data */ time_t rawtime; time (&rawtime); - fprintf (outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename); + fprintf (outfile, "%s Playtune bytestream for file \"%s.mid\" ", comment, filebasename); fprintf (outfile, "created by MIDITONES V%s on %s", VERSION, asctime (localtime (&rawtime))); print_command_line (outfile, argc, argv); if (channel_mask != 0xffff) - fprintf (outfile, "// Only the masked channels were processed: %04X\n", channel_mask); + fprintf (outfile, "%s Only the masked channels were processed: %04X\n", comment, channel_mask); if (keyshift != 0) - fprintf (outfile, "// Keyshift was %d chromatic notes\n", keyshift); + fprintf (outfile, "%s Keyshift was %d chromatic notes\n", comment, keyshift); if (define_progmem) { fprintf (outfile, "#ifdef __AVR__\n"); fprintf (outfile, "#include \n"); fprintf (outfile, "#else\n"); fprintf (outfile, "#define PROGMEM\n"); fprintf (outfile, "#endif\n"); } - fprintf (outfile, "const unsigned char PROGMEM %s [] = {\n", + if (asm_output) // create the assembler label for the score + fprintf(outfile, "\n%s:\n%s", filebasename, byteop); + else fprintf (outfile, "const unsigned char PROGMEM %s [] = {\n", scorename ? filebasename : "score"); - if (do_header) { // write the C initialization for the file header - fprintf (outfile, "'P','t', 6, 0x%02X, 0x%02X, ", file_header.f1, file_header.f2); + if (do_header) { // write the initialization for the file header + fprintf (outfile, "'P','t', 6, %s%02X, %s%02X, ", hex, file_header.f1, hex, file_header.f2); fflush (outfile); file_header_num_tgens_position = ftell (outfile); // remember where the number of tone generators is - fprintf (outfile, "%2d, // (Playtune file header)\n", file_header.num_tgens); + fprintf (outfile, "%2d%s %s (Playtune file header)\n%s", file_header.num_tgens, asm_output ? "" : ",", comment, byteop); outfile_bytecount += 6; } } else if (do_header) { // write the binary file header int i; @@ -1773,11 +1814,14 @@ int main (int argc, char *argv[]) { // generate the ending commentary if (!binaryoutput) { - fprintf(outfile, "\n// This %ld byte score contains %d notes and uses %d tone generator%s\n", - outfile_bytecount, note_on_commands, num_tonegens_used, + fprintf(outfile, "\n%s This %ld byte score contains %d notes and uses %d tone generator%s.\n", + comment, outfile_bytecount, note_on_commands, num_tonegens_used, num_tonegens_used == 1 ? "" : "s"); + fprintf(outfile, "%s It encodes %u.%03u seconds of music with %d tempo change%s.\n", + comment, (unsigned)(timenow_usec / 1000000), (unsigned)(timenow_usec / 1000 % 1000), + tempo_changes, tempo_changes == 1 ? "" : "s"); if (notes_skipped) - fprintf(outfile, "// %d notes had to be skipped\n", notes_skipped); } + fprintf(outfile, "%s %d notes had to be skipped.\n", comment, notes_skipped); } printf(" %s %d tone generators were used.\n", num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used); if (notes_skipped) @@ -1793,8 +1837,9 @@ int main (int argc, char *argv[]) { " (Consider recompiling with MAX_TRACKNOTES bigger than %d, to allow more simultaneous notes.)\n", noteinfo_overflow, noteinfo_notfound, MAX_CHANNELNOTES); printf(" %ld bytes of score data were generated, ", outfile_bytecount); - printf("representing %u.%03u seconds of music with %d tempo changes\n", - (unsigned)(timenow_usec / 1000000), (unsigned)(timenow_usec / 1000 % 1000), tempo_changes); + printf("encoding %u.%03u seconds of music with %d tempo change%s.\n", + (unsigned)(timenow_usec / 1000000), (unsigned)(timenow_usec / 1000 % 1000), + tempo_changes, tempo_changes == 1 ? "" : "s"); if (delaymin_usec) printf(" %ld delays were removed because the minimum delay of %u msec caused events to be merged\n", delays_saved, (unsigned)(delaymin_usec / 1000)); diff --git a/miditones.exe b/miditones.exe index 07e6d9c..832d376 100644 Binary files a/miditones.exe and b/miditones.exe differ