Autore Topic: M.I.D.I. - Inviare dati Midi mediante le funzioni di Alsa  (Letto 169 volte)

Offline vuott

  • Moderatore globale
  • Senatore Gambero
  • *****
  • Post: 11.270
  • Ne mors quidem nos iunget
    • Mostra profilo
M.I.D.I. - Inviare dati Midi mediante le funzioni di Alsa
« il: 01 Dicembre 2023, 17:10:14 »
Di seguito un semplicissimo programma che, usando alcune funzioni esterne del sistema audio ALSA, mediante una tastierina monofonica invia dati Midi ad un softsynth per la riproduzione del suono.

Se nel proprio sistema è stato installato il softsynth Fluidsynth, allora il programma, sopra descritto, dovrebbe connettersi automaticamente a quel softsynth.
Qualora ciò non avvenga, procedere come segue:
1) da Terminale lanciare questa riga: ~$ fluidsynth /percorso/del/file/soundfont/.sf2
2) senza chiudere il Terminale, verificare nell'utilità "Monitor di sistema " che Fluidsynth sia presente fra i processi attivi;
3) se Fluidsynth è presente fra i processi, verificare anche che esso sia presente al num. 128 nel file /proc/asound/seq/clients;
4) in caso affermativo - senza chiudere il Terminale - lanciare il programma della tastierina Midi, avente il codice sotto mostrato.
Se si usa il softsynth Fluidsynth, bisogna avere installato nel proprio sistema il pacchetto Fluid (R3) General MIDI SoundFont (GM): fluid-Soundfont-gm

Codice: [Seleziona]
Private cb As ComboBox
Private Const ALTEZZA_TASTI_BIANCHI 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
 CreaClient()
 With cb = New ComboBox(Me) As "Combo"
   .W = 160
   .H = 25
   .X = (Me.W * 0.94) - .W
   .Y = 0
   .List = instrumenta
   .Index = 0
 End With

 CreaTastiera()

End

Private Procedure CreaTastiera()

 Dim pn As Panel
 Dim neri As New Byte[40]
 Dim tasti 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
   neri[b] = 25 + (12 * b / 5)
   neri[b + 1] = 27 + (12 * b / 5)
   neri[b + 2] = 30 + (12 * b / 5)
   neri[b + 3] = 32 + (12 * b / 5)
   neri[b + 4] = 34 + (12 * b / 5)
   b += 5
 Until b == neri.Count

 tasti = New Button[109]

 For t As Short = 0 To tasti.Max
   With tasti[t] = New Button(Me) As "Tasti"
     .W = 0
     If t > 23 Then
       If neri.Exist(t) Then  ' Imposta i tasti neri
         .W = Me.W * 0.013
         .H = ((Me.H * ALTEZZA_TASTI_BIANCHI) * 66.66) / 100
         Select Case t
           Case 25 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 27 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 30 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 32 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 34 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
             Inc n
         End Select
         .Y = Me.H * 0.375
         .Background = Color.Black
         .Tag = t
       Else
' Imposta i tasti bianchi:
         .W = Me.W * 0.018
         .H = Me.H * ALTEZZA_TASTI_BIANCHI
         .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 Tasti_MouseDown()

 bb[0] = 0
 bb[1] = Last.Tag
 bb[2] = &64

 InvioMIDI(bb)

 If Last.Background = Color.Black Then Last.Background = Color.DarkGray
 Me.Title = "Nota Midi:  " & Last.Tag

End

Public Sub Tasti_MouseUp()

 bb[0] = 0
 bb[1] = Last.Tag
 bb[2] = 0

 InvioMIDI(bb)

 If Last.Background = Color.DarkGray Then Last.Background = Color.Black
 Me.Title = Null

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_EVENT_NOTEON As Byte = 6
Private Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11
Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253

' 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 CreaClient()

 Dim rit As Integer

 rit = snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & snd_strerror(rit))

End


Public Sub Combo_Change()  ' Imposta lo strumento musicale

 Dim evento As New Snd_seq_event_t

 With evento
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = 128
   .dest_port = 0
   .channel = 0
 End With

' Imposta il tipo di strumento musicale mediante il Messaggio Midi "Program-Change":
 Messaggio(evento, SND_SEQ_EVENT_PGMCHANGE, [0, 0, 0], cb.Index)

End


