View Source Midiex (Midiex v0.5.3)
This is the main Midiex module.
It's built around three basic concepts:
- Ports:
- list or count MIDI ports availble (for example, a keyboard or synth)
- Connections:
- open or close connections to MIDI ports
- create a virtual input or output connections so your Elixir application appears as a MIDI device
- Messages:
- send or receive messages to and from connections.
examples
Examples
# List MIDI ports
Midiex.ports()
# Lists MIDI ports discoverable on your system
# [
# %Midiex.MidiPort{
# direction: :input,
# name: "IAC Driver Bus 1",
# num: 0,
# port_ref: #Reference<0.2239960018.1937899544.176288>
# },
# %Midiex.MidiPort{
# direction: :output,
# name: "IAC Driver Bus 1",
# num: 0,
# port_ref: #Reference<0.2239960018.1937899544.176289>
# }
# ]
# Create a virtual output connection
piano = Midiex.create_virtual_output("piano")
# Returns an output connection:
# %Midiex.OutConn{
# conn_ref: #Reference<0.1633267383.3718381569.210768>,
# name: "piano",
# port_num: 0
# }
# Send MIDI messages to a connection
# In the message below, the note 60 is equivalent to Middle C and 127 means maximum velocity
note_on = <<0x90, 60, 127>>
note_off = <<0x80, 60, 127>>
Midiex.send_msg(piano, note_on)
:timer.sleep(3000) # wait three seconds
Midiex.send_msg(piano, note_off)
livebook-tour
Livebook tour
Also see the introductory tour in LiveBook at /livebook/midiex_notebook.livemd.
Link to this section Summary
Port discovery
Returns the count of the number of input and output MIDI ports in as a map.
Lists MIDI ports availabile on the system.
List MIDI ports matching the specified direction (e.g. input or output)
Lists MIDI ports matching the name. This can be either a string match or a regex pattern.
Virtual ports & connections
Creates a virtual input port struct.
Creates a virtual output connection.
Notifications & hot-plugging
Ensures that hot-plugging of devices is supported on MacOS.
Low-level API for subscribing to MIDI notification messages.
Link to this section Port discovery
Returns the count of the number of input and output MIDI ports in as a map.
Midiex.port_count()
# Returns a map in the following format:
# %{input: 2, output: 0}
@spec ports() :: [ %Midiex.MidiPort{ direction: term(), name: term(), num: term(), port_ref: term() } ]
Lists MIDI ports availabile on the system.
Midiex.ports()
# Returns a list of input or output ports:
# [
# %Midiex.MidiPort{
# direction: :input,
# name: "Piano",
# num: 0,
# port_ref: #Reference<0.249304305.242352152.40090>
# },
# %Midiex.MidiPort{
# direction: :input,
# name: "Drums",
# num: 1,
# port_ref: #Reference<0.249304305.242352152.40091>
# }
# ]
@spec ports(:input | :output) :: [ %Midiex.MidiPort{ direction: term(), name: term(), num: term(), port_ref: term() } ]
List MIDI ports matching the specified direction (e.g. input or output)
Takes an atom as the first parameter representing the direction:
- :input - lists input ports only
- :output - lists output ports only.
Midiex.ports(:input)
# Returns a list of input or output MIDI ports:
# [
# %Midiex.MidiPort{
# direction: :input,
# name: "Piano",
# num: 0,
# port_ref: #Reference<0.249304305.242352152.40090>
# },
# %Midiex.MidiPort{
# direction: :input,
# name: "Drums",
# num: 1,
# port_ref: #Reference<0.249304305.242352152.40091>
# }
# ]
@spec ports(binary() | map(), (:input | :output) | nil) :: [ %Midiex.MidiPort{ direction: term(), name: term(), num: term(), port_ref: term() } ]
Lists MIDI ports matching the name. This can be either a string match or a regex pattern.
Optionally takes a direction (:input or :output) can be given.
Examples:
# Regex examples
# List ports containing the word 'Arturia' and ignore case
Midiex.ports(~r/Arturia/i)
# List output ports starting the word 'Arturia' and ignore case
Midiex.ports(~r/^Arturia/i, :output)
# String matching examples
# List input ports with the name 'KeyStep Pro'
Midiex.ports("KeyStep Pro", :input)
# List output ports with the name 'Arturia MicroFreak'
Midiex.ports("Arturia MicroFreak", :output)
Link to this section Output connections
@spec close( %Midiex.OutConn{conn_ref: term(), name: term(), port_num: term()} | [%Midiex.OutConn{conn_ref: term(), name: term(), port_num: term()}] ) :: any()
Closes a MIDI output connection.
Accepts as the first parameter either a:
- MIDI output connection, e.g. a
%Midiex.OutConn{}
struct - List of output connections.
example
Example
# Connect to the first output port
out_port = Midiex.ports(:output) |> List.first()
out_conn = Midiex.open(out_port)
Midiex.close_out_conn(out_conn)
# Will return :ok if successful
@spec open( %Midiex.MidiPort{ direction: :output, name: term(), num: term(), port_ref: term() } | [ %Midiex.MidiPort{ direction: :output, name: term(), num: term(), port_ref: term() } ] ) :: %Midiex.OutConn{conn_ref: term(), name: term(), port_num: term()} | [%Midiex.OutConn{conn_ref: term(), name: term(), port_num: term()}]
Creates a connection to the MIDI port.
Accepts one of the following as a parameter:
- MIDI output port, e.g. a
%Midiex.MidiPort{direction: :output}
struct - List of MIDI output ports.
Returns an output connection (%Midiex.OutConn{)
) or a list of output connections if a list was output ports was given as the first parameter.
example
Example
connect-to-a-single-output-port
Connect to a single output port
# Get the first available MIDI output port
out_port = Midiex.ports(:output) |> List.first()
# Open an output connection
out_conn = Midiex.open(out_port)
# Returns an output connection struct, for example:
%Midiex.OutConn{
conn_ref: #Reference<0.3613027763.2067398660.163505>,
name: "IAC Driver Bus 1",
port_num: 0
}
You can now send messages to the connection:
# Send a note on message for D4
Midiex.msg_send(out_conn, Midiex.Message.note_on(:D4))
# Let note play for 2 seconds
:timer.sleep(2000)
# Send a note off message for D4
Midiex.msg_send(out_conn, Midiex.Message.note_off(:D4))
connect-to-a-list-of-output-ports
Connect to a list of output ports
# Get a list of available output ports
out_ports = Midiex.ports(:output)
# Returns a list of available MIDI output ports, e.g.:
[
%Midiex.MidiPort{
direction: :output,
name: "Arturia MicroFreak",
num: 1,
port_ref: #Reference<0.3139841870.4103995416.58431>
},
%Midiex.MidiPort{
direction: :output,
name: "KeyStep Pro",
num: 2,
port_ref: #Reference<0.3139841870.4103995416.58432>
},
%Midiex.MidiPort{
direction: :output,
name: "MiniFuse 2",
num: 3,
port_ref: #Reference<0.3139841870.4103995416.58433>
}
]
# Connect to each port
out_conns = Midiex.open(out_ports)
# Returns a list of MIDI output connections, e.g.:
[
%Midiex.OutConn{
conn_ref: #Reference<0.1633267383.3718381569.210768>,
name: "Arturia MicroFreak",
port_num: 1
},
%Midiex.OutConn{
conn_ref: #Reference<0.3139841870.4103995416.58432>,
name: "KeyStep Pro",
port_num: 2
},
%Midiex.OutConn{
conn_ref: #Reference<0.3139841870.4103995416.58433>,
name: "MiniFuse 2",
port_num: 3
}
]
Link to this section Virtual ports & connections
@spec create_virtual_input(String.t()) :: %Midiex.VirtualMidiPort{ direction: term(), name: term(), num: term() }
Creates a virtual input port struct.
Takes a name as the first parameter.
This is only available on platforms that support virtual ports (currently every platform but Windows).
Important
Even though this creates an input port, beacause it's a virtual port it is listed as an 'output' when querying the OS for available devices.
That means other software or devices will discover it and use it as a an output and can send messages to it.
It also means it will show as
%Midiex.MidiPort{direction: :output}
when callingMidiex.ports()
.Note that it won't be discoverable until the input port is subscribed.
example
Example
# Create a virtual MIDI input by giving it a name. MIDIex will also assign it an input port number (`num`).
my_virtual_in = Midiex.create_virtual_input("My Virtual Input")
# This will return a VirtualMidiPort struct in the following format
# %Midiex.VirtualMidiPort{direction: :input, name: "My Virtual Input", num: 1}
The %Midiex.VirtualMidiPort{}
struct can then be passed to MIDI input port listener functions, such as:
Midiex.subscribe(my_virtual_in)
- If using a Listener GenServer:
Midiex.Listener.start_link(port: my_virtual_in)
Midiex.Listener.subscribe(listener, my_virtual_in)
Likewise, once subscribed to, the virtual input port can be unsubscribed to:
Midiex.unsubscribe(my_virtual_in)
- If using a Listener GenServer:
Midiex.Listener.unsubscribe(my_virtual_in)
@spec create_virtual_output(String.t()) :: %Midiex.OutConn{ conn_ref: term(), name: term(), port_num: term() }
Creates a virtual output connection.
This allows your Elixir application to be seen as a MIDI device.
Note this is only available on platforms that support virtual ports (currently every platform but Windows).
# Create an output connection called "piano"
piano_conn = Midiex.create_virtual_output("piano")
You can send messages to MIDI software or hardware connected to this virtual device in the standard way, e.g.:
note_on = <<0x90, 60, 127>>
note_off = <<0x80, 60, 127>>
Midiex.send_msg(piano, note_on)
:timer.sleep(3000) # wait three seconds
Midiex.send_msg(piano, note_off)
Important
Even though this creates an output port, beacause it's a virtual port it is listed as an 'input' when querying the OS for available devices.
That means other software or devices will discover it and use it as a an input, such as to receive messages.
It also means it will show as
%Midiex.MidiPort{direction: :input}
when callingMidiex.ports()
.
Link to this section Send & receive messages
Link to this section Notifications & hot-plugging
Ensures that hot-plugging of devices is supported on MacOS.
By default on MacOS, Midiex port based functions, such as Midiex.ports()
will only list ports visible when the Elixir app was first started. That means devices added or removed afterwards will not be reflected in Midiex.ports()
.
Important
If you'd like functions like
Midiex.ports()
to reflect the current available ports on your system, such as when plugging or unplugging physical devices, you will need to call thehotplug/0
function beforeMidiex.ports
orMidiex.port_count
.You will only need to call
hotplug/0
once to enable this mode for the rest of your application's session.
This function is similar to the notifications/0
function, expect the calling Elixir process will not receive any MIDI notification messages.
If you need to respond to MIDI notification messages, use notifications/0
instead of this function (or use the Midiex.Notifier
GenServer) and make sure it has been called before Midiex.ports
or Midiex.port_count
so hot-plugging is supported.
Low-level API for subscribing to MIDI notification messages.
Currently only MacOS is supported.
The calling process will receive MIDI notification messages.
Instead of this function, consider using the Midiex.Notifier
GenServer, which will listen to notifications and allow you to create handlers to respond to them.
Important
To make sure hotplug support works on
Midiex.ports()
andMidiex.port_count()
, make sure this function is called first. It only needs to be called once to enable hotmode support mode for the rest of your application's session.If you want hotplug support but don't need to receive and respond to MIDI notifications, see
hotplug/0
instead.