Alsa e Gambas: Invio dati con l'uso di un'area di memoria allocata

Da Gambas-it.org - Wikipedia.
Versione del 11 gen 2022 alle 17:10 di Vuott (Discussione | contributi) (Creata pagina con "Una modalità per creare ed inviare eventi Midi ad Alsa è quella mediante l'allocazione di memoria puntata da una variabile di tipo puntatore. ==Riservare innanzitutto memo...")

(diff) ← Versione meno recente | Versione attuale (diff) | Versione più recente → (diff)

Una modalità per creare ed inviare eventi Midi ad Alsa è quella mediante l'allocazione di memoria puntata da una variabile di tipo puntatore.


Riservare innanzitutto memoria per il puntatore

Utilizzeremo quindi la funzione Alloc() per riservare una porzione di memoria e porla a disposizione del pointer, che chiameremo: "ev". Più precisamente con Alloc() si prende una parte di memoria dal sistema e la si assegna al programma. A questo punto è possibile operare con il pointer, a patto che si vada a scrivere esclusivamente dentro quella porzione di memoria che è stata appositamente riservata per il pointer |1|.

Assegneremo un'ampia porzione pari a 32 byte, il cui valore lo attribuiremo ad una apposita costante dichiarata all'inizio della classe CAlsa.class:

PRIVATE CONST SIZE_OF_SEQEV AS Integer = 32
......
ev = Alloc(SIZE_OF_SEQEV)


Uso dei Memory Stream per scrivere nell'area di memoria allocata

Per scrivere i valori arbitrari dentro la predetta zona di memoria, necessari alla definizione dell'evento Midi, useremo i Memory Stream mediante la funzione Memory. In questo modo, dopo aver allocato la zona di memoria riservata allo stream, agganceremo il Memory Stream a detta zona di memoria attraverso una variabile globale di tipo Stream posta nella classe seondaria, che abbiamo chiamato: CAlsa, predisposta per la scrittura:

Private st As Stream
......
Private st = Memory ev For Write

Viene così creato lo stream, ed il suo puntatore interno, implicito, è posto automaticamente all'inizio dello stream medesimo.

I memory stream dispongono di un puntatore interno che all'atto della scrittura

WRITE #stream, espressione As Byte

automaticamente viene incrementato di un byte innanzi |2|.


La struttura degli eventi Midi trascritta in Gambas con i Memory Stream

Procediamo dunque ad inserire nel nostro codice in Gambas-3 la suddetta struttura all'interno di una sub-routine generale per la preparazione degli eventi, la quale sarà richiamata dalle routine principali di ciascun evento Midi.

Inseriremo fra le dichiarazioni iniziali due costanti relative agli indirizzi del sorgente e della destinazione degli eventi, che nella documentazione di ALSA sono così descritte:

  • #define SND_SEQ_ADDRESS_SUBSCRIBERS 254
    send event to all subscribed ports
  • #define SND_SEQ_ADDRESS_UNKNOWN 253
    special client (port) ids unknown source
Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254

Ricordiamo che il nostro scopo iniziale era di creare un semplice applicativo capace di inviare unici eventi Midi. Pertanto, escluderemo i campi relativi al Timestamp e più in generale al Tempo (Time), ossia i campi denominati: flags, tag e time (li riportiamo comunque scritti per opportuna memoria):

PRIVATE Sub prepareev(type As Byte)      ' Inseriamo solo il valore del campo Type,
                                         ' perché in questo nostro caso è l'unico che ci serve !

 Dim i As Integer
' Dim flags, tag As Byte
 
 ' pulisce la zona di memoria allocata...
  Seek #st, 0                  '...partendo dal 1° byte, ossia dal n° 0.
   For i = 1 To SIZE_OF_SEQEV
    Write #st, 0 As Byte
   Next

 ' Inizio struttura degli eventi Midi

' Type
  Seek #st, 0
  Write #st, type As Byte

' Flags
' Write #st, flags As Byte

' Tag
' Write #st, tag As Byte

 ' Eliminando i campi precedenti, il puntatore implicito dello stream non avanza, ovviamente.
 ' Pertanto, dobbiamo porre il puntatore sul byte della zona di memoria opportuno, cioè quello richiesto da ALSA.
 Seek #st, 3