Private Procedure InvioMIDI(mid As Byte[])

 Dim evento As New Snd_seq_event_t

 With evento
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = 128
   .dest_port = 0
   .channel = mid[0]
 End With

' Imposta il Messaggio Midi "Note-ON":
 Messaggio(evento, SND_SEQ_EVENT_NOTEON, mid, 0)

End


Private Procedure Messaggio(ev As Snd_seq_event_t, tipo As Byte, nota As Byte[], strum As Integer)

 With ev
   .type = tipo
   .channel = nota[0]
   .note = nota[1]
   .velocity = nota[2]
   .value = strum
 End With

' Inserisce l'Evento di ALSA nel buffer:
 snd_seq_event_output(seq, ev)

' Invia l'Evento:
 snd_seq_drain_output(seq)

End


Public Sub Form_Close()

 snd_seq_close(seq)

End

Per saperne di più:

   https://www.gambas-it.org/wiki/index.php/La_gestione_dei_dati_Midi_con_il_subsistema_Seq


« Ultima modifica: 29 Dicembre 2023, 15:14:45 da vuott »
« Chiunque, non ricorrendo lo stato di necessità, nel proprio progetto Gambas fa uso delle istruzioni Shell o Exec, è punito con la sanzione pecuniaria da euro 20,00 a euro 60,00. »

Offline vuott

  • Moderatore globale
  • Senatore Gambero
  • *****
  • Post: 11.270
  • Ne mors quidem nos iunget
    • Mostra profilo
Re:M.I.D.I. - Inviare dati Midi mediante le funzioni di Alsa
« Risposta #1 il: 05 Dicembre 2023, 20:39:26 »
Brevi note su come inviare nella maniera più semplice possibile "Messaggi MIDI" a un softsynth via A.L.S.A..

Come è noto, un flusso di dati MIDI non contiene - come è per quello di tipo audio - le informazioni che descrivono le caratteristiche dell'onda sonora, bensì informazioni che dovranno essere utilizzate da altri programmi per eseguire un suono individuato fra una collezione. Questa collezione standardizzata di suoni appartenenti a vari strumenti musicali, per lo più di formato WAV, è contenuta in un apposito file, chiamato soundfont bank.

Il programma che deve usare i suoni, contenuti nei file del banco di fonti sonori (soundfont bank), affinché una nota MIDI possa essere udita, sono chiamati softsynth.
Il protocollo MIDI mediante un "Messaggio MIDI" informa il softsynth di utilizzare un certo font sonoro WAV (corrispondente in pratica a uno strumento musicale) modificandolo a una certa frequenza sonora e per un determinato periodo di tempo.

Pertanto, un programma, che produce "Messaggi MIDI", deve far recapitare i dati necessari al softsynth. Solo così il "Messaggio MIDI" potrà procurare la produzione del suono richiesto.

Attualmente la comunicazione fra un programma, che invia dati MIDI e il softsynth in dotazione avviene tramite il sistema sonoro A.L.S.A. che si incarica di gestire il protocollo di trasmissione, ricevimento ed eventuale temporizzazione dei dati MIDI, nonché il rapporto con il sistema operativo e l'hardware audio in dotazione.

ALSA si comporta da "Server" centrale che fornisce funzionalità e servizi ai programmi tenuti a rapportarsi con esso per la riproduzione audio.
Tale programmi, pertanto, diventano "Client" di ALSA, e si rapportano con tale sistema centrale sonoro per poter comunicare con altri "Client" e con il sistema operativo.
I "Client" del "Server" ALSA sono visibili consultando il file "/proc/asound/seq/clients".

Se dunque vogliamo in Gambas realizzare un qualsiasi programma che invii "Messaggi MIDI" al softsynth in dotzione per la loro gestione sonora, esso dovrà relazionarsi con ALSA.

Il sistema ALSA è composto da alcuni sub-sistemi. Il sub-sistema deputato a gestire i dati MIDI è chiamato "sequencer" di ALSA e identificato con l'abbreviativo "seq". Pertanto bisognerà aprire tale sub-sistema per poter da un lato trasformare il nostro programma in un "client" di ALSA e dall'altro fruire delle risorse che tale sub-sistema fornisce.

