Table of contents

1 Introduction

1.1 What is midish?

Midish is an open-source MIDI sequencer/filter for Unix-like operating systems (tested on OpenBSD and Linux). Implemented as a simple command-line interpreter (like a shell) it's intended to be lightweight, fast and reliable for real-time performance.

Important features:

Midish is open-source software distributed under a 2-clause BSD-style license.

1.2 Installation

Requirements: (without any MIDI devices midish will be probably useless).

To install midish:

  1. Untar and gunzip the tar-ball:
            gunzip midish-0.3.tar.gz
            tar -xf midish-0.3.tar
            cd midish-0.3
    	
  2. Edit Makefile and change the readline(3) options in Makefile
  3. Compile midish, just type "make all", this will build midish and rmidish, the readline(3) front-end to midish.
  4. Install binaries, documentation and examples by typing "make install". They are copied as follows:
  5. If there isn't a /etc/midishrc file, then copy the sample file by typing "cp midishrc /etc" in your shell.
  6. Read the documentation and modify /etc/midishrc in order to choose the default MIDI device by using the devattach function, example:
    	devattach 0 "/dev/rmidi3" rw
    	
    see next section for details.

1.3 Invocation

Midish is a MIDI sequencer/filter driven by a command-line interpreter (like a shell). Once midish started, the interpreter prompts for commands. Then, it can be used to configure MIDI devices, create tracks, define channel/controller mappings, route events from one device to another, play/record a song etc. To start midish, just type:

        rmidish
        
Then, commands are issued interactively on the midish prompt, example:
        print "hello world"
        
Midish has two modes: prompt mode and performance mode.

In prompt mode, midish interactively waits for commands from the command-line. This mode is used to configure the sequencing engine: create and manage tracks, define filtering rules start/stop recording, etc... MIDI devices aren't used, they are let closed in order to let other applications to use them.

In performance mode, midish processes midi input/output in real-time. Commands cannot be issued on the command-line. This mode is typically used for playback and record. To stop performance mode, hit control-C (or send an interrupt signal (SIGINT) to midish).

1.4 How does it work

Midish uses the following objects to represent a project: The above objects are grouped in a project (a song) and manipulated in prompt mode by issuing interactively commands.

Performance mode is used to play/record the project. When performance mode is entered, MIDI devices are opened, and all sysex messages and channel configuration events are sent to them. There are three performance modes:

The above performance modes are started respectively with songidle, songplay, songrecord. Performance mode is blocking (no commands can be issued on the prompt). To return again to prompt mode, send a interrupt signal (SIGINT) to midish (hit control-C on the console).

1.5 An example

Suppose that there are two devices: Thus, the /etc/midishrc file contains the following lines:
	devattach 0 "/dev/rmidi4" rw	# attach the module as dev number 0
	devattach 1 "/dev/rmidi3" rw	# attach the keyboard as dev number 1
	
the rw means that the device will be opened in read-write mode. The following session shows how to record a simple track. First, we define a filter named piano that routes events from device 1, channel 0 (input channel of the keyboard) to device 0, channel 5 (output channel of the sound module). Then we create a new track pi1, we start recording and we save the song into a file.
	1> filtnew piano			# create filter "piano"
	2> filtchanmap piano {1 0} {0 5}	# dev=1,ch=0 -> dev=0,ch=5
	3> tracknew pi1				# create track "pi1"
	4> songrecord				# start recording
	press control-C to finish

	--interrupt--

	5> songsave "mysong"			# save the song into a file
	6>					# EOF (control-D) to quit
	

The same task can be achieved in a much easier way by using the simple procedures (or macros) defined in the default /etc/midishrc.

	1> ci {1 0}			# select default input {dev, chan}
	2> ni piano {0 5}		# create piano on dev=0, chan=5
	3> nt pi1 			# new track named "pi1"
	4> r				# start recording
	press control-C to finish

	--interrupt--

	5> save "mysong"		# save to file "mysong"
	6>				# EOF (control-D) to quit
	

Note: It is strongly recommended to define simple procedures and to use them instead of directly using the built-in functions of midish.

2 Devices setup

In midish, MIDI devices are numbered from 0 to 15. Each MIDI device has its "slot number". For instance, suppose that there is a MIDI sound module known as /dev/rmidi3 and a MIDI keyboard known as /dev/rmidi4. The following commands will configure the module as device number 0 and the keyboard as device number 1:
        devattach 0 "/dev/rmidi4" rw
        devattach 1 "/dev/rmidi3" rw
        

Note: To make easier the import/export procedure from systems with different configurations, it's strongly recommended to attach the main sound module (the mostly used one) as device number 0.

In order to check that the sound module is properly configured play the demo song:

        songload "sample.sng"
        songplay
        
(hit control-C to stop playback). When the configuration is setup, put the "devattach" commands in the user's $HOME/.midishrc. It will be automatically executed the next time midish is run.

3 Channels

Because midish handles multiple devices, instead of using MIDI channels, it uses device / MIDI channel pairs to represent instruments. So channel refers the device / MIDI channel pair. Channels are handled by two-item lists, like this:
        {0 1}                   # device 0, MIDI channel 1
        
Channels can also be named, as follows:
        channew mybass {0 1}
        
this defines a named-channel mybass that can be used instead of the {0 1} pair.

3.1 Channel configuration

A channel represents one musical instrument; midish allows to attach to the channel definition basic "configuration" events like program changes and controllers. Such events are sent to the output when performance mode is entered, for instance just before playback is started. This approach avoids flooding MIDI devices with "slow" events (like program changes).

For instance; to select patch 34 on channel mybass, attach a "program change" event:

        chanconfev mybass {pc mybass 32}
        
the list-argument gives the event to attach to the channel. See the event section for more details about events.

To set the volume (controller 7) of this channel to 120:

        chanconfev mybass {ctl mybass 7 120}
        
If several events of the same type are attached then just the last one is kept. So, the following will change the volume to 125 by replacing the above event:
        chanconfev mybass {ctl mybass 7 125}
        

4 Filtering/routing

Midish supports MIDI filtering: a filter transforms incoming MIDI events and send them to the output. The filter also "sanitizes" the input MIDI stream by removing nested notes, duplicate controllers and other anomalies. Filters are in general used to:

