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 BSD-style license.
Requirements:
Certain Linux distributions split libraries in two packages: one with run-time files only and one with development files. Both are necessary to build midish. Usually development packages have the ``-dev'' or ``-devel'' suffix. For instance, on Debian, package name is ``libasound2-dev''.
To install midish:
gunzip midish-1.0.tar.gz tar -xf midish-1.0.tar cd midish-1.0
On Linux systems it will use ALSA devices, on OpenBSD it will use sndio(7) devices and on other systems it will use raw MIDI devices. Binaries, scripts, examples and documentation will be installed in the /usr/local directory subtree; this can be overridden with the ``--prefix'' option. Example:
./configure --prefix=$HOME
cp midishrc /etc
dnew 0 "/dev/rmidi3" rwor if you're using ALSA (default on Linux), the following formats are accepted:
dnew 0 "28:0" rw dnew 0 "FLUID Synth (qsynth)" woon OpenBSD, the following formats are accepted:
dnew 0 "rmidi/3" rw dnew 0 "midithru/0" rw dnew 0 "snd/0" rwsee next section for details.
Once started, midish prompts for commands. Example:
print "hello world"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.
Once MIDI devices are set up, one of the performance modes can be started or stopped with one of the following single letter commands:
In performance mode certain features are not available (like most editing functions). Thus performance mode should be disabled in order to be able to edit the song; furthermore MIDI devices are closed and thus are available to other applications.
Midish uses the following objects to represent a project:
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 output configuration events are sent. There are three performance modes:
+---------+ +------------+ +----------+ | | | | | | | MIDI in |--------->| filter |--------->| MIDI out | | | | | | | +---------+ +------------+ +----------+
+--------------+ | track_1 play |---\ +--------------+ | | ... ---+ | +--------------+ | | track_N play |---+ +--------------+ | | +---------+ +------------+ | +----------+ | | | | \--->| | | MIDI in |------------>| filter |--------->| MIDI out | | | | | | | +---------+ +------------+ +----------+
+--------------+ | track_1 play |---\ +--------------+ | | ... ---+ | +--------------+ | | track_N play |---+ +--------------+ | | +---------+ +------------+ | +----------+ | | | | \--->| | | MIDI in |-----+------>| filter |----+---->| MIDI out | | | | | | | | | +---------+ | +------------+ | +----------+ | | | | +----------------+ | +--------------+ \---->| track_X record | \------>| sysex record | +----------------+ +--------------+
The above performance modes are started with the single letter commands ``i'', ``p'' and ``r'' respectively. Certain functions are not available during performance mode; to stop it, use the ``s'' function.
Suppose that there are two devices:
In this case, the ``/etc/midishrc'' file probably should contain the following lines:
dnew 0 "/dev/rmidi4" wo # attach the module as dev number 0 dnew 1 "/dev/rmidi3" ro # attach the keyboard as dev number 1
the ``wo'' parameter means that the device will be opened in write-only mode.
If you're using ALSA, instead of /dev/rmidi3 and /dev/rmidi4, use the ALSA sequencer ports, as listed by ``aseqdump -l'', example:
dnew 0 "28:0" wo # attach the module as dev number 0 dnew 1 "32:0" ro # attach the keyboard as dev number 1
Alsa also accepts client names, instead of client numbers, ex:
dnew 0 "FLUID Synth (qsynth)" wo
If you're using OpenBSD, insted of /dev/rmidi3 and /dev/rmidi4, use the sndio(7) port names:
dnew 0 "rmidi/3" wo # attach the module as dev number 0 dnew 1 "rmidi/4" ro # 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 any event from device 1, channel 0 (the keyboard input) to device 0, channel 5 (the sound module output). Then we create a new track ``pi1'', we start recording and we save the song into a file.
send EOF character (control-D) to quit [0000:00]> fnew piano # create filter "piano" [0000:00]> fmap {any {1 0}} {any {0 5}} # dev=1,ch=0 -> dev=0,ch=5 [0000:00]> tnew pi1 # create track "pi1" [0000:00]> r # start recording [0006:02]> s # stop recording [0000:00]> save "mysong.msh" # save the song into a file [0000:00]> # EOF (control-D) to quit
In midish, MIDI devices are numbered from 0 to 15. Each MIDI device has its device 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:
dnew 0 "/dev/rmidi4" rw dnew 1 "/dev/rmidi3" rw
If you're using ALSA, then use ALSA port names (as listed by ``aseqdump -l'' command) instead of the device node paths. If ``nil'' is given instead of the path, then the port is not connected to any existing port; this allows other ALSA sequencer clients to subscribe to it and to provide events to midish or to consume events midish sends to it.
If you're using OpenBSD, then use sndio(7) port names (hardware ports, software MIDI thru boxes, aucat(1) control devices).
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:
send EOF character (control-D) to quit [0000:00]> load "sample.msh" # load the file [0000:00]> p # start playback [0004:02]> s # stop playback
When devices are set up, put the corresponding dnew commands in the user's ``$HOME/.midishrc''. It will be automatically executed the next time midish is run.
Inputs are named ``{device channel}'' pairs on which MIDI events arrive. Inputs are handled by two-item lists, like this:
{1 0} # device 1, MIDI channel 0
Inputs are named using the ``inew'' command, as follows:
inew keyboard {1 0}
this defines the ``keyboard'' symbol that may be used instead of the ``{1 0}'' pair.
Note: Unlike simple ``{device channel}'' pairs, inputs are used internally by certain functions. For instance, inputs are used to create default filter rules.
Outputs are named ``{device channel}'' pairs where go outgoing MIDI events. They are handled by two-item lists, like this:
{0 4} # device 0, MIDI channel 4
Outputs are named using the ``onew'' command, as follows:
onew mybass {0 4}
this defines an output named ``mybass'' that may be used instead of the ``{0 4}'' pair.
Outputs have a filter with the same name; it defines how input events are routed to the output. The filter comes with a default rule that routes all named inputs to the given output. See filter section for further details.
Inputs and outputs may hold configuration events (like program changes and controllers) to be attached. Such events are sent when performance mode is entered, for instance just before playback is started.
For instance, to set program number 34 on output ``mybass'', attach a ``program change'' event as follows. First make ``mybass'' the current output:
co mybassthen, set its program number:
oaddev {pc mybass 34}
the list argument gives the event to attach to the output. See the event section for more details about events.
To set its volume (controller 7) to 120:
oaddev {ctl mybass 7 120}
If a setting is set multiple times, the last one is kept. For instance, the following will change the volume to 125 by replacing the above event:
oaddev {ctl mybass 7 125}Events attached to inputs work the same way. When performance mode is entered, they are sent as if they come from the input. Consequently they pass through the current filter and will be recorded on the current track in record mode. This makes them useful only in very specific cases.
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 on |
kat | key after-touch (poly) |
ctl | 7-bit controller |
xctl | 14-bit 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} 1234 34}
set controller number 7 to 99 on the ``drums'' output:
{ctl drums 7 99}
Filter and track editing functions use event sets to select the set of events they will affect. For instance a filter may check if incoming events match an user supplied event set in order to decide whether to drop or to keep the incoming event. The user must specify event sets as lists of parameter ranges. An event set is a list containing:
In the above, empty lists can also be used. An empty list means "match everything". If the ``none'' keyword is used as event set type then the event set is the empty set, i.e. it matches no events.
Examples:
{} # match everything { any } # match everything { none } # match nothing { any 1 } # match anything on device 1 { any bass } # match anything on "bass" output { any {1 4} } # match anything on device 1, channel 4 { note } # match note events { note {1 9} } # match notes on dev 1, channel 9 { note {0 3..5} } # match notes on device 0, channel 3, 4, 5 { note {} 0..64 } # match notes between 0 an 64 { ctl bass } # match controllers on "bass" output { ctl bass 7 } # match controller 7 on "bass" output { bend {} } # match bender { xctl {} 19 } # match 14-bit controller number 19 { xpc {} 1234 21 } # match patch 21 on bank 1234 { nrpn {} 21 } # match NRPN 21 change
Short system-exclusive patterns can be handled as events. Such events are defined with the evpat function.
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:
Multiple filters may 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. In any cases input events are checked for inconsistencies: nested note-on, orphaned note-off and duplicate controller, bender and aftertouch events are removed. The rate of controller, bender and aftertouch events is normalized in order not to flood output devices.
Filters are defined as follows:
fnew myfilt # create filter "myfilt"
initially the filter is empty and will send input to output as-is. Once the filter is created, filtering rules may be added, modified and removed. The filtering rules may be listed with the finfo function. All rules may be removed with the freset function. See filtering functions section for details.
The filter is configured through a list of filtering rules provided by the user. Each rule teaches the filter which incoming events to transform on which outgoing events. A rule is defined as
When the filter is running if an incoming event is contained in the source set, then it is rewritten to match the destination set. With the following rule:
note {1 3} -> note {2 8}
note events arriving on device 1 channel 3 are sent to device 2, channel 8.
The source and destination event sets must be of the same type and must contain ranges of the same size. If the source and destination event sets contain ranges, then the incoming event will be rewritten to match the destination by being shifted. For instance, the following rule:
note {0 0} 40..49 -> note {0 0} 60..69
will map notes on input ``{0 0}'' by rewriting:
In order to discard a set of input events, it's possible to create rules with the empty set as destination set. For instance to discard the volume controller (number 7) on input ``{0 0}'' the following rule might be used:
ctl {0 0} 7 -> none
Rules are created and deleted with the fmap and funmap functions respectively. For instance, to create a filter with the above rules:
send EOF character (control-D) to quit [0000:00]> fnew myfilt [0000:00]> fmap {note {1 3}} {note {2 8}} [0000:00]> fmap {note {0 0} 40..49} {note {0 0} 60..69} [0000:00]> fmap {ctl {0 0} 7} {none} [0000:00]> finfo { evmap xctl {0 0} 7 > none evmap note {0 0} 40..49 > note {0 0} 60..69 evmap note {1 3} 0..127 > note {2 8} 0..127 }
Filters may contain multiple rules. If the input event matches the source set of two rules, then the most specific rule wins, i.e. the event is run only through the rule with the smaller source set. If both rules have the same source set, then the event is duplicated and run through both rules.
This mechanism is useful to define a default rule with a large source set and then to refine it by defining exceptions to it. For instance consider the following filter:
fnew myfilt fmap {any kbd} {any piano} fmap {ctl kbd 1} {ctl piano 11}
the first rule will map all events coming from the ``kbd'' input to the ``piano'' output; the second rule maps controller 1 (modulation) to controller 11 (expression). Controller 1 events match source sets of both rules, but they will be run through the second rule only, because it has the smaller set. The order in which rules are created is not important because the fmap and funmap functions sort them.
In order to avoid inconsistencies there's a constraint on the source sets of rules of the same filter: if two source sets overlap, then one of them must contain the other one. The user doesn't need to care about this constraint since fmap and funmap functions will do the right thing in all cases.
The following example defines a filter that routes events from device number 1 (the MIDI keyboard) to device number 0 (the sound module).
fnew mydevmap # define filter "mydevmap" fmap {any 1} {any 0} # make it route device 1 -> device 0
To test the filter, start performance mode using the ``i'' command and then type ``s'' to stop performance mode
The following example defines a filter that routes events from device 1, midi channel 0 (first channel of the keyboard) to device 0, midi channel 9 (default drum channel of the sound module).
fnew mydrums # define filter "mydrums" fmap {any {1 0}} {any {0 9}} # route dev/chan {1 0} to {0 9}
To test the filter, start performance mode using the ``i'' command. Playing on channel 0 of the keyboard will make sound channel 9 of the sound-module. To stop performance mode, use ``s'' command.
The following example adds a new rule to the above filter that maps the modulation wheel (controller 1) of the input (i.e. device 1, midi channel 0) to the expression controller (number 11) of the output (device 0, midi channel 9).
fmap {ctl {1 0} 1} {ctl {0 9} 11}
Rules of the filter may be listed with the finfo function. It displays them as follows:
{ evmap any {1 0} > any {0 9} evmap xctl {1 0} 1 > xctl {0 9} 11 }
The following will transpose notes on ``{0 2}'' by 12 halftones:
fnew mypiano # define filter ``mypiano'' fmap {any {1 0}} {any {0 2}} # route {1 0} -> {0 9} ftransp {any {0 2}} 12 # transpose by 12 halftones
First we create the filter, and we add a ``default'' rule to map anything coming from ``{1 0}'' (i.e. the MIDI keyboard) to ``{0 2}''. The second rule transposes anything the filter generated on output ``{0 2}''. Note that the order of the rules is not important, the ``ftransp'' rule always applies to outgoing events.
In the same way it is possible to create a keyboard-split with two 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.
fnew mysplit fmap {any {1 0}} {any {0 2}} fmap {any {1 0}} {any {0 3}} fmap {note {1 0} 0..63} {note {0 2} 0..63} fmap {note {1 0} 64..127} {note {0 3} 64..127}
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 eighth note etc... By default a whole note corresponds to 96 ticks, thus by default one beat corresponds to one quarter note, i.e. the time signature is 4/4.
The following selects the current position in the song to measure number 3:
g 3
This will make ``p'' and ``r'' commands start at this particular position instead of measure number 0. Furthermore all track editing function will process the track starting at this position.
The metronome has the following states:
The ``m'' command changes the mode of the metronome, example:
m on # switch the metronome on p # start playback s # stop
The metronome has two kind of click-sounds:
The click-sound can be configured by giving a couple of note-on events, as follows:
metrocf {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 are achieved by inserting or deleting measures. The following starts a song with time signature of 6/8 (at measure 0) and change the time signature to 4/4 at measure 2 during 3 measures:
g 0 # go to measure 0 mins 4 {6 8} # insert 4 measures at 8/6 g 2 # move to measure 2 mins 3 {4 4} # insert 3 measure at 4/4 m on # turn metronome on p # test it, i.e. start playback s # stop playback
To suppress measure number 2 (the first 4/4 measure)
g 2 # go to measure 2 sel 1 # select 1 measure mcut # remove it m on # switch the metronome on p # test it s #
To get the time signature at any given measure number, the msig function can be used. It returns the denominator and the numerator in a two integer list. For instance, to print the time signature at measure number 17:
g 17 # go to measure 17 print [msig] # print what msig returned
A handy way to duplicate the time structure of a portion of the song is given by the mdup function. It copies the current selection at another place of the song:
g 17 # go to measure 17 sel 16 # select 16 measures mdup 0 # copy them behind the selection
The parameter to mdup is the number of measures to leave between the the original and the point where the replica is inserted.
Tempo changes are achieved simply by moving within the song and using the ``t'' command to change the tempo at the current position. The tempo must be provided 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.
g 0 # go to measure 0 t 100 # set tempo to 100 bpm g 2 # go to measure 2 t 180 # set tempo to 180 bpm
To get the tempo at any given measure number, the mtempo function can be used. It returns the tempo in beats per minute. For instance, to print the tempo at measure number 17:
g 17 # go to measure 17 print [msig] # print what mtempo returned
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}'' pair. A track may contain MIDI data from any device and 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.
The following defines a track and record events as-is from all MIDI devices:
[0000:00]> tnew mytrack # create a new track [0000:00]> r # start recording [0003:02]> s # stop
tracks are played as follows:
[0000:00]> p # start playback [0001:01]> s # stop playback
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).
The following creates a filter and uses it to record to the above track:
[0000:00]> fnew mypiano # create the filter [0000:00]> fmap {any {1 0}} {any {0 0}} # dev1/chan0 -> dev0/chan0 [0000:00]> tnew mytrack # create the track [0000:00]> r # start recording [0001:03]> s # stop
This setup, is more suitable for multitracking. The correct approach for multitracking is to create a filter for each musical instrument, and then to use this filter to record one or more tracks per instrument.
Most track editing functions use the project context, i.e. a set of ``current values'':
So editing tracks consists in modifying above parameters and issuing commands to process the current selection.
To clear the current selection of the current track:
tclr
above command will clear only events matching the current event type, which by defaults is set to ``any event''. To clear only controller number 7 of the current selection:
ev {ctl {} 7} tclr
see the event set section for more details. If you don't plan to continue working only on controller number 7 events, then don't forget to revert the current event selection to the default value (which is ``{}'', all events).
To cut a piece of a track, for instance to cut 2 measures starting at measure number 5:
g 5 # move to measure 5 sel 2 # select 2 measures tcut # cut them
this command removes 2 measures of ``time'' from the current track; thus this will shift all measures following the current position by 2 measures.
The following inserts 2 blank measures at measure number 3:
g 3 tins 2
similarly, since this commands insert time, this will shift all measures following the current position by 2 measures. Note: the tcut and tins functions cause a part of the track to be shifted. If there are signature changes in the project, the track contents may no more correspond to the project's signature. The correct way of cutting or inserting a portion of the project is to use mcut and mins, which preserve the signature.
The current selection of a track could be copied into another track. For instance the following will copy the current selection at measure 5 of track ``mypiano2''.
tcopy # copy current selection ct mypiano2 # change current track to ``mypiano2'' g 5 # go to measure 5 tpaste # paste the copy
A complete portion of the project (all tracks and time structure included) can be copied with the mdup function. It copies the current selection and inserts it at the position given as argument to mdup. The argument is relative to the end of the current selection (if positive) or to the beginning of the current selection (if negative). Example:
g 17 # go to measure 17 sel 4 # select 4 measures mdup 0 # create 3 copies mdup 0 mdup 0
A track can be quantized by rounding event positions to the nearest exact position. The following will quantize the current selection of the current track by rounding event positions to the nearest quarter note.
setq 4 # quarter note = 1/4-th of a whole tquanta 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 sounds too regular especially on acoustic patches.
There are two quantization algorithms:
It is possible that a MIDI device transmits bogus MIDI data. The following scans the track and removes bogus notes and unused controller events:
ct badtrack tcheck
This function can be useful to remove nested notes when a track is recorded twice (or more) without being erased.
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.
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 tclr function, only note frames whose "non" events are in the selected region are erased.
Conflicting note frames (i.e. 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.
A pitch bend frame is made of "bend" events. It starts with a "bend" event whose value is different from the default value (i.e. 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, tclr may erase the middle of "bend" frame resulting in two new "bend" frames (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.
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'' (i.e. 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 }
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.
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'':
xnew mybank
Then, messages can be added:
xadd 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 using the ``i'' command.
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:
cx mybank # set current sysex bank r # start recording
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:
xinfo
A bank can be cleared by:
xrm {}
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:
xrm {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:
xsetd 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:
xsetd 1 {0xF0 0x7E 0x7F}
The following functions gives some information about midish objects:
ls # summary minfo # list tempo and signature changes iinfo # list config events of current input oinfo # list config events of current output finfo # list rules of current filter tinfo # list events distribution of current track dinfo 0 # list device 0 properties
Objects can be listed as follows:
print [tlist] # print tracks list print [ilist] # print inputs list print [olist] # print outputs list print [flist] # print filters list print [xlist] # print sysex banks list
Current values can be obtained as follows:
print [getunit] # ticks per whole note print [getpos] # print current position print [getlen] # print current selection length print [getf] # current filter print [gett] # current track print [getx] # current sysex bank print [tgetf] # default filter of current track
The device and the MIDI channel of a input or output may be obtained as follows:
print [igetc] # print midi chan number of current input print [igetd] # print device number of current input print [ogetc] # print midi chan number of current output print [ogetd] # print device number of current output
To check if object exists:
print [iexists myinput] print [oexists myouput] print [fexists myfilt] print [texists mytrack] print [xexists mysx]
this will print 1 if the corresponding object exists and 0 otherwise.
A song can be saved into a file as follows:
save "myfile.msh"
In a similar way, the song can be load from a file as follows:
load "myfile.msh"
All inputs, outputs, filters, tracks, their properties, and values of the current track, current filter are saved and restored. However, note that the local settings (like device configuration, metronome settings) are not saved.
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:
import "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 output is exported as a track containing its 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:
export "mysong.mid"
Most operations may be undone with the ``u'' command, example:
tquant 90 # quantize current track u # restore current track
The the ``ul'' command lists the previous command calls that may be undone.
Theres no way to redo operations that are undone.
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.
Variables can be used to store numbers, strings and references to tracks, inputs, outputs 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.
For instance, let us create a procedure named ``gmon'' that creates a sysex bank and stores a the standard sysex message to turn on General MIDI mode on device number 0:
proc gmon { xnew gm xadd 0 { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 } }
The ``proc'' keyword is followed by the procedure name ``gmon'' and then follows a list of statements between braces.
Procedures can take arguments. For instance, to improve above procedure to take the device number as argument:
proc gmon devnum { xnew gmon xadd $devnum { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 } }
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 ("$").
If the last argument name is the "..." string, then a variable argument list could be used. In this case, in the code block between braces, the "..." token will is replaced by the variable list of arguments. For instance:
proc gmon ... { xnew gmon for devnum in ... { xadd $devnum { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 } } }
A lot of similar procedures are defined in the sample ``midishrc'' file, shipped in the source tar-ball.
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.
The following table summarizes the device attributes:
attribute | description |
---|---|
device number | integer that is used to reference the device |
clkrate | number of ticks per whole note, default is 96, which corresponds to the MIDI standard |
clock ``tx'' flag | boolean; if it is set, the real-time MIDI clock events (like start, stop, ticks) are transmitted to the MIDI device. |
ixctlset | list of continuous controllers that are expected to be received with 14-bit precision. |
oxctlset | list of continuous controllers that will be transmitted with 14-bit precision |
iev | list of compound event types the device transmits; it's a subset of ``xpc'', ``nrpn'', ``rpn''. |
oev | list of compound event types the device accepts; it's a subset of ``xpc'', ``nrpn'', ``rpn''. |
The following table summarizes the input or output attributes:
attribute | description |
---|---|
name | identifier used to reference the input or output |
{dev chan} | device and MIDI channel where events are sent to or received to |
conf | events that are sent when performance mode is entered |
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 |
The following table summarizes the track attributes:
attribute | description |
---|---|
name | identifier used to reference the track |
mute flag | if true then the track is silent during 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 filter. |
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 device number to which the message has to be sent. |
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 whole 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 mode | can be ``on'', ``off'' or ``rec'', see ``m'' command. |
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 |
current track | default track: the track that will be recorded in record mode |
current filter | default filter. The filter with which the default track is recorded if it hasn't its default filter. |
current input | default input. This value isn't used in real-time, however it can be used as default value when adding new rules to the filter. |
current output | default output. This value isn't used in real-time, however it can be used as default value when adding new rules to the filter. |
current position | 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 |
current selection | length (in measures) of the current selection. This value isn't used in real-time, however it is used as default value in track editing functions. |
current quant step | current quantization step. This value isn't used in real-time, however it's used as default value for the track editing functions |
current event set | current event selection This value isn't used in real-time, however it is used as default value for the track editing functions |
tap start mode | one of ``off'', ``start'' or ``tempo''. See ``tap'' command. |
tap start events | Events used to trigger start and/or measure tempo. |
The input line is split into tokens:
mytrack _mytrack my34_track23 # good 123mytrack # bad, starts with digit mytrackabcdefghijklmnopqrstuvwxyz # bad, to long
token | value |
---|---|
123 | 123 in decimal |
0x100 | 256 in hex |
0xF0 | 240 in hex |
Multiple lines ending with ``\'' are parsed as a single line. Anything else generates a ``bad token'' error.
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:
myproc arg1 arg2
let x = 123 let y = 1 + 2 * (3 + x)The left-hand side should be the name of a variable and the right hand side an expression (see the expression section).
if $i { print "i is not zero"; } else { print "i is zero"; }
the ``else'' and the second block are not mandatory. Note that since newline character is interpreted as a ``;'', the line cannot be broken in an arbitrary way. If the expression following the ``if'' keyword is true the first block is executed, otherwise the second one (if any) is executed. The expression is evaluated in the following way:
expression | bool. value |
---|---|
non-zero integer | true |
zero integer | false |
non-empty list | true |
empty list | false |
non-empty string | true |
empty string | false |
any name | true |
nil | false |
for i in {"bli" "bla" "blu"} { print $i; }
the block is executed for each value of the list to which ``$i'' is set.
return $x * $x;
exit
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 | |
| | bitwise OR | |
&& | logical AND | left to right |
|| | logical OR | |
.. | range definition | left to right |
Examples:
2 * (3 + 4) + $x
is an usual integer arithmetic expression.
[tlist]
is the returned value of the procedure ``tlist''.
12..56
is the range of integer between 12 and 65 (included).
{"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}}
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 }
print [tlist]
The quantization step will be used by tquanta and tquantf functions and also by all editing functions to optimize event selection. If the special ``nil'' value is specified as quantization step, then quatization is disabled.
If midish is configured to use ALSA (default on Linux systems) then ``filename'' should contain the ALSA sequencer port, as listed by ``aseqdump -l'', (eg. ``28:0'', ``FLUID Synth (qsynth)''). If ``nil'' is given instead of the path, then the port is not connected to any existing port; this allows other ALSA sequencer clients to subscribe to it and to provide events to midish or to consume events midish sends to it.
evpat master {0xf0 0x7f 0x7f 0x04 0x01 v0_lo v0_hi 0xf7}defines a new event type for the standard master volume system exclusive message.
Midish could be used from general purpose scripting languages to do MIDI-related tasks. This is accomplished by starting the ``midish'' binary and writing commands to it's standard input. To ease this process, midish should be started in batch mode, with the -b flag. In batch mode the ``~/.midishrc'' and ``/etc/midishrc'' files are not parsed, errors cause midish to exit, and ``p'', ``r'' and ``i'' commands are blocking.
For instance the following simple shell script will play, on the ``/dev/rmidi1'' device, standard midi files enumerated on the command line:
#!/bin/sh trap : 2 for arg; do midish -b <<END dnew 0 "/dev/rmidi1" wo import "$arg" p END done
The ``smfplay'' and ``smfrec'' files shipped in the source tar-balls are examples of such scripts.
A program that wants to use a midish feature, may start midish and issue commands on its standard input. Then, the standard output of midish could be parsed so the program can obtain the desired information (if any).
To ease this process, the midish binary can be started with the -v flag; in this case it will write additional information on its standard output, allowing the caller to be notified of changes of the state of midish. The information is written on a single line starting with the + sign, as follows:
No midish function (like print) can generate a line starting with the + sign, so it is safe to assume that such lines are synchronization lines and not the output of a function. Furthermore, such lines will never appear in the middle of the output of a function. Additional information may be available in the same format in future versions of midish; thus front-ends should be able to ignore unknown lines starting with +.
Generally, any front-end should use a loop similar to the following:
while (!eof) { command = get_command_from_user(); wait_for("+ready"); write_to_midish_stdin(command); result = parse_midish_stdout(); do_something(result); }
The ``rmidish'' program shipped in the source tar-ball is and example of such front-end.
The following session show how to configure a keyboard split:
send EOF character (control-D) to quit [0000:00]> inew kbd {1 0} [0000:00]> onew bass {0 5} [0000:00]> oaddev {pc bass 33} [0000:00]> onew piano {0 6} [0000:00]> oaddev {pc piano 2} [0000:00]> fnew split [0000:00]> fmap {any kbd} {any bass} [0000:00]> fmap {any kbd} {any piano} [0000:00]> fmap {note kbd 12..62} {note bass 0..50} [0000:00]> fmap {note kbd 63..127} {note piano 63..127} [0000:00]> finfo { evmap any {1 0} > any {0 5} evmap any {1 0} > any {0 6} evmap note {1 0} 12..62 > note {0 5} 0..50 evmap note {1 0} 63..127 > note {0 6} 63..127 } [0000:00]> i [0000:00]> s [0000:00]> save "piano-bass.msh"
First we set the default input to device 1, channel 6, on which the keyboard is available. Then 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 62 (note D3), the bass is transposed by -12 half-tones (one octave).
The following session show how to record a track.
send EOF character (control-D) to quit [0000:00]> inew kbd {1 0} # select default input [0000:00]> onew drums {0 9} # create drum output [0000:00]> tnew dr1 # create track ``dr1'' [0000:00]> t 90 # tempo to 90 bpm [0000:00]> r # start recording [0003:03]> s # stop [0000:00]> setq 16 # set quantization step [0000:00]> sel 32 # select 32 measures [0000:00]> tquanta 75 # quantize to 75% [0000:00]> p # play [0001:02]> s # stop playing [0000:00]> save "myrythm.msh" # save to a file [0000:00]> # hit ^D to quit
first, we set the default input to ``{1 0}'' (the keyboard). Then, we define the ``drum'' output as device 0, channel 9, this creates a default filter that maps ``kbd'' to ``drums''. 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.
Copyright (c) 2003-2010 Alexandre Ratchov <alex@caoua.org>
Copyright (c) 2008 Willem van Engen <wvengen@stack.nl>
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
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, Will Woodruff, and Willem van Engen.
Copyright (c) 2003-2019 Alexandre Ratchov
Last updated jun 5, 2019