Midish user's manual and tutorial

Table of contents

1 Introduction

1.1 What is midish?

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

Important features:

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

1.2 Installation

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 names are ``libreadline-dev'' and ``libasound2-dev''.

To install midish:

  1. Untar and gunzip the tar-ball:
    gunzip midish-1.0.tar.gz
    tar -xf midish-1.0.tar
    cd midish-1.0
    
  2. Configure midish, type ``./configure'', it will select reasonable defaults.

    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
    
  3. Compile midish, just type ``make'', this will build midish and rmidish, the readline(3) front-end to midish.
  4. Install binaries, documentation and examples by typing ``make install'', possibly as root.
  5. If there isn't a ``/etc/midishrc'' file, then copy the sample file by typing in your shell:
    cp midishrc /etc
    
  6. Read the documentation and modify ``/etc/midishrc'' in order to choose the default MIDI device by using the ``dnew'' function, example (if you're using raw devices):
    dnew 0 "/dev/rmidi3" rw
    
    or if you're using ALSA (default on Linux), the following formats are accepted:
    dnew 0 "28:0" rw
    dnew 0 "FLUID Synth (qsynth)" wo 
    
    on OpenBSD, the following formats are accepted:
    dnew 0 "rmidi/3" rw
    dnew 0 "midithru/0" rw
    dnew 0 "snd/0" rw
    
    see next section for details.

1.3 Invocation

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

rmidish

Then, commands are issued interactively on the midish prompt, example:

print "hello world"

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.

1.4 How does it work

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 channel configuration events are sent. There are three performance modes:

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.

1.5 An example

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

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"                     # save the song into a file
[0000:00]>                                   # EOF (control-D) to quit

2 Devices setup

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.sng"         # 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.

3 Input channels

Since midish handles multiple input sources, instead of using MIDI channels, it uses device / MIDI channel pairs to represent inputs. So in this document input channel refers the ``{device MIDI-channel}'' pair on which MIDI events are incoming. Channels are handled by two-item lists, like this:

{1 0}                   # device 1, MIDI channel 0

Channels can also be named using the ``inew'' command, as follows:

inew keyboard {1 0}

this defines a named input channel ``keyboard'' that can be used instead of the ``{1 0}'' pair.

Note: input channels are used as default values by certain functions, so it's better to register inputs using the ``inew'' command. The easier is to add a ``inew'' statement in your ``$HOME/.midishrc'' file, to avoid typing it every time midish is started.

4 Output channels

As for input channels, midish uses device / MIDI channel pairs to represent outputs. So in this document output channel refers the ``{device MIDI-channel}'' pair to which MIDI events will be sent. Output channels are handled by two-item lists, like this:

{0 4}                   # device 0, MIDI channel 4

they can also be named using the ``onew'' command, as follows:

onew mybass {0 4}

this defines a named channel ``mybass'' that can be used instead of the ``{0 4}'' pair.

Output channels expose a built-in filter with the same name; it defines how input events are routed to the output channel. The filter comes with a default rule that routes all input channels to the given output channel. See filter section for further details.

4.1 Channel configuration

An output channel represents one musical instrument; midish allows basic channel configuration events (like program changes and controllers) to be attached to the channel definition. 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:

oaddev 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:

oaddev 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:

oaddev mybass {ctl mybass 7 125}

5 Events and event sets specification

5.1 Event specification

An event is specified as a list containing:

Event references correspond to the following MIDI events:

ref. name MIDI event
noff note off
non note off
kat key after-touch (poly)
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} 34 1234}

set controller number 7 to 99 on device/channel drums:

{ctl drums 7 99}