4.1 About filters

Multiple filters can be defined, however only the current filter will run in performance mode. If no filters are defined or if there is no current filter then, in performance mode, input is sent to the output as-is. The current filter processes input MIDI events in the following order:

  1. First, input events are checked for inconsistencies: nested note-on, orphaned note-off and duplicate controller, bender and aftertouch events are removed. Zero-length notes are lengthened to one "tick". The rate of controller, bender and aftertouch events is normalized in order not to flood output devices.
  2. The input event is checked against all voice rules and the resulting events (if any) are sent to the output of the filter (if the event matches more than one voice rule then it is duplicated if needed).
  3. If the input didn't match any voice rule then it is checked against all channel rules and the resulting events (if any) are sent to the output of the filter. (if the event matches more than one channel rule then it is duplicated if needed).
  4. If the input event didn't match any channel rule then it is checked against all device rules and the resulting events (if any) are sent to the output of the filter (if several device rules match the input event then it is duplicated if needed).
  5. Finally, if the input event didn't match any device rule then it is passed to the output of the filter as-is.

The following diagram summarizes the event path through the filter:

                +------------+  
                |   voice    | match
        in ---->|            |-----------------------------------------> out
                |   rules    |
                +------------+
              doesn't |         +------------+
                match |         |  channel   | match
                      \-------->|            |-------------------------> out
                                |   rules    |
                                +------------+
                              doesn't |         +------------+
                                match |         |   device   | match
                                      \-------->|            |---------> out
                                                |   rules    |
                                                +------------+
                                              doesn't | 
                                                match | 
                                                      \----------------> out
        
        

Filters are defined as follows:

        filtnew myfilt                  # define filter "myfilt"
        
initially the filter is empty and will send input to output as-is. Once the filter is created, filtering rules can be added, modified and removed. See filtering functions section for details.

Rules can be listed with filtinfo. All filtering rules can be removed with filtreset.

4.2 Examples

4.2.1 Device redirections

The following example defines a filter that routes events from device number 1 (the MIDI keyboard) to device number 0 (the sound module).
        filtnew mydevmap                # define filter "mydevmap"
        filtdevmap mydevmap 1 0         # make it route device 1 -> device 0
        
To test the filter, start performance mode:
        songidle
        
(hit control-C to stop performance mode).

4.2.2 Channel maps

The following example defines a filter that routes events from device 1, channel 0 (first channel of the keyboard) to device 0, channel 9 (default drum channel of the sound module).
        filtnew mydrums                 # define filter "mydrums"
        filtchanmap mydrums {1 0} {0 9} # route dev/chan {1 0} to {0 9}
        
The device/channel pair is in braces. The first {1 0} is the input device/channel and {0 9} is the output channel. To test the filter, start performance mode:
        songidle
        
(hit control-C to stop performance mode). Playing on channel 0 of the keyboard will make sound channel 9 of the sound-module.

4.2.3 Controller maps

The following example add a new rule to the above filter that maps the modulation wheel (controller 1) of the source channel (ie device 1, channel 0) to the expression controller (number 11) of the destination channel (device 0, channel 9).
        filtctlmap mydrums {1 0} {0 9} 1 11
        
the first three arguments are the name of the filter, the input and the output device/channel pair. The 4-th argument is the controller number on the input (1 = modulation) and the 5-th argument is the controller number on the output (11 = expression). Rules of the filter can be listed as follows:
        filtinfo mydrums
        
which will print:
        {
                chanmap {1 0} {0 9}
                ctlmap {1 0} {0 9} 1 11 id
        }
        

4.2.4 Transpose

The following example transposes by 12 half-tones (one octave) notes on device 1, channel 0 (keyboard) and sends them on device 0 channel 2 (sound-module).
        filtnew mypiano                 # define filter "mypiano"
        filtchanmap mypiano {1 0} {0 2} # route dev/chan {1 0} to {0 9}
        filtkeymap  mypiano {1 0} {0 2} 0 127 12
        
both rules are necessary. Note events are handled by the key-rule and other events (controllers) fall trough the channel-rule. Arguments 4 and 5 to filtkeymap give the note range that will be handled (from 0 to 127, i.e. the whole keyboard) and the 6-th argument gives the number of half-tones (12, one octave) to transpose.

4.2.5 Keyboard splits

In the same way it is possible to create a keyboard-split with two key-rules and two channel-rules. The following example splits the keyboard in two parts (left and right) on note 64 (note E3, the middle of the keyboard). Notes on the left part will be routed to channel 3 of the sound module and notes on the right part will be routed to channel 2 of the sound module.
        filtnew mysplit
        filtchanmap mysplit {1 0} {0 2}
        filtkeymap mysplit  {1 0} {0 2} 0  63  0
        filtchanmap mysplit {1 0} {0 3}
        filtkeymap mysplit  {1 0} {0 3} 64 127 0
        

Defining filters seems quite tedious, however it's possible to define procedures that do the same in a very simpler way. See the interpreter language for more details.

5 Time structure

In midish, time is split in measures. Each measure is split in beats and each beat is split in ticks. The tick is the fundamental time unit in midish. Duration of ticks is fixed by the tempo. By default midish uses:

From the musical point of view, a beat often corresponds to a quarter note, to an eight note etc... By default an unit note corresponds to 96 ticks, thus by default one beat corresponds to one quarter note, i.e. the time signature is 4/4.

5.1 Metronome

In order to "hear" time, a metronome can be used. It is used only in play and record modes. It produces a click sound on every beat. To start the metronome:
        metroswitch 1           # switch the metronome on
        songplay                # start playback
        
The metronome has two kind of click-sound: The click-sound can be configured by giving a couple of note-on events, as follows:
        metroconf {non {0 9} 48 127} {non {0 9} 64 100}
        
this configures the high-click with note 48, velocity 127 on device 0, channel 9 and the low-click with note 64, velocity 100 on device 0, channel 9.

5.2 Time signature changes

Time signature changes are achieved by inserting or deleting measures. The following starts a song with time signature of 4/4 (at measure 0) and change the time signature to 6/8 at measure 2 during 5 measures:
        songtimeins 0 2 4 4     # 4/4 at measure 0 during 2 measures
        songtimeins 2 5 6 8     # 8/6 at measure 2 during 5 measures
        metroswitch 1           # switch the metronome on
        songplay                # test it
        
