Midish 1.0 - User's manual

# Midish 1.0
user's manual and tutorial

## Table of contents

- 1 Introduction
  - 1.1 What is midish?
  - 1.2 Installation
  - 1.3 Invocation
  - 1.4 How does it work
  - 1.5 An example
- 2 Devices setup
- 3 Input channels
- 4 Output channels
  - 4.1 Channel configuration
- 5 Events and event sets specification
  - 5.1 Event specification
  - 5.2 Event set specification
- 6 Filtering/routing
  - 6.1 Filter rules
  - 6.2 Rules hierarchy
  - 6.3 Examples
    - 6.3.1 Device redirections
    - 6.3.2 Channel maps
    - 6.3.3 Controller maps
    - 6.3.4 Transpose
    - 6.3.5 Keyboard splits
- 7 Time structure
  - 7.1 Moving within the song
  - 7.2 Metronome
  - 7.3 Time signature changes
  - 7.4 Tempo changes
- 8 Tracks
  - 8.1 Recording a track without a filter
  - 8.2 Recording a track with a filter
  - 8.3 Basics about editing tracks
  - 8.4 Copy, cut, insert
  - 8.5 Track quantization
  - 8.6 Checking a track
- 9 Frames, more about filtering and editing
  - 9.1 Note frames
  - 9.2 Pitch bend frames
  - 9.3 Controller frames
  - 9.4 Other frames
- 10 System exclusive messages
- 11 Obtaining information
- 12 Saving and loading songs
- 13 Import/export standard MIDI files
- 14 The interpreter's language
  - 14.1 Global variables
  - 14.2 Defining simple procedures
- 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
- 16 Project attributes
  - 16.1 Device attributes
  - 16.2 Channel attributes
  - 16.3 Filter attributes
  - 16.4 Track attributes
  - 16.5 Sysex attributes
  - 16.6 Song attributes
- 17 Language reference
  - 17.1 Lexical structure
  - 17.2 Statements
  - 17.3 Expressions
  - 17.4 Procedure definition
- 18 Function reference
  - 18.1 Track functions
  - 18.2 Input functions
  - 18.3 Output functions
  - 18.4 Filter functions
  - 18.5 System exclusive messages functions
  - 18.6 Real-time functions
  - 18.7 Song functions
  - 18.8 Device functions
  - 18.9 Controller functions
  - 18.10 Misc. functions
- 19 Using midish in other programs
  - 19.1 Creating scripts: batch mode
  - 19.2 Creating front-ends: verbose mode
- 20 Sample midishrc-file
- 21 Example sessions
  - 21.1 Example - MIDI filtering
  - 21.2 Example - recording a track
- 22 Thanks

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

- real-time MIDI filtering/routing (controller mapping, keyboard splitting, ...)
- track recording, metronome
- track editing (insert, copy, delete, ...)
- progressive track quantization
- multiple MIDI devices handling
- synchronization to external audio and MIDI software/hardware
- import and export of standard MIDI files
- tempo and time-signature changes
- system exclusive messages handling

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

### 1.2 Installation

Requirements:

- A MIDI sound module and a MIDI keyboard (without any MIDI devices midish will be probably useless).
- A POSIX unix-like operating system with "raw" MIDI support. Works on OpenBSD (its development platform) and Linux.
- A ``C'' complier (gcc), the ``make'' utility, and other common unix tools
- The readline library (which depends on the ncurses library on most systems)
- The ALSA library (on Linux only).

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

- ``i'' - idle, open MIDI devices but do nothing, just process input and send it to the output.
- ``p'' - play, open MIDI devices and start playback
- ``r'' - record, open MIDI devices and start playback and recording
- ``s'' - stop one of the above and close MIDI devices.

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:

- MIDI devices:
  these are actual MIDI devices (keyboards, sound modules, external sequencers...) from which events are received and/or to which they are sent.
- Input channels:
  they represent ``{device midi-channel}'' pair on which MIDI events arrive, like a MIDI keyboard, a MIDI control surface etc...
- Output channels:
  they represent ``{device midi-channel}'' pair on which MIDI events can be sent, like synths. Output channel objects hold properties like patch number, volume, reverb depth or other controllers.
- Filters:
  a filter is a set of rules that determines which MIDI event to discard and how to transform incoming events. The filter also "sanitizes" the input MIDI stream by removing nested notes, duplicate controllers and other anomalies.
- Tracks:
  tracks represent pieces of music: they hold MIDI voice events (notes, controllers ...). Tracks aren't bound to a particular channel and can contain events from any channel.
- Sysex banks:
  a sysex bank is a set of system exclusive messages. They will be sent to MIDI devices when performance mode is entered.
