Usage

Client and ports

To interface ALSA sequencer a client needs to be created. This is done by creating a SequencerClient object:

from alsa_midi import SequencerClient

client = SequencerClient("my client")

To receive or send MIDI events at least one port will be needed too:

port = client.create_port("inout")

By default a generic bi-directional MIDI port with full subscription access is created. Additional arguments SequencerClient.create_port() method can be used to change that:

from alsa_midi import WRITE_PORT, PortType

input_port = client.create_port("input",
                                caps=WRITE_PORT,
                                type=PortType.MIDI_GENERIC | PortType.MIDI_GM | PortType.SYNTHESIZER)

Note: use WRITE_PORT (or PortCaps.WRITE) for creating input ports (ports other clients can write to) and READ_PORT (or PortCaps.READ) for creating output ports (that other clients can read from).

External port discovery

Client and port discovery can be done using SequencerClient.query_next_client() and SequencerClient.query_next_port() methods, which are Python interface to the original the ALSA API calls.

There is also a convenience SequencerClient.list_ports() method provided do get list of all available and relevant ports at once. The result is also sorted in a way, that the first entry should be the most useful one (according to some heuristics).

To get a list of available ports for MIDI output:

out_ports = client.list_ports(output=True)

To get a list of hardware MIDI input ports:

in_ports = client.list_ports(input=True, type=PortType.MIDI_GENERIC | PortType.HARDWARE)

Port subscriptions

ALSA sequencer keeps a list of subscriptions (connections) between ports. Events can then be sent either to a specific port or to all connected ports, the latter being often more useful.

Creating a connection of own port to some other port is as easy as calling the Port.connect_to() or Port.connect_from() method:

dest_port = client.list_ports(output=True)[0]
port.connect_to(dest_port)

source_port = client.list_ports(input=True)[0]
input_port.connect_from(source_port)

Ports can be disconnected using Port.disconnect_to() or Port.disconnect_from() method, appropriately.

Client can also manage connection between ports on other clients with SequencerClient.subscribe_port() and SequencerClient.unsubscribe_port() methods.

Event output

Events, which are instances of Event subclasses can be sent out using the SequencerClient.event_output() method. It does not send the events immediately, unless the buffer gets full, so SequencerClient.drain_output() has to be called afterwards:

import time
from alsa_midi import NoteOnEvent, NoteOffEvent

for event in NoteOnEvent(note=60), NoteOnEvent(note=64), NoteOnEvent(note=67):
    client.event_output(event, port=port)
client.drain_output()

time.sleep(1)

for event in NoteOffEvent(note=60), NoteOffEvent(note=64), NoteOffEvent(note=67):
    client.event_output(event, port=port)
client.drain_output()

Event input

Incoming events can be received using the SequencerClient.event_input() method:

while True:
    event = client.event_input()
    print(repr(event))

Queues

What makes ALSA sequencer a sequencer is precise control of event timing using event queues. A queue is created using the SequencerClient.create_queue() method:

queue = client.create_queue("my queue")

Then queue tempo should be set:

beats_per_minute = 120
ticks_per_quarter_note = 96

queue.set_tempo(int(60.0 * 1000000 / beats_per_minute), ticks_per_quarter_note)

And the queue start command should be issued (will be executed after SequencerClient.drain_output():

queue.start()

The queue now might be used for placing output events in time:

 for event in NoteOnEvent(note=60, tick=0), NoteOffEvent(note=60, tick=96):
    client.event_output(event, port=port)
client.drain_output()

Queues can also be used for setting timestamps (in MIDI ticks or seconds and nanoseconds) on incoming events:

port = client.create_port("input", WRITE_PORT,
                          timestamping=True,
                          timestamp_real=True,
                          timestamp_queue=queue)

while True:
    event = client.event_input()
    print("Time:", event.time, "Event:", repr(event))

Asynchronous Interface

python-alsa-midi can work with asyncio event loop. For this task there is AsyncSequencerClient class. It is mostly the same as AsyncSequencerClient, but event_input(), drain_output(), event_output() and event_output_direct() are coroutines here.

Example:

import asyncio

from alsa_midi import (AsyncSequencerClient, READ_PORT, WRITE_PORT,
                       NoteOnEvent, NoteOffEvent)

async def play_chord(client):
    port = client.create_port("output", READ_PORT)
    port.connect_to(client.list_ports(output=True)[0])

    for event in NoteOnEvent(note=60), NoteOnEvent(note=64), NoteOnEvent(note=67):
        await client.event_output(event, port=port)
    await client.drain_output()

    await asyncio.sleep(1)

    for event in NoteOffEvent(note=60), NoteOffEvent(note=64), NoteOffEvent(note=67):
        await client.event_output(event, port=port)
    await client.drain_output()

async def show_input(client):
    port = client.create_port("input", WRITE_PORT)
    port.connect_to(client.list_ports(input=True)[0])
    while True:
            event = await client.event_input()
            print(repr(event))

client = AsyncSequencerClient("async example")
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(play_chord(client), show_input(client)))

Direct access to ALSA API

The raw ALSA cffi bindings are available through alsa_midi.alsa and alsa_midi.ffi objects:

from alsa_midi import alsa, ffi

version = alsa.snd_asoundlib_version()
print(ffi.string(version).decode())

Thread safety

SequencerClient, AsyncSequencerClient objects and Port and Queue objects created through them should only be used in a single thread and a single asyncio loop.

Event objects do not contain any external resources, so the can be safely passed around. Simultaneous access from different threads should still be avoided.

MIDO back-end

The package also includes a MIDO back-end module. To use it set shell variable $MIDO_BACKEND to ‘alsa_midi.mido_backend’ or programmatically:

mido.set_backend('alsa_midi.mido_backend')