À propos de ce code
Comme on le sait, un flux de données MIDI ne contient pas - comme c'est le cas pour les données audio - les informations décrivant les caractéristiques de l'onde sonore, mais plutôt des informations à utiliser par d'autres programmes pour jouer un son sélectionné dans une collection. Cette collection standardisée de sons appartenant à divers instruments de musique, le plus souvent au format WAV, est contenue dans un fichier spécial, appelé banque de sons.
Les programmes qui doivent utiliser les sons contenus dans les fichiers de la banque de sources sonores (banque de soundfont) pour qu'une note MIDI soit entendue sont appelés softsynths.
Le protocole MIDI, au moyen d'un "message MIDI", informe le synthétiseur logiciel d'utiliser une certaine police de son WAV (correspondant en pratique à un instrument de musique) en la modifiant à une certaine fréquence sonore et pendant une certaine période de temps.
Par conséquent, un programme qui produit des "messages MIDI" doit fournir les données nécessaires au softsynth. Ce n'est qu'ensuite que le "message MIDI" peut produire le son souhaité.
Actuellement, la communication entre un programme qui envoie des données MIDI et le synthétiseur logiciel fourni s'effectue par l'intermédiaire du système sonore A.L.S.A., qui est responsable de la gestion du protocole de transmission, de la réception et de l'éventuelle synchronisation des données MIDI, ainsi que de la relation avec le système d'exploitation et le matériel audio fourni.
ALSA agit comme un "serveur" central qui fournit des fonctionnalités et des services aux programmes qui doivent être en relation avec lui pour la reproduction audio.
Ces programmes deviennent donc des "clients" de l'ALSA et se connectent à ce système sonore central pour communiquer avec d'autres "clients" et avec le système d'exploitation.
Les "Clients" du "Serveur" ALSA peuvent être vus en consultant le fichier "/proc/asound/seq/clients".
Par conséquent, si nous voulons créer un programme dans Gambas qui envoie des "messages MIDI" au softsynth en dotation pour leur gestion sonore, il doit se rapporter à ALSA.
Le système ALSA se compose de plusieurs sous-systèmes. Le sous-système responsable du traitement des données MIDI est appelé le "séquenceur" ALSA et est identifié par l'abréviation "seq". Il est donc nécessaire d'ouvrir ce sous-système pour pouvoir, d'une part, transformer notre programme en "client" ALSA et, d'autre part, utiliser les ressources que ce sous-système met à notre disposition.
Pour travailler directement avec ALSA et ses ressources, notre programme Gambas devra utiliser les fonctions externes d'ALSA au moyen de l'instruction "Extern", après avoir de toute façon déclaré la bibliothèque partagée d'ALSA, contenant les fonctions externes qui seront utilisées pour envoyer les "Messages Midi".
La bibliothèque partagée externe d'ALSA sera déclarée comme suit :
1
| LIBRARY "libasound:2.0.0"
|
La fonction ALSA externe qui permet à notre programme Gambas d'être un "client" ALSA est la suivante :
Parmi les quatre paramètres formels, il faut souligner le premier, qui est un pointeur et qui, dans Gambas, doit être reproduit comme suit : VarPtr(Pointer) ; ainsi que le troisième : l'argument passé sera une constante pour définir le "séquenceur" ALSA pour les données de la "Sortie".
Cette fonction externe sera déclarée dans notre programme comme suit :
1
| PRIVATE EXTERN snd_seq_open(handle AS POINTER, name AS STRING, streams AS INTEGER, mode AS INTEGER) AS INTEGER
|
Une fois terminé, le sous-système "seq" doit être fermé, afin de libérer la mémoire utilisée pour la gestion des ressources fournies par l'ALSA.
La fermeture doit être effectuée à l'aide de la fonction externe ALSA déclarée dans Gambas comme suit :
1
| PRIVATE EXTERN snd_seq_close(handle AS POINTER) AS INTEGER
|
La gestion des erreurs avec les fonctions externes de l'ALSA doit être effectuée avec la fonction externe spécifique déclarée dans Gambas comme suit:
1
| PRIVATE EXTERN snd_strerror(err AS INTEGER) AS STRING
|
En ouvrant le sous-système ALSA "seq" au moyen d'un "ToggleButton", nous aurons donc le code suivant jusqu'à présent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| 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("Errore: " & snd_strerror(rit)) ELSE snd_seq_close(midi) ENDIF END
|
En cliquant sur le bouton ''ToggleButton'', notre programme deviendra un ''Client'' ALSA. On peut le voir dans le fichier système susmentionné "/proc/asound/seq/clients". Si le softsynth a déjà été lancé, le numéro de "client" ALSA 129 sera attribué à notre programme.
Notre programme MIDI est maintenant prêt à envoyer des données MIDI au softsynth via ALSA.
ALSA est très strict et exige que le "Message MIDI" soit composé de diverses données à placer dans une zone de mémoire réservée, composée de 28 octets.
Chaque "Message MIDI" est représenté dans le protocole d'ALSA par un "Evénement MIDI" composé de la zone de mémoire allouée de 28 octets mentionnée ci-dessus.
Gambas nous offre plus d'une option pour créer une telle zone de mémoire allouée. Poiché il nostro esempio si limiterà ai soli "Messaggi MIDI" di accensione e spegnimento di una nota Midi, potremo usare con tranquillità un vettore di tipo Byte[].
En cliquant sur un "Button", sur les 28 octets, nous n'en valoriserons que quelques-uns:
A l'élément d'index zéro nous assignerons un entier représentant le ''Message MIDI'' de Note-ON (allumage de la note MID) ou Note-OFF (extinction de la note MIDI jouée).
A l'élément d'index 3, nous attribuerons la valeur 253 pour signifier que l'envoi de l'"événement MIDI" ALSA est direct.
A l'élément d'index 14 nous assignerons le numéro d'identification du softsynth à qui l'"événement MIDI" d'ALSA doit être envoyé, qui est généralement 128, comme on peut le voir dans le fichier "/proc/asound/seq/clients" susmentionné.
A l'élément d'index 17, nous assignerons un numéro de note MIDI, à jouer ou à mettre en sourdine.
A l'élément d'index 18, nous assignerons une valeur de "vitesse de toucher" de 100.
L'élément d'index 16 représente le canal MIDI (0 à 15). Si, par exemple, la valeur 9 (canal MIDI 10) est attribuée à cet élément, un instrument de percussion sera entendu.
Une fois que l'ALSA "MIDI Event" a été constitué dans le vecteur de type Byte[], il est nécessaire de l'envoyer au softsynth via l'ALSA.
Dans notre cas essentiel, cela se fait par l'intermédiaire d'une fonction externe, déclarée dans notre programme comme suit :
1
| PRIVATE EXTERN snd_seq_event_output_direct(handle AS POINTER, ev AS Byte[])
|
Tout est prêt.
Voici le code complet de notre programme Gambas
très simple et
essentiel:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| PRIVATE midi AS POINTER PRIVATE evento 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("Errore: " & snd_strerror(rit)) Button1.Enabled = TRUE ELSE snd_seq_close(midi) Button1.Enabled = FALSE ENDIF END
PUBLIC SUB Button1_MouseDown()
evento[0] = SND_SEQ_EVENT_NOTEON evento[3] = SND_SEQ_QUEUE_DIRECT evento[14] = 128 evento[17] = 64 evento[18] = 100 snd_seq_event_output_direct(midi, evento)
END
PUBLIC SUB Button1_MouseUp()
evento[0] = SND_SEQ_EVENT_NOTEOFF evento[3] = SND_SEQ_QUEUE_DIRECT evento[14] = 128 evento[17] = 64 evento[18] = 0 snd_seq_event_output_direct(midi, evento)
END
|
Si le softsynth Fluidsynth a été installé sur votre système, le programme décrit ci-dessus devrait se connecter automatiquement à ce softsynth.
Si ce n'est pas le cas, procédez comme suit :
1) à partir du Terminal, lancez la ligne suivante : ~$
fluidsynth reload 0 ;
2) sans fermer le Terminal, vérifier dans l'utilitaire "Moniteur système" (
System Monitor) que Fluidsynth est présent parmi les processus actifs ;
3) si Fluidsynth est présent parmi les processus, vérifier également qu'il est présent au n° 128 du fichier /proc/asound/seq/clients ;
4) si c'est le cas - sans fermer le Terminal - exécuter le programme.
Si vous utilisez le logiciel Fluidsynth, vous devez avoir installé le paquet "Fluid (R3) General MIDI SoundFont (GM)" sur votre système:
fluid-soundfont-gm
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
PRIVATE cb AS ComboBox
PRIVATE CONST HAUTEUR_TOUCHES_BLANCHES AS SINGLE = 0.35
PRIVATE bb AS NEW BYTE[3]
PRIVATE instrumenta AS String[] = ["Acustic Grand Piano", "Bright Acustic Piano", "Electric Grand Piano", "Honky-tonk",
"Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", "Celesta", "Glockenspiel", "Music Box", "Vibraphone",
"Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Hammond Organ", "Percussive Organ", "Rock Organ", "Church Organ",
"Reed Organ", "Accordion", "Harmonica", "Tango Accordion", "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)",
"Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar(muted)", "Overdriven Guitar", "Distortion Guitar",
"Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1",
"Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings",
"Pizzicato Strings", "Orchestral Harp", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1",
"SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet",
"French Horn", "Brass Section", "Synth Brass 1", "Synth Brass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax",
"Oboe", "English Horn", "Basson", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Bottle Blow", "Shakuhachi",
"Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (caliope lead)", "Lead 4 (chiff lead)",
"Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8(brass+lead)", "Pad 1 (new age)", "Pad 2 (warm)",
"Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)",
"FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)",
"FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo",
"Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise",
"Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot"]
PUBLIC SUB Form_Open()
WITH ME
.W = Screen.AvailableWidth * 0.5
.H = Screen.AvailableHeight * 0.2
.Center
END WITH
CreeClient()
WITH cb = NEW ComboBox(ME) AS "Combo"
.W = 160
.H = 25
.X = (ME.W * 0.94) - .W
.Y = 0
.List = instrumenta
.Placeholder = "Instruments musicaux"
END WITH
CreeClavier()
END
PRIVATE PROCEDURE CreeClavier()
DIM pn AS Panel
DIM noirs AS NEW BYTE[40]
DIM touches AS Button[]
DIM b, c, n AS BYTE
WITH pn = NEW Panel(ME)
.W = ME.W * 0.88
.H = ME.H * 0.2
.X = (ME.W / 2) - (pn.W / 2)
.Y = ME.H * 0.2
.Border = Border.Sunken
.Background = &8b4513
END WITH
REPEAT
noirs[b] = 25 + (12 * b / 5)
noirs[b + 1] = 27 + (12 * b / 5)
noirs[b + 2] = 30 + (12 * b / 5)
noirs[b + 3] = 32 + (12 * b / 5)
noirs[b + 4] = 34 + (12 * b / 5)
b += 5
UNTIL b == noirs.Count
touches = NEW Button[109]
FOR t AS SHORT = 0 TO touches.Max
WITH touches[t] = NEW Button(ME) AS "Touches"
.W = 0
IF t > 23 THEN
IF noirs.Exist(t) THEN ' Définit les touches en noir
.W = ME.W * 0.013
.H = ((ME.H * HAUTEUR_TOUCHES_BLANCHES) * 66.66) / 100
SELECT CASE t
CASE 25 + (12 * n)
.X = touches[t - 1].X + (((ME.W * 0.025) / 2))
CASE 27 + (12 * n)
.X = touches[t - 1].X + (((ME.W * 0.025) / 2))
CASE 30 + (12 * n)
.X = touches[t - 1].X + (((ME.W * 0.025) / 2))
CASE 32 + (12 * n)
.X = touches[t - 1].X + (((ME.W * 0.025) / 2))
CASE 34 + (12 * n)
.X = touches[t - 1].X + (((ME.W * 0.025) / 2))
INC n
END SELECT
.Y = ME.H * 0.375
.Background = Color.Black
.Tag = t
ELSE
' Définit les touches blanches:
.W = ME.W * 0.018
.H = ME.H * HAUTEUR_TOUCHES_BLANCHES
.X = (.W * c) + (ME.W / 16)
.Y = ME.H * 0.37
.Background = Color.White
.Tag = t
.Lower
INC c
ENDIF
ENDIF
END WITH
NEXT
END
PUBLIC SUB Touches_MouseDown()
bb[0] = 0
bb[1] = LAST.Tag
bb[2] = &64
EnvoiMIDI(SND_SEQ_EVENT_NOTEON, bb)
IF LAST.Background = Color.Black THEN LAST.Background = Color.DarkGray
ME.Title = "Nota Midi: " & LAST.Tag
END
PUBLIC SUB Touches_MouseUp()
bb[0] = 0
bb[1] = LAST.Tag
bb[2] = 0
EnvoiMIDI(SND_SEQ_EVENT_NOTEOFF, bb)
IF LAST.Background = Color.DarkGray THEN LAST.Background = Color.Black
ME.Title = " "
END
'''''''''''''''''''''''''''''''''''''''''''''
PRIVATE seq AS POINTER
LIBRARY "libasound:2"
PUBLIC STRUCT snd_seq_event_t ' Struttura dell'Evento Midi di ALSA
type AS BYTE
flags AS BYTE
tag AS BYTE
queue AS BYTE
tick_time AS INTEGER
real_time AS INTEGER
source_client AS BYTE
source_port AS BYTE
dest_client AS BYTE
dest_port AS BYTE
channel AS BYTE
note AS BYTE
velocity AS BYTE
off_velocity AS BYTE
param AS INTEGER
value AS INTEGER
END STRUCT
PRIVATE CONST SND_SEQ_OPEN_OUTPUT AS INTEGER = 1
PRIVATE CONST SND_SEQ_QUEUE_DIRECT AS BYTE = 253
PRIVATE CONST SND_SEQ_EVENT_NOTEON AS BYTE = 6
PRIVATE CONST SND_SEQ_EVENT_NOTEOFF AS BYTE = 7
PRIVATE CONST SND_SEQ_EVENT_PGMCHANGE AS BYTE = 11
' int snd_seq_open (snd_seq_t **handle, const char * name, int streams, int mode)
' Open the ALSA sequencer.
PRIVATE EXTERN snd_seq_open(handle AS POINTER, name AS STRING, streams AS INTEGER, mode AS INTEGER) AS INTEGER
' const char * snd_strerror (int errnum)
' Returns the message for an error code.
PRIVATE EXTERN snd_strerror(err AS INTEGER) AS STRING
' int snd_seq_event_output (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event.
PRIVATE EXTERN snd_seq_event_output(handle AS POINTER, ev AS Snd_seq_event_t)
' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
PRIVATE EXTERN snd_seq_drain_output(seq AS POINTER) AS INTEGER
' int snd_seq_close (snd_seq_t *handle)
' Close the sequencer.
PRIVATE EXTERN snd_seq_close(handle AS POINTER) AS INTEGER
PRIVATE PROCEDURE CreeClient()
DIM rit AS INTEGER
rit = snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
IF rit < 0 THEN Error.Raise("Impossible d'ouvrir le sous-système ALSA 'seq'.: " snd_strerror(rit))
END
PUBLIC SUB Combo_Change() ' Définit l'instrument de musique
DIM ev AS NEW Snd_seq_event_t
WITH ev
.queue = SND_SEQ_QUEUE_DIRECT
.dest_client = 128
.dest_port = 0
.channel = 0
END WITH
' Définit le type d'instrument de musique via le message midi "Program-Change":
Message(ev, SND_SEQ_EVENT_PGMCHANGE, [0, 0, 0], cb.Index)
END
PRIVATE PROCEDURE EnvoiMIDI(type AS BYTE, mid AS Byte[])
DIM ev AS NEW Snd_seq_event_t
WITH ev
.queue = SND_SEQ_QUEUE_DIRECT
.dest_client = 128
.dest_port = 0
.channel = mid[0]
END WITH
' Définit le message MIDI "Note-ON":
Message(ev, type, mid, 0)
END
PRIVATE PROCEDURE Message(ev AS Snd_seq_event_t, tp AS BYTE, nota AS Byte[], strum AS INTEGER)
WITH ev
.type = tp
.channel = nota[0]
.note = nota[1]
.velocity = nota[2]
.value = strum
END WITH
' Insère l'événement ALSA dans le 'buffer':
snd_seq_event_output(seq, ev)
' Envoyer un événement MIDI:
snd_seq_drain_output(seq)
END
PUBLIC SUB Form_Close()
snd_seq_close(seq)
END