5.2 Event set specification

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 channel "bass"
{ 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 channel "bass"
{ ctl bass 7 }          # match controller 7 on channel "bass"
{ bend {} }             # match bender
{ xctl {} 19 }          # match 14-bit controller number 19
{ xpc  {} 21 1234 }     # match patch 21 on bank 1234
{ nrpn {} 21 }          # match NRPN 21 change

5.3 User-defined events

Short system-exclusive patterns can be handled as events. Such events are defined with the evpat function.

6 Filtering/routing

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 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. 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 can be added, modified and removed. The filtering rules can be listed with the finfo function. All rules can be removed with the freset function. See filtering functions section for details.

6.1 Filter rules

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 midi-channel 3 are sent to device 2, midi-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 channel ``{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 channel ``{0 0}'' the following rule might be used:

ctl {0 0} 7 -> none

Rules are created and delete 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
}

6.2 Rules hierarchy

In real life, the filter will probably have to 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 very useful because it allows 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 channel ``kbd'' (the input) to channel ``piano'' (the 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 fmap and funmap functions will always sort them.

That last point to retain is that 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.

6.3 Examples

6.3.1 Device redirections

The following example defines a filter that routes events from device number 1 (the MIDI keyboard) to device number 0 (the sound module).

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

6.3.2 Channel maps

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.

6.3.3 Controller maps

The following example add a new rule to the above filter that maps the modulation wheel (controller 1) of the source channel (i.e. device 1, midi channel 0) to the expression controller (number 11) of the destination channel (device 0, midi channel 9).

fmap {ctl {1 0} 1} {ctl {0 9} 11}

Rules of the filter can be listed with the finfo functions, which should display the list of rules as follows:

{
        evmap any {1 0} > any {0 9}
        evmap xctl {1 0} 1 > xctl {0 9} 11
}

6.3.4 Transpose

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 channel of the synth). The second rule transposes anything the filter generated on channel ``{0 2}''. Note that the order of the rules is not important, the ``ftransp'' rule always applies to events outgoing from the filter.

6.3.5 Keyboard splits

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

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}

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.

7 Time structure

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

From the musical point of view, a beat often corresponds to a quarter note, to an 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.

7.1 Moving within the song

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.

7.2 Metronome

In order to "hear" time, a metronome can be used. It has three modes:

The ``m'' command can be used to change the mode of the metronome:

m on            # switch the metronome on
p               # start playback
s               # stop

The metronome has two kind of click-sound:

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.

7.3 Time signature changes

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.

7.4 Tempo changes

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

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

8.1 Recording a track without a filter

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

8.2 Recording a track with a filter

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.

8.3 Basics about editing tracks

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.

8.4 Copy, cut, insert

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

8.5 Track quantization

A track can be quantized by rounding note-positions to the nearest exact position. The following will quantize the current selection of the current track by rounding notes to the nearest quarter note.

setq 4          # quarter note = 1/4-th of a whole
tquant 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 to regular especially on acoustic patches.

8.6 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:

ct badtrack
tcheck

This function can be useful to remove nested notes when a track is recorded twice (or more) without being erased.

9 Frames, more about filtering and editing

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

9.1 Note frames

Note frames are made of a starting "non" event any optional "kat" events and the stopping "noff" event. In editing functions note frames are copied/moved/deleted as a whole; they are never truncated. The starting event (note on) determines whether the frame is selected. For instance, in 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.

9.2 Pitch bend frames

A pitch bend frame is made of "bend" events. It starts with a "bend" event whose value is different from the default value (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.

9.3 Controller frames

The way controller events are packed in a frame depend on the controller type. Currently the following controllers are supported:

The user can specify for each controller number the desired behavior. The ctlconf function configures the controller. As arguments, it takes the name of the controller (an identifier) the controller number and the default value of the controller. If the default value is ``nil'' (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
}

9.4 Other frames

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

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

10 System exclusive messages

Midish can send system exclusive messages to MIDI devices before starting performance mode. Typically, this feature can be used to change the configuration of MIDI devices. System exclusive (aka "sysex") messages are stored into named banks. To create a sysex bank named ``mybank'':

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}

11 Obtaining information

The following functions gives some information about midish objects:

ls              # summary
minfo           # list tempo and signature  changes
iinfo           # list config events of current input channel
oinfo           # list config events of current output channel
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 track list
print [ilist]   # print named input channel list
print [olist]   # print named output channel list
print [flist]   # print filter list
print [xlist]   # print sysex back 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 channel definition can 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.

12 Saving and loading songs

A song can be saved into a file as follows:

save "myfile"

In a similar way, the song can be load from a file as follows:

load "myfile"

All channel definitions, 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.

13 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:

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

export "mysong.mid"

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

14.1 Global variables

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

let x = 53              # store 53 into "x"
print $x                # prints "53"

The ``let'' keyword is used to assign values to variables and the dollar sign (``$'') is used to obtain variable values.

14.2 Defining simple procedures

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 ("$").

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.

15 Changes

15.1 Changes from release 0.1 to release 0.2

15.2 Changes from release 0.2 to release 0.3

15.3 Changes from release 0.3 to release 0.4

15.4 Changes from release 0.4 to release 1.0

15.5 Changes from release 1.0 to release 1.1

