mididings - Basics

Connections

Serial

A >> B
Chain([ A, B, ... ])

Connects units in series. Incoming MIDI events will be processed by unit A first, then by unit B. If the event is filtered out by unit A, it is not processed any further, and unit B will not be called.

Transpose one octave down, then set channel to 1:
Transpose(-12) >> Channel(1)

Parallel

A // B
[ A, B, ... ]
Fork([ A, B, ... ], remove_duplicates=True)

Connects units in parallel. All units will be called with identical copies of incoming MIDI events. The output will be the sum of all the units' outputs. With Fork() it's also possible to disable the automatic removal of identical MIDI events by setting remove_duplicates to False.

Send events to both channels 1 and 2:
Channel(1) // Channel(2)

or:
[ Channel(1), Channel(2) ]

+A

Applies A to a duplicate of each event and leaves the original unchanged. Equivalent to [ Pass(), A ].

Splits

{ T1: A, T2: B, ... }
Split({ T1: A, T2: B, ... })

Splits by event type. Equivalent to [ Filter(T1) >> A, Filter(T2) >> B, ... ].

Send note events to channel 1 and CC events to channel 2:
{ NOTE: Channel(1), CTRL: Channel(2) }

Filters

~F
F.invert()

Inverts the filter F. Note that for filters which only affect certain kinds of events, other events will remain unaffected when the filter is inverted. For example, an inverted KeyFilter will match a different note range, but neither the original nor the inverted filter will have any effect on controllers or program changes.

Remove CC events with controller number 42:
~CtrlFilter(42)

-F
F.negate()

Negates the filter F. Unlike ~F, this matches exactly the events that F doesn't.

Selectors

Every filter can act as a selector. In addition, it's possible to combine multiple filters into more complex selectors:

S1 & S2
AndSelector([S1, S2, ...])

Builds a selector for events that match all of the given filters or selectors.

S1 | S2
OrSelector([S1, S2, ...])

Builds a selector for events that match at least one of the given filters or selectors.

S % A
S.apply(A)

Applies A only to events which match selector S, but keeps events which don't. If S is a single filter, this is equivalent to [ S >> A, -S ].
Note that operator % has higher precedence than & and |, so the selector will usually have to be written in parentheses.

Transpose only events on channel 2:
ChannelFilter(2) % Transpose(3)
Change CC events with controller number 23 or 42 from channel 1 to channel 2:
(ChannelFilter(1) & (CtrlFilter(23) | CtrlFilter(42))) % Channel(2)

Note names and ranges

Many mididings units accept notes and/or note ranges as parameters. Notes can be specified either as a MIDI note number or by their name, consisting of one letter, optionally 'b' or '#', and an octave number. Examples of valid note names are 'c3', 'g#4', 'eb2'.

Note ranges can be specified either as a 2-tuple of note numbers, e.g. (48, 60), or as two note names separated by a colon, e.g. 'g#3:c6'. Like all ranges in Python, note ranges are semi-open (do not include their upper limit), so 'g#3:c6' matches notes from 'g#3' up to 'b5', but not 'c6'!
It's also possible to leave out either the upper or the lower limit, for example 'c4:' matches all notes above (and including) C4, while ':a2' matches all note up to (but not including) A2.

Port numbers and names

Internally, ports are always referred to by their number. When an event is received on the second input port, and is not explicitly routed to another port, it will be sent to the second output port.
If you named your input and output ports using the in_ports and out_ports parameters to config(), you can also refer to them by their names in all units that accept ports as parameters. To avoid ambiguities, port names should be unique (with the JACK backend they must be).

Miscellaneous

A couple of things you might need to know...

Overloaded functions

Many function and unit names in mididings are overloaded to have somewhat different meanings depending on the number and/or names of their parameters. For example, KeyFilter(note) filters a single note, while KeyFilter(lower, upper) filters a range of notes.

When multiple versions of a unit accept the same number of parameters, it's necessary to explicitly name the parameters of the version you'd like to use. Cases where parameter names are required are indicated in the documentation as "param=...". It's usually possible to name parameters like this regardless of whether it's actually necessary.

Add an offset of 42 to velocity values:
Velocity(42)

Set velocities to a fixed value of 42:
Velocity(fixed=42)

Everything is an object

Everything in mididings is a Python object, and can be assigned to variables, returned from functions, etc.

Add a fifth (7 semitones) above each note, route all events to channel 2 (of course there are easier ways to do this):
def add_interval(n):
    return Pass() // Transpose(n)

route = Channel(2)
mypatch = add_interval(7) >> route

run(mypatch)

Python syntax

In Python, line breaks delimit statements, and indentation delimits blocks. However, as a rule of thumb, both are irrelevant as long as at least one parenthesis or bracket is still open. Many mididings patches consist of lists and dictionaries, so line breaks and indentation are usually not an issue.

Sometimes it makes sense to put a patch in parentheses to allow it to span multiple lines:

mypatch = (
    Transpose(12) >> Velocity(curve=1.0) >>
    Filter(~PROGRAM)
)

Operator precedence

Overloading operators in Python does not change their precedence. This is a list of all operators relevant to mididings, in order of their precedence (highest to lowest):

(...)
[A, B, ...]
Binding (parentheses)
Connection in parallel (list)
~F
-F
+F
Filter inversion
Filter negation
Apply to duplicate
A // B
S % A
Connection in parallel
Selector "then"
A >> B Connection in series
F & G Selector "and"
F | G Selector "or"


In short, just remember that...

Also note that operators can't be overloaded if both sides are builtin Python types like lists or dictionaries. In some cases it may be necessary to wrap those in Fork() or Split().