- The "meta-track":
  it is a particular hidden track that contains only special events like tempo changes and time signature changes.
- Song parameters:
  these are miscellaneous parameters like the metronome configuration, the current position, current selection...

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:

- "Idle" mode
  The MIDI input passes through the current filter and the result is sent to the MIDI output. No tracks are played or recorded.

      +---------+          +------------+          +----------+
      |         |          |            |          |          |
      | MIDI in |--------->|   filter   |--------->| MIDI out |
      |         |          |            |          |          |
      +---------+          +------------+          +----------+

- Play mode
  The MIDI input passes through the current filter the result is mixed with the currently played tracks and finally sent to the MIDI output. No tracks are recorded.

                              +--------------+
                              | track_1 play |---\
                              +--------------+   |
                                                 |
                                   ...        ---+
                                                 |
                              +--------------+   |
                              | track_N play |---+
                              +--------------+   |
                                                 |
      +---------+             +------------+     |    +----------+
      |         |             |            |     \--->|          |
      | MIDI in |------------>|   filter   |--------->| MIDI out |
      |         |             |            |          |          |
      +---------+             +------------+          +----------+

- Record mode
  The MIDI input passes through the current filter and is recorded on the current track. The result is mixed with the currently played tracks and finally sent to the MIDI output. System exclusive messages are recorded to the current sysex without being passed through the filter.

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

### 1.5 An example

Suppose that there are two devices:

- ``/dev/rmidi4'' - a MIDI sound module
- ``/dev/rmidi3'' - a MIDI keyboard

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:

- a reference from the following list: noff, non, kat, xctl, xpc, cat, bend rpn, nrpn.
- a channel
- a number
- a second number (not for ``cat'' and ``bend'')

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:

- A keyword specifying the type of the selected events: any, note, ctl, pc, cat, bend, rpn, nrpn xpc, xctl, none
- an optional channel set. If a name is given, its interpreted as an input or output created with inew or onew. If a number is given, it matches all MIDI channels of the given device number. Alternatively, a list containing a device number range and a MIDI channel number range can be used (see examples below).
- an optional number range, specified as a list of two number separated by the ``..'' operator, like ``12..65''.
- an optional second number range, in the same format as the first one (not permitted for ``pc'', ``bend'' and ``cat'')

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

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

- route events from a device/channel to another
- map a controller to another
- split the keyboard
- drop unwanted events
- etc...

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

- a source event set
- a destination event set

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:

- note 40 into note 60
- note 41 into note 61
- note 42 into note 62
- ...
- note 49 into note 69

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:

- 24 ticks per beat
- 4 beats per measure
- 120 beats per minute

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:

- off - metronome is disabled
- on - metronome is enabled
- rec - metronome is enabled only for recording (default)

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:

- high-click: on the first beat of the measure
- low-click: on the other beats.

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

- current track - the track that will be processed, it's selected with the ``ct'' command.
- current position - the measure number where processing will start. It's set using the ``g'' command
- current selection - number of measures to process. It's set using the ``sel'' command
- current event set - events type that will be processed. It's set using the ``ev'' command. Most of the time all events types need to be processed, but sometimes one might want to process only a subset (eg. only controller number 7), leaving the rest as-is.
- current quantization step - this defines how notes at the selection boundaries are handled. It's set using the ``setq'' command. Using a quantization step allows to properly process events at the selection boundaries, by slightly enlarging the selection in order to select for processing events that would be outside the selection.

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:

- Parameters: this controller change a parameter of the channel but it doesn't have a special default value. For instance volume controller, reverb depth etc... These controllers are not packed together, they form single event frames. This is the default behavior for most controllers.
- Frames: these are controllers that have special default value and that are packed in frames, similar to "bend" frames. For instance modulation wheel, sustain pedal. For those frames special care is taken in editing functions.

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:

- tempo changes
- time signature changes

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