To suppress measure number 2 (the first 6/8 measure)
        songtimerm  2 1         # remove 1 measure starting a measure 2
        metroswitch 1           # switch the metronome on
        songplay                # test it
        

5.3 Tempo changes

Tempo changes are achieved simply by giving the measure number and the tempo value in beats per minute. For instance, the following changes tempo on measure 0 to 100 beats per minute and on measure 2 to 180 beats per minute.
        songsettempo 0 100
        songsettempo 2 180
        

5.4 Moving within the song

The following selects the current position in the song to measure number 3:
        songsetcurpos 3
        
This will make songrecord and songplay start at this particular position instead of measure number 0.

6 Tracks

A track is a piece of music, namely an ordered in time list of MIDI events. In play mode, midish play simultaneously all defined tracks, in record-mode it plays all defined tracks and records the current track.

Tracks aren't assigned to any particular device/channel; a track can contain MIDI data from any device/channel. A track can have its current filter; in this case, MIDI events are passed through that filter before being recorded. If the track has no current filter, then the song current filter is used instead. If there is neither track current filter nor song current filter, then MIDI events from all devices are recorded as-is.

6.1 Recording a track without a filter

The following defines a track and record events as-is from all MIDI devices:

        tracknew mytrack
        songrecord
        
tracks are played as follows:
        songplay
        
However, with the above configuration this will not work as expected because events from the input keyboard (device number 1) will be recorded as-is and then sent back to the device number 1 instead of being sent to the sound module (device number 0).

6.2 Recording a track with a filter

The following creates a filter and uses it to record to the above track:
        filtnew mypiano
        filtchanmap mypiano {1 0} {0 0}         # dev1/chan0 -> dev0/chan0
        tracknew mytrack
        songrecord
        

6.3 Basic editing of a track

Most track editing functions in midish take at least the following arguments:

For instance to blank measure number 3 of track mypiano:
        trackblank mypiano 3 1 (96 / 16) {}
        
the 3-rd argument set the precision to sixteenth note (assuming 96 ticks per unit note). This means that notes position is rounded to the nearest 16-th note before being removed. This is useful, because often recorded notes doesn't start exactly on the measure boundary. The precision argument makes possible to edit a track that is not quantized. The latest argument (empty list) selects the events to be deleted (see section event ranges).

In a similar way, one can cut a piece of a track, for instance to cut 2 measures starting at measure number 5:

        trackcut mypiano 5 2 (96 / 16)
        

The following inserts 2 blank measures at measure number 3:

        trackinsert mypiano 3 2 (96 / 16)
        

A track portion can be copied into another track as follows:

        trackcopy mypiano 3 2 mypiano2 5 (96 / 16) {}
        
this will copy 2 measures starting from measure number 3 into (the already existing) track mypiano2 at measure 5. The latest argument (empty list) selects the events to be copied (see section event ranges).

6.4 Track quantization

A track can be quantized by rounding note-positions to the nearest exact position. The following will quantize 4 measures starting at measure number 3 by rounding notes to the nearest quarter note.
        trackquant mypiano 3 4 (96 / 4) 75
        
The last arguments gives the percent of quantization. 100% means full quantization and 0% leans no quantization at all. This is useful because full quantization often sound to regular especially on acoustic patches.

6.5 Checking a track

It is possible that a MIDI device transmits bogus MIDI data. The following scans the track and removes bogus notes and unused controller events:
        trackcheck mytrack
        
This function can be useful to remove nested notes when a track is recorded twice (or more) without being erased.

7 Frames, more about filtering and editing

In midish, MIDI events are packed into frames. For instance a note-on event followed by a note-off with the same note number constitute a frame. All filtering and editing functions work on frames, not on events. That means that all events within a frame are processed consistently. For instance, deleting a note-on event will also delete related note-off and key-aftertouch events. This ensures full consistency of tracks and MIDI I/O streams.

7.1 Note frames

Note frames are made of a starting "non" event any optional "kat" events and the stopping "noff" event. In editing functions note frames are copied/moved/deleted as a whole; they are never truncated. The starting event (note on) determines whether the frame is selected. For instance, in trackblank function, only note frames whose "non" events are in the selected region are erased.

Conflicting note frames (ie with the same note number) are never merged. An attempt to copy a note frame on the top of a second one will erase the second one. This avoids having nested notes.

7.2 Pitch bend frames

A pitch bend frame is made of "bend" events. It starts with a "bend" event whose value is different from the default value (ie different from 0x3FFF). It stops when the value reaches the default value of 0x3FFF. In editing functions a "bend" frame may be truncated or split into multiple frames, however resulting frames always terminate with the default value. For instance, trackblank may erase the middle of "bend" frame resulting in two new "bend" frame (the beginning and the ending of the old one).

Conflicting "bend" frames are merged. An attempt to copy a frame on top of another one will overwrite conflicting regions of the second one and "glue" the rest; this will result in a single frame, containing chunks of both original frames.

7.3 Controller frames

The way controller events are packed in a frame depend on the controller type. Currently the following controllers are supported: The user can specify for each controller number the desired behavior. The ctlconf function configures the controller. As arguments, it takes the name of the controller (an identifier) the controller number and the default value of the controller. If the default value is nil (ie no default value), then the controller is considered as of type parameter. For instance, following command:
        ctlconf expr 11 127
        
configures controller number 11 of type frame and with default value of 127. The controller name can be any identifier, it can be used for other functions to reference a controller. The ctlinfo function can be used to display the current configuration of all controllers:
	ctltab {
	        #
	        # name  number  defval
		#
	        mod     1       0
	        vol     7       nil
		sustain 64      0
	}
        

7.4 Other frames

Channel aftertouch events are packed in channel aftertouch frames. They behave exactly as pitch bender frames, except that the default value is zero.

Program/bank change, NRPN and RPN events are not packed together, they are single event frames.

8 System exclusive messages

Midish can send system exclusive messages to MIDI devices before starting performance mode. Typically, this feature can be used to change the configuration of MIDI devices. System exclusive (aka "sysex") messages are stored into named banks. To create a sysex bank named mybank:
        sysexnew mybank
        