' Queue
  Write #st, outq As Byte

' Time 
' Write #st, ts As Integer        ' timestamp
' Write #st, ts As Integer        ' 2^ parte (realtime event)

 Seek #st, 12                     ' Dobbiamo comunque spostare il puntatore dello stream al byte n° 12 (ossia al 13°)
' Source
  Write #st, id As Byte
 
  Write #st, outport As Byte

' Dest
  Write #st, SND_SEQ_ADDRESS_SUBSCRIBERS As Byte   ' 254/dclient

  Write #st, SND_SEQ_ADDRESS_UNKNOWN As Byte       ' 253/dport

End


Il codice completo

Mostriamo di seguito il codice definitivo sin qui descritto, proposto per un'applicazione in ambiente grafico, ed avente un'apposita Classe destinata a contenenre le funzioni e risorse per la gestione di ALSA e degli eventi Midi:

CLASSE: FMain.class
Public alsa as CAlsa       ' classe che incapsula le funzioni ALSA
Public gross As Integer    ' dovrebbe ricevere il valore da uno Slider


Public Sub Form_Open()

' Crea ("istanzia") la classe per poterla usare
alsa = NEW CAlsa as "alsa"

' Apre alsa e gli assegna un nome
alsa.alsa_open("Progetto sequencer in Gambas-3") ' La stringa fra virgolette sarà il nome del nostro Client

' Sceglie la periferica su cui suonare.
' Ident.vo numerico e num. porta di altro dispositivo/client (ad esempio: QSynth), al quale il client si connetterà.
alsa.setdevice(128, 0)

End


Public Sub noteOn_Click()          ' Note ON
  
 ' chiama la subroutine "noteon" nella classe secondaria CAlsa.class;
 ' poniamo come esempio la nota da suonare num. 60 (il DO centrale) e velocità 100.
 alsa.noteon(0, 60, 100)

 alsa.flush()

End


Public Sub noteOff_Click()          ' Note OFF
  
 ' chiama la subroutine "noteoff" nella classe secondaria CAlsa.class;
 ' poniamo come esempio la nota da far cessare di suonare num. 60 (il DO centrale) e velocità 0.
 alsa.noteoff(0, 60, 0)

 alsa.flush()

End


Public Sub polypho_Click()          ' Polyphonic Aftertouch (Key pressure)
   
  ' chiama la subroutine, che chiameremo "polypho", nella classe secondaria CAlsa.class;
  ' poniamo come esempio alla nota 50 un valore di pressione aggiuntiva di 110.
  alsa.polypho(0, 60, 110)

  alsa.flush()

End

 
Public Sub controller_Click()          ' Control Change
   
  ' chiama la subroutine, che chiameremo "controller", nella classe secondaria CAlsa.class;
  ' poniamo come esempio il tipo di controller num. 7 (Volume). Il terzo valore sarà posto a 100.
  alsa.controller(0, 7, 100)

  alsa.flush()

End


Public Sub pgmchange_Click()          ' Program Change
   
  ' chiama la subroutine, che chiameremo "pgmchange", nella classe secondaria CAlsa.class;</font>
  ' poniamo come esempio lo strumento musicale num. 71 (Fagotto).
  alsa.pgmchange(0, 71)

  alsa.flush()

End


Public Sub chanpress_Click()          ' Channel Aftertouch (Channel Pressure)
   
  ' chiama la subroutine, che chiameremo "chanpress", nella classe secondaria CAlsa.class;
  ' poniamo come esempio un valore di pressione aggiuntiva di 110.
  alsa.chanpress(0, 110)

  alsa.flush()

End


Public Sub SliderPB_Change()          ' Pitch Bend (Pitch Wheel)
   
  gross = SliderPB.Value

  ' chiama la subroutine, che chiameremo "pitchbend", nella classe secondaria CAlsa.class;
  alsa.pitchbend(0, 0, gross)

  alsa.flush()

End
 
----


CLASSE: CAlsa.class
Private handle As Pointer
Private id As Integer
Private outport As Integer
Private outq As Integer