- added ``device'' parameter to ``sendraw''
- removed ``filtchangein'' and added: filtchgich, filtchgidev, filtswapich, filtswapidev, filtchgoch, filtchgodev, filtswapoch, filtswapodev.
- added new filter rules: filtdevdrop, filtchandrop, filtctldrop, filtkeydrop.
- added functions that remove existing rules: filtnodevdrop, filtnodevmap, filtnochandrop, filtnochanmap, filtnoctldrop, filtnoctlmap, filtnokeydrop, filtnokeymap.
- added support for external MIDI synchronization, added: devsetmaster, devgetmaster, devsendrt
- added trackdelete, chandelete, filtdelete
- added trackrename, chanrename, filtrename
- split ``changetnum'' into ``changetdev'' and ``changetch''
- added ``chanset''
- added event ranges, and an event range argument to ``trackblank'' and ``trackcopy''
- removed support for single number channels.
- added ``tracksetmute'' and ``trackgetmute''
- added support for system exclusive messages; added sysexlist, sysexnew, sysexdelete, sysexrename, sysexexists, sysexinfo, sysexclear, sysexsetunit, sysexadd, songgetcursysex, songsetcursysex.
- removed ``tracksave'' and ``trackload''
- removed support for gnu readline(3) library from midish, and created the ``rmidish'' utility, a front-end to midish using the readline(3) library.
- added filters the ability to limit MIDI traffic (one controller, one bender, one aftertouch per tick)
- make the filter drop duplicate controllers, pitch bends and aftertouches
- added global and per filter current channel: songgetcurchan, songsetcurchan, filtgetcurchan, filtsetcurchan.
- added global and per channel current input: songgetcurinput, songsetcurinput, changetcurinput, chansetcurinput.
- added ``trackchanlist'' and improved ``songinfo''
- added a MIDI file player and recorder: smfplay(1) and smfrec(1)

### 15.2 Changes from release 0.2 to release 0.3

- added ``tracktransp'' to transpose tracks
- added ``trackmerge'' to merge two tracks
- make rmidish run midish from the same directory where rmidish is located.
- now multiple bender events are gathered together. So track editing functions (``trackcopy'' & friends) always leave the bender in a neutral state.
- improved ``trackcheck''
- added ``exit'' keyword to exit midish
- Added a ``mode'' argument to ``devattach'' function. Now devices can be used in read-only or write-only mode (allowing to use named pipes as MIDI devices).
- added basic support for the MIDI "Active Sensing" feature
- make metronome click audible with non-percussive patches
- save metronome settings
- added tempo factor: ``songsetfactor'' and ``songgetfactor''
- changed all track functions to gracefully handle nested notes, overlapping controllers/benders.
- when entering real-time mode, midish restores states of all controllers/benders corresponding to the current position
- when leaving real-time mode midish release only notes that are sounding and reset only controllers that are touched instead of resetting all notes/controllers. This allows midish to better share MIDI devices with other sequencers.
- added support for 14-bit controllers
- added support for RPN/NRPN MIDI events.
- split format 0 MIDI files into 16 tracks (one per MIDI channel)
- made controller interpretation in editing functions configurable: ctlconf, ctlconfx, ctlunconf, ctlinfo.
- added per-device controller precision selection (7-bit or 14-bit): devixctl, devoxctl.
- added ``chanunconfev'' function to remove an event from a channel

### 15.3 Changes from release 0.3 to release 0.4

- added output MIDI merger, to avoid conflicts between tracks and MIDI input
- simplifications: removed current input and filter's current channel attributes.
- continuous controllers are merged more intelligently in performance mode, avoiding clipping
- changed all functions to use internal context instead of taking dozens of parameters, while we're at it give them shorter names. Old functions are deprecated, they are still available for compatibility. Below is the list of new functions:

  > ci, co, geti, geto, cx, getx, setunit, getunit, goto, getpos, sel, getlen, setq, getq, fac, getfac, ct, gett, cf, getf, mute, unmute, getmute, ls, save, load, reset, export, import, idle, play, rec, stop, tempo, mins, mcut, mdup, minfo, mtempo, msig, mend, ctlconf, ctlconfx, ctlunconf, ctlinfo, metro, metrocf, tlist, tnew, tdel, tren, texists, taddev, tsetf, tgetf, tcheck, tcut, tins, tclr, tpaste, tcopy, tmerge, tquant, ttransp, tclist, tinfo, ilist, olist, iexists, oexists, inew, onew, idel, odel, iren, oren, iset, oset, igetc, ogetc, igetd, ogetd, iaddev, oaddev, irmev, ormev, iinfo, oinfo, flist, fnew, fdel, fren, fexists, finfo, freset, fmap, funmap, ftransp, fvcurve, fchgin, fchgout, fswapin, fswapout, xlist, xexists, xnew, xdel, xren, xinfo, xrm, xsetd, xadd, dnew, ddel, dclkrx, dclktx, dclkrate, dinfo, dixctl, doxctl