Then, messages can be added:
        sysexadd mybank 0 {0xF0 0x7E 0x7F 0x09 0x01 0xF7}
        
This will store the "General-MIDI ON" messages into the bank. The second argument (here "0") is the device number to which the message will be sent when performance mode is entered. To send the latter messages to the corresponding device, just enter performance mode, for instance:
        songidle
        
Sysex messages can be recorded from MIDI devices, this is useful to save "bulk dumps" from synthesizers. Sysex messages are automatically recorded on the current bank. So, to record a sysex:
        songsetcursysex mybank
        songrecord
        
The next time performance mode is entered, recorded sysex messages will be sent back to the device. Information about the recorded sysex messages can be obtained as follows:
        sysexinfo mybank
        
A bank can be cleared by:
        sysexclear mybank {}
        
the second argument is a (empty) pattern, that matches any sysex message in the bank. The following will remove only sysex messages starting with 0xF0 0x7E 0x7F:
        sysexclear mybank {0xF0 0x7E 0x7F}
        
Sysex messages recorded from any device can be configured to be sent to other devices. To change the device number of all messages to 1:
        sysexsetunit mybank 1 {}
        
the second argument is an empty pattern, thus it matches any sysex message in the bank. The following will change the device number of only sysex messages starting with 0xF0 0x7E 0x7F:
        sysexsetunit mybank 1 {0xF0 0x7E 0x7F}
        

9 Obtaining information

The following functions gives some information about midish objects:

        songinfo                        # summary
        songtimeinfo                    # tempo changes
        chaninfo mydrums                # list config. events in "mydrums"
        filtinfo myfilt                 # list rules in "myfilt"
        trackinfo mytrack (96 / 16) {}  # list number of events per measure
        devinfo 0                       # device properties
        

Objects can be listed as follows:

        print [tracklist]
        print [chanlist]
        print [filtlist]
        print [devlist]
        

Current values can be obtained as follows:

        print [songgetunit]             # ticks per unit note
        print [songgetcurpos]           # print current position
        print [songgetcurlen]        # print current selection length
        print [songgetcurfilt]          # current filter
        print [songgetcurtrack]         # current track
        print [songgetcursysex]
        print [trackgetcurfilt mypiano] # current filter of track "mypiano"
        print [filtgetcurchan mysplit]  # current channel of filter "mysplit"
        

The device and the MIDI channel of a channel definition can be obtained as follows:

        print [changetch mydrums]       # print midi chan number
        print [changetdev mydrums]      # print device number
        

To check if object exists:

        print [chanexists]
        print [filtexists]
        print [trackexists]
        print [sysexexists]
        
this will print 1 if the corresponding object exists and 0 otherwise.

10 Saving and loading songs

A song can be saved into a file. All channel definitions, filters, tracks, their properties, and values of the current track, current filter will be saved by:

        songsave "myfile"
        
In a similar way, the song can be load from a file as follows:
        songload "myfile"
        

Note that the "local settings" (like device configuration, metronome settings) are not saved.

11 Import/export standard MIDI files

Standard MIDI files type 0 or 1 can be imported. Each track in the standard MIDI file corresponds to a track in midish. Tracks are named trk00, trk01, ... All MIDI events are assigned to device number 0. Only the following meta events are handled:

all meta-events are removed from the "voice" tracks and are moved into the midish's meta-track. Finally tracks are checked for anomalies. Example:
        songimportsmf "mysong.mid"
        

Midish songs can be exported into standard MIDI files. Tempo changes and time signature changes are exported to a meta-track (first track of the MIDI file). Each channel definition is exported as a track containing the channel configuration events. Voice tracks are exported as is in separate tracks. Note that device numbers of MIDI events are not stored in the MIDI file because the file format does not allow this. Example:

        songexportsmf "mysong.mid"
        

12 The interpreter's language

Even to achieve some simple tasks with midish, it's sometimes necessary to write several long statements. To make midish more usable, it suggested to use variables and/or to define procedures, as follows.

12.1 Global variables

Variables can be used to store numbers, strings and references to tracks, channels and filters, like:

        let x = 53              # store 53 into "x"
        print $x                # prints "53"
        
The let keyword is used to assign values to variables and the dollar sign ("$") is used to obtain variable values.

12.2 Defining simple procedures

For instance, let us create a procedure named "i" that just replaces songidle in order to avoid typing its name.

        proc i {songidle;}
        
The proc keyword is followed by the procedure name and then follows a list of statements between braces. In a similar way can define the following procedures:
        proc p {
                metroswitch 0           # turn off metronome
                songplay                # start playback
        }
        
        proc r {
                metroswitch 1           # turn on metronome
                songrecord              # start recording
        }
        

Procedures can take arguments. For instance, to define a procedure named nt that creates a new track:

	proc nt name {
		tracknew $name
	}
        
After the name of the procedure follows the argument names list that can be arbitrary identifiers. The value of an argument is obtained by preceding the variable name by the dollar sign ("$"). We can use the above procedure to create a track:
        nt myfilt
        
A lot of similar procedures are defined in the sample midishrc file, shipped in the source tar-ball. To make midish easy to use, most of the usual tasks can be performed with only 2 or three character statements.

Procedure and variables definitions can be stored in the ~/.midishrc file (or /etc/midishrc). It will be automatically executed the next time you run midish.

13 Changes

13.1 Changes from release 0.1 to release 0.2

13.2 Changes from release 0.2 to release 0.3

14 Project attributes

14.1 Device attributes

The following table summarizes the device attributes:

attribute description
unit number integer that is used to reference the device
tickrate number of tick per unit note, default is 96, which corresponds to the MIDI standard
sendrt flag boolean; if sendrt = true, the real-time events (like start, stop, ticks) are transmitted to the MIDI device.
ixctlset list of continuous controllers that are expected to be received with 14bit precision.
oxctlset list of continuous controllers that will be transmitted with 14bit precision

14.2 Channel attributes

The following table summarizes the channel attributes:

attribute description
name identifier used to reference the channel
{dev chan} device and MIDI channel to which MIDI events are sent
conf events that are sent when performance mode is entered
curinput default input {dev chan} pair. This value isn't used in real-time, however it can be used as default value when adding new rules to a filter that uses this channel.

