midialsa.lua - the ALSA library, plus some interface functions
local ALSA = require 'midialsa' ALSA.client( 'Lua client', 1, 1, false ) ALSA.connectfrom( 0, 14, 0 ) -- input port is lower (0) ALSA.connectto( 1, 'TiMidity' ) -- output port is higher (1) while true do local alsaevent = ALSA.input() if alsaevent[1] == ALSA.SND_SEQ_EVENT_PORT_UNSUBSCRIBED then break end if alsaevent[1] == ALSA.SND_SEQ_EVENT_NOTEON then local channel = alsaevent[8][1] local pitch = alsaevent[8][2] local velocity = alsaevent[8][3] elseif alsaevent[1] == ALSA.SND_SEQ_EVENT_CONTROLLER then local channel = alsaevent[8][1] local controller = alsaevent[8][5] local value = alsaevent[8][6] end ALSA.output( alsaevent ) end
This module offers a Lua interface to the ALSA library. It translates into Lua the Python modules alsaseq.py and alsamidi.py by Patricio Paez; it also offers some functions to translate events from and to the format used in Peter Billam's MIDI.lua Lua module and Sean Burke's MIDI-Perl CPAN module.
This module is in turn translated also into a call-compatible Perl CPAN module: MIDI::ALSA
As from version 1.15, note durations are in seconds rather than milliseconds, for consistency with the timestamps. This introduces a backward incompatibility which only affects you if are putting together your own alsaevents without using the noteevent() function. In the worst case you have to detect versions:
if tonumber(ALSA.Version) < 1.145 then alsaevent[8][5] = 1000*alsaevent[8][5] end
Functions based on those in alsaseq.py: client(), connectfrom(), connectto(), disconnectfrom(), disconnectto(), fd(), id(), input(), inputpending(), output(), start(), status(), stop() and syncoutput()
Functions based on those in alsamidi.py: noteevent(), noteonevent(), noteoffevent(), pgmchangeevent(), pitchbendevent(), controllerevent(), chanpress() and sysex()
Functions to interface with MIDI.lua: alsa2scoreevent() and scoreevent2alsa()
Functions to to get the current ALSA status: listclients(), listnumports(), listconnectedto(), listconnectedfrom() and parse_address()
Create an ALSA sequencer client with zero or more input or output ports,
and optionally a timing queue. ninputports and noutputports
are created if the quantity requested is between 1 and 64 for each.
If createqueue = true, it creates a queue for stamping the arrival time
of incoming events and scheduling future start times of outgoing events.
For full ALSA functionality, the name
should contain only letters, digits, underscores, equal-signs or spaces,
and should contain at least one letter.
Unlike in the alsaseq.py Python module,
it returns success or failure.
Connect from src_client:src_port to my inputport.
Each input port can connect from more than one client.
The input()
function will receive events
from any intput port and any of the clients connected to each of them.
Events from each client can be distinguished by their source field.
Unlike in the alsaseq.py Python module,
it returns success or failure.
Unlike in the alsaseq.py Python module,
if src_client is a string and src_port is undefined,
then
parse_address(src_client)
automatically gets invoked.
This allows you to refer to the src_client by name, for example
connectfrom(inputport,'Virtual:1') will connect from
port 1 of the 'Virtual Raw MIDI' client.
Connect my outputport to dest_client:dest_port.
Each outputport can connect to more than one client.
Events sent to an output port using the
output()
funtion will be sent
to all clients that are connected to it using this function.
Unlike in the alsaseq.py Python module,
it returns success or failure.
Unlike in the alsaseq.py Python module,
if dest_client is a string and dest_port is undefined,
then
parse_address(dest_client)
automatically gets invoked.
This allows you to refer to the dest_client by name, for example
connectto(outputport,'Roland XV-2020') will connect to
port 0 of the 'Roland XV-2020' client.
Disconnect the connection
from the remote src_client:src_port to my inputport.
Returns success or failure.
Unlike in the alsaseq.py Python module,
if src_client is a string and src_port is undefined,
then
parse_address(src_client)
automatically gets invoked.
This allows you to refer to the src_client by name, for example
disconnectfrom(inputport,'Virtual:1') will disconnect from
port 1 of the 'Virtual Raw MIDI' client.
Disconnect the connection
from my outputport to the remote dest_client:dest_port.
Returns success or failure.
Unlike in the alsaseq.py Python module,
if dest_client is a string and dest_port is undefined,
then
parse_address(dest_client)
automatically gets invoked.
This allows you to refer to the dest_client by name, for example
disconnectto(outputport,'Virtual:2') will disconnect to
port 2 of the 'Virtual Raw MIDI' client.
Return fileno of sequencer.
Return the client number, or 0 if the client is not yet created.
Wait for an ALSA event in any of the input ports and return it. ALSA events are returned as an array with 8 elements:
{type, flags, tag, queue, time, source, destination, data}
Unlike in the alsaseq.py Python module, the time element is in floating-point seconds. The last three elements are also arrays:
source = { src_client, src_port } destination = { dest_client, dest_port } data = { varies depending on type }
The source and destination arrays may be useful within an application for handling events differently according to their source or destination. The event-type constants, beginning with SND_SEQ_, are available as module variables.
The data array is mostly as documented in
http://alsa-project.org/alsa-doc/alsa-lib/seq.html
For NOTE events, the elements are
{ channel, pitch, velocity, unused, duration }
where the duration is in floating-point seconds (unlike in
the alsaseq.py Python module where it is in milliseconds).
For SYSEX, the data array has just one element:
the byte-string, including any F0 and F7 bytes.
For most other events, the elements are
{ channel, unused,unused,unused, param, value }
The channel element is always 0..15
In the SND_SEQ_EVENT_PITCHBEND event
the value element is from -8192..+8191
(not 0..16383).
If a connection terminates,
then input()
returns,
and the next event will be of type SND_SEQ_EVENT_PORT_UNSUBSCRIBED
Note that if the event is of type SND_SEQ_EVENT_PORT_SUBSCRIBED or SND_SEQ_EVENT_PORT_UNSUBSCRIBED, then that message has come from the System, and its dest_port tells you which of your ports is involved. But its src_client and src_port do not tell you which other client connected or disconnected; you'll need to use listconnectedfrom() or listconnectedto() to see what's happened.
Returns the number of bytes available in the input buffer.
Use before input()
to check whether an event is ready to be read.
Send an ALSA-event to an output port.
The format of the event is as discussed in
input()
above.
The event will be output immediately
either if no queue was created in the client
or if the queue parameter is set to ALSA.SND_SEQ_QUEUE_DIRECT,
and otherwise it will be queued and scheduled.
The source is an array with two elements:
{src_client, src_port},
specifying the local output-port from which the event will be sent.
If only one output-port exists, all events are sent from it.
If two or more exist, the src_port determines which to use.
The smallest available port-number (as created by
client()
)
will be used if src_port is less than it,
and the largest available will be used if src_port is greater than it.
The destination is an array with two elements:
{dest_client, dest_port},
specifying the remote client/port to which the event will be sent.
If dest_client is zero,
(as generated by
scoreevent2alsa()
or
noteevent()
),
or is the same as the local client
(as generated by
input()
),
then the event will be sent to all clients
that the local port is connected to
(see connectto()
and
listconnectedto()
).
But if you set dest_client to a remote client,
then the event will be sent to that
dest_client:dest_port and nowhere else.
It is possible to send an event to a destination to which there is no connection, but it's not usually the right thing to do. Normally, you should set up a connection, to allow the underlying RawMIDI ports to remain open while playing; otherwise, ALSA will reset the port after every event.
If the queue buffer is full, output()
will wait until space is available to output the event.
Use status()
to know how many events are scheduled in the queue.
If no queue has been started, a SND_SEQ_EVENT_NOTE event can only emerge as a SND_SEQ_EVENT_NOTEON, since a queue is necessary in order to schedule the corresponding NOTEOFF.
Start the queue. It is ignored if the client does not have a queue.
Return { status, time, events } of the queue.
Status: 0 if stopped, 1 if running. Time: current time in seconds. Events: number of output events scheduled in the queue.
If the client does not have a queue the value {0,0,0} is returned. Unlike in the alsaseq.py Python module, the time element is in floating-point seconds.
Stop the queue. It is ignored if the client does not have a queue.
Wait until output events are processed.
Returns an ALSA-event-array, ready to be scheduled by
output()
.
Unlike in the alsaseq.py Python module,
the start and duration elements are in floating-point seconds.
Returns an ALSA-event-array, ready to be scheduled by
output()
.
If start is not used, the event will be sent directly.
Unlike in the alsaseq.py Python module,
if start is provided, the event will be scheduled in a queue.
The start element, when provided, is in floating-point seconds.
Returns an ALSA-event-array, ready to be scheduled by
output()
.
If start is not used, the event will be sent directly.
Unlike in the alsaseq.py Python module,
if start is provided, the event will be scheduled in a queue.
The start element, when provided, is in floating-point seconds.
Returns an ALSA-event-array for a patch_change
to be sent by output()
.
If start is not used, the event will be sent directly;
if start is provided, the event will be scheduled in a queue.
Unlike in the alsaseq.py Python module,
the start element, when provided, is in floating-point seconds.
Returns an ALSA-event-array to be sent by output()
.
The value is from -8192 to 8191.
If start is not used, the event will be sent directly;
if start is provided, the event will be scheduled in a queue.
Unlike in the alsaseq.py Python module,
the start element, when provided, is in floating-point seconds.
(Note that fluidsynth uses a different convention, in which the value is from 0 to 16383 and 8192 represents the central position.)
Returns an ALSA-event-array to be sent by output()
.
If start is not used, the event will be sent directly;
if start is provided, the event will be scheduled in a queue.
Unlike in the alsaseq.py Python module,
the start element, when provided, is in floating-point seconds.
Returns an ALSA-event-array to be sent by output()
.
If start is not used, the event will be sent directly;
if start is provided, the event will be scheduled in a queue.
Unlike in the alsaseq.py Python module,
the start element, when provided, is in floating-point seconds.
Returns an ALSA-event-array to be sent by output()
.
If start is not used, the event will be sent directly;
if start is provided, the event will be scheduled in a queue.
The string should start with your Manufacturer ID,
but should not contain any of the F0 or F7 bytes,
they will be added automatically;
indeed the string must not contain any bytes with the top-bit set.
Returns an event in the millisecond-tick score-format used by the MIDI.lua and MIDI.py modules, based on the score-format in Sean Burke's MIDI-Perl CPAN module. See: MIDI.html#events
Since it combines a note_on and a note_off event into one note event, it will return nil when called with the note_on event; the calling loop must therefore detect nil and not, for example, try to index it.
Returns an ALSA-event-array to be scheduled in a queue by
output()
.
The input is an event in the millisecond-tick score-format used by the
MIDI.lua and
MIDI.py modules,
based on the score-format in Sean Burke's MIDI-Perl CPAN module.
See: MIDI.html#events.
For example:
ALSA.output(ALSA.scoreevent2alsa{'note',4000,1000,0,62,110})
Some events in a .mid file have no equivalent
real-time-midi event, which is the sort that ALSA deals in;
these events will cause scoreevent2alsa()
to return nil.
Therefore if you are going through the events in a midi score
converting them with scoreevent2alsa()
,
you should check that the result is not nil
before doing anything further.
Returns a table with the client-numbers as key
and the descriptive strings of the ALSA client as value :
local clientnumber2clientname = ALSA.listclients()
Returns a table with the client-numbers as key
and how many ports they are running as value,
so if a client is running 4 ports they will be numbered 0..3
local clientnumber2howmanyports = ALSA.listnumports()
Returns an array of three-element arrays
{ {outputport, dest_client, dest_port}, }
with the same data as might have been passed to
connectto()
,
or which could be passed to
disconnectto()
.
Returns an array of three-element arrays
{ {inputport, src_client, src_port}, }
with the same data as might have been passed to
connectfrom()
,
or which could be passed to
disconnectfrom()
.
Given a string, this function returns two integers,
client_number and port_number,
as might be needed by
connectto()
or
connectfrom()
.
For example, even if
client()
has not been called,
"24" will return 24,0 and "25:1" will return 25,1
If the local client is running, then parse_address()
also looks up names. For example, if aconnect -oil
reveals a timidity client:
client 128: 'TiMidity' [type=user]
then parse_address("TiM")
will return 128,0
and parse_address("TiMi:1")
will return 128,1
because it finds the first client with a start-of-string
case-sensitive match to the given name.
But it seems to be a feature of underlying C library that clients
with a name that looks like a number (for example 2600
is the client-name of the Behringer 2600 synthesiser)
can not be parsed - you should consult aconnect -oil
and connect to them by address, eg: 24:0
parse_address() is called automatically by
connectto()
,
connectfrom()
,
disconnectto()
and
disconnectfrom()
if they are called with the second argument a string
and the third argument undefined.
parse_address() was introduced in version 1.11
and is not present in the alsaseq.py Python module.
SND_SEQ_EVENT_BOUNCE SND_SEQ_EVENT_CHANPRESS SND_SEQ_EVENT_CLIENT_CHANGE SND_SEQ_EVENT_CLIENT_EXIT SND_SEQ_EVENT_CLIENT_START SND_SEQ_EVENT_CLOCK SND_SEQ_EVENT_CONTINUE SND_SEQ_EVENT_CONTROL14 SND_SEQ_EVENT_CONTROLLER SND_SEQ_EVENT_ECHO SND_SEQ_EVENT_KEYPRESS SND_SEQ_EVENT_KEYSIGN SND_SEQ_EVENT_NONE SND_SEQ_EVENT_NONREGPARAM SND_SEQ_EVENT_NOTE SND_SEQ_EVENT_NOTEOFF SND_SEQ_EVENT_NOTEON SND_SEQ_EVENT_OSS SND_SEQ_EVENT_PGMCHANGE SND_SEQ_EVENT_PITCHBEND SND_SEQ_EVENT_PORT_CHANGE SND_SEQ_EVENT_PORT_EXIT SND_SEQ_EVENT_PORT_START SND_SEQ_EVENT_PORT_SUBSCRIBED SND_SEQ_EVENT_PORT_UNSUBSCRIBED SND_SEQ_EVENT_QFRAME SND_SEQ_EVENT_QUEUE_SKEW SND_SEQ_EVENT_REGPARAM SND_SEQ_EVENT_RESET SND_SEQ_EVENT_RESULT SND_SEQ_EVENT_SENSING SND_SEQ_EVENT_SETPOS_TICK SND_SEQ_EVENT_SETPOS_TIME SND_SEQ_EVENT_SONGPOS SND_SEQ_EVENT_SONGSEL SND_SEQ_EVENT_START SND_SEQ_EVENT_STOP SND_SEQ_EVENT_SYNC_POS SND_SEQ_EVENT_SYSEX SND_SEQ_EVENT_SYSTEM SND_SEQ_EVENT_TEMPO SND_SEQ_EVENT_TICK SND_SEQ_EVENT_TIMESIGN SND_SEQ_EVENT_TUNE_REQUEST SND_SEQ_EVENT_USR0 SND_SEQ_EVENT_USR1 SND_SEQ_EVENT_USR2 SND_SEQ_EVENT_USR3 SND_SEQ_EVENT_USR4 SND_SEQ_EVENT_USR5 SND_SEQ_EVENT_USR6 SND_SEQ_EVENT_USR7 SND_SEQ_EVENT_USR8 SND_SEQ_EVENT_USR9 SND_SEQ_EVENT_USR_VAR0 SND_SEQ_EVENT_USR_VAR1 SND_SEQ_EVENT_USR_VAR2 SND_SEQ_EVENT_USR_VAR3 SND_SEQ_EVENT_USR_VAR4 SND_SEQ_QUEUE_DIRECT SND_SEQ_TIME_STAMP_REAL Version VersionDate
You should avoid hard-coding their numerical values into your programs; but you may sometimes want to inspect ALSA data eg. with DataDumper. So, sorted by number as gleaned from the source:
0 SND_SEQ_EVENT_SYSTEM 1 SND_SEQ_EVENT_RESULT 5 SND_SEQ_EVENT_NOTE 6 SND_SEQ_EVENT_NOTEON 7 SND_SEQ_EVENT_NOTEOFF 8 SND_SEQ_EVENT_KEYPRESS 10 SND_SEQ_EVENT_CONTROLLER 11 SND_SEQ_EVENT_PGMCHANGE 12 SND_SEQ_EVENT_CHANPRESS 13 SND_SEQ_EVENT_PITCHBEND 14 SND_SEQ_EVENT_CONTROL14 15 SND_SEQ_EVENT_NONREGPARAM 16 SND_SEQ_EVENT_REGPARAM 20 SND_SEQ_EVENT_SONGPOS 21 SND_SEQ_EVENT_SONGSEL 22 SND_SEQ_EVENT_QFRAME 23 SND_SEQ_EVENT_TIMESIGN 24 SND_SEQ_EVENT_KEYSIGN 30 SND_SEQ_EVENT_START 31 SND_SEQ_EVENT_CONTINUE 32 SND_SEQ_EVENT_STOP 33 SND_SEQ_EVENT_SETPOS_TICK 34 SND_SEQ_EVENT_SETPOS_TIME 35 SND_SEQ_EVENT_TEMPO 36 SND_SEQ_EVENT_CLOCK 37 SND_SEQ_EVENT_TICK 38 SND_SEQ_EVENT_QUEUE_SKEW 39 SND_SEQ_EVENT_SYNC_POS 40 SND_SEQ_EVENT_TUNE_REQUEST 41 SND_SEQ_EVENT_RESET 42 SND_SEQ_EVENT_SENSING 50 SND_SEQ_EVENT_ECHO 51 SND_SEQ_EVENT_OSS 60 SND_SEQ_EVENT_CLIENT_START 61 SND_SEQ_EVENT_CLIENT_EXIT 62 SND_SEQ_EVENT_CLIENT_CHANGE 63 SND_SEQ_EVENT_PORT_START 64 SND_SEQ_EVENT_PORT_EXIT 65 SND_SEQ_EVENT_PORT_CHANGE 66 SND_SEQ_EVENT_PORT_SUBSCRIBED 67 SND_SEQ_EVENT_PORT_UNSUBSCRIBED 90 SND_SEQ_EVENT_USR0 91 SND_SEQ_EVENT_USR1 92 SND_SEQ_EVENT_USR2 93 SND_SEQ_EVENT_USR3 94 SND_SEQ_EVENT_USR4 95 SND_SEQ_EVENT_USR5 96 SND_SEQ_EVENT_USR6 97 SND_SEQ_EVENT_USR7 98 SND_SEQ_EVENT_USR8 99 SND_SEQ_EVENT_USR9 130 SND_SEQ_EVENT_SYSEX 131 SND_SEQ_EVENT_BOUNCE 135 SND_SEQ_EVENT_USR_VAR0 136 SND_SEQ_EVENT_USR_VAR1 137 SND_SEQ_EVENT_USR_VAR2 138 SND_SEQ_EVENT_USR_VAR3 139 SND_SEQ_EVENT_USR_VAR4 255 SND_SEQ_EVENT_NONE
The MIDI standard specifies that a NOTEON event with velocity=0 means the same as a NOTEOFF event; so you may find a little function like this convenient:
local function is_noteoff(alsaevent) if alsaevent[1] == ALSA.SND_SEQ_EVENT_NOTEOFF then return true end if alsaevent[1] == ALSA.SND_SEQ_EVENT_NOTEON and alsaevent[8][3] == 0 then return true end return false endSince Version 1.20, the output-ports are marked as WRITE, so they can receive SND_SEQ_EVENT_PORT_SUBSCRIBED or SND_SEQ_EVENT_PORT_UNSUBSCRIBED events from System Announce.
ALSA.connectfrom(0,'System:1')
This alerted you unnecessarily to events which didn't involve your client,
and the connection showed up confusingly
in the output of aconnect -oil
This module is available as a LuaRock in
luarocks.org/modules/peterbillam
so you should be able to install it with the command:
$ su
Password:
# luarocks install midialsa
or:
# luarocks install https://www.pjb.com.au/comp/lua/midialsa-1.25-0.rockspec
If this results in an error message such as:
Error: Could not find expected file libasound.a, or libasound.so,
or libasound.so.* for ALSA -- you may have to install ALSA in your
system and/or pass ALSA_DIR or ALSA_LIBDIR to the luarocks command.
Example: luarocks install midialsa ALSA_DIR=/usr/local
then you need to find the appropriate directory with:
find /usr/lib -name 'libasound.*' -print
and then invoke:
luarocks install \
http://www.pjb.com.au/comp/lua/midialsa-1.25-0.rockspec \
ALSA_LIBDIR=/usr/lib/x86_64-linux-gnu/ # or wherever
accordingly.
Or if it results in an error message such as:
Error: Could not find header file for ALSA
No file alsa/asoundlib.h in /usr/local/include
No file alsa/asoundlib.h in /usr/include
then you need to install your distribution's package with the asoundlib
development files, eg on debian that would be:
aptitude install libasound-dev
You can see the source-code in:
https://pjb.com.au/comp/lua/midialsa-1.25.tar.gz
20211118 1.25 include lua5.4
20150421 1.23 include lua5.3, and move pod and doc back to luarocks.org
20150416 1.22 asound and asoundlib.h specified as external dependencies
20140609 1.21 switch pod and doc over to using moonrocks
20140416 1.20 outputports marked WRITE to get UNSUBSCRIBED
messages from System
20140404 1.19 (dis)connect(to,from) use the new
parse_address;
some doc fixes
20130514 1.18
parse_address
matches startofstring to hide an alsa-lib 1.0.24 bug
20130211 1.18
noteonevent and
noteoffevent accept a start parameter
20121208 1.17 test script handles alsa_1.0.16 quirk
20121206 1.16 queue_id; test prints better diagnostics; 5.2-compatible
20120930 1.15 output() timestamp and duration in floating-point seconds
20111112 1.14 but output() does broadcast if destination is self
20111108 1.12 output() does not broadcast if destination is set
20111101 1.11
add parse_address() and call automatically from connectto() etc
20110624 1.09 maximum_nports increased from 4 to 64
20110428 1.06 fix bug in status() in the time return-value
20110323 1.05 controllerevent()
20110303 1.04 output, input, *2alsa and alsa2* now handle sysex events
20110228 1.03 add listclients, listconnectedto and listconnectedfrom
20110213 1.02 add disconnectto and disconnectfrom
20110210 1.01 output() no longer floors the time to the nearest second
20110209 1.01 pitchbendevent() and chanpress() return correct data
20110129 1.00 first working version
Perhaps there should be a general connect_between()
mechanism,
allowing the interconnection of two other clients,
a bit like aconnect 32 20
ALSA does not transmit Meta-Events like text_event, and there's not much can be done about that.
Peter J Billam, pjb.com.au/comp/contact.html
aconnect -oil http://pp.com.mx/python/alsaseq (now 404 ...) http://search.cpan.org/perldoc?MIDI::ALSA http://www.pjb.com.au/comp/lua/midialsa.html http://luarocks.org/modules/peterbillam/midialsa http://www.pjb.com.au/comp/lua/fluidsynth.html http://www.pjb.com.au/comp/lua/MIDI.html http://www.pjb.com.au/comp/lua/MIDI.html#events http://alsa-project.org/alsa-doc/alsa-lib/seq.html http://alsa-project.org/alsa-doc/alsa-lib/group___seq_events.html http://alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__ev__note.html http://alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__ev__ctrl.html http://alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__ev__queue__control.html http://alsa-project.org/alsa-doc/alsa-lib/group___seq_client.html http://alsa-utils.sourcearchive.com/documentation/1.0.20/aconnect_8c-source.html http://alsa-utils.sourcearchive.com/documentation/1.0.8/aplaymidi_8c-source.html snd_seq_client_info_event_filter_clear snd_seq_get_any_client_info snd_seq_get_client_info snd_seq_client_info_t