Public dclient As Byte
Public dport As Byte

Private ev As Pointer
Private p As Stream

Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576  '  (1<<20)

Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254

Const SND_SEQ_EVENT_NOTEON As Byte = 6
Const SND_SEQ_EVENT_NOTEOFF As Byte = 7
Const SND_SEQ_EVENT_KEYPRESS As Byte = 8
Const SND_SEQ_EVENT_CONTROLLER As Byte = 10
Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11
Const SND_SEQ_EVENT_CHANPRESS As Byte = 12
Const SND_SEQ_EVENT_PITCHBEND As Byte = 13


Private Const SIZE_OF_SEQEV As Integer = 64

Library "libasound:2"

'---- Parte relativa alla creazione del Client e delle sue porte ----

' int snd_seq_open(snd_seq_t **seqp, const char * name, Int streams, Int mode); 
Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name(snd_seq_t* seq, const char* name)
Private Extern snd_seq_set_client_name(handle As Pointer, name As String) As Integer

' int snd_seq_create_simple_port(snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type)
Private Extern snd_seq_create_simple_port(handle As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_client_id(snd_seq_t * seq)
Private Extern snd_seq_client_id(handle As Pointer) As Integer

' int snd_seq_connect_to(seq as pointer, myport as integer, src_client as integer, src_port as integer)
Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

' int snd_seq_alloc_named_queue(snd_seq_t * seq, CONST char * name)
Private Extern snd_seq_alloc_queue(seq As Pointer, name As String) As Integer
 
' int snd_seq_event_output (snd_seq_t *handle, snd_seq_event_t *ev)</font>
Private Extern snd_seq_event_output(handle As Pointer, ev As Pointer) As Integer
 
 ' int snd_seq_drain_output (snd_seq_t * handle)
Private Extern snd_seq_drain_output(handle As Pointer) As Integer


Public Sub alsa_open(myname As String)
 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_DUPLEX, 0)
 Print "Opening alsa="; err
 If err < 0 Then error.RAISE("Error opening alsa")
  
 snd_seq_set_client_name(handle, myname)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID="; id
  
 err = snd_seq_create_simple_port(handle, "Seq-Out", 0, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)
 Print "My alsa client port="; err
 If err < 0 Then error.Raise("Error creating alsa port")
 outport = err
  
 err = snd_seq_alloc_queue(handle, "outqueue")         ' per creare una coda di eventi
  printerr("Creating queue", err)
 If err < 0 Then error.Raise("Error creating out queue")
 outq = err
  
' alloca un evento nella zona di memoria riservata per lavorarci.
' E' globale per evitare allocazioni/deallocazioni onerose.
 ev = Alloc(SIZE_OF_SEQEV)
 
 p = Memory ev For Write

End


Public Sub setdevice(client As Integer, port As Integer)
 Dim err As Integer
 
 dclient = client
 dport = port
 err = snd_seq_connect_to(handle, outport, dclient, dport)
 printerr("Subscribe", err)
 If err < 0 Then error.Raise("Error subscribe output device")

End


'---- Parte relativa alla gestione degli errori ----

Private Extern snd_strerror(err As Integer) As Pointer

Public Sub errmsg(err As Integer) As String
 Return String@(snd_strerror(err))

End


Private Sub printerr(operation As String, err As Integer)

 If err < 0 Then Print operation; ": err ="; err; " ("; errmsg(err); ")"

End


'---- Parte relativa all’invio degli eventi Midi ----
 
' Note ON
Public Sub noteon(channel As Byte, note As Byte, velocity As Byte)

 Dim err As Integer
 
prepareev(SND_SEQ_EVENT_NOTEON)

 Write #p, channel As Byte

 Write #p, note As Byte

 Write #p, velocity As Byte


 err = snd_seq_event_output(handle, ev)
 printerr("Noteon = ", err)

End


' Note OFF
Public Sub noteoff(channel As Byte, note As Byte, velocity As Byte)

 Dim err As Integer
 