14.3 Filter attributes

The following table summarizes the filter attributes:

attribute description
name identifier used to reference the filter
rules set set of rules that handle MIDI events
current channel default channel. This value isn't used in real-time, however it can be used as default value when adding new rules to the filter.

14.4 Track attributes

The following table summarizes the track attributes:

attribute description
name identifier used to reference the track
mute flag if true then the track is not played on by songplay/songrecord
current filter default filter. The track is recorded with this filter. If there is no current filter, then is is recorded with the song's default filter.

14.5 Sysex attributes

The following table summarizes the sysex back attributes:

attribute description
name identifier used to reference the sysex back
list of messages each message in the list contains the actual message and the unit number of the device to which the message has to be sent.

14.6 Song attributes

The following table summarizes the song attributes:

attribute description
meta track a track containing tempo changes and time signature changes
ticks_per_unit number of MIDI ticks per unit note, the default value is 96 which corresponds to the MIDI standard.
tempo_factor number by which the tempo is multiplied on play and record.
metronome flag if the metronome = true, then the metronome is audible
metro_hi a note-on event that is sent on the beginning of every measure if the metronome is enabled
metro_lo a note-on event that is sent on the beginning of every beat if the metronome is enabled
curtrack default track: the track that will be recorded in record mode
curfilt default filter. The filter with which the default track is recorded if it hasn't its default filter.
curchan default channel. This value isn't used in real-time, however it can be used as default value when adding new rules to the filter.
curpos current position (in measures) within the song. Playback and record start from this positions. It is also user as the beginning of the current selection
curlen length (in measures) of the current selection. This value isn't used in real-time, however it can be used as default value in track editing functions.
curquant current quantization step in ticks. This value isn't used in real-time, however it can be used as default value for the track editing functions
curinput default input {dev chan} pair. This value isn't used in real-time, however it can be uses as default value when adding new values to a filter that uses this channel.

15 Events and event ranges specification

15.1 Event specification

Some functions take events as arguments. An event is specified as a list containing:

Event references correspond to the following MIDI events:

ref. name MIDI event
noff note off
non note off
kat key after-touch (poly)
xctl 7bit controller
xctl 14bit controller
xpc bank and program change
cat channel after-touch (mono)
bend pitch bend
rpn RPN change
nrpn NRPN change

Examples:
note-on event on device 2, channel 9, note 64 with velocity 100:

        {non {2 9} 64 100}
        
program change device 1, channel 3, patch 34, bank 1234
        {xpc {1 3} 34 1234}
        
set controller number 7 to 99 on device/channel drums:
        {ctl drums 7 99}
        

15.2 Event ranges specification

Some track editing functions take an event range as argument. The event range is specified as a list containing: In the above, empty lists can also be used. An empty list means "match everything".

Examples:

        {}                      # match everything
        { any }                 # match everything
        { any bass }            # match anything on channel "bass"
        { any {1 4} }           # match anything on device 1, channel 4
        { any {1 {}} }          # match anything on device 1, any channel
        { any {{} 9} }          # match anything on any device and channel 9
        { note }                # match note events
        { note {1 9} }          # match notes on dev 1, channel 9
        { note {0 {}} }         # match notes on device 0
        { note {0 {3 5}} }      # match notes on device 0, channel 3, 4, 5
        { note {} {0 64} }      # match notes between 0 an 64
        { note {} 60 {80 127} } # match note 60 with velocity between 80 an 127
        { ctl bass }            # match controllers on channel "bass"
        { ctl bass 7 }          # match controller 7 on channel "bass"
        { ctl bass 7 {0 64} }   # match ctl 7 on chan 'bass' with value 0->64
        { bend {} {0 0x1fff} }  # match lower bender
        { xctl {} 19 }          # match 14bit controller number 19
        { xpc  {} 21 1234 }     # match patch 21 on bank 1234
        { nrpn {} 21 }          # NRPN 21 change
        

16 Language reference

16.1 Lexical structure

The input line is split into tokens: Multiple lines ending with "\" are parsed as a single line. Anything else generates a "bad token" error.

16.2 Statements

Any input line can be ether a function definition or a statement. Most statements end with the ";" character. However, in order to improve interactivity, the newline character can be used instead. Thus, the newline character cannot be used as a space. A statement can be:

16.3 Expressions

An expression can be an arithmetic expression of constants, expressions, variable values, return values of function calls. The following constant types are supported:

token type
"this is a string" a string
12345 a number
mytrack a reference
nil has no value

Variable are referenced by their identifier. Value of a variable is obtained with the "$" character.

        let i = 123             # puts 123 in i
        print $i                # prints the value of i
        
The following operators are recognized:

oper. usage associativity
{} list definition left to right
() grouping
[] function call
! logical NOT right to left
~ bitwise NOT
- unary minus
* multiplication left to right
/ division
% reminder
+ addition left to right
- subtraction
<< left shift left to right
>> right shift
< less left to right
<= less or equal
> greater
>= greater or equal
== equal left to right
!= not equal
& bitwise AND left to right
^ bitwise XOR left to right
| bitwise OR left to right
&& logical AND left to right
|| logical OR left to right

Examples:

   
        2 * (3 + 4) + $x
        
is an usual integer arithmetic expression.
        [tracklen mytrack]
        
is the returned value of the procedure tracklen called with a single argument mytrack.
        {"bla" 3 zer}
        
is a list containing the string "bla" the integer 3 and the name zer. A list is a set of expressions separated by spaces and enclosed between braces, a more complicated example is:
        {"hello" 1+2*3 mytrack $i [myproc] {a b c}} 
        

16.4 Procedure definition

A procedure is defined with the keyword proc followed by the name of the procedure, the names of its arguments and a block containing its body, example:
        proc doubleprint x y { 
                print $x
                print $y
        }
        
Arguments and variables defined within a procedure are local to that procedure and may shadow a global variable with the same name. The return value is given to the caller with a return statement:
        proc square x {
                return $x * $x
        }
        

17 Function reference

17.1 Track functions

tracklist
return the list of names of the tracks in the song example:
        print [tracklist]
        
