From 760d4d5a0fc89e5b14681879577a80e79795e4a3 Mon Sep 17 00:00:00 2001 From: pepper Date: Mon, 19 Jan 2015 00:02:46 -0800 Subject: first --- .midisep.pl.swo | Bin 0 -> 16384 bytes .midisep.pl.swp | Bin 0 -> 12288 bytes example.mid | Bin 0 -> 21504 bytes midicsv-1.1/Csvmidi.exe | Bin 0 -> 53248 bytes midicsv-1.1/Csvmidi.vcproj | 134 +++++++ midicsv-1.1/Makefile | 103 ++++++ midicsv-1.1/Midicsv.exe | Bin 0 -> 45056 bytes midicsv-1.1/Midicsv.sln | 27 ++ midicsv-1.1/Midicsv.vcproj | 137 +++++++ midicsv-1.1/README | 134 +++++++ midicsv-1.1/W32test.bat | 7 + midicsv-1.1/acomp.pl | 69 ++++ midicsv-1.1/bad.csv | 39 ++ midicsv-1.1/ce3k.csv | 23 ++ midicsv-1.1/chorus.pl | 28 ++ midicsv-1.1/count_events.pl | 18 + midicsv-1.1/csv.c | 146 ++++++++ midicsv-1.1/csv.h | 9 + midicsv-1.1/csv.o | Bin 0 -> 10752 bytes midicsv-1.1/csvmidi | Bin 0 -> 39848 bytes midicsv-1.1/csvmidi.1 | 128 +++++++ midicsv-1.1/csvmidi.c | 854 ++++++++++++++++++++++++++++++++++++++++++++ midicsv-1.1/csvmidi.o | Bin 0 -> 40528 bytes midicsv-1.1/drummer.pl | 59 +++ midicsv-1.1/exchannel.pl | 18 + midicsv-1.1/general_midi.pl | 187 ++++++++++ midicsv-1.1/getopt.c | 117 ++++++ midicsv-1.1/getopt.h | 10 + midicsv-1.1/getopt.o | Bin 0 -> 8816 bytes midicsv-1.1/log.txt | 407 +++++++++++++++++++++ midicsv-1.1/midicsv | Bin 0 -> 27808 bytes midicsv-1.1/midicsv.1 | 82 +++++ midicsv-1.1/midicsv.5 | 597 +++++++++++++++++++++++++++++++ midicsv-1.1/midicsv.c | 510 ++++++++++++++++++++++++++ midicsv-1.1/midicsv.o | Bin 0 -> 25200 bytes midicsv-1.1/midifile.h | 90 +++++ midicsv-1.1/midio.c | 136 +++++++ midicsv-1.1/midio.h | 17 + midicsv-1.1/midio.o | Bin 0 -> 9720 bytes midicsv-1.1/test.mid | Bin 0 -> 44298 bytes midicsv-1.1/torture.pl | 173 +++++++++ midicsv-1.1/transpose.pl | 34 ++ midicsv-1.1/types.h | 10 + midicsv-1.1/version.h | 2 + midisep.pl | 161 +++++++++ 45 files changed, 4466 insertions(+) create mode 100644 .midisep.pl.swo create mode 100644 .midisep.pl.swp create mode 100644 example.mid create mode 100755 midicsv-1.1/Csvmidi.exe create mode 100644 midicsv-1.1/Csvmidi.vcproj create mode 100644 midicsv-1.1/Makefile create mode 100755 midicsv-1.1/Midicsv.exe create mode 100644 midicsv-1.1/Midicsv.sln create mode 100644 midicsv-1.1/Midicsv.vcproj create mode 100644 midicsv-1.1/README create mode 100644 midicsv-1.1/W32test.bat create mode 100644 midicsv-1.1/acomp.pl create mode 100644 midicsv-1.1/bad.csv create mode 100644 midicsv-1.1/ce3k.csv create mode 100644 midicsv-1.1/chorus.pl create mode 100644 midicsv-1.1/count_events.pl create mode 100644 midicsv-1.1/csv.c create mode 100644 midicsv-1.1/csv.h create mode 100644 midicsv-1.1/csv.o create mode 100755 midicsv-1.1/csvmidi create mode 100644 midicsv-1.1/csvmidi.1 create mode 100644 midicsv-1.1/csvmidi.c create mode 100644 midicsv-1.1/csvmidi.o create mode 100644 midicsv-1.1/drummer.pl create mode 100644 midicsv-1.1/exchannel.pl create mode 100644 midicsv-1.1/general_midi.pl create mode 100644 midicsv-1.1/getopt.c create mode 100644 midicsv-1.1/getopt.h create mode 100644 midicsv-1.1/getopt.o create mode 100644 midicsv-1.1/log.txt create mode 100755 midicsv-1.1/midicsv create mode 100644 midicsv-1.1/midicsv.1 create mode 100644 midicsv-1.1/midicsv.5 create mode 100644 midicsv-1.1/midicsv.c create mode 100644 midicsv-1.1/midicsv.o create mode 100644 midicsv-1.1/midifile.h create mode 100644 midicsv-1.1/midio.c create mode 100644 midicsv-1.1/midio.h create mode 100644 midicsv-1.1/midio.o create mode 100644 midicsv-1.1/test.mid create mode 100644 midicsv-1.1/torture.pl create mode 100644 midicsv-1.1/transpose.pl create mode 100644 midicsv-1.1/types.h create mode 100644 midicsv-1.1/version.h create mode 100755 midisep.pl diff --git a/.midisep.pl.swo b/.midisep.pl.swo new file mode 100644 index 0000000..bc725f6 Binary files /dev/null and b/.midisep.pl.swo differ diff --git a/.midisep.pl.swp b/.midisep.pl.swp new file mode 100644 index 0000000..8ece1c5 Binary files /dev/null and b/.midisep.pl.swp differ diff --git a/example.mid b/example.mid new file mode 100644 index 0000000..bf3bfc0 Binary files /dev/null and b/example.mid differ diff --git a/midicsv-1.1/Csvmidi.exe b/midicsv-1.1/Csvmidi.exe new file mode 100755 index 0000000..febf438 Binary files /dev/null and b/midicsv-1.1/Csvmidi.exe differ diff --git a/midicsv-1.1/Csvmidi.vcproj b/midicsv-1.1/Csvmidi.vcproj new file mode 100644 index 0000000..5b76257 --- /dev/null +++ b/midicsv-1.1/Csvmidi.vcproj @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/midicsv-1.1/Makefile b/midicsv-1.1/Makefile new file mode 100644 index 0000000..75703f9 --- /dev/null +++ b/midicsv-1.1/Makefile @@ -0,0 +1,103 @@ + +CC = gcc +CFLAGS = -g -Wall + +INSTALL_DEST = /usr/local + +# You shouldn't need to change anything after this line + +VERSION = 1.1 +PROGRAMS = midicsv csvmidi +MANPAGES = $(PROGRAMS:%=%.1) midicsv.5 +DOC = README log.txt +BUILD = Makefile +SOURCE = csv.c csvmidi.c midicsv.c midio.c getopt.c getopt.h +HEADERS = csv.h midifile.h midio.h types.h version.h +EXAMPLES = test.mid bad.csv ce3k.csv acomp.pl chorus.pl \ + count_events.pl drummer.pl exchannel.pl general_midi.pl \ + transpose.pl torture.pl +WIN32EXE = Midicsv.exe Csvmidi.exe +WIN32 = $(WIN32EXE) Midicsv.sln Midicsv.vcproj Csvmidi.vcproj W32test.bat +DISTRIBUTION = $(DOC) $(BUILD) $(SOURCE) $(MANPAGES) $(HEADERS) $(EXAMPLES) $(WIN32) + +all: $(PROGRAMS) + +MIDICSV_OBJ = midicsv.o midio.o getopt.o + +midicsv: $(MIDICSV_OBJ) + $(CC) $(CFLAGS) -o midicsv midicsv.o midio.o getopt.o + +Midicsv.exe: $(MIDICSV_OBJ:%.o=%.c) + @echo 'Yar! Midicsv.exe needs to be rebuilt on WIN32!' + @exit 1 + +CSVMIDI_OBJ = csvmidi.o midio.o csv.o getopt.o + +csvmidi: $(CSVMIDI_OBJ) + $(CC) $(CFLAGS) -o csvmidi csvmidi.o midio.o csv.o getopt.o + +Csvmidi.exe: $(CSVMIDI_OBJ:%.o=%.c) + @echo 'Yar! Csvmidi.exe needs to be rebuilt on WIN32!' + @exit 1 + +check: all + @./midicsv test.mid /tmp/test.csv + @./csvmidi /tmp/test.csv /tmp/w.mid + @./midicsv /tmp/w.mid /tmp/w1.csv + @-cmp -s test.mid /tmp/w.mid ; if test $$? -ne 0 ; then \ + echo '** midicsv/csvmidi: MIDI file comparison failed. **' ; else \ + diff -q /tmp/test.csv /tmp/w1.csv ; if test $$? -ne 0 ; then \ + echo '** midicsv/csvmidi: CSV file comparison failed. **' ; else \ + echo 'All tests passed.' ; fi ; fi + @rm -f /tmp/test.csv /tmp/w.mid /tmp/w1.csv + +pipetest: all + ./midicsv test.mid | tee /tmp/test.csv | ./csvmidi | ./midicsv - /tmp/w1.csv + diff /tmp/test.csv /tmp/w1.csv + rm /tmp/test.csv /tmp/w1.csv + +torture: all + perl torture.pl | ./csvmidi | tee /tmp/w.mid | ./midicsv | ./csvmidi >/tmp/w1.mid + @cmp /tmp/w.mid /tmp/w1.mid ; if test $$? -ne 0 ; then \ + echo '** midicsv/csvmidi: Torture test CSV file comparison failed. **' ; else \ + echo 'Torture test passed.' ; fi + @rm /tmp/w.mid /tmp/w1.mid + +install: all + install -d -m 755 $(INSTALL_DEST)/bin + install -m 755 $(PROGRAMS) $(INSTALL_DEST)/bin + install -d -m 755 $(INSTALL_DEST)/man/man1 + install -m 644 midicsv.1 csvmidi.1 $(INSTALL_DEST)/man/man1 + install -d -m 755 $(INSTALL_DEST)/man/man5 + install -m 644 midicsv.5 $(INSTALL_DEST)/man/man5 + +uninstall: + rm -f $(INSTALL_DEST)/bin/csvmidi $(INSTALL_DEST)/bin/midicsv + rm -f $(INSTALL_DEST)/man/man1/csvmidi.1 $(INSTALL_DEST)/man/man1/midicsv.1 + rm -f $(INSTALL_DEST)/man/man5/midicsv.5 + +dist: $(WIN32EXE) + rm -f midicsv*.tar midicsv*.tar.gz + tar cfv midicsv.tar $(DISTRIBUTION) + mkdir midicsv-$(VERSION) + ( cd midicsv-$(VERSION) ; tar xfv ../midicsv.tar ) + rm -f midicsv.tar + tar cfv midicsv-$(VERSION).tar midicsv-$(VERSION) + gzip midicsv-$(VERSION).tar + rm -rf midicsv-$(VERSION) + rm -f midicsv-$(VERSION).zip + zip midicsv-$(VERSION).zip $(WIN32EXE) + +# Zipped archive for building WIN32 version +winarch: + rm -f midicsv.zip + zip midicsv.zip $(DISTRIBUTION) + +# Publish distribution on Web page (Fourmilab specific) +WEBDIR = $(HOME)/ftp/webtools/midicsv + +publish: dist + cp -p midicsv-$(VERSION).tar.gz midicsv-$(VERSION).zip $(WEBDIR) + +clean: + rm -f $(PROGRAMS) *.o *.bak core core.* *.out midicsv.zip diff --git a/midicsv-1.1/Midicsv.exe b/midicsv-1.1/Midicsv.exe new file mode 100755 index 0000000..649d3da Binary files /dev/null and b/midicsv-1.1/Midicsv.exe differ diff --git a/midicsv-1.1/Midicsv.sln b/midicsv-1.1/Midicsv.sln new file mode 100644 index 0000000..1ac04e3 --- /dev/null +++ b/midicsv-1.1/Midicsv.sln @@ -0,0 +1,27 @@ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Csvmidi", "Csvmidi.vcproj", "{FA22F5FC-B0B2-4626-B402-7EE4E023CA5B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Midicsv", "Midicsv.vcproj", "{2A76FE95-C368-4A6F-BFB7-EAF1DB195CCB}" +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + ConfigName.0 = Debug + ConfigName.1 = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {FA22F5FC-B0B2-4626-B402-7EE4E023CA5B}.Debug.ActiveCfg = Debug|Win32 + {FA22F5FC-B0B2-4626-B402-7EE4E023CA5B}.Debug.Build.0 = Debug|Win32 + {FA22F5FC-B0B2-4626-B402-7EE4E023CA5B}.Release.ActiveCfg = Release|Win32 + {FA22F5FC-B0B2-4626-B402-7EE4E023CA5B}.Release.Build.0 = Release|Win32 + {2A76FE95-C368-4A6F-BFB7-EAF1DB195CCB}.Debug.ActiveCfg = Debug|Win32 + {2A76FE95-C368-4A6F-BFB7-EAF1DB195CCB}.Debug.Build.0 = Debug|Win32 + {2A76FE95-C368-4A6F-BFB7-EAF1DB195CCB}.Release.ActiveCfg = Release|Win32 + {2A76FE95-C368-4A6F-BFB7-EAF1DB195CCB}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/midicsv-1.1/Midicsv.vcproj b/midicsv-1.1/Midicsv.vcproj new file mode 100644 index 0000000..a9d9361 --- /dev/null +++ b/midicsv-1.1/Midicsv.vcproj @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/midicsv-1.1/README b/midicsv-1.1/README new file mode 100644 index 0000000..06328dc --- /dev/null +++ b/midicsv-1.1/README @@ -0,0 +1,134 @@ + + MIDI File CSV Editing Tools + + by John Walker + http://www.fourmilab.ch/ + +This distribution contains tools which permit you to convert +standard MIDI files to a CSV (Comma-Separated Format) +equivalent which preserves all the information in the MIDI file +and then translate such CSV files back to standard MIDI files. + +Exporting MIDI files as CSV makes it easy to manipulate them +with speadsheets, database utilities, or programs in languages +such as Perl and C. + +The following files are included in the distribution: + + C Source Code + csv.c CSV input parser + csv.h Definitions for csv.c + csvmidi.c CSV to MIDI translator + getopt.c Command line option parser + getopt.h Definitions for getopt.c + midicsv.c MIDI to CSV translator + midifile.h MIDI file definitions + midio.c MIDI file I/O routines + midio.h Definitions for midio.c + types.h Common type definitions + version.h Version number definition + + Build Utilities + Makefile Make file + + Sample MIDI Manipulation Programs and Data + acomp.pl Simple-minded algorithmic composition + example. + bad.csv CSV file chock full of errors to test + csvmidi error detection and recovery. + ce3k.csv Sample MIDI file in CSV format: + "Close Encounters of the Third Kind" + count_events.pl Perl program which counts events by type + in a CSV MIDI file and prints the results. + chorus.pl Perl program which adds a one + octave lower chorus to all notes in + a MIDI file. + drummer.pl Dumbest possible drum machine; illustrates + MIDI generation from scratch. + exchannel.pl Perl program which extracts all events on + a given channel (General MIDI percussion channel + as supplied), passing through meta-events + unchanged. + general_midi.pl Perl include file which defines hashes + that allow General MIDI patch and percussion + note assignments to be specified symbolically. + torture.pl Perl program to generate csvmidi/midicsv + "torture test". Include all event types and + verifies handling of long strings and byte + sequences with arbitrary content. + transpose.pl Perl program which transposes a MIDI + file, shifting all notes down one octave + test.mid Sample MIDI file: "Silent Running" by Mike + and the Mechanics + + Documentation + README This file + csvmidi.1 csvmidi manual page + log.txt Development log + midicsv.1 midicsv manual page + midicsv.5 MIDI CSV representation file format documentation + + WIN32 Executable Files + Csvmidi.exe Csvmidi WIN32 executable + Midicsv.exe Midicsv WIN32 executable + + WIN32 Build Files (for Microsoft Visual Studio .NET) + Midicsv.sln Solution (Workspace) for Csvmidi and Midicsv + Csvmidi.vcproj Csvmidi project file + Midicsv.vcproj Midicsv project file + W32test.bat Test script for WIN32 programs + +BUILDING AND INSTALLATION + +I can't bring myself to burden such a small, simple set of programs as +this with a grotesque Autoconf script which would dwarf the source +code. Just edit the Makefile and change the C compiler and options +if necessary, and the install directory tree if you wish to use +the install target. The build the program with: + + make + +and run the self-test with: + + make check + +which should report "All tests passed." if all is well. If you're +patient, have Perl installed on your system, and have lots of free +disc space, you can run the "torture test" with: + + make torture + +You may then proceed to install the programs and manual pages +with + + make install + +which, unless you've changed the INSTALL_DEST directory in the +Makefile, will install into /usr/local, which will require you +to perform the installation with super-user privilege. + +COMPATIBILITY NOTES + +These programs were originally developed on an MS-DOS system +with a compiler in which the "int" type was 16 bits, but it's +been a long time since they've been compiled with such a +compiler so it's unlikely they'll work with 16 bit ints without +some tweaking. The programs don't care whether the "char" type +is signed or unsigned. I've not tested the programs on a +system in which "int" or "long" is wider than 32 bits, but I +don't anticipate any problems. + +On systems such as WIN32 which distinguish text from binary files, +it is essential that midicsv.c open its input file and csvmidi.c +open its output file in *binary* mode, otherwise the MIDI file will +be corrupted due to end of line sequence translation. If input +and output file names are specified explicitly, the "b" option supplied +in the fopen() call should take care of this (and cause no damage on +modern Unix systems, which ignore this option). When reading or writing +to standard input/output, however, the open file must be explicitly set +to binary mode, and the means for doing this vary among development +systems. The midicsv.c and csvmidi.c contain code conditional on +_WIN32 which sets binary file mode with the mechanism provided by +the Microsoft Visual C/C++ library; if you're building with a different +compiler and library, you may need to change this code accordingly. + diff --git a/midicsv-1.1/W32test.bat b/midicsv-1.1/W32test.bat new file mode 100644 index 0000000..e08c717 --- /dev/null +++ b/midicsv-1.1/W32test.bat @@ -0,0 +1,7 @@ +@Rem Test release versions of Midicsv and Csvmidi with +@Rem an identity transform. + +Release\Midicsv test.mid w.csv +Release\Csvmidi w.csv w.mid +Rem The following comparison should not find any differences +cmp test.mid w.mid diff --git a/midicsv-1.1/acomp.pl b/midicsv-1.1/acomp.pl new file mode 100644 index 0000000..dd36f5c --- /dev/null +++ b/midicsv-1.1/acomp.pl @@ -0,0 +1,69 @@ + + # Incredibly dumb algorithmic composer + + require 'general_midi.pl'; + + $instrument = $GM_Patch{'Distortion Guitar'}; + $tonespan = 32; + $num_notes = 120; + $percussion = $GM_Percussion{'Ride Cymbal 1'}; + $beat = 6; + + print << "EOD"; +0, 0, Header, 1, 1, 480 +1, 0, Start_track +1, 0, Tempo, 500000 +1, 0, Program_c, 1, $instrument +EOD + + $time = 0; + srand(time()); + + for ($i = 0; $i < $num_notes; $i++) { + $n = 60 + int((rand() * $tonespan) - int($tonespan / 2)); + $notelength = 120 + (60 * int(rand() * 6)); + ¬e(1, $n, $notelength, 127); + if (($i % $beat) == 0) { + print("1, $time, Note_on_c, 9, $percussion, 127\n"); + } elsif (($i % $beat) == ($beat - 1)) { + print("1, $time, Note_off_c, 9, $percussion, 0\n"); + } + } + + # Cymbal crash at end + $cymbal = $GM_Percussion{'Crash Cymbal 2'}; + print("1, $time, Note_on_c, 9, $cymbal, 127\n"); + $time += 480; + print("1, $time, Note_off_c, 9, $cymbal, 0\n"); + + # Audience applause + $time += 480; + print("1, $time, Program_c, 1, $GM_Patch{'Applause'}\n"); + print("1, $time, Note_on_c, 1, 60, 100\n"); + for ($i = 16; $i <= 32; $i++) { + $time += 120; + $v = int(127 * ($i / 32)); + print("1, $time, Poly_aftertouch_c, 1, 60, $v\n"); + } + for ($i = 32; $i >= 0; $i--) { + $time += 240; + $v = int(127 * ($i / 32)); + print("1, $time, Poly_aftertouch_c, 1, 60, $v\n"); + } + print("1, $time, Note_off_c, 1, 60, 0\n"); + + print << "EOD"; +1, $time, End_track +0, 0, End_of_file +EOD + + sub note { # ¬e($channel, $note_number, $duration [, $velocity]) + local ($channel, $which, $duration, $vel) = @_; + + if (!defined($vel)) { + $vel = 127; + } + print("1, $time, Note_on_c, $channel, $which, $vel\n"); + $time += $duration; + print("1, $time, Note_off_c, $channel, $which, 0\n"); + } diff --git a/midicsv-1.1/bad.csv b/midicsv-1.1/bad.csv new file mode 100644 index 0000000..e6b79ed --- /dev/null +++ b/midicsv-1.1/bad.csv @@ -0,0 +1,39 @@ +0, 0, Header, 1, 2, 480 +1, 0, Start_track +1, 0, Title_t, "Close Encounters" +1, 0, Text_t, "Sample for MIDIcsv Distribution" +1, 0, Copyright_t, "This file is in the public domain" +1, 0, Time_signature, 4, 2, 24, 8 +1, 0, Tempo, 500000 +1, 0, End_track +2, -11, Start_track +2, 0, Instrument_name_t, "Church Organ" +2, 0, Program_c, 1, 19 +2, -6, Note_on_c, 1, 79, 81 +2, 100, BogusBogusBogus, 1, 111 +2, 960, Note_off_c, 1, 79, 0 +2, 960, Note_on_c, 1, 81, 81 +2, 1920, Note_off_c, 1, 81, 0 +4, 1920, Note_on_c, 1, 77, 81 +2, 2880, Note_off_c, 1, 77, 0 +2, 2880, Note_on_c, -3, 300, 81 +2, 2880, Pitch_bend_c, 1, 99999 +999, -17, Note_off_c, 1, 65, 0 +2, 3840 +2, 3840, Note_off_c, 1, 65, 0 +2, 3840, Note_on_c, +2, 3840, Note_on_c, 1, 72 +2, 3840, Note_on_c, 1, 72, 81 +2, 3900, system_exclusive, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 +2, 3900, system_exclusive_packet, 10, 1, 2, 3, 4, 5, 6, 7 +2, 3900, sequencer_specific, 4, 1, 2, 3, 4 +2, 3900, sequencer_specific, 4, 1, 2, 3 +2,3900, unknown_meta_event, 121, 5, 1, 2, 3, 4, 5 +2,3900, unknown_meta_event, 121, 5, 1, 2, 3 +# The following event is actually +# 4, 3030, Key_signature, -4, "major" +# coded as an Unknown_meta_event. +2, 3900, Unknown_meta_event, 89, 2, 252, 0 +2, 4800, Note_off_c, 1, 72, 0 +2, 4800, End_track +0, 0, End_of_file diff --git a/midicsv-1.1/ce3k.csv b/midicsv-1.1/ce3k.csv new file mode 100644 index 0000000..de66411 --- /dev/null +++ b/midicsv-1.1/ce3k.csv @@ -0,0 +1,23 @@ +0, 0, Header, 1, 2, 480 +1, 0, Start_track +1, 0, Title_t, "Close Encounters" +1, 0, Text_t, "Sample for MIDIcsv Distribution" +1, 0, Copyright_t, "This file is in the public domain" +1, 0, Time_signature, 4, 2, 24, 8 +1, 0, Tempo, 500000 +1, 0, End_track +2, 0, Start_track +2, 0, Instrument_name_t, "Church Organ" +2, 0, Program_c, 1, 19 +2, 0, Note_on_c, 1, 79, 81 +2, 960, Note_off_c, 1, 79, 0 +2, 960, Note_on_c, 1, 81, 81 +2, 1920, Note_off_c, 1, 81, 0 +2, 1920, Note_on_c, 1, 77, 81 +2, 2880, Note_off_c, 1, 77, 0 +2, 2880, Note_on_c, 1, 65, 81 +2, 3840, Note_off_c, 1, 65, 0 +2, 3840, Note_on_c, 1, 72, 81 +2, 4800, Note_off_c, 1, 72, 0 +2, 4800, End_track +0, 0, End_of_file diff --git a/midicsv-1.1/chorus.pl b/midicsv-1.1/chorus.pl new file mode 100644 index 0000000..00b398f --- /dev/null +++ b/midicsv-1.1/chorus.pl @@ -0,0 +1,28 @@ + + # Chorus all notes in a CSV MIDI file + + $offset = -12; + $percussion = 9; + + while ($a = <>) { + print($a); + + # Recognise Note_on_c and Note_off_c records and crack into: + + # $1 Start of record + # $2 Channel number + # $3 Note number + # $a Balance of record + + if ($a =~ s/(\d+,\s*\d+,\s*Note_\w+,\s*(\d+),\s*)(\d+)//) { + if ($2 != $percussion) { + $n = $3; + $n += $offset; + if ($n < 0) { + next; + } + $a = "$1$n$a"; + print($a); + } + } + } diff --git a/midicsv-1.1/count_events.pl b/midicsv-1.1/count_events.pl new file mode 100644 index 0000000..377dcd4 --- /dev/null +++ b/midicsv-1.1/count_events.pl @@ -0,0 +1,18 @@ + + # Count number of events by type in CSV MIDI file + # and report. + + while ($a = <>) { + + if (!($a =~ m/\s*[\#\;]/)) { # Ignore comment lines + if ($a =~ m/\d+\s*,\s*\d+\s*,\s*(\w+)/) { + $events{$1}++; + } else { + print("Cannot parse: $a"); + } + } + } + + foreach $k (sort(keys(%events))) { + printf("%9d %s\n", $events{$k}, $k); + } diff --git a/midicsv-1.1/csv.c b/midicsv-1.1/csv.c new file mode 100644 index 0000000..ada7d75 --- /dev/null +++ b/midicsv-1.1/csv.c @@ -0,0 +1,146 @@ +/* + + Comma separated value database format scanner + + This function implements a somewhat extended flavour + of CSV. In addition to the standard quoted fields, + permitting embedded commas, with embedded quotes + represented as "", backslash escaped characters + expressed as three octal digits are also permitted, + with a double backslash representing an embedded + backslash. This is necessary to permit fields + which include end-of-line delimiters which would + otherwise truncate the record when it is read. + +*/ + +#include +#include +#include +#include + +#include "csv.h" + +#define BufferInitial 256 /* Initial buffer size */ +#define BufferExpansion 1024 /* Buffer expansion increment */ + +#define EOS '\0' + +#define FALSE 0 +#define TRUE 1 + +static const char *csptr; /* CSV scan pointer */ + +/* CSVSCANINIT -- Initialise scanning of a CSV record. */ + +void CSVscanInit(const char *s) +{ + csptr = s; +} + +/* CSVSCANFIELD -- Scan next field from a CSV record. The actual + length of the field is placed in the + global variable CSVfieldLength. If the + field is not quoted, leading and trailing + spaces are discarded. + + The argument b_f is a pointer to a +*/ + +int CSVfieldLength = 0; /* Length of CSV field scanned */ + +#define f (*b_f) +#define flen (*b_flen) + +static void expand_buf(char **b_f, int *b_flen) +{ + if ((f == NULL) || (flen == 0)) { + f = (char *) malloc(BufferInitial); + if (f == NULL) { + fprintf(stderr, "Unable to allocate %d byte CSV field buffer.\n", BufferInitial); + abort(); + } + flen = BufferInitial; + } else { + flen += BufferExpansion; + f = (char *) realloc(f, flen); + if (f == NULL) { + fprintf(stderr, "Unable to expand CSV field buffer to %d bytes.\n", flen); + abort(); + } + } +} + +#define store(c) if (CSVfieldLength >= (flen - 1)) \ + { expand_buf(b_f, b_flen); } \ + f[CSVfieldLength] = c; \ + CSVfieldLength++; + +int CSVscanField(char **b_f, int *b_flen) +{ + int foundfield = FALSE, quoted = FALSE; + + CSVfieldLength = 0; + if (*csptr != EOS) { + foundfield = TRUE; + while ((*csptr != EOS) && isspace(*csptr)) { + csptr++; + } + if (*csptr == '"') { + quoted = TRUE; + csptr++; + while (*csptr != EOS) { + if (*csptr == '"') { + if (csptr[1] == '"') { + store('"'); + csptr += 2; + } else { + csptr++; + break; + } + } else if (*csptr == '\\') { + if (csptr[1] == '\\') { + store('\\'); + csptr += 2; + } else { + unsigned int v = 0; + int i; + + for (i = 0; i < 3; i++) { + csptr++; + if ((*csptr >= '0') && (*csptr <= '7')) { + v = (v << 3) | (*csptr - '0'); + } else { + csptr--; + } + } + csptr++; + store((char) v); + } + } else { + char c = *csptr++; + store(c); + } + } + } + while (*csptr != ',' && *csptr != EOS) { + char c = *csptr++; + store(c); + } + if (*csptr == ',') { + csptr++; + } + } + f[CSVfieldLength] = EOS; /* Append C string terminator */ + + /* If the field wasn't quoted, elide any trailing spaces. */ + + if (foundfield && !quoted) { + while (CSVfieldLength > 0 && isspace(f[CSVfieldLength - 1])) { + f[--CSVfieldLength] = EOS; + } + } + return foundfield; +} +#undef f +#undef flen diff --git a/midicsv-1.1/csv.h b/midicsv-1.1/csv.h new file mode 100644 index 0000000..6d7e54e --- /dev/null +++ b/midicsv-1.1/csv.h @@ -0,0 +1,9 @@ +/* + + CSV Parsing Function Definitions + +*/ + +extern void CSVscanInit(const char *s); +extern int CSVscanField(char **b_f, int *b_flen); +extern int CSVfieldLength; /* Length of CSV field scanned */ diff --git a/midicsv-1.1/csv.o b/midicsv-1.1/csv.o new file mode 100644 index 0000000..3af2ce6 Binary files /dev/null and b/midicsv-1.1/csv.o differ diff --git a/midicsv-1.1/csvmidi b/midicsv-1.1/csvmidi new file mode 100755 index 0000000..29165ea Binary files /dev/null and b/midicsv-1.1/csvmidi differ diff --git a/midicsv-1.1/csvmidi.1 b/midicsv-1.1/csvmidi.1 new file mode 100644 index 0000000..964e2df --- /dev/null +++ b/midicsv-1.1/csvmidi.1 @@ -0,0 +1,128 @@ +'\" t +.TH CSVMIDI 1 "9 FEB 2004" +.UC 4 +.SH NAME +csvmidi \- encode CSV file as MIDI +.SH SYNOPSIS +.B csvmidi +[ +.B \-u +.B \-v +.B \-x +.B \-z +] [ +.I infile +[ +.I outfile +] ] +.SH DESCRIPTION +.B csvmidi +reads a +CSV (Comma-Separated Value) file in the format written +by +.B midicsv +and creates the equivalent standard MIDI file. +.SH OPTIONS +.TP 10 +.B \-u +Print how-to-call information. +.TP +.B \-v +Print verbose debugging information on standard +error. The MIDI file header is dumped, along +with the length of each track in the file. +.TP +.B \-x +MIDI streams support a rudimentary form of compression +in which successive events with the same ``status'' +(event type and channel) may omit the status byte. +By default +.B csvmidi +avails itself of this compression. +If the +.B \-x +option is specified, the status byte is emitted +for all events\-it is never compressed even when the +MIDI standard permits it to be. +.TP +.B \-z +Most errors detected in CSV records cause a warning message +to be displayed on standard error and the record +ignored. The +.B \-z +option causes +.B csvmidi +to immediately terminate processing when the first error +is detected. +.SH "EXIT STATUS" +If no errors or warnings are detected +.B csvmidi +exits with status 0. A status of of 1 is returned if one +or more errors were detected in the CSV input file, while +a status of 2 indicates a syntax error on the command line or +inability to open the input or output file. +.SH FILES +If no +.I infile +is specified or +.I infile +is +.RB `` \- '', +.B csvmidi +reads its input from standard input; if no +.I outfile +is given or +.I outfile +is +.RB `` \- '', +MIDI output is written to standard output. The input and +output are processed in a strictly serial manner; consequently +.B csvmidi +may be used in pipelines without restrictions. +.SH BUGS +.PP +.B csvmidi +assumes its input is in the format written by +.BR midicsv . +If supplied a CSV file with well-formed records which +nonetheless makes no semantic sense as a MIDI file, the results +will, in all likelihood, simply perplex any program or instrument +to which it's sent. +.B csvmidi +checks for missing fields and range checks +all numeric values, but does not perform higher-level +consistency checking (for example, making sure that every +note on event is paired with a subsequent note off). That +level of verification, if required, should be done on the +CSV file before it is processed by +.BR csvmidi . +.PP +Exporting a file to CSV with +.B midicsv +and then importing it with +.B csvmidi +is not guaranteed to create an identical MIDI file. MIDI +files support compression modes which are not obligatory. +A MIDI file exported to CSV and then re-imported should, +however, be +.I equivalent +to the original file and should, if exported to CSV, be +identical to the CSV exported from the original file. +.PP +Please report problems to +.BR bugs@fourmilab.ch . +.SH "SEE ALSO" +.PD +.BR midicsv (1), +.BR midicsv (5) +.ne 10 +.SH AUTHOR +.ce 2 +John Walker +http://www.fourmilab.ch/ +.PP +This software is in the public domain. +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +without any conditions or restrictions. This software is provided ``as +is'' without express or implied warranty. diff --git a/midicsv-1.1/csvmidi.c b/midicsv-1.1/csvmidi.c new file mode 100644 index 0000000..3e3d94d --- /dev/null +++ b/midicsv-1.1/csvmidi.c @@ -0,0 +1,854 @@ +/* + + Encode CSV file into MIDI + + The CSV file must be in the format generated by midicsv--while + you can add and delete events and change their contents, the + overall organisation of the file must be the same. + + Designed and implemented in October of 1998 by John Walker. + Revised and updated in February of 2004 by the same perp. + + http://www.fourmilab.ch/ + + This program is in the public domain. + +*/ + +#define PROG "csvmidi" + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#define strcasecmp _stricmp +#endif + +#include "version.h" +#include "types.h" +#include "midifile.h" +#include "midio.h" +#include "csv.h" +#include "getopt.h" + +/* List of comment delimiters recognised in CSV files. + These are whole-line comments which must be marked + by one of the following characters as the first + nonblank character of a record. Rest-of-line comments + are not implemented. Since the track number always + begins a data record, any non-numeric character may + be used as a comment delimiter. */ + +#define COMMENT_DELIMITERS "#;" + +#define FALSE 0 +#define TRUE 1 + +#define ELEMENTS(array) (sizeof(array)/sizeof((array)[0])) + +/* Codes for control items. */ + +typedef enum { + Header, + Start_track, + End_of_file +} controlMessages; + +/* The following table lists all possible item codes which may + appear in a CSV-encoded MIDI file. These should be listed + in order of frequency of occurrence since the list is + searched linearly. */ + +struct mitem { + char *name; + int icode; +}; + +#define EVENT 0 +#define META 0x100 +#define MARKER 0x200 + +#define Event(x) ((x) | EVENT) +#define Meta(x) ((x) | META) +#define Marker(x) ((x) | MARKER) + +static struct mitem mitems[] = { + { "Note_on_c", Event(NoteOn) }, + { "Note_off_c", Event(NoteOff) }, + { "Pitch_bend_c", Event(PitchBend) }, + { "Control_c", Event(ControlChange) }, + { "Program_c", Event(ProgramChange) }, + { "Poly_aftertouch_c", Event(PolyphonicKeyPressure) }, + { "Channel_aftertouch_c", Event(ChannelPressure) }, + + { "System_exclusive", Event(SystemExclusive) }, + { "System_exclusive_packet", Event(SystemExclusivePacket) }, + + { "Sequence_number", Meta(SequenceNumberMetaEvent) }, + { "Text_t", Meta(TextMetaEvent) }, + { "Copyright_t", Meta(CopyrightMetaEvent) }, + { "Title_t", Meta(TrackTitleMetaEvent) }, + { "Instrument_name_t", Meta(TrackInstrumentNameMetaEvent) }, + { "Lyric_t", Meta(LyricMetaEvent) }, + { "Marker_t", Meta(MarkerMetaEvent) }, + { "Cue_point_t", Meta(CuePointMetaEvent) }, + { "Channel_prefix", Meta(ChannelPrefixMetaEvent) }, + { "MIDI_port", Meta(PortMetaEvent) } , + { "End_track", Meta(EndTrackMetaEvent) }, + { "Tempo", Meta(SetTempoMetaEvent) }, + { "SMPTE_offset", Meta(SMPTEOffsetMetaEvent) }, + { "Time_signature", Meta(TimeSignatureMetaEvent) }, + { "Key_signature", Meta(KeySignatureMetaEvent) }, + { "Sequencer_specific", Meta(SequencerSpecificMetaEvent) }, + { "Unknown_meta_event", Meta(0xFF) }, + + { "Header", Marker(Header) }, + { "Start_track", Marker(Start_track) }, + { "End_of_file", Marker(End_of_file) }, +}; + +static char *progname; /* Program name string */ +static int verbose = FALSE; /* Debug output */ +static int zerotol = FALSE; /* Any warning terminates processing */ +static int errors = 0; /* Errors and warnings detected */ +static char *s = NULL; /* Dynamically expandable CSV input buffer */ + +/* OUTBYTE -- Store byte in track buffer. */ + +static byte of[10]; +static byte *trackbuf = NULL, *trackbufp; +static int trackbufl; +static char *f = NULL; +static int flen = 0; + +#define Warn(msg) { errors++; fprintf(stderr, "%s: Error on line %d:\n %s\n %s.\n", PROG, lineno, s, msg); if (zerotol) { exit(1); } } + +static void outbyte(const byte c) +{ + long l = trackbufp - trackbuf; + + if (l >= trackbufl) { + trackbuf = realloc(trackbuf, trackbufl += 16384); + if (trackbuf == NULL) { + fprintf(stderr, "%s: Unable to allocate memory to expand track buffer to %d bytes.\n", PROG, trackbufl); + exit(2); + } + trackbufp = trackbuf + l; + } + *trackbufp++ = c; +} + +/* OUTEVENT -- Output event, optimising repeats. */ + +static long abstime, tabstime = 0; +static void outVarLen(const vlint value); +static int optimiseStatus = TRUE, lastStatus = -1; + +static void outevent(const byte c) +{ + outVarLen(abstime - tabstime); + tabstime = abstime; + if (!optimiseStatus || (c != lastStatus)) { + outbyte(c); + lastStatus = c; + } +} + +/* OUTMETA -- Output file meta-event. */ + +static void outmeta(const byte c) +{ + /* Running status may not be used by file meta-events, + and meta-events cancel any running status. */ + lastStatus = -1; + outevent(FileMetaEvent); + outbyte(c); +} + +/* OUTSHORT -- Output two-byte value to track buffer. */ + +static void outshort(const short v) +{ + outbyte((byte) ((v >> 8) & 0xFF)); + outbyte((byte) (v & 0xFF)); +} + +/* OUTBYTES -- Output a linear array of bytes. */ + +static void outbytes(const byte *s, const int n) +{ + int i; + + outVarLen(n); + for (i = 0; i < n; i++) { + outbyte(*s++); + } +} + +/* OUTVARLEN -- Output variable length number. */ + +static void outVarLen(const vlint v) +{ + vlint value = v; + long buffer; + + buffer = value & 0x7F; + while ((value >>= 7) > 0) { + buffer <<= 8; + buffer |= 0x80; + buffer += (value & 0x7F); + } + + while (TRUE) { + outbyte((byte) (buffer & 0xFF)); + if (buffer & 0x80) { + buffer >>= 8; + } else { + break; + } + } +} + +/* XFIELDS -- Parse one or more numeric fields. Returns FALSE if + all fields were scanned successfully, TRUE if an + error occurred. The fbias argument gives the absolute + field number of the first field to be parsed; it is used + solely to identify fields in error messages. */ + +#define MAX_NFIELDS 10 + +static int lineno = 0; +static long nfld[MAX_NFIELDS]; +static char *csvline; + +static int xfields(const int n, const int fbias) +{ + int i; + +#ifndef ndebug + if (n > MAX_NFIELDS) { + fprintf(stderr, "%s: Internal error: nfields(%d) exceeds max of %d.\n", + PROG, n, MAX_NFIELDS); + abort(); + } +#endif + for (i = 0; i < n; i++) { + if (!CSVscanField(&f, &flen)) { + errors++; + fprintf(stderr, "%s: Error on line %d:\n %s\n Missing field %d.\n", + PROG, lineno, s, fbias + i); + return TRUE; + } + if (sscanf(f, "%ld", &nfld[i]) != 1) { + errors++; + fprintf(stderr, "%s: Error on line %d:\n %s\n Invalid field %d.\n", + PROG, lineno, s, fbias + i); + return TRUE; + } + } + return FALSE; +} + +/* XFIELDS -- Parse one or more numeric fields. This is a + wrapper for xfields which specifies the default + starting field of 4 used by most events. */ + +static int nfields(const int n) +{ + return xfields(n, 4); +} + +/* CHECKBYTES -- Pre-parse a sequence of arguments representing + a byte vector. If an error is detected, return + TRUE. Otherwise, reset the CSV parser to the + first byte of the sequence and return FALSE. This + permits code which handles arbitrary length byte + vectors to recover from syntax errors and ignore + the line prior to the irreversible step of emitting + the event type and length. */ + +static int checkBytes(const int fieldno, const int length) +{ + int i; + + for (i = 0; i < length; i++) { + if (xfields(1, i + fieldno)) { + if (zerotol) { + exit(1); + } + return TRUE; + } + } + + /* The preliminary parsing pass passed. (Got that?) Now restart + the CSV parser at the start of the line and ignore fields prior + to the first byte of the data, leaving them ready to be processed + by the actual translation code. */ + + CSVscanInit(s); + for (i = 0; i < (fieldno - 1); i++) { + CSVscanField(&f, &flen); + } + return FALSE; +} + +/* GETCSVLINE -- Get next line from CSV file. Reads into a dynamically + allocated buffer s which is expanded as required to + accommodate longer lines. All standard end of line + sequences are handled transparently, and the line + is returned with the end line sequence stripped + with a C string terminator (zero byte) appended. */ + +static int getCSVline(FILE *fp) +{ + static int sl = 0; + int c, ll = -1; + + if (s == NULL) { + sl = 1024; + s = (char *) malloc(sl); + if (s == NULL) { + fprintf(stderr, "%s: Unable to allocate %d byte CSV input line buffer.\n", + PROG, sl); + exit(2); + } + } + + while ((c = getc(fp)) >= 0) { + + /* Test for end of line sequence. We accept either a carriage return or + line feed as an end of line delimiter, optionally followed by no more + than one of the other delimiter. */ + + if (c == '\n') { + c = getc(fp); + if (c != '\r') { + ungetc(c, fp); + } + break; + } + if (c == '\r') { + c = getc(fp); + if (c != '\n') { + ungetc(c, fp); + } + break; + } + + /* Increment line length, expand buffer if necessary, and store character + in buffer. */ + + ll++; + if (ll >= (sl - 1)) { + sl += 1024; + s = (char *) realloc(s, sl); + if (s == NULL) { + fprintf(stderr, "%s: Unable to expand CSV input line buffer to %d bytes.\n", + PROG, sl); + exit(2); + } + } + s[ll] = c; + } + + /* If we got a line, append a C string terminator to it. */ + + if (ll >= 0) { + s[ll + 1] = 0; + } + + return (ll >= 0); +} + +/* CLAMP -- Constrain a numeric value to be within a specified + inclusive range. If the value is outside the range + an error message is issued and the value is forced to + the closest limit of the range. */ + +static void clamp(long *value, const long minval, const long maxval, const char *fieldname) +{ + if (((*value) < minval) || ((*value) > maxval)) { + char errm[256]; + + sprintf(errm, "%s out of range. Value (%ld) outside limits of %ld to %ld", + fieldname, *value, minval, maxval); + Warn(errm); + + if ((*value) < minval) { + *value = minval; + } else { + *value = maxval; + } + } +} + +/* Main program */ + +int main(int argc, char *argv[]) +{ + struct mhead mh; + struct mtrack mt; + FILE *fp = stdin, *fo = stdout; + int i, n, track, rtype, etype, + headerseen = FALSE, eofseen = FALSE, intrack = 0, ntrack = 0; + char errm[256]; + long tl; + + /* Parse command line arguments. */ + + progname = argv[0]; + csvline = s; + while ((n = getopt(argc, argv, "uvxz")) != -1) { + switch (n) { + case 'u': + fprintf(stderr, "Usage: %s [ options ] [ csv_file ] [ midi_file ]\n", progname); + fprintf(stderr, " Options:\n"); + fprintf(stderr, " -u Print this message\n"); + fprintf(stderr, " -v Verbose: dump header and track information\n"); + fprintf(stderr, " -x Expand running status\n"); + fprintf(stderr, " -z Abort on any warning message\n"); + fprintf(stderr, "Version %s\n", VERSION); + return 0; + + case 'v': + verbose = TRUE; + break; + + case 'x': + optimiseStatus = FALSE; + break; + + case 'z': + zerotol = TRUE; + break; + + case '?': + fprintf(stderr, "%s: undefined option -%c specified.\n", + PROG, n); + return 2; + } + } + + /* Open input and output files, if supplied. Otherwise + standard input and output are used. A file name of + "-" is equivalent to no specification. */ + + i = 0; + while (optind < argc) { + switch (i++) { + case 0: + if (strcmp(argv[optind], "-") != 0) { + fp = fopen(argv[optind], "r"); + if (fp == NULL) { + fprintf(stderr, "%s: Unable to to open CSV input file %s\n", + PROG, argv[optind]); + return 2; + } + } + break; + + case 1: + if (strcmp(argv[optind], "-") != 0) { + fo = fopen(argv[optind], "wb"); + if (fo == NULL) { + fprintf(stderr, "%s: Unable to to create MIDI output file %s\n", + PROG, argv[optind]); + return 2; + } + } + break; + } + optind++; + } + +#ifdef _WIN32 + + /* If output is to standard output, set the output + file mode to binary. */ + + if (fo == stdout) { + _setmode(_fileno(fo), _O_BINARY); + } +#endif + + /* Allocate initial track assembly buffer. The buffer is + expanded as necessary. */ + + trackbufp = trackbuf = (byte *) malloc(trackbufl = 16384); + memcpy(mt.chunktype, MIDI_Track_Sentinel, sizeof mt.chunktype); + +#define Nfields(n) { if (nfields(n)) { if (zerotol) { exit(1); } else { continue; } } } +#define Clamp(fld, low, high, what) clamp(&nfld[fld - 4], low, high, "Field " # fld " (" what ")") + + while (getCSVline(fp)) { + char *p = s + strlen(s); + + lineno++; + + /* Trim any white space from the end of the record. */ + + while (p >= s && isspace(*(p - 1))) { + *(--p) = 0; + } + + /* Test for and ignore blank lines and comments. A comment + is any line whose first character (ignoring leading white + space) is one of the COMMENT_DELIMITERS). Note that we + do not permit in-line comments, although the user is free + to supply extra CSV fields which we ignore for that + purpose. */ + + p = s; + while ((*p != 0) && isspace(*p)) { + p++; + } + if ((*p == 0) || (strchr(COMMENT_DELIMITERS, *p) != NULL)) { + continue; + } + + CSVscanInit(s); + + /* Scan track, absolute time, and record type. These are + present in all records. */ + + if (CSVscanField(&f, &flen)) { + track = atoi(f); + } else { + Warn("Missing track number (field 1)"); + continue; + } + tl = track; + clamp(&tl, 0, 65535, "Field 1 (Track)"); + track = (int) tl; + + if (CSVscanField(&f, &flen)) { + abstime = atol(f); + } else { + Warn("Missing absolute time (field 2)"); + continue; + } + clamp(&abstime, 0, 0x7FFFFFFF, "Field 2 (Time)"); + + if (!CSVscanField(&f, &flen)) { + Warn("Missing record type (field 3)"); + continue; + } + + /* Look up record type and dispatch to appropriate handler. */ + + rtype = -1; + for (i = 0; i < ELEMENTS(mitems); i++) { + if (strcasecmp(f, mitems[i].name) == 0) { + rtype = mitems[i].icode; + break; + } + } + + if (rtype < 0) { + sprintf(errm, "Unknown record type: \"%s\"", f); + Warn(errm); + continue; + } + + etype = rtype & 0xFF; + + /* File structure pseudo-events. These records do + not correspond to items in the MIDI file. */ + + switch (rtype) { + + case Marker(Header): + if (headerseen) { + Warn("Duplicate header record"); + continue; + } + Nfields(3); + memcpy(mh.chunktype, MIDI_Header_Sentinel, sizeof mh.chunktype); + mh.length = 6; + clamp(&(nfld[0]), 0, 2, "Field 4 (Format)"); + mh.format = (short) nfld[0]; + clamp(&(nfld[1]), 0, 65535, "Field 5 (Number of tracks)"); + mh.ntrks = (short) nfld[1]; + clamp(&(nfld[2]), 0, 65535, "Field 6 (Pulses per quarter note)"); + mh.division = (short) nfld[2]; + writeMidiFileHeader(fo, &mh); + if (verbose) { + fprintf(stderr, "Format %d MIDI file. %d tracks, %d ticks per quarter note.\n", + mh.format, mh.ntrks, mh.division); + } + headerseen = TRUE; + continue; + + case Marker(Start_track): + if (intrack != 0) { + Warn("Previous track end missing"); + continue; + } + intrack = track; + tabstime = 0; + continue; + + case Marker(End_of_file): + eofseen = TRUE; + if (intrack != 0) { + Warn("Last track end missing"); + } + continue; + } + + /* Verify events occur within Start_track / End_track + brackets and that track number is correct. */ + + if (track != intrack) { + if (intrack == 0) { + Warn("Event not within track"); + continue; + } else { + Warn("Incorrect track number in event"); + continue; + } + } + + /* Make sure absolute time isn't less than that + in previous event in this track. */ + + if (abstime < tabstime) { + Warn("Events out of order; this event is before the previous."); + continue; + } + + switch (rtype) { + +#define ClampChannel Clamp(4, 0, 15, "Channel") + case Event(NoteOff): + case Event(NoteOn): + case Event(PolyphonicKeyPressure): + case Event(ControlChange): + Nfields(3); + ClampChannel; + Clamp(5, 0, 127, "Note number"); + Clamp(6, 0, 127, "Value"); + outevent((byte) (etype | nfld[0])); + outbyte((byte) nfld[1]); + outbyte((byte) nfld[2]); + break; + + case Event(ProgramChange): + case Event(ChannelPressure): + Nfields(2); + ClampChannel; + Clamp(5, 0, 127, "Value"); + outevent((byte) (etype | nfld[0])); + outbyte((byte) nfld[1]); + break; + + case Event(PitchBend): + Nfields(2); + ClampChannel; + Clamp(5, 0, (1 << 14) - 1, "Value"); + outevent((byte) (etype | nfld[0])); + /* Note that the pitch bend event consists of two + bytes each containing 7 bits of data with a + zero MSB. Consequently, we cannot use outshort() + for the pitch bend data and must generate and emit + the two 7 bit values here. The values are output + with the least significant 7 bits first, followed + by the most significant 7 bits. */ + outbyte((byte) (nfld[1] & 0x7F)); + outbyte((byte) ((nfld[1] >> 7) & 0x7F)); + break; + + /* System-exclusive messages */ + + case Event(SystemExclusive): + case Event(SystemExclusivePacket): + Nfields(1); + Clamp(4, 0, (1 << 28) - 1, "Length"); + lastStatus = -1; /* Running status not used for sysex, + and sysex cancels any running + status in effect. */ + n = nfld[0]; + if (checkBytes(5, n)) { + continue; + } + outevent((byte) etype); + outVarLen(n); /* Length of following data */ + for (i = 0; i < n; i++) { + /* We must call xfields() inside the loop for + each individual system exclusive field since + the number of fields is unlimited and may + exceed MAX_NFIELDS. */ + if (xfields(1, i + 5)) { + abort(); /* Can't happen, thanks to checkBytes() above */ + } + clamp(&nfld[0], 0, 255, "Sysex data byte"); + outbyte((byte) nfld[0]); + } + break; + + /* File meta-events */ + + case Meta(SequenceNumberMetaEvent): + Nfields(1); + Clamp(4, 0, (1 << 16) - 1, "Sequence number"); + outmeta((byte) etype); + outbyte(2); + outshort((short) nfld[0]); + break; + + case Meta(TextMetaEvent): + case Meta(CopyrightMetaEvent): + case Meta(TrackTitleMetaEvent): + case Meta(TrackInstrumentNameMetaEvent): + case Meta(LyricMetaEvent): + case Meta(MarkerMetaEvent): + case Meta(CuePointMetaEvent): + if (!CSVscanField(&f, &flen)) { + Warn("Missing field 4."); + } + outmeta((byte) etype); + outbytes((byte *) f, CSVfieldLength); + break; + + case Meta(ChannelPrefixMetaEvent): + case Meta(PortMetaEvent): + Nfields(1); + Clamp(4, 0, 255, "Number"); + outmeta((byte) etype); + of[0] = (byte) nfld[0]; + outbytes(of, 1); + break; + + case Meta(EndTrackMetaEvent): + outmeta((byte) etype); + outbyte(0); /* All meta events must include length */ + mt.length = trackbufp - trackbuf; + writeMidiTrackHeader(fo, &mt); + if (verbose) { + fprintf(stderr, "Track %d: length %ld.\n", ntrack + 1, mt.length); + } + fwrite(trackbuf, trackbufp - trackbuf, 1, fo); + trackbufp = trackbuf; + tabstime = 0; + lastStatus = -1; + intrack = 0; + ntrack++; + break; + + case Meta(SetTempoMetaEvent): + Nfields(1); + Clamp(4, 0, (1 << 24) - 1, "Value"); + outmeta((byte) etype); + of[0] = (byte) ((nfld[0] >> 16) & 0xFF); + of[1] = (byte) ((nfld[0] >> 8) & 0xFF); + of[2] = (byte) (nfld[0] & 0xFF); + outbytes(of, 3); + break; + + case Meta(SMPTEOffsetMetaEvent): + Nfields(5); + Clamp(4, 0, 255, "Hour"); + Clamp(5, 0, 255, "Minute"); + Clamp(6, 0, 255, "Second"); + Clamp(7, 0, 255, "Frame"); + Clamp(8, 0, 255, "FrameFraction"); + outmeta((byte) etype); + for (i = 0; i < 5; i++) { + of[i] = (byte) nfld[i]; + } + outbytes(of, 5); + break; + + case Meta(TimeSignatureMetaEvent): + Nfields(4); + Clamp(4, 0, 255, "Numerator"); + Clamp(5, 0, 255, "Denominator"); + Clamp(6, 0, 255, "Click"); + Clamp(7, 0, 255, "NotesPerQuarter"); + outmeta((byte) etype); + for (i = 0; i < 4; i++) { + of[i] = (byte) nfld[i]; + } + outbytes(of, 4); + break; + + case Meta(KeySignatureMetaEvent): + Nfields(1); + if (!CSVscanField(&f, &flen)) { + Warn("Missing field 5"); + } + outmeta((byte) etype); + Clamp(4, -7, 7, "Key"); + of[0] = (byte) nfld[0]; + if (strcasecmp(f, "major") == 0) { + of[1] = 0; + } else if (strcasecmp(f, "minor") == 0) { + of[1] = 1; + } else { + sprintf(errm, "Field 5 has invalid major/minor indicator \"%s\"", f); + Warn(errm); + of[1] = 0; + } + outbytes(of, 2); + break; + + case Meta(SequencerSpecificMetaEvent): + Nfields(1); + Clamp(4, 0, (1 << 28) - 1, "Length"); + n = nfld[0]; + if (checkBytes(5, n)) { + continue; + } + outmeta((byte) etype); + outVarLen(n); /* Length of following data */ + for (i = 0; i < n; i++) { + if (xfields(1, i + 5)) { /* Scan data field */ + abort(); /* Can't happen, thanks to checkBytes() above */ + } + clamp(&nfld[0], 0, 255, "Sequencer specific data byte"); + outbyte((byte) nfld[0]); + } + break; + + /* The code 0xFF indicates an unknown file meta + event. Since meta events include a length field, + such events can be preserved in a CSV file by + including the unknown event code in the record. */ + + case Meta(0xFF): + Nfields(2); /* Get type and length */ + Clamp(4, 0, 255, "UnknownMetaType"); + Clamp(5, 0, (1 << 28) - 1, "UnknownMetaLength"); + etype = nfld[0]; + n = nfld[1]; + if (checkBytes(6, n)) { + continue; + } + /* We output the length as a variable-length quantity + on the assumption that any meta event which can + have data longer than 127 bytes will use a variable + length len field. */ + outmeta((byte) etype); + outVarLen(n); + for (i = 0; i < n; i++) { + if (xfields(1, i + 6)) { /* Scan data field */ + abort(); /* Can't happen, thanks to checkBytes() above */ + } + clamp(&nfld[0], 0, 255, "Unknown meta data byte"); + outbyte((byte) nfld[0]); + } + break; + } + } + fclose(fp); + + if (!eofseen) { + fprintf(stderr, "%s: Missing End_of_file record.\n", PROG); + return 1; + } + + return ((errors > 0) ? 1 : 0); +} diff --git a/midicsv-1.1/csvmidi.o b/midicsv-1.1/csvmidi.o new file mode 100644 index 0000000..1b768e1 Binary files /dev/null and b/midicsv-1.1/csvmidi.o differ diff --git a/midicsv-1.1/drummer.pl b/midicsv-1.1/drummer.pl new file mode 100644 index 0000000..8299e8b --- /dev/null +++ b/midicsv-1.1/drummer.pl @@ -0,0 +1,59 @@ + + require 'general_midi.pl'; + +# Repeats, Note, +# Duration, Velocity + @track = (4, $GM_Percussion{'Acoustic Bass Drum'}, + 480, 127, + 4, $GM_Percussion{'Low-Mid Tom'}, + 240, 127, + 1, 0, 120, 0, + 2, $GM_Percussion{'Hand Clap'}, + 240, 127, + 1, 0, 240, 0 + ); + + print << "EOD"; +0, 0, Header, 1, 1, 480 +1, 0, Start_track +1, 0, Tempo, 500000 +EOD + + $time = 0; + + &loop(4, @track); + + print << "EOD"; +1, $time, End_track +0, 0, End_of_file +EOD + + sub note { # ¬e($note_number, $duration [, $velocity]) + local ($which, $duration, $vel) = @_; + + if ($which > 0) { + if (!defined($vel)) { + $vel = 127; + } + print("1, $time, Note_on_c, 9, $which, $vel\n"); + } + $time += $duration; + if ($which > 0) { + print("1, $time, Note_off_c, 9, $which, 0\n"); + } + } + + sub loop { # &loop($ntimes, @track) + local ($loops, @tr) = @_; + local ($i, $r); + + for ($i = 0; $i < $loops; $i++) { + local @t = @tr; + while ($#t > 0) { + local ($repeats, $note, $duration, $velocity) = splice(@t, 0, 4); + for ($r = 0; $r < $repeats; $r++) { + ¬e($note, $duration, $velocity); + } + } + } + } diff --git a/midicsv-1.1/exchannel.pl b/midicsv-1.1/exchannel.pl new file mode 100644 index 0000000..b1fcde8 --- /dev/null +++ b/midicsv-1.1/exchannel.pl @@ -0,0 +1,18 @@ + + # Extract events for a channel from a MIDI CSV file. + # All non-channel events are output unchanged. + # Comments are discarded. + + $which_channel = 9; # Extract General MIDI percussion for demo + + while ($a = <>) { + if (!($a =~ m/\s*[\#\;]/)) { # Ignore comment lines + if ($a =~ m/\s*\d+\s*,\s*\d+\s*,\s*\w+_c\s*,\s*(\d+)/) { + if ($1 == $which_channel) { + print($a); + } + } else { + print($a); + } + } + } diff --git a/midicsv-1.1/general_midi.pl b/midicsv-1.1/general_midi.pl new file mode 100644 index 0000000..a56ea65 --- /dev/null +++ b/midicsv-1.1/general_midi.pl @@ -0,0 +1,187 @@ + + # The following hashes can be used to reference General MIDI + # patch (instrument) number and percussion note assignments + # by name. + + %GM_Patch = ( + 'Acoustic Grand Piano', 0, + 'Bright Acoustic Piano', 1, + 'Electric Grand Piano', 2, + 'Honky-tonk Piano', 3, + 'Electric Piano 1', 4, + 'Electric Piano 2', 5, + 'Harpsichord', 6, + 'Clavinet', 7, + 'Celesta', 8, + 'Glockenspiel', 9, + 'Music Box', 10, + 'Vibraphone', 11, + 'Marimba', 12, + 'Xylophone', 13, + 'Tubular Bells', 14, + 'Dulcimer', 15, + 'Drawbar Organ', 16, + 'Percussive Organ', 17, + 'Rock Organ', 18, + 'Church Organ', 19, + 'Reed Organ', 20, + 'Acordion', 21, + 'Harmonica', 22, + 'Tango Accordion', 23, + 'Acoustic Guitar (nylon)', 24, + 'Acoustic Guitar (steel)', 25, + 'Electric Guitar (jazz)', 26, + 'Electric Guitar (clean)', 27, + 'Electric Guitar (muted)', 28, + 'Overdriven Guitar', 29, + 'Distortion Guitar', 30, + 'Guitar Harmonics', 31, + 'Acoustic Bass', 32, + 'Electric Bass (finger)', 33, + 'Electric Bass (pick)', 34, + 'Fretless Bass', 35, + 'Slap Bass 1', 36, + 'Slap Bass 2', 37, + 'Synth Bass 1', 38, + 'Synth Bass 2', 39, + 'Violin', 40, + 'Viola', 41, + 'Cello', 42, + 'Contrabass', 43, + 'Tremolo Strings', 44, + 'Pizzicato Strings', 45, + 'Orchestral Harp', 46, + 'Timpani', 47, + 'String Ensemble 1', 48, + 'String Ensemble 2', 49, + 'SynthStrings 1', 50, + 'SynthStrings 2', 51, + 'Choir Aahs', 52, + 'Voice Oohs', 53, + 'Synth Voice', 54, + 'Orchestra Hit', 55, + 'Trumpet', 56, + 'Trombone', 57, + 'Tuba', 58, + 'Muted Trumpet', 59, + 'French Horn', 60, + 'Brass Section', 61, + 'Synth Brass 1', 62, + 'Synth Brass 2', 63, + 'Soprano Sax', 64, + 'Alto Sax', 65, + 'Tenor Sax', 66, + 'Baritone Sax', 67, + 'Oboe', 68, + 'English Horn', 69, + 'Bassoon', 70, + 'Clarinet', 71, + 'Piccolo', 72, + 'Flute', 73, + 'Recorder', 74, + 'Pan Flute', 75, + 'Blown Bottle', 76, + 'Shakuhachi', 77, + 'Whistle', 78, + 'Ocarina', 79, + 'Lead 1 (square)', 80, + 'Lead 2 (sawtooth)', 81, + 'Lead 3 (calliope)', 82, + 'Lead 4 (chiff)', 83, + 'Lead 5 (charang)', 84, + 'Lead 6 (voice)', 85, + 'Lead 7 (fifths)', 86, + 'Lead 8 (bass+lead', 87, + 'Pad 1 (new age)', 88, + 'Pad 2 (warm)', 89, + 'Pad 3 (polysynth)', 90, + 'Pad 4 (choir)', 91, + 'Pad 5 (bowed)', 92, + 'Pad 6 (metallic)', 93, + 'Pad 7 (halo)', 94, + 'Pad 8 (sweep)', 95, + 'FX 1 (train)', 96, + 'FX 2 (soundtrack)', 97, + 'FX 3 (crystal)', 98, + 'FX 4 (atmosphere)', 99, + 'FX 5 (brightness)', 100, + 'FX 6 (goblins)', 101, + 'FX 7 (echoes)', 102, + 'FX 8 (sci-fi)', 103, + 'Sitar', 104, + 'Banjo', 105, + 'Shamisen', 106, + 'Koto', 107, + 'Kalimba', 108, + 'Bagpipe', 109, + 'Fiddle', 110, + 'Shanai', 111, + 'Tinkle Bell', 112, + 'Agogo', 113, + 'Steel Drums', 114, + 'Woodblock', 115, + 'Tailo Drum', 116, + 'Melodic Drum', 117, + 'Synth Drum', 118, + 'Reverse Cymbal', 119, + 'Guitar Fret Noise', 120, + 'Breath Noise', 121, + 'Seashore', 122, + 'Bird Tweet', 123, + 'Telephone Ring', 124, + 'Helicopter', 125, + 'Applause', 126, + 'Gunshot', 127 + ); + + %GM_Percussion = ( + 'Acoustic Bass Drum', 35, + 'Bass Drum 1', 36, + 'Side Stick', 37, + 'Acoustic Snare', 38, + 'Hand Clap', 39, + 'Electric Snare', 40, + 'Low Floor Tom', 41, + 'Closed Hi-Hat', 42, + 'High Floor Tom', 43, + 'Pedal Hi-Hat', 44, + 'Low Tom', 45, + 'Open Hi-Hat', 46, + 'Low-Mid Tom', 47, + 'High-Mid Tom', 48, + 'Crash Cymbal 1', 49, + 'High Tom', 50, + 'Ride Cymbal 1', 51, + 'Chinese Cymbal', 52, + 'Ride Bell', 53, + 'Tambourine', 54, + 'Splash Cymbal', 55, + 'Cowbell', 56, + 'Crash Cymbal 2', 57, + 'Vibraslap', 58, + 'Ride Cymbal 2', 59, + 'High Bongo', 60, + 'Low Bongo', 61, + 'Mute Hi Conga', 62, + 'Open Hi Conga', 63, + 'Low Conga', 64, + 'High Timbale', 65, + 'Low Timbale', 66, + 'High Agogo', 67, + 'Low Agogo', 68, + 'Cabasa', 69, + 'Maracas', 70, + 'Short Whistle', 71, + 'Long Whistle', 72, + 'Short Guiro', 73, + 'Long Guiro', 74, + 'Claves', 75, + 'Hi Woodblock', 76, + 'Low Woodblock', 77, + 'Mute Cuica', 78, + 'Open Cuica', 79, + 'Mute Triangle', 80, + 'Open Triangle', 81 + ); + + 1; diff --git a/midicsv-1.1/getopt.c b/midicsv-1.1/getopt.c new file mode 100644 index 0000000..1564e53 --- /dev/null +++ b/midicsv-1.1/getopt.c @@ -0,0 +1,117 @@ +/* +Date: Tue, 25 Dec 84 19:20:50 EST +From: Keith Bostic +To: genrad!sources +Subject: public domain getopt(3) + +There have recently been several requests for a public +domain version of getopt(3), recently. Thought this +might be worth reposting. + + Keith Bostic + ARPA: keith@seismo + UUCP: seismo!keith + +====================================================================== +In April of this year, Henry Spencer (utzoo!henry) released a public +domain version of getopt (USG, getopt(3)). Well, I've been trying to +port some USG dependent software and it didn't seem to work. The problem +ended up being that the USG version of getopt has some external variables +that aren't mentioned in the documentation. Anyway, to fix these problems, +I rewrote the public version of getopt. It has the following advantages: + + -- it includes those "unknown" variables + -- it's smaller/faster 'cause it doesn't use the formatted + output conversion routines in section 3 of the UNIX manual. + -- the error messages are the same as S5's. + -- it has the same side-effects that S5's has. + -- the posted bug on how the error messages are flushed has been + implemented. (posting by Tony Hansen; pegasus!hansen) + +I won't post the man pages since Henry already did; a special note, +it's not documented in the S5 manual that the options ':' and '?' are +illegal. It should be obvious, but I thought I'd mention it... +This software was derived from binaries of S5 and the S5 man page, and is +(I think?) totally (I'm pretty sure?) compatible with S5 and backward +compatible to Henry's version. + + Keith Bostic + ARPA: keith@seismo + UUCP: seismo!keith + +*UNIX is a trademark of Bell Laboratories + +Further modified by John Walker on 2001-02-19 to accept +GNU-style "--xxx" options. The original code considered +any option beginning with "--" as the end of the options +list. I changed the logic so that only "--" without a +suffix is treated as an end of option marker. + +.. cut along the dotted line ......................................... +*/ + +#include + +#include + +#include "getopt.h" + +/* + * get option letter from argument vector + */ +int optind = 1, /* index into parent argv vector */ + optopt; /* character checked for validity */ +char *optarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define EMSG "" +#define tell(s) fputs(*nargv,stderr);fputs(s,stderr); \ + fputc(optopt,stderr);fputc('\n',stderr);return(BADCH); + +int Getopt(int nargc, char *nargv[], char *ostr) +{ + static char *place = EMSG; /* option letter processing */ + static char *lastostr = (char *) 0; + register char *oli; /* option letter list index */ + char *index(); + + /* LANCE PATCH: dynamic reinitialization */ + if (ostr != lastostr) { + lastostr = ostr; + place = EMSG; + } + if(!*place) { /* update scanning pointer */ + if((optind >= nargc) || (*(place = nargv[optind]) != '-') + || ! *++place) { + place = EMSG; + return(EOF); + } + /* Test for "--" as terminator of options. Note that + options which begin with "--" remain acceptable + (for example, "--help"); only "--" by itself + terminates the option list. */ + if (*place == '-' && place[1] == 0) { + ++optind; + return(EOF); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr, optopt))) { + if(!*place) ++optind; + tell(": illegal option -- "); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) ++optind; + } + else { /* need an argument */ + if (*place) optarg = place; /* no white space */ + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + tell(": option requires an argument -- "); + } + else optarg = nargv[optind]; /* white space */ + place = EMSG; + ++optind; + } + return(optopt); /* dump back option letter */ +} diff --git a/midicsv-1.1/getopt.h b/midicsv-1.1/getopt.h new file mode 100644 index 0000000..50927bd --- /dev/null +++ b/midicsv-1.1/getopt.h @@ -0,0 +1,10 @@ + +extern int optind, optopt; +extern char *optarg; + +/* We do the following naming side-step to permit testing + our local getopt() on systems which include getopt() + and declare it incompatibly in stdio.h or stdlib.h. */ + +extern int Getopt(int nargc, char *nargv[], char *ostr); +#define getopt(a, b, c) Getopt(a, b, c) diff --git a/midicsv-1.1/getopt.o b/midicsv-1.1/getopt.o new file mode 100644 index 0000000..d0868b0 Binary files /dev/null and b/midicsv-1.1/getopt.o differ diff --git a/midicsv-1.1/log.txt b/midicsv-1.1/log.txt new file mode 100644 index 0000000..880e240 --- /dev/null +++ b/midicsv-1.1/log.txt @@ -0,0 +1,407 @@ + + MIDICSV / CSVMIDI + Development Log + +2003 December 29 + +Changed declarations from "char" to "byte" where necessary +so everything works correctly on platforms where char is +signed without the need to compile with an option to force +unsigned char. + +Moved definitions of "byte" and "vlint" to a new types.h +file and included it in files which need these definitions. + +Changed some code which used "long" where a variable length +integer appears in the MIDI file to use "vlint" instead. This +doesn't change functionality, but improves documentation in +the code. + +Fixed several instances in the Makefile where test cases +assumed the current directory was on the PATH, + +Reversed argument order of the (presently unused) function +writeVarLen in midio.c to agree with the other write functions +in this file. + +Integrated code from XD to set the input file (midicsv.c) +and output file (csvmidi.c) mode to binary when reading +or writing standard input/output on a WIN32 platform. This +code sets the mode using the _setmode() function of the +Microsoft Visual C library--it may have to be changed if +you build using another WIN32 compiler. + +Added a -v option to csvmidi.c and verbose output of the +file header and track information corresponding to that generated +by midicsv.c. + +2003 December 30 + +Created manual pages csvmidi.1 and midicsv.1 (first cut) for +the programs. + +Added an "install" target to the Makefile with default +installation in the /usr/local tree. + +Created a README file which will eventually contain all the +gory details and dark underside of the distribution. + +Cleaned up the "check" case in the Makefile to use a version +of the Mike and the Mechanics "Silent Running" sequence which +has run the midicsv/csvmidi gauntlet and is hence invariant. +This permits verifying both text and binary equality of the +CSV and MIDI files from encoding and decoding this file. If +the check passes, the only output is now "All tests passed.". +The check target now cleans up the temporary files it creates +along the way. + +Integrated the local getopt() from Base64 to permit building +on Win32. I modified getopt.c to remove Autoconf trash from +the includes. We always use our own getopt()--never the one +from the system (if any). + +Csvmidi printed nonsense track numbers in verbose output. +Fixed. + +Added a version number definition in version.h and included +the version number in the "-u" option output from midicsv.c +and csvmidi.c. + +2004 January 1 + +Fixed some harmless compiler warnings in csvmidi.c and midicsv.c +when WIN32 binaries were built with Microsoft Visual C 5.0. + +Copied the built WIN32 executables: Csvmidi.exe and Midicsv.exe +into the release directory, along with the Workspace and Project +files: Miditools.dsw, Csvmidi.dsp, and Midicsv.dsp and added +to the list of files included in the distribution by the Makefile. + +2004 January 3 + +Text fields in file meta-events which contained zero bytes were +incorrectly truncated at the zero byte by midicsv.c. Since +these fields are specified in MIDI as a length followed by +arbitrary bytes, zero is permissible in such fields. I modified +textcsv() in midicsv.c to permit zero bytes, which are output as +octal escapes like other control characters. + +If a MIDI text field in CSV contained a zero byte (duly quoted +as \000), csdmidi.c would truncate the MIDI text field at the +zero byte, interpreting it as a C string terminator. I modified +CSVscanField() in csv.c to store the length of the field it +scans in a global variable CSVfieldLength and modified csvmidi.c +to use this field to determine how many text bytes to write, +rather than incorrectly applying strlen() to the text to +determine its length. + +Modified CSVscanField() in csv.h to take a second argument which +gives the length of the field buffer and refuse to store outside +the buffer. Characters which would overflow the buffer are +discarded, and the buffer is guaranteed to be terminated by a +zero byte for those who count on treating it as a C string. The +actual field length, including characters dropped, may be +obtained from CSVfieldLength (see previous paragraph), and may +thus be used to determine whether a truncation has occurred. + +Modified textcsv() in midicsv.c to quote only non-graphic +characters in ISO 8859-1. This permits ISO accented and +punctuation characters to appear in text strings without being +quoted as octal sequences. If CSV_Quote_ISO is not defined, +this function reverts to its previous behaviour--octal quoting +all characters which aren't in the 7-bit ASCII graphic +character set. + +Rewrote getCSVline() in csvmidi.c to dynamically allocate the +CSV line input buffer and expand it as required to accommodate +arbitrarily long lines (assuming they fit in available memory). +This avoids the need for a long compiled-in input buffer and +worries about possible truncation when reading outrageously long +system exclusive byte dump records. + +2004 January 12 + +Modified the "dist" target in the Makefile to create an +archive whose name includes the version number (specified +by "VERSION" in the Makefile, regrettably uncoupled to +version.h, and which creates an eponymous directory into +which its files are extracted. + +Created a logo for the Web page, which is maintained in the +subdirectory IMGWORK. + +2004 January 17 + +Ported the WIN32 build to Visual C++ .NET, building on Ovni. As +usual, I had to add "libc.lib" to the list of explicitly +included libraries in the +Project/Properties/Linker/Input/Additional Dependencies item +and "libcd.lib" to the .../Ignore Specific Libraries list to +get around the "Library is Corrupt" dagger in the back from +.NET. + +Fixed three warnings in the Meta(SetTempoMetaEvent) case +in midicsv.c where 8 bit values weren't explicitly cast +to byte before being stored into the temporary track item +array. While I was at it, I fixed several other places where +a (char) cast was inadvertently used on a value being stored +into a byte type. None of these could cause any problems +apart from compiler warnings. + +Imported the "Midicsv.sln" solution file and the two projects, +"Csvmidi.vcproj" and "Midicsv.vcproj" from the .NET build +environment, along with the generated Release executables. + +Modified the WIN32 file list in the Makefile to include the +.NET .sln and .vcproj files in the distribution instead of +the .dsw and .dsp equivalents from Visual C 5.0. + +Created a rudimentary round-trip test for WIN32 builds +in W32test.bat, which I added to the WIN32 file list in +the Makefile. + +2004 January 25 + +Changed the ce3k.csv sample file to use a General MIDI organ +patch for the track rather than a piano. + +Much work documenting CSV message formats in midicsv.5; much +work remains. + +2004 February 6 + +Completed documentation of CSV message formats in midicsv.5. + +2004 February 7 + +Key signature meta-events for flat keys were not treated +as signed values, but rather output as two's complement +unsigned bytes between 128 and 255. I fixed midicsv.c to +output these values as signed integers. + +The test for major and minor key indicators in the +Key_signature record in csvmidi.c was backwards. In +addition, the process of assembling the MIDI output event +overwrote the major/minor string, causing the signature +to always be considered as minor. Both were fixed. + +2004 February 8 + +Implemented range checking for all fields in csvmidi.c. The +field checking for items such as time signature and SMPTE +offsets is permissive in the sense that any value which fits +into the MIDI file binary field (in all such cases, as it +happens, one byte) are accepted without warnings. The key signature +field is, however, required to specify a key in the range +-7 to 7 and a major/minor indicator of "major" or +"minor" (case-insensitive). + +The "install" target in the Makefile neglected to copy the file +format document, midicsv.5, to the corresponding manual page +directory. I further modified the install target to use a more +or less standard "install" command to guarantee the target directories +are created. + +Added an "uninstall" target to the Makefile which deletes the +files copied by "install". + +Updated the README file to reflect name changes due to the port +of the WIN32 build to Visual Studio .NET. + +2004 February 9 + +Darned if I knew you could carry a running status across +a file meta-event or Sysex! Well, you can, and I managed to +stumble over a MIDI file (one, among hundreds I've tested +with) which does it. As I'd written the code, I preserved +any byte with the high bit set as the running status, which +caused the 0xFF to be saved as the running status after +a meta-event, so if the next item didn't begin with a status +byte, it would be misinterpreted as a meta-event, with +disastrous consequences downstream. I modified midicsv.c +to only save genuine channel status events (0x00-0xEF) +as running status. + +Eliminated some obsolete code in midicsv.c associated with +the way we used to output text in meta-events before the advent +of textcsv(). + +2004 February 10 + +It turns out textcsv() in midicsv.c did not actually handle +text in meta-events with full generality (i.e. up to 2^28 +bytes in length). I rewrote the function and code that calls +it to entirely eliminate all intermediate memory use. The +revised textcsv() is passed the output stream pointer and +writes characters directly to it, quoting as required on +the fly. + +Well, csvmidi.c had its own weaknesses when it came to really +long strings. I modified CSVscanField() in csv.c to work +with a dynamically allocated buffer (which can either be provided +by that function on the first call or supplied by the caller) +which is passed as a pointer to the pointer to the buffer, along with +a pointer to its length. The buffer is expanded as required, +according to the BufferInitial and BufferExpansion definitions +in csv.c which are set to 256 and 1024 bytes respectively. Calls +to CSVscanField() in csvmidi.c were modified accordingly, with +the field buffer f now dynamically allocated and its length kept +in flen. To test this, I ran the entire test suite with an initial +field buffer length of 8 bytes and expansion increment of 4 bytes +and everything worked fine. + +If the realloc() to dynamically expand the track assembly buffer +in outbyte() in csvmidi.c failed, the code would attempt to +store through a NULL pointer. I added a check for failure of +the realloc() which issues a message to standard error and exits +with a return code of 2 in case the allocation fails. Also, +the trackbufl (current track buffer length) was declared as +a long, a heritage from the 1988-vintage progenitor of this +code which ran on a 16 bit MS-DOS system. I changed it to +a more conventional int, which avoids worries about printf +format phrase compatibilities. + +Code in csvmidi.c sloppily reused the CSV field scanning buffer +to assemble MIDI parameter bytes for meta-events. This actually +did no harm, since in every case all fields have been scanned +prior to this operation, but it's tacky and looked even worse +now that the field buffer is dynamically allocated within +csv.c. I changed all the field assembly code to use a small +static buffer of[], which is dedicated to this purpose. Note +that Sysex and Sequencer Specific events, which may have +arbitrary amounts of data, do not use this static buffer but +emit the data on the fly, avoiding worries about overflow. + +Rebuilt the WIN32 executables. Everything built without any +warnings and passed the regression test. + +2004 February 11 + +Wrote the first cut of torture.pl, a program to generate the +torture test for csvmidi and midicsv. The test is generated +programmatically because we want to include some very long +byte array (Sysex, etc.) events and text strings, and cranking +them out by a Perl program avoid the need to include a huge +torture test CSV file in the distribution. The current version +of the torture test includes hard-coded examples of every kind +of event we recognise, including an Unknown_meta_event used +to test pass-through of unknowns and another used to fake a +Key_signature, which confirms handling of unknown meta-events +is compatible with known ones. + +The programmed part outputs a 5123 byte System_exclusive, a +11213 byte ASCII text string, a 74219 byte arbitrary string +(all byte values from 0 to 255), a 3497861 byte +Sequencer_specific, and finally a 4256233 byte arbitrary +string, all pseudorandomly generated. The pseudorandom +generator is seeded with a constant valuf of 1234 so the test +is reproducible and output can be saved for regression testing. + +Missing or bad fields on records with an arbitrary number of +byte srguments (such as SysEx) were erroneously reported as +field 4 regardless of the actual field in error. I created a +new xfields() function in csvmidi.c which accepts the start of +the fields to be parsed as an argument, used that for variable +length byte argument parsing, and created a wrapper nfields() +which passes 4 for the usual case of a fixed number of numeric +arguments after the Type field. + +Added the ability to csvmidi.c to recover from missing or +unparseable fields in CSV records which contain a variable +number of byte arguments (SysEx for example). The handlers for +these events now call a new function, checkBytes(), to +pre-parse and error check the byte list before emitting the +event code and length to the output MIDI file. If an error is +detected, they can now ignore the erroneous record with no +damage to the MIDI file. This makes all CSV syntax errors now +recoverable. + +Added a "torture" target to the Makefile to run the torture test. + +Included the "bad.csv" file in the distribution. This is a +hand-crafted CSV file full of errors to verify csvmidi's +error detection and recovery. + +Modified the comment detection code in csvmidi.c to permit +white space before the comment delimiter. Previously, it +had to appear in column one; now it must simply be the first +nonblank character on the line. + +Added range checking for all arguments of the Header +record in csvmidi.c. + +Built with GCC 3.2.2 and re-tested to make sure no warnings +or problems were manifest. All went well. + + +The sscanf() function in the GCC library apparently calls +the equivalent of strlen() on its argument before parsing it. +The "sscanf(s, "%3o")" used in csv.c to parse backslash-escaped +octal characters, and string parsing slowed down enormously +for long strings with many escaped characters (as produced by +the torture test). I rewrote the octal escape parser to +scan the digits with in-line code, and string parsing sped +up for strings in the megabyte range by more than a factor of +1000. + +2004 February 12 + +Added documentation of CSV comment syntax to midicsv.5 file format +manual page. + +Rebuilt WIN32 executables and imported Release binaries into +development directory. + +2004 February 13 + +Integrated HTML versions of manual pages produced by man2html +(with substantial hand patching of the output) into the Web page. + +2004 February 14 + +To simplify bulk (or, more precisely, near-blind) processing of +CSV, I added a suffix of "_c" to all channel event messages (note +on, note off, program, pitch bend, etc.) and "_t" to all meta-events +which take a text string argument. + +Missing or erroneous numeric CSV fields detected by the xfields() +function in csvmidi.c generated error messages with indentation +inconsistent with error messages reported elsewhere. Fixed. + +2004 February 17 + +Added two targets to the Makefile which cause the build of the +distribution archives to fail if the WIN32 executables are out +of date with respect to the source code. + +Added a new general_midi.pl file to the distribution which defines +two hashes, %GM_Patch and %GM_Percussion, which permit specifying +General MIDI patch numbers and percussion note numbers as descriptive +strings. + +Integrated two new demo programs, drummer.pl, a rudimentary +drum machine, and acomp.pl, a moronic algorithmic composer, +to serve as examples of ab ovo creation of MIDI files using +Perl and csvmidi. + +2004 February 18 + +Added a status check and go/no-go results report to the +"torture" target in the Makefile. + +2008 January 20 + +Updated version to "Version 1.1 (January 2008)". + +Both midicsv.c and csvmidi.c handled the two byte argument +to a Pitch bend event in reverse order. The 14 bit pitch +bend value (with 8192 indicating no bend) is supposed to be +sent as two 7 bit values with the least significant byte +first, but the code processed the bytes with the most +significant byte first. Since both programs had the same +error, a "round trip" from MIDI to CSV and back to MIDI would +not damage the file. I fixed both programs to process the +bytes in the correct order. (Reported by Pete Goodeve.) + +Fixed a GCC 4.1.2 -Wall quibble about the signedness of a +byte pointer value in csvmidi.c. diff --git a/midicsv-1.1/midicsv b/midicsv-1.1/midicsv new file mode 100755 index 0000000..bdf9b64 Binary files /dev/null and b/midicsv-1.1/midicsv differ diff --git a/midicsv-1.1/midicsv.1 b/midicsv-1.1/midicsv.1 new file mode 100644 index 0000000..d367430 --- /dev/null +++ b/midicsv-1.1/midicsv.1 @@ -0,0 +1,82 @@ +'\" t +.TH MIDICSV 1 "9 FEB 2004" +.UC 4 +.SH NAME +midicsv \- translate MIDI file to CSV +.SH SYNOPSIS +.B midicsv +[ +.B \-u +.B \-v +] [ +.I infile +[ +.I outfile +] ] +.SH DESCRIPTION +.B midicsv +reads a standard MIDI file and decodes it into a +CSV (Comma-Separated Value) file which preserves all the +information in the MIDI file. The ASCII CSV file may be +loaded into a spreadsheet or database application, or processed +by a program to transform the MIDI data (for example, +to key transpose a composition or extract a track +from a multi-track sequence). A CSV file +in the format created by +.B midicsv +may be converted back into a standard MIDI file with the +.B csvmidi +program. +.SH OPTIONS +.TP 10 +.B \-u +Print how-to-call information. +.TP +.B \-v +Print verbose debugging information on standard +error. The MIDI file header is dumped, along +with the length of each track in the file. +.SH FILES +If no +.I infile +is specified or +.I infile +is +.RB `` \- '', +.B midicsv +reads its input from standard input; if no +.I outfile +is given or +.I outfile +is +.RB `` \- '', +CSV output is written to standard output. The input and +output are processed in a strictly serial manner; consequently +.B midicsv +may be used in pipelines without restrictions. +.SH BUGS +.PP +.B midicsv +assumes its input is a well-formed standard MIDI file; +while some error checking is performed, gross errors in +the input file may cause +.B midicsv +to crash. +.PP +Please report problems to +.BR bugs@fourmilab.ch . +.SH "SEE ALSO" +.PD +.BR csvmidi (1), +.BR midicsv (5) +.ne 10 +.SH AUTHOR +.ce 2 +John Walker +http://www.fourmilab.ch/ +.PP +This software is in the public domain. +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +without any conditions or restrictions. This software is provided ``as +is'' without express or implied warranty. diff --git a/midicsv-1.1/midicsv.5 b/midicsv-1.1/midicsv.5 new file mode 100644 index 0000000..2ef101b --- /dev/null +++ b/midicsv-1.1/midicsv.5 @@ -0,0 +1,597 @@ +'\" t +.TH MIDICSV 5 "29 MAR 2004" +.UC 4 +.SH NAME +midicsv \- MIDI Comma-Separated Value (CSV) file format +.SH DESCRIPTION +The +.B midicsv +and +.B csvmidi +programs permit you to intertranslate standard MIDI +files and comma-separated value (CSV) files. These CSV files +preserve all information in the MIDI file, and +may be loaded into spreadsheet and database programs +or easily manipulated with text processing tools. +This document describes the CSV representation of +MIDI files written by +.B midicsv +and read by +.BR csvmidi . +Readers are assumed to understand the structure, +terminology, and contents of MIDI files\-please +refer to a MIDI file reference for details. +.SH "RECORD STRUCTURE" +Each record in the CSV representation of a MIDI contains +at least three fields: +.TP 10 +.B Track +Numeric field identifying the track to which this +record belongs. Tracks of MIDI data are numbered +starting at 1. Track 0 is reserved for file header, information, +and end of file records. +.TP +.B Time +Absolute time, in terms of MIDI clocks, at which this +event occurs. Meta-events for which time is not +meaningful (for example, song title, copyright information, +etc.) have an absolute time of 0. +.TP +.B Type +Name identifying the type of the record. Record types +are text consisting of upper and lower case letters +and the underscore (``_''), contain no embedded spaces, +and are not enclosed in quotes. +.B csvmidi +ignores upper/lower case in the +.B Type +field; the specifications +.RB `` Note_on_c '', +.RB `` Note_On_C '', +and +.RB `` NOTE_ON_C '' +are considered identical. +.PP +Records in the CSV file are sorted first by the track +number, then by time. Out of order records will be discarded +with an error message from +.BR csvmidi . +Following the three +required fields are parameter fields which depend upon +the +.BR Type ; +some +.BR Type s +take no parameters. Each +.B Type +and its parameter fields is discussed below. +.PP +Any line with an initial nonblank +character of +.RB `` # '' +or +.RB `` ; '' +is ignored; either delimiter may be used to introduce +comments in a CSV file. Only full-line comments +are permitted; you cannot use these delimiters to terminate +scanning of a regular data record. Completely blank lines are +ignored. +.SS "File Structure Records" +.TP 5 +.BI "0, 0, Header" ", format, nTracks, division" +The first record of a CSV MIDI file is always the +.B Header +record. Parameters are +.IR format : +the MIDI file type +(0, 1, or 2), +.IR nTracks : +the number of tracks in the file, +and +.IR division : +the number of clock pulses per quarter note. +The +.B Track +and +.B Time +fields are always zero. +.TP +.B "0, 0, End_of_file" +The last record in a CSV MIDI file is always an +.B End_of_file +record. Its +.B Track +and +.B Time +fields are always zero. +.TP +.IB "Track, " "0, Start_track" +A +.B Start_track +record marks the start of a new track, with the +.I Track +field giving the track number. All records between +the +.B Start_track +record and the matching +.B End_track +will have the same +.I Track +field. +.TP +.IB "Track, Time, " "End_track" +An +.B End_track +marks the end of events for the specified +.IR Track . +The +.I Time +field gives the total duration of the track, which will be +identical to the +.I Time +in the last event before the +.BR End_track . +.SS "File Meta-Events" +The following events occur within MIDI tracks and specify various +kinds of information and actions. They may appear at any time +within the track. Those which provide general information for +which time is not relevant usually appear at the start of the +track with +.B Time +zero, but this is not a requirement. +.PP +Many of these meta-events include a text string argument. Text +strings are output in CSV records enclosed in ASCII +double quote (") characters. Quote characters embedded +within strings are represented by two consecutive quotes. +Non-graphic characters in the +ISO 8859/1 Latin 1 set are output as a backslash followed by their +three digit octal character code. Two consecutive backslashes +denote a literal backslash in the string. +Strings in MIDI files can be +extremely long, theoretically as many as +.if t 2\s-2\v'-0.4m'28\v'0.4m'\s+2\-1 +.if n 2**28-1 +characters; programs which process +MIDI CSV files should take care to avoid buffer overflows or truncation +resulting from lines containing long string items. All meta-events which +take a text argument are identified by a suffix of +.RB `` _t ''. +.TP +.IB "Track, Time, " "Title_t, """ Text """ +The +.I Text +specifies the title of the track or sequence. The first +.B Title +meta-event in a type 0 MIDI file, or in the first track of a +type 1 file gives the name of the work. Subsequent +.B Title +meta-events in other tracks give the names of those +tracks. +.TP +.IB "Track, Time, " "Copyright_t, """ Text """ +The +.I Text +specifies copyright information for the sequence. +This is usually placed at time 0 of the first track in the +sequence. +.TP +.IB "Track, Time, " "Instrument_name_t, """ Text """ +The +.I Text +names the instrument intended to play the +contents of this track, +This is usually placed at time 0 of the track. Note +that this meta-event is simply a description; MIDI synthesisers +are not required (and rarely if ever) respond to it. +This meta-event is particularly useful in sequences +prepared for synthesisers which do not conform to the +General MIDI patch set, as it documents the intended +instrument for the track when the sequence is +used on a synthesiser with a different patch +set. +.TP +.IB "Track, Time, " "Marker_t, """ Text """ +The +.I Text +marks a point in the sequence which occurs at +the given +.IR Time , +for example +"Third\ Movement". +.TP +.IB "Track, Time, " "Cue_point_t, """ Text """ +The +.I Text +identifies synchronisation point which occurs at +the specified +.IR Time , +for example, +"Door\ slams". +.TP +.IB "Track, Time, " "Lyric_t, """ Text """ +The +.I Text +gives a lyric intended to be sung at the given +.IR Time . +Lyrics are often broken down into separate syllables +to time-align them more precisely with the sequence. +.TP +.IB "Track, Time, " "Text_t, """ Text """ +This meta-event supplies an arbitrary +.I Text +string tagged to the +.I Track +and +.IR Time . +It can be used for textual information which doesn't fall into +one of the more specific categories given +above. +.TP +.IB "Track, " "0, Sequence_number, " Number +This meta-event specifies a sequence +.I Number +between 0 and 65535, used to arrange multiple tracks in +a type 2 MIDI file, or to identify the sequence in which +a collection of type 0 or 1 MIDI files should be played. +The +.B Sequence_number +meta-event should occur at +.B Time +zero, at the start of the track. +.TP +.IB "Track, Time, " "MIDI_port, " Number +This meta-event specifies that subsequent events in the +.B Track +should be sent to MIDI port (bus) +.IR Number , +between 0 and 255. +This meta-event usually appears at the start of a track +with +.B Time +zero, but may appear within a track should the need +arise to change the port while the track is being played. +.TP +.IB "Track, Time, " "Channel_prefix, " Number +This meta-event specifies the MIDI channel that subsequent +meta-events and +.B System_exclusive +events pertain to. The channel +.I Number +specifies a MIDI channel from 0 to 15. In fact, +the +.I Number +may be as large as 255, but the consequences of +specifying a channel number greater than 15 are undefined. +.TP +.IB "Track, Time, " "Time_signature, " "Num, Denom, Click, NotesQ" +The time signature, metronome click rate, and number of 32nd +notes per MIDI quarter note (24 MIDI clock times) are +given by the numeric arguments. +.I Num +gives the numerator of the time signature as specified +on sheet music. +.I Denom +specifies the denominator as a negative power of two, +for example 2 for a quarter note, 3 for an eighth note, +etc. +.I Click +gives the number of MIDI clocks per metronome click, and +.I NotesQ +the number of 32nd notes in the nominal MIDI quarter +note time of 24 clocks (8 for the default MIDI quarter +note definition). +.TP +.IB "Track, Time, " "Key_signature, " "Key, Major/Minor" +The key signature is specified by the numeric +.I Key +value, which is 0 for the key of C, a positive value for +each sharp above C, or a negative value for each flat below +C, thus in the inclusive range \-7 to 7. The +.I Major/Minor +field is a quoted string which will be +.B """major""" +for a major key and +.B """minor""" +for a minor key. +.TP +.IB "Track, Time, " "Tempo, " "Number" +The tempo is specified as the +.I Number +of microseconds per quarter note, between 1 and +16777215. A value of 500000 corresponds to +120 quarter notes ("beats") per minute. To convert +beats per minute to a +.B Tempo +.IR value , +take the quotient from dividing 60,000,000 +by the beats per minute. +.TP +.IB "Track, " "0, SMPTE_offset, " "Hour, Minute, Second, Frame, FracFrame" +This meta-event, which must occur with a zero +.B Time +at the start of a track, specifies the SMPTE time code at which +it should start playing. The +.I FracFrame +field gives the fractional frame time (0 to 99). +.TP +.IB "Track, Time, " "Sequencer_specific, " "Length, Data, ..." +The +.B Sequencer_specific +meta-event is used to store vendor-proprietary data in +a MIDI file. The +.I Length +can be any value between 0 and +.if t 2\s-2\v'-0.4m'28\v'0.4m'\s+2\-1, +.if n 2**28-1, +specifying the number of +.I Data +bytes (between 0 and 255) which follow. +.B Sequencer_specific +records may be very long; programs which process MIDI CSV +files should be careful to protect against buffer overflows +and truncation of these records. +.TP +.IB "Track, Time, " "Unknown_meta_event, " "Type, Length, Data, ..." +If +.B midicsv +encounters a meta-event with a code not defined by the standard +MIDI file specification, it outputs an unknown meta-event record +in which +.I Type +gives the numeric meta-event type code, +.I Length +the number of data bytes in the meta-event, which can be +any value between 0 and +.if t 2\s-2\v'-0.4m'28\v'0.4m'\s+2\-1, +.if n 2**28-1, +followed by the +.I Data +bytes. Since meta-events include their own length, it +is possible to parse them even if their type and meaning +are unknown. +.B csvmidi +will reconstruct unknown meta-events with the same type code +and content as in the original MIDI file. +.SS "Channel Events" +These events are the ``meat and potatoes'' of MIDI files: the +actual notes and modifiers that command the instruments to play +the music. Each has a MIDI channel number as its first argument, +followed by event-specific parameters. To permit programs which process +CSV files to easily distinguish them from meta-events, names +of channel events all have a suffix of +.RB `` _c ''. +.TP +.IB "Track, Time, " "Note_on_c, " "Channel, Note, Velocity" +Send a command to play the specified +.I Note +(Middle C is defined as +.I Note +number 60; all other notes are relative in the MIDI specification, +but most instruments conform to the well-tempered scale) on the +given +.I Channel +with +.I Velocity +(0 to 127). A +.B Note_on_c +event with +.I Velocity +zero is equivalent to a +.BR Note_off_c . +.TP +.IB "Track, Time, " "Note_off_c, " "Channel, Note, Velocity" +Stop playing the specified +.I Note +on the given +.IR Channel . +The +.I Velocity +should be zero, but you never know what you'll find in +a MIDI file. +.TP +.IB "Track, Time, " "Pitch_bend_c, " "Channel, Value" +Send a pitch bend command of the specified +.I Value +to the given +.IR Channel . +The pitch bend +.I Value +is a 14 bit unsigned integer and hence must be in the inclusive range +from 0 to 16383. +.TP +.IB "Track, Time, " "Control_c, " "Channel, Control_num, Value" +Set the controller +.I Control_num +on the given +.I Channel +to the specified +.IR Value . +.I Control_num +and +.I Value +must be in the inclusive range 0 to 127. The assignment of +.I Control_num +values to effects differs from instrument to instrument. The +General MIDI specification defines the meaning of controllers +1 (modulation), 7 (volume), 10 (pan), 11 (expression), +and 64 (sustain), but not all instruments and patches respond +to these controllers. Instruments which support those +capabilities usually assign reverberation to controller 91 +and chorus to controller 93. +.TP +.IB "Track, Time, " "Program_c, " "Channel, Program_num" +Switch the specified +.I Channel +to program (patch) +.IR Program_num , +which must be between 0 and 127. The program or patch selects which +instrument and associated settings that channel will emulate. +The General MIDI specification provides a standard set of +instruments, but synthesisers are free to implement other sets +of instruments and many permit the user to create custom patches +and assign them to program numbers. +.TP +\ +Apparently due to instrument manufacturers' skepticism about +musicians' ability to cope with the number zero, many instruments +number patches from 1 to 128 rather than the 0 to 127 used within +MIDI files. When interpreting +.I Program_num +values, note that they may be one less than the patch numbers +given in an instrument's documentation. +.TP +.IB "Track, Time, " "Channel_aftertouch_c, " "Channel, Value" +When a key is held down after being pressed, some synthesisers +send the pressure, repeatedly if it varies, until the key is +released, but do not distinguish pressure on different keys played +simultaneously and held down. This is referred to as +``monophonic'' or ``channel'' aftertouch (the latter indicating +it applies to the +.I Channel +as a whole, not individual note numbers +on that channel). The pressure +.I Value +(0 to 127) is typically taken to apply to the last note played, +but instruments are not guaranteed to behave in this +manner. +.TP +.IB "Track, Time, " "Poly_aftertouch_c, " "Channel, Note, Value" +Polyphonic synthesisers (those capable of playing multiple +notes simultaneously on a single channel), often provide +independent aftertouch for each note. This event specifies +the aftertouch pressure +.I Value +(0 to 127) for the specified +.I Note +on the given +.IR Channel . +.SS "System Exclusive Events" +System Exclusive events permit storing vendor-specific +information to be transmitted to that vendor's products. +.TP +.IB "Track, Time, " "System_exclusive, " "Length, Data, ..." +The +.I Length +bytes of +.I Data +(0 to 255) +are sent at the specified +.I Time +to the MIDI channel defined by the most recent +.B Channel_prefix +event on the +.IR Track , +as a +System Exclusive message. +Note that +.I Length +can be any value between 0 and +.if t 2\s-2\v'-0.4m'28\v'0.4m'\s+2\-1. +.if n 2**28-1. +Programs which process MIDI CSV +files should be careful to protect against buffer overflows +and truncation of these records. +.TP +.IB "Track, Time, " "System_exclusive_packet, " "Length, Data, ..." +The +.I Length +bytes of +.I Data +(0 to 255) +are sent at the specified +.I Time +to the MIDI channel defined by the most recent +.B Channel_prefix +event on the +.IR Track . +The +.I Data +bytes are simply blasted out to the MIDI bus without +any prefix. This message is used by MIDI devices which +break up long system exclusive message into small +packets, spaced out in time to avoid overdriving their +modest microcontrollers. +Note that +.I Length +can be any value between 0 and +.if t 2\s-2\v'-0.4m'28\v'0.4m'\s+2\-1. +.if n 2**28-1. +Programs which process MIDI CSV +files should be careful to protect against buffer overflows +and truncation of these records. +.SH EXAMPLES +The following CSV file defines the five-note motif from the +film +.I "Close Encounters of the Third Kind" +using an organ patch from the General MIDI instrument +set. When processed by +.B midicsv +and sent to a synthesiser which conforms to General +MIDI, the sequence will be played. +.PP +.RS 5 +.nf 23 +0, 0, Header, 1, 2, 480 +1, 0, Start_track +1, 0, Title_t, "Close Encounters" +1, 0, Text_t, "Sample for MIDIcsv Distribution" +1, 0, Copyright_t, "This file is in the public domain" +1, 0, Time_signature, 4, 2, 24, 8 +1, 0, Tempo, 500000 +1, 0, End_track +2, 0, Start_track +2, 0, Instrument_name_t, "Church Organ" +2, 0, Program_c, 1, 19 +2, 0, Note_on_c, 1, 79, 81 +2, 960, Note_off_c, 1, 79, 0 +2, 960, Note_on_c, 1, 81, 81 +2, 1920, Note_off_c, 1, 81, 0 +2, 1920, Note_on_c, 1, 77, 81 +2, 2880, Note_off_c, 1, 77, 0 +2, 2880, Note_on_c, 1, 65, 81 +2, 3840, Note_off_c, 1, 65, 0 +2, 3840, Note_on_c, 1, 72, 81 +2, 4800, Note_off_c, 1, 72, 0 +2, 4800, End_track +0, 0, End_of_file +.RE +.SH BUGS +.PP +The CSV representation of a MIDI file is simply a text-oriented +encoding of its contents. If the input to +.B midicsv +contains errors which violate the MIDI standard, the +resulting CSV file will faithfully replicate these errors. +Similarly, the CSV input to +.B csvmidi +must not only consist of records which conform to the +syntax given in this document, the input as a whole +must also be a +.I semantically +correct MIDI file. Programs which wish to use +.B csvmidi +to generate MIDI files from scratch should be careful to +conform to the structure required of MIDI files. When in +doubt, use +.B midicsv +to dump a sequence comparable to the one your program will +create and use its structure as a template for your own. +.PP +Please report errors to +.BR bugs@fourmilab.ch . +.SH "SEE ALSO" +.PD +.BR csvmidi (1), +.BR midicsv (1) +.ne 10 +.SH AUTHOR +.ce 2 +John Walker +http://www.fourmilab.ch/ +.PP +This software is in the public domain. +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +without any conditions or restrictions. This software is provided ``as +is'' without express or implied warranty. diff --git a/midicsv-1.1/midicsv.c b/midicsv-1.1/midicsv.c new file mode 100644 index 0000000..04f43de --- /dev/null +++ b/midicsv-1.1/midicsv.c @@ -0,0 +1,510 @@ +/* + + Encode MIDI file into CSV format + + Designed and implemented in December of 1995 by John Walker. + Revised and updated by John Walker in October 1998 and + February 2004. + + http://www.fourmilab.ch/ + + This program is in the public domain. + +*/ + +#include +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +#include "version.h" +#include "types.h" +#include "midifile.h" +#include "midio.h" +#include "getopt.h" + +#define FALSE 0 +#define TRUE 1 + +static char *progname; /* Program name string */ +static int verbose = FALSE; /* Debug output */ + +/* VLENGTH -- Parse variable length item from in-memory track */ + +static vlint vlength(byte **trk, long *trklen) +{ + vlint value; + byte ch; + byte *cp = *trk; + + trklen--; + if ((value = *cp++) & 0x80) { + value &= 0x7F; + do { + value = (value << 7) | ((ch = *cp++) & 0x7F); + trklen--; + } while (ch & 0x80); + } +#ifdef DUMP + fprintf(stderr, "Time lapse: %d bytes, %d\n", cp - *trk, value); +#endif + *trk = cp; + return value; +} + +/* TEXTCSV -- Convert text field to CSV, quoting as necessary. + + If CSV_Quote_ISO if defined ISO 8859-1 graphic + characters are permitted in text strings + without octal quoting. Otherwise all + characters other than 7-bit ASCII graphics are + output as octal. + + Output is appended directly to output stream fo. +*/ + +#define CSV_Quote_ISO + +static void textcsv(FILE *fo, const byte *t, const int len) +{ + byte c; + int i; + + putc('"', fo); + for (i = 0; i < len; i++) { + c = *t++; + if ((c < ' ') || +#ifdef CSV_Quote_ISO + ((c > '~') && (c <= 160)) +#else + (c > '~') +#endif + ) { + putc('\\', fo); + fprintf(fo, "%03o", c); + } else { + if (c == '"') { + putc('"', fo); + } else if (c == '\\') { + putc('\\', fo); + } + putc(c, fo); + } + } + putc('"', fo); +} + +/* TRACKCSV -- Compile track into CSV written to fo. */ + +static void trackcsv(FILE *fo, const int trackno, + byte *trk, long trklen, const int ppq) +{ + int levt = 0, evt, channel, note, vel, control, value, + type; + vlint len; + byte *titem; + vlint abstime = 0; /* Absolute time in track */ +#ifdef XDD +byte *strk = trk; +#endif + + while (trklen > 0) { + vlint tlapse = vlength(&trk, &trklen); + abstime += tlapse; + + fprintf(fo, "%d, %ld, ", trackno, abstime); + + /* Handle running status; if the next byte is a data byte, + reuse the last command seen in the track. */ + + if (*trk & 0x80) { +#ifdef XDD +fprintf(fo, " (Trk: %02x NS: %02X : %d) ", *trk, evt, trk - strk); +#endif + evt = *trk++; + + /* One subtlety: we only save channel voice messages + for running status. System messages and file + meta-events (all of which are in the 0xF0-0xFF + range) are not saved, as it is possible to carry a + running status across them. You may have never seen + this done in a MIDI file, but I have. */ + + if ((evt & 0xF0) != 0xF0) { + levt = evt; + } + trklen--; + } else { + evt = levt; +#ifdef XDD +fprintf(fo, " (Trk: %02x RS: %02X : %d) ", *trk, evt, trk - strk); +#endif + } + + channel = evt & 0xF; + + /* Channel messages */ + + switch (evt & 0xF0) { + + case NoteOff: /* Note off */ + if (trklen < 2) { + return; + } + trklen -= 2; + note = *trk++; + vel = *trk++; + fprintf(fo, "Note_off_c, %d, %d, %d\n", channel, note, vel); + continue; + + case NoteOn: /* Note on */ + if (trklen < 2) { + return; + } + trklen -= 2; + note = *trk++; + vel = *trk++; + /* A note on with a velocity of 0 is actually a note + off. We do not translate it to a Note_off record + in order to preserve the original structure of the + MIDI file. */ + fprintf(fo, "Note_on_c, %d, %d, %d\n", channel, note, vel); + continue; + + case PolyphonicKeyPressure: /* Aftertouch */ + if (trklen < 2) { + return; + } + trklen -= 2; + note = *trk++; + vel = *trk++; + fprintf(fo, "Poly_aftertouch_c, %d, %d, %d\n", channel, note, vel); + continue; + + case ControlChange: /* Control change */ + if (trklen < 2) { + return; + } + trklen -= 2; + control = *trk++; + value = *trk++; + fprintf(fo, "Control_c, %d, %d, %d\n", channel, control, value); + continue; + + case ProgramChange: /* Program change */ + if (trklen < 1) { + return; + } + trklen--; + note = *trk++; + fprintf(fo, "Program_c, %d, %d\n", channel, note); + continue; + + case ChannelPressure: /* Channel pressure (aftertouch) */ + if (trklen < 1) { + return; + } + trklen--; + vel = *trk++; + fprintf(fo, "Channel_aftertouch_c, %d, %d\n", channel, vel); + continue; + + case PitchBend: /* Pitch bend */ + if (trklen < 1) { + return; + } + trklen--; + value = *trk++; + value = value | ((*trk++) << 7); + fprintf(fo, "Pitch_bend_c, %d, %d\n", channel, value); + continue; + + default: + break; + } + + switch (evt) { + + /* System exclusive messages */ + + case SystemExclusive: + case SystemExclusivePacket: + len = vlength(&trk, &trklen); + fprintf(fo, "System_exclusive%s, %lu", + evt == SystemExclusivePacket ? "_packet" : "", + len); + { + vlint i; + + for (i = 0; i < len; i++) { + fprintf(fo, ", %d", *trk++); + } + fprintf(fo, "\n"); + } + break; + + /* File meta-events */ + + case FileMetaEvent: + + if (trklen < 2) { + return; + } + trklen -= 2; + type = *trk++; + len = vlength(&trk, &trklen); + titem = trk; + trk += len; + trklen -= len; + + switch (type) { + case SequenceNumberMetaEvent: + fprintf(fo, "Sequence_number, %d\n", (titem[0] << 8) | titem[1]); + break; + + case TextMetaEvent: +#ifdef XDD +fprintf(fo, " (Len=%ld Trk=%02x) ", len, *trk); +#endif + fputs("Text_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case CopyrightMetaEvent: + fputs("Copyright_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case TrackTitleMetaEvent: + fputs("Title_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case TrackInstrumentNameMetaEvent: + fputs("Instrument_name_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case LyricMetaEvent: + fputs("Lyric_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case MarkerMetaEvent: + fputs("Marker_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case CuePointMetaEvent: + fputs("Cue_point_t, ", fo); + textcsv(fo, titem, len); + putc('\n', fo); + break; + + case ChannelPrefixMetaEvent: + fprintf(fo, "Channel_prefix, %d\n", titem[0]); + break; + + case PortMetaEvent: + fprintf(fo, "MIDI_port, %d\n", titem[0]); + break; + + case EndTrackMetaEvent: + fprintf(fo, "End_track\n"); + trklen = -1; + break; + + case SetTempoMetaEvent: + fprintf(fo, "Tempo, %d\n", (titem[0] << 16) | + (titem[1] << 8) | titem[2]); + break; + + case SMPTEOffsetMetaEvent: + fprintf(fo, "SMPTE_offset, %d, %d, %d, %d, %d\n", + titem[0], titem[1], titem[2], titem[3], titem[4]); + break; + + case TimeSignatureMetaEvent: + fprintf(fo, "Time_signature, %d, %d, %d, %d\n", + titem[0], titem[1], titem[2], titem[3]); + break; + + case KeySignatureMetaEvent: + fprintf(fo, "Key_signature, %d, \"%s\"\n", ((signed char) titem[0]), + titem[1] ? "minor" : "major"); + break; + + case SequencerSpecificMetaEvent: + fprintf(fo, "Sequencer_specific, %lu", len); + { + vlint i; + + for (i = 0; i < len; i++) { + fprintf(fo, ", %d", titem[i]); + } + fprintf(fo, "\n"); + } + break; + + default: + if (verbose) { + fprintf(stderr, "Unknown meta event type 0x%02X, %ld bytes of data.\n", + type, len); + } + fprintf(fo, "Unknown_meta_event, %d, %lu", type, len); + { + vlint i; + + for (i = 0; i < len; i++) { + fprintf(fo, ", %d", titem[i]); + } + fprintf(fo, "\n"); + } + break; + } + break; + + default: + if (verbose) { + fprintf(stderr, "Unknown event type 0x%02X.\n", evt); + } + fprintf(fo, "Unknown_event, %02Xx\n", evt); + break; + } + } +} + +/* Main program. */ + +int main(int argc, char *argv[]) +{ + struct mhead mh; + FILE *fp, *fo; + long track1; + int i, n, track1l; + + fp = stdin; + fo = stdout; + progname = argv[0]; + while ((n = getopt(argc, argv, "uv")) != -1) { + switch (n) { + case 'u': + fprintf(stderr, "Usage: %s [ options ] [ midi_file ] [ csv_file ]\n", progname); + fprintf(stderr, " Options:\n"); + fprintf(stderr, " -u Print this message\n"); + fprintf(stderr, " -v Verbose: dump header and track information\n"); + fprintf(stderr, "Version %s\n", VERSION); + return 0; + + case 'v': + verbose = TRUE; + break; + + case '?': + fprintf(stderr, "%s: undefined option -%c specified.\n", + progname, n); + return 2; + } + } + + i = 0; + while (optind < argc) { + switch (i++) { + case 0: + if (strcmp(argv[optind], "-") != 0) { + fp = fopen(argv[optind], "rb"); + if (fp == NULL) { + fprintf(stderr, "%s: Unable to to open MIDI input file %s\n", + progname, argv[optind]); + return 2; + } + } + break; + + case 1: + if (strcmp(argv[optind], "-") != 0) { + fo = fopen(argv[optind], "w"); + if (fo == NULL) { + fprintf(stderr, "%s: Unable to to create CSV output file %s\n", + progname, argv[optind]); + return 2; + } + } + break; + } + optind++; + } +#ifdef _WIN32 + + /* If input is from standard input, set the input file + mode to binary. */ + + if (fp == stdin) { + _setmode(_fileno(fp), _O_BINARY); + } +#endif + + /* Read and validate header */ + + readMidiFileHeader(fp, &mh); + if (memcmp(mh.chunktype, "MThd", sizeof mh.chunktype) != 0) { + fprintf(stderr, "%s is not a Standard MIDI File.\n", argv[1]); + return 2; + } + if (verbose) { + fprintf(stderr, "Format %d MIDI file. %d tracks, %d ticks per quarter note.\n", + mh.format, mh.ntrks, mh.division); + } + + /* Output header */ + + fprintf(fo, "0, 0, Header, %d, %d, %d\n", mh.format, mh.ntrks, mh.division); + + /* Process tracks */ + + for (i = 0; i < mh.ntrks; i++) { + struct mtrack mt; + byte *trk; + + if (i == 0) { + track1 = ftell(fp); + } + + readMidiTrackHeader(fp, &mt); + if (memcmp(mt.chunktype, "MTrk", sizeof mt.chunktype) != 0) { + fprintf(stderr, "Track %d header is invalid.\n", i + 1); + return 2; + } + + if (verbose) { + fprintf(stderr, "Track %d: length %ld.\n", i + 1, mt.length); + } + fprintf(fo, "%d, 0, Start_track\n", i + 1); + + trk = (byte *) malloc(mt.length); + if (trk == NULL) { + fprintf(stderr, "%s: Cannot allocate %ld bytes for track.\n", + progname, mt.length); + return 2; + } + + fread((char *) trk, (int) mt.length, 1, fp); + if (i == 0) { + track1l = (int) (ftell(fp) - track1); + } + + trackcsv(fo, i + 1, trk, mt.length, mh.division); + free(trk); + } + fprintf(fo, "0, 0, End_of_file\n"); + return 0; +} diff --git a/midicsv-1.1/midicsv.o b/midicsv-1.1/midicsv.o new file mode 100644 index 0000000..f9c4124 Binary files /dev/null and b/midicsv-1.1/midicsv.o differ diff --git a/midicsv-1.1/midifile.h b/midicsv-1.1/midifile.h new file mode 100644 index 0000000..2d96588 --- /dev/null +++ b/midicsv-1.1/midifile.h @@ -0,0 +1,90 @@ +/* + + MIDI File Definitions + +*/ + +/* MIDI command codes */ + +typedef enum { + + /* Channel voice messages */ + + NoteOff = 0x80, + NoteOn = 0x90, + PolyphonicKeyPressure = 0xA0, + ControlChange = 0xB0, + ProgramChange = 0xC0, + ChannelPressure = 0xD0, + PitchBend = 0xE0, + + /* Channel mode messages */ + + ChannelMode = 0xB8, + + /* System messages */ + + SystemExclusive = 0xF0, + SystemCommon = 0xF0, + SystemExclusivePacket = 0xF7, + SystemRealTime = 0xF8, + SystemStartCurrentSequence = 0xFA, + SystemContinueCurrentSequence = 0xFB, + SystemStop = 0xFC, + + /* MIDI file-only messages */ + + FileMetaEvent = 0xFF +} midi_command; + +/* MIDI file meta-event codes */ + +typedef enum { + SequenceNumberMetaEvent = 0, + TextMetaEvent = 1, + CopyrightMetaEvent = 2, + TrackTitleMetaEvent = 3, + TrackInstrumentNameMetaEvent = 4, + LyricMetaEvent = 5, + MarkerMetaEvent = 6, + CuePointMetaEvent = 7, + + ChannelPrefixMetaEvent = 0x20, + PortMetaEvent = 0x21, + EndTrackMetaEvent = 0x2F, + + SetTempoMetaEvent = 0x51, + SMPTEOffsetMetaEvent = 0x54, + TimeSignatureMetaEvent = 0x58, + KeySignatureMetaEvent = 0x59, + + SequencerSpecificMetaEvent = 0x7F +} midifile_meta_event; + +/* The following structures are for in-memory manipulation of MIDI + file components and must not be used for reading or writing + MIDI files to external media. MIDI files must be written in + big-endian byte order with no padding to word boundaries and + I/O code must comply with this format regardless of the host's + in-memory representation. */ + +/* MIDI file header */ + +#define MIDI_Header_Sentinel "MThd" + +struct mhead { + char chunktype[4]; /* Chunk type: "MThd" */ + long length; /* Length: 6 */ + short format; /* File format */ + short ntrks; /* Number of tracks in file */ + short division; /* Time division */ +}; + +/* MIDI track header */ + +#define MIDI_Track_Sentinel "MTrk" + +struct mtrack { + char chunktype[4]; /* Chunk type: "MTrk" */ + long length; /* Length of track */ +}; diff --git a/midicsv-1.1/midio.c b/midicsv-1.1/midio.c new file mode 100644 index 0000000..b5d9f08 --- /dev/null +++ b/midicsv-1.1/midio.c @@ -0,0 +1,136 @@ +/* + + MIDI File Input/Output Utilities + +*/ + +#include +#include + +#include "types.h" +#include "midifile.h" +#include "midio.h" + +/* Low level input functions. */ + +/* READLONG -- Read long from a file (byte-order independent) */ + +long readlong(FILE *fp) +{ + unsigned char c[4]; + + fread((char *) c, 1, sizeof c, fp); + return (long) ((c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]); +} + +/* READSHORT -- Read short from a file (byte-order independent) */ + +short readshort(FILE *fp) +{ + unsigned char c[2]; + + fread((char *) c, 1, sizeof c, fp); + return (short) ((c[0] << 8) | c[1]); +} + +/* READVARLEN -- Parse variable length value from MIDI file */ + +vlint readVarLen(FILE *fp) +{ + long value; + int ch; + + if ((value = getc(fp)) & 0x80) { + value &= 0x7F; + do { + value = (value << 7) | ((ch = getc(fp)) & 0x7F); + } while (ch & 0x80); + } + return value; +} + +/* High level input functions. */ + +/* READMIDIFILEHEADER -- Read file header structure. */ + +void readMidiFileHeader(FILE *fp, struct mhead *h) +{ + fread(h->chunktype, sizeof h->chunktype, 1, fp); + h->length = readlong(fp); + h->format = readshort(fp); + h->ntrks = readshort(fp); + h->division = readshort(fp); +} + +/* READMIDITRACKHEADER -- Read track header structure. */ + +void readMidiTrackHeader(FILE *fp, struct mtrack *t) +{ + fread(t->chunktype, sizeof t->chunktype, 1, fp); + t->length = readlong(fp); +} + +/* Low level output functions. */ + +/* WRITELONG -- Write a long to a file in big-endian order */ + +void writelong(FILE *fp, const long l) +{ + putc((l >> 24) & 0xFF, fp); + putc((l >> 16) & 0xFF, fp); + putc((l >> 8) & 0xFF, fp); + putc(l & 0xFF, fp); +} + +/* WRITESHORT -- Write a short to a file in big-endian order */ + +void writeshort(FILE *fp, const short s) +{ + putc((s >> 8) & 0xFF, fp); + putc(s & 0xFF, fp); +} + +/* WRITEVARLEN -- Write variable length value to MIDI file */ + +void writeVarLen(FILE *fp, const vlint v) +{ + vlint value = v; + long buffer; + + buffer = value & 0x7F; + while ((value >>= 7) > 0) { + buffer <<= 8; + buffer |= 0x80; + buffer += (value & 0x7F); + } + + while (1) { + putc((int) (buffer & 0xFF), fp); + if (buffer & 0x80) { + buffer >>= 8; + } else { + break; + } + } +} + +/* High level output functions. */ + +/* WRITEMIDIFILEHEADER -- Write file header structure. */ + +void writeMidiFileHeader(FILE *fp, struct mhead *h) +{ + fwrite(h->chunktype, sizeof h->chunktype, 1, fp); + writelong(fp, h->length); + writeshort(fp, h->format); + writeshort(fp, h->ntrks); + writeshort(fp, h->division); +} + +/* WRITEMIDITRACKHEADER -- Write track header structure. */ + +void writeMidiTrackHeader(FILE *fp, struct mtrack *t) +{ + fwrite(t->chunktype, sizeof t->chunktype, 1, fp); + writelong(fp, t->length); +} diff --git a/midicsv-1.1/midio.h b/midicsv-1.1/midio.h new file mode 100644 index 0000000..1ec40b0 --- /dev/null +++ b/midicsv-1.1/midio.h @@ -0,0 +1,17 @@ +/* + + MIDI I/O Function Definitions + +*/ + +extern long readlong(FILE *fp); +extern short readshort(FILE *fp); +extern vlint readVarLen(FILE *fp); +extern void readMidiFileHeader(FILE *fp, struct mhead *h); +extern void readMidiTrackHeader(FILE *fp, struct mtrack *t); + +extern void writelong(FILE *fp, const long l); +extern void writeshort(FILE *fp, const short s); +extern void writeVarLen(FILE *fp, const vlint v); +extern void writeMidiFileHeader(FILE *fp, struct mhead *h); +extern void writeMidiTrackHeader(FILE *fp, struct mtrack *t); diff --git a/midicsv-1.1/midio.o b/midicsv-1.1/midio.o new file mode 100644 index 0000000..0021537 Binary files /dev/null and b/midicsv-1.1/midio.o differ diff --git a/midicsv-1.1/test.mid b/midicsv-1.1/test.mid new file mode 100644 index 0000000..d1939c8 Binary files /dev/null and b/midicsv-1.1/test.mid differ diff --git a/midicsv-1.1/torture.pl b/midicsv-1.1/torture.pl new file mode 100644 index 0000000..f37e44d --- /dev/null +++ b/midicsv-1.1/torture.pl @@ -0,0 +1,173 @@ + +# Generate "torture test" for MidiCSV / CSVmidi + +# The output is a CSV file written on standard output +# which should be fed to through CSVMIDI and MIDICSV +# and the output compared. + + srand(1234); # Make pseudorandom sequences repeatable + + print << 'EOD'; + +; + ; MIDIcsv Torture Test + # +0, 0, Header, 1, 4, 480 +1, 0, Start_track +1, 0, TiTlE_t, "MidiCSV Torture Test" +1,0, copyRight_t, "© 1808 L. van Beethoven. This document is in the public domain." +1, 0, SMPTE_offset, 96, 0, 0, 0, 0 +1, 0, key_signature, 0, minor +1, 0, Time_signature, 4, 2, 24, 8 +1, 100, Text_t, "The tempo varies somewhat in this passage." +1, 482, Tempo, 335977 +1, 961, Tempo, 333493 +1, 1442, Tempo, 333319 +1, 10578, End_track +2, 0, Start_track +2, 0, Sequence_number, 64000 +2, 0, MIDI_port, 0 +2, 0, Instrument_Name_t, "Brass Section" +2, 0, Program_c, 1, 61 +2, 259, marker_T, "Bom bom bom..." +2, 259, Lyric_t, "Bom" +2, 259, Note_on_c, 1, 67, 104 +2, 501, Note_off_c, 1, 67, 0 +2, 525, Lyric_t, "Bom" +2, 525, Note_on_c, 1, 67, 104 +2, 740, Note_on_c, 1, 67, 0 +2, 764, Lyric_t, "Bom" +2, 764, Note_on_c, 1, 67, 104 +2, 978, Note_on_c, 1, 67, 0 +2, 1000, Marker_t, "...BOM!" +2, 1003, Lyric_t, "BOMMMMM!" +2, 1003, Note_on_c, 1, 63, 104 +2, 4809, Note_on_c, 1, 63, 0 + # Second 4 notes +2, 4850, Cue_point_t, "Conductor falls off podium" +2, 5047, Instrument_name_t, """Rock"" Organ" +2, 5048, Program_c, 1, 18 +2, 5048, Note_on_c, 1, 65, 104 +2, 5100, pitch_bend_c, 1, 10000 +2, 5100, channel_aftertouch_c, 1, 127 +2, 5200, pitch_bend_c, 1, 4000 +2, 5289, note_off_c, 1, 65, 0 +2, 5300, Control_c, 1, 91, 120 +2,5300, Control_c, 1, 7, 40 +2, 5311, Note_on_c, 1, 65, 104 +2,5400,pitch_BEND_c,1,0 +2, 5526, Note_on_c, 1, 65, 0 +2, 5540, control_c, 1, 7, 127 +2,5540, control_c, 1, 10, 0 +2, 5549, Pitch_Bend_c, 1,8192 +2, 5549, Note_on_c, 1, 65, 104 +2, 5766, Note_on_c, 1, 65, 0 +2,5790, control_c, 1, 10, 127 +2, 5790, Note_on_c, 1, 62, 104 +2, 9000, Poly_aftertouch_c, 1, 62, 127 +2, 10578, Note_off_c, 1, 62, 0 +2, 10578, End_track +3, 0, Start_track +3, 10578, End_track +4, 0, Start_Track +4, 10, Channel_prefix, 1 +4, 400, System_exclusive, 10, 0, 255, 127, 30, 96, 255, 224, 108, 31, 0 +4, 510, System_exclusive, 4, 0, 121, 31, 19 +4, 1071, channel_prefix, 15 +4, 1071, System_exclusive_packet, 10, 255, 18, 33, 111, 0, 77, 201, 7, 4, 255 +4, 1076, System_exclusive_packet, 0 +4, 1108, System_exclusive_packet, 3, 0, 0, 0 +4, 2000, Sequencer_specific, 10, 80, 211, 54, 71, 229, 0, 13, 128, 12, 40 +4, 2020, Sequencer_specific, 0 +4, 3000, Unknown_meta_event, 121, 9, 39, 201, 118, 6, 91, 223, 0, 78, 56 +; The following event is actually + ; 4, 3030, Key_signature, -4, "major" +# coded as an Unknown_meta_event. +4, 3100, Unknown_meta_event, 89, 2, 252, 0 +EOD + + # Now programmatically generate some long strings + # and byte vectors to push the limits. + + # Moderately long Sysex + $n = 5123; + print("4, 4000, System_exclusive, $n"); + &obytes($n); + print("\n"); + + # Moderately long pure ASCII text string + $n = 11213; + print("4, 4100, Text_t, \""); + for ($i = 0; $i < $n; $i ++) { + $r = chr(ord(' ') + int(rand(127 - ord(' ')))); + if ($r eq '"') { + print('"'); + } elsif ($r eq '\\') { + print('\\'); + } + print("$r"); + } + print("\"\n"); + + # Rather long arbitrary text string + print("4, 4200, Text_t, "); + &ostring(74219); + print("\n"); + + # Really long Sequence_specific + $n = 3497861; # 250,000th prime! + print("4, 4300, Sequencer_specific, $n"); + &obytes($n); + print("\n"); + + # Really long arbitrary text string + print("4, 4400, Lyric_t, "); + &ostring(4256233); # 300,000th prime! + print("\n"); + + # Wind up the track and the file with canned data + + print << 'EOD'; +4,10500, End_track +0, 0, End_of_file +EOD + + # Generate a random character string containing all + # byte values from 0 through 255. The argument gives + # the length in bytes to generate. + + sub ostring + { + local ($howlong) = @_; + local ($i, $r); + + print('"'); + for ($i = 0; $i < $howlong; $i ++) { + $r = chr(int(rand(256))); + if ($r eq '"') { + print('"'); + } elsif ($r eq '\\') { + print('\\'); + } elsif ((ord($r) < ord(' ')) || + ((ord($r) >= 127) && (ord($r) <= 160))) { + printf("\\%03o", ord($r)); + next; + } + print("$r"); + } + print('"'); + } + + # Generate a byte sequence whose length is + # given by the argument. + + sub obytes + { + local ($howlong) = @_; + local ($i, $r); + + for ($i = 0; $i < $howlong; $i ++) { + $r = int(rand(256)); + print(", $r"); + } + } diff --git a/midicsv-1.1/transpose.pl b/midicsv-1.1/transpose.pl new file mode 100644 index 0000000..d2958cb --- /dev/null +++ b/midicsv-1.1/transpose.pl @@ -0,0 +1,34 @@ + + # Transpose all notes in a CSV MIDI file + + # This Perl program is an example of how simple it can + # be to transform MIDI files in CSV format. This program + # filters a CSV MIDI file from standard input to standard + # output, shifting all notes by the value given as + # $offset. Notes on the $percussion channel are not + # shifted. + + $offset = -12; + $percussion = 9; + + while ($a = <>) { + + # Recognise Note_on_c and Note_off_c records and crack into: + + # $1 Start of record + # $2 Channel number + # $3 Note number + # $a Balance of record + + if ($a =~ s/(\d+,\s*\d+,\s*Note_\w+,\s*(\d+),\s*)(\d+)//) { + $n = $3; + if ($2 != $percussion) { + $n += $offset; + } + if ($n < 0) { + next; + } + $a = "$1$n$a"; + } + print($a); + } diff --git a/midicsv-1.1/types.h b/midicsv-1.1/types.h new file mode 100644 index 0000000..3d16657 --- /dev/null +++ b/midicsv-1.1/types.h @@ -0,0 +1,10 @@ +/* + + Type definitions for MIDI tools + +*/ + +typedef unsigned char byte; /* MIDI data stream byte */ +typedef unsigned long vlint; /* Variable length integer: this must + be an unsigned type of at least + 32 bits (longer is OK). */ diff --git a/midicsv-1.1/version.h b/midicsv-1.1/version.h new file mode 100644 index 0000000..548380e --- /dev/null +++ b/midicsv-1.1/version.h @@ -0,0 +1,2 @@ + +#define VERSION "1.1 (January 2008)" diff --git a/midisep.pl b/midisep.pl new file mode 100755 index 0000000..ebd2f9d --- /dev/null +++ b/midisep.pl @@ -0,0 +1,161 @@ +#!/usr/bin/perl +use Data::Dumper; +use Cwd; + +our $DEBUG = 1; + +our $MIDICSV_BIN = Cwd::abs_path("midicsv-1.1/midicsv"); +our $CSVMIDI_BIN = Cwd::abs_path("midicsv-1.1/csvmidi"); + +our $CHANNEL_EVENTS = [ + 'Note_on_c', 'Note_off_c', 'Pitch_bend_c', 'Control_c', + 'Program_c', 'Channel_aftertouch_c', 'Poly_aftertouch_c', +]; + +our $SYSEX_EVENTS = [ + 'System_exclusive', 'System_exclusive_packet' +]; + + +sub usage{ + print STDERR sprintf('$>%s midifile [output_dir]'."\n", $0); + exit 1; +} + + + +sub error{ + my $msg = shift; + print STDERR $msg . "\n"; + exit(1); +} + +sub warn_sysex_found{ + my $event = shift; + my $warning = sprintf("Sysex event found on channel %s\n", $event->{channel}); + print STDERR $warning; +} + + +sub correct_bad_note_ons{ + my $event = shift; + if ($event->{type} =~ /Note_on/i && $event->{data}->[-1] == 0) { + $event->{type} = "Note_off_c"; + $event->{data}->[2] =~ s/on/off/i; + } + return $event; +} + +sub file_to_list{ + my $file = shift; + my $total_channels = shift; + my $csvdata = `$MIDICSV_BIN $file`; + my $list = [ + map { + chomp($_); + my $ctx = {}; + + my $line = [ split(',', $_ ) ]; + $ctx->{data} = $line; + $ctx->{type} = $line->[2]; + if (grep { $ctx->{type} =~ /$_/i } @$CHANNEL_EVENTS ){ + $ctx->{channel} = ($line->[3] =~ m/([0-9]+)/g)[0]; + if (!(grep { $ctx->{channel} == $_ } @$total_channels )){ + push(@$total_channels, $ctx->{channel}); + } + } +# [ @line ]; + $ctx; + } split('\n', $csvdata) ]; + return $list; +} + +sub add_to_all_outputs{ + my $event = shift; + my $output_files = shift; + foreach my $events (values %$output_files){ + push (@$events, join(',' , @{$event->{data}})); + }; +} + +sub add_to_single_output{ + my $event = shift; + my $output_files = shift; + push(@{$output_files->{$event->{channel}}}, join(',', @{$event->{data}})); +} + + +sub process_event_list{ + my $event_list = shift; + my $total_channels = shift; + my $output_data = {}; + + if ($event_list->[0]->{type} !~ /header/i){ + error("Bad header"); + } + #marker header as type0 + $event_list->[0]->{data}->[3] =~ s/[0-9]+/0/g; + + #check if file has more than one track/composition + my $tracks_count = $header->{data}->[4]; + if ($tracks_count > 1){ + error("Multiple tracks in same file, not yet implemented."); + } + foreach my $channel(@$total_channels){ + $output_data->{$channel} = []; + } + + #main loop + foreach $event(@$event_list){ + if ( grep { $event->{type} =~ /$_/i } @$CHANNEL_EVENTS ){ + $event = correct_bad_note_ons($event); + add_to_single_output( $event, $output_data ); + } + elsif ( grep { $event->{type} =~ /$_/i} @$SYSEX_EVENTS ){ + #consider adding special handle for sysex events + #currently just ignores them + warn_sysex_found ($event); + } + else { + add_to_all_outputs( $event, $output_data); + } + }; + + return $output_data; +} + + +sub data_to_files{ + my $output_data = shift; + my $filename_prefix = shift; + foreach my $channel (keys %$output_data){ + my $midi_filename = sprintf("%s-%s.mid", $filename_prefix, $channel); + my $csv_filename = sprintf("/tmp/%s-%s.csv", $filename_prefix, $channel); + open F, ">$csv_filename" or die $!; + print F (join("\n", @{$output_data->{$channel}})); + close(F); + system("$CSVMIDI_BIN $csv_filename $midi_filename"); + unlink($csv_filename); + } +} + +sub main{ + if (scalar(@ARGV) < 1){ + if ($DEBUG){ + print STDERR "DEBUG: using example.mid as default midifile\n"; + }else{ + usage; + } + } + + my $file = $ARGV[0] || "example.mid"; + my $output_dir = $ARGV[2] || "./"; + our $total_channels = []; + + our $filename_prefix = $output_dir . ($file =~ m/^(.+)\./g)[0]; + my $event_list = file_to_list ($file, $total_channels); + my $output_data = process_event_list ($event_list, $total_channels); + data_to_files($output_data, $filename_prefix); +# +}; +main; -- cgit v1.2.3-70-g09d2