Ich möchte MIDI-Events in Echtzeit erzeugen und über einen Synthesizer als Sound ausgeben. Die Hauptfunktion des Sequenzers, dass nämlich die Töne zu einem definierten späteren Zeitpunkt gespielt werden, benötige ich hier gar nicht. Aber dieses Beispiel demonstriert die grundsätzliche Vorgehensweise bei der Programmierung des ALSA-Sequenzers.
Der erste Schritt ist immer, den Sequenzer zu öffnen. Ich kann ihn danach über ein Handle ansprechen.
#include <alsa/asoundlib.h>
snd_seq_t *handle = 0;
errorcode = snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
Wenn der Rückgabewert der Funktion snd_seq_open negativ ist, dann ist ein Fehler aufgetreten.
Es muss nun unbedingt ein Port erzeugt werden. Über diesen Port werden danach dann die MIDI-Events verschickt. Ein Port wird so erzeugt:
int portNumber = snd_seq_create_simple_port(handle, "my-port", SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC);
Und wieder gibt die Funktion einen negativen Wert zurück, wenn ein Fehler aufgetreten ist.
Um tatsächlich einen Ton zu erzeugen, brauche ich jetzt noch einen Synthesizer. Ich verwende den freien Software-Synthesizer Qsynth. Dieser muss gestartet sein.
Nun gelingt es mit folgendem einfachen Code einen Ton zu erzeugen. In diesem Fall ein eingestrichenes c.
snd_seq_event_t midiEvent;
snd_seq_ev_clear(&midiEvent);
snd_seq_ev_set_source(&midiEvent, portNumber);
snd_seq_ev_set_dest(&midiEvent, destinationClient, destinationPort);
snd_seq_ev_set_direct(&midiEvent);
midiEvent.type = SND_SEQ_EVENT_NOTEON;
midiEvent.data.note.channel = 0;
midiEvent.data.note.note = 64;
midiEvent.data.note.velocity = 64;
errorcode = snd_seq_event_output(handle, &midiEvent);
errorcode = snd_seq_drain_output(handle);
Der obere Teil des Codes stellt das MIDI-Event zusammen. Die Funktion snd_seq_event_output übergibt es direkt an die Ausgabeports und die Funktion snd_seq_drain_output sorgt dafür, dass die Events auch wirklich sofort geschickt werden.
Die beiden Funktionen snd_seq_ev_set_subs und snd_seq_ev_ste_direct sorgen dafür, dass das MIDI-Event direkt also in Echtzeit ausgegeben wird.
Der Code benötigt die beiden Parameter destinationClient und destinationPort. Das ist die Adresse, unter der der Qsynth erreichbar ist. Man kann diese Parameter mit Hilfe des ALSA-Utilities aconnect auf der Kommandozeile abfragen. Bei mir sieht das so aus:
$ aconnect -lo
client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'
Connecting To: 128:0
client 128: 'FLUID Synth (Qsynth1)' [type=user,pid=3773]
0 'Synth input port (Qsynth1:0)'
Connected From: 0:1, 14:0
Der Qsynth hat hier die Client-Nummer 128 und er stellt den Port Nummer 0 zur Verfügung. Folgerichtig kann ich im Code oben setzten:
destinationCliend = 128
destinationPort = 0