tracknew trackname
create an empty track named trackname
trackdelete trackname
delete existing track trackname. Current track cannot be deleted.
trackrename trackname newname
rename track trackname to newname
trackexists trackname
return 1 if trackname is a track, 0 otherwise
trackaddev trackname measure beat tick ev
put the event ev on track trackname at the position given by measure, beat and tick
tracksetcurfilt trackname filtname
set the default filter (for recording) of trackname to filtname. It will be user if there is no current filter.
trackgetcurfilt trackname
return the default filter (for recording) of trackname, returns nil if none
trackcheck trackname
check the whole track for orphaned notes, nested notes and other anomalies; also removes multiple controllers in the same tick
trackcut trackname from amount quantum
cut amount measures of the track trackname from measure from.
trackblank trackname from amount quantum evspec
clear amount measures of the track trackname from the measure from. Only events matching the evspec argument are removed (see event ranges)
trackinsert trackname from amount
insert amount blank measures in track trackname just before the measure from
trackcopy trackname1 from amount trackname2 where quantum evspec
copy amount measures starting at from from track trackname1 into trackname2 at position where. Only events matching the evspec argument are copied (see event ranges)
trackquant trackname from amount rate quantum
quantize amount measures of the track trackname from measure from. Note positions are rounded to the nearest tick multiple of the quantum argument; the later must be larger than 4 ticks. Rate must be between 0 and 100: 0 means no quantization and 100 means full quantization.
tracktransp trackname start length halftones quantum evspec
transpose note events of measures within the [start; start + len] interval by halftones half tones. Only events matching the evspec argument are transposed (see event ranges)
trackmerge source dest
merge all frames from source track into dest track
tracksetmute trackname muteflag
If muteflag is equal to 1 the the track is muted ie it will not be played during record/playback. If muteflag is equal to 0 the the track is no more muted ie it will be played during record/playback.
trackgetmute trackname
Return 1 if the give track is muted and 0 otherwise.
trackchanlist trackname
Return the list of channels used by events stored in track trackname.
trackinfo trackname quantum evspec
display the number of events that match evspec for each measure of track trackname.

17.2 Channel functions

channew channelname {dev midichan}
create an new channel named channelname and assigned the given device and MIDI channel.
chanset channelname {dev midichan}
set the device/channel pair of an existing channel named channelname.
chandelete channame
delete existing channel channame.
chanrename channame newname
rename channel channame to newname
chanexists channelname
return 1 if channelname is a channel, 0 otherwise
changetch channelname
return the MIDI channel number of channel named channelname
changetdev channelname
return the device number of channel named channelname
chanconfev channelname event
add the event to the configuration of channel channelname, typically used set the program, volume, depth etc... The channel of the event is not used.
chanunconfev channelname evspec
remove events matching evspec (see event ranges) from the configuration of the channel
chaninfo channame
print all events on the config of the channel.
chansetcurinput channame {dev chan}
set the default input {dev chan} pair of channel channame. These values are currently not used in realtime.
changetcurinput channame
return the default input {dev chan} pair of channel channame.

17.3 Filter functions

filtnew filtname
create an new filter named filtname
filtdelete filtname
delete existing filter filtname. Current filter and filters used by tracks cannot be deleted.
filtrename filtname newname
rename filter filtname to newname
filtexists filtname
return 1 if filtname is a filter, 0 otherwise
filtreset filtname
remove all rules from the filter filtname
filtinfo filtname
list the rules of the given filter
filtsetcurchan filtname channame
set the default channel of filter filtname to channame.
filtgetcurchan filtname
return the default channel of filtname, returns nil if none
filtchgich filtname oldchan newchan
change the input channel of all rules with input channel equal to oldchan to newchan
filtchgidev filtname olddev newdev
change the input device of all rules with input device equal to olddev to newdev
filtswapich filtname oldchan newchan
change the input channel of all rules with input channel equal to oldchan to newchan, and the input channel of all rules with input channel equal to newchan to oldchan,
filtswapidev filtname olddev newdev
change the input device of all rules with input device equal to olddev to newdev, and the input device of all rules with input device equal to newdev to olddev
filtchgoch filtname oldchan newchan
change the output channel of all rules with output channel equal to oldchan to newchan
filtchgodev filtname olddev newdev
change the output device of all rules with output device equal to olddev to newdev
filtswapoch filtname oldchan newchan
change the output channel of all rules with output channel equal to oldchan to newchan, and the output channel of all rules with output channel equal to newchan to oldchan,
filtswapodev filtname olddev newdev
change the output device of all rules with output device equal to olddev to newdev, and the output device of all rules with output device equal to newdev to olddev
filtdevdrop filtname inputdev
make filter drop events from device inputdev
filtnodevdrop filtname inputdev
remove devdrop rules that drop events from device inputdev
filtdevmap filtname inputdev outputdev
route all events from device inputdev to device outputdev. If multiple dev maps are defined for the same input device then events are duplicated
filtnodevmap filtname outputdev
remove devmap rules that route events to device number outputdev.
filtchandrop filtname inputchan
make filter drop events from channel inputchan
filtnochandrop filtname inputchan
remove chandrop rules that drop events from channel inputchan
filtchanmap filtname inputchan outputchan
route all events from channel inputchan (ie device/channel pair) to channel outputchan. If multiple channel maps are defined for the same input channel then events are duplicated
filtnochanmap filtname outputchan
remove channel map rules that route events to channel outputchan.
filtctldrop filtname inchan inctl
make the filter drop controller number inctl on channel inctl.
filtnoctldrop filtname inchan inctl
remove ctldrop rules that drop controller number inctl on channel inctl.
filtctlmap filtname inchan outchan inctl outctl
route controller inctl from inchan to controller outctl on outchan. If multiple ctlmaps are defined for the same input channel and the same input controller then events are duplicated
filtnoctlmap filtname outchan outctl
remove ctlmap rules that route controllers to controller outctl, channel outchan.
filtkeydrop filtname inchan keystart keyend
drop notes between keystart and keyend on channel inchan.
filtnokeydrop filtname inchan keystart keyend
remove keydrop rule that drop notes between keystart and keyend on channel inchan.
filtkeymap filtname inchan outchan keystart keyend keyplus
route note events from channel inchan and in the range keystart..keyend to outchan. Routed notes are transposed by keyplus half-tones If multiple keymaps are defined for the same input channel and key-range then events are duplicated
filtnokeymap filtname outchan keystart keyend
remove keymap rules that route note events in the range keystart..keyend to channel outchan.