- output channels are no more the only to be named and/or configured. Now input channels can be named too.
- added ``msig'' and ``mtempo'' functions returning time signature and tempo respectively at the current position
- added ``err'' function to print an error message and stop the interpreter execution flow.
- added ``current event set'' in the context and associated ``ev'' function. All track editing functions use it.
- greatly simplified the filter: more features with less code and concepts. The filter can map any event set to any other set. So all mapping can be created with a single fmap function.
- added a new ``integer range'' data type, useful to define event sets.
- added a new metronome mode ``rec'' to turn on the metronome only in record mode.
- switched to multithreaded performance mode, meaning that the prompt is available during performance mode.
- added new messages for front ends allowing to get asynchronously the current position. Updated rmidish to use it to display the current position in the prompt.
- make tcopy copy selection into a hidden temporary track and add tpaste to paste it at the current position.
- added basic readline completion of built-in functions to rmidish.
- add ftransp command to transpose output easily.
- add fvcurve command to adjust note velocity.
- add mend function to return the end of the song (i.e. the first unused measure).
- make mins and mcut modify all tracks, not only the time structure.
- add mdup function to copy portions of the project preserving its time structure.
- added built-in filter to output channels, so there's no more need to create filter for every channel.

### 15.4 Changes from release 0.4 to release 1.0

- removed all legacy functions
- added tevmap function to transform a set of events of a track into another set (eg. to change the device number).
- allow midish to synchronize to a MTC source, meaning that it can be used with audio applications. Added dmtcrx function.
- send MMC start, stop and relocate messages allowing MMC-capable audio applications to be controlled from midish. Added dmmctx function.

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

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

- Identifiers
  an identifier is a sequence of up to 32 characters, digits and underscores (``_''). However, an identifier cannot start with a digit. Examples:

      mytrack _mytrack  my34_track23          # good
      123mytrack                              # bad, starts with digit
      mytrackabcdefghijklmnopqrstuvwxyz       # bad, to long

- Numbers
  numbers should be in decimal or hex format and may not exceed 2^31. Hex numbers are preceded by "0x", as in the C language. Examples:

  token | value
  --- | ---
  123 | 123 in decimal
  0x100 | 256 in hex
  0xF0 | 240 in hex

- String constants
  string constants are sequence of printable characters enclosed in pairs of double quotes. Thus a string cannot contain double quotes, tabs, newlines.
- Keywords
  valid keywords are: nil, let, if, else, for, in, return, exit and proc.
- Operators, separators etc...
  sequences of meta-characters like ``('', ``)'', ``+'', ``$'', newline character, ``;'', etc...
- Comments
  comments are ignored, they start with ``#'' and end with the new line character.

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:

- A procedure call, example:

      myproc arg1 arg2

- An assignment using the ``let'' keyword, examples:

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

- An ``if..else'' statement, example:

      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

- A loop over a list:

      for i in {"bli" "bla" "blu"} {
              print $i;
      }

  the block is executed for each value of the list to which ``$i'' is set.
- A return statement. The return statement ends the procedure execution and returns a value to the caller. Example:

      return $x * $x;

- An exit statement. The exit statement ends the execution of the current script. It takes no arguments. Example:

      exit

### 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.
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.
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:
  - negative - sensitivity is decreased
  - positive - sensitivity is increased
  - zero - the velocity is unchanged

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

### 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:
  - 4 - quarter note
  - 6 - quarter note triplet
  - 8 - eighth note
  - 12 - eighth note triplet
  - 16 - sixteenth note
  - 24 - sixteenth note triplet
  - etc...
  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:
  - ``ro'' - read-only, for input only devices
  - ``wo'' - write-only, for output only devices
  - ``rw'' - read and write.

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

### 18.9 Controller functions

ctlconf ctlname ctlnumber defval
  Configure controller number ``ctlnumber'' with name ``ctlname'', and default value ``defval''. If defval is ``nil'' then there is no default value and corresponding controller events are not grouped into frames. See sec. Controller frames.
ctlconfx ctlname ctlnumber defval
  Same as ctlconf function, but for 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

### 18.10 Misc. functions

m mode
  Set the mode of the metronome. The following modes are available:
  - ``on'' - turned on for both playback and record
  - ``rec'' - turned on for record only
  - ``off'' - turned off
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:
  - ``filt'' - show events passing through the current filter
  - ``mididev'' - show raw MIDI traffic on stderr
  - ``mixout'' - show conflicts in the output MIDI merger
  - ``norm'' - show events in the input normalizer
  - ``pool'' - show pool usage on exit
  - ``song'' - show start/stop events
  - ``timo'' - show timer internal errors
  - ``mem'' - show memory usage
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:

- +ready
  means that midish is ready to parse and execute a new line. A front-end should always wait for this string before issuing any command.
- +pos measure beat tick
  is written during performace mode on every beat, it gives the current song position.

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 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-2010 Alexandre Ratchov
Last updated apr 19, 2010