16 Project attributes

16.1 Device attributes

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

16.2 Channel attributes

The following table summarizes the channel attributes:

attribute description
name identifier used to reference the channel
{dev chan} device and MIDI channel where events are sent to or received to
conf events that are sent when performance mode is entered

16.3 Filter attributes

The following table summarizes the filter attributes:

attribute description
name identifier used to reference the filter
rules set set of rules that handle MIDI events

16.4 Track attributes

The following table summarizes the track attributes:

attribute description
name identifier used to reference the track
mute flag if true then the track is 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.

16.5 Sysex attributes

The following table summarizes the sysex back attributes:

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

16.6 Song attributes

The following table summarizes the song attributes:

attribute description
meta track a track containing tempo changes and time signature changes
ticks_per_unit number of MIDI ticks per 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 channel. 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 channel. 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

17 Language reference

17.1 Lexical structure

The input line is split into tokens:

Multiple lines ending with ``\'' are parsed as a single line. Anything else generates a ``bad token'' error.

17.2 Statements

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

17.3 Expressions

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

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

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

let i = 123             # puts 123 in i
print $i                # prints the value of i

The following operators are recognized:

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

17.4 Procedure definition

A procedure is defined with the keyword ``proc'' followed by the name of the procedure, the names of its arguments and a block containing its body, example:

proc doubleprint x y {
        print $x
        print $y
}

Arguments and variables defined within a procedure are local to that procedure and may shadow a global variable with the same name. The return value is given to the caller with a ``return'' statement:

proc square x {
        return $x * $x
}

18 Function reference

18.1 Track functions

tlist
return the list of names of the tracks in the song example:
print [tlist]
tnew trackname
create an empty track named ``trackname''
tdel
delete the current track.
tren newname
change the name of the current track to ``newname''
texists trackname
return 1 if ``trackname'' is a track, 0 otherwise
taddev measure beat tick ev
put the event ``ev'' on the current track at the position given by ``measure'', ``beat'' and ``tick''
tsetf filtname
set the default filter (for recording) of the current track to ``filtname''. It will be used in performace mode if there is no current filter.
tgetf
return the default filter (for recording) of the current track, returns ``nil'' if none
tcheck
check the current track for orphaned notes, nested notes and other anomalies; also removes multiple controllers in the same tick
tcut
cut the current selection of the current track.
tclr
clear the current selection of the current track. only events matching the current event selection (see ev function) are removed.
tins amount
insert ``amount'' empty measures in the current track, at the current position.
tpaste
copy the hidden temporary track (filled by tcopy) on the current position of the current track. the current event selection (see ev function) are copied
tcopy
copy the current selection of the current track into a hidden temporary track. Only events matching the current event selection (see ev function) are copied
tquant rate
quantize the current selection of the current track using the current quantization step (see setq function). Note positions are rounded to the nearest tick multiple of the quantization step; Rate must be between 0 and 100: 0 means no quantization and 100 means full quantization.
ttransp halftones
transpose note events of current selection of the current track, by ``halftones'' half tones. Only events matching the current event selection (see ev function) are transposed.
tevmap evspec1 evspec2
convert events matching evspec1 (source) into events matching evspec2 (destination) in the current selection of the current track. Both evspec1 and evspec2 must have the same number of devices, channels, notes, controllers etc..
trackmerge sourcetrack
merge the ``sourcetrack'' into the current track
mute trackname
Mute the given track, i.e. events from ``trackname'' will not be played during record/playback.
unmute trackname
Unmute the given track, i.e. events from ``trackname'' will be played during record/playback.
getmute trackname
Return 1 if the given track is muted and 0 otherwise.
tclist
Return the list of channels used by events stored in the current track.
tinfo
scan the current selection of the current track, an for each measure display the number of events that match the current event selection

18.2 Input functions

inew channelname {dev midichan}
create an new channel named ``channelname'' and assigned the given device and MIDI channel.
iset {dev midichan}
set the device/channel pair of the current channel. All filters are updated to use the new channel setting as if the appropriate fchin function was invoked for each filter.
idel
delete current channel.
iren newname
rename the current channel to ``newname''
iexists channelname
return 1 if ``channelname'' is a channel, 0 otherwise
igetc
return the MIDI channel number of the current channel
igetd channelname
return the device number of the current channel
iaddev event
add the event to the configuration of the current channel, it's not used yet.
irmev evspec
remove all events matching ``evspec'' (see event ranges) from the configuration of the current channel
iinfo
print all events on the config of the current channel.