17.4 System exclusive messages functions

sysexnew sysexname
create a new bank of sysex messages named sysexname
sysexdelete sysexname
delete the bank of sysex messages named sysexname. Current sysex cannot be deleted.
sysexrename sysexname newname
rename sysex bank sysexname to newname
sysexexists sysexname
return 1 if sysexname is a sysex bank, 0 otherwise
sysexclear sysexname pattern
remove all sysex messages starting with pattern from sysex bank sysexname. The given pattern is a list of bytes; an empty pattern matches any sysex message.
sysexsetunit sysexname newunit pattern
set device number to newunit on all sysex messages starting with pattern from sysex bank sysexname. The given pattern is a list of bytes; an empty pattern matches any sysex message.
sysexadd sysexname unit data
add to sysex bank sysexname an new sysex message. data is a list containing the MIDI system exclusive message and unit is the device number to which the message will be sent
sysexinfo sysexname
print debug info about sysex bank sysexname

17.5 Real-time functions

songidle
put MIDI input to the MIDI output, data passes through the current filter (if any) or through the current track's filter (if any).
songplay
play the song from the current position. Input passes through the current filter (if any) or through the current track's filter (if any).
songrecord
play the song and record the input. Input passes through the current filter (if any) or through the current track's filter (if any). songrecord always tries to play one measure before the actual position on which recording starts.
sendraw device arrayofbytes
send raw MIDI data to device number device, can be used to send system exclusive messages, example:
        sendraw 0 {0xF0 0x7E 0x7F 0x09 0x01 0xF7}
        

17.6 Song functions