prepareev(SND_SEQ_EVENT_NOTEOFF)

 Write #p, channel As Byte

 Write #p, note As Byte

 Write #p, velocity As Byte


 err = snd_seq_event_output(handle, ev)
 printerr("NoteOFF = ", err)

End


' Polyphonic Aftertouch (Key Pressure)
 Public Sub polypho(channel As Byte, note As Byte, velocity As Byte)
 
 Dim err As Integer

' Chiama la subroutine prepareev
  prepareev(SND_SEQ_EVENT_KEYPRESS)
 
  Write #p, channel As Byte

  Write #p, note As Byte

  Write #p, poly As Byte


  err = snd_seq_event_output(handle, ev)        ' output an event
  printerr("Polyphonic = ", err)

End


 ' Control Change
 Public Sub controller(channel As integer, ctrl As integer, valCtr as integer)

 Dim err As Integer
 
 ' Chiama la subroutine prepareev
  prepareev(SND_SEQ_EVENT_CONTROLLER)
 
   Write #p, channel As Integer

   Write #p, ctrl As Integer

   Write #p, valCtr As Integer

  err = snd_seq_event_output(handle, ev)        ' output an event
  printerr("Controller = ", err)

End



' Program Change
Public Sub pgmchange(channel As Byte, strum As Integer)
 
 Dim err As Integer
 
 ' Chiama la subroutine prepareev
  prepareev(SND_SEQ_EVENT_PGMCHANGE)
  
  Write #p, channel As Byte
  Seek #p, 24                             ' Spostiamo il puntatore dello stream sul byte num. 24
  Write #p, strum As Integer

  err = snd_seq_event_output(handle, ev)        ' output an event
  printerr("Pgmchange = ", err)
 
End


' Channel Aftertouch (Channel Pressure) 
 Public Sub chanpress(channel As Byte, press As Integer)

 Dim err As Integer
 
' Chiama la subroutine prepareev
  prepareev(SND_SEQ_EVENT_CHANPRESS)
 
  Write #p, channel As Byte
  Seek #p, 24                             ' Spostiamo il puntatore dello stream sul byte num. 24
  Write #p, press As Integer
  
  err = snd_seq_event_output(handle, ev)        ' output an event
  printerr("ChanPress = ", err)

End


' Pitch Bend (Pitch Wheel)  
 Public Sub pitchbend(channel As Integer, valorePB1 As Integer, gross As Integer)

 Dim err As Integer
 
  ' Chiama la subroutine prepareev
  prepareev(SND_SEQ_EVENT_PITCHBEND)
  
  Write #p, channel As Integer
  
  Write #p, valorePB1 As Integer           ' questo valore resta a 0
  
  Write #p, gross As Integer
  
  err = snd_seq_event_output(handle, ev)    ' output an event
  printerr("PitchBend = ", err)
  
End


 ---> Scrittura dei dati generali per tutti i messaggi Midi <--- 

Private Sub prepareev(type As Byte)

 Dim i As Integer
 Dim flags, tag As Byte
 
' ripulisce la zona di memoria riservata per la definizione dell’evento
  Seek #p, 0
  For i = 1 To SIZE_OF_SEQEV
    Write #p, 0 As Byte
  Next

  Seek #p, 0
  Write #p, type As Byte
 
' Write #p, flags As Byte
 
' Write #p, tag As Byte

  Seek #p, 3
  Write #p, outq As Byte

' Write #p, ts As Integer  ' timestamp
  
' Write #p, ts As Integer  ' 2^ parte (realtime event)

 Seek #p, 12
' source
 Write #p, id As Byte
 
 Write #p, outport As Byte
 
' dest
 Write #p, SND_SEQ_ADDRESS_SUBSCRIBERS As Byte   ' 254/dclient
 
 Write #p, SND_SEQ_ADDRESS_UNKNOWN As Byte   ' 253/dport

End


' Svuota il buffer in ALSA
 
Public Sub flush()
 Dim err As Integer

  err = snd_seq_drain_output(handle)    ' drain output buffer to sequencer
  Printerr("Flush", err)

End


