MIDI reference information gleaned from elsewhere. Not all MIDI devices nor SoundFont files honor the conventions that follow: drum placements might be mixed up or different, and some SoundFont do not put the drums on channel 9/bank 128/program 0, or may have weird or incomplete instrument lists. However the following are probably a good starting point.
a very small MIDI writer library for Common LISP
The MIDI specification is very simple, though this may not be clear if the simplicity has been covered over by some library. The following is a minimal script that writes some cowbell events to a MIDI file.
#!/usr/bin/perl use 5.36.0; open my $out, '>', 'tiny.midi' or die "nope $!\n"; binmode $out; my $channel = 9; sub emit_header ( $fh, $format = 1, $ntrack = 1, $division = 96 ) { syswrite $fh, 'MThd' . pack( Nnnn => 6, $format, $ntrack, $division ), 14; } sub emit_track ( $fh, $track, $dtime = 0 ) { my $footer = pack( w => $dtime ) . "\xFF\x2F\x00"; my ( $tl, $fl ) = ( length $track, length $footer ); syswrite $fh, 'MTrk' . pack( N => $tl + $fl ), 8; syswrite $fh, $track, $tl; syswrite $fh, $footer, $fl; } emit_header( $out, 0 ); my $track = ''; for ( 1 .. 8 ) { $track .= pack( w => 0 ) . sprintf( "%c%c%c", ( 0x90 | $channel ), 56, 120 ) . pack( w => 96 ) . sprintf( "%c%c%c", ( 0x80 | $channel ), 56, 0 ); } emit_track( $out, $track );
Tempo can be given as microseconds (1e6 per second) per MIDI quarter note, though more useful may be the means to convert between tempo (bpm) and milliseconds.
(deftype strictly-positive () '(integer 1 *)) (defun bpm2ms (bpm &optional (beats 4)) (declare (strictly-positive bpm beats)) (round (/ 240000 (* bpm beats)))) (defun ms2bpm (ms &optional (beats 4)) (declare (strictly-positive ms beats)) (round (/ 240000 (* ms beats)))) (bpm2ms 120) ; 500 ms (bpm2ms 144) ; 417 ms
There is also a negative value for ticks (a SMPTE format); in that case see the specification.
The drum channel is usually 9 (counting from 0; musicians tend to count from 1 so under that scheme the drums are on 10, but in the protocol it's 9). A conventional drumtrack might involve a bass drum, high-hat, and hand clap, which may involve MIDI pitch 36, 44, and 39. However 44 was pretty inaudible on the SoundFonts I tried, so I switched that to 42. The velocities of the various parts may need better balancing. YMMV.
With the Perl MIDI module installed, MIDI files can be inspected by using the "dump tracks" option.
#!/usr/bin/env perl use MIDI; my $file = shift; die "Usage: dump.pl midi-file\n" unless defined $file; MIDI::Opus->new( { from_file => $file } ) ->dump( { dump_tracks => 1 } );
With a MIDI cable (or probably USB these days) one can throw numbers at a synth. The MIDI protocol is pretty simple (it was designed for not too complicated devices back in the 1980s) though I have no idea how complicated the modern MIDI 2.0 or whatever has become.
An important script may be to shut the synthesizer up, in the (likely) event code you wrote had an error. This script is for OpenBSD, but other operating systems that offer a MIDI device you can print bytes to should be pretty similar.