songsetcurquant ticks
set the number of ticks in the time scale (for quantization, track editing...). Currently this value is not used in real-time.
songgetcurquant ticks
get the number of ticks in the time scale.
songsetcurpos measure
set the current song position pointer to the measure measure. Record and playback will start a that position. This corresponds also to the start position of the current selection.
songgetcurpos
return the current measure ie the current song position pointer. This corresponds to the start position of the current selection.
songsetcurlen length
set the length of the current selection to length measures.
songgetcurlen
return the length (in measures) of the current selection.
songsetcurtrack trackname
set the current track ie the track to be recorded
songgetcurtrack
return the current track (if any) or nil
songsetcurfilt filtname
set the current filter ie the one used (with songplay and songidle).
songgetcurfilt
return the current filter or nil if none
songsetcursysex sysexname
set the current sysex bank, ie the one that will be recorded
songgetcursysex
return the current sysex bank or nil if none
songsetcurchan channame
set the current (named) channel.
songgetcurchan
return the name of the current channel or nil if none
songsetcurinput {dev chan}
set the (global) default input {dev chan} pair. These values are currently not used in realtime.
changetcurinput channame
return the (global) default input {dev chan} pair.
songsetunit tpu
set the time resolution of the sequencer to tpu ticks per unit (1 unit = 4 quarter notes). the unit shall be changed before creating any tracks. The default is 96 ticks per unit, which is the default of the MIDI standard.
songgetunit
return the number of ticks per unit note
songsetfactor tempo_factor
set the tempo factor for play and record to the given integer value. The tempo factor must be between 50 (play half of the real tempo) and 200 (play at twice the real tempo).
songgetfactor
return the current tempo factor
songsettempo measure bpm
set the tempo to bpm beats per minute at measure measure
songtimeins from amount numerator denominator
insert amount blank measures at measure from. The used time signature is given by numerator/denominator.
songtimerm from amount
delete amount measures starting at measure from. The time signature is restored with the value preceding the from measure.
songtimeinfo
print the meta-track (tempo changes, time signature changes
songinfo
display some info about the default values of the song
songsave filename
save the song in a file, filename is a quoted string.
songload filename
load the song from a file named filename. the current song is destroyed, even if the load command failed
songreset
destroy completely the song, useful to start a new song without restarting the program
songexportsmf filename
save the song into a standard MIDI file, filename is a quoted string.
songimportsmf filename
load the song from a standard MIDI file, filename is a quoted string. Currently only MIDI file "type 1" is supported.

17.7 Device functions

devlist
return the list of attached devices (list of numbers)
devattach devnum filename mode
attach MIDI device filename as device number devnum; filename is a quoted string. The mode argument is the name of the mode, it can be on if the following:
devdetach devnum
detach device number devnum
devsetmaster devnum
set device number devnum to be the master clock source. It will give tempo, start event and stop event. If devnum is nil, then the internal clock will be used and midish will act as master device.
devgetmaster devnum
return the current master device. If non, nil is returned.
devsendrt devnum bool
If bool is true, the real-time information (MIDI ticks, MIDI start and MIDI stop events) will be transmitted to device number devnum. Otherwise no real-time MIDI events are transmitted.
devticrate devnum ticrate
set the number of ticks per unit note that are transmitted to the MIDI device if "sendrt" feature is active. Default value is 96 ticks. This is the standard MIDI value and its not recommended to change it.
devinfo devnum
Print some information about the MIDI device.
devixctl devnum list
Setup the list of controllers that are expected to be received as 14bit numbers (ie both coarse and fine MIDI controller messages will be expected). By default only coarse values are used, if unsure let this list empty.
devoxctl devnum list
Setup the list of controllers that will be transmitted as 14bit numbers (both coarse and fine MIDI controller messages).

17.8 Controller functions

ctlconf ctlname ctlnumber defval
Configure controller number ctlnumber with name ctlname, and default value defval. If defval is nil then there is no default value and corresponding controller events are not grouped into frames. See sec. Controller frames.
ctlconfx ctlname ctlnumber defval
Same as ctlconf function, but for 14bit controllers. Thus defval is in the range 0..16383.
ctlconf ctlname
Unconfigure the given controller. ctlname is the identifier that was used with ctlconf
ctlinfo
Print the list of configured controllers

17.9 Misc. functions

metroswitch number
if number is equal to zero then the metronome is disabled else it is enabled
metroconf eventhi eventlo
select the notes that the metronome plays. The pair of events must be note-ons
info
display the list of built-in and user-defined procedures and global variables
print expression
display the value of the expression
exec filename
read and executes the script from a file, filename is a quoted string. The execution of the script is aborted on error. If the script executes an exit statement, only the script is terminated.
debug flag val
set debug-flag flag to (integer) value val. If val=0 the corresponding debug-info are turned off. flag can be:
panic
cause the sequencer to core-dump

18 Sample midishrc-file

The sample midishrc file shipped in the source tar-balls contains a lot of examples of procedure definitions.

ci {dev chan}

set the current input device/channel pair. It will be used as default value when tracks, filters and channels are created.
cc channame
set the current output channel. It will be used as default value when tracks and filters are created. The current input that was used the last time this channel was the current one is restored.
cf filtname
set the default filter, that will be used in performance mode. It will be used as default value when tracks are created. The value of the default track is reset to nil (no default track). The current channel and the current input that were used the last time this filter was the current one are restored.
ct trackname
set the current track, that will be recorded in record mode. The current filter, the current channel and the current input that were used the last time this track was the current one are restored.
ni instrname {device channel}
create a new channel and a new filter with the same name in such a way that the current input is routed to this channel.
ctldrop ictl
make the current filter drop controller number ictl on the current input
ctlmap ictl octl
make the current filter route controller number ictl on the current input to controller octl on the current channel
transpose halftones
make the current filter to transpose all notes from the current input and to route them to the current channel.
nt trackname
create a new track.
i
go into performance mode and run the current filter of the current track.
p
go in performance mode and play the all defined tracks and run the current filter of the current track
r
go in performance mode and record the current track using its current filter
l
list tracks, channels and filter and print some additional info
g measurenum
move the current song position to measure number measurenum. Play/record and all editing procedures that follow will start at that position
sel nummeasures
select measures from the current position. Track editing procedures (q, copy, cut, clr ...) will use the current selection.
n denominator
set the current note length for quantization to denominator, 4 means quarter-note, 8 means eighth-note, 16 means sixteenth-note etc... This value will be used by track editing functions.
q rate
quantize the current selection of the current track (rate=100 means full quantization and rate=0 means no quantization).
cut amount
cut the given number of measures from the current position on the current track.
clr
clear (removes events but not blank space) the current selection on the current track.
ins num
insert num empty measures into the current track at the current position
copy pos
copy the current selection pos measures forward from the current position.
gcut
same as cut but acts on all tracks simultaneously ("g" like global).
gins num
same as ins but acts on all tracks simultaneously ("g" like global).
gcopy num
same as copy but acts on all tracks simultaneously ("g" like global).
mute trackname
mute track trackname. The track will be no more audible during playback.
solo
mute all tracks but current
unmute trackname
unmute track trackname. The track will be audible during playback.
nomute
unmute all tracks.
save filename
save the whole project (filters, tracks, channels, and current settings) in file filename.
load filename
load the whole project (filters, tracks, channels, and current settings) from file filename.
tempo bpm
change the tempo at the current position to bpm beats per measure.
fac tempo_percent
change the tempo factor to the given integer value. Values greater than (smaller than) 100 will make play/record use faster (slower) tempo.
timeins num numerator denominator
insert num measures in the meta-track with the time signature numerator/denominator
timerm num
remove num measures from the meta-track.
gmon devnum
send "general midi on" system exclusive message to device number devnum
gmp patch
configures the current channel to use general MIDI patch number patch. (this will send program change event when performance mode is entered).
vol value
set volume (controller number 7) of the current channel
reverb value
set reverb (controller number 91) of the current channel
chorus value
set chorus (controller number 93) of the current channel
rpn addr val
set registered parameter number (RPN) addr for the current channel to val. Both addr and val are integers in the range 0..16383.
nrpn addr val
set not registered parameter number (NRPN) addr for the current channel to val. Both addr and val are integers in the range 0..16383.

19 Example sessions

19.1 Example - MIDI filtering

The following session show how to configure a keyboard split:
        1> channew bass {0 5}
        2> chanconfev bass {pc bass 33}
        3> channew piano {0 6}
        4> chanconfev piano {pc piano 2}
        5> filtnew split
        6> filtchanmap split {1 0} bass 
        7> filtchanmap split {1 0} piano
        8> filtkeymap  split {1 0} bass   0  63 (-12)
        9> filtkeymap  split {1 0} piano 64 127 0   
        10> filtinfo split
        {
                keymap {1 0} {0 2} 65 127 0 id
                keymap {1 0} {0 1} 0 64 116 id
                chanmap {1 0} {0 2}
                chanmap {1 0} {0 1}
        }
        11> songidle
        press enter to finish
        ^C
        -- interrupt --

        12> songsave "piano-bass"
	13>
        
First we define 2 named-channels bass on device 0, channel 5 and piano on device 0 channel 6. Then we assign patches to the respective channels. After this, we define a new filter split and we add rules corresponding to the keyboard-split on note number 64 (note E3), the bass is transposed by -12 half-tones (one octave).

19.2 Example - recording a track

The following session show how to record a track. It uses the procedures defined in the default /etc/midishrc.
	1> ci {1 0}
	2> ni drums {0 9}
	3> nt dr1
	4> tempo 90
	5> r
	press control-C to finish

	--interrupt--

	6> n 16
	7> sel 32
	8> q 75
	9> p
	press control-C to finish

	--interrupt--

	10> save "myrhythm"
	11> 
        
first, we set the default input channel to {1 0} (default channel of the keyboard). Then, we define the drum channel on device 0, channel 9 and the corresponding filter that will route events from the keyboard to the drum channel. Then we define a new track named dr1 an we start recording. Then, we set the quantization step to 16 (sixteenth note), we select the first 32 measures of the track and we quantize them. Finally, we start playback and we save the song into a file.

20 Thanks

Many thanks to all who contributed (new features, bug fixes, bug reports, packaging efforts and other improvements): Julien Claassen, Karim Saddem, Marcell Mars, Richard L. Hamilton, Samuel Mimram, and Will Woodruff.


Copyright (c) 2003-2007 Alexandre Ratchov
Last updated aug 19, 2007