Connections

Serial and Parallel

A >> B
Chain(units)

Connect units in series. A >> B is equivalent to Chain([A, B]).

Incoming MIDI events will be processed by unit A first, then by unit B. If an 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)
[ A, B, ... ]
A // B
Fork(units, remove_duplicates=True)

Connect units in parallel. [ A, B ] is equivalent to A // B and Fork([A, B]). All units will be called with identical copies of incoming MIDI events. The output will be the sum of all the units’ outputs.

# send events to both channels 1 and 2
[ Channel(1), Channel(2) ]
# or:
Channel(1) // Channel(2)
+A
A.add()

Apply A to a duplicate of each event and leave the original unchanged. This is a shortcut for [ Pass(), A ].

Units are always called in the order they appear in a patch, using a “breadth-first” approach. For example, given the patch

Fork([ Port('foo'), Port('bar') ]) >> Fork([ Channel(1), Channel(2) ])

Port('foo') will be called before Port('bar'), and the two events produced by the first Fork() are then passed on to the second Fork() individually. The four events resulting from this patch are thus emitted in the order ‘foo 1’, ‘foo 2’, ‘bar 1’, ‘bar 2’.

Normally, mididings automatically removes identical events that are the immediate result of units being connected in parallel. For example, while [Pass(), Transpose(12)] results in two different events for each incoming note-on or note-off event, it does not produce duplicate controller or program change events, although both Pass() and Transpose() leave those events entirely unchanged. With Fork() it is also possible to disable the automatic removal of identical MIDI events by setting remove_duplicates to False.

Filtering and Splitting

Filter(types, ...)

Filter by event type. Types are represented by the constants described in section Event Types. Multiple types can be combined as bit masks, or given as lists or as separate parameters.

# allow only program and control changes to go through
Filter(PROGRAM, CTRL)
# these are equivalent:
Filter(PROGRAM|CTRL)
Filter([PROGRAM, CTRL])

See section Filters for units that allow filtering by different event properties.

~F
F.invert()

Invert 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()

Negate the filter F. Unlike ~F, this matches exactly the events that F doesn’t.

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

Split by event type. { t1: a, t2: b } is equivalent to Split({ T1: A, T2: B, ... }), and both are merely a shorter way of saying [ Filter(T1) >> A, Filter(T2) >> B ].

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

See section Splits for units that allow splitting by different event properties.

All split units are just combinations of multiple filters of the same type. Tuples can be used to pass multiple arguments to a split’s underlying filters (lists won’t work, as they are not hashable and thus can’t be used as keys in a dictionary). In addition to any filter arguments, splits also allow None to be used as a key. This acts as an else clause that is executed when none of the other conditions match:

ChannelSplit({
    1:      ...,    # if channel == 1
    (2, 3): ...,    # if channel == 2 or channel == 3
    None:   ...,    # else
})

Selective Processing

Every filter can act as a selector that restricts some processing steps to certain events:

S % A
S.apply(A)

Apply A only to events which match selector S, and leave events which don’t unchanged. If S is a single filter, this is equivalent to [ S >> A, -S ].

# transpose only events on channel 2
ChannelFilter(2) % Transpose(3)
S % (A, B)
S.apply(A, B)

Apply A to events which match selector S, and B to events which don’t.

# change notes between C3 and C5 to channel 1,
# and all other notes to channel 2:
KeyFilter('c3:c5') % (Channel(1), Channel(2))

Multiple filters can be combined into more complex selectors:

(S1 & S2 & ...)
And(conditions)

Build a selector for events that match all of the given filters or selectors. (S1 & S2) is equivalent to And([S1, S2]).

(S1 | S2 | ...)
Or(conditions)

Build a selector for events that match at least one of the given filters or selectors. (S1 | S2) is equivalent to Or([S1, S2]).

# change controllers 23 and 42 on channel 1 to channel 2:
(ChannelFilter(1) & (CtrlFilter(23) | CtrlFilter(42))) % Channel(2)