Table of contents

Introduction

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.

Installation

Requirements: (without any midi-devices Midish will be probably useless).

To install midish:

  1. Untar and gunzip the tar-ball:
            gunzip midish-0.2.tar.gz
            tar -xf midish-0.2.tar
            cd midish-0.2
    	
  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"', see next section)

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).

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).

An example

Suppose that there are two devices: Thus, the /etc/midishrc file contains the following lines:
	devattach 0 "/dev/rmidi4"	# attach the module as dev number 0
	devattach 1 "/dev/rmidi3"	# attach the keyboard as dev number 1
	
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.

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"
        devattach 1 "/dev/rmidi3"
        

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.

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.

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 }
        

Filtering/routing

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

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 "tic". The rate of controller, bender and aftertouch events is normalised 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 summarises 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.

Examples

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).

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.

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
        }
        

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.

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.

Time structure

In midish, time is split in measures. Each measure is split in beats and each beat is split in tics. The tic is the fundamental time unit in midish. Duration of tics 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 tics, thus by default one beat corresponds to one quarter note, i.e. the time signature is 4/4.

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.

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
        

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
        

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.

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.

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).

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
        

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 tics 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 quantised. 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).

Track quantisation

A track can be quantised by rounding note-positions to the nearest exact position. The following will quantise 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 quantisation. 100% means full quantisation and 0% leans no quantisation at all. This is useful because full quantisation often sound to regular especially on acoustic patches.

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.

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 the midi devices. System exclusive ('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 synthesisers. 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}
        

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]             # tics 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.

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.

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"
        

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.

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.

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.

Changes

Changes from release 0.1 to release 0.2

Project attributes

Device attributes

The following table summarises the device attributes:

attrubute description
unit number integer that is used to reference the device
ticrate number of tic 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, tics) are transmitted to the midi device.

Channel attributes

The following table summarises the channel attributes:

attrubute 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.

Filter attributes

The following table summarises the filter attributes:

attrubute description
name identifier used to reference the fitler
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.

Track attributes

The following table summarises the track attributes:

attrubute description
name identifier used to reference the track
mute flag if the mute = true, the the track is not played on playback
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 fitler.

Sysex attributes

The following table summarises the sysex back attributes:

attrubute 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.

Song attributes

The following table summarises the song attributes:

attrubute description
meta track a track containing tempo changes and time signature changes
tics_per_unit number of midi tics per unit note, the default value is 96 which corresponds to the midi standard.
metronome flag if the metronome = true, then the metronome is audible
metro_hi a note-on event that is sent on the begining of every measure if the metronome is enabled
metro_lo a note-on event that is sent on the begining 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 trach 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 quatisation step in tics. 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.

Events and event ranges specification

Event specification

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

Event references correspond to the following midi events:

noff note off
non note off
kat key after-touch (poly)
ctl controller
pc program change
cat channel after-touch (mono)
bend pitch bend

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
        { pc {1 3} 34 }
        
set controller number 7 to 99 on device/channel drums:
        { ctl drums 7 99 }
        

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
        

Language reference

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.

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:

Expressions

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

"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 recognised:

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 righ
!= 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 } } 
        

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
        }
        

Function reference

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 tic ev
put the event ev on track trackname at the position given by measure, beat and tic
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 tic
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
quantise amount measures of the track trackname from measure from ; the quantum is the round in tics (see songsetunit). Rate must be between 0 and 100. 0 means no quantisation and 100 means full quantisation.
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.

Channel functions

channew channelname { device 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.
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.

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/midi-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.

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

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 }
        

Song functions

songsetcurquant tics
set the number of tics in the time scale (for quantisation, track editing...). Currently this value is not used in real-time.
songgetcurquant tics
get the number of tics 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 postition of the current selection.
songgetcurpos
return the current measure ie the current song position pointer. This corresponds to the start postition 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 tics per unit (1 unit = 4 quarter notes). the unit shall be changed before creating any tracks. The default is 96 tics per unit, which is the default of the midi standard.
songgetunit
return the number of tics per unit note
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.

Device functions

devlist
return the list of attached devices (list of numbers)
devattach devnum filename
attach midi device filename as device number devnum; filename is a quoted string.
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 tics, midi start and midi trop events) will be transmitted to device number devnum. Otherwise no real-time midi events are transmitted.
devticrate devnum ticrate
set the number of tics per unit note that are transmitted to the midi device if "sendrt" feature is active. Default value is 96 tics. This is the standard MIDI value and its not recommended to change it.
devinfo devnum
Print some information about the midi device.

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.
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

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 quantisation 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
quantise the current selection of the current track (rate=100 means full quantisation and rate=0 means no quantisation).
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.
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

Example sessions

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).

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 quantisation step to 16 (sixteenth note), we select the first 32 measures of the track and we quantise them. Finally, we start playback and we save the song into a file.
Copyright (c) 2003-2005 Alexandre Ratchov
Last updated oct 27, 2005