' ENUM snd_seq_event_type {
'    SND_SEQ_EVENT_SYSTEM = 0, SND_SEQ_EVENT_RESULT, SND_SEQ_EVENT_NOTE = 5, SND_SEQ_EVENT_NOTEON = 6, 
'    SND_SEQ_EVENT_NOTEOFF = 7, SND_SEQ_EVENT_KEYPRESS = 8, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE = 11, 
'    SND_SEQ_EVENT_CHANPRESS = 12, SND_SEQ_EVENT_PITCHBEND = 13, SND_SEQ_EVENT_CONTROL14, SND_SEQ_EVENT_NONREGPARAM, 
'    SND_SEQ_EVENT_REGPARAM, SND_SEQ_EVENT_SONGPOS = 20, SND_SEQ_EVENT_SONGSEL, SND_SEQ_EVENT_QFRAME, 
'    SND_SEQ_EVENT_TIMESIGN, SND_SEQ_EVENT_KEYSIGN, SND_SEQ_EVENT_START = 30, SND_SEQ_EVENT_CONTINUE, 
'    SND_SEQ_EVENT_STOP, SND_SEQ_EVENT_SETPOS_TICK, SND_SEQ_EVENT_SETPOS_TIME, SND_SEQ_EVENT_TEMPO, 
'    SND_SEQ_EVENT_CLOCK, SND_SEQ_EVENT_TICK, SND_SEQ_EVENT_QUEUE_SKEW, SND_SEQ_EVENT_SYNC_POS, 
'    SND_SEQ_EVENT_TUNE_REQUEST = 40, SND_SEQ_EVENT_RESET, SND_SEQ_EVENT_SENSING, SND_SEQ_EVENT_ECHO = 50, 
'    SND_SEQ_EVENT_OSS, SND_SEQ_EVENT_CLIENT_START = 60, SND_SEQ_EVENT_CLIENT_EXIT, SND_SEQ_EVENT_CLIENT_CHANGE, 
'    SND_SEQ_EVENT_PORT_START, SND_SEQ_EVENT_PORT_EXIT, SND_SEQ_EVENT_PORT_CHANGE, SND_SEQ_EVENT_PORT_SUBSCRIBED, 
'    SND_SEQ_EVENT_PORT_UNSUBSCRIBED, SND_SEQ_EVENT_USR0 = 90, SND_SEQ_EVENT_USR1, SND_SEQ_EVENT_USR2, 
'    SND_SEQ_EVENT_USR3, SND_SEQ_EVENT_USR4, SND_SEQ_EVENT_USR5, SND_SEQ_EVENT_USR6, 
'    SND_SEQ_EVENT_USR7, SND_SEQ_EVENT_USR8, SND_SEQ_EVENT_USR9, SND_SEQ_EVENT_SYSEX = 130, 
'    SND_SEQ_EVENT_BOUNCE, SND_SEQ_EVENT_USR_VAR0 = 135, SND_SEQ_EVENT_USR_VAR1, SND_SEQ_EVENT_USR_VAR2, 
'    SND_SEQ_EVENT_USR_VAR3, SND_SEQ_EVENT_USR_VAR4, SND_SEQ_EVENT_NONE = 255 
'  }


Note

[1] Quando è dichiarato un Pointer, Gambas riserva 8 byte tutti per lui (in un sistema a 64 bit), che rappresentano l'indirizzo di memoria contenuto dal Pointer. Quando viene poi manipolato, vengono toccati quegli otto byte. Ma l'unica operazione utile da compiere su un Pointer è la dereferenziazione, che si riferisce ad un'altra area di memoria, alla quale si accede attraverso quell'indirizzo di memoria individuato - come abbiamo detto - dai predetti 8 byte.

Il Pointer risulta così legato a due aree distinte di memoria operative:
- una dove sono scritti otto byte dell'indirizzo di memoria (cioè l'altra) contenente i byte del valore solitamente contenuto da una variabile;
- l'altra ove, come appena detto, sono realmente stipati i byte costituenti il valore contenuto da una variabile.

[2] Qualora volessimo spostare il puntatore interno dello stream ad uno specifico byte, dovremo utilizzare la funzione Seek, la quale esegue - come è noto - uno spiazzamento assoluto puntando dritto al byte di numero definito nella funzione medesima.