Per operare direttamente con ALSA e le sue risorse, il nostro programma Gambas dovrà utilizzare le funzione esterne di ALSA mediante l'istruzione "Extern", dopo aver comunque dichiarato la libreria condivisa di ALSA, contenente le funzioni esterne che serviranno per l'invio dei "Messaggi Midi".
La libreria condivisa esterna di ALSA sarà così dichiarata:
 
Codice: [Seleziona]
Library "libasound:2.0.0"
La funzione esterna di ALSA che permette di rendere il nostro programma Gambas un "Client" di ALSA è:
 snd_seq_open()
Dei quattro parametri formali qui rilevano in particolare il primo, che è un Puntatore di Puntatore, e che in Gambas va così riprodotto: VarPtr(Pointer); nonché il terzo: l'argomento passato sarà una Costante per l'impostazione del "sequencer" di ALSA per i dati in "Uscita".
Tale funzione esterna sarà nel nostro programma così dichiarata:
 Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer
Al termine il sub-sistema "seq" va chiuso, al fine di liberare la memoria usata per la gestione delle risorse fornite da ALSA.
La chiusura va effettuata con la funzione esterna di ALSA così dichiarata in Gambas:
 
Codice: [Seleziona]
Private Extern snd_seq_close(handle As Pointer) As Integer
La gestione degli eventuali errori con le funzioni esterne di ALSA avverrà con la specifica funzione esterna così dichiarata in Gambas:
 
Codice: [Seleziona]
Private Extern snd_strerror(err As Integer) As String
Disponendo l'apertura del sub-sistema "seq" di ALSA mediante un ''ToggleButton'', avremo dunque sino ad adesso il seguente codice:
 
Codice: [Seleziona]
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
Dunque cliccando sul ''ToggleButton'' si renderà il nostro programma un "Client" di ALSA. Ciò può essere constatato nel predetto file di sistema "/proc/asound/seq/clients". Se è stato già lanciato il sofsynth, allora al nostro programma sarà assegnato il numero identificativo 129 di "Client" di ALSA.

Il nostro programma MIDI è ora pronto per inviare dati MIDI al softsynth via ALSA.
ALSA è molto rigoroso e richiede che il "Messaggio MIDI" sia composto da vari, specifici dati da disporre su un'area di memoria riservata, formata da 28 byte.
Ogni "Messaggio MIDI" è rappresentato nel protocollo di ALSA con un "Evento MIDI" costituito dalla predetta area di memoria allocata di 28 byte, per la quale il protocollo di ALSA prevede l'uso di una Struttura.
Gambas ci mette a disposizione più di una opzione per creare tale area di memoria riservata. 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[].
Cliccando su un ''Button'', dei 28 byte valorizzeremo solo alcuni:
 All'elemento di indice zero assegneremo un intero che rappresenta di volta in volta il "Messaggio MIDI" di Note-ON (accensione della nota MID) oppure di Note-OFF (spegnimento della nota MIDI che è in esecuzione).
 All'elemento di indice 3 assegneremo il valore 253 per significare che l'invio dell'"Evento MIDI" di ALSA è diretto.
 All'elemento di indice 14 assegnaremo il numero identificativo del softsynth, al quale inviare l'"Evento MIDI" di ALSA, e che solitamente risulta essere 128, come è constatabile nel predetto file "/proc/asound/seq/clients".
 All'elemento di indice 17 assegnaremo un numero della nota MIDI, da eseguire o da silenziare.
 All'elemento di indice 18 assegnaremo il valore 100 di "velocità di tocco".
L'elemento d'indice 16 rappresenta il canale MIDI, che resterà - nel nostro caso - prestabilito a zero (canale MIDI 1). Se lo poniamo a 9 (canale MIDI 10), udremo uno strumento a percussione.

Costituito l'"Evento MIDI" di ALSA nel vettore di tipo Byte[], è necessario inviarlo al softsynth tramite ALSA.
Ciò avviene nel nostro caso essenziale tramite un'apposita funzione esterna, così dichiarata nel nostro programma:
 
Codice: [Seleziona]
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Byte[])

Tutto è pronto.
Di seguito il codice completo del nostro semplicissimo ed essenziale programma Gambas:
Codice: [Seleziona]
 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
« Ultima modifica: 06 Dicembre 2023, 11:56:37 da vuott »
« Chiunque, non ricorrendo lo stato di necessità, nel proprio progetto Gambas fa uso delle istruzioni Shell o Exec, è punito con la sanzione pecuniaria da euro 20,00 a euro 60,00. »