Page 1 of 1

Sending MIDI data to a softsynth via ALSA

Posted: Tuesday 5th December 2023 9:52pm
by vuott
Brief notes on how to send 'MIDI messages' to a softsynth via A.L.S.A. in the simplest manner possible.

As is well known, a MIDI data stream does not - as is the case with audio data - contain information describing the characteristics of the sound wave, but rather information to be used by other programs to play a sound selected from a collection. This standard collection of sounds belonging to various musical instruments, mostly in WAV format, is contained in a special file, called a soundfont bank.

The programs which must use the sounds, contained in the sound source bank files (soundfont bank), in order for a MIDI note to be heard, are called softsynths.

The MIDI protocol by means of a "MIDI Message" informs the softsynth to use a certain WAV sound font (corresponding in practice to a musical instrument) by modifying it at a certain sound frequency and for a certain period of time.

Therefore, a program, which produces "MIDI Messages", must deliver the necessary data to the softsynth. Only then can the "MIDI Message" procure the production of the required sound.

Currently, communication between a program, which sends MIDI data, and the supplied softsynth takes place via the A.L.S.A. sound system, which is responsible for managing the transmission protocol, reception and possible timing of the MIDI data, as well as the relationship with the operating system and the supplied audio hardware.

ALSA acts as a central "Server" providing functionality and services to the programs required to relate with it for audio reproduction.

Such programs, therefore, become 'Clients' of ALSA, and relate with this central sound system in order to communicate with other 'Clients' and the operating system.

The "Clients" of the ALSA "Server" are visible by consulting the file "/proc/asound/seq/clients".

Therefore, if we want to create any program in Gambas that sends "MIDI messages" to the softsynth supplied for their sound management, it must relate to ALSA.

The ALSA system consists of several sub-systems. The sub-system responsible for handling MIDI data is called the ALSA "sequencer" and is identified with the abbreviation "seq". It is therefore necessary to open this sub-system in order to be able, on the one hand, to transform our program into an ALSA "client" and, on the other hand, to make use of the resources that this sub-system provides.

In order to work directly with ALSA and its resources, our Gambas program will have to use ALSA's external functions by means of the "EXTERN" instruction, after having in any case declared ALSA's shared library, containing the external functions that will be used to send "Midi Messages".

The ALSA external shared library will be declared as follows:
Library "libasound:2.0.0"
The external ALSA function that allows our Gambas program to be an ALSA 'client' is:

snd_seq_open()

Of the four formal parameters here, of particular note is the first, which is a Pointer, and which in Gambas should be reproduced as follows: VarPtr(Pointer); as well as the third: the argument passed will be a Constant for setting the ALSA "sequencer" for the data in the "Output".

This external function will be declared in our program as follows:
Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer
When finished, the sub-system 'seq' must be closed, in order to free up the memory used for handling the resources provided by ALSA.

The closure is to be carried out with the external ALSA function thus declared in Gambas:
Private Extern snd_seq_close(handle As Pointer) As Integer
The handling of any errors with the external functions of ALSA will be done with the specific external function so declared in Gambas:
Private Extern snd_strerror(err As Integer) As String
By opening ALSA's 'seq' sub-system by means of a 'ToggleButton', we will therefore have the following code so far:
Private midi As Pointer
 
 
 Library "libasound:2.0.0"
 
 Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
 
 Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer
 Private Extern snd_strerror(err As Integer) As String
 Private Extern snd_seq_close(handle As Pointer) As Integer
 
 Public Sub ToggleButton1_Click()
 
  If ToggleButton1.Value Then
    Dim rit As Integer
    rit = snd_seq_open(VarPtr(midi), "default", SND_SEQ_OPEN_OUTPUT, 0)
    If rit < 0 Then Error.Raise("Error: " & snd_strerror(rit))
  Else
    snd_seq_close(midi)
  Endif
 
 End

So clicking on the ''ToggleButton'' will make our program an ALSA 'Client'. This can be seen in the aforementioned system file "/proc/asound/seq/clients". If the sofsynth has already been launched, then our program will be assigned the ALSA "Client" ID number 129.