18.3 Output functions

onew channelname {dev midichan}
create an new channel named ``channelname'' and assigned the given device and MIDI channel. Output channels contain a built-in filter having the same name; by defaut it maps all inputs to the newly created output channel.
oset {dev midichan}
set the device/channel pair of the current channel. All filters are updated to use the new channel setting as if the appropriate fchout function was invoked for each filter.
odel
delete current channel.
oren newname
rename the current channel to ``newname''
iexists channelname
return 1 if ``channelname'' is a channel, 0 otherwise
ogetc
return the MIDI channel number of the current channel
ogetd channelname
return the device number of the current channel
oaddev event
add the event to the configuration of the current channel, it's not used yet.
ormev evspec
remove all events matching ``evspec'' (see event ranges) from the configuration of the current channel
oinfo
print all events on the config of the current channel.

18.4 Filter functions

fnew filtname
create an new filter named ``filtname''
fdel filtname
delete the current filter.
fren newname
rename the current filter to ``newname''
fexists filtname
return 1 if ``filtname'' is a filter, 0 otherwise
freset
remove all rules from the current filter.
finfo
list all fitering rules of the current filter
fchgin old_evspec new_evspec
rewrite all filtering rules of the current filter to consume ``new_evspec'' events instead of ``old_evspec'' events. This means that each rule that would consume ``old_evspec'' on the input will start consuming ``new_evspec'' instead.
fswapin evspec1 evspec2
Similar to fchgin but swap ``evspec1'' and ``evspec2'' in the source events set of each rule.
fchgout old_evspec new_evspec
rewrite all filtering rules of the current filter to produce ``new_evspec'' events instead of ``old_evspec'' events. This means that each rule that would produce ``old_evspec'' on the output will start producing ``new_evspec'' instead.
fswapout evspec1 evspec2
Similar to fchgout but swap ``evspec1'' and ``evspec2'' in the destination events set of each rule.
fmap evspec1 evspec2
add a new rule to the current filter, to make it convert events matching evspec1 (source) into events matching evspec2 (destination). Both evspec1 and evspec2 must have the same number of devices, channels, notes, controllers etc..
funmap evspec1 evspec2
remove event maps from the current filter. Any mapping with source included in evspec1 and destination inluded in evspec2 is deleted.
ftransp evspec halftones
transpose events generated by the filter and matching ``evspec'' by the give number of halftones
fvcurve evspec weight
adjusts velocity of note events produced by the filter, using the given ``weight'' in the -63..63 range. If ``weight'' is:

18.5 System exclusive messages functions

xnew sysexname
create a new bank of sysex messages named ``sysexname''
xdel
delete the current bank of sysex messages.
xren newname
rename the current sysex bank to ``newname''
xexists sysexname
return 1 if ``sysexname'' is a sysex bank, 0 otherwise
xrm pattern
remove all sysex messages starting with ``pattern'' from the current sysex bank. The given pattern is a list of bytes; an empty pattern matches any sysex message.
xsetd newdev pattern
set device number to ``newdev'' on all sysex messages starting with ``pattern'' in the current sysex bank. The given pattern is a list of bytes; an empty pattern matches any sysex message.
xadd devnum data
add to the current sysex bank a new sysex message. ``data'' is a list containing the MIDI system exclusive message and ``devname'' is the device number to which the message will be sent when performance mode is entered
xinfo
print all sysex messages of the current sysex bank. Messages that are too long to be desplayed on a single line are truncated and the ``...'' string is displayed.
ximport devnum path
replace contents of the current sysex bank by contents of the given .syx file; messages are assigned to ``devnum'' device number.
xexport path
store contents of the current sysex bank in the given .syx file

18.6 Real-time functions

i
enter ``idle'' performance mode. Start processing MIDI input and generating MIDI output. data passes through the current filter (if any) or through the current track's filter (if any).
p
play the song from the current position. Input passes through the current filter (if any) or through the current track's filter (if any).
r
play the song and record the input. Input passes through the current filter (if any) or through the current track's filter (if any). On startup, this function play one measure of countdown before the data start being recorded.
s
stop performance and release MIDI devices. I.e. stop the effect ``i'', ``p'' or ``r'' functions;
sendraw device arrayofbytes
send raw MIDI data to device number ``device'', for debugging purposes only.

18.7 Song functions

ev evspec
set the current event selection. Most track editing functions will act only on events matching "evspec", ignoring all other events.
setq step
set the current quantization step to the given note value, as follow:

The quantization step will be used by tquant function and also by all editing functions to optimize event selection. If the special ``nil'' value is specified as quantization step, then quatization is disabled.

getq
return the current quatization step
g measure
set the current song position pointer to the given measure number. Record and playback will start a that position. This also defines the beginning of the current selection used by most track editing functions.
getpos
return the current song position pointer which is also the start position of the current selection.
sel length
set the length of the current selection to ``length'' measures. The current selection start at the current position set with the ``g'' function.
getlen
return the length (in measures) of the current selection.
ct trackname
set the current track. The current track is the one that will be recorded. Most track editing functions act on it.
gett
return the current track (if any) or ``nil''
cf filtname
set the current filter to ``filtname''. The current filter is the one used to process input MIDI events in performance mode. It's also the one affected by all filter editing functions.
getf
return the current filter or ``nil'' if none
cx sysexname
set the current sysex bank, i.e. the one that will be recorded. The current sysex back is the one affected by all sysex editing functions.
getx
return the current sysex bank or ``nil'' if none
ci channame
set the current (named) input channel. All input channel editing functions will act on it.
geti
return the name of the current input channel or ``nil'' if none
co channame
set the current (named) output channel. All output channel editing functions will act on it.
geto
return the name of the current output channel or ``nil'' if none
setunit ticks_per_unit
set the time resolution of the sequencer to ``tpu'' ticks in a whole note (1 unit note = 4 quarter notes). The default is 96 ticks, which is the default of the MIDI standard.
getunit
return the number of ticks in a whole note
fac tempo_factor
set the tempo factor for play and record to the given integer value. The tempo factor must be between 50 (play half of the real tempo) and 200 (play at twice the real tempo).
getfac
return the current tempo factor
t beats_per_minute
set the tempo at the current song position
mins amount {num denom}
insert ``amount'' blank measures at the current song position. The time signature used is num/denom. If the time signature is an empty list (i.e. ``{}'') then the time signature at the current position is used.
mcut
cut the current selection of all tracks, including the time structure.
mdup where
duplicate the current selection inserting a copy of it at the position given by the ``where'' parameter. The target position is a measure number relative to the current selection to be copied. If ``where'' is positive it's relative to the end of the current selection; if it's negative it's relative to the beginning of the current selection.
minfo
print the meta-track (tempo changes, time signature changes.
mtempo
Return the tempo at the current song position. The unit is beats per minute.
msig
Return the time signature at the current song position. The result is a two number list: numerator and denominator.
mend
Return the ending measure of the song (i.e. its size in measures).
ls
list all tracks, channels, filters and various default values
save filename
save the song into the given file. The ``filename'' is a quoted string.
load filename
load the song from a file named ``filename''. the current song is destroyed, even if the load command fails.
reset
destroy completely the song, useful to start a new song without restarting the program
export filename
save the song into a standard MIDI file, ``filename'' is a quoted string.
import filename
load the song from a standard MIDI file, ``filename'' is a quoted string. Only MIDI file ``type 1'' and ``type 0'' are supported.

18.8 Device functions

dlist
return the list of attached devices (list of numbers)
dnew devnum filename mode
attach MIDI device ``filename'' as device number ``devnum''; ``filename'' is a quoted string. The ``mode'' argument is the name of the mode, it can be on if the following:

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.

ddel devnum
detach device number ``devnum''
dmtcrx devnum
use device number ``devnum'' as MTC source. In this case, midish will relocate, start and stop according to incoming MTC messages. Midish will generate its clock ticks from MTC, meaning that it will run at the same speed as the MTC device. This is useful to synchronize midish to an audio multi-tracker or any MTC capable audio application. If ``devnum'' is ``nil'', then MTC messages are ignored and the internal timer will be used instead.
dmmctx { devnum1 devnum2 ... }
Configure the given devices to transmit MMC start, stop and relocate messages. Useful to control MMC-capable audio applications from midish. By default, devices transmit MMC.
dclktx { devnum1 devnum2 ... }
Configure the given devices to transmit MIDI clock information (MIDI ticks, MIDI start and MIDI stop events). Useful to synchronize an external sequencer to midish.
dclkrx devnum
set device number ``devnum'' to be the master MIDI clock source. It will give midish MIDI ticks, MIDI start and MIDI stop events. This useful to synchronize midish to an external sequencer. If ``devnum'' is ``nil'', then the internal clock will be used and midish will act as master device.
dclkrate devnum ticrate
set the number of ticks in a whole note that are transmitted to the MIDI device (if dclktx was called for it). Default value is 96 ticks. This is the standard MIDI value and its not recommended to change it.
dinfo devnum
Print some information about the MIDI device.
dixctl devnum list
Setup the list of controllers that are expected to be received as 14-bit numbers (i.e. both coarse and fine MIDI controller messages will be expected). By default only coarse values are used, if unsure let this list empty.
devoxctl devnum list
Setup the list of controllers that will be transmitted as 14-bit numbers (both coarse and fine MIDI controller messages).
diev devnum list
Configure the device to process as a single event the following patterns of input MIDI messages. By default all of the above are enabled, which allows banks, NRPNs and RPNs to be handled by midish the standard way. It makes sense to disable grouping of above messages on rare hardware that maps above-mentioned controller numbers (0, 6, 32, 38, 98, 99, 100, 101) to other parameters than bank number and NRPN/RPN.
doev devnum list
Same as diev but for output MIDI messages.

18.9 Event functions

ctlconf ctlname ctlnumber defval
Configure controller number ``ctlnumber'' with name ``ctlname'', and default value ``defval''. If defval is ``nil'' then there is no default value and corresponding controller events are not grouped into frames. See sec. Controller frames.
ctlconfx ctlname ctlnumber defval
Same as ctlconf function, but for 14-bit controllers. Thus defval is in the range 0..16383.
ctlconf ctlname
Unconfigure the given controller. ``ctlname'' is the identifier that was used with ctlconf
ctlinfo
Print the list of configured controllers
evpat name sysex_pattern
Define a new event type corresponding to the given system exclusive message pattern. The pattern is a list of bytes or event parameter identifiers (aka atoms). The following atoms are supported: v0, v0_lo, v0_hi, v1, v1_lo, v1_hi. They correspond to the full 7-bit value (coarse parameter), the low 7-bit nibble and the high 7-bit nibble (fine grained parameters) of the first and second parameters respectively. Example:
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.
evinfo
Print the list of event patterns.

18.10 Misc. functions

m mode
Set the mode of the metronome. The following modes are available:
metrocf 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
err string
display the given string and abort the statement being executed.
h funcname
display list of arguments function ``funcname''
exec filename
read and executes the script from a file, ``filename'' is a quoted string. The execution of the script is aborted on error. If the script executes an exit statement, only the script is terminated.
debug flag val
set debug-flag ``flag'' to (integer) value ``val''. It's a developer knob. If ``val=0'' the corresponding debug-info are turned off. ``flag'' can be:
version
Display midish version.
panic
Cause the sequencer to core-dump, useful to developpers.
proclist
Return the list of all user defined procs.
builtinlist
Return a list of all builtin commands.

19 Using midish in other programs

19.1 Creating scripts: batch mode

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.

19.2 Creating front-ends: verbose mode

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.

20 Sample midishrc-file

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

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
solo
mute all tracks but current
nomute
unmute all tracks.
gmon devnum
create a new gmon sysex back and generate a ``general midi on'' system exclusive message for device number ``devnum'' and store it to the current sysex bank.
gmp patch
configures the current channel to use general MIDI patch number ``patch''. (this will send program change event when performance mode is entered).
vol value
set volume (controller number 7) of the current channel
reverb value
set reverb (controller number 91) of the current channel
chorus value
set chorus (controller number 93) of the current channel
rpn addr val
set registered parameter number (RPN) ``addr'' for the current channel to ``val''. Both ``addr'' and ``val'' are integers in the range 0..16383.
nrpn addr val
set not registered parameter number (NRPN) ``addr'' for the current channel to ``val''. Both ``addr'' and ``val'' are integers in the range 0..16383.

21 Example sessions

21.1 Example - MIDI filtering

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"

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

21.2 Example - recording a track

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 channel
[0000:00]> onew drums {0 9}                  # create drum channel
[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]> tquant 75                         # quantize to 75%
[0000:00]> p                                 # play
[0001:02]> s                                 # stop playing
[0000:00]> save "myrythm"                    # save to a file
[0000:00]>                                   # hit ^D to quit

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

22 Copyright

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.

23 Thanks

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

Copyright (c) 2003-2012 Alexandre Ratchov
Last updated dec 17, 2012