simple-dog
v1.2.0
Published
The goal of this project is to provide a network adapter for the Linux ALSA Sequencer API.
Downloads
4
Readme
Linux ALSA Sequencer UDP interface
Purpose
The goal of this project is to provide a network adapter for the Linux ALSA Sequencer API.
This project grew out of limitations experienced with MIDI packages in Node.js (issues when using MIDI and networking, or MIDI and Electron, for example), and the lack of up-to-date MIDI packages for Python. It should also be useful to easily integrate third-party, IoT-based devices into the Linux MIDI stack.
This project implements a MIDI-to-UDP interface which allows to receive, send, and control ALSA MIDI Sequencer sentences. The UDP interface is then made available as an API in a separate application; in the present case this package provides a Node.js API, but the implementation is language-agnostic.
The MIDI/UDP gateway is written entirely in C with alsa-lib
(what else) and libuv
to ensure fast delivery of messages.
Similar work
I am not aware of similar work but would greatly appreciate any references.
This is orthogonal to the purpose of RTP MIDI, in that RTP-MIDI is meant to be used between MIDI instruments (horizontally) while this is meant to be used between a MIDI stack and a non-MIDI environment (vertically).
Prerequisites: Debian, Ubuntu
apt install -y --no-recommends libasound2 libasound2-dev libuv1 libuv1-dev build-essential
Prerequisites: Alpine Linux
apk add musl-dev alsa-lib alsa-lib-dev libuv libuv-dev gcc
Installation
Using npm
npm install simple-dog
Using yarn
yarn add simple-dog
Starting the process
As indicated, this package contains two parts,
- a Linux ALSA MIDI to UDP gateway written in C
- an implementation of an API for Node.js.
In the regular case, the gateway is started (on the local host) by the Node.js process.
However the gateway can also be started manually, either on the local machine or on a remote machine.
./bin/alsa-midi-gateway
The process will bind on UDP port 3001 and send messages to UDP port 3002 on localhost by default.
The bind options can be overriden with SOURCE_HOST
and SOURCE_PORT
environment variables.
The remote options can be overriden with DEST_HOST
and DEST_PORT
environment variables.
API
Either
import {AlsaMidiClient} from 'simple-dog'
or
const {AlsaMidiClient} = require('simple-dog')
new AlsaMidiClient
const midi = new AlsaMidiClient('MIDI')
createPort
Create a new MIDI port for input and output.
const port = await midi.createPort('Port 1')
Create a new MIDI port for input only.
const port = await midi.createPort('Port 1','input')
Create a new MIDI port for output only.
const port = await midi.createPort('Port 1','output')
deletePort
await midi.deletePort(port)
sendMidi
await midi.sendMidi([0x91,0x40,velocity])
portConnect
const client_id = await midi.clientId()
await midi.portConnect(client_id,port, other_client_id,other_port)
portDisconnect
await midi.portDisconnect(client_id,port, other_client_id,other_port)
.on('ready', () => … )
Called when the system is ready to accept commands.
.on('midi', (midi_data) => … )
Receives plain MIDI data.
.on( event, (data) => … )
Receives any ALSA MIDI Sequencer event.
Some of these are standard MIDI events: NOTEON
, NOTEOFF
, KEYPRESS
, CONTROLLER
, … START
, STOP
, …
Others are ALSA MIDI specific events: CLIENT_START
, CLIENT_EXIT
, …
The complete list is available in the source.
Packet Format
Multi-byte values are Big-Endian-encoded.
Message types
SD_MSG_MARK
(timestamp-only) is sent from the interface to provide real-time timestamp information (MIDI data length = 0).SD_MSG_MIDI
(UDP→MIDI out) is sent to the interface to force the output of MIDI content onto the interface; MIDI data length = 0 is used to indicate end of stream, while any other value is parsed and sent out accordingly.SD_MSG_SEQ_EVENT
(MIDI in→UDP) is sent from the interface to provide event notifications, based on the ALSA Sequencer specifications.
Common fields
- offset 0: 1 byte version + time flags
- offset 1: 1 byte message type (
SD_MSG_*
) - offset 2: 8 bytes for timestamp
- offset 10: 2 bytes for MIDI data length = M (0 if no MIDI data is included)
- offset 12: M bytes for MIDI data
Additional fields
For SD_MSG_SEQ_EVENT
, the MIDI data might be present or absent (MIDI data length = 0) and the following fields are added:
- offset 12+M: 1 byte for event type (
SND_SEQ_EVENT_*
defined in ALSA-lib) - offset 13+M: 1 byte for event flags
- offset 14+M: 1 byte for queue
- offset 15+M: 1 byte for source client address
- offset 16+M: 1 byte for source client port
- offset 17+M: 1 byte for destination client address
- offset 18+M: 1 byte for destination client port
- offset 19+M: 1 byte for event data type (
SD_TYPE_*
, indicates the type of the data event content) - offset 20+M: 2 byte for event data length = N // Not sure why I'm doing this, wbuf can only store 128 bytes anyway
- offset 22+M: N bytes for event data (interpreted per
SD_TYPE
)