Our MIDI program is now ready to send MIDI data to the softsynth via ALSA.

ALSA is very strict and requires that the "MIDI Message" be composed of various, specific data to be placed on a reserved memory area, consisting of 28 bytes.

Each "MIDI Message" is represented in ALSA's protocol by a "MIDI Event" consisting of the aforementioned 28-byte allocated memory area.

Gambas provides us with more than one option for creating such an allocated memory area. Since our example will be limited to ''MIDI messages'' of turning a Midi note on and off, we can safely use a vector of type Byte[].

By clicking on a ''Button'', we will only value a few of the 28 bytes:

To the zero index element we will assign an integer representing the ''MIDI Message'' of either Note-ON (turning on the MID note) or Note-OFF (turning off the MIDI note that is playing).
To index element 3 we will assign the value 253 to signify that the sending of ALSA's "MIDI Event" is direct.
To index element 14 we will assign the identification number of the softsynth, to which the ALSA "MIDI Event" is to be sent, and which is usually 128, as can be seen in the aforementioned "/proc/asound/seq/clients" file.
To index element 17 we will assign a MIDI note number, to be played or muted.
To index element 18 we will assign a "touch speed" value of 100.
Index element 16 represents the MIDI channel, which will remain - in our case - set to zero (MIDI channel 1). If we set it to 9 (MIDI channel 10), we will hear a percussion instrument.

Once the ALSA 'MIDI Event' has been constituted in the Byte[] type vector, it must be sent to the softsynth via ALSA.
This is done in our essential case by means of an external function, thus declared in our program:
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Byte[])
Everything is ready.
Below is the complete code for our very simple and essential Gambas program:
 Private midi As Pointer
 Private ev As New Byte[28]
 
 
 Library "libasound:2.0.0"
 
 Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
 Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253
 Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF
 
 Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer
 Private Extern snd_strerror(err As Integer) As String
 Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Byte[])
 Private Extern snd_seq_close(handle As Pointer) As Integer
 
 
 Public Sub Form_Open()
  
  Button1.Enabled = False
  
End

 
 Public Sub ToggleButton1_Click()
 
  If ToggleButton1.Value Then
    Dim rit As Integer
    rit = snd_seq_open(VarPtr(midi), "default", SND_SEQ_OPEN_OUTPUT, 0)
    If rit < 0 Then Error.Raise("Error: " & snd_strerror(rit))
    Button1.Enabled = True
  Else
    snd_seq_close(midi)
    Button1.Enabled = False
  Endif
 
 End

Public Sub Button1_MouseDown()

  ev[0] = SND_SEQ_EVENT_NOTEON
  ev[3] = SND_SEQ_QUEUE_DIRECT
  ev[14] = 128
  ev[17] = 64
  ev[18] = 100
  
  snd_seq_event_output_direct(midi, ev)

End


Public Sub Button1_MouseUp()

  ev[0] = SND_SEQ_EVENT_NOTEOFF
  ev[3] = SND_SEQ_QUEUE_DIRECT
  ev[14] = 128
  ev[17] = 64
  ev[18] = 0
  
  snd_seq_event_output_direct(midi, ev)

End


If you have the Fluidsynth softsynth installed on your system, then the program, described above, should automatically connect to that softsynth.

If this is not the case, proceed as follows:
1) from the Terminal run this line: ~$ fluidsynth and soundfont bank file (.sf2) path (i.e. "/usr/share/soundfonts/FluidR3_GM.sf2" or "/usr/share/sounds/soundfonts/FluidR3_GM.sf2");
2) without closing the Terminal, check in the "System Monitor" utility that Fluidsynth is present among the active processes;
3) if Fluidsynth is present among the processes, also check that it is present at no. 128 in the file /proc/asound/seq/clients;
4) if so - without closing the Terminal - run the program above.
If you use the Fluidsynth softsynth, you must have the Fluid (R3) General MIDI SoundFont (GM) package installed on your system: fluid-soundfont-gm