IL LINUX OPEN SOUND SYSTEM

         ANTONIO TRINGALI


            maggio 1999
ii
Indice

1 LA     SCHEDA AUDIO E OSS                                                                                       4
  1.1     Modello della scheda audio . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .    4
  1.2     L’interfaccia di programmazione di OSS         .   .   .   .   .   .   .   .   .   .   .   .   .   .    6
  1.3     Inizializzazione di OSS . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .    9
          1.3.1 Gestione degli errori . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
          1.3.2 L’exit handler . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   12
          1.3.3 Il signal handler . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   13
   1.4    Scrittura di codice portabile . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   14
   1.5    Anatomia del driver . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   16
          1.5.1 Scrittura sul buffer DMA . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   17
          1.5.2 Lettura dal buffer DMA . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   18
   1.6    Schede audio ISA e PCI . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
   1.7    File system e device file . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
   1.8    Le versioni di OSS . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   21

2 IL MIXER E LA GESTIONE DEI CANALI                                                                              23
  2.1 Descrizione di /dev/mixer . . . . . . . . . . .                .   .   .   .   .   .   .   .   .   .   .   23
  2.2 I canali del mixer . . . . . . . . . . . . . . . .             .   .   .   .   .   .   .   .   .   .   .   24
      2.2.1 Lettura della configurazione . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   26
      2.2.2 Selezione del canale da campionare . .                   .   .   .   .   .   .   .   .   .   .   .   27
  2.3 Livelli di volume dei canali . . . . . . . . . . .             .   .   .   .   .   .   .   .   .   .   .   28
  2.4 Dipendenza dall’hardware . . . . . . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   29
  2.5 Nuove caratteristiche . . . . . . . . . . . . . .              .   .   .   .   .   .   .   .   .   .   .   30
  2.6 Esempio di programma . . . . . . . . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   31

3 CAMPIONAMENTO E RIPRODUZIONE                                                                                   36
  3.1 I device file audio . . . . . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   36
  3.2 Il buffer audio . . . . . . . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   36
  3.3 Parametri di campionamento . . . . . . . . .               .   .   .   .   .   .   .   .   .   .   .   .   37
      3.3.1 Reset dei parametri . . . . . . . . . .              .   .   .   .   .   .   .   .   .   .   .   .   38
      3.3.2 Pause nelle operazioni . . . . . . . .               .   .   .   .   .   .   .   .   .   .   .   .   39
  3.4 Il campionamento . . . . . . . . . . . . . . .             .   .   .   .   .   .   .   .   .   .   .   .   39
  3.5 La riproduzione . . . . . . . . . . . . . . . .            .   .   .   .   .   .   .   .   .   .   .   .   40
  3.6 Il formato audio . . . . . . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   41

                                          iii
3.6.1 Little e big endian . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   42
          3.6.2 La codifica lineare . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   43
   3.7    Il tempo reale . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   44
          3.7.1 Le capacit` del driver . . . . .
                              a                        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   45
          3.7.2 Gestione del buffer DMA . . .           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   46
          3.7.3 I/O non bloccante . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   48
          3.7.4 La sincronizzazione . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   52
          3.7.5 Accesso diretto al buffer DMA           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   53
   3.8    Il full duplex . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   55
   3.9    Uso di coprocessori . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   56
   3.10   Nuove caratteristiche . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   58
   3.11   Esempio di programma . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   59

4 SINTETIZZATORI E MIDI                                                                                                 64
  4.1 I device file del sequencer . . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .    64
       4.1.1 I chip sintetizzatori . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .    65
       4.1.2 I sintetizzatori MIDI . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .    66
  4.2 Il buffer degli eventi . . . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .    67
  4.3 Lettura della configurazione . . . . . . . . . .                      .   .   .   .   .   .   .   .   .   .   .    69
       4.3.1 Parametri generali . . . . . . . . . . .                      .   .   .   .   .   .   .   .   .   .   .    70
       4.3.2 Le porte MIDI . . . . . . . . . . . . .                       .   .   .   .   .   .   .   .   .   .   .    71
  4.4 Predisposizione dei chip sintetizzatori . . . . .                    .   .   .   .   .   .   .   .   .   .   .    72
       4.4.1 Caricamento degli algoritmi FM . . . .                        .   .   .   .   .   .   .   .   .   .   .    73
       4.4.2 Caricamento delle patch wavetable . .                         .   .   .   .   .   .   .   .   .   .   .    76
       4.4.3 Caricamento delle sysex patch . . . . .                       .   .   .   .   .   .   .   .   .   .   .    81
  4.5 La temporizzazione . . . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .    81
       4.5.1 La sincronizzazione per /dev/music . .                        .   .   .   .   .   .   .   .   .   .   .    83
       4.5.2 La sincronizzazione per /dev/sequencer                        .   .   .   .   .   .   .   .   .   .   .    85
  4.6 Output degli eventi . . . . . . . . . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .    86
       4.6.1 Messaggi di canale . . . . . . . . . . .                      .   .   .   .   .   .   .   .   .   .   .    86
       4.6.2 Messaggi di sistema . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .    89
       4.6.3 Controllo della coda degli eventi . . . .                     .   .   .   .   .   .   .   .   .   .   .    91
  4.7 Formato degli eventi . . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .    92
  4.8 Input degli eventi . . . . . . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .    94
       4.8.1 Eventi per /dev/music . . . . . . . . .                       .   .   .   .   .   .   .   .   .   .   .    95
       4.8.2 Eventi per /dev/sequencer . . . . . . .                       .   .   .   .   .   .   .   .   .   .   .    97
  4.9 Nuove caratteristiche . . . . . . . . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .    97
       4.9.1 SoftOSS . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   100
       4.9.2 OSSlib . . . . . . . . . . . . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .   101
  4.10 Esempio di programma . . . . . . . . . . . . .                      .   .   .   .   .   .   .   .   .   .   .   102

                                           iv
5 IL MIDI A BASSO LIVELLO                                                                                          106
  5.1 Descrizione dei device file MIDI .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   106
  5.2 Lettura dalla porta MIDI . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   107
  5.3 Scrittura sulla porta MIDI . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   109
  5.4 Programmazione della MPU–401         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   109
  5.5 Esempio di programma . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   110

A GENERAL MIDI                                                               121
  A.1 Timbri strumentali . . . . . . . . . . . . . . . . . . . . . . . . . . 121
  A.2 Timbri delle percussioni . . . . . . . . . . . . . . . . . . . . . . . 125




                                       1
Sommario

Questo lavoro si propone come tutorial per la programmazione della scheda audio
tramite l’Open Sound System (OSS) per il sistema operativo Linux, ponendo
particolare enfasi sulle strategie di programmazione pi` opportune per la sintesi
                                                       u
audio in tempo reale.

Capitolo 1 Si introduce un modello “medio” delle varie schede audio presenti
    sul mercato; dopo sono descritte la visione orientata a UNIX che ne ha OSS
    (in particolare alla fine descrivendo il funzionamento interno del driver), la
    sua inizializzazione e la gestione delle uscite da programma e dei segnali in
    un ambiente multiutente e multiprogrammato, con qualche riferimento alla
    scrittura di programmi portabili

Capitolo 2 Il mixer gestisce i canali di ingresso/uscita facenti capo alla scheda
    audio; ` spiegato come scoprire le capacit` di quest’ultima, selezionare i
            e                                    a
    canali e regolare i livelli di volume per campionamento e riproduzione
            `
Capitolo 3 E spiegato come campionare da un qualsiasi canale del mixer e ripro-
    durre i campioni sintetizzati o immagazzinati su un file, eventualmente in
    maniera non bloccante

Capitolo 4 Ci si occupa della programmazione ad alto livello, cio` in maniera
                                                                       e
    indipendente dai dispositivi, del chip sintetizzatore interno alla scheda audio
    e delle porte MIDI

Capitolo 5 Si tratta della programmazione MIDI a basso livello, cio` a livello
                                                                   e
    di I/O di byte dei messaggi
               `
Appendice A E riportata una tabella di corrispondenza fra i preset timbrici
    definiti nello standard General MIDI level 1 e i nomi delle patch wavetable
    per la Gravis UltraSound
INTRODUZIONE ALL’OPEN SOUND SYSTEM

     All’inizio degli anni ’90 Hannu Savolainen scrisse la prima versione di un driver
per la scheda audio Sound Blaster 1.5 sotto Minix–386; nel 1992 ne fece il porting
sotto Linux: era nato il Linux Sound Driver (LSD).
     Al crescere del numero di versione e delle funzionalit` implementate, nonch´
                                                              a                      e
all’aumentare del numero di porting ad altre variet` di UNIX, il driver cambi`
                                                         a                           o
il nome in VoxWare; sfortunatamente era omonimo della VoxWare Incorporat-
ed, quindi per problemi di copyright per un certo periodo rest` il Temporarily
                                                                     o
Anonymous Sound Driver (TASD).
     Poco dopo la 4Front Technologies, con la collaborazione dello stesso Hannu
Savolainen, svilupp` l’Unix Sound System (USS), avente qualche caratteristica in
                       o
pi` rispetto al driver freeware e un maggior numero di schede audio supportate;
   u
lo stesso Savolainen continuava indipendentemente a sviluppare il driver freeware
(il cui codice sorgente era differente da quello di USS), noto ora come USS/Lite.
     In ossequio a POSIX e alla gran variet` di sistemi di tipo UNIX al quale `
                                              a                                      e
stato portato, il nome ` cambiato nuovamente e il driver ` commercializzato dalla
                          e                                  e
4Front Technologies come Open Sound System, mentre nella versione freeware
` noto come OSS/Free. Di questo Savolainen non mantiene pi` il codice: la
e                                                                      u
responsabilit` ` passata a Alan Cox dagli inizi del 1998.
                ae
     D’ora in poi ci si riferir` a questi driver globalmente come OSS, sottinten-
                                a
dendo che ci` che sar` detto vale per entrambi (evidenziando, ove necessario,
                o         a
le differenze); la versione assunta come “riferimento” ` la 3.5.4. A seguire dal
                                                            e
Capitolo 2, la penultima Sezione di ogni Capitolo elenca le differenze di program-
mazione introdotte dalla versione 3.6 in poi e l’ultima Sezione ospita un breve
programma di esempio in C che illustra le caratteristiche di OSS introdotte nel
Capitolo stesso. Delle chiamate alla libreria di OSS elencate in soundcard.h, si
` scelto di riportare solo quelle pienamente supportate.
e
     OSS ` in pratica il primo tentativo di unificare l’architettura di audio digitale
           e
per UNIX: alle capacit` di campionamento e riproduzione (piuttosto articolate
                           a
rispetto a quanto prima disponibile per questo ambiente) sono affiancate le possi-
bilit` del MIDI, con il supporto dell’audio sincronizzato alla riproduzione video.
      a
`
E allo stato attuale un insieme modulare di device driver che garantiscono un’in-
terfaccia di programmazione uniforme e compatibile a livello di codice sorgente
per tutte le piattaforme su cui ` stato portato, tanto che per queste ` valido la
                                   e                                       e
quasi totalit` di ci` che ` scritto nel seguito di questo lavoro.
               a      o      e
     Una panoramica generica delle caratteristiche supportate sia dall’OSS com-
merciale (OSS/Linux) che da OSS/Free ` la seguente:
                                            e

   • supporto di vari formati audio per i campioni (8 e 16 bit signed e unsigned,
     8 bit µ–Law e A-Law, IMA–ADPCM)

   • supporto di canali stereo e mono

   • frequenze di campionamento comprese tra 4kHz e 48kHz

                                          2
• supporto half e full duplex (con apposite schede audio)
   • possibilit` di accesso diretto al buffer audio DMA (per applicazioni con
               a
     richieste temporali pi` stringenti, come i giochi)
                           u
   • possibilit` di accesso indipendente dall’hardware a MIDI e a sintetizzatori
               a
     FM o wavetable sulla scheda audio
   • caricamento delle patch in maniera indipendente dall’hardware
   • supporto per SMPTE/MTC
   • supporto per UART di tipo MP–401, Sound Blaster MIDI e XG MIDI
   • supporto di IBM/Motorola PowerPC (bus ISA e PCI), 386/486/Pentium
     (bus ISA, EISA e PCI), Sun SPARC e DEC Alpha/AXP (bus ISA e PCI)

   Rispetto a OSS/Free, OSS/Linux ha in pi` le seguenti caratteristiche:
                                          u
   • supporto diretto dello standard PnP (senza dover inizializzare precedente-
     mente le schede con il comando isapnp)
   • supporto di un maggior numero di schede audio
   • non si deve ricompilare il kernel ad ogni cambio di configurazione o driver
     (questo ` un modulo separato)
             e
   • supporto delle schede Sound Blaster 16/32/64/AWE in full duplex
   • supporto delle patch wavetable E–mu SoundFont 2.0
   • librerie DirectAudio per l’accesso diretto ai chip sintetizzatori FM e alle
     porte MIDI
si ` scelto di non trattare gli ultimi due punti, in quanto di interesse marginale
   e
per la sintesi in tempo reale.

                    CONVENZIONI TIPOGRAFICHE

Per tutto il testo sono seguite per le parole le seguenti convenzioni:
     corsivo indica una sigla o un termine tecnico rilevante introdotto per la prima
volta
     neretto indica un identificatore o una variabile (con specificato il tipo) per
l’interfaccia di programmazione di OSS o di Linux
     spaziatura fissa indica una parola chiave appartenente alla libreria di
OSS o codice sorgente di esempio; se le parole sono contenute tra parentesi an-
golate, come ad esempio per <valore>, si intende che il programmatore debba
sostituirvi un adeguato valore numerico


                                         3
Capitolo 1

LA SCHEDA AUDIO E OSS

1.1       Modello della scheda audio
Una scheda audio ha diverse funzioni: converte i suoni immagazzinati nel sistema
(ad esempio, su file) dalla forma digitale all’analogica affinch´ li si possa udire
                                                                e
o registrare, converte da opportuni canali di ingresso (Line–In1 , microfono, CD
audio) i segnali da analogico a digitale affinch´ possano essere immagazzinati o
                                                e
manipolati dal computer, pu` consentire essa stessa di creare nuovi suoni tramite
                              o
eventuali sintetizzatori interni.
     Secondo le specifiche MPC2 deve anche essere dotata di una porta MIDI perch´  e
il computer possa controllare strumenti musicali o sintetizzatori esterni con tali
capacit`, invece di generare da s´ i suoni. Il PC diventa in pratica un sequencer,
        a                         e
cio` un direttore d’orchestra allo stato solido; virtualmente non ` pi` di una
    e                                                                e u
memoria e un sistema di messaggi dalla e alla strumentistica, con capacit` di  a
editing.
     L’OSS cerca di offrire una visione idealizzata della scheda audio al program-
matore, nascondendo le differenze tecniche fra le varie schede presenti sul mercato;
essa pu` essere vista come un mixer, di cui il programmatore (il Disc Jockey) ha
        o
possibilit` di controllare ogni canale3 .
           a
     Per la riproduzione (conversione D/A) la sorgente ` da file (brano digitaliz-
                                                         e
zato o MIDI) o i campioni sono creati con un opportuno algoritmo di sintesi, da
CD audio4 (il segnale ` semplicemente spedito alla sezione analogica della scheda
                        e
   1`
      E un ingresso con livelli simili a quelli d’ingresso per un amplificatore HI–FI, utile per
campionare il segnale proveniente da un registratore analogico o da un CD player esterni
    2
      Specifiche rilasciate da Microsoft e Intel nel 1991 per definire una configurazione hardware
minima affinch´ un computer potesse essere definito “multimediale”: si doveva cio` disporre
                e                                                                      e
almeno di un PC 386SX/16MHz con 4MB di RAM e 40MB di hard disk, una VGA a colori, un
mouse, una scheda audio e un CD–ROM player
    3
      Indipendentemente o meno dagli altri; relativamente alle capacit` della scheda audio stessa,
                                                                       a
con il programmatore che pu` interrogare OSS sulle capacit` di quest’ultima
                               o                                a
    4
      Non ` presa in considerazione la programmazione del drive CD in questo tutorial, solo come
           e
si fa a campionare o riprodurre il segnale che ` presente sull’eventuale canale CD Audio In della
                                                e
scheda audio

                                                4
CAPITOLO 1. LA SCHEDA AUDIO E OSS                         1.1. MODELLO DELLA SCHEDA AUDIO



audio senza che sia stato campionato prima), da Line–In e da microfono. In cam-
pionamento (conversione A/D) si pu` acquisire dagli ingressi CD Audio, Line–In
                                       o
e microfono o si possono acquisire eventi MIDI.
    La coppia di convertitori A/D e D/A viene spesso chiamata PCM, DSP,
CODEC o ADC/DAC ; nella Tabella seguente sono elencate le massime frequenze
di campionamento5 per le varie generazioni di schede audio che OSS supporta (la
minima ` sempre intorno ai 5 kHz); la fc ` generata dividendo l’alta frequenza di
         e                                  e
un oscillatore di riferimento, per cui non ` possibile ottenere tutte le frequenze
                                              e
dell’intervallo: le differenze di qualche percento dovrebbero essere ignorate perch´
                                                                                  e
solitamente non avvertibili (OSS cerca di ottenere dalla scheda audio la frequenza
pi` vicina a quella richiesta). La risoluzione di campionamento pu` essere di 8 o
  u                                                                  o
16 bit, in mono o stereo.


                    Generazione         Max fc        Note
                        1a            22.05 kHz       in riproduzione
                                      11.025 kHz      in campionamento
                          2a           44.1 kHz       mono
                                      22.05 kHz       stereo
                          3a           44.1 kHz       qualit` audio CD
                                                            a
                                        48 kHz        qualit` DAT
                                                            a
                          4a            96 kHz        Ultra Hi–Fi


    Oltre alle succitate possibilit` di sintesi FM6 e di gestione dei dispositivi MI-
                                    a
DI, alcune schede consentono la wavetable synthesis e la possibilit` di caricare
                                                                         a
campioni di uno strumento (patch) in una speciale memoria della scheda stessa.
    In figura 1.1 ` evidenziata la visione che possiamo dare della nostra scheda
                   e
audio idealizzata come output mixer per quanto riguarda la riproduzione, che ben
si conf` alle caratteristiche pi` comuni riscontrabili nelle schede audio attualmente
       a                        u
presenti sul mercato (1999). In figura 1.2 ` invece schematizzato l’input mixer a
                                              e
cui fare riferimento per quanto riguarda il campionamento.
    Infatti dentro una scheda audio possono esserci realmente due mixer che gestis-
cono separatamente i casi di campionamento e riproduzione, ma OSS gestisce
automaticamente il passaggio dall’uno all’altro in base ai comandi del program-
matore di lettura o scrittura dalla/alla scheda audio. In particolare, se tali attivit`
                                                                                      a
sono implementate dalla scheda audio come mutuamente esclusive ` definita half
                                                                        e
duplex, mentre ` definita full duplex se possono aver luogo contemporaneamente.
                 e

   5
    Alcune vecchie schede permettono di scegliere solo fra frequenze di campionamento fisse:
11025 Hz, 22050 Hz, 32000 Hz e 44100 Hz
   6`
    E sfruttata la tecnica degli operatori per produrre pi` voci: i chip “classici” sono lo Yamaha
                                                          u
OPL–2 (a due operatori, nove voci con timbri non realistici) e OPL–3 (a quattro operatori,
diciotto voci): aumentando il numero di operatori per voce migliora la qualit` della sintesi
                                                                                  a

                                                5
1.2. L’INTERFACCIA DI PROGRAMMAZIONE DI OSS                                                            CAPITOLO 1. LA SCHEDA AUDIO E OSS




                                                                                                            Memoria del computer
                                                                                                              o Hard-Disk
            LINE-OUT
                                       ALTOPARLANTI



                                                                                                   L    R                      L       R    L       R

        L      R                              L         R   L       R    L       R                             STEREO   MONO


                              STEREO   MONO


                   TREBLE




                       BASS
                                                                                                   RECORD                      CD Audio     LINE-IN      MIC
        MASTER                                    DAT       CD Audio     LINE-IN      MIC                      MIDI ON/OFF
                                                                                                   VOLUME                          ON/OFF       ON/OFF   ON/OFF
                              MIDI ON/OFF
        VOLUME                                    ON/OFF        ON/OFF       ON/OFF   ON/OFF




    Figura 1.1: Modello come mixer della
                                         Figura 1.2: Modello come mixer
    scheda audio in riproduzione
                                         della scheda audio in campiona-
                                         mento

1.2                L’interfaccia di programmazione di OSS
Per gestire il “mixer virtuale” delle figure 1.1 e 1.2 OSS sfrutta la visione orientata
al file–system che Linux ha di ogni device 7 . I canali del mixer, campionamento
e riproduzione possono quindi essere gestiti manipolando degli speciali file che
si trovano nella directory /dev tramite le primitive di sistema open(), ioctl(),
read(), write() e close().
    In figura 1.3 sono evidenziati i device file relativi alla manipolazione dei vari
canali per la riproduzione, mentre in figura 1.4 c’` lo schema equivalente per il
                                                      e
campionamento.
    Quest’organizzazione ` conveniente, poich´ in tal modo sono schermate sia le
                           e                     e
complessit` dell’hardware e del software sottostante (la scheda audio, ma anche
            a
la gestione del DMA, della memoria virtuale e del multitasking), sia le diversit`    a
fra le varie schede audio in commercio.
    Si pu` ora procedere alla descrizione di ogni device file:
          o

/dev/mixer Questa ` l’interfaccia di accesso alle funzioni del mixer e pu`
                        e                                                    o
    essere un link simbolico a /dev/mixer0; se ` presente un secondo mixer, ci
                                               e
    si riferisce ad esso come /dev/mixer1

/dev/dsp Identifica il DSP della scheda (per default con codifica lineare 8 bit
    unsigned) e pu` essere un link simbolico a /dev/dsp0; in genere ` presente
                  o                                                 e
   7`
     E un’interfaccia a un dispositivo hardware (dischi, linee seriali, etc.) o a “entit`” a cui
                                                                                        a
non corrisponde un vero e proprio dispositivo hardware (memoria di sistema, kernel, etc.); a un
device fisico — come la scheda audio — corrisponde un device driver — come OSS — che ha
il compito di pilotarlo

                                                                                               6
CAPITOLO 1. LA SCHEDA AUDIO E OSS                                                           1.2. L’INTERFACCIA DI PROGRAMMAZIONE DI OSS



                                                                         = Link simbolico




                                                                                                                         OSS driver
                                  OSS driver
       Memoria del computer
         o Hard-Disk
                                                             /dev/dsp     /dev/dsp0
                                                                          /dev/dsp1                  MIDI in
                                                                                                                                              /dev/dsp
                                                         /dev/dspW        /dev/dspW0
                                                                                                                                              /dev/dspW
        MIDI file (out)                                                   /dev/dspW1
                                                                                                                                              /dev/audio
                                                        /dev/audio        /dev/audio0
                                                                                                   CD Audio                                   /dev/music
                                                                          /dev/audio1
                                                                                                                                              /dev/sequencer
          CD Audio                                                                                                                            /dev/midi00    Memoria del computer
                                                                                                                                              /dev/midi01      o Hard-Disk
                              /dev/sndstat
                                                                                                                   /dev/sndstat               /dev/midi02
                              /dev/sndproc                                                        Line-In
                                                             /dev/music                                            /dev/sndproc               /dev/midi03
         Line-In                                             /dev/sequencer
                                                             /dev/midi00
                                                             /dev/midi01                                             MIXER
                               MIXER                         /dev/midi02
                                                             /dev/midi03                             Mic
            Mic



                   /dev/mixer                  /dev/mixer0                                                  /dev/mixer                /dev/mixer0
                                               /dev/mixer1                                                                            /dev/mixer1




       Figura 1.3: Visione della scheda au- Figura 1.4: Visione della scheda
       dio in riproduzione che OSS d` al audio in campionamento che OSS
                                       a
       programmatore                        d` al programmatore
                                             a


           anche /dev/dsp1, che pu` identificare un secondo DSP o lo stesso con una
                                  o
           diversa funzione

/dev/dspW Se presente (dipende dalla versione di OSS), si riferisce al DSP con
    codifica lineare 16 bit signed e little endian8 ; pu` essere un link simbolico
                                                       o
    a /dev/dspW0 ed eventualmente ` presente anche /dev/dspW1
                                      e

              `
/dev/audio E presente per limitata compatibilit` con le workstation Sun (non
                                                 a
    sono supportati cambiamenti da 8 kHz/mono/8 bit) e utilizza la codifica
    µ–Law9 ; ` disponibile in mutua esclusione con /dev/dsp e generalmente `
             e                                                             e
    un link a /dev/audio0, potendo essere presente anche /dev/audio1

/dev/music Permette di accedere al sintetizzatore interno alla scheda audio e
    alle porte MIDI in maniera indipendente dal dispositivo (sono trattati allo
    stesso modo dal punto di vista dell’interfaccia di programmazione), con
    modalit` di temporizzazione piuttosto articolate; pu` essere presente il link
            a                                           o
    simbolico ad esso /dev/sequencer2 (obsoleto)

                  `
/dev/sequencer E un device file a pi` basso livello di /dev/music, rispetto al
                                      u
    quale ha capacit` limitate di temporizzazione, sincronizzazione e gestione
                    a
    automatica del chip sintetizzatore interno alla scheda audio
   8
     Little endian e big endian si riferiscono a come sono conservati i campioni in memoria dalla
CPU: per il primo l’indirizzo del dato corrisponde all’indirizzo del byte meno significativo (Intel,
Alpha), per il secondo al byte pi` significativo (Motorola, Sparc, PowerPC, HP–PA)
                                   u
   9
     Un campione a 12 o 16 bit ` compresso logaritmicamente a 8 bit: OSS in riproduzione
                                    e
non effettua l’operazione opposta, ma converte in un campione a 8 bit lineare prima di inviare
al device audio (sono introdotti un overhead per il calcolo e della distorsione); ` un formato
                                                                                     e
derivante dalla tecnologia telefonica digitale

                                                                                              7
1.2. L’INTERFACCIA DI PROGRAMMAZIONE DI OSS     CAPITOLO 1. LA SCHEDA AUDIO E OSS



                `
/dev/midi00 E un’interfaccia a basso livello al canale MIDI, orientata ai carat-
    teri come tty (raw mode) e indirizzata ad applicazioni che non richiedono
    una sincronizzazione in tempo reale come i sequencer (pu` essere usata
                                                                 o
    per inviare sysex, per caricare campioni sugli strumenti o per effettuare il
    dump dei canali); se presenti, possono essere gestite altre porte MIDI con
    /dev/midi01, /dev/midi02 e /dev/midi03

/dev/sndproc Rappresenta l’interfaccia interna di accesso a un eventuale co-
    processore presente sulla scheda audio; pu` essere una soluzione tempo-
                                              o
    ranea, da eliminare in seguito

/dev/sndstat Questo, a differenza degli altri, ` un device file a sola lettura:
                                                   e
    stampa informazioni diagnostiche riguardo la configurazione di OSS in for-
    ma leggibile agli umani, ma se ne sconsiglia l’utilizzo da parte dei programmi
    perch` in futuro il formato delle informazioni da esso fornite potrebbe cam-
          e
    biare; non ci si far` pi` riferimento d’ora in poi. L’output ha una sezione
                         a u
    per ogni categoria di dispositivi, e questi sono numerati nell’ordine in cui il
    driver li inizializza (che non ` fisso, per cui ` meglio non fare assunzioni a
                                   e               e
    priori); un esempio del suo utilizzo ` il seguente:
                                          e

     ~>cat /dev/sndstat
     Sound Driver:3.5.4-960630
     Kernel: Linux papo 2.0.32 #1 Wed Nov 19 00:46:45 EST 1997 i486
     Config options: 0

     Installed drivers:

     Card config:

     Audio devices:
     0: Sound Blaster 16 (4.13)

     Synth devices:
     0: Yamaha OPL-3

     Midi devices:

     Timers:
     0: System clock

     Mixers:
     0: Sound Blaster

   OSS consente di avere pi` schede audio installate nel computer, il che si
                           u
traduce in un maggior numero di device file indirizzabili, elencati nell’output

                                        8
CAPITOLO 1. LA SCHEDA AUDIO E OSS                        1.3. INIZIALIZZAZIONE DI OSS




di /dev/sndstat: l’esempio sopra rivela che il computer ha solo /dev/dsp0,
ma in generale con pi` DSP li si potrebbe indirizzare operando su /dev/dspn;
                     u
analogamente per il mixer su /dev/mixern, etc.


1.3      Inizializzazione di OSS
Il minimo insieme di header file da includere ` rappresentato da stdio.h (per le
                                               e
funzioni di libreria tipo printf() e perror()), unistd.h, fcntl.h (per open(),
ioctl(), read(), write() e close()) e sys/soundcard.h (le definizioni vere e
proprie per la libreria di OSS).
    Adesso si riporter` uno scheletro di codice C per l’apertura di un device file di
                       a
OSS (nell’esempio ` /dev/dsp, ma potrebbe essere /dev/mixer, . . . ); nei succes-
                     e
sivi Capitoli lo si dar` come sottinteso man mano che si introducono le primitive
                       a
per una gestione vieppi` sofisticata della scheda audio.
                          u

#include   <stdio.h>
#include   <unistd.h>
#include   <fcntl.h>
#include   <sys/soundcard.h>

int main()
{
   int dspfd;                        /* File descriptor per /dev/dsp */

    dspfd = open("/dev/dsp", O_WRONLY);      /* Apre il device                */
    if (dspfd == -1) {
        perror("/dev/dsp");                 /* Gestione errore                */
        exit(-1);
    }
       /* Qui ci puo’ andare tutto il codice per sfruttare il                 */
       /* device file, con le ioctl() necessarie a configurare                */
       /* il driver poste prima delle read() o write()                        */

    close(dspfd);                            /* Chiusura del device file */
    return 0;
}

   Il secondo argomento di open() ` la modalit` di accesso al device file, che nel
                                    e         a
caso di OSS pu` essere una fra le seguenti:
               o

O RDONLY Accesso in sola lettura (read() o ioctl())

O WRONLY Accesso in sola scrittura (write() o ioctl())

O RDWR Accesso in lettura/scrittura (read(), write() o ioctl())

                                         9
1.3. INIZIALIZZAZIONE DI OSS                   CAPITOLO 1. LA SCHEDA AUDIO E OSS




    Per read() e write() i flag O_NDELAY o O_NONBLOCK, affinch´ le operazioni di
                                                              e
I/O sul device file non blocchino il processo chiamante, non hanno effetto: se il
processo effettua una read() e i campioni richiesti non sono ancora disponibili,
questo viene messo in wait finch´ l’operazione ` completata (analogamente con
                                e               e
una write(), se il buffer del driver non ha spazio sufficiente per i campioni che
si vogliono riprodurre).
    Per la open() il funzionamento ` sempre del tipo O_NDELAY; se fallisce il
                                     e
file descriptor ` posto uguale a -1 e la macro errno ` impostata a un oppor-
                e                                     e
tuno valore (questa ` messa a disposizione del programmatore per mezzo di
                      e
#include <errno.h>).


1.3.1     Gestione degli errori
I codici di errore pi` comuni riportati da errno sono i seguenti:
                     u

ENOENT Il device file che si ` tentato di aprire non ` presente in /dev
                            e                       e

ENODEV Esiste il device file in /dev, ma il driver non ` stato caricato dal
                                                       e
   kernel (si pu` controllare con cat /dev/sndstat o dmesg | more)
                o

ENOSPC Esiste il device file in /dev, ma il driver non ` stato in grado di
                                                            e
   allocare la memoria per il buffer DMA tramite sound_mem_init() durante
   il boot del sistema; il modulo del driver dovrebbe essere uno dei primi ad
   esser caricato dal kernel, in modo da garantire tale operazione anche in caso
   di poca memoria installata

ENXIO Esiste il device file in /dev e il driver ` presente nel kernel, ma non
                                                   e
   esiste il dispositivo hardware che si tenta di indirizzare (ad esempio, perch´
                                                                                e
   la configurazione del driver non corrisponde all’hardware audio)

EINVAL Uno degli argomenti della chiamata a una funzione non ha un valore
    valido

EBADF Il file descriptor non si riferisce a un device file aperto, una read() `
                                                                            e
   stata rivolta a un device file aperto con O_WRONLY o una write() ` stata
                                                                       e
   rivolta a un device file aperto con O_RDONLY

EBUSY Solo un processo alla volta pu` gestire /dev/dspn e /dev/audion, per
                                        o
   cui si verifica se un altro processo (anche appartenente allo stesso utente)
   tenta di accedervi (si verifica anche se l’IRQ o il canale DMA sono occupati);
   pu` essere gestito dal programma tentando di riaprire il device file che ha
      o
   causato l’errore dopo qualche tempo, ma non ` garantito che esso divenga
                                                     e
   mai disponibile
        `
EINTR E ritornato da read() o write() qualora queste siano risultate bloccan-
   ti e durante lo stato di wait il processo utente abbia ricevuto un signal()

                                       10
CAPITOLO 1. LA SCHEDA AUDIO E OSS                       1.3. INIZIALIZZAZIONE DI OSS




EAGAIN La risorsa a cui si ` cercato di accedere ` temporaneamente non
                               e                       e
   disponibile (ad esempio, si ` cercato di scrivere su un buffer pieno o leggere
                               e
   da un buffer vuoto con il device file aperto in O_NONBLOCK)

EACCES Per i device /dev/dspn e /dev/audion l’accesso ` consentito solo al
                                                           e
   root (a meno che il sistema sia stato configurato in altro modo), e questo
   errore si verifica se un utente normale cerca di accedere a tali device file;
   questa ` una misura di sicurezza per impedire che, ad esempio, qualora il
           e
   computer sia connesso in rete e abbia un microfono qualcuno possa ascoltare
   remotamente le conversazioni che si svolgono nella stanza ove si trova il
   computer

   Un error handler un po’ pi` sofisticato potrebbe rassomigliare a:
                             u

if ((dspfd = open("/dev/dsp2", O_WRONLY)) == -1) {
  switch (errno) {
    case ENOENT: perror("/dev/dsp2");
                 exit(-1);
    case EBUSY: close(dspfd);    /* Aspetta un po’ per riaprire */
                 sleep(10);
                 if (dspfd = open("/dev/dsp2", O_WRONLY)) == -1) {
                     perror("/dev/dsp2");
                     exit(-1);
                 }
                 break;
    default: perror("Altro tipo di errore");
              fprintf(stderr, "Errore numero: %dn", errno);
              exit(-1);
  }
}

   Per quanto invece riguarda il seguito di questo lavoro, si demander` la gestione
                                                                      a
degli errori a una semplice routine del tipo:

void errore(const char *msgerr)
{
    perror(msgerr);
    exit(-1);
}

    Sarebbe corretto mettere un error handler non solo dopo una open(), ma an-
che dopo read() o write() per verificare se siano stati letti o scritti il numero
corretto di byte; tuttavia nella sintesi in tempo reale ci` tende a rappresentare
                                                           o
cicli di CPU sprecati. Invece dopo una ioctl() conviene controllare quasi ob-
bligatoriamente il valore ritornato dal driver nell’ultimo dei suoi argomenti, per
vedere cosa si riesce a ottenere rispetto a quanto richiesto dal programmatore.

                                        11
1.3. INIZIALIZZAZIONE DI OSS                         CAPITOLO 1. LA SCHEDA AUDIO E OSS




   Un altro buon accorgimento di programmazione ` di installare un exit handler
                                                   e
e/o un signal handler subito dopo una open() riuscita: il primo pu` essere utile
                                                                   o
                                                      10
per operazioni di routine alla chiusura del programma (regolare o in seguito a
exit()), il secondo pu` gestire i segnali impostati da altri processi, dal kernel
                       o
o dal processo stesso. Per una dettagliata descrizione di questi argomenti si
veda [10].

1.3.2      L’exit handler
Un exit handler richiede la funzione di libreria atexit(), disponibile in segui-
to a #include <stdlib.h>. Essa registra le funzioni di tipo void f() date in
argomento come exit handler (max 32), venendo richiamate nell’ordine inverso
rispetto a quello con cui sono state registrate; atexit() restituisce 0 se la regis-
trazione ` stata possibile, altrimenti -1 con errno==ENOMEM (memoria insufficiente
          e
per aggiungere la funzione).
    Nell’esempio seguente si dimostra l’utilizzo di atexit() chiudendo un device
file all’uscita dal programma (anche se ci` non ` strettamente necessario per
                                               o     e
quanto prima affermato):

#include    <stdio.h>
#include    <stdlib.h>
#include    <unistd.h>
#include    <fcntl.h>
#include    <sys/soundcard.h>

int dspfd;                                             /* Variabile globale */

void Messaggio()                        /* Chiamata per prima */
{
   puts("Premi <Invio> per chiudere /dev/dsp...");
   getchar();
}

void ChiudiTutto()                                   /* Chiamata per ultima */
{
   close(dspfd);
   puts("Fine del programma");
}

void errore(const char *msgerr)                   /* Gestione degli errori */
{
    perror(msgerr);
  10
    Linux svuota i buffer di I/O e chiude automaticamente i device file rimasti eventualmente
aperti prima che il processo termini, tramite librerie a livello utente o tramite kernel

                                            12
CAPITOLO 1. LA SCHEDA AUDIO E OSS                              1.3. INIZIALIZZAZIONE DI OSS




         exit(-1);
}

int main()
{
   if ((dspfd = open("/dev/dsp", O_RDONLY)) == -1)
       errore("/dev/dsp");

         if (atexit(ChiudiTutto) == -1)             /* Registrata per prima */
             errore("ChiudiTutto()");
         if (atexit(Messaggio) == -1)               /* Registrata per ultima */
             errore("Messaggio()");

         return 0;                                  /* Uscita dal programma */
}

    L’exit handler ` richiamato in seguito ad exit(), abort()11 , return o alla
                   e
consegna di un segnale la cui azione di default ` di uccidere il processo; l’output
                                                e
del programma sarebbe:

Premi <Invio> per chiudere /dev/dsp...

Fine del programma

   Per evitare la chiamata all’exit handler lo standard POSIX.1 (1990) prevede
_exit(), che inoltre evita lo svuotamento dei buffer prima della chiusura dei file.


1.3.3         Il signal handler
Un signal handler funziona in modo molto simile ad un exit handler: previo
#include <signal.h>, si usa la funzione di libreria signal() per registrare delle
funzioni (non di libreria) che hanno il compito di reagire a segnali provenienti da
altri processi o dallo stesso processo12 (rispettivamente generati tramite le funzioni
di libreria kill() e raise()).
    In signum.h sono definiti una trentina di segnali che si conformano ad ANSI,
POSIX, System V e BSD 4.2, per i quali esistono delle disposizioni di default
(ci` equivale a signal(<segnale>, SIG_DFL)); se si vuole ignorare un partico-
   o
lare segnale si pu` porre nel codice signal(<segnale>, SIG_IGN). La signal()
                   o
ritorna il valore precedente del signal handler o SIG_ERR se si verifica un errore.
    11
     Viene impostato un segnale SIGABRT al processo chiamante: non ` ignorabile o bloccabile
                                                                      e
da un signal handler
  12
     Un segnale ` definibile come un’interruzione asincrona software nel flusso di un processo:
                e
esso ` “impostato” dal processo che lo genera ed ` “consegnato” al processo che lo riceve; il
     e                                              e
modo in cui quest’ultimo reagisce al segnale si chiama “disposizione”

                                             13
1.4. SCRITTURA DI CODICE PORTABILE             CAPITOLO 1. LA SCHEDA AUDIO E OSS




   Per esempio, se si vuole che il processo reagisca a un segnale di interruzione
(SIGINT) e a un segnale di terminazione del programma (SIGTERM) con una
disposizione diversa dalla predefinita, all’inizio di main() si potr` porre:
                                                                   a
signal(SIGINT, SignalHandler);          /* Le funzioni possono essere */
signal(SIGTERM, SignalHandler);         /* uguali o diverse           */
ove SignalHandler ` una funzione del tipo:
                  e
void SignalHandler(int segnale) /* Il segnale invocante   */
{                               /* e’ passato all’handler */
    switch (segnale) {
        case SIGINT:
        case SIGTERM: puts("SIGINT o SIGTERM");
                       exit(0);
        default:
    }
}
il processo relativo termina se da shell di comando si digita kill -s SIGINT <PID>
o kill -s SIGTERM <PID>, ove <PID> ` l’identificatore del processo (visualizz-
                                            e
abile tramite il comando ps).
    Sono da tenere presenti i seguenti fatti:
   • non possono essere variate le disposizioni di SIGKILL e SIGSTOP (signal()
     ritorna EINVAL)

   • il comportamento di un programma ` indefinito se sono ignorati SIGFPE,
                                      e
     SIGILL o SIGSEGV

   • ignorare il segnale derivante dalla divisione intera per zero ha un compor-
     tamento indefinito, che potrebbe condurre a un blocco del computer

   • per certi segnali la disposizione di default implica la terminazione del pro-
     cesso e il salvataggio dell’area dati e heap in un file core a fini di debug;
     altri provocano la semplice terminazione del processo o sono ignorati

   • a differenza di BSD, Linux non ridispone il signal handler a SIG_DFL dopo
     la consegna di un segnale
   Si veda man 7 signal per una dettagliata descrizione dei segnali e della loro
disposizione di default, e in ogni caso [10] per una gestione pi` sofisticata.
                                                                u


1.4     Scrittura di codice portabile
Di seguito sono elencati alcuni consigli per la scrittura di programmi che siano
portabili sotto i vari sistemi operativi per cui sia stato portato anche OSS:

                                       14
CAPITOLO 1. LA SCHEDA AUDIO E OSS               1.4. SCRITTURA DI CODICE PORTABILE




   • Come si vedr` nei successivi Capitoli, conviene usare delle specifiche macro
                   a
     per impostare dei parametri, ad esempio del mixer, tramite ioctl() (queste
     schermano i dettagli dell’implementazione dei parametri per le future ver-
     sioni del driver); in particolare bisognerebbe controllare se ` adeguato il
                                                                    e
     valore ritornato nell’ultimo argomento (per le chiamate che lo prevedono)

     `
   • E meglio riferirsi a un link simbolico per un device piuttosto che utilizzare un
     riferimento assoluto, ad esempio usando /dev/dsp al posto di /dev/dsp0;
     ci` d` flessibilit` all’utente per poter far puntare i link simbolici ad altri
       o a            a
     device file, se questi garantiscono migliori risultati (i programmi dovrebbero
     sfruttare i nomi “veri” solo se resi facilmente configurabili)

   • Non bisogna sfruttare delle caratteristiche non documentate (in quanto
     obsolete — scompariranno in futuro — o non ancora ben testate)

   • L’appesantimento di un programma con caratteristiche al di fuori dell’essen-
     ziale o con “trucchi” pu` compromettere la compatibilit` del codice con le
                              o                             a
     future versioni del driver

   • Se si utilizza la risoluzione a 16 bit bisogna fare attenzione che la CPU
     memorizzi i campioni come il DSP della scheda audio, cio` coincida per
                                                                   e
     entrambi la codifica big endian o little endian; in tal senso bisogna evitare
     di accedere ai campioni a 16 bit ciecamente come signed short

   • Non bisogna fidarsi delle impostazioni di default di un device (anche perch`
                                                                               e
     potrebbero essere state modificate da un precedente processo); ad esempio,
     anche se il default per /dev/dsp ` 8 kHz/8 bit unsigned/mono, molte schede
                                      e
     non supportano la frequenza di campionamento di 8 kHz e si corre il rischio
     di ottenere solo rumore con le future schede audio a 24 bit; analogamente,
     non bisogna assumere per /dev/sequencer il clock di default di 100 Hz
     (Linux/Alpha ha un clock di 1024 Hz), mentre non ci sono valori di de-
     fault per /dev/music (bisogna sempre impostare per primi i parametri di
     temporizzazione)

   • Non si devono scrivere programmi che funzionano solo a 16 bit, poich´ molte
                                                                         e
     schede vecchie sono a 8 bit: campioni da 16 bit riprodotti su queste danno
     luogo solo a rumore ad alto volume

   • Non si deve dare per scontato che per ogni scheda audio ci sia /dev/mixer
     (non lo possiedono le pi` vecchie, o quelle non ancora pienamente support-
                             u
     ate, o le schede audio completamente digitali); non tutti i mixer hanno un
     controllo di master volume (ma se ce l’hanno bisogna tenere presente che
     questo influenza il volume di tutti i canali), inoltre si deve sempre testare
     la presenza di un canale prima di cercare di indirizzarlo (ad esempio, non
     tutte le schede possiedono un sintetizzatore interno e/o una porta MIDI)

                                        15
1.5. ANATOMIA DEL DRIVER                              CAPITOLO 1. LA SCHEDA AUDIO E OSS




   • Non si deve usare il full duplex senza prima controllare che la scheda audio
     supporti tale modalit`a


   • I device audio non devono essere tenuti aperti quando non sono richiesti,
     altrimenti altri programmi non vi possono accedere; in tal senso un pro-
     gramma dovrebbe gestire flessibilmente le situazioni di EBUSY, ad esempio
     riprovando ad accedere dopo qualche tempo al device file



1.5       Anatomia del driver
OSS sfrutta il Direct Memory Access (DMA) per trasferire i campioni dalla scheda
audio a un’opportuna area di RAM e viceversa: questa in genere non coincide
con il buffer del processo che li elabora, per cui il driver deve copiare i campioni
dal/al buffer DMA a/da quest’ultimo13 .
    Nei PC–compatibili della copia se ne occupa la CPU, dal momendo che il
DMA Controller (DMAC) compatibile Intel 8237 ha dei pesanti limiti: non pu`          o
effettuare copie fra le porte di I/O o fra memoria e memoria; inoltre il buffer
DMA deve risiedere al di sotto dei primi 16 MB di RAM per le schede audio
ISA (non per le PCI), poich´ questo ` il limite di indirizzamento del bus, e deve
                              e         e
essere un blocco di memoria non frammentato che inizia e finisce nella stessa
pagina DMA. Quest’ultima ha dimensione di 64 kB per i canali 0÷3 a 8 bit e
128 kB per i canali 5÷7 a 16 bit: ci` rende difficile usare direttamente il buffer
                                       o
locale del processo come buffer DMA con le schede ISA e pi` di 16 MB, poich´
                                                                 u                   e
dovrebbe risiedere al di sotto del limite dei 16 MB; tuttavia i nuovi controller nelle
periferiche bypassano l’Intel 8237 completamente (fly–by), per cui in particolari
condizioni si pu` arrivare a mappare il buffer DMA all’interno dell’area dati del
                 o
processo (ci` ` sempre possibile con le schede PCI).
            oe
    L’elaborazione dei campioni da parte del processo deve avvenire almeno un
po’ pi` velocemente del ritmo al quale il DMAC trasferisce i campioni, affinch´
       u                                                                             e
non ci siano pause in fase di campionamento o riproduzione. Se ci` si verifica
                                                                         o
bisogner` usare una frequenza di campionamento inferiore all’attuale, o usare un
         a
formato audio pi` “compatto” per i campioni, in modo da ridurre la quantit` di
                  u                                                               a
byte trasferiti dal DMAC.
    Linux ha il “problema” di essere multiutente e multiprogrammato, per cui
i processi competono per l’utilizzo della CPU e un processo a pi` alta pri-
                                                                          u
orit` potrebbe porre il processo che sfrutta i servizi di OSS in stato di wait
    a
per diversi ms: in tal modo il tempo che questo ha per elaborare i campioni

  13
    Alcune schede possono auto–iniziare il trasferimento senza attendere risposta dal driver,
usando il DMAC in modalit` auto–restart (ci` non ` supportato da tutti i sistemi operativi, ad
                          a                 o      e
esempio da BSD)

                                             16
CAPITOLO 1. LA SCHEDA AUDIO E OSS                                    1.5. ANATOMIA DEL DRIVER




diminuisce. In pratica ci deve essere abbastanza spazio nel buffer DMA per
garantire l’operativit` per il tempo di wait14 .
                      a
   OSS gestisce il buffer DMA con la tecnica del multi–buffering: in pratica
questo ` diviso in frammenti di uguale dimensione, per default calcolata dal driver
        e
in modo tale che la latenza15 sia attorno a 0.5s per la riproduzione e attorno a 0.1s
per il campionamento. In tal modo ` possibile aumentare la dimensione del buffer
                                     e
senza influire sulla latenza stessa poich´ il DMAC lavora solo su un frammento
                                         e
per volta, mentre l’applicazione legge o scrive sul resto del buffer.

   memoria
                    wait                                                             Scheda audio
                                        Kernel

         Processo
                                                 1111
                                                 0000         DMAC

                                                 1111
                                                 0000
                                                                     bus ISA o PCI
                                       OSS
        buffer audio                  driver
                           read() o
                            write()


                                                 buffer DMA




                       Figura 1.5: Schema di utilizzo del multi–buffering



1.5.1        Scrittura sul buffer DMA
In fase di riproduzione, quando il programma chiama write() per la prima volta
dopo l’apertura del device, si verificano i seguenti eventi:

    • il driver programma la scheda audio con i parametri di campionamento
      predisposti (risoluzione, numero di canali e frequenza)

    • di default ` calcolata la dimensione adeguata per un frammento, se tramite
                 e
      un’opportuna chiamata ioctl() non se ne ` stabilita un’altra
                                                  e

    • viene iniziato il riempimento del primo frammento del buffer con i dati
      passati dalla write()

    • se il primo frammento ` stato riempito completamente, il DMAC ne inizia
                               e
      il trasferimento alla scheda audio
  14
     Alcune schede dispongono di RAM locale per la riproduzione, ma prima che questa possa
avvenire i campioni devono esservi trasferiti (per la Gravis UltraSound ci sono 256 kB per
canale, ovvero sono “coperti” 2.9 s di suono continuo in modalit` 16 bit/stereo/44.1 kHz)
                                                                a
  15
     La latenza ` il tempo che il processo deve aspettare per avere accesso a un frammento
                e
in campionamento o perch´ questo venga suonato in riproduzione quando il buffer ` pieno;
                            e                                                          e
essa dipende dal data rate, che ` la quantit` di dati che il DMAC deve trasferire nell’unit` di
                                e           a                                              a
tempo (per esempio, con un campionamento 16 bit/stereo/44.1 kHz il data rate ` 2 · 2 · 44.1 =
                                                                                e
176.4 kB/s), nonch´ dalla dimensione del frammento
                    e

                                                     17
1.5. ANATOMIA DEL DRIVER                       CAPITOLO 1. LA SCHEDA AUDIO E OSS




   • il driver copia il resto dei dati nel buffer, eventualmente riempiendo altri
     frammenti; se tutti i frammenti del buffer sono stati riempiti il processo
     relativo al programma che ha chiamato la write() ` messo in stato di wait
                                                          e
     finch´ non ` libero almeno un frammento (condizione di overrun)
           e     e

    Alle successive chiamate di write() i dati sono immagazzinati nel buffer
secondo la disponibilit` di frammenti liberi.
                        a
    L’overrun si verifica normalmente per un processo che scriva i campioni nel
buffer pi` velocemente di quanto vengano riprodotti. Se al contrario il processo
         u
` leggermente pi` lento a scrivere i campioni rispetto alla velocit` con la quale
e                u                                                  a
sono riprodotti si verifica la condizione di underrun, per uno dei seguenti motivi:

   • l’applicazione ` troppo lenta nell’elaborazione dei campioni (perch´ la CPU
                    e                                                   e
     ` troppo lenta rispetto al data rate richiesto o ci sono troppi processi in
     e
     esecuzione che competono per la CPU)

   • ci sono leggere variazioni nel tempo di CPU ricevuto (un’applicazione gen-
     eralmente ben funzionante pu` occasionalmente andare in underrun)
                                     o

   • l’applicazione tenta di lavorare troppo in tempo reale (frammenti pi` piccoli
                                                                         u
     decrescono la latenza, tuttavia bisogna sempre scrivere altri campioni prima
     che il buffer si svuoti)

    Un underrun provoca in genere un difetto udibile nel segnale riprodotto: pu`o
essere una breve pausa, un “click” o la ripetizione di una parte del segnale
(looping); se quest’ultima si verifica con frequenza uniforme si avvertir` un tono
                                                                        a
sovrapposto al segnale riprodotto, con frequenza pari a quella con cui si verifica
l’underrun.


1.5.2    Lettura dal buffer DMA
In fase di campionamento, quando il programma chiama read() per la prima
volta dopo l’apertura del device, si verificano i seguenti eventi:

   • il driver programma la scheda audio con i parametri di campionamento
     predisposti (risoluzione, numero di canali e frequenza)

   • di default ` calcolata la dimensione adeguata per un frammento, se tramite
                e
     un’opportuna chiamata ioctl() non se ne ` stabilita un’altra
                                                 e

   • sono attivati il processo di campionamento da parte della scheda audio e il
     trasferimento dei campioni nel primo frammento del buffer

   • il processo ` messo in wait finch´ non ` riempito un numero di frammenti
                 e                   e     e
     che forniscono globalmente una quantit` di campioni maggiore o uguale a
                                            a
     quella richiesta

                                       18
CAPITOLO 1. LA SCHEDA AUDIO E OSS                        1.6. SCHEDE AUDIO ISA E PCI




   • i campioni richiesti sono copiati nel buffer del processo; gli eventuali cam-
     pioni in pi` rimangono nel buffer DMA
                u

    Le read() successive funzionano come sopra, senza che sia necessario ripredis-
porre la scheda audio.
    Un overrun in campionamento si verifica se il buffer ` completamente riempito:
                                                         e
in tal caso gli ulteriori campioni sono scartati; le ragioni per cui si verifica sono
simili a quelle per cui si verifica in riproduzione.


1.6      Schede audio ISA e PCI
L’approccio seguito in questo lavoro ` di essere il pi` indipendenti possibile dal-
                                         e              u
l’hardware, in modo da poter creare dei programmi che girino su ogni piattaforma
per cui ` stato portato OSS con tutt’al pi` una semplice ricompilata del codice
         e                                     u
sorgente. Tuttavia si vogliono elencare i motivi per i quali le schede audio PCI
sono superiori alle schede audio ISA (al di l` della qualit` audio); al momento in
                                                a             a
cui si scrive (1999) la quasi totalit` delle schede audio in commercio sono PCI, ma
                                     a
fino a un anno fa erano quasi tutte ISA: un musicista professionista dovrebbe pren-
dere in considerazione l’acquisto di una scheda audio PCI, anche se attualmente
OSS non supporta del tutto le nuove caratteristiche, come l’audio 3D.
    Si elencheranno ora le differenze fra ISA e PCI pertinenti le schede audio:

   • Il bus ISA ha un clock nominale di 8 MHz, il che darebbe un throughput
     teorico di 16 MB/s; in realt`, a causa di overhead vari nella gestione dei seg-
                                  a
     nali, nonch´ il fatto che sono richiesti due cicli di clock per il trasferimento
                  e
     dei dati, il throughput si aggira attorno ai 5 MB/s. Se si guarda la quantit`  a
     di dati da trasferire per una scheda che campiona a 16 bit/stereo/44.1 kHz
     (circa 176 kB/s) questo throughput appare adeguato, ma si pu` verificare
                                                                          o
     che il DMA blocchi l’accesso della CPU al bus durante il trasferimento dei
     campioni o se la richiesta d’interrupt ` occupata (con ISA le IRQ non sono
                                             e
     condivisibili), il che pu` causare click nel suono in sistemi pesantemente
                              o
     caricati. La capacit` di indirizzamento massima per ISA ` di 16 MB, per
                           a                                        e
     cui sono difficilmente applicabili le tecniche di allocazione del buffer DMA
     nel buffer del processo, che dovrebbe risiedere al di sotto di tale limite anche
     per sistemi con pi` RAM. Pu` inoltre risultare difficile la configurazione di
                         u          o
     una scheda audio, soprattutto con OSS/Free se ` PnP.e

   • Le specifiche PCI 2.1 consentono un clock sul bus fino a 66 MHz, con un
     throughput teorico di 264 MB/s, mentre in pratica ci si aggira intorno ai
     108 MB/s; con un clock sul bus di 33 MHz questi valori si dimezzano. Gli
     interrupt sono condivisibili, per cui con la tecnica dell’interrupt binding, se
     si verificano pi` interrupt contemporaneamente, questi vengono raggrup-
                     u
     pati e serviti in base alla priorit` (e Linux distingue fra fast interrupt —
                                        a
     quelli che richiedono un salvataggio del contesto parziale, come la richiesta

                                        19
1.7. FILE SYSTEM E DEVICE FILE                  CAPITOLO 1. LA SCHEDA AUDIO E OSS




      di un servizio DMA — e gli interrupt normali, con salvataggio completo del
      contesto — riducendo in tal modo l’overhead). Il PCI consente il busmas-
      tering multiplo (con due busmaster), ovvero due arbitri nella gestione del
      bus, il che si traduce nell’accesso contemporaneo di CPU e DMA al bus se
      le aree di memoria interessate non coincidono; l’indirizzamento ` a 32 bit,
                                                                        e
      per cui sono applicabili le tecniche di allocazione del buffer DMA nel buffer
                      `
      del processo. E inoltre pi` semplice la configurazione delle periferiche PCI,
                                 u
      in quanto dopo il boot queste negoziano fra loro, in pratica autoallocandosi.

   Queste ed altre considerazioni, come la tendenza delle nuove schede audio
ad incrementare la frequenza di campionamento, spingono a concludere che una
scheda audio PCI pu` risultare fino a dieci volte pi` efficiente di una scheda ISA.
                     o                             u


1.7      File system e device file
Come si ` avuto occasione di affermare precedentemente, OSS ` un driver che
           e                                                        e
fornisce al programmatore la possibilit` di gestire audio e MIDI tramite opportuni
                                         a
device file inseriti nella struttura dell’albero monolitico del file system di Linux.
Ogni device file ` caratterizzato da un major number e da un minor number : il
                  e
primo ` utilizzato come indice in una tabella del kernel per identificare il tipo
         e
di driver che deve gestire un dispositivo hardware, il secondo ` passato al driver
                                                                 e
stesso per identificare l’unit` su cui agire (classe del device).
                              a
     In Linux il major number per OSS ` 14, ma per altri sistemi operativi potrebbe
                                        e
essere diverso. Il minor number pu` essere codificato tramite un solo byte: in
                                      o
tal caso, come da figura 1.6, i quattro bit meno significativi identificano la classe
del dispositivo, mentre i quattro bit pi` significativi identificano un dispositivo
                                           u
all’interno di una stessa classe; ne consegue che ci possono essere fino a sedici
dispositivi dello stesso tipo per ogni classe.

                                                 Tipo device    Classe
                                                   mixer           0
                                                   sequencer       1
                                                   midi            2
        7                 4 3             0
                                                   dsp             3
            num. device          classe            audio           4
                                                   dspW            5
                       1 byte
                                                   sndstat         6
                                                   riservato       7
                                                   music           8
                                                   sndproc         9

         Figura 1.6: Codifica del minor number di un device file di OSS

                                          20
CAPITOLO 1. LA SCHEDA AUDIO E OSS                             1.8. LE VERSIONI DI OSS




    Ad esempio, /dev/midi00 identifica il primo dispositivo di classe 2 (MIDI),
per cui ` il numero del device ` 0: ci` implica che il minor number per il device file
        e                       e     o
relativo ` 0x02 (2 in decimale). Analogamente per /dev/midi01 il minor number
         e
per il device file relativo sar` 0x12 (18 in decimale), per /dev/midi02 sar` 0x22
                              a                                               a
(34 in decimale) e per /dev/midi03 sar` 0x32 (50 in decimale).
                                          a
    Anche se nelle odierne distribuzioni di Linux non ci dovrebbero essere problemi
del genere, se un device file dovesse mancare ` possibile crearlo effettuando il login
                                               e
come root e dando il seguente comando:

mknod -m <permessi> <nome device file> c 14 <classe del device>

ad esempio, il nome del device file potrebbe essere /dev/music e la classe sarebbe 8;
<permessi> ` un numero ottale che predispone i permessi di accesso al file, che
             e
pu` essere posto pari a 666 per accesso in lettura/scrittura da parte di tutti gli
  o
utenti (vedere man chmod).


1.8      Le versioni di OSS
Il riconoscimento della versione di OSS in uso varia secondo che si stia utilizzando
una versione precedente o successiva alla 3.6. Ad esempio, la versione 3.5.4 di
soundcard.h definisce le seguenti macro:

#define SOUND_VERSION   350
#define UNIX_SOUND_SYSTEM

mentre nella versione 3.8.2 sono definite le seguenti altre macro:

#define SOUND_VERSION   0x030802
#define OPEN_SOUND_SYSTEM

SOUND_VERSION contiene il numero di versione, con formato che varia secondo che
sia definito UNIX_SOUND_SYSTEM o OPEN_SOUND_SYSTEM.
    Dalla versione 3.6 in poi ` possibile usare il seguente frammento di codice, che
                              e
interroga direttamente il driver per ricavare il numero di versione (` pi` affidabile
                                                                     e u
che ricavarlo da SOUND_VERSION):

int versione;
if (ioctl(dspfd, OSS_GETVERSION, &versione) == -1) {
    /* Versione precedente alla 3.6: errno==EINVAL */
}

   Il seguente frammento di codice ricava i numeri di versione e release indipen-
dentemente dalla versione di OSS utilizzata:

int versione, release1, release2, tmpver;
#ifdef UNIX_SOUND_SYSTEM

                                         21
1.8. LE VERSIONI DI OSS                CAPITOLO 1. LA SCHEDA AUDIO E OSS




   versione = SOUND_VERSION / 100;
   release1 = (SOUND_VERSION - versione*100) / 10;
   release2 = SOUND_VERSION - versione*100 - release1*10;
#else   /* e’ definito OPEN_SOUND_SYSTEM */
   if (ioctl(dspfd, OSS_GETVERSION, &tmpver) == -1)
       errore("OSS_GETVERSION");
   versione = (tmpver & 0x00ff0000) >> 16;
   release1 = (tmpver & 0x0000ff00) >> 8;
   release2 = tmpver & 0x000000ff;
#endif




                                22
Capitolo 2

IL MIXER E LA GESTIONE
DEI CANALI

2.1      Descrizione di /dev/mixer
Non tutte le schede audio possiedono un mixer: nella fattispecie possono non
averlo le schede pi` vecchie, quelle non ancora pienamente supportate, quelle
                    u
professionali e le completamente digitali. Anche se il mixer ` mancante si pu`
                                                                 e                 o
sempre aprire /dev/mixer, ma un’eventuale ioctl() ritorna errno==ENXIO.
    Nella Sezione 1.3 si ` fornito uno scheletro di codice C per l’apertura e la
                          e
chiusura di un device file; si ` scritto che fra la open() e la close() di questo
                                e
ci possono essere delle read(), write() o ioctl(). /dev/mixer ` un device file
                                                                     e
atipico, in quanto non accetta operazioni di read() o write() e l’unica primitiva
utilizzabile ` ioctl(): infatti il mixer svolge solo un lavoro di gestione dei canali
             e
cambiando la configurazione della scheda audio, praticamente non impiegando
risorse di calcolo per operare.
    A differenza di /dev/dsp e /dev/audio, pi` di un processo alla volta pu`
                                                   u                               o
aprire /dev/mixer; generalmente si usa O_RDONLY come argomento di open().
Le modifiche effettuate alla configurazione del mixer permangono anche dopo
la chiusura dell’ultimo processo modificante, fino a quando un eventuale altro
processo non effettuer` nuovi cambiamenti o fino al reboot del computer. All’atto
                       a
del boot ` il kernel che si occupa di configurare la scheda audio con dei valori
           e
di default ragionevoli, ma che non dovrebbero comunque essere dati per scontati
per non creare programmi inaffidabili.
    Solo per il primo mixer, l’uso delle ioctl() per /dev/mixer su questo o su un
altro device di OSS sono equivalenti: ad esempio, se si ` aperto /dev/sequencer `
                                                         e                         e
inutile aprire anche /dev/mixer per variare la configurazione della scheda audio,
basta usare gli ioctl() che si sarebbero utilizzati col secondo direttamente col
primo.
    `
    E importante verificare le capacit` del mixer prima di usarne i canali, in mo-
                                        a
do da creare programmi che siano portabili per quasi tutte le schede audio. Le

                                         23
2.2. I CANALI DEL MIXER              CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI




differenze di comportamento riscontrate dovrebbero essere indicate nella docu-
mentazione, evitando descrizioni troppo specifiche nei confronti dei canali, che
possono avere caratteristiche diverse con schede diverse.



2.2      I canali del mixer
Per i canali del mixer ci si pu` rifare alla metafora delle figure 1.1 e 1.2, per cui
                               o
un canale identifica la classe del dispositivo (CD, microfono, . . . ) che a questo
` connesso; il programmatore pu` effettuarne la selezione per la riproduzione o
e                                  o
il campionamento, regolandone il livello di volume (se il dispositivo ` stereo ci
                                                                         e
sono due livelli di volume indipendentemente controllabili, il che consente di real-
izzare il balance). Il volume principale pu` essere mancante (Gravis UltraSound,
                                            o
Microsoft Sound System).
    Sono definiti SOUND_MIXER_NRDEVICES canali, a cui ` associato un numero da
                                                          e
0 a SOUND_MIXER_NRDEVICES-1; un programma non dovrebbe cercare di accedere
a numeri di canale superiori a quest’ultimo.
    OSS mette a disposizione del programmatore dei nomi simbolici per ogni
canale; quelli definiti in soundcard.h versione 3.5.4 si trovano nella tabella della
prossima pagina.
    Ad ogni canale ` associato un int, nella cui rappresentazione in binario (bit-
                     e
mask ) ` posto a 1 il bit di posizione corrispondente al numero del canale; le
        e
bitmask sono utili con i comandi di gestione della configurazione del mixer.
    Sempre in soundcard.h sono definiti dei nomi simbolici da dare ai canali
tramite le macro:


#define SOUND_DEVICE_LABELS {"Vol ",           "Bass ",    "Trebl",   "Synth", 
                             "Pcm ",           "Spkr ",    "Line ",   
                             "Mic ",           "CD   ",    "Mix ",    "Pcm2 ", 
                             "Rec ",           "IGain",    "OGain",   
                             "Line1",          "Line2",    "Line3"}

#define SOUND_DEVICE_NAMES         {"vol", "bass", "treble", "synth", 
                                    "pcm", "speaker", "line", "mic", 
                                    "cd", "mix", "pcm2", "rec", "igain", 
                                    "ogain", "line1", "line2", "line3"}


la differenza fra i due ` che il primo formato ` adatto per la stampa dei nomi a
                        e                       e
video quali etichette dei canali, mentre del secondo formato ` pi` adatto l’utilizzo
                                                             e u
quando i nomi dei device audio sono forniti sulla riga di comando di una shell.

                                        24
CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI              2.2. I CANALI DEL MIXER




 Nomi dei canali         Bitmask associate      Descrizione
 SOUND_MIXER_VOLUME      SOUND_MASK_VOLUME      Livello volume principale
 SOUND_MIXER_BASS        SOUND_MASK_BASS        Regolazione toni bassi principale
 SOUND_MIXER_TREBLE      SOUND_MASK_TREBLE      Regolazione toni acuti principale
 SOUND_MIXER_SYNTH       SOUND_MASK_SYNTH       Livello di uscita sintetizzatore
                                                interno (FM, wavetable); per alcune
                                                schede ne controlla anche il livello
                                                di campionamento
 SOUND_MIXER_PCM         SOUND_MASK_PCM         Livello di uscita di /dev/dsp
                                                e /dev/audio
 SOUND_MIXER_SPEAKER     SOUND_MASK_SPEAKER     Livello di uscita del segnale
                                                all’altoparlantino nel PC (se
                                                connesso alla scheda audio);
                                                su altre schede pu` essere un
                                                                    o
                                                generico ingresso mono con
                                                qualche altra funzione
 SOUND_MIXER_LINE        SOUND_MASK_LINE        Livello di ingresso per Line–In
 SOUND_MIXER_MIC         SOUND_MASK_MIC         Livello del segnale microfonico in
                                                campionamento o inviato a cuffie e
                                                Line–Out; qualche volta il microfono
                                                non ` connesso a questo canale, ma
                                                     e
                                                a un line level input della scheda
 SOUND_MIXER_CD          SOUND_MASK_CD          Livello del CD Audio–In
 SOUND_MIXER_IMIX        SOUND_MASK_IMIX        Recording monitor campionamento;
                                                durante tale fase, su alcune schede
                                                controlla il volume delle cuffie
 SOUND_MIXER_ALTPCM      SOUND_MASK_ALTPCM      Livello di un DSP secondario; nella
                                                Pro Audio Spectrum 16 ` il canale
                                                                           e
                                                dell’emulazione della Sound Blaster
 SOUND_MIXER_RECLEV      SOUND_MASK_RECLEV      Livello di campionamento per tutti
                                                i canali (nella Sound Blaster 16 si
                                                hanno solo quattro livelli possibili)
 SOUND_MIXER_IGAIN       SOUND_MASK_IGAIN       Livello di guadagno di ingresso
 SOUND_MIXER_OGAIN       SOUND_MASK_OGAIN       Livello di guadagno di uscita
 SOUND_MIXER_LINE1       SOUND_MASK_LINE1       Canale generico 1 (aux1); i codec
                                                AD1848 e compatibili hanno tre
                                                line level input a cui diversi
                                                costruttori assegnano funzioni
                                                diverse, per cui tali nomi si
                                                usano quando il significato preciso
                                                di un canale fisico ` sconosciuto
                                                                     e
 SOUND_MIXER_LINE2       SOUND_MASK_LINE2       Canale generico 2 (aux2)
 SOUND_MIXER_LINE3       SOUND_MASK_LINE3       Canale generico 3 (line)




                                       25
2.2. I CANALI DEL MIXER             CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI




   Se in un programma si effettuano le assegnazioni:

const char *nome_canale[] = SOUND_DEVICE_LABELS;
const char *nome_cmline[] = SOUND_DEVICE_NAMES;

allora, ad esempio, nome_canale[SOUND_MIXER_CD] corrisponde a “CD           ”, men-
tre nome_cmline[SOUND_MIXER_CD] corrisponde a “cd”.
    Il mixer consente la selezione dei canali da cui effettuare il campionamento,
che per buona parte delle schede avviene in mutua esclusione.
    Il canale di default dopo il boot ` quello del microfono, ma non dovrebbe
                                        e
essere dato per scontato, poich´ il driver non altera le predisposizioni del mixer
                                e
a meno di un comando da programma; un qualche altro processo dopo il boot
potrebbe averle modificate, con tali modifiche che permangono anche dopo la sua
terminazione.
    L’insieme di canali disponibili non ` fisso, ma dipende dalla scheda audio; si
                                         e
pu` verificare che ai canali dello stesso chip mixer costruttori diversi assegnino
   o
funzioni diverse, per cui bisogner` verificarne caso per caso il reale significato.
                                  a
    Sarebbe meglio non includere funzionalit` di mixer nei programmi se si vuole
                                               a
la massima portabilit` del proprio codice, demandandole a programmi specializ-
                      a
zati per le varie schede audio. Nel caso si volesse realizzare un tale programma
mixer bisogna ben documentare le sue capacit` se ` sviluppato per una precisa
                                                 a    e
scheda, altrimenti ` meglio evitare di essere troppo specifici nella documentazione
                    e
per non trarre in inganno gli utenti: potrebbero credere che la propria scheda
audio sia diversa da come ` realmente, basandosi su ci` che il programma fa
                             e                             o
vedere.


2.2.1     Lettura della configurazione
Come ` stato visto sopra, converrebbe effettuare il controllo sia delle capacit`
        e                                                                       a
della scheda audio che dell’esistenza o meno dei canali di interesse prima di in-
traprendere qualsiasi altra azione; un’operazione di ioctl() fallita (ad esempio,
perch´ il canale non esiste) ritorna -1 e errno==EINVAL.
      e
    Per effettuare la lettura della configurazione dei canali del mixer il codice `
                                                                                e
simile per tutti i comandi a disposizione:

int bitmask;
if (ioctl(mixfd, SOUND_MIXER_READ_****, &bitmask) == -1) {
    /* Il mixer e’ mancante - errno==ENXIO */
}

ove SOUND_MIXER_READ_**** ` l’identificatore del comando di lettura che ritorna
                              e
in bitmask una maschera di bit; questa pu` essere esaminata per determinare le
                                           o
capacit` di un canale o del mixer in base al comando dato.
       a

                                        26
CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI              2.2. I CANALI DEL MIXER




   Per i canali il controllo si pu` effettuare con:
                                  o
if (bitmask & (1 << numero_canale)) {
    /* Il canale possiede la capacita’ in esame */
}
ove numero_canale ` un intero tra 0 e SOUND_MIXER_NRDEVICES-1 o il nome
                     e
mnemonico del canale; al posto di (1 << numero_canale) si pu` utilizzare di-
                                                                  o
rettamente la bitmask ad esso associata.
    I comandi a disposizione per la lettura della configurazione sono:

 Comandi                             Richiesta a OSS
 SOUND_MIXER_READ_DEVMASK            Quali canali sono presenti?
 SOUND_MIXER_READ_STEREODEVS         Quali canali sono stereo?
 SOUND_MIXER_READ_RECMASK            Quali sono i canali campionabili?
 SOUND_MIXER_READ_RECSRC             Qual ` il canale campionabile attivo?
                                          e
 SOUND_MIXER_READ_CAPS               Si pu` campionare solo un canale per volta?
                                          o
    Con l’ultimo comando si testa se si possono campionare i canali solo in mutua
esclusione; il controllo si effettua con:
if (bitmask & SOUND_CAP_EXCL_INPUT) {
    /* Campionamento solo in mutua esclusione */
}
    La differenza fra SOUND_MIXER_READ_RECMASK e SOUND_MIXER_READ_RECSRC
` che il primo comando ritorna una bitmask con un bit a 1 per ogni canale per
e
cui la scheda audio possiede la capacit` di campionamento, mentre il secondo
                                       a
ritorna una bitmask con un bit a 1 per ogni canale attualmente selezionato per
il campionamento (se questo ` possibile in mutua esclusione solo un bit in tutta
                            e
la bitmask pu` essere a 1).
              o

2.2.2    Selezione del canale da campionare
Per selezionare il canale da cui campionare basta il seguente frammento di codice:
int bitmask = SOUND_MASK_****;
if (ioctl(mixfd, SOUND_MIXER_WRITE_RECSRC, &bitmask) == -1) {
    /* Non c’e’ il mixer o il canale */
    /* errno==ENXIO oppure EINVAL    */
}
ove SOUND_MASK_**** ` la bitmask associata al canale desiderato.
                       e
    Nel caso fosse possibile campionare da pi` canali contemporaneamente, li si
                                             u
seleziona ponendo in bitmask un OR aritmetico delle bitmask associate ai canali;
ad esempio, per selezionare il campionamento simultaneo da CD e da microfono:

                                        27
2.3. LIVELLI DI VOLUME DEI CANALI     CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI




bitmask = SOUND_MASK_CD | SOUND_MASK_MIC;

    Se nessun bit ` posto a 1 (bitmask==0x00000000), il driver seleziona il canale
                  e
del microfono.


2.3      Livelli di volume dei canali
Le schede audio rappresentano il livello di un canale con un numero di bit vari-
abile: 3, 8 e anche 16 bit. L’architettura di OSS svincola il programmatore dalla
conoscenza dei livelli assoluti di volume introducendo una rappresentazione in
percentuale: il volume di un canale pu` variare fra 0 (spento) e 100 (massimo);
                                         o
se il canale ` stereo si hanno due di queste percentuali, che possono essere uguali
             e
o meno per realizzare il balance.
    Per leggere il livello attuale di volume per un canale si sfrutta il seguente
frammento di codice:

int volume;
if ((ioctl(mixfd, MIXER_READ(numero_canale), &volume) == -1) {
    /* Non c’e’ il mixer o il canale */
    /* errno==ENXIO oppure EINVAL    */
}

ove numero_canale ` un numero compreso tra 0 e SOUND_MIXER_NRDEVICES-1,
                       e
oppure il nome mnemonico del canale. Al posto di MIXER_READ(numero_canale)
si pu` utilizzare anche SOUND_MIXER_READ_****, con **** nome del canale; ad
      o
esempio, per il microfono ` SOUND_MIXER_READ_MIC.
                             e
    Il volume ` codificato come in figura 2.1: se il dispositivo ` stereo la codifica dei
               e                                               e
canali destro e sinistro si trova nella parola meno significativa di volume (intero
a 32 bit); i 16 bit della parola pi` significativa sono indefiniti e dovrebbero essere
                                   u
ignorati. Nel byte pi` significativo della LSW c’` il volume del canale destro, nel
                       u                            e
byte meno significativo il volume del canale sinistro; per i canali mono ` valido
                                                                              e
solo il byte meno significativo, essendo l’MSB posto uguale all’LSB dal driver.

             31                       16 15             8 7           0
                  XXXXXXXX Destro Sinistro

                      MSW                     MSB             LSB
                   (da ignorare)
                                                      LSW
          Figura 2.1: Rappresentazione del volume di un canale stereo

                                         28
CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI     2.4. DIPENDENZA DALL’HARDWARE




   Per estrarre il volume dei canali destro e sinistro si possono sfruttare le
seguenti linee di codice:
int volume, vol_sinistro, vol_destro;
vol_sinistro = volume & 0x000000ff;
vol_destro   = (volume & 0x0000ff00) >> 8;
   Inversamente, per costituire una parola di volume:
int volume;
volume = (vol_destro << 8) | vol_sinistro;
   Per cambiare il volume si utilizza il seguente frammento di codice:
int volume = <valore>;
if ((ioctl(fd, MIXER_WRITE(numero_canale), &volume) == -1) {
    /* Non c’e’ il mixer o il canale */
    /* errno==ENXIO oppure EINVAL    */
}
ove numero_canale ` un numero compreso tra 0 e SOUND_MIXER_NRDEVICES-1,
                       e
oppure il nome mnemonico del canale. Al posto di MIXER_WRITE(numero_canale)
si pu` utilizzare anche SOUND_MIXER_WRITE_****, con **** nome del canale; ad
      o
esempio, per il CD ` SOUND_MIXER_WRITE_CD.
                     e
    Dopo il cambiamento bisognerebbe verificare se il livello ritornato in volume
risulta di proprio gradimento, dal momento che ` di solito pi` piccolo di quanto
                                                 e           u
richiesto; sequenze di scrittura/lettura ripetute (senza cambiare tale variabile)
                                       `
possono portare al suo azzeramento. E conveniente effettuare la predisposizione
del volume durante l’inizializzazione del programma, ignorando poi il volume
ritornato in seguito.


2.4     Dipendenza dall’hardware
Per ottenere dal mixer il nome della scheda audio si pu` utilizzare il seguente
                                                       o
frammento di codice:
mixer_info info;
if ((ioctl(fd, SOUND_MIXER_INFO, &info) == -1) {
    /* Non c’e’ il mixer - errno==ENXIO */
}
ove mixer_info ` una struct cos` composta:
               e               ı
char id[16 ] identificatore della scheda audio (in genere un paio di caratteri)
char name[32 ] nome per esteso della scheda
   Ecco un output di esempio per SOUND_MIXER_INFO con una scheda Sound
Blaster 16:
SB
Sound Blaster

                                       29
2.5. NUOVE CARATTERISTICHE           CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI




2.5      Nuove caratteristiche
In questa Sezione ci si occuper` di descrivere le nuove caratteristiche di OSS
                                 a
riguardanti il mixer introdotte dalla versione 3.6 in poi.

   • ` stata implementata la possibilit` di gestire dei canali in pi` :
     e                                 a                            u


       Nomi dei canali           Bitmask associate         Descrizione
       SOUND_MIXER_DIGITAL1      SOUND_MASK_DIGITAL1       Ingresso digitale 1
       SOUND_MIXER_DIGITAL2      SOUND_MASK_DIGITAL2       Ingresso digitale 2
       SOUND_MIXER_DIGITAL3      SOUND_MASK_DIGITAL3       Ingresso digitale 3
       SOUND_MIXER_PHONEIN       SOUND_MASK_PHONEIN        Ingresso livello fono
       SOUND_MIXER_PHONEOUT      SOUND_MASK_PHONEOUT       Uscita livello fono
       SOUND_MIXER_VIDEO         SOUND_MASK_VIDEO          Ingresso audio per video/TV
       SOUND_MIXER_RADIO         SOUND_MASK_RADIO          Ingresso radio
       SOUND_MIXER_MONITOR       SOUND_MASK_MONITOR        Volume monitor (di solito
                                                           il microfono)

   • le macro SOUND_DEVICE_LABELS e SOUND_DEVICE_NAMES risultano di con-
     seguenza arricchite rispettivamente delle etichette e dei nomi da linea di
     comando dei nuovi canali:

      #define SOUND_DEVICE_LABELS        {"Vol ", "Bass ", "Trebl", "Synth", 
                                          "Pcm ", "Spkr ", "Line ", "Mic ", 
                                          "CD   ", "Mix ", "Pcm2 ", "Rec ", 
                                          "IGain", "OGain", "Line1", "Line2", 
                                          "Line3", "Digital1", "Digital2", 
                                          "Digital3", "PhoneIn", "PhoneOut", 
                                          "Video", "Radio", "Monitor"}

      #define SOUND_DEVICE_NAMES         {"vol", "bass", "treble", "synth", 
                                          "pcm", "speaker", "line", "mic", 
                                          "cd", "mix", "pcm2", "rec", "igain", 
                                          "ogain", "line1", "line2", "line3", 
                                          "dig1", "dig2", "dig3", "phin", 
                                          "phout", "video", "radio", "monitor"}

   • ` stata modificata mixer_info; la sua nuova struttura ` la seguente:
     e                                                    e

      char id[16 ] identificatore della scheda audio (in genere un paio di caratteri)
      char name[32 ] nome per esteso della scheda
      int modify counter numero di modifiche
      int fillers[10 ] “riempitivi” per evoluzioni future

                                        30
CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI               2.6. ESEMPIO DI PROGRAMMA




        la vecchia struct mixer_info ` stata rinominata _old_mixer_info e ad
                                        e
        essa si accede con la chiamata SOUND_OLD_MIXER_INFO

      • le nuove chiamate SOUND_MIXER_GETLEVELS e SOUND_MIXER_SETLEVELS ser-
        vono rispettivamente per interrogare il driver sulle predisposizioni dei livelli
        di volume di default e per impostare dei nuovi livelli subito dopo open(),
        prima di attivare il driver con read()/write(); esse sono definite solo per
        uso interno degli sviluppatori del codice del driver, di conseguenza i normali
        programmatori dovrebbero astenersi dal loro utilizzo:

        mixer_vol_table voltab;
        if (ioctl(mixfd, SOUND_MIXER_****, &voltab) == -1) {
            /* Non c’e’ il mixer o il canale */
            /* errno==ENXIO oppure EINVAL    */
        }

        ove la struct mixer_vol_table ` cos` definita:
                                      e    ı

        int num indice alla tabella dei volumi
        char name[32 ] nome mixer
        int levels[32 ] livelli di volume in percentuale


2.6        Esempio di programma
Il programma seguente implementa un mixer molto semplice: se il comando mixer
` dato da linea di comando senza argomenti si limita a listare i canali della scheda
e
audio disponibili, riferendo se un canale ` campionabile, se ` selezionato, se `
                                                e                  e                  e
stereo e i livelli di volume in percentuale.
     Se il comando ` dato nella forma mixer <canale> [livSx] [livDx] senza
                      e
specificare i livelli ` reso attivo <canale> per il campionamento (se era gi` attivo
                      e                                                        a
lo deseleziona); se ` fornito anche il livello di volume in percentuale livSx l’altro `
                     e                                                                e
posto uguale per i canali stereo, altrimenti il primo si riferisce al canale sinistro e
il secondo al canale destro. Per un canale mono l’eventuale livDx verr` ignorato.
                                                                           a
/*
 *         mixer.c - Implementa un mixer command-line (solo /dev/mixer)
 */

#include     <stdio.h>
#include     <stdlib.h>
#include     <string.h>
#include     <unistd.h>
#include     <fcntl.h>
#include     <sys/soundcard.h>


                                           31
2.6. ESEMPIO DI PROGRAMMA          CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI




/* Indici   per la matrice   caratteristiche[][] */
const int   NCANALE      =   0;
const int   CAMPIONABILE =   1;
const int   CAN_CAMP_ATT =   2;
const int   CAN_STEREO   =   3;
const int   VOL_SINISTRO =   4;
const int   VOL_DESTRO   =   5;

/* Qualche funzione utile per la stampa delle caratteristiche */
void SI_NO(int si)
{
    si ? printf("    SI       ") : printf("    NO       ");
}

void STEREO_MONO(int stereo, int vol_sx, int vol_dx)
{
    stereo ? printf("Stereo    %d    %dn", vol_sx, vol_dx) :
             printf(" Mono        %dn", vol_sx);
}

void errore(const char *msgerr)      /* Gestione degli errori */
{
    perror(msgerr);
    exit(-1);
}

int limita(int valore)          /* Limita valore fra 0 e 100 */
{
    valore = (valore < 0) ? 0 : valore;
    return (valore > 100) ? 100 : valore;
}

int main(int argc, char *argv[])
{
   int mixfd;
   mixer_info info;
   int canale, volume, num_canali = 0, num_camp = 0;
   int c_bmask, dev_bmask, canst_bmask, camp_bmask, catt_bmask;
   int caratteristiche[SOUND_MIXER_NRDEVICES][6];

   const char *nome[] = SOUND_DEVICE_LABELS;            /* Nomi dei canali */
   const char *cm_nome[] = SOUND_DEVICE_NAMES;

   if ((mixfd = open("/dev/mixer", O_RDONLY)) == -1)
       errore("/dev/mixer");




                                      32
CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI   2.6. ESEMPIO DI PROGRAMMA




   /* Dopo l’apertura del device, raccolta di informazioni */
   if (ioctl(mixfd, SOUND_MIXER_INFO, &info) == -1)
       errore("SOUND_MIXER_INFO");
   if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &dev_bmask) == -1)
       errore("SOUND_MIXER_READ_DEVMASK");
   if (ioctl(mixfd, SOUND_MIXER_READ_STEREODEVS, &canst_bmask) == -1)
       errore("SOUND_MIXER_READ_STEREODEVS");
   if (ioctl(mixfd, SOUND_MIXER_READ_RECMASK, &camp_bmask) == -1)
       errore("SOUND_MIXER_READ_RECMASK");
   if (ioctl(mixfd, SOUND_MIXER_READ_RECSRC, &catt_bmask) == -1)
       errore("SOUND_MIXER_READ_RECSRC");
   for (canale=0; canale<SOUND_MIXER_NRDEVICES; canale++) {
     c_bmask = 1 << canale;
     if (dev_bmask & c_bmask) {                       /* Canale presente? */
       if (ioctl(mixfd, MIXER_READ(canale), &volume) == -1)
           errore("MIXER_READ(canale)");
       caratteristiche[num_canali][NCANALE] = canale;
       caratteristiche[num_canali][CAMPIONABILE] = camp_bmask & c_bmask;
       caratteristiche[num_canali][CAN_CAMP_ATT] = catt_bmask & c_bmask;
       caratteristiche[num_canali][CAN_STEREO] = canst_bmask & c_bmask;
       caratteristiche[num_canali][VOL_SINISTRO] = volume & 0x000000ff;
       caratteristiche[num_canali][VOL_DESTRO] = (volume & 0x0000ff00) >> 8;
       if (caratteristiche[num_canali][CAMPIONABILE])
           num_camp++;                  /* Numero dei canali campionabili */
       num_canali++;
     }
   }

   /* In base al numero di argomenti varia il comportamento del mixer */
   switch (argc) {

    /* mixer invocato senza argomenti - visualizza informazioni */
    case 1: if (ioctl(mixfd, SOUND_MIXER_READ_CAPS, &c_bmask) == -1)
                errore("SOUND_MIXER_READ_CAPS");
            printf("nScheda %s, %d canali campionabili ", info.name,num_camp);
            (c_bmask & SOUND_CAP_EXCL_INPUT) ? puts("in mutua esclusione.") :
                                               puts("simultaneamente.");
            printf("nCanale   Nome    Campionabile "
                   "Selezionato   Tipo   VolSx VolDxn");
            printf("--------------------------------"
                   "--------------------------------n");
            for (canale=0; canale<num_canali; canale++) {
                 printf("%-9.9s", nome[caratteristiche[canale][NCANALE]]);
                 printf("%-9.9s", cm_nome[caratteristiche[canale][NCANALE]]);
                 SI_NO(caratteristiche[canale][CAMPIONABILE]);
                 SI_NO(caratteristiche[canale][CAN_CAMP_ATT]);


                                       33
2.6. ESEMPIO DI PROGRAMMA       CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI




                  STEREO_MONO(caratteristiche[canale][CAN_STEREO],
                              caratteristiche[canale][VOL_SINISTRO],
                              caratteristiche[canale][VOL_DESTRO]);
            }
            break;

    /* mixer invocato solo col nome del canale */
    case 2: canale = 0;
            do {     /* Ricerca il nome del canale fra quelli disponibili */
              if (!strcmp(argv[1], cm_nome[caratteristiche[canale][NCANALE]]))
                   break;
            } while (canale++ < num_canali-1);
            if (canale == num_canali)                 /* Nome non trovato */
                 puts("Canale non disponibile.");
            else {                                        /* Nome trovato */
                 if (!caratteristiche[canale][CAMPIONABILE]) {
                     puts("Il canale non e’ fra quelli campionabili.");
                     break; /* Per selezionarlo deve essere campionabile */
                 }
                 c_bmask = 1 << caratteristiche[canale][NCANALE];
                 catt_bmask ^= c_bmask;            /* Setta o resetta il bit */
                 if (ioctl(mixfd, SOUND_MIXER_WRITE_RECSRC, &catt_bmask) == -1)
                   errore("SOUND_MIXER_WRITE_RECSRC"); /* catt_bmask variata */
                 printf("%s ", cm_nome[canale]);
                 (catt_bmask & c_bmask) ? puts("selezionato.") :
                                          puts("deselezionato.");
            }
            break;

    /* mixer invocato col nome del canale e uno o due livelli di volume */
    case 3:
    case 4: canale = 0;
            do {     /* Ricerca il nome del canale fra quelli disponibili */
              if (!strcmp(argv[1], cm_nome[caratteristiche[canale][NCANALE]]))
                   break;
            } while (canale++ < num_canali-1);
            if (canale == num_canali)                 /* Nome non trovato */
                 puts("Canale non disponibile.");
            else {                                        /* Nome trovato */
                 volume = limita(atoi(argv[2]));
                 if ((argc == 4) && caratteristiche[canale][CAN_STEREO])
                     volume |= limita(atoi(argv[3])) << 8;   /* Se stereo */
                 else volume |= volume << 8;     /* Se 2 argomenti o mono */
                 if (ioctl(mixfd, MIXER_WRITE(canale), &volume) == -1)
                     errore("MIXER_WRITE(canale)");
                 printf("Volume di %s a ", argv[1]); /* Stampa nuovi volumi */


                                   34
CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI         2.6. ESEMPIO DI PROGRAMMA




                   if (caratteristiche[canale][CAN_STEREO])
                       printf("%d% / %d%n", volume & 0x000000ff,
                                              (volume & 0x0000ff00) >> 8);
                   else printf("%d%n", volume & 0x000000ff);
               }
               break;

        /* Numero sbagliato di argomenti */
        default: printf("uso: %s <canale> [livSx%] [livDx%]n", argv[0]);
    }

    close(mixfd);
    return 0;
}
    Di seguito ` riportato un esempio di utilizzo del programma:
               e
$ mixer ?
Canale non disponibile.
$ mixer ? ? ? ? ?
uso: mixer <canale> [livSx%] [livDx%]
$ mixer
Scheda Sound Blaster, 4 canali campionabili simultaneamente.
Canale   Nome     Campionabile Selezionato   Tipo   VolSx VolDx
----------------------------------------------------------------
Vol      vol           NO          NO       Stereo    75     75
Bass     bass          NO          NO       Stereo    75     75
Trebl    treble        NO          NO       Stereo    75     75
Synth    synth         SI          NO       Stereo    75     75
Pcm      pcm           NO          NO       Stereo    75     75
Spkr     speaker       NO          NO        Mono        75
Line     line          SI          NO       Stereo    75     75
Mic      mic           SI          SI        Mono        16
CD       cd            SI          SI       Stereo    75     75
IGain    igain         NO          NO       Stereo    75     75
OGain    ogain         NO          NO       Stereo    75     75
$ mixer cd
cd deselezionato.
$ mixer mic 0
Volume di mic a 0%
$ mixer cd 75 40
Volume di cd a 75% / 40%
$ mixer cd 80
Volume di cd a 80% / 80%
$ mixer cd
cd selezionato.

                                       35
Capitolo 3

CAMPIONAMENTO E
RIPRODUZIONE

3.1      I device file audio
Nel Capitolo precedente si ` appreso come selezionare un canale e impostarne il
                            e
livello di volume per il campionamento e la riproduzione. L’interfaccia di accesso
al DSP ` rappresentata dai seguenti device file (possono essere link simbolici):
         e

/dev/dsp codifica 8 bit unsigned

/dev/dspW codifica 16 bit signed

/dev/audio codifica 8 bit µ–Law

la frequenza di campionamento predefinita ` di 8 kHz/mono (per /dev/audio
                                               e
` l’unica possibile), il canale predefinito ` il microfono (con le solite avvertenze
e                                          e
riguardo i parametri di default). Un metodo alternativo ` di procedere predispo-
                                                           e
nendo il formato opportuno per i campioni tramite ioctl() su /dev/dsp, ma
l’uso di uno di questi device file equivale a sceglierlo automaticamente.
    I device file dovrebbero essere aperti in read–only (O_RDONLY) o in write–
only (O_WRONLY) per motivi di efficienza di OSS; per le schede audio che non
supportano il full duplex aprire in read–only il device file, richiudere e riaprire in
write–only, e cos` via, ` pi` efficiente dell’apertura in read–write (O_RDWR).
                  ı      e u


3.2      Il buffer audio
Il campionamento effettuato da una scheda audio ` uniforme; il risultato ` una
                                                  e                        e
sequenza di campioni (ampiezze del segnale in ingresso al canale negli istanti di
campionamento, opportunamente codificate), che costituisce una quantit` di byte
                                                                        a

                                         36
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                 3.3. PARAMETRI DI CAMPIONAMENTO




dipendente dalla risoluzione di campionamento (8 o 16 bit), dal numero di canali
coinvolti1 (mono o stereo) e dalla frequenza di campionamento.
    In generale, bisogna predisporre un opportuno buffer per ospitare i campioni
da riprodurre o letti da un canale; un esempio di codice per dichiarare un buffer
di dimensione DIM_BUF ` il seguente:
                        e
const int DIM_BUF = 4096;
unsigned char audio_buffer[DIM_BUF];
ove si presupponga di utilizzare /dev/dsp o /dev/audio, poich´ il buffer ` di
                                                                        e          e
unsigned char.
    `
    E possibile ridurre l’overhead nell’I/O aumentando DIM_BUF, quindi passando
pi` campioni nelle write() o richiedendone di pi` nelle read(). Buffer pi` corti
  u                                                 u                           u
danno migliori risultati nel caso di elaborazione simultanea al campionamento dei
dati: per un uso “normale”2 una dimensione indicativa per DIM_BUF ` compresae
fra 1024 e 4096 per un campionamento mono a 8 bit (tali limiti raddoppiano in
caso di campionamento a 16 bit e quadruplicano se ` anche stereo).
                                                        e
    Se il campionamento ` da un canale stereo i campioni per ambo i canali si
                            e
presentano alternati in sequenza nel buffer con prima il canale sinistro (L) del
destro (R): LRLRLRLRLRLR. . . , ove L o R avranno dimensione di un byte se il
campionamento ` a 8 bit o di due byte se il campionamento ` a 16 bit. Quanto
                  e                                                 e
detto fino ad ora ` da tenere presente anche nel caso di riproduzione di campioni,
                   e
per cui il buffer deve essere riempito con le stesse regole.
    Si deve decidere se in un’applicazione sia effettivamente il caso di campionare
a 16 bit invece che a 8 bit: in generale ci sono molti casi in cui i primi non portano
a un miglioramento evidente della qualit` audio, a fronte invece di un raddoppio
                                             a
dei dati trasferiti dal DMA nel buffer. Un modo per vedere quali dei due sia
meglio utilizzare (oltre l’ascolto diretto) ` di guardare i bit meno significativi
                                               e
dei campioni: se pi` di quattro sono sempre nulli, il campionamento a 8 bit `
                     u                                                               e
preferibile a quello a 16 bit.


3.3       Parametri di campionamento
Il default per OSS ` un campionamento a 8kHz/mono/8 bit, ma ` ovvio che possa
                   e                                             e
non essere soddisfacente per tutte le applicazioni. I parametri di campionamento
e riproduzione possono essere cambiati con delle opportune chiamate a ioctl()
dopo l’apertura del device audio, ma prima di qualsiasi read() o write() a
quest’ultimo, altrimenti il comportamento della scheda audio ` indefinito. Inoltre
                                                              e
i parametri devono essere predisposti nel seguente ordine:
    • risoluzione di campionamento (8 o 16 bit)
   1
      La prossima generazione di schede audio (1999) sar` a 24 bit, con 4 o 6 canali di uscita per
                                                              a
il surround; la massima frequenza di campionamento ` di 96 kHz (Ultra Hi–Fidelity)
                                                            e
    2
      Il normale ` qui inteso nel senso di utilizzare una frequenza di campionamento relativamente
                 e
bassa, ove il programma non abbia stringenti requisiti di elaborazione in tempo reale

                                               37
3.3. PARAMETRI DI CAMPIONAMENTO             CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




    • numero di canali (1 per mono, 2 per stereo)

    • frequenza di campionamento (in Hz)
utilizzando il seguente frammento di codice:

int parametro = <valore>;
ioctl(dspfd, SNDCTL_DSP_****, &parametro);
if ( /* parametro e’ molto diverso da <valore> */) {
    /* la scheda audio non puo’ supportare */
    /* il valore richiesto per parametro   */
}

infatti il driver ritorna in parametro il valore che pu` supportare rispetto a quanto
                                                       o
richiesto; SNDCTL_DSP_**** ` uno dei seguenti:
                                e
 Comandi                         Alias                              Per cambiare. . .
 SNDCTL_DSP_SAMPLESIZE           SOUND_PCM_WRITE_BITS               Risoluzione (8, 16)
                                 SNDCTL_DSP_SETFMT
                                 SOUND_PCM_SETFMT
 SNDCTL_DSP_CHANNELS             SOUND_PCM_WRITE_CHANNELS           Numero di canali (1, 2)
 SNDCTL_DSP_SPEED                SOUND_PCM_WRITE_RATE               Frequenza (Hz)

per scegliere se il campionamento deve essere mono o stereo esiste anche un altro
modo: si usa SNDCTL_DSP_STEREO, con parametro posto a 0 se si vuole mono o
a 1 se si vuole stereo.
    Al solito la ioctl() restituisce -1 e cambia errno se non ` stata in grado di
                                                                  e
portare a termine l’azione richiesta. Il programmatore dovrebbe inoltre verificare
se parametro, dopo la chiamata a ioctl(), soddisfa i requisiti voluti3 .
    Per interrogare il driver riguardo il valore per un parametro di campionamento,
con significato analogo ai relativi comandi di predisposizione, si pu` usare il
                                                                          o
seguente frammento di codice:
int parametro;
ioctl(dspfd, SOUND_PCM_READ_****, &parametro);
ove SOUND_PCM_READ_**** ` uno fra i seguenti comandi: SOUND_PCM_READ_BITS,
                        e
SOUND_PCM_READ_CHANNELS e SOUND_PCM_READ_RATE.

3.3.1      Reset dei parametri
Dopo la prima read() o write() i parametri di campionamento non sono pi`      u
modificabili a meno di resettare il DSP interno alla scheda; il metodo per comu-
nicare a OSS quest’azione ` il seguente:
                          e
   3
    Ad esempio, il divisore del clock interno alla scheda audio pu` non supportare una certa
                                                                    o
frequenza di campionamento, per cui il driver ne sceglier` una il pi` vicino possibile; possono
                                                          a           u
non essere supportati anche il campionamento a 16 bit o i canali stereo per le schede pi` vecchie
                                                                                        u

                                               38
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                    3.4. IL CAMPIONAMENTO




ioctl(dspfd, SNDCTL_DSP_****);
ove SNDCTL_DSP_**** ` uno fra i comandi:
                    e
      Comandi              Alias               Azione
      SNDCTL_DSP_SYNC      SOUND_PCM_SYNC      Svuota i buffer e resetta
      SNDCTL_DSP_RESET     SOUND_PCM_RESET     Resetta senza svuotare i buffer

    Il primo comando fa attendere l’applicazione finch´ l’ultimo byte scritto sul
                                                         e
device ` stato riprodotto (il che pu` richiedere diversi secondi, in funzione della
         e                           o
quantit` di campioni nel buffer non ancora riprodotti); dopo il controllo ritorna
         a
al programma chiamante. SNDCTL_DSP_SYNC ` invocata automaticamente dalla
                                                 e
close() del device audio.
    Il secondo comando ferma immediatamente il device, ponendolo nello stato in
cui pu` essere ripredisposto, se in fase di riproduzione; in campionamento, dopo
        o
l’ultima read(), ` utile se non si ha intenzione di chiudere immediatamente il
                  e
device, in modo che si prevenga il driver dalla visualizzazione di un messaggio di
errore non necessario riguardo la condizione di overrun in campionamento.
    Entrambe queste chiamate possono essere utilizzate per passare “al volo” da
campionamento a riproduzione; in tal caso il device audio dovr` essere aperto
                                                                    a
con O_RDWR. Si ricorda tuttavia, per le schede audio che non supportano il full
duplex (vedi Sezione 3.8), che per passare da campionamento a riproduzione una
gestione pi` efficiente del buffer di I/O ` ottenuta chiudendo il device (che era
            u                              e
aperto in modalit` O_RDONLY) e riaprendolo nella modalit` O_WRONLY, e viceversa.
                  a                                        a

3.3.2    Pause nelle operazioni
Qualora sia necessario effettuare una pausa relativamente lunga nell’output con-
tinuo dei campioni, ` possibile “informare” di ci` il driver affinch´ la gestione
                     e                           o                 e
di quest’evento sia effettuata in maniera pi` intelligente; ci` si effettua con il
                                            u                o
seguente comando:
ioctl(dspfd, SNDCTL_DSP_POST);
(l’alias ` SOUND_PCM_POST) che ` una versione pi` leggera di SNDCTL_DSP_SYNC.
         e                       e               u
    Le circostanze nelle quali questo comando risulta utile sono quelle in cui si `
                                                                                  e
riprodotta una serie di campioni (come un effetto sonoro in un gioco) e non si vuole
riprodurre immediatamente un’altra serie, oppure prima di iniziare operazioni
molto lunghe (come l’I/O da tastiera o da disco). Si veda la Sezione 3.10 per
ulteriori informazioni su quest’argomento.


3.4     Il campionamento
Per campionare da un certo canale (che si suppone sia stato precedentemente
selezionato tramite il mixer, nonch´ di cui sia stato fissato un certo livello di
                                   e
campionamento) si pu` usare il seguente frammento di codice:
                       o

                                        39
3.5. LA RIPRODUZIONE                    CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




int num_ric = <valore>, num_camp;
if ((num_camp = read(dspfd, audio_buffer, num_ric)) == -1)
    errore("read()");
ove num_camp ` il numero di byte che vengono effettivamente campionati rispetto
                e
al numero richiesto num_ric; quest’ultimo dev’essere un multiplo intero della
dimensione in byte del campione (ci` fa funzionare meglio il buffer interno usato
                                        o
                         n
da OSS): num_ric = 2 , con num_ric <= sizeof(audio_buffer).
    Per un device file audio in campionamento (aperto con O_RDONLY o O_RDWR)
non esiste condizione di End Of File (EOF), per cui se read() ritorna -1 ` pos-    e
sibile che ci sia un problema hardware permanente o che il programma abbia
tentato di fare qualcosa di impossibile; per rimuovere la condizione d’errore a
volte basta chiudere e riaprire il device file.
    Nella sintesi in tempo reale si ha la necessit` di dover creare un algoritmo il pi`
                                                  a                                     u
efficiente possibile; ci` spesso va a collidere con il tipo di codice sopra e in tal caso `
                       o                                                                e
meglio che non venga effettuato alcun tipo di controllo sulla quantit` di byte ritor-
                                                                        a
nati da read(), riducendo il tutto a read(dspfd, audio_buffer, num_ric);.
Il campionamento risulta inoltre pi` efficiente se ` letta una quantit` di byte pari
                                      u               e                  a
alla dimensione di un frammento (vedi le Sezioni 3.7.2 e 3.7.3).
    A condizione che il processo campionante non venga posto in stato di wait per
periodi di tempo relativamente lunghi, il numero di byte campionati pu` essere  o
utilizzato per misurare il tempo trascorso durante il campionamento in maniera
abbastanza precisa: infatti il data rate (byte/secondo) dipende dalla frequenza
di campionamento fc (in Hz), dalla dimensione del campione dc (1 o 2 byte) e dal
numero di canali utilizzati c (1 se mono, 2 se stereo), da cui:

      o                                                no byte campionati
     n byte campionati = fc · dc · c · tempo ⇒ tempo =
                                                            fc · dc · c
ad esempio, campionare 1024 byte a 8 kHz, stereo (2 canali) e a 16 bit (2
                                                         1024
byte/campione) equivale a un tempo trascorso di circa: 8000·2·2 = 32ms.


3.5       La riproduzione
Per riprodurre i campioni presenti nel buffer audio (su Line–Out o in cuffia,
eventualmente avendo precedentemente regolato i livelli di volume e i controlli di
tono tramite il mixer) si pu` far uso del seguente frammento di codice:
                            o
int num_ric = <valore>, num_ripr;
if ((num_ripr = write(dspfd, audio_buffer, num_ric)) == -1)
    errore("write()");
ove num_ric ` la quantit` di byte da scrivere sul device audio (aperto in O_WRONLY
              e           a
o O_RDWR) e num_ripr ` la quantit` effettivamente scritta.
                        e           a
    Le considerazioni da fare per la riproduzione sono del tutto analoghe a quelle
gi` fatte per quanto riguarda il campionamento.
  a

                                           40
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                       3.6. IL FORMATO AUDIO




3.6        Il formato audio
Per formato audio si intende la codifica con la quale i campioni sono rappresentati;
` un parametro che ha diretta conseguenza sia sulla qualit` del segnale audio
e                                                             a
campionato o riprodotto che sul data rate. Scegliendo il formato in pratica si
scelgono la risoluzione del campione (numero di bit), il tipo di legge con cui lo
si rappresenta (lineare, logaritmica A–Law o µ–Law, IMA ADPCM4 ) e la sua
codifica nel buffer audio (signed o unsigned, little o big endian); per predisporlo
si pu` usare il seguente codice:
     o
int formato = <formato audio>;
ioctl(dspfd, SNDCTL_DSP_SETFMT, &formato);
if (formato != <formato audio>) {
    /* Il formato audio non e’ supportato dall’hardware */
    /* o dal driver in software                         */
}
SOUND_PCM_SETFMT ` un alias di SNDCTL_DSP_SETFMT; <formato audio> ` uno
                 e                                                e
dei seguenti:
        Identificatore     No bit   Formato audio
        AFMT_MU_LAW         8      µ–Law (logaritmica)
        AFMT_A_LAW          8      A–Law (logaritmica)
        AFMT_IMA_ADPCM     ≈4      campioni a 16 bit compressi mediamente 4:1
        AFMT_U8             8      lineare unsigned
        AFMT_S16_LE        16      lineare signed little endian
        AFMT_S16_BE        16      lineare signed big endian
        AFMT_S8             8      lineare signed
        AFMT_U16_LE        16      lineare unsigned little endian
        AFMT_U16_BE        16      lineare unsigned big endian
        AFMT_MPEG           ?      audio MPEG 2
      Per interrogare OSS sul formato audio attualmente in uso si pu` usare:
                                                                    o
int formato = AFMT_QUERY;
ioctl(dspfd, SNDCTL_DSP_SETFMT, &formato);
in formato ` ritornato l’identificatore relativo.
             e
    Se il formato che si vuole utilizzare non ` supportato dalla scheda audio o
                                               e
dal driver si pu` decidere di uscire dal programma, si pu` utilizzare il formato
                 o                                          o
ritornato da OSS in formato (` quello che pi` si avvicina a quanto richiesto) o
                                 e             u
si pu` eseguire una conversione all’interno del proprio programma. Quest’ultima
     o
opzione aggiunge dell’overhead sulla CPU, come l’emulazione in software di un
formato da parte del driver, per cui sarebbe meglio utilizzare i formati diretta-
mente supportati in hardware quanto pi` ` possibile; per sapere quali questi siano
                                         ue
si pu` utilizzare il seguente frammento di codice:
     o
  4
    IMA ` l’acronimo di Interactive Multimedia Association; il formato ADPCM da questa
         e
proposto ` incompatibile con quello supportato dalla Sound Blaster 16
         e

                                         41
3.6. IL FORMATO AUDIO                  CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




int formato;
ioctl(dspfd, SNDCTL_DSP_GETFMTS, &formato);
if (formato & <formato audio>) {
    /* La scheda audio supporta il formato */
}
ove <formato audio> ` uno degli identificatori della tabella precedente; la chia-
                         e
mata SOUND_PCM_GETFMTS ` un alias di SNDCTL_DSP_GETFMTS.
                             e
    ` bene tenere presente una cosa: la riproduzione di un formato non supportato
    E
consiste in genere solo in rumore ad alto volume, che pu` danneggiare altoparlanti
                                                        o
e orecchie.

3.6.1     Little e big endian
Con il campionamento a 16 bit bisogna fare attenzione, in quanto la rappresen-
tazione del campione non ` portabile. Per molti microprocessori e schede audio
                             e
` in little endian (ad esempio, per un processore della famiglia Intel x86 e una
e
Sound Blaster compatibile), il che non crea problemi; per altri processori ` bige
endian (PowerPC, Sparc, HP–PA), per cui ` possibile avere una scheda audio
                                                e
che codifica i campioni in little endian su una macchina big endian: la ripro-
duzione di campioni in tali situazioni “miste” conduce allo stesso risultato che si
ha riproducendo un formato non supportato.
    In generale, siccome si ` gi` visto come verificare il supporto dei formati per
                             e a
quanto riguarda il driver, sorge la necessit` di individuare che tipo di architettura
                                            a
sia quella per la quale si sta compilando il proprio sorgente, little o big endian:
   • se si sta utilizzando come compilatore il gcc (il che ` molto probabile se si
                                                           e
     ha Linux, ma non ` detto con altri sistemi operativi), nella distribuzione
                          e
     di questo ` incluso l’header file endian.h, il quale definisce il tipo di ar-
                e
     chitettura della macchina sulla quale si sta compilando; il suo utilizzo ` il
                                                                              e
     seguente:

      #ifdef __GLIBC__         /* con la GNU C library */
      #include <endian.h>
      #endif
      #if __BYTE_ORDER == __LITTLE_ENDIAN
         /* Architettura little endian */
      #else
         /* Architettura big endian */
      #endif

   • si pu` creare una funzione come quella che segue:
          o

      int isLittleEndian()         /* Ritorna TRUE se little endian */
      {

                                         42
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                            3.6. IL FORMATO AUDIO




           int numero = 0x12345678;
           char *pnumero = (char *)&numero;
           return(*pnumero == 0x78);
       }


3.6.2      La codifica lineare
Dal campionamento di un segnale audio (supposto per il momento mono/8 bit)
si ottiene una sequenza di numeri rappresentante i livelli del segnale negli istanti
di campionamento. Nel formato AFMT_U85 tali ampiezze sono codificate come
unsigned char: ci` implica che il livello zero ` a 128, il massimo a 255 e il
                    o                           e
minimo a 0. Per convertire in signed char si pu` usare:
                                                o

unsigned char campione_unsigned = <valore unsigned>;
signed char campione_signed = campione_unsigned ^ 128;

per fare la trasformazione inversa:

signed char campione_signed = <valore signed>;
unsigned char campione_unsigned = campione_signed ^ 128;

sono operazioni equivalenti rispettivamente sommare 128 a campione_unsigned
e sottrarre 128 a campione_signed.
    I campioni a 16 bit sono attualmente rappresentabili come signed short (nei
PC, ma ci` potrebbe non essere pi` vero su altre macchine o in futuro con architet-
          o                         u
ture a 64 bit); nel caso si volesse una rappresentazione come unsigned short si
dovrebbe fare:

signed short campione_signed = <valore signed>;
unsigned short campione_unsigned = campione_signed ^ 0x00008000;

per fare la trasformazione inversa:

unsigned short campione_unsigned = <valore unsigned>;
signed short campione_signed = campione_unsigned ^ 0x00008000;

sono operazioni equivalenti rispettivamente sommare 32768 a campione_unsigned
e sottrarre 32768 a campione_signed.
    Nel caso un algoritmo di sintesi abbia prodotto una sequenza di campioni a
16 bit (che si supporranno short), bisogna porli nel buffer audio con la codifica
AFMT_S16_LE; un esempio di codice che fa ci`, supponendo il buffer stereo ` il
                                              o                             e
seguente:
   5
    Questo ` il formato “standard Sound Blaster”, nel senso che la maggior parte delle schede
            e
audio lo supporta in hardware di default; altre schede supportano solo il formato a 16 bit

                                             43
3.7. IL TEMPO REALE                        CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




short campioni[DIM_BUF];             /* Campioni sintetizzati */
unsigned char audio_buffer[DIM_BUF * 2];      /* Buffer audio */
int i, j = 0;

  /* Calcola i campioni e li mette in campioni[], */
  /* disposti LRLRLR... se il buffer e’ stereo    */

for (i=0; i<DIM_BUF; i++) {
  audio_buffer[j++] = (unsigned char)(campioni[i] & 0x00ff);
  audio_buffer[j++] = (unsigned char)((campioni[i] >> 8) & 0x00ff);
}

write(dspfd, audio_buffer, DIM_BUF * 2);                         /* Riproduzione */

ove DIM_BUF ` una dimensione per il buffer secondo i suggerimenti della Sezione 3.2.
             e
Uno stralcio di codice che compie l’operazione opposta ` invece:
                                                        e

read(dspfd, audio_buffer, DIM_BUF * 2);      /* Campionamento */
j = 0;
for (i=0; i<DIM_BUF; i++)
  campioni[i] = audio_buffer[j++]|((audio_buffer[j++]<<8)&0xff00);

i campioni si ritrovano disposti LRLRLR. . . in campioni[] se il campionamento
` stereo.
e


3.7       Il tempo reale
In questo contesto per “sistema in tempo reale” s’intende un elaboratore con
capacit` di acquisire tramite il dispositivo audio dei dati (campioni o eventi MI-
        a
DI), di compiere delle elaborazioni in base a questi e intraprendere delle azioni
(ad esempio, riproduzione di campioni o output di eventi MIDI) in un tempo
prevedibile (ancorch´ breve).
                      e
    Linux ` strutturato abbastanza bene da avere tempi di reazione adeguata-
           e
mente corti per quanto riguarda le capacit` di I/O6 , anche se esistono altri tipi
                                              a
di sistemi operativi studiati appositamente per i compiti in tempo reale.
    In campo musicale anche ritardi di poche decine di millisecondi sono avvertibili
da un musicista che sta suonando, il che falsa le sensazioni che ha in ritorno dal suo
strumento (in questo caso il sistema elaboratore pi` dispositivo di input MIDI),
                                                      u
rendendolo probabilmente “fastidioso” dal punto di vista esecutivo.
    Al di l` di fare in modo che non ci siano troppi processi che competano per
           a
la CPU, per migliorare la risposta del sistema si possono implementare delle
opportune strategie nell’utilizzo della libreria di OSS, in modo tale che il processo
   6`
   E in fase di sviluppo una variante del kernel di Linux meglio strutturata per le attivit` in
                                                                                           a
tempo reale

                                              44
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                         3.7. IL TEMPO REALE




che ne sfrutta i servizi non sia frequentemente bloccato e possa massimizzare
l’efficienza del suo algoritmo; si vedr` anche come in un certo caso sia possibile
                                     a
mappare il buffer DMA nel buffer del processo per evitare operazioni di copia dei
campioni fra l’uno e l’altro.


3.7.1     Le capacit` del driver
                    a
Molte delle caratteristiche in seguito introdotte non funzionano con tutte le schede
audio, per cui prima di applicarle bisogna testare se sono supportate dal driver (il
comportamento ` altrimenti indefinito). Ci` si pu` fare con il seguente frammento
                 e                           o      o
di codice:

int cap;
ioctl(dspfd, SNDCTL_DSP_GETCAPS, &cap);
if (cap & <id. capacita’>) {
    /* Il driver supporta tale capacita’ */
}

SOUND_PCM_GETCAPS ` un alias di SNDCTL_DSP_GETCAPS; <id. capacita’> ` uno
                      e                                             e
degli identificatori seguenti:

  Identificatore         Capacit` di. . .
                                 a
  DSP_CAP_DUPLEX        full duplex: bisogna abilitarlo prima, altrimenti il driver
                        potrebbe ritornare che non ` supportato (vedi Sezione 3.8)
                                                     e
  DSP_CAP_REALTIME      tempo reale: l’hardware o Linux hanno una precisione
                        attorno a un paio di campioni nel riportare la posizione
                        del puntatore al buffer DMA in riproduzione usando
                        SNDCTL_DSP_GETOPTR; altrimenti essa ` precisa
                                                                e
                        attorno a un frammento
  DSP_CAP_BATCH         la scheda audio ha un buffer locale in campionamento
                        e/o riproduzione: risulta inaccurata SNDCTL_DSP_GETxPTR
  DSP_CAP_COPROC        coprocessore programmabile (potrebbe essere un DSP):
                        attualmente questo bit ` riservato per uso futuro
                                                e
  DSP_CAP_TRIGGER       triggering: sincronizzazione fra il campionamento e la
                        riproduzione o fra audio e MIDI
  DSP_CAP_MMAP          supporto di mmap(): accesso diretto al buffer DMA in
                        riproduzione e/o campionamento

   Per determinare la versione di questa chiamata si usa:

int versione = cap & DSP_CAP_REVISION;

in versione dovrebbe essere ritornato un numero tra 0 e 255; attualmente `
                                                                         e
ritornato versione==1.

                                         45
3.7. IL TEMPO REALE                    CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




3.7.2     Gestione del buffer DMA
Come ` stato visto nel Capitolo 1, il buffer DMA agisce dietro le quinte per
        e
adattare la velocit` di campionamento/riproduzione all’I/O effettuato sui device
                   a
file dal processo che sfrutta i servizi di OSS. In base a ci` in casi “normali” non
                                                            o
ci si cura del numero di campioni scritti o letti, ma se ci sono stringenti requisiti
di elaborazione in tempo reale, il driver pu` funzionare meglio se ` letto o scritto
                                            o                        e
un frammento per volta.
    Per determinare la dimensione in byte di un frammento ` possibile usare il
                                                                e
codice seguente:
int dim_frammento;
ioctl(dspfd, SNDCTL_DSP_GETBLKSIZE, &dim_frammento);
    Questa chiamata calcola la dimensione del frammento nel caso non fosse stato
fatto prima, per cui la si deve usare solo dopo aver predisposto i parametri di
campionamento, o esplicitamente la dimensione dei frammenti (nonch´ il loro
                                                                       e
numero nel buffer DMA) con la seguente:

int frammento = 0xNNNNDDDD;
ioctl(dspfd, SNDCTL_DSP_SETFRAGMENT, &frammento);

ove frammento ha nella word pi` significativa (NNNN) il massimo numero di fram-
                               u
menti voluti nel buffer (minimo 2, massimo 0x7fff), mentre nella word meno
significativa (DDDD) si ha codificato l’esponente a cui si deve elevare 2 per avere
la dimensione in byte del frammento:

                                  min 16 byte (DDDD = 4)
                      2DDDD =
                                  max dim. totale buffer
                                               2

in frammento il driver ritorna i valori che esso ` stato capace di fornire rispetto
                                                 e
a quanto richiesto.
    Esiste un’altra chiamata, oramai obsoleta rispetto alla precedente, con lo
scopo di stabilire la dimensione/numero dei frammenti nel buffer DMA; ques-
ta richiede come parametro di ioctl() un valore di divisore, che consente di
predisporre la dimensione del frammento da un massimo di 4 kB a un minimo
di 1 kB, se il kernel ` stato configurato per una dimensione del buffer DMA di
                      e
64 kB (il numero dei frammenti varia di conseguenza da 16 a 64). La chiamata
ha la forma:
int div = <valore>;
ioctl(dspfd, SNDCTL_DSP_SUBDIVIDE, &div);
ove SOUND_PCM_SUBDIVIDE ` un alias di SNDCTL_DSP_SUBDIVIDE, mentre <valore>
                           e
pu` essere 1, 2, 4.
   o
    Il primo uso di read() o write() blocca il numero di frammenti nel buffer e
la loro dimensione; per cambiarli ` necessario chiudere e riaprire il device audio,
                                  e

                                         46
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                        3.7. IL TEMPO REALE




per cui SNDCTL_DSP_SETFRAGMENT o SNDCTL_DSP_SUBDIVIDE dovrebbero essere
poste subito dopo la open().
   Non c’` un metodo universale per calcolare la dimensione di un frammento,
          e
a parte la prova sul campo; nella maggior parte dei casi potrebbe essere utile la
seguente formula empirica:
                                    numero canali · byte/campione · fc
          dim. frammento [byte] =
                                                eventi/s
ove eventi/s ` il numero di eventi che bisognerebbe gestire nell’unit` di tempo:
              e                                                       a
nella simulazione di uno strumento musicale questi potrebbero essere gli eventi
MIDI, a cui bisogna prontamente rispondere con la sintesi dei campioni relativi,
ad esempio, a un NOTE ON o a un NOTE OFF. Anche se con una macchina
veloce si pu` scendere alla dimensione minima di 16 byte, un frammento non
             o
dovrebbe essere cos` piccolo da rischiare di incorrere in condizioni di underrun:
                    ı
indicativamente con meno di 128 o 256 byte i frammenti diventano critici da
gestire con una CPU lenta o con molti processi che competono per questa.
    Per ottenere informazioni sull’evoluzione della situazione di I/O nel buffer
(frammenti e byte elaborati) si possono utilizzare le seguenti chiamate:
audio_buf_info IO_info;
ioctl(dspfd, SNDCTL_DSP_GETISPACE, &IO_info);
per il buffer di input, e analogamente per il buffer di output:
audio_buf_info IO_info;
ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &IO_info);
SOUND_PCM_GETISPACE e SOUND_PCM_GETOSPACE sono alias rispettivamente di
SNDCTL_DSP_GETISPACE e SNDCTL_DSP_GETOSPACE; la struct audio_buf_info
` cos` composta:
e    ı
int fragments numero di frammenti che possono essere letti/scritti senza bloc-
      care (sono esclusi i parzialmente riempiti); questo campo ` affidabile solo
                                                                e
      se l’applicazione legge/scrive interi frammenti per volta

int fragstotal numero totale di frammenti allocati per il buffer

int fragsize dimensione di un frammento in byte (valore ritornato dalla chiama-
      ta SNDCTL_DSP_GETBLKSIZE)

int bytes numero di byte che possono essere letti/scritti immediatamente sen-
     za bloccare, tenendo conto anche dei frammenti parzialmente riempiti; di
     conseguenza si pu` verificare che bytes>fragments*fragsize
                       o
queste chiamate possono essere sfruttate per scrivere applicazioni non bloccanti.
La dimensione del buffer (secondo la chiamata, di input o di output) ` pari a
                                                                         e
IO_info.fragstotal * IO_info.fragsize.

                                       47
3.7. IL TEMPO REALE                       CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




    Per avere informazioni sull’evoluzione dinamica dei puntatori DMA al buffer
sono disponibili due chiamate con forma analoga alle SNDCTL_DSP_GETxSPACE; i
valori ritornati hanno una precisione attorno a un paio di campioni in difetto,
quando funzionano: infatti buffer locali nelle schede audio e imprecisioni nel-
l’hardware possono contribuire a ridurre la precisione attorno a un frammento, e
inoltre alcuni sistemi operativi non consentono di ottenere i valori dei puntatori
DMA7 . Le chiamate sono:
count_info cinfo;
ioctl(dspfd, SNDCTL_DSP_GETIPTR, &cinfo);
per il buffer di input, e analogamente per il buffer di output:
count_info cinfo;
ioctl(dspfd, SNDCTL_DSP_GETOPTR, &cinfo);
ove le SOUND_PCM_GETIPTR e SOUND_PCM_GETOPTR sono alias rispettivamente di
SNDCTL_DSP_GETIPTR e SNDCTL_DSP_GETOPTR; la struct count_info ` cos` com-
                                                                e   ı
posta:
int bytes numero di byte elaborati dall’apertura del device; ` un valore preciso
                                                             e
     se ` preciso il valore determinabile del puntatore DMA
        e
int blocks numero di transizioni di frammento (interrupt hardware) dalla prece-
     dente chiamata a SNDCTL_DSP_GETxPTR (il valore di questo campo ` azzer-
                                                                         e
     ato dopo quest’ultima ed ` valido solo se si usa l’accesso diretto al buffer
                               e
     DMA); potrebbe essere usato per rilevare condizioni di underrun
int ptr puntatore in byte alla posizione corrente nel buffer DMA dal suo inizio;
      nel caso non sia possibile accedere al puntatore DMA attuale questo valore
      ` troncato al bordo del frammento
      e
              bytes             · 1000 fornisce un timer (in ms) abbastanza preciso,
numero canali·byte/campione fc
in teoria: fatte salve le considerazioni riguardo alla disponibilit` dei puntatori
                                                                    a
DMA, la precisione ` compromessa dal verificarsi di condizioni di overrun, under-
                     e
run e dalle chiamate a SNDCTL_DSP_RESET, SNDCTL_DSP_POST e SNDCTL_DSP_SYNC.

3.7.3      I/O non bloccante
La possibilit` di effettuare l’I/O non bloccante sui device audio ` una caratter-
              a                                                  e
istica fondamentale ove si voglia creare un sistema efficiente di sintesi in tempo
reale: infatti quel tempo che il processo eventualmente avrebbe perso in stato di
wait per ottenere la disponibilit` del device, pu` essere impiegato utilmente in
                                  a               o
altre attivit`, ad esempio calcolando nuovi campioni.
             a
    Essenzialmente ci sono tre metodi per gestire l’I/O non bloccante:
   7
    Queste chiamate possono di conseguenza essere non portabili fra i vari sistemi operativi,
n´ compatibili con tutte le schede audio
 e

                                             48
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                                3.7. IL TEMPO REALE




    • uso della chiamata SNDCTL_DSP_NONBLOCK, per la riproduzione di piccoli
      effetti audio

    • uso delle chiamate a SNDCTL_DSP_GETxSPACE o a SNDCTL_DSP_GETxPTR, per
      poter calibrare accuratamente la quantit` di byte da porre in I/O tramite
                                              a
      read()/write()

    • uso della funzione di libreria select(), per effettuare un polling con time–
      out sui device audio

   Il primo metodo funziona solo in riproduzione, la read() ritorna sempre senza
aver eseguito il campionamento; nel flusso di un programma pu` essere posta
                                                                  o
ovunque la chiamata:

ioctl(dspfd, SNDCTL_DSP_NONBLOCK);

ove SOUND_PCM_NONBLOCK ` un alias di SNDCTL_DSP_NONBLOCK; essa istruisce il
                           e
driver a far ritornare immediatamente la write() facendo trasferire nel buffer
DMA una quantit` massima di campioni pari allo spazio attualmente disponibile.
                   a
Ci` implica che una sequenza troppo ravvicinata di write(), o la scrittura di
   o
troppi campioni, potrebbe saturare il buffer DMA8 ; in tal caso la scrittura che
provoca quest’evento ` troncata secondo lo spazio disponibile nel buffer, mentre
                      e
le seguenti sono ignorate finch´ ` disponibile nuovo spazio libero.
                              ee

    Il secondo metodo consiste nell’ottenere informazioni dal driver sull’evoluzione
della situazione nel buffer DMA; il trasferimento dei campioni pu` essere eseguito
                                                                   o
secondo la quantit` di spazio disponibile nel buffer:
                   a

int dspfd;
unsigned char buf[DIM_BUF];       /* DIM_BUF qualunque */
unsigned char *p_buf = buf;
const unsigned char *p_fine_buf = buf + sizeof(buf);
audio_buf_info ainfo;

/* Apre /dev/dsp e riempie il buffer */
/* del processo buf[] di campioni    */

while (p_buf < p_fine_buf) {
    ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo);
    if (ainfo.bytes) {      /* C’e’ spazio disponibile */
        if (ainfo.bytes > (p_fine_buf - p_buf))
            ainfo.bytes = p_fine_buf - p_buf;
        write(dspfd, p_buf, ainfo.bytes);
   8
    Questa chiamata ` quindi usata in quei casi in cui bisogna riprodurre piccoli effetti sonori
                    e
abbastanza distanziati l’uno dall’altro in termini temporali; l’“abbastanza” dipende dalla
frequenza di campionamento, dallo spazio nel buffer DMA e dalla quantit` di campioni scritti
                                                                         a

                                              49
3.7. IL TEMPO REALE                  CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




          p_buf += ainfo.bytes;
     }
     else { /* Fa qualcos’altro - il device e’ occupato */
     }
}

    Un altro modo consiste nel trasferire un frammento per volta:

int dspfd, nfram = 0;
unsigned char buf[DIM_BUF];         /* DIM_BUF dim. di un frammento */
audio_buf_info ainfo;

/* Apre /dev/dsp e predispone la dimensione */
/* di un frammento a DIM_BUF                */

while (nfram < NUM_FRAMMENTI) {
    ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo);
    if (ainfo.fragments && <buf[] riempito>) {
        write(dspfd, buf, DIM_BUF);   /* Scrive un frammento */
        nfram++;
    }
    else { /* Fa qualcos’altro - il device e’ occupato */
           /* oppure non ci sono campioni in buf[]     */
    }
}

ove NUM_FRAMMENTI ` il numero di frammenti di dimensione DIM_BUF da ripro-
                      e
durre. Gli esempi per il campionamento sono analoghi, sostituendo alla chiamata
SNDCTL_DSP_GETOSPACE la SNDCTL_DSP_GETISPACE.
    In generale ` possibile affermare che la prima metodologia ` preferibile quando
                e                                             e
bisogna trasferire una grande quantit` di campioni gi` pronti al buffer DMA per
                                       a               a
la riproduzione, in quanto verranno generate meno interruzioni del flusso del
programma (viene copiata inizialmente una gran quantit` di dati, poi quanto
                                                            a
basta per tenere il buffer pieno); la seconda metodologia ` preferibile quando i
                                                            e
campioni siano sintetizzati in tempo reale (specie se la dimensione dei frammenti
diventa piccola), in quanto la prima metodologia potrebbe generare dei frammenti
solo parzialmente riempiti, con uno sfruttamento non ottimale del buffer DMA.
Questi discorsi rimangono validi anche per il campionamento, con gli opportuni
adattamenti del caso.
    La gestione di campionamento/riproduzione per frammenti impegna di pi` la  u
CPU di quanto faccia la prima metodologia, con questa tendenza che aumenta al
diminuire della dimensione dei frammenti; tuttavia essa garantisce al sistema una
risposta pi` pronta, a patto che l’intero processo di consumazione/creazione di
           u
campioni sia abbastanza veloce da non generare condizioni di overrun/underrun.


                                       50
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                        3.7. IL TEMPO REALE




    Il terzo metodo implica l’utilizzo di select() per effettuare il polling (bloc-
cante o meno fino a un determinato tempo massimo) sui device file di OSS al
fine di testare la disponibilit` alla lettura e/o scrittura oppure al fine di rilevare
                              a
l’insorgere di eccezioni. Volendo sfruttare il time–out ` necessario includere gli
                                                          e
header file time.h e types.h; un esempio di utilizzo ` il seguente:
                                                        e
int retval;
retval = select(fd+1, &readset, &writeset, &exceptset, &timeout);
essa opera su degli insiemi di file descriptor, cio` pu` essere usata per controllare
                                                  e o
pi` device file contemporaneamente: readset raccoglie quelli aperti in lettura,
  u
writeset quelli aperti in scrittura e exceptset quelli per i quali si voglia cat-
turare l’insorgere di un’eccezione. Quest’ultima ` pi` facile che insorga su file
                                                     e u
descriptor che si riferiscano a pipe o socket, non a device audio. Se uno o due
dei tre insiemi sono mancanti, vi si pu` sostituire NULL. Nel seguente esempio di
                                        o
codice ` mostrato come aggiungere il file descriptor dspfd, aperto in lettura, a
        e
readset:
fd_set readset;
FD_ZERO(&readset);               /* Azzera l’insieme */
FD_SET(dspfd, &readset);         /* Aggiunge dspfd   */
nel caso dspfd sia aperto con O_RDWR, readset ` influenzato solo nel caso di
                                                      e
disponibilit` di campioni nel buffer di input, separato da quello di output; per
            a
testare la disponibilit` alla scrittura su quest’ultimo bisogner` aggiungere dspfd
                       a                                        a
a un eventuale writeset.
    Quando un device file ` causa di un evento che fa ritornare select() la
                              e
macro FD_ISSET(filedes, &filedes_set) ritorna un valore diverso da zero; in
tal caso retval ` il numero di file descriptor che hanno cambiato stato all’uscita
                 e
da select(), mentre fd ` il massimo fra i file descriptor dei tre insiemi. La macro
                          e
FD_CLR(filedes, &filedes_set) rimuove il file descriptor filedes dall’insieme
filedes_set.
    Il tempo massimo che select() attende per il verificarsi di un evento su uno
degli insiemi ` stabilito dalla struct timeout come nel seguente esempio:
              e
struct timeval timeout;
timeout.tv_sec = 1;            /* secondi      */
timeout.tv_usec = 500;         /* microsecondi */
in questo caso select() attenderebbe al massimo 1.5 secondi prima di ritornare
(con retval==0) non verificandosi un evento. In Linux la struct timeout `           e
modificata all’uscita con la quantit` di tempo non atteso: questo non ` il com-
                                    a                                     e
portamento della versione POSIX di select(), quindi per produrre programmi
portabili bisogner` assumere che timeout abbia un valore indefinito all’uscita da
                   a
questo, reinizializzandola prima del riutilizzo. Un NULL al posto di &timeout fa
attendere indefinitamente il verificarsi di un evento sugli insiemi, mentre il polling
` non bloccante ponendo:
e

                                        51
3.7. IL TEMPO REALE                  CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




timeout.tv_sec = timeout.tv_usec = 0;
   Un esempio dell’utilizzo di select() ` riportato a pagina 108.
                                        e

3.7.4    La sincronizzazione
Per sincronizzazione si intende la possibilit` di fare andare in sincrono eventi
                                               a
audio con altri eventi, esterni o interni a OSS: ad esempio I/O da disco, eventi
MIDI, campionamento e riproduzione.
    Prima di applicare le capacit` di sincronizzazione (triggering) del driver,
                                    a
bisogna controllare se queste siano supportate o meno testando DSP_CAP_TRIGGER,
altrimenti il comportamento di OSS ` indefinito. Nel caso particolare del full du-
                                       e
plex, se campionamento e riproduzione devono andare in sincrono, bisogna testare
anche DSP_CAP_DUPLEX come descritto nella Sezione 3.8.
    Per impostare il trigger si usa il seguente frammento di codice:
int trigger = <enable bits>;
if (ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger) == -1)
    errore("SNDCTL_DSP_SETTRIGGER");
ove SOUND_PCM_SETTRIGGER ` un alias di SNDCTL_DSP_SETTRIGGER; la bitmask
                              e
<enable bits> ` una fra PCM_ENABLE_INPUT e PCM_ENABLE_OUTPUT, per abil-
                  e
itare rispettivamente campionamento e riproduzione. Un OR di queste due abili-
ta contemporaneamente campionamento e riproduzione nel caso di full duplex,
mentre se trigger ` posto a 0 vengono disabilitate entrambe; per disabilitarle
                     e
singolarmente si usano ~PCM_ENABLE_INPUT e ~PCM_ENABLE_OUTPUT.
    Un esempio di utilizzo ` di disabilitare la riproduzione, scrivere sul device
                            e
audio dei campioni e riabilitarla, con write() che ritorna -1 (errno==EAGAIN)
se il buffer DMA si riempie; la read() ritorna sempre -1 (errno==EAGAIN) se il
campionamento non ` abilitato. Dopo l’apertura del device audio con O_RDWR
                       e
campionamento e riproduzione sono abilitate per default.
    Per conoscere lo stato di abilitazione di campionamento e riproduzione `    e
disponibile la seguente chiamata:
int trigger;
ioctl(dspfd, SNDCTL_DSP_GETTRIGGER, &trigger);
if (trigger & <enable bits>) {
    /* Sono abilitati campionamento o riproduzione */
    /* secondo l’identificatore <enable bits>      */
}
SOUND_PCM_GETTRIGGER ` un alias di SNDCTL_DSP_GETTRIGGER e <enable bits>
                         e
` uno fra gli identificatori PCM_ENABLE_INPUT, PCM_ENABLE_OUTPUT o un OR
e
aritmetico di questi.
    Per sincronizzare campionamento e/o riproduzione con gli eventi MIDI out si
pu` sfruttare la seguente chiamata:
   o

                                       52
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                            3.7. IL TEMPO REALE




ioctl(dspfd, SNDCTL_DSP_SETSYNCRO);

ove SOUND_PCM_SETSYNCRO ` un alias di SNDCTL_DSP_SETSYNCRO; questa disat-
                            e
tiva gli eventi audio, che vengono riattivati solo quando ` utilizzata la macro
                                                          e
SEQ_PLAYAUDIO(). Sfortunatamente quest’ultima, pur essendo elencata in soundcard.h,
non ` ancora stata implementata.
     e


3.7.5     Accesso diretto al buffer DMA
Mappare il buffer DMA nell’area di memoria del processo ` una tecnica che pu`
                                                             e                    o
ottimizzare i tempi del driver: soprattutto se i campioni sono stati prodotti da un
algoritmo di sintesi in tempo reale, pu` essere pi` efficiente eliminarne la copia
                                          o         u
dal/al buffer del processo. In generale ` da evitarne l’uso, a meno di avere dei
                                           e
buoni motivi; infatti questa tecnica ` poco portabile fra varie schede audio, in
                                        e
quanto queste devono rendere il buffer DMA accessibile dalla CPU (ad esempio,
quelle con il chip CS4232) e dev’essere supportata mmap() per la mappatura in
memoria del device file9 (includendo l’header file sys/mman.h). Altri requisiti
sono il supporto del triggering e che la scheda audio non abbia memoria locale
che falsi l’utilizzo delle chiamate a SNDCTL_DSP_GETxPTR.
    Per sfruttare l’accesso diretto al buffer DMA ` necessario compiere una se-
                                                     e
quenza di preliminari:

   • si deve aprire il device audio, tenendo presente che la modalit` O_RDWR
                                                                    a
     implica l’adozione di due buffer DMA separati per la riproduzione e il
     campionamento

   • affinch´ la tecnica sia applicabile, ` essenziale testare le capacit` del driver
           e                            e                              a
     con DSP_CAP_TRIGGER e DSP_CAP_MMAP (la precisione del puntatore al buffer
     DMA ` influenzata da DSP_CAP_REALTIME e DSP_CAP_BATCH); usando il full
           e
     duplex si deve testare anche DSP_CAP_DUPLEX

   • si deve predisporre la frequenza di campionamento con SNDCTL_DSP_SPEED

   • si seleziona la dimensione del frammento con SNDCTL_DSP_SETFRAGMENT;
     ci` influisce sulla velocit` da tenere nella sintesi dei campioni e su quanto
       o                       a
     spesso un’eventuale select() ritorna

   • si calcola la dimensione del buffer DMA con:

      audio_buf_info ainfo;
      int dim_buf_DMA;
      ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo);
      dim_buf_DMA = ainfo.fragstotal * ainfo.fragsize;
  9
   Solo sotto Linux per versioni del driver superiori alla 3.5β7 e sotto FreeBSD, BSD/OS,
UnixWare e Solaris per versioni del driver superiori alla 3.8

                                           53
3.7. IL TEMPO REALE                  CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




   • si alloca in memoria il device file audio con una chiamata a mmap(); uti-
     lizzando il full duplex ci sono due buffer separati per l’I/O, per cui le
     allocazioni da eseguire diventano due

     caddr_t p_buf;    /* Inizio del buffer in memoria */
     if ((p_buf = mmap(NULL, dim_buf_DMA, prot, MAP_FILE|MAP_SHARED,
                       dspfd, 0)) == (caddr_t) -1)
         errore("mmap()");

     il primo parametro di mmap() ` un suggerimento sull’inizio dell’area di
                                     e
     memoria, l’ultimo ` un offset rispetto a questo: di solito si pongono rispeti-
                       e
     vamente a NULL e 0; l’argomento prot seleziona il tipo di buffer da allocare:
     PROT READ buffer di input
     PROT WRITE buffer di output
     PROT READ|PROT WRITE buffer di output nel caso di BSD, men-
        tre in Linux tale combinazione ` ammessa per versioni di OSS superiori
                                       e
        alla 3.8β16 (in BSD il solo PROT_WRITE causerebbe un errore di seg-
        mentazione/bus); nel caso di apertura del device file non in O_RDWR
        questa combinazione ha un risultato indefinito
   • poich´ il driver non permette l’uso di read()/write() con il device file
           e
     allocato in memoria, per iniziare le operazioni si devono resettare i bit di
     enable per la sincronizzazione:

     int trigger = 0;
     ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger);

     nel caso si debba fare in sincrono output di eventi MIDI si sfrutta invece:
     ioctl(dspfd, SNDCTL_DSP_SETSYNCRO);
   • devono essere scritti dei campioni nel buffer prima di iniziare la ripro-
     duzione, per evitare che l’equivalente di una condizione di underrun si
     verifichi subito; quando si ` pronti si avvia il driver con:
                                e

     int trigger = PCM_ENABLE_OUTPUT; /* | PCM_ENABLE_INPUT */
     ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger);

    L’accesso al buffer ` possibile solo tramite puntatori: l’inizio dell’area di
                        e
memoria ` p_buf, mentre la fine ` data da p_buf+(caddr_t)(dim_buf_DMA-1); la
          e                     e
posizione del puntatore DMA di riproduzione o campionamento ` ritornata dalle
                                                               e
chiamate SNDCTL_DSP_GETxPTR, fatte salve le considerazioni della Sezione 3.7.2
sulla sua precisione.
    In riproduzione risulta efficiente scrivere nel buffer un frammento per volta
e parte del frammento seguente (play ahead ), per fare in modo che non ci siano

                                       54
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                                  3.8. IL FULL DUPLEX




“buchi” nella riproduzione in casi di rallentamento nella produzione di campioni
da parte del processo. Prima della sua scrittura ci si deve allineare ai bordi del
frammento nel buffer, tenendo cura di non eccedere i limiti dell’area di memoria
allocata: un errore di scrittura/lettura anche di un solo byte al di fuori causa un
errore di segmentazione/bus; per allinearsi ai bordi del frammento si pu` usare il
                                                                           o
codice seguente:

count_info cinfo;                          /* Buffer di output */
ioctl(dspfd, SNDCTL_DSP_GETOPTR, &cinfo);
cinfo.ptr = (cinfo.ptr / dim_frammento) * dim_frammento;

ove dim_frammento ` la dimensione di un frammento in byte.
                       e
    Nell’utilizzo normale del driver (non allocando il device file in memoria) dopo
che un frammento ` stato riprodotto esso ` azzerato automaticamente, in modo
                     e                       e
tale da evitare la riproduzione di campioni “vecchi” (looping) nel caso di pause
o rallentamenti nella produzione di campioni: ` il caso di considerare se non sia
                                                 e
conveniente effettuare una tale operazione anche con l’accesso diretto, poich´ se
                                                                               e
non si aggiorna il buffer i suoi contenuti saranno riprodotti a ciclo continuo a
meno di fermare il driver con SNDCTL_DSP_TRIGGER.
    Un’altra differenza rispetto al normale utilizzo ` data dal fatto che i campioni
                                                    e
vengono presi dal buffer e sono inviati direttamente alla scheda audio, senza che
siano effettuate conversioni di formato intermedie: di conseguenza i campioni
devono essere in un formato supportato direttamente in hardware dalla scheda.


3.8       Il full duplex
Con le schede audio che non lo supportano in hardware ` possibile solo un’ap-
                                                            e
prossimazione di full duplex: conviene campionare e alternativamente riprodurre,
ogni volta chiudendo e riaprendo il device audio rispettivamente in O_RDONLY e
O_WRONLY, invece di aprirlo in O_RDWR (ci` ottimizza il comportamento di OSS).
                                           o
    ` anche possibile avere due schede audio half duplex installate nel comput-
    E
er, usandone una in campionamento e l’altra in riproduzione10 : ci` a patto che
                                                                    o
entrambe supportino precisamente la stessa frequenza di campionamento e la
capacit` di triggering.
        a
    Se la scheda audio ` per` una vera full duplex ed ` supportata questa capacit`
                        e   o                         e                          a
dal driver, prima di sfruttarla bisogna compiere i seguenti preliminari:

    • si deve aprire il device audio con O_RDWR

    • si attiva il full duplex con ioctl(dspfd, SNDCTL_DSP_SETDUPLEX,0); se
      non si effettua quest’operazione prima, testando DSP_CAP_DUPLEX verr`a
      riportato che il driver non supporta il full duplex
  10
    Volendo sottilizzare, in questo caso non si tratterebbe di full duplex vero e proprio, ma di
uso simultaneo di due device audio

                                              55
3.9. USO DI COPROCESSORI              CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




   • si predispongono i parametri di campionamento e/o la dimensione dei fram-
     menti
    A questo punto si pu` leggere/scrivere sul device audio, a patto che queste
                          o
operazioni siano sincronizzate e non bloccanti: infatti leggendo e scrivendo la
stessa quantit` di campioni, nonch´ evitando condizioni di underrun e overrun,
              a                    e
si mantiene un preciso riferimento temporale nell’I/O.


3.9     Uso di coprocessori
OSS supporta un modello di programmazione per eventuali coprocessori o DSP
aggiuntivi su schede audio high–end; ad esempio, questi potrebbero essere imp-
iegati per la creazione di effetti di spazializzazione. Il test di DSP_CAP_COPROC
rivela se un coprocessore ` presente sulla scheda.
                           e
    Per resettare il coprocessore si pu` utilizzare la seguente chiamata:
                                       o
ioctl(dspfd, SNDCTL_COPR_RESET);
   Il coprocessore ` programmabile, e come tale dispone di memoria locale per
                   e
dati e programmi; OSS definisce un comando per effettuare il caricamento di
questi nella memoria:
copr_buffer cbuf;
if (ioctl(dspfd, SNDCTL_COPR_LOAD, &cbuf) == -1)
    errore("SNDCTL_COPR_LOAD");
ove la struct copr_buffer ` cos` fatta:
                          e    ı
int command se non si usa ` posto a 0
                          e
int flags il caricamento di un programma si spezza in blocchi da 4000 byte qualo-
     ra sia pi` lungo di questa dimensione: il blocco iniziale lo si indica ponendo
              u
     flags=CPF_FIRST, il blocco finale lo si indica con flags=CPF_LAST, gli even-
     tuali blocchi in mezzo con flags=CPF_NONE; se un blocco di programma ` di  e
     dimensione inferiore a 4000 byte, e come tale pu` essere caricato in un’unica
                                                      o
     soluzione, lo si indica con flags=CPF_FIRST|CPF_LAST
int len lunghezza in byte del blocco di programma (max 4000)
int offs il blocco di programma potrebbe avere in testa un’area dati, e in tal caso
     offs dice al coprocessore dove sia l’effettivo punto d’inizio del programma,
     in byte dall’inizio; ` dipendente dal coprocessore e di solito si pone a 0 se
                          e
     non ` usato
          e
unsigned char data[4000 ] in questo array viene posto il blocco di programma
   Il programma per il coprocessore ` di solito generato a parte utilizzando un
                                     e
cross–assembler; segue un esempio di caricamento:

                                        56
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE                   3.9. USO DI COPROCESSORI




int dspfd, c_fd;
copr_buffer cbuf;

/*   Si suppone che il codice risieda su disco e che             */
/*   si sia gia’ aperto il file che lo contiene, con             */
/*   file descriptor c_fd; in quest’esempio il codice            */
/*   e’ lungo meno di 4000 byte                                  */

cbuf.command = cbuf.offs = 0;
cbuf.flags = CPF_FIRST | CPF_LAST;       /* Unico blocco */
cbuf.len = read(c_fd, cbuf.data, 4000); /* Carica da disco */
if ((cbuf.len == -1) || (cbuf.len == 4000))
    errore("Caricamento programma coprocessore");
if (ioctl(dspfd, SNDCTL_COPR_LOAD, &cbuf) == -1)
    errore("SNDCTL_COPR_LOAD");
   Sono definiti dei comandi per il debug dei programmi del coprocessore, che
hanno il seguente prototipo di utilizzo:
copr_debug_buf dbuf;
if (ioctl(dspfd, SNDCTL_COPR_****, &dbuf) == -1)
    errore("SNDCTL_COPR_****");
ove la struct copr_debug_buf ` cos` fatta:
                             e    ı
int command si pone a 0, essendo usato internamente dal driver

int parm1 1o parametro; se usato in coppia con parm2 generalmente specifica
     un indirizzo nella memoria del coprocessore

int parm2 2o parametro; generalmente ` un dato da scrivere (o letto) in memoria
                                     e
     del coprocessore

int flags eventuale registro dei flag del coprocessore

int len lunghezza di parm2 in byte
     I comandi SNDCTL_COPR_**** per il debug sono:
 Comandi                parm1      parm2     Azione
 SNDCTL_COPR_RDATA     indirizzo   parola    Ritorna una parola dati dall’indirizzo
 SNDCTL_COPR_RCODE     indirizzo   parola    Ritorna una parola di codice macchina
                                             dall’indirizzo
 SNDCTL_COPR_RUN          —          —       Esegue un programma
 SNDCTL_COPR_HALT         —          —       Arresta un programma
 SNDCTL_COPR_WDATA     indirizzo   parola    Scrive una parola dati all’indirizzo
 SNDCTL_COPR_WCODE     indirizzo   parola    Scrive una parola di codice macchina
                                             all’indirizzo

                                        57
3.10. NUOVE CARATTERISTICHE               CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




   Nel caso il coprocessore lo consenta, ` previsto un modo per inviare/ricevere
                                         e
        11
messaggi al/dal coprocessore:

copr_msg cmsg;
if (ioctl(dspfd, SNDCTL_COPR_****, &cmsg) == -1)
    errore("SNDCTL_COPR_****");

i comandi sono rispettivamente SNDCTL_COPR_SENDMSG e SNDCTL_COPR_RCVMSG;
la struct copr_msg ` cos` fatta:
                     e   ı

int len lunghezza in byte del messaggio (max 4000)

unsigned char data [4000 ] messaggio arbitrario, con significato dipendente
     dal coprocessore


3.10        Nuove caratteristiche
   • ` stato aggiunto l’identificatore di formato audio AFMT_S16_NE, che assume
     e
     il valore AFMT_S16_LE se il computer su cui ` compilato il codice sorgente
                                                   e
     ` little endian, mentre assume il valore AFMT_S16_BE se il computer ` big
     e                                                                    e
     endian:

       if (AFMT_S16_NE == AFMT_S16_LE) {
           /* Architettura little endian             */
           /* Intel, Alpha                           */
       }
       else { /* Architettura big endian             */
              /* Sparc, HP-PA, PowerPC               */
       }

   • ` stata aggiunta la chiamata SNDCTL_DSP_GETODELAY, che ha lo scopo di
     e
     ritornare la quantit` di campioni in byte non riprodotti attualmente nel
                         a
     buffer di output:

       int nbyte;
       ioctl(dspfd, SNDCTL_DSP_GETODELAY, &nbyte);

       per la versione 3.5.4 di OSS questa chiamata ` approssimativamente ripro-
                                                    e
       ducibile con il seguente codice:
  11
     Per messaggio si intende un pacchetto di dati scambiato tra programma e coprocessore;
ad esempio, potrebbe essere una copia dei registri di quest’ultimo, in modo da “fotografare”
l’evoluzione attuale del codice macchina

                                            58
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE              3.11. ESEMPIO DI PROGRAMMA




     audio_buf_info ainfo;
     int nbyte;
     ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo);
     nbyte = ainfo.fragstotal*ainfo.fragsize - ainfo.bytes;

   • la chiamata SNDCTL_DSP_PROFILE consente di definire il profilo di un’ap-
     plicazione, ovvero il modo in cui eventuali condizioni di underrun in ripro-
     duzione dovrebbero essere gestite dal driver; queste sono classificabili con i
     seguenti identificatori:

     APF NORMAL applicazioni “normali” (default), che producono campi-
        oni pi` velocemente di quanto il driver riesca a riprodurne; quest’ulti-
              u
        mo azzera il frammento quando ` stato riprodotto, per cui sono pre-
                                          e
        venute le situazioni di looping a fronte di un aumento del tempo per
        la gestione del buffer da parte della CPU
     APF NETWORK gli underrun sono causati da eventi esterni; ad esem-
        pio, si sta riproducendo dell’audio mentre lo si scarica dalla rete, per
        cui il flusso dei pacchetti potrebbe essere discontinuo
     APF CPUINTENS gli underrun sono sporadicamente causati da pro-
        grammi che sfruttano parecchio la CPU, quindi la produzione di cam-
        pioni ` meno veloce della riproduzione; viene disabilitato l’azzeramento
              e
        del buffer, per cui si possono verificare situazioni di looping in seguito
        alle quali ne ` riprodotto il precedente contenuto
                      e

     la chiamata opera nel modo seguente:

     int profilo = APF_****;   /* Uno dei tre profili */
     if (ioctl(dspfd, SNDCTL_DSP_PROFILE, &profilo) == -1)
         errore("SNDCTL_DSP_PROFILE");


3.11      Esempio di programma
Il programma seguente dimostra l’accesso diretto al buffer DMA in campiona-
mento, scaricando su un file l’audio campionato mono/8 bit da uno dei canali
                                 `
del mixer (hard disk recording). E consentita la scelta della frequenza di campi-
onamento e sono stampate informazioni diagnostiche (precisione del puntatore,
dimensione dei frammenti e del buffer, durata del campionamento).
    Il ciclo principale che implementa il campionamento lo fa in maniera non
bloccante, per una durata complessiva di dieci secondi. All’uscita da questo `  e
effettuata una riproduzione normale (write(), utilizzata in maniera bloccante).
    Sia in campionamento che in riproduzione ` lasciata al driver la scelta della
                                               e
dimensione dei frammenti da utilizzare nel buffer.

                                       59
3.11. ESEMPIO DI PROGRAMMA          CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




/*
 *      dirbuf.c - Esempio di accesso diretto al buffer DMA in
 *                 campionamento (mono/8 bit), registrando su hard disk
 *                 per dieci secondi, poi riproducendo i campioni
 *                 caricati da hard disk
 */

#include   <stdio.h>
#include   <unistd.h>
#include   <sys/types.h>
#include   <sys/stat.h>
#include   <fcntl.h>
#include   <sys/soundcard.h>
#include   <sys/mman.h>
#include   <time.h>

/* Definizione di alcune costanti simboliche */
#define FILE_CAMP   "camp.raw"
const int T_CAMP      = 10;
const float N_CANALI = 1.0;
const float BYTE_CAMP = 1.0:
const int DIM_BUF     = 16384;

void errore(const char *msgerr)     /* Gestione degli errori */
{
   perror(msgerr);
   exit(-1);
}

int main()
{
   int dspfd, camp_fd;
   audio_buf_info ainfo;
   count_info cinfo;
   caddr_t p_buf;
   int i, cap, freq_camp, dim_buf_DMA, trigger, buf_ptr;
   time_t tempo_inizio;
   unsigned char out_buf[DIM_BUF];


/* 1^ fase, campionamento: aprire in read-only e poi write-only e’ piu’
            efficiente che aprire il device in read-write nel caso che
            la scheda non sia full duplex o il driver non la supporti */

      if ((dspfd = open("/dev/dsp", O_RDONLY)) == -1)
          errore("/dev/dsp input");


                                     60
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE       3.11. ESEMPIO DI PROGRAMMA




/* Test capacita’ del driver: se non e’ passato, inutile continuare */
   if (ioctl(dspfd, SNDCTL_DSP_GETCAPS, &cap) == -1)
       errore("SNDCTL_DSP_GETCAPS");
   if (!(cap & DSP_CAP_TRIGGER) || !(cap & DSP_CAP_MMAP))
       errore("Accesso diretto non supportato!");
   if (!(cap & DSP_CAP_REALTIME))
       puts("Il puntatore al buffer DMA non e’ preciso.");
   if (cap & DSP_CAP_BATCH)
       puts("La scheda audio ha memoria locale che "
            "falsa la posizione del puntatore.");

/* Immissione della frequenza di campionamento */
   printf("Immettere la frequenza di campionamento [Hz]: ");
   scanf("%d", &freq_camp);
   if (ioctl(dspfd, SNDCTL_DSP_SPEED, &freq_camp) == -1)
       errore("SNDCTL_DSP_SPEED");
   printf("Frequenza di campionamento selezionata: %d Hzn", freq_camp);

/* Calcola la dimensione del buffer DMA di input */
   if (ioctl(dspfd, SNDCTL_DSP_GETISPACE, &ainfo) == -1)
       errore("SNDCTL_DSP_GETISPACE");
   dim_buf_DMA = ainfo.fragstotal * ainfo.fragsize;
   printf("Buffer di input: %d frammenti da %d byte, "
          "per un totale di % d byte.n", ainfo.fragstotal,
          ainfo.fragsize, dim_buf_DMA);

/* Allocazione in memoria del device file audio in input */
   if ((p_buf = mmap(NULL, dim_buf_DMA, PROT_READ, MAP_FILE|MAP_SHARED,
                     dspfd, 0)) == (caddr_t) -1)
       errore("mmap() input buffer");

/* Usa il trigger per disattivare l’input, per abilitarlo in
   seguito all’apertura del file dei campioni su disco */
   trigger = 0;
   if (ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger) == -1)
       errore("SNDCTL_DSP_SETTRIGGER");
   if ((camp_fd = open(FILE_CAMP, O_WRONLY|O_CREAT|O_TRUNC|O_NONBLOCK,
                       S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)) == -1)
       errore("Creazione file dei campioni " FILE_CAMP);
   trigger = PCM_ENABLE_INPUT;                      /* Abilita input */
   ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger);
   printf("Campionamento mono/8 bit avviato per %d secondi...n", T_CAMP);


/* Ciclo di lettura del buffer DMA e scrittura su disco dei campioni */


                                    61
3.11. ESEMPIO DI PROGRAMMA       CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE




   buf_ptr = 0;
   time(&tempo_inizio);
   while (1) {
       ioctl(dspfd, SNDCTL_DSP_GETIPTR, &cinfo);
       if ((cinfo.ptr - buf_ptr) >= ainfo.fragsize)
       {   /* Scrive il frammento su hard disk */
           write(camp_fd, p_buf + buf_ptr, ainfo.fragsize);
           buf_ptr += ainfo.fragsize;
           if (buf_ptr == dim_buf_DMA) /* Raggiunta la fine buffer */
               buf_ptr = 0;
           printf("* ");     /* Visualizza * per ogni frammento scritto */
           fflush(stdout);
       }
       else /* Controlla se sono passati T_CAMP secondi */
            if ((time(NULL) - tempo_inizio) >= T_CAMP)
                break;
   }
   printf("nElaborati %d byte per %.2f secondi.nn", cinfo.bytes,
                    cinfo.bytes / (N_CANALI * BYTE_CAMP * freq_camp));


/* 2^ fase: riproduzione del contenuto del file dei campioni su disco */
   close(dspfd);
   close(camp_fd);
   if ((dspfd = open("/dev/dsp", O_WRONLY)) == -1)
       errore("/dev/dsp output");
   if ((camp_fd = open(FILE_CAMP, O_RDONLY)) == -1)
       errore("Apertura file dei campioni " FILE_CAMP);

/* Predisposizione della frequenza con visualizzazione parametri buffer */
   ioctl(dspfd, SNDCTL_DSP_SPEED, &freq_camp); /* Fc uguale all’input */
   ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo);
   dim_buf_DMA = ainfo.fragstotal * ainfo.fragsize;
   printf("Buffer di output: %d frammenti da %d byte, "
          "per un totale di % d byte.n", ainfo.fragstotal,
          ainfo.fragsize, dim_buf_DMA);
   printf("Avvio riproduzione mono/8 bit a %d Hz...n", freq_camp);

/* Ciclo di scrittura dei campioni prelevati da disco sul buffer DMA */
   while (read(camp_fd, out_buf, DIM_BUF) > 0)
       write(dspfd, out_buf, DIM_BUF);

   close(dspfd);
   close(camp_fd);
   return 0;


                                  62
CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE              3.11. ESEMPIO DI PROGRAMMA




}

    Ci` che segue ` un esempio di output del programma; si noti che esso riporta
      o           e
l’imprecisione del puntatore al buffer DMA per la scheda audio utilizzata e che
per la frequenza selezionata il driver sceglie di utilizzare frammenti da 8 kB per
                                                        `
il campionamento e da 32 kB per la riproduzione. E visualizzato un * per ogni
frammento trasferito su hard disk.
Il puntatore al buffer DMA non e’ preciso.
Immettere la frequenza di campionamento [Hz]: 48000
Frequenza di campionamento selezionata: 44100 Hz
Buffer di input: 8 frammenti da 8192 byte, per un totale di 65536 byte.
Campionamento mono/8 bit avviato per 10 secondi...
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * *
Elaborati 463286 byte per 10.51 secondi.

Buffer di output: 2 frammenti da 32768 byte, per un totale di          65536 byte.
Avvio riproduzione mono/8 bit a 44100 Hz...

    Si pu` anche notare come il driver scelga la massima frequenza che la scheda
         o
audio del sistema su cui ` stato eseguito il programma pu` supportare (44100 Hz),
                         e                               o
rispetto alla frequenza richiesta (48000 Hz), cercando di fornire il valore pi`u
vicino.




                                       63
Capitolo 4

SINTETIZZATORI E MIDI

4.1      I device file del sequencer
L’interfaccia di programmazione di OSS consente di controllare chip sintetizzatori
interni alla scheda audio e porte MIDI in maniera indipendente dal dispositivo
e con una temporizzazione precisa. OSS mette a disposizione due device file:
/dev/music e /dev/sequencer, con il secondo che ha capacit` inferiori rispetto al
                                                               a
primo per quanto riguarda le caratteristiche di temporizzazione, sincronizzazione
con eventi esterni e indipendenza dal dispositivo. Nelle intenzioni dell’autore
di OSS /dev/sequencer si presta alla realizzazione di module player (ovvero di
esecutori dei file .MOD nati in ambiente Amiga con il programma NoiseTracker),
mentre /dev/music ` rivolto alla realizzazione di sequencer generici.
                       e
    L’indipendenza dal dispositivo ` ottenuta scrivendo sui device file un flusso
                                      e
di eventi che il driver interpreta secondo i dispositivi indirizzati, come coman-
di per i primi e come messaggi MIDI per gli strumenti connessi ai canali delle
seconde. Entrambi, in risposta agli eventi, producono output audio o cambi di
configurazione.
    Un evento ` un breve record (di 4 o 8 byte), formattato opportunamente
                 e
dalle macro che fanno parte dell’interfaccia di programmazione di OSS, recante
il messaggio per un dispositivo e i suoi parametri. Ogni messaggio ` capace di
                                                                        e
compiere singole transazioni: ad esempio, pu` selezionare un timbro o iniziare a
                                                o
suonare una nota.
    In analogia alla scrittura dei campioni su /dev/dsp, la scrittura degli eventi su
/dev/music o /dev/sequencer li accoda in un buffer del driver che generalmente
` in grado di ospitare 1024 eventi. A differenza di /dev/dsp lo svuotamento
e
del buffer, l’equivalente della condizione di underrun per il buffer audio DMA,
provoca un errore di ritmo nell’esecuzione e non click o looping del suono; gli
eventi di per s´ non generano campioni, si limitano a pilotare sintetizzatori.
               e
    La capacit` di realizzare un sequencer ` ottenuta tramite la possibilit` di acco-
               a                            e                              a
dare nel buffer degli eventi marcatempo, i quali forniscono un preciso riferimento
                                                                  `
temporale al driver per l’esecuzione degli altri tipi di eventi. E quindi normale
che un evento possa essere eseguito diversi secondi dopo la scrittura sul device file

                                         64
CAPITOLO 4. SINTETIZZATORI E MIDI                          4.1. I DEVICE FILE DEL SEQUENCER




(il ritardo potrebbe anche essere di minuti), per cui l’applicazione deve tenerne
conto se si vuole gestire qualcos’altro in concorrenza con la musica sintetizzata.
    La forma degli eventi ` simile a quella dei messaggi standard MIDI; si prenda
                           e
ad esempio l’evento creato dalla macro SEQ_START_NOTE(), equivalente al NOTE
ON del MIDI:

SEQ_START_NOTE(<device>,             <voce>,        <nota>,    <volume>);

                     ↓        ↓         ↓         ↓
          NOTE ON [porta] → <canale>, <nota>, <velocit`>
                                                      a
    Il <device> identifica il numero del chip sintetizzatore o della porta MIDI:
per il primo tipo di dispositivo questo ` il numero che appare a lato della lista dei
                                        e
“Synth devices:” ottenuta tramite cat /dev/sndstat, mentre per il secondo
tipo ` il numero nella lista dei “Midi devices:” sommato alla quantit` di synth
      e                                                                   a
device presenti nel sistema. Ad esempio, si consideri il seguente stralcio di output
di cat /dev/sndstat:
    .
    .
    .

Synth devices:
0: Yamaha OPL-3

Midi devices:
0: SoundBlaster16 MIDI
1: MQX-32M MIDI Interface #1
    .
    .
    .
    In base a ci` che ` scritto in questa lista il chip sintetizzatore ` uno solo e lo
                o     e                                                e
si indirizza sostituendo 0 a <device>, mentre le porte MIDI si indirizzerebbero
con 1 e 2 (1 synth device + 0 e 1).
    Solo con /dev/sequencer ` possibile utilizzare la macro SEQ_MIDIOUT(), che
                                e
segue invece la numerazione di “Midi devices:”, per poter effettuare l’output
di byte MIDI arbitrari (vedere Sezione 4.6).
    Per la definizione del parametro <voce> si veda la Sezione 4.1.2, mentre
<nota> e <volume> sono entrambi definiti nell’intervallo 0 ÷ 127; per il MIDI
l’ultimo parametro corrisponde al key velocity.

4.1.1      I chip sintetizzatori
I chip sintetizzatori disponibili nelle schede audio generalmente operano mediante
sintesi FM o wavetable: nel primo caso i timbri sono prodotti mediante l’impiego
di oscillatori1 (il suono ottenuto non risulta “naturale” all’ascolto), nel secondo
   1
    Ogni oscillatore ` noto come operatore: miscelando le forme d’onda prodotte tramite con-
                      e
nessioni diverse fra gli operatori (algoritmi) si ottengono timbri “pi` ricchi”, per cui il suono
                                                                      u
diventa complessivamente migliore

                                               65
4.1. I DEVICE FILE DEL SEQUENCER                      CAPITOLO 4. SINTETIZZATORI E MIDI




caso si ha la riproduzione di timbri campionati da strumenti reali con diverse
velocit` (per cui il suono risulta pi` reale, anche se non completamente reale).
        a                            u
    Il chip pi` famoso utilizzante la prima tecnica ` l’Yamaha YM3812 (OPL–2),
              u                                      e
con nove voci2 fisse a disposizione e un output monofonico anche per le schede
audio stereo. Le nove voci (ciascuna ottenuta con due operatori — 2OP) pos-
sono essere sfruttate direttamente, ma ` anche disponibile una modalit` ritmo
                                           e                                   a
che “sacrificando” tre voci ricava cinque strumenti a percussione: Bass Drum,
Snare Drum, Tom–Tom, Cymbal e Hi–Hat. Il primo ` ottenuto con 2OP, gli al-
                                                         e
tri quattro con 1OP; dei primi tre pu` essere decisa la frequenza, gli ultimi due
                                        o
hanno frequenza fissa.
    All’YM3812 ` seguito l’YMF262 (OPL–3), che supporta di default la modalit`
                  e                                                                  a
OPL–2. In modalit` OPL–3 il chip supporta diciotto voci 2OP (per un totale di
                     a
trentasei operatori), oppure pu` fornire sei voci a quattro operatori (4OP) e sei
                                 o
voci a 2OP; nella modalit` ritmo di nuovo vengono supportati cinque strumenti
                            a
a percussione sottraendo tre voci a 2OP, quindi oltre a questi possono essere
fornite quindici voci 2 OP oppure sei voci 4OP e tre voci 2OP. L’OPL–3 ` stereoe
nel senso che di ogni voce pu` essere deciso se mandarla al canale sinistro, destro
                              o
o a entrambi per centrarla.
    In seguito la Yamaha ha prodotto l’OPL–4: questo chip supporta solo la
modalit` OPL–3, ma ha capacit` di sintesi wavetable. I chip pi` famosi sono
          a                        a                                   u
Yamaha, ma altre schede (Gravis UltraSound) incorporano chip come il Crystal
Sound CS4232, che secondo la modalit` consente di ottenere da 14 a 32 voci
                                           a
con sintesi FM o la sintesi wavetable. Di ogni voce ` possibile decidere meglio
                                                         e
dell’OPL–3 la localizzazione spaziale fra i canali stereo sinistro e destro, in quanto
sono previste sedici posizioni intermedie.


4.1.2      I sintetizzatori MIDI
Una porta MIDI pu` gestire fino a sedici canali: su ognuno di essi pi` dispositivi
                       o                                                 u
MIDI possono essere in ascolto, siano essi expander, master keyboard, sequencer o
interfacce per pilotare le luci di scena. Questi obbediranno ognuno con la propria
logica: ad esempio, se si ha un messaggio di NOTE ON rivolto a un sintetizzatore,
questo potrebbe smettere di suonare la nota precedente o eseguire quella nuova in
polifonia3 . I sintetizzatori nei dispositivi MIDI tendono ad essere pi` sofisticati di
                                                                       u
quelli disponibili nelle schede audio, anche se la differenza qualitativa nel tempo
si va sempre pi` assottigliando.
                  u
    La differenza fondamentale nell’uso di /dev/music rispetto a /dev/sequencer
consiste nel fatto che per il primo la gestione delle voci per i chip sintetizzatori
` automatica, come fanno i dispositivi MIDI una volta fissati l’opportuno pre-
e
   2
     Convenzionalmente con voce si intende una forma d’onda generata da uno o pi` operatori;
                                                                                u
una nota pu` essere composta da una o pi` voci
             o                           u
   3
     La polifonia ` data dal numero massimo di note che un sintetizzatore riesce a suonare
                  e
contemporaneamente, per cui generalmente tale numero ` variabile in base al numero di voci
                                                        e
che si impiegano per ogni nota

                                            66
CAPITOLO 4. SINTETIZZATORI E MIDI                                                        4.2. IL BUFFER DEGLI EVENTI




set timbrico e/o il modo (OMNI ON/OFF e POLY/MONO); in altri termini,
/dev/music cerca di emulare una porta MIDI per ogni chip sintetizzatore: ci
sono sedici canali (<voce> va da 0 a 15), con <voce>==9 (ovvero il canale 10
MIDI) per default dedicato alle percussioni (si seleziona un preset timbrico con
il valore di <nota>).
    Per /dev/sequencer ` invece responsabilit` del programmatore allocare e
                           e                    a
deallocare le singole voci: se una nota ` associata a una voce, l’inizio di una
                                         e
nuova nota termina la precedente (effetti particolari, come il legato, implicano un
impegno di programmazione pi` elevato). Il parametro <voce> va da 1 fino al
                                u
massimo numero di voci ammesse per la modalit` corrente.
                                                 a

   memoria
                      wait                                                      Comandi            Scheda audio
                                      Kernel                                  sintetizzatore
                                                                              o porta MIDI
                                                                                                                  1111
                                                                                                                  0000
                                                                                                                   1 1
                                                                                                                   0 0
                                                                                                                   11
                                                                                                                   00
                                                   coda degli eventi
                                                                                                    OPL3          1111
                                                                                                                  0000
                                                                                                                   1 1
                                                                                                                   0 0
                                                                                                                   11
                                                                                                                   00
                                                                                                                  1111
                                                                                                                  0000
                                                                                                                   1 1
                                                                                                                   0 0
                                                                                                                   11
                                                                                                                   00
                                                                                                           UART
          Processo                                                     byte
                                    OSS                                MIDI
      buffer degli eventi          driver

                 SEQ_DUMPBUF()

                                        INT
                                                                                Sincronizzazione
                                                                                    esterna



                                 Controller
                                  interrupt     CLOCK
                                               di sistema




                        Figura 4.1: Schema di funzionamento del sequencer



4.2        Il buffer degli eventi
In analogia al buffer dei campioni dichiarato in un programma per /dev/dsp, `
                                                                           e
necessario dichiarare un buffer degli eventi per /dev/music o /dev/sequencer;
ci` si effettua ponendo le seguenti dichiarazioni subito dopo gli #include:
  o

int seqfd;
SEQ_DEFINEBUF(<dimensione buffer>);

ove <dimensione buffer> ` la dimensione del buffer utente in byte; ` consigli-
                             e                                         e
abile adoperare multipli di 1024 byte, anche se 1024 ` pi` che sufficiente per la
                                                       e u
maggior parte delle applicazioni. Il file descriptor deve chiamarsi proprio seqfd,
in quanto ci sono delle macro nell’interfaccia di programmazione che vi fanno
riferimento.
    Con l’attuale struttura di soundcard.h la SEQ_DEFINEBUF() equivale a:

unsigned char seqbuf[<dimensione buffer> ] il buffer ` un vettore lungo
                                                   e
     <dimensione buffer> byte

                                                67
4.2. IL BUFFER DEGLI EVENTI                      CAPITOLO 4. SINTETIZZATORI E MIDI




int seqbuflen=<dimensione buffer> conserva la lunghezza del vettore

int seqbufptr=0 posiziona al principio il cursore all’interno del buffer
   L’apertura dei device file si effettua come per /dev/dsp; ad esempio:
if ((seqfd = open("/dev/music", O_WRONLY)) == -1)
    errore("/dev/music");
a differenza di /dev/dsp, l’uso di O_NONBLOCK ` efficace: il processo utente non
                                                  e
viene bloccato in lettura/scrittura nella coda degli eventi in condizioni normali,
ma se si legge e la coda ` vuota o se si scrive e la coda ` piena verr` ritornato
                           e                                 e           a
l’errore EAGAIN rispettivamente da read() e write(). Per un programma che
effettui solo sintesi si consiglia di usare O_WRONLY, in quanto l’apertura in O_RDWR
abilita anche l’input dalle porte MIDI; per il solo input di eventi MIDI ` possibile
                                                                          e
l’apertura del device file con O_RDONLY.
    Dopo che nel buffer utente ` stato posto un qualche evento, con macro come
                                  e
SEQ_START_NOTE() che saranno esaminate in seguito, ` cura del programmatore
                                                          e
spedirne i contenuti al driver tramite la macro:
SEQ_DUMPBUF();
affinch´ questa abbia effetto, nel solo file principale deve essere dichiarata una
      e
funzione void seqbuf_dump() come la seguente:
void seqbuf_dump()
{
    if (_seqbufptr && (write(seqfd, _seqbuf, _seqbufptr) == -1))
        errore("/dev/music");
    _seqbufptr = 0;
}
   Con la funzione sopra riportata la SEQ_DUMPBUF() risulter` bloccante per il
                                                                  a
processo utente se il buffer interno al driver si riempie; quando quest’ultimo si
svuota per met` il driver lo toglier` dallo stato di wait. Siccome l’esecuzione di
                a                   a
un evento pu` richiedere parecchi secondi, ` da tenere presente che il processo
              o                               e
utente pu` rimanere bloccato per parecchio tempo.
          o
   Nel caso il programma fosse diviso in pi` file sorgente compilati separatamente
                                           u
che sfruttano /dev/music o /dev/sequencer, solo nel file principale andranno
effettuate le precedenti dichiarazioni; per tutti gli altri file dopo gli #include si
mettono:
SEQ_USE_EXTBUF();
extern int seqfd;
extern void seqbuf_dump();
al posto di SEQ_USE_EXTBUF() si pu` usare SEQ_DECLAREBUF(); con l’attuale
                                     o
struttura di soundcard.h, tali macro equivalgono alle seguenti dichiarazioni:

                                        68
CAPITOLO 4. SINTETIZZATORI E MIDI               4.3. LETTURA DELLA CONFIGURAZIONE




extern unsigned char _seqbuf[];
extern int _seqbuflen;
extern int _seqbufptr;

  Tutte le macro descritte nella Sezione 4.6 fanno riferimento alle seguenti due
macro per gestire l’allocazione degli eventi all’interno del buffer:
SEQ NEEDBUF(<numero>) verifica la possibilit` di allocare all’interno del
                                                a
   buffer <numero> byte; se non si pu` lo svuota
                                    o
SEQ ADVBUF(<numero>) fa avanzare l’indice del buffer _seqbufptr di
   <numero> byte
fra queste macro vengono poste le assegnazioni di valori all’interno del buffer che
costituiscono gli eventi stessi.
    Se si hanno delle necessit` particolari ` possibile definire il buffer in modo tale
                              a             e
da inserirvi un solo evento per volta; per far ci` bisogna definire la macro vuota
                                                  o
USE_SIMPLE_MACROS prima dell’inclusione di soundcard.h:
#define USE_SIMPLE_MACROS
#include <soundcard.h>
in tal caso ` lasciata al programmatore la responsabilit` di definire subito dopo
            e                                           a
queste due righe le seguenti macro:
seqbuf nome del buffer (unsigned char[])
seqbufptr nome del cursore all’interno del buffer o 0 se non richiesto
SEQ ADVBUF(<lunghezza>) da usare se l’applicazione sfrutta eventi di
   <lunghezza> diversa, altrimenti deve essere definita vuota


4.3      Lettura della configurazione
Se si vuole rilevare qual ` la configurazione hardware incapsulata da OSS si hanno
                          e
a disposizione delle chiamate che interrogano il driver riguardo vari parametri.
    Il seguente frammento di codice ritorna in ndevice la somma del numero di
chip sintetizzatori e di porte MIDI presenti nel sistema:
int ndevice;
ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &ndevice);
    Si ` gi` detto che /dev/music consente una maggiore indipendenza dall’hard-
       e a
ware di quanto invece consenta /dev/sequencer: ne consegue che il primo device
file vede tutti i dispositivi (chip o porte) allo stesso modo, mentre il secondo con-
sente anche di considerare le porte MIDI come separate. Per evidenziare questa
differenza di comportamento nella Sezione seguente sono listate le chiamate che
per entrambi i device file si comportano identicamente, mentre nella Sezione dopo
sono elencate le chiamate peculiari per il solo /dev/sequencer.

                                         69
4.3. LETTURA DELLA CONFIGURAZIONE                CAPITOLO 4. SINTETIZZATORI E MIDI




4.3.1     Parametri generali
Il driver ` in grado di caratterizzare ogni dispositivo sintetizzatore presente nel
          e
sistema tramite la seguente chiamata:
struct synth_info sinfo;
sinfo.device = <numero device>;
ioctl(seqfd, SNDCTL_SYNTH_INFO, &sinfo);
ove <numero device> ` il numero identificatore del dispositivo di cui interes-
                       e
sa rilevare informazioni, compreso tra 0 e ndevice-1. I campi ritornati nella
struct synth_info hanno il significato:
char name[30 ] nome del dispositivo (lo stesso riportato con cat /dev/sndstat)
int device numero del dispositivo, da inizializzare prima della chiamata
int synth type tipo del dispositivo, pu` essere uno dei seguenti identificatori:
                                       o
        SYNTH_TYPE_FM         sintetizzatore FM
        SYNTH_TYPE_SAMPLE     sintetizzatore wavetable
        SYNTH_TYPE_MIDI       porta MIDI
int synth subtype qualifica ulteriormente il dispositivo, pu` essere uno dei
                                                           o
     seguenti identificatori:
        FM_TYPE_ADLIB        compatibilit` OPL–2
                                         a
        FM_TYPE_OPL3         compatibilit` OPL–3
                                         a
        MIDI_TYPE_MPU401     UART compatibile MPU–401
        SAMPLE_TYPE_GUS      wavetable di tipo Gravis UltraSound
int nr voices massimo numero di voci supportate dal dispositivo
int instr bank size numero di strumenti FM caricabili contemporaneamente
      dal driver
unsigned int capabilities specifica capacit` supplementari del dispositivo tramite
                                          a
     i seguenti identificatori:
        SYNTH_CAP_OPL3      supporto OPL–3
        SYNTH_CAP_INPUT     il dispositivo pu` effettuare input MIDI
                                             o
   Se il sintetizzatore ` wavetable (sinfo.synth_type==SYNTH_TYPE_SAMPLE) e
                        e
consente il caricamento delle patch, esiste una chiamata che ritorna la quantit`
                                                                               a
di memoria in byte ancora disponibile a tal scopo:
int mem_disp = <numero device>;
ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &mem_disp);
ove <numero device> ` il numero identificatore del sintetizzatore; questa chia-
                     e
mata ritorna mem_disp==0x7FFFFFFF se rivolta a un sintetizzatore FM o a una
porta MIDI.

                                        70
CAPITOLO 4. SINTETIZZATORI E MIDI             4.3. LETTURA DELLA CONFIGURAZIONE




4.3.2     Le porte MIDI
Quanto detto in questa Sezione ` valido per il solo /dev/sequencer.
                                e
   Se si vuole conoscere il numero delle sole porte MIDI installate nel sistema `
                                                                                e
disponibile la seguente chiamata:

int nporte;
ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &nporte);

per cui il numero di chip sintetizzatori interni alla scheda audio sar` pari a
                                                                        a
ndevice-nporte. Per quanto detto in precedenza, se questa chiamata ` effettuata
                                                                      e
per /dev/music si avr` sempre 0 ritornato in nporte.
                      a
   Si pu` riconoscere il tipo delle porte MIDI tramite la seguente chiamata:
        o

struct midi_info minfo;
minfo.device = <numero porta>;
ioctl(seqfd, SNDCTL_MIDI_INFO, &minfo);

ove <numero porta> ` il numero della porta MIDI, compreso tra 0 e nporte-1.
                       e
I campi ritornati nella struct midi_info hanno il significato:

char name[30 ] nome del dispositivo (lo stesso riportato con cat /dev/sndstat)

int device numero della porta MIDI, da inizializzare prima della chiamata a
     SNDCTL_MIDI_INFO

int dev type identificatore del tipo di scheda audio ove ` installata la porta
                                                        e
     MIDI, pu` essere uno dei seguenti:
             o
        SNDCARD_ADLIB          Adlib
        SNDCARD_SB             Sound Blaster
        SNDCARD_PAS            Pro Audio Spectrum
        SNDCARD_GUS            Gravis UltraSound
        SNDCARD_MPU401         MPU–401
        SNDCARD_SB16           Sound Blaster 16
        SNDCARD_SB16MIDI       Sound Blaster 16 MIDI
        SNDCARD_UART6850       6850 UART
        SNDCARD_GUS16          Gravis UltraSound 16
        SNDCARD_MSS            Microsoft Sound System
        SNDCARD_PSS            Personal Sound System
        SNDCARD_SSCAPE         Ensoniq SoundScape
        SNDCARD_PSS_MPU        Personal Sound System + MPU–401
        SNDCARD_PSS_MSS        Personal Sound System/Microsoft Sound System
        SNDCARD_SSCAPE_MSS     Ensoniq SoundScape/Microsoft Sound System
        SNDCARD_TRXPRO         Mediatrix PRO
        SNDCARD_TRXPRO_SB      Mediatrix PRO/Sound Blaster
        SNDCARD_TRXPRO_MPU     Mediatrix PRO + MPU–401

                                       71
4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI           CAPITOLO 4. SINTETIZZATORI E MIDI



        SNDCARD_MAD16               MAD 16
        SNDCARD_MAD16_MPU           MAD 16 + MPU–401
        SNDCARD_CS4232              CS4232
        SNDCARD_CS4232_MPU          CS4232 + MPU–401
        SNDCARD_MAUI                Turtle Beach Maui
        SNDCARD_PSEUDO_MSS          Pseudo Microsoft Sound System
        SNDCARD_GUSPNP              Gravis UltraSound PnP
        SNDCARD_UART401             UART MPU–401 (pass–through)
    Un esempio interessante dell’applicazione di questa chiamata ` il rilevamento
                                                                    e
della presenza di una WaveBlaster4 : si esegue un ciclo fra 0 e nporte-1, ferman-
dosi quando minfo.dev_type==SNDCARD_SB16_MIDI; se non lo si trova si pu`           o
ripiegare su minfo.dev_type==SNDCARD_SB16, ma per entrambi non ` ancora    e
garantita la presenza della WaveBlaster. Questa c’` se risponde alla sequenza di
                                                    e
inizializzazione del Proteus SoundEngine (il processore di cui ` dotata), da inviare
                                                               e
alla porta come una sequenza sysex di byte MIDI: 0xF0, 0x18, 0x04, 0x00, 0x23,
0xF7.
    Per testare se una porta MIDI ` disponibile e funzionante si dispone della
                                    e
chiamata:
int nporta = <numero porta>;
if (ioctl(seqfd, SNDCTL_SEQ_TESTMIDI, &nporta) == -1) {
    /* Gestione dell’errore */
}
ove <numero porta> ` compreso tra 0 e nporte-1. Essa verifica prima di tutto
                        e
che la porta esista; in tal caso, se non ` gi` aperta, la apre in I/O, restituendo un
                                         e a
codice di errore in errno se l’apertura non ` andata a buon fine.
                                               e


4.4       Predisposizione dei chip sintetizzatori
I chip sintetizzatori, siano essi FM o wavetable, per produrre un qualsiasi suono
necessitano di una programmazione dei propri registri che specifica nel primo caso
come collegare gli operatori per produrre dei determinati timbri (algoritmi) e nel
secondo caso come riprodurre le waveform (patch). Per comodit` di trattazione,
                                                                   a
d’ora in poi si chiameranno patch sia gli algoritmi che le waveform.
    I preset timbrici a disposizione in genere sono gi` pronti sotto forma di file,
                                                       a
e nelle due Sezioni seguenti si illustra come caricarli per i due casi in questione.
Questo ` un passo necessario, in quanto ogni sintetizzatore parte senza alcuna
         e
programmazione dopo che il sequencer inizializza i dispositivi.
    I preset timbrici sono definiti secondo lo standard General MIDI level 1, di
conseguenza il programmatore sa gi` che a una certa patch corrisponde un dato
                                      a
   4
     La WaveBlaster ` una schedina di espansione della Sound Blaster 16, visibile al resto del
                    e
sistema come un dispositivo connesso a un canale della porta MIDI di cui questa ` dotata, e
                                                                                  e
che si comporta come un expander multitimbrico con sintesi wavetable

                                             72
CAPITOLO 4. SINTETIZZATORI E MIDI            4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI




strumento; se questo ` mancante ` prevista una patch sostitutiva. Ci` non fa al-
                      e            e                                  o
tro che rimarcare l’architettura indipendente dal dispositivo che ha il sequencer,
per cui un dato brano MIDI pu` essere eseguito col giusto preset timbrico indif-
                                 o
ferentemente su un costoso expander o su una scheda audio poco costosa senza
praticamente alcuna modifica.

    OSS prevede una chiamata per il reset dell’intero sequencer, che ha l’effetto
di ripristinare le condizioni di default per ogni dispositivo:

ioctl(seqfd, SNDCTL_SEQ_RESET);

con /dev/music per ogni porta MIDI sono spediti i messaggi All Notes OFF e
Bender OFF, nonch´ sono azzerati tutti i controller; con /dev/sequencer si cerca
                     e
di ottenere questi effetti spedendo i messaggi di Active Sensing 5 per le porte e
All Notes OFF per ogni canale di queste. Sono inoltre azzerate tutte le patch.
    `
    E disponibile un’altra chiamata, che in teoria dovrebbe essere l’equivalente del
Panic Button (termina tutte le note in “sospeso” nel caso di blocco del sistema)
presente sulle tastiere MIDI:

ioctl(seqfd, SNDCTL_SEQ_PANIC);

allo stato attuale dello sviluppo del driver (1999), essa si comporta allo stesso mo-
do di SNDCTL_SEQ_RESET; in futuro dovrebbe prevedere il Note OFF sistematico
per tutte le note di ogni canale di ogni porta MIDI, giusto per quei dispositivi
che non riconoscono l’Active Sensing con /dev/sequencer.

4.4.1      Caricamento degli algoritmi FM
In un sistema Linux ` altamente probabile che i file contenenti gli algoritmi per i
                     e
sintetizzatori FM Yamaha si trovino in /etc; i loro nomi sono std.o3 e drums.o3,
che rispettivamente definiscono strumenti e percussioni per l’OPL–3 (4OP). Sem-
pre in /etc si possono trovare std.sb e drums.sb per l’OPL–2 (2OP); questi
possono essere utilizzati dall’OPL–3, poich´ esso parte in modalit` 2OP e per
                                             e                       a
passare alla 4OP bisogna richiederlo esplicitamente invocando la chiamata:

int nsint = <numero sintetizzatore>;
if (ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &nsint) == -1)
    errore("SNDCTL_FM_4OP_ENABLE");

i numeri di patch fra 0 e 127 sono dedicati alla sezione strumentale, quelli fra 128
e 255 alla sezione ritmica.
    Di seguito ` l’elenco dei passi che bisogna compiere per poter caricare le
                e
patch FM:
   5
    Quando ` spedito il primo messaggio di questo tipo il dispositivo MIDI si aspetta di ricevere
             e
un altro messaggio MIDI entro 300 ms, al limite un altro Active Sensing; se ci` non avviene il
                                                                                o
dispositivo si pone nello stato di MIDI error e disattiva tutte le sue funzioni

                                               73
4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI     CAPITOLO 4. SINTETIZZATORI E MIDI




   • in relazione al tipo di sintetizzatore (OPL–2 o OPL–3), si aprono in lettura
     rispettivamente i file std.sb e drums.sb oppure std.o3 e drums.o3

   • si inizializza la struct sbi_instrument, i cui campi hanno il significato:

     unsigned short key specifica il tipo di patch FM e pu` essere uno dei
                                                         o
         seguenti identificatori:
          FM_PATCH       sintetizzatore OPL–2
          OPL3_PATCH sintetizzatore OPL–3
     short device numero del sintetizzatore
     int channel numero della patch (invocata dalla macro SEQ_SET_PATCH())
     unsigned char operators[32 ] valori dei registri che programmano il sin-
         tetizzatore per la patch

   • si posiziona il cursore del file (std o drums, secondo che il numero della
     patch sia inferiore di 128, oppure maggiore o uguale tale valore) al numero
     della patch moltiplicata per 60 e si leggono sessanta byte se il sintetizzatore
     ` OPL–3; per un OPL–2 il procedimento ` lo stesso ma il numero della
     e                                             e
     patch ` moltiplicato per 52 e i byte da leggere sono appunto cinquantadue
            e

   • si mettono 22 byte in operators[] a partire dalla posizione 36 del buffer

   • si scrive la struct sbi_instrument patch tramite la macro:

     SEQ_WRPATCH(&patch, sizeof(patch));

     la quale controlla se il buffer contiene qualche evento: se s` ne fa il dump
                                                                  ı
     e scrive la patch con controllo d’errore, venendo passata direttamente al
     driver del sintetizzatore senza che sia accodata (l’esecuzione ` immediata);
                                                                    e
     ` disponibile un’altra macro, che effettua quanto sopra senza il controllo
     e
     d’errore:

     SEQ_WRPATCH2(&patch, sizeof(patch));

   Di seguito ` riportata una semplice funzione per caricare le patch FM; il suo
               e
primo argomento ` il numero del sintetizzatore interessato, il suo secondo argo-
                   e
mento ` il tipo di patch da caricare (FM_TYPE_ADLIB per OPL–2, FM_TYPE_OPL3
      e
per OPL–3):
/* Path per i file    che contengono gli algoritmi FM */
char STD_OPL3[] =     "/etc/std.o3";         /* OPL-3 */
char DRUM_OPL3[] =    "/etc/drums.o3";
char STD_OPL2[] =     "/etc/std.sb";         /* OPL-2 */
char DRUM_OPL2[] =    "/etc/drums.sb";


                                        74
CAPITOLO 4. SINTETIZZATORI E MIDI   4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI




void FM_patch_load(int ndev, int tiposint)
{
    int stdfd, drumfd, preset, i, lunpatch;
    unsigned char buf[100];
    char *STD_FILE, *DRUM_FILE;
    struct sbi_instrument patch;

    if (tiposint == FM_TYPE_OPL3)
    {                                     /* YMF262 */
        STD_FILE = STD_OPL3;
        DRUM_FILE = DRUM_OPL3;
        patch.key = OPL3_PATCH;
        lunpatch = 60;
    }
    else {                                /* YM3812 */
           STD_FILE = STD_OPL2;
           DRUM_FILE = DRUM_OPL2;
           patch.key = FM_PATCH;
           lunpatch = 52;
    }

    /* Apertura dei file degli algoritmi in lettura */
    if ((stdfd = open(STD_FILE, O_RDONLY)) == -1)
        errore(STD_FILE);
    if ((drumfd = open(DRUM_FILE, O_RDONLY)) == -1)
        errore(DRUM_FILE);

    patch.device = ndev;                       /* Numero sint. */
    for (preset = 0; preset < 255; preset++) /* 255 preset */
    {
         patch.channel = preset;               /* Numero del preset */
         if (preset < 128) { /* Algoritmi degli strumenti */
             if ((lseek(stdfd, preset*60, SEEK_SET) == -1) ||
                               (read(stdfd, buf, lunpatch) != lunpatch))
                 errore(STD_FILE);
         }
         else { /* Algoritmi della sezione ritmica */
                if ((lseek(drumfd, (preset-128)*60, SEEK_SET) == -1) ||
                               (read(drumfd, buf, lunpatch) != lunpatch))
                    errore(DRUM_FILE);
         }
         /* Scrittura della patch */
         for (i = 0; i < 22; i++)
              patch.operators[i] = buf[i+36];
         SEQ_WRPATCH(&patch, sizeof(patch));
    }


                                     75
4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI      CAPITOLO 4. SINTETIZZATORI E MIDI




     close(stdfd);
     close(drumfd);
}


4.4.2     Caricamento delle patch wavetable
OSS consente il caricamento delle patch wavetable di tipo GF1 per la Gravis
UltraSound (GUS); queste sono file che possono contenere campioni di diversi
strumenti o dello stesso strumento per frequenze diverse.
    Un file GF1 ha un header che contiene delle informazioni generali, seguito
da una o pi` sezioni di campioni (waveform), ciascuna delle quali ha un suo
              u
header con le caratteristiche della waveform stessa. Essi hanno estensione .PAT,
e nell’Appendice A si pu` trovare la corrispondenza fra il loro nome e i preset
                           o
timbrici General MIDI. L’insieme dei file GF1 distribuito con la GUS ` copyright
                                                                     e
della Voice Crystal, ma ` disponibile il patchset public domain MIDIA in [8].
                         e
    OSS fornisce una chiamata per azzerare la memoria ove sono caricate le patch
per il sintetizzatore wavetable:

int nsint = <numero sintetizzatore>;
if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &nsint) == -1)
    errore("SNDCTL_SEQ_RESETSAMPLES");

pu` essere utilizzata in fase di inizializzazione, prima di caricare qualsiasi patch.
  o
   Di seguito ` l’elenco dei passi che bisogna compiere per poter caricare le patch
               e
wavetable:

    • si apre in lettura il file .PAT relativo a un dato preset timbrico

    • si controllano alcuni campi dell’header del file GF1, in modo tale da attes-
      tarne la validit` come patch
                      a

    • si esegue un ciclo per il caricamento di tutte le waveform presenti nel file,
      il quale prevede al suo interno l’allocazione di memoria dinamica per poter
      ospitare ogni waveform e la struttura dati che la caratterizza; quest’ultima
      ` la struct patch_info descritta di seguito:
      e

      unsigned short key si inizializza con GUS_PATCH
      short device no numero del sintetizzatore wavetable
      short instr no numero del preset timbrico; se ` seguito l’ordine dato in
                                                     e
          Appendice A, i preset saranno conformi allo standard General MIDI
      unsigned int mode specifica il tipo di waveform e il modo di riprodurla;
          ` un OR aritmetico dei seguenti identificatori:
          e

                                         76
CAPITOLO 4. SINTETIZZATORI E MIDI    4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI



            WAVE_16_BITS        campioni della waveform a 16 bit
            WAVE_UNSIGNED       la codifica ` unsigned
                                             e
            WAVE_LOOPING        abilita il looping per la riproduzione
                                della porzione centrale della waveform
            WAVE_BIDIR_LOOP     effettua il looping in entrambi i versi
                                (dall’inizio della porzione alla fine e
                                dalla fine all’inizio); per default `
                                                                   e
                                effettuato solo dall’inizio alla fine
            WAVE_LOOP_BACK      il looping procede solo dalla fine
                                all’inizio della porzione
            WAVE_SUSTAIN_ON     abilita il sustain (inviluppo ADSR)
            WAVE_ENVELOPES      consente di sagomare l’inviluppo della
                                waveform con una forma particolare
            WAVE_VIBRATO        abilita una lieve modulazione di frequenza
            WAVE_TREMOLO        abilita una lieve modulazione d’ampiezza
            WAVE_SCALE          abilita la scalatura delle armoniche superiori
            WAVE_FRACTIONS      suddivide la porzione di waveform su
                                cui effettuare il looping in ulteriori
                                porzioni per la modulazione in ampiezza
          gli identificatori da WAVE_VIBRATO in poi sono specifici di OSS, mentre
          gli altri seguono le convenzioni dei file GF1
     int len dimensione della waveform in byte
     int loop start posizione di inizio del looping in byte, rispetto all’inizio
          della waveform
     loop end posizione di fine del looping in byte, rispetto all’inizio della
         waveform
     unsigned int base freq, base note quest’ultima ` l’altezza della nota
                                                       e
         che ` udita quando si usa base_freq come frequenza di riproduzione;
              e
         la frequenza di base_note ` moltiplicata per mille (ad esempio, il LA
                                    e
         ` 440000)
         e
     unsigned int high note, low note rispettivamente definiscono la massi-
         ma e la minima frequenza delle note per le quali la waveform ` valida,
                                                                         e
         in quanto ` possibile associare pi` waveform a un dato preset timbrico e
                   e                       u
         questi campi consentono di discriminare quella pi` adatta per la ripro-
                                                           u
         duzione di una nota; entrambi contengono le frequenze moltiplicate
         per mille
     int panning consente il posizionamento di uno strumento fra i canali stereo
          sinistro e destro; -128≤ panning ≤+127, con -128 corrispondente al
          solo canale sinistro, +127 solo al destro e 0 al centro
     int detuning sposta in alto o in basso la frequenza di riproduzione, con-
          sentendo l’alterazione del timbro

                                      77
4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI    CAPITOLO 4. SINTETIZZATORI E MIDI




     unsigned char env rate[6 ], env offset[6] descrittori per il filtro di invilup-
         po della waveform; i valori per ogni byte si mappano in percentuali,
         con 0==0% e 255==100%, per ogni fase dell’inviluppo
     unsigned char tremolo sweep, tremolo rate, tremolo depth parametri
         per la lieve modulazione d’ampiezza
     unsigned char vibrato sweep, vibrato rate, vibrato depth parametri
         per la lieve modulazione di frequenza
     int scale frequency frequenza per la scalatura delle armoniche superiori
          della waveform
     unsigned int scale factor decide la quantit` di scalatura delle armoniche
                                                 a
         superiori da applicare; questo parametro varia da 0 a 2048 o da 0 a 2
     int volume intensit` alla quale si deve riprodurre la waveform
                        a
     fractions numero di sottoporzioni in cui ` divisa la porzione di waveform
                                              e
          su cui si effettua il looping
     char data[1 ] posizione del primo campione della waveform

   • si esegue la scrittura della patch tramite SEQ_WRPATCH() o SEQ_WRPATCH2();
     secondo la dimensione delle waveform l’intera operazione di caricamento
     delle waveform pu` richiedere anche dei secondi, per cui ` meglio caricare le
                         o                                    e
     patch prima che inizi l’esecuzione della partitura in modo da non provocare
     errori di ritmo
    Di seguito ` riportata una semplice funzione per caricare le patch wavetable;
               e
il suo primo argomento ` il numero del sintetizzatore, il suo secondo argomento `
                         e                                                       e
il preset timbrico della patch. WAVE_patch_load() fa riferimento a un vettore di
stringhe patch_names[], che contiene i nomi dei file .PAT associati al preset tim-
brico secondo lo standard General MIDI; ` disponibile con l’inclusione dell’header
                                            e
file gmidi.h, il quale si trova in [6] nella directory sndkit/OSSlib.
void WAVE_patch_load(int ndev, int preset)
{
    int patfd, i, offset, lun_waveform;
    unsigned char buf[256];
    unsigned short nwaveform, master_volume;
    struct patch_info *patch;

/* Macro per la manipolazione di short e int (mem. little endian) */
#define uSHORT(b) ((unsigned short)((*(b+1)<<8)|*b))
#define SHORT(b) ((short)((*(b+1)<<8)|*b))
#define    INT(b) ((int)((*(b+3)<<24)|(*(b+2)<<16)|(*(b+1)<<8)|*b))

    if (patch_names[preset][0] = ’0’) { /* La patch e’ definita? */
        fprintf(stderr, "Preset %d: patch non definita!n", preset);
        return;

                                        78
CAPITOLO 4. SINTETIZZATORI E MIDI   4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI




    }
    else { /* La patch e’ definita: prova ad aprirla in lettura */
           strcat(NOME_PATCH, patch_names[preset]);
           strcat(NOME_PATCH, ".pat");
           if ((patfd = open(NOME_PATCH, O_RDONLY)) == -1)
                errore(NOME_PATCH);
    }

    /* Controlli sulla validita’ della patch */
    if (read(patfd, buf, 0xEF) != 0xEF) { /* Lettura del patch header */
        fprintf(stderr, "Patch %d (%s): file troppo corto!n",
                        preset, NOME_PATCH);
        return;
    }
    if (strncmp(buf, "GF1PATCH110", 12)) { /* ID patch GF1 */
        fprintf(stderr, "%s: non e’ un patch file!n", NOME_PATCH);
        return;
    }
    if (strncmp(&buf[12], "ID#000002", 10)) { /* ID creatore */
        fprintf(stderr, "%s: versione patch incompatibile!n", NOME_PATCH);
        return;
    }

    nwaveform = uSHORT(&buf[85]);         /* Numero di waveform            */
    master_volume = uSHORT(&buf[87]);     /* Volume per tutti i campioni   */
    offset = 0xEF;                        /* Posizione primo instr. header */

    /* Ciclo per il caricamento delle waveform dal patch file */
    for (i = 0; i < nwaveform; i++)
    {
         if (lseek(patfd, offset, SEEK_SET) == -1)
             errore(NOME_PATCH);
         if (read(patfd, buf, 64) != 64) { /* Lettura instrument header */
             fprintf(stderr, "Patch %d (%s): file troppo corto!n",
                             preset, NOME_PATCH);
             return;
         }
         offset += 96;
         lun_waveform = INT(&buf[8]);     /* Lunghezza waveform in byte */
         if ((patch = (struct patch_info *)
                      malloc(sizeof(*patch) + lun_waveform)) == NULL) {
             fprintf(stderr, "Patch %d (%s): non sono riuscito ad "
                             "allocare %d byte!n", preset, NOME_PATCH,
                              sizeof(*patch) + lun_waveform);
             return;
         }


                                     79
4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI   CAPITOLO 4. SINTETIZZATORI E MIDI




          /* Inizializzazione dei campi della struct patch_info *patch */
          patch->key        = GUS_PATCH;
          patch->instr_no   = preset;
          patch->mode       = (unsigned char)buf[55] | WAVE_TREMOLO |
                              WAVE_VIBRATO | WAVE_SCALE;
          patch->len        = lun_waveform;
          patch->loop_start = INT(&buf[12]);
          patch->loop_end   = INT(&buf[16]);
          patch->base_freq = uSHORT(&buf[20]);
          patch->low_note   = INT(&buf[22]);
          patch->high_note = INT(&buf[26]);
          patch->base_note = INT(&buf[30]);
          patch->detuning   = SHORT(&buf[34]);
          patch->panning    = ((unsigned char)buf[36] - 7) * 16;
          memcpy(patch->env_rate, &buf[37], 6);
          memcpy(patch->env_offset, &buf[43], 6);
          patch->tremolo_sweep   = (unsigned char)buf[49];
          patch->tremolo_rate    = (unsigned char)buf[50];
          patch->tremolo_depth   = (unsigned char)buf[51];
          patch->vibrato_sweep   = (unsigned char)buf[52];
          patch->vibrato_rate    = (unsigned char)buf[53];
          patch->vibrato_depth   = (unsigned char)buf[54];
          patch->scale_frequency = SHORT(&buf[56]);
          patch->scale_factor    = uSHORT(&buf[58]);
          patch->volume          = master_volume;

          /* Caricamento della waveform dal patch file */
          if (lseek(patfd, offset, SEEK_SET) == -1)
              errore(NOME_PATCH);
          if (read(patfd, patch->data, lun_waveform) != lun_waveform) {
              fprintf(stderr, "Patch %d (%s): file troppo corto!n",
                              preset, NOME_PATCH);
              return;
          }

          /* Passa la patch al driver e poi libera la memoria allocata */
          SEQ_WRPATCH(patch, sizeof(*patch) + lun_waveform);
          offset += lun_waveform;
          free(patch);
    }

    close(patfd);
}




                                        80
CAPITOLO 4. SINTETIZZATORI E MIDI                         4.5. LA TEMPORIZZAZIONE




4.4.3    Caricamento delle sysex patch
Ci sono dei sintetizzatori wavetable che accettano il caricamento di patch tramite
delle system exclusive; uno di questi si trova nella scheda audio Turtle Beach
Maui. OSS supporta questo meccanismo tramite la struct sysex_info, i cui
campi hanno il seguente significato:

short key questo campo ` da inizializzare con l’identificatore SYSEX_PATCH o con
                        e
     MAUI_PATCH, rispettivamente se si deve trasferire una sysex patch generica
     o una Maui patch

short device no numero del sintetizzatore

int len dimensione dei dati della patch in byte

unsigned char data[1 ] la sysex patch inizia qui: il primo byte deve valere 0xF0
     e l’ultimo 0xF7; ci` che sta in mezzo ` responsabilit` del programmatore
                        o                   e              a
     (una descrizione del formato delle sysex Maui pu` essere trovato in [9])
                                                       o

   Il seguente frammento di codice evidenzia il modo in cui il caricamento viene
effettuato:

struct sysex_info *patch;
int lun_patch = <dimensione della sysex patch in byte>;

if ((patch = (struct sysex_info *)
              malloc(sizeof(*patch) + lun_patch)) == NULL) {
    fprintf(stderr, "Sysex patch: non sono riuscito ad allocare "
                    "%d byte!n", sizeof(*patch) + lun_patch);
    exit(-1);
}
/* Inizializzazione dei campi puntati da patch */
patch->key = SYSEX_PATCH;      /* o MAUI_PATCH */
patch->device_no = <numero sintetizzatore o porta>;
patch->len = lun_patch;
/* Si mette la sysex patch a partire da patch->data    */
/* ad esempio con read(patfd, patch->data, lun_patch) */
/* primo byte: MIDI_SYSTEM_PREFIX (0xF0), ultimo: 0xF7 */

SEQ_WRPATCH(patch, sizeof(*patch) + lun_patch);


4.5     La temporizzazione
Si ` gi` avuto modo di dire che la coda degli eventi ` interpretata dal driver sec-
   e a                                               e
ondo la temporizzazione fornita da eventi marcatempo, inseriti nel buffer tramite

                                        81
4.5. LA TEMPORIZZAZIONE                           CAPITOLO 4. SINTETIZZATORI E MIDI




opportune macro. Quando il driver ne incontra uno aspetta per il tempo indica-
to dall’evento prima di riprendere l’interpretazione degli altri eventi in coda, che
possono essere di esecuzione o di cambiamento di configurazione; questi ultimi vir-
tualmente non introducono ritardi: ad esempio, tre NOTE ON di seguito possono
costituire un accordo, mentre interponendo fra ogni nota un evento marcatempo
si otterr` un arpeggio.
         a
    L’attesa ` espressa in tick : il timer inizia a contare sequenzialmente dalla sua
             e
attivazione, per cui si pu` aspettare fino a un tempo espresso in termini assoluti
                          o
tramite la macro seguente:

SEQ_WAIT_TIME(<tick>);

ad esempio, SEQ_WAIT_TIME(60) inserisce in coda un evento marcatempo che
provoca l’attesa della routine che scandisce la coda degli eventi finch´ il timer in-
                                                                         e
terno non ha raggiunto il valore di 60 tick; se dopo fosse seguito da SEQ_WAIT_TIME(40)
tale evento non avrebbe effetto, in quanto si ` gi` passato il conteggio di 40 tick.
                                                 e a
    Gli eventi marcatempo possono essere espressi anche in termini relativi con la
seguente macro:

SEQ_DELTA_TIME(<tick>);

in tal caso l’evento costringe ad aspettare la routine che scandisce la coda degli
eventi per il numero di tick sopra specificato. Questo metodo per esprimere
le attese ` il pi` delle volte preferibile, in quanto la durata delle note risulta
           e     u
meglio “manipolabile” in termini relativi piuttosto che assoluti. Ad esempio, un
Note ON seguito da SEQ_DELTA_TIME(100) potrebbe esprimere una nota da un
quarto; con un’attesa assoluta, sapendo che il timer era arrivato magari a un
conteggio di 50 tick, lo stesso effetto lo si otterrebbe col Note ON seguito da
SEQ_WAIT_TIME(150). L’inserimento di una nuova nota implica il ricalcolo di
tutte le attese se queste sono espresse in termini assoluti, cosa che non avviene
esprimendole in termini relativi.
    Se si vogliono eseguire delle variazioni di tempo ` prevista la macro seguente:
                                                      e

SEQ_SET_TEMPO(<bpm>);

ove <bpm> sono le battute/minuto (BPM), comprese tra 8 e 360 (60 per default
dopo l’apertura del device file); ogni battuta corrisponde a una nota da un quarto
e dura un certo numero di tick secondo il tempo selezionato. Ad esempio, un
dimezzamento del tempo di esecuzione di una sequenza di eventi lo si ottiene con
SEQ_SET_TEMPO(30).
   Il timer pu` esser rispettivamente fatto partire (azzerato), fermato e fatto
               o
continuare dalla posizione in cui era arrivato con le seguenti chiamate:

ioctl(seqfd, SNDCTL_TMR_START);
ioctl(seqfd, SNDCTL_TMR_STOP);
ioctl(seqfd, SNDCTL_TMR_CONTINUE);

                                         82
CAPITOLO 4. SINTETIZZATORI E MIDI                         4.5. LA TEMPORIZZAZIONE




    Ci sono delle macro che generano eventi con effetti equivalenti a quelli delle
chiamate sopra elencate:

SEQ_START_TIMER();
SEQ_STOP_TIMER();
SEQ_CONTINUE_TIMER();

la differenza ` che l’effetto di queste si avr` quando il driver raggiunger` l’evento
              e                              a                            a
relativo nella coda degli eventi, poich´ l’elaborazione di quest’ultima ` sincrona
                                         e                               e
e sequenziale come per l’esecuzione di una partitura musicale. Le chiamate sono
invece asincrone, cio` ottengono il loro scopo nel momento in cui il flusso del
                      e
programma le raggiunge.
    `
    E d’obbligo far notare una differenza fra i due device file: per /dev/music la
macro SEQ_START_TIMER() deve essere chiamata prima di tutte le altre macro, al-
trimenti il timer non parte e l’applicazione aspetta infinitamente. Per /dev/sequencer
non ` obbligatorio chiamarla, poich´ il timer si avvia automaticamente al primo
     e                                 e
SEQ_DUMPBUF().

4.5.1    La sincronizzazione per /dev/music
Con /dev/music esiste la possibilit` di variare la risoluzione del timer; ci` si
                                   a                                        o
effettua con la seguente chiamata:

int frequenza = <frequenza in Hz>;
if (ioctl(seqfd, SNDCTL_SEQ_CTRLRATE, &frequenza) == -1)
    errore("SNDCTL_SEQ_CTRLRATE");
                                                         1000
di conseguenza la risoluzione temporale sar` di <frequenza in Hz> ms; se si pone
                                             a
frequenza pari a 0 la chiamata restituisce la lettura della risoluzione attuale.
    `
    E stata introdotta la possibilit` di esprimere la durata delle note in termini
                                    a
relativi usufruendo della Sincronizzazione Timebase, proposta originariamente
da Roland: il metodo ` basato sull’assegnazione di una certa durata in tick alla
                         e
nota da un quarto; questa a sua volta corrisponde a una battuta del metronomo.
Tempo e attese sono ancora manipolati rispettivamente da SEQ_SET_TEMPO() e
da SEQ_WAIT_TIME() o SEQ_DELTA_TIME().
    Originariamente si adottarono 24 tick/battuta (Pulse Per Quarter Note —
PPQN o PPQ), ma sono normali anche 48 o 96 PPQN (in certi sequencer si
arriva anche a 480 o 960). La ragione per cui sono adottati questi valori ` presto
                                                                          e
detta: sono tutti numeri divisibili per 2, 3 o 4; ci` permette l’assegnazione di
                                                     o
un numero intero di note ad ogni duina, terzina o quartina. Le assegnazioni per
quintine e settimine risultano invece inadeguate, in quanto i numeri non sono
divisibili per 5 e per 7.
    L’indicazione temporale ` completa quando sia specificato anche il numero di
                             e
battute al minuto: ad esempio, se inizialmente si ` fissato un tempo di 60 BPM, il
                                                  e
raddoppio della velocit` di esecuzione ` ottenuto portando il tempo a 120 BPM.
                          a             e

                                       83
4.5. LA TEMPORIZZAZIONE                          CAPITOLO 4. SINTETIZZATORI E MIDI




Infatti la variazione delle battute/minuto non varia i tick/battuta, per cui rad-
doppia anche la frequenza di svolgimento dei tick pur permanendo invariato il
valore della nota.
    Per ricavare la durata assoluta di una nota dai PPQN e BPM, se si conosce
la sua durata espressa in tick, si pu` applicare la seguente formula:
                                     o
                                                                 60000
       durata della nota in ms = (durata espressa in tick) ·
                                                               PPQN · BPM
    La definizione dei PPQN e dei BPM si pu` effettuare subito dopo l’apertura
                                             o
del device file tramite le seguenti chiamate:
int ppqn = <tick>, bpm = <bpm>;
if (ioctl(seqfd, SNDCTL_TMR_TIMEBASE, &ppqn) == -1)
    errore("SNDCTL_TMR_TIMEBASE");
if (ioctl(seqfd, SNDCTL_TMR_TEMPO, &bpm) == -1)
    errore("SNDCTL_TMR_TEMPO");
ove 1 ≤ppqn≤ 1000 e 8 ≤bpm≤ 250.

La Roland MPU–401
Con /dev/music ` stata anche introdotta la possibilit` di poter gestire certe
                   e                                       a
caratteristiche della MPU–401, una scheda MIDI che ha sofisticate capacit` di    a
gestione dei dati MIDI e dei sincronismi esterni: questi possono essere segnali ana-
logici provenienti da registratori multitraccia o da videoregistratori (ad esempio,
FSK o SMPTE).
    La chiamata che consente di scegliere fra i due tipi di timer ` la seguente:
                                                                  e
int modo_timer = <identificatore modo>;
if (ioctl(seqfd, SNDCTL_TMR_SOURCE, &modo_timer) == -1)
    errore("SNDCTL_TMR_SOURCE");
ove <identificatore modo> ` rispettivamente TMR_INTERNAL (clock di sistema)
                               e
o TMR_EXTERNAL (sincronismo esterno). Nel caso la scheda supporti il sincronismo
esterno si pu` sceglierne il tipo facendo un OR aritmetico fra TMR_EXTERNAL e
             o
uno dei seguenti identificatori:
  Modo sincronismo esterno         Per selezionare...
  TMR_MODE_MIDI                    MIDI Timing Clock (MIDI SYNC), associabile
                                   a messaggi SPP (Song Position Pointer)
  TMR_MODE_FSK                     Frequency–Shift Keying (FSK), riferimento
                                   di clock proveniente da un registratore
  TMR_MODE_CLS                     Clear Screen (CLS), generato a ogni screen
                                   blank (25 o circa 30 Hz)
  TMR_MODE_SMPTE                   Society for Motion Picture and Television
                                   Engineers (SMPTE), riferimento assoluto
                                   proveniente da un videoregistratore

                                        84
CAPITOLO 4. SINTETIZZATORI E MIDI                           4.5. LA TEMPORIZZAZIONE




in modo_timer il driver ritorna <identificatore modo> solo se ` in grado di
                                                               e
supportarlo. Ad esempio, se si vuole selezionare come sincronismo esterno il
MIDI SYNC, si pu` utilizzare il seguente frammento di codice:
                  o
int modo_timer = TMR_EXTERNAL | TMR_MODE_MIDI;
ioctl(seqfd, SNDCTL_TMR_SOURCE, &modo_timer);
if (modo_timer != (TMR_EXTERNAL | TMR_MODE_MIDI)) {
    /* Il driver non supporta tale sincronizzazione */
}
    Una caratteristica della MPU–401 ` che non supporta tutti i possibili valori
                                         e
di ppqn, per cui il driver effettua una conversione per adattare ci` che la scheda
                                                                     o
riesce a fornire rispetto a quanto richiesto; in particolare questo valore ` limitato
                                                                           e
fra 48 e 1000.
    Il cosiddetto intelligent mode della MPU–401 consente di poter gestire la
temporizzazione con la quale la scheda invia i byte MIDI; la chiamata seguente
predispone i valori temporali per una misura:
int numeratore = <num>, denominatore = <den>;
int ppqn = <ppqn>, trent_b = <tb>;
int misura = (numeratore<<24)|(denominatore<<16)|(ppqn<<8)|trent_b;
ioctl(seqfd, SNDCTL_TMR_METRONOME, &misura);
<num> e <den> costituiscono numeratore e denominatore della frazione che es-
prime il tempo per una misura, mentre <ppqn> sono i tick che esprimono la
durata della nota da un quarto e <tb> sono i trentaduesimi di battuta. Alla
scheda sono inviati ppqn e le battute/misura=(4*numeratore)>>denominatore,
abilitando la funzione metronomo senza accenti. Se misura==0, questa funzione
` disabilitata.
e
    La macro seguente genera un evento equivalente alla chiamata precedente:
SEQ_TIME_SIGNATURE(<misura>);
ove <misura> ha lo stesso formato della variabile misura.

4.5.2     La sincronizzazione per /dev/sequencer
Con /dev/sequencer non si ha la possibilit` di variare la risoluzione del timer
                                                 a
n´ di usufruire della Sincronizzazione Timebase, per cui le possibilit` di temporiz-
  e                                                                   a
zazione sono quelle elencate all’inizio: si usa SEQ_WAIT_TIME() o SEQ_DELTA_TIME(),
variando il tempo dell’esecuzione con SEQ_SET_TEMPO().
     Il timer su cui si basa /dev/sequencer ` quello del kernel, che dovrebbe avere
                                               e
una frequenza fissa di 100 Hz per tutte le architetture su cui ` stato portato OSS.
                                                               e
Ci` tuttavia non ` garantito (ad esempio, il timer del kernel per Linux/Alpha ha
   o                e
una frequenza di 1024 Hz), quindi se si vuole avere la sicurezza si pu` utilizzare
                                                                         o
la chiamata SNDCTL_SEQ_CTRLRATE con frequenza=0 (un altro valore causer`           a
errore).

                                         85
4.6. OUTPUT DEGLI EVENTI                         CAPITOLO 4. SINTETIZZATORI E MIDI




4.6      Output degli eventi
Come si ` sin qui visto, /dev/music ha caratteristiche superiori per indipenden-
          e
za dal dispositivo rispetto a /dev/sequencer; quest’ultimo tuttavia consente la
possibilit` di accesso a pi` basso livello a una porta MIDI tramite la seguente
          a                u
macro:

SEQ_MIDIOUT(<numero porta MIDI>, <byte>);

essa spedisce alla porta <numero porta MIDI> un arbitrario <byte>, inframez-
zandolo con i normali eventi posti nel buffer dalle altre macro; costituisce un
evento lungo quattro byte.
    Tutte le altre macro che verranno in seguito descritte si comportano allo stesso
modo sia per /dev/music che per /dev/sequencer, salvo il fatto che per il pri-
mo device file l’allocazione delle voci ` automatica (il chip sintetizzatore sembra
                                       e
comportarsi come una porta MIDI).
    In figura 4.2 ` visibile l’organizzazione degli eventi, che ricalca fedelmente
                   e
quella con cui sono organizzati i messaggi Standard MIDI (nella descrizione delle
macro che li creano sono evidenziate le differenze rispetto a questi).


                                                                    System Common
                           Voice

  MESSAGGI                                       MESSAGGI
                                                                       Real Time
  DI CANALE                                      DI SISTEMA


                           Mode
                                                                    System Exclusive



    Figura 4.2: Organizzazione degli eventi come i messaggi standard MIDI

    Come raccomandazione, l’ultimo evento scritto nel buffer prima della chiusura
del device file dovrebbe essere un’attesa, per evitare che lo svuotamento del buffer
provocato da close() tronchi l’esecuzione dell’ultima nota. Alternativamente si
pu` utilizzare la seguente chiamata:
  o

ioctl(seqfd, SNDCTL_SEQ_SYNC);

che risulta bloccante per il processo chiamante finch´ la coda degli eventi non si
                                                    e
` svuotata.
e

4.6.1     Messaggi di canale
CHANNEL VOICE MESSAGES
Note ON: SEQ_START_NOTE(<dev>, <can>, <nota>, <vol>);

                                        86
CAPITOLO 4. SINTETIZZATORI E MIDI                        4.6. OUTPUT DEGLI EVENTI




     inizia a suonare <nota> con volume o key velocity <vol> sul canale <can>
     del dispositivo <dev>; per i sintetizzatori interni se nota==255 il driver non
     inizia una nuova nota, ma regola il volume di quella attuale (questa carat-
     teristica pu` non funzionare con tutte le schede audio), mentre se vol==255
                 o
     il driver user` per <nota> il volume della nota precedente e, solo per
                    a
     /dev/music, SEQ_START_NOTE() con vol==0 equivale a SEQ_STOP_NOTE(<dev>, <can>,

Note OFF: SEQ_STOP_NOTE(<dev>, <can>, <nota>, <vol>);
     termina <nota> in esecuzione sul canale <can>, ma non ha effetto se questa
     ` gi` decaduta o non ` mai iniziata, e se non si conosce <vol> si pu` utiliz-
     e a                   e                                               o
     zare 64; generalemente il Note OFF non termina la nota immediatamente,
     poich´ il tempo di rilascio dipende dalle caratteristiche del preset timbrico
           e
     selezionato (potrebbe essere pi` di dieci secondi)
                                     u

Polyphonic key pressure: SEQ_KEY_PRESSURE(<dev>, <can>, <nota>, <pres>);
     modula l’aftertouch <pres> per la singola <nota> in esecuzione sul canale
     <can>; per un chip sintetizzatore interno alla scheda audio quest’evento si
     traduce in genere in una lieve modulazione di frequenza del suono (OPL–3)

Channel pressure/Aftertouch: SEQ_CHN_PRESSURE(<dev>, <can>, <pres>);
     modula l’aftertouch <pres> per tutte le note in esecuzione sul canale <can>;
     con /dev/sequencer il canale corrisponde a una voce per il chip sinte-
     tizzatore interno, per cui quest’evento ` corrispondente al Polyphonic key
                                             e
     pressure

Control change: SEQ_CONTROL(<dev>, <can>, <ctl>, <valore>);
     predispone il valore di un controller MIDI (prima o dopo che la nota sia
     suonata); nella pagina dopo ` riportata la lista (1997) dei controller <ctl>
                                   e
     supportati da OSS: con /dev/music sono specifici per un canale (ne in-
     fluenzano le note) e i valori rimangono in effetto finch´ sono esplicitamente
                                                           e
     cambiati con un altro Control change, mentre con /dev/sequencer sono
     specifici per una voce e sono resettati al loro default dopo un Note OFF
     (devono essere ripredisposti prima di un Note ON, con il loro range numerico
     che differisce dal MIDI)

Program change: SEQ_SET_PATCH(<dev>, <can>, <preset>);
     cambia il <preset> timbrico per il canale <can>; la patch relativa deve
     essere gi` stata caricata per quanto riguarda il sintetizzatore interno alla
              a
     scheda audio, altrimenti il driver produrr` un messaggio su stderr e non
                                               a
     si udir` alcun suono
            a

                                       87
4.6. OUTPUT DEGLI EVENTI                      CAPITOLO 4. SINTETIZZATORI E MIDI



 Numero            Tipo         Nome Controller MIDI         Identificatore OSS
   0x00                         Bank Select                  CTL_BANK_SELECT
   0x01                         Modulation Wheel or Lever    CTL_MODWHEEL
   0x02                         Breath Controller            CTL_BREATH
   0x03                         undefined
   0x04                         Foot Controller              CTL_FOOT
   0x05                         Portamento Time              CTL_PORTAMENTO_TIME
   0x06                         Data Entry MSB               CTL_DATA_ENTRY
   0x07                         Main Volume                  CTL_MAIN_VOLUME
   0x08       MSB Continui      Balance                      CTL_BALANCE
   0x09         di Base         undefined
   0x0A                         PAN                          CTL_PAN
   0x0B                         Expression Controller        CTL_EXPRESSION
0x0C÷0x0F                       undefined
   0x10                         General Purpose #1           CTL_GENERAL_PURPOSE1
   0x11                         General Purpose #2           CTL_GENERAL_PURPOSE2
   0x12                         General Purpose #3           CTL_GENERAL_PURPOSE3
   0x13                         General Purpose #4           CTL_GENERAL_PURPOSE4
0x14÷0x1F                       undefined
0x20÷0x3F   LSB Cont. di Base                                non definiti
   0x40                         Damper Pedal (Sustain)       CTL_DAMPER_PEDAL
                                                             CTL_SUSTAIN o CTL_HOLD
   0x41           Switch        Portamento                   CTL_PORTAMENTO
   0x42         (ON/OFF)        Sostenuto                    CTL_SOSTENUTO
   0x43                         Soft Pedal                   CTL_SOFT_PEDAL
   0x44                         Legato Footswitch            non definito
   0x45                         Hold 2                       CTL_HOLD2
0x46÷0x4F                       Sound Controller #1–10       non definiti
   0x50                         General Purpose #5           CTL_GENERAL_PURPOSE5
   0x51                         General Purpose #6           CTL_GENERAL_PURPOSE6
   0x52                         General Purpose #7           CTL_GENERAL_PURPOSE7
   0x53                         General Purpose #8           CTL_GENERAL_PURPOSE8
0x54÷0x5A                       undefined
   0x5B                         Effect 1: Ext Effect Depth     CTL_EXT_EFF_DEPTH
   0x5C          Continui       Effect 2: Tremolo Depth       CTL_TREMOLO_DEPTH
   0x5D         addizionali     Effect 3: Chorus Depth        CTL_CHORUS_DEPTH
   0x5E                         Effect 4: Detune Depth        CTL_DETUNE_DEPTH
                                (Celeste Depth)              CTL_CELESTE_DEPTH
   0x5F                         Effect 5: Phaser Depth        CTL_PHASER_DEPTH
   0x60                         Data Increment               CTL_DATA_INCREMENT
   0x61                         Data Decrement               CTL_DATA_DECREMENT
   0x62                         Non Reg. Par. Number LSB     CTL_NONREG_PARM_NUM_LSB
   0x63                         Non Reg. Par. Number MSB     CTL_NONREG_PARM_NUM_MSB
   0x64                         Regist. Par. Number LSB      CTL_REGIST_PARM_NUM_LSB
   0x65                         Regist. Par. Number MSB      CTL_REGIST_PARM_NUM_MSB
0x66÷0x78                       undefined


                                        88
CAPITOLO 4. SINTETIZZATORI E MIDI                      4.6. OUTPUT DEGLI EVENTI




Pitch bend change: SEQ_BENDER(<dev>, <can>, <valore>);
     fa le funzioni di pitch bender per il canale <can>: -8192≤ <valore> ≤+8191,
     si pu` applicare prima o dopo l’inizio di una nota e al Note OFF ritorna a
           o
     zero (default); l’intervallo del bender ` ±2 semitoni
                                              e

CHANNEL MODE MESSAGES
Sono effettivi solo per il MIDI e si inviano usando:

SEQ_CONTROL(<dev>, <can>, <oxXX>, <0xYY>);

Reset all controllers: 0xXX==0x79, 0xYY==0x00

Local control ON/OFF: 0xXX==0x7A, 0xYY==0x00 (OFF) oppure 0xYY==0x80
    (ON)

All notes OFF: 0xXX==0x78, 0xYY==0x00

Omni mode OFF: 0xXX==0x7C, 0xYY==0x00

Omni mode ON: 0xXX==0x7D, 0xYY==0x00

Mono mode ON: 0xXX==0x7E, 0xYY==0x0m (riserva i canali MIDI da <can> a
   <can>+m-1)

Poly mode ON: 0xXX==0x7F, 0xYY==0x00


4.6.2    Messaggi di sistema
SYSTEM COMMON MESSAGES
Espletano particolari funzioni MIDI:

MIDI Time Code quarter frame: non c’` una macro che generi un evento
                                          e
   corrispondente; pu` essere gestito automaticamente da una scheda MIDI
                     o
   come la MPU–401

Song position pointer: SEQ_SONGPOS(<pos>);
     se il timer ` esterno utilizzando una MPU–401, l’evento corrispondente a
                 e
     questa macro ` depositato nel buffer di input; non ha effetto in output
                    e

Song select: pu` essere utilizzata SEQ_MIDIOUT() per spedire i byte MIDI 0xF3
               o
    e 0xXX, con 0x00≤ 0xXX ≤0x7F

Tune request: pu` essere utilizzata SEQ_MIDIOUT() per spedire il byte MIDI
                o
    0xF6

                                       89
4.6. OUTPUT DEGLI EVENTI                          CAPITOLO 4. SINTETIZZATORI E MIDI




REAL TIME MESSAGES
Sono costituiti da un solo byte e possono essere inviati in qualsiasi momento,
anche inframezzandosi ai byte MIDI di altri messaggi; le macro dell’interfaccia di
programmazione del sequencer non consentono di far ci`, tuttavia le chiamate del
                                                        o
tipo SNDCTL_TMR_**** possono emulare tale comportamento in quanto asincrone
rispetto alla gestione della coda degli eventi effettuata dalla routine del driver.

Timing clock: ` generato automaticamente da schede MIDI come la MPU–401
              e

Start: SEQ_START_TIMER();
      la controparte asincrona ` la chiamata SNDCTL_TMR_START; pu` essere sped-
                               e                                 o
      ito anche col SEQ_MIDIOUT() di 0xFA

Stop: SEQ_STOP_TIMER();
      la controparte asincrona ` la chiamata SNDCTL_TMR_STOP; pu` essere spedito
                               e                                o
      anche col SEQ_MIDIOUT() di 0xFC

Continue: SEQ_CONTINUE_TIMER();
      la controparte asincrona ` la chiamata SNDCTL_TMR_CONTINUE; pu` essere
                               e                                    o
      spedito anche col SEQ_MIDIOUT() di 0xFB

Active sensing: questo messaggio ` gestito automaticamente in output dalla
                                  e
     MPU–401, mentre il sequencer lo ignora in input

System reset: pu` essere spedito col SEQ_MIDIOUT() di 0xFF
                o


SYSTEM EXCLUSIVE MESSAGES
SEQ_SYSEX(<dev>, <buf>, <lun>);
     Permette di spedire dei sysex, ma non dei byte MIDI arbitrari (ci` causerebbe
                                                                        o
dei problemi con l’intelligent mode dell’MPU–401), e il messaggio deve essere
spezzato in blocchi da sei byte l’uno (cio` SEQ_SYSEX() deve essere richiamato
                                            e
pi` volte per messaggi pi` lunghi di sei byte): <buf>, che ospita tali byte, ` quindi
   u                      u                                                  e
un vettore di sei unsigned char e <lun> ` la lunghezza di un singolo blocco
                                              e
(lun==6 per ogni blocco, tranne l’ultimo). Il primo blocco ha per primo byte
0xF0 (corrispondente all’identificatore MIDI_SYSTEM_PREFIX) e l’ultimo blocco
                            `
ha nell’ultimo byte 0xF7. E un evento solo di output.
     In soundcard.h versione 3.5.4 SEQ_SYSEX() ha un errore, che ` stato corretto
                                                                    e
nelle versioni successive: nel byte numero 1 del record andrebbe immagazzinato
<dev>, cosa che non avviene. Il rimedio a ci` ` spedire il primo blocco della sysex
                                              oe
con il primo byte uguale a <dev> e il resto dei byte normalmente definiti, oppure
si pu` modificare SEQ_SYSEX() in soundcard.h come segue:
      o

                                         90
CAPITOLO 4. SINTETIZZATORI E MIDI                         4.6. OUTPUT DEGLI EVENTI




#define SEQ_SYSEX(dev, buf, len) 
           {int i, l=(len); if (l>6)l=6;
            _SEQ_NEEDBUF(8);
            _seqbuf[_seqbufptr] = EV_SYSEX;
            _seqbuf[_seqbufptr+1] = (dev);
            for(i=0;i<l;i++)_seqbuf[_seqbufptr+i+2] = (buf)[i];
            for(i=l;i<6;i++)_seqbuf[_seqbufptr+i+2] = 0xFF;
            _SEQ_ADVBUF(8);}

4.6.3     Controllo della coda degli eventi
Come si ` avuto modo di dire in precedenza, la struttura del sequencer ruo-
          e
ta attorno alla coda degli eventi: questa ha il compito principale di prevenire
l’esecuzione in ritardo degli eventi stessi, ed ` gestita dal driver con una tempo-
                                                e
rizzazione abbastanza precisa. Tuttavia c’` una grossa limitazione legata a questa
                                             e
architettura: la coda introduce una grossa latenza nell’elaborazione degli eventi.
    Per cercare di aggirare questo problema ` disponibile la seguente chiamata:
                                               e
struct seq_event_rec evento;
/* Si riempie evento.arr[] (8 byte) */
ioctl(seqfd, SNDCTL_SEQ_OUTOFBAND, &evento);
essa esegue immediatamente l’output di evento, effettuando il by–pass di altri
eventi eventualmente in coda; la struct seq_event_rec ha un unico campo:
unsigned char arr[8 ] vettore in grado di ospitare un evento da otto byte, sia
     per /dev/music che per /dev/sequencer; il formato di ogni evento che pu`o
     contenere ` descritto nella prossima Sezione 4.7
               e
questa chiamata non ` comunque adatta alla spedizione di eventi di tipo sys-
                        e
tem exclusive (se non quelli da sei byte al massimo), poich´ gli eventi spediti
                                                                e
da SNDCTL_SEQ_OUTOFBAND si inframezzano a quelli nella coda, mentre i sysex
devono essere contigui; inoltre, proprio perch´ il driver non tiene conto di quali
                                               e
eventi siano in esecuzione quando ` effettuato l’output di evento, questa chiama-
                                   e
ta potrebbe avere l’effetto collaterale di far perdere traccia delle note che stanno
suonando.
    Un altro problema della coda degli eventi ` di poter risultare bloccante per
                                                 e
il processo se essa di riempie, qualora non si sia usato O_NONBLOCK nell’apertura
del device file: in tal caso il comportamento di default del driver ` di sbloccare il
                                                                    e
processo quando la coda si ` svuotata per met`. Ad esempio, se la coda di output
                             e                 a
pu` ospitare 1024 eventi (che ` la dimensione di default, se uno non ricompila i
   o                             e
sorgenti del driver con un valore diverso), quando si avranno 512 posti liberi per
gli eventi da porre in coda (soglia di output) il processo sar` sbloccato.
                                                              a
    Una chiamata consente di modificare il valore della soglia di output:
int soglia = <valore>;
ioctl(seqfd, SNDCTL_SEQ_THRESHOLD, &soglia);

                                        91
4.7. FORMATO DEGLI EVENTI                         CAPITOLO 4. SINTETIZZATORI E MIDI




ove soglia ` compreso tra 0 e il massimo numero di eventi in coda meno uno; se
              e
la soglia di output cresce il processo sar` bloccato pi` a lungo.
                                            a            u
    Esiste la possibilit` di realizzare output non bloccante avvalendosi della seguente
                        a
chiamata:

int neventi;
ioctl(seqfd, SNDCTL_SEQ_GETOUTCOUNT, &neventi);

essa ritorna in neventi il numero di eventi che possono essere ancora inseriti in
coda prima che l’inserimento di un ulteriore evento risulti bloccante per il processo
utente. Questa chiamata, effettuata quando la coda ` vuota (ad esempio, quando
                                                       e
ancora non ` stato effettuato alcun output di eventi), ritorna il massimo numero
             e
di eventi ospitabili in coda; se il device file ` stato aperto con O_RDONLY essa
                                                e
ritorna neventi==0.
    In pratica, affinch´ il processo non sia bloccato, si deve sempre controllare
                         e
che il numero di eventi da accodare con SEQ_DUMPBUF() (tenendo quindi conto
del numero di eventi nel buffer utente) sia sempre inferiore a neventi. Un’altra
possibilit` ` aprire il device file con O_NONBLOCK e scrivere un evento per volta con
          ae
write(); quando ` restituito -1 e errno==EAGAIN la coda degli eventi ` piena:
                    e                                                      e

if (write(seqfd, buf, <dim. evento>) == -1) {
    /* Se errno==EAGAIN la coda di output e’ piena */
}

   Sull’output degli eventi c’` da dire un’ultima cosa, ovvero che l’interfaccia di
                              e
programmazione di OSS rende disponibile una macro per la sincronizzazione del
processo utente con la coda degli eventi:

SEQ_ECHO_BACK(<codice>);

essa mette nella coda di output un evento che, alla sua esecuzione da parte del
driver, provoca l’inserimento nella coda di input di un evento uguale (se il device
file ` stato aperto con O_RDWR). Il processo utente ` cos` in grado di sapere, quan-
    e                                               e   ı
do tale evento ` letto dalla coda di input, che la sequenza di output degli eventi
                e
ha raggiunto il punto in cui esso era stato inserito; <codice> reca 32 bit di in-
formazione arbitraria se si sta usando /dev/music o 24 bit per /dev/sequencer
(come si vedr` in Sezione 4.8). Ad esempio, <codice> potrebbe essere un conta-
              a
tore progressivo inviato ad ogni fine misura, per sincronizzare il processo utente
con l’esecuzione della partitura.


4.7      Formato degli eventi
Nel buffer o nella coda degli eventi, un evento ` un record di quattro o otto
                                                    e
byte formattato dalle macro precedentemente descritte. Adesso li si esaminer` ina
dettaglio, anche se l’approccio sin qui seguito ha volutamente trascurato i dettagli

                                         92
CAPITOLO 4. SINTETIZZATORI E MIDI                         4.7. FORMATO DEGLI EVENTI




          d’implementazione, perch´ ` necessario sapere come gli eventi sono composti se
                                     ee
          si vuole utilizzare la chiamata SNDCTL_SEQ_OUTOFBAND o se si vuole effettuare
          l’input di eventi.
              Per quanto riguarda /dev/music, gli eventi sono cos` composti:
                                                                 ı


              • il primo byte descrive il tipo, per cui ` uno dei seguenti identificatori:
                                                         e
                EV_CHN_VOICE, EV_CHN_COMMON, EV_TIMING, EV_SYSEX; il primo ` un even-
                                                                                   e
                to di sistema (riguarda la temporizzazione), gli altri sono eventi a livello di
                dispositivo (canale o porta)


              • per EV_CHN_VOICE e EV_CHN_COMMON il secondo byte ` il numero del dis-
                                                                       e
                positivo, mentre per EV_TIMING ` il sottotipo di evento di temporizzazione:
                                               e
                TMR_START, TMR_STOP, TMR_CONTINUE, TMR_WAIT_ABS, TMR_WAIT_REL, TMR_ECHO,
                TMR_TEMPO, TMR_SPP, TMR_TIMESIG


              • per EV_CHN_VOICE e EV_CHN_COMMON il terzo byte esprime l’azione da in-
                traprendere; pu` essere uno dei seguenti identificatori MIDI: MIDI_NOTEON,
                               o
                MIDI_NOTEOFF, MIDI_KEY_PRESSURE, MIDI_CHN_PRESSURE, MIDI_CTL_CHANGE,
                MIDI_PGM_CHANGE, MIDI_PITCH_BEND


              Nelle seguenti tabelle si riporta come sono composti i record per ogni evento,
          raggruppati secondo il tipo; se il record ` memorizzato in unsigned char buf[8],
                                                    e
          le posizioni che seguono il nome della macro per l’evento corrispondono ai byte
          contenuti in buf[].


       Eventi per le voci           0           1             2              3     4       5      6   7
       SEQ_START_NOTE()        EV_CHN_VOICE    dev       MIDI_NOTEON        can   nota    vol     0   0
       SEQ_STOP_NOTE()         EV_CHN_VOICE    dev      MIDI_NOTEOFF        can   nota    vol     0   0
       SEQ_KEY_PRESSURE()      EV_CHN_VOICE    dev    MIDI_KEY_PRESSURE     can   nota   pres     0   0


Eventi per i canali         0            1            2              3      4     5      6           7
SEQ_CHN_PRESSURE()    EV_CHN_COMMON     dev   MIDI_CHN_PRESSURE     can   pres    0      0           0
SEQ_CONTROL()         EV_CHN_COMMON     dev    MIDI_CTL_CHANGE      can    ctl    0   LSB val     MSB val
SEQ_SET_PATCH()       EV_CHN_COMMON     dev    MIDI_PGM_CHANGE      can   patch   0      0           0
SEQ_BENDER()          EV_CHN_COMMON     dev    MIDI_PITCH_BEND      can     0     0   LSB val     MSB val


          NOTA: per SEQ_CONTROL() e SEQ_BENDER() si accede allo short immagazzinato
             nei byte 6 e 7 con *(short *)&buf[6]; la rappresentazione in tabella ` per
                                                                                  e
             un’architettura little endian

                                                     93
4.8. INPUT DEGLI EVENTI                             CAPITOLO 4. SINTETIZZATORI E MIDI



  Eventi temporali                0              1            2    3   4    5     6    7
  SEQ_START_TIMER()           EV_TIMING      TMR_START        0    0          0
  SEQ_STOP_TIMER()            EV_TIMING       TMR_STOP        0    0          0
  SEQ_CONTINUE_TIMER()        EV_TIMING     TMR_CONTINUE      0    0          0
  SEQ_WAIT_TIME()             EV_TIMING     TMR_WAIT_ABS      0    0        tick
  SEQ_DELTA_TIME()            EV_TIMING     TMR_WAIT_REL      0    0        tick
  SEQ_ECHO_BACK()             EV_TIMING       TMR_ECHO        0    0       codice
  SEQ_SET_TEMPO()             EV_TIMING      TMR_TEMPO        0    0         bpm
  SEQ_SONGPOS()               EV_TIMING       TMR_SPP         0    0         pos
  SEQ_TIME_SIGNATURE()        EV_TIMING     TMR_TIMESIG       0    0       misura

NOTA: il valore del parametro per gli ultimi sei eventi ` un unsigned int immagazz-
                                                        e
   inato nei byte dal 4 al 7 con *(unsigned int *)&buf[4]

  System exclusive           0         1     2       3       4      5       6          7
  SEQ_SYSEX()             EV_SYSEX    dev   byte    byte    byte   byte    byte       byte

NOTA: il primo byte inviato nel primo blocco della sysex deve essere uguale a 0xF0,
   mentre l’ultimo blocco ha l’ultimo byte pari a 0xF7; se l’ultimo blocco non `  e
   riempito (len==6), i byte rimanenti sono riempiti con 0xFF

    /dev/sequencer accetta in output eventi da quattro (quello generato da
SEQ_MIDIOUT()) e otto byte (quelli per /dev/music), ma per quanto riguarda
l’input ci sono solo tre eventi, da quattro byte ciascuno:

                Eventi corti              0           1        2       3
               SEQ_MIDIOUT()         SEQ_MIDIPUTC    byte    nporta    0
                     —                 SEQ_WAIT               tick
                     —                 SEQ_ECHO              codice

NOTA: negli ultimi due eventi tick e codice sono entrambi da 24 bit


4.8     Input degli eventi
L’input di un evento si effettua in maniera molto semplice:
unsigned char buf[<dim. evento>];
if (read(seqfd, buf, <dim. evento>) != <dim. evento>)
    errore("Lettura dal sequencer");
ove <dim. evento> ` la dimensione di un evento: per /dev/music tutti gli eventi
                     e
di input sono di otto byte, mentre per /dev/sequencer sono da quattro byte. Se
il buffer di input si riempie (as esempio, se il processo utente non ` abbastanza
                                                                       e
veloce ad estrarre gli eventi) ulteriori eventi in arrivo verranno persi.
    OSS mette a disposizione una chiamata per conoscere il numero di eventi che
attendono nella coda di input:

                                            94
CAPITOLO 4. SINTETIZZATORI E MIDI                            4.8. INPUT DEGLI EVENTI




int neventi;
ioctl(seqfd, SNDCTL_SEQ_GETINCOUNT, &neventi);
questa chiamata ritorna neventi==0 se non c’` alcun evento in coda oppure se il
                                                  e
device file ` stato aperto con O_WRONLY.
            e
   Per default la read() risulta bloccante per il processo utente se non ci sono
eventi in coda o se si ` cercato di leggere un numero di byte superiori a neventi*<dim. evento>.
                       e
Con la chiamata precedente ` possibile realizzare input non bloccante controllan-
                                e
do il numero di eventi in coda prima della lettura:
ioctl(seqfd, SNDCTL_SEQ_GETINCOUNT, &neventi);
if ((neventi > 0) &&
          (read(seqfd, buf, <dim. evento>) != <dim. evento>))
    errore("Lettura dal sequencer");
   L’apertura del device file con O_NONBLOCK fa ritornare alla read() -1 con
errno==EAGAIN se si ` cercato di leggere un numero di byte superiore a quelli
                      e
presenti in coda; quindi un altro modo per realizzare l’input non bloccante `:
                                                                            e
if (read(seqfd, buf, <dim. evento>) == -1) {
    /* Se errno==EAGAIN la coda di input e’ vuota */
}
questo metodo implica la perdita di un tick di tempo di CPU, poich´ deve essere
                                                                      e
“svegliato” dallo stato di wait il modulo del kernel che si occupa della gestione
della porta MIDI. Nel caso di Alpha ci` corrisponde a circa un millisecondo,
                                            o
mentre per gli altri a circa dieci millisecondi.
    C’` anche la possibilit` di effettuare un’attesa con timeout da parte di read()
      e                    a
tramite la seguente chiamata:
int timeout = <valore>;
ioctl(seqfd, SNDCTL_MIDI_PRETIME, &valore);
ove <valore> ` dieci volte la risoluzione temporale del timer del kernel. Ad
                 e
esempio, se il timer del kernel ha una frequenza di 100 Hz la risoluzione temporale
` di 10ms e l’attesa ` per multipli di 100ms; se timeout==10 l’attesa da parte di
e                     e
read() per un evento di input dura al massimo un secondo.
    Si consiglia, se si vuole precisione nell’attesa, di controllare la frequenza del
timer prima di impostare il timeout; ci` si effettua usando SNDCTL_SEQ_CTRLRATE
                                        o
con /dev/sequencer. Incidentalmente, se timeout==0 la read() esegue un in-
put non bloccante, cio` se ci sono dei byte in coda li estrae, altrimenti ritorna
                         e
immediatamente senza attendere.

4.8.1     Eventi per /dev/music
Per questo device file la read(seqfd, buf, 8) ritorner` uno fra gli eventi rela-
                                                          a
tivi alle seguenti macro (il cui formato ` nella Sezione 4.7): SEQ_START_NOTE(),
                                         e

                                         95
4.8. INPUT DEGLI EVENTI                         CAPITOLO 4. SINTETIZZATORI E MIDI




SEQ_STOP_NOTE(), SEQ_KEY_PRESSURE(), SEQ_CONTROL(), SEQ_SET_PATCH(),
SEQ_BENDER(), SEQ_CHN_PRESSURE(), SEQ_ECHO_BACK(). Il driver ignora le se-
quenze system exclusive e gli Active Sensing.
    Inoltre, se la gestione del timer ` esterna (ad esempio, demandata al modo
                                       e
intelligente della Roland MPU–401), possono essere ritornati anche gli eventi rela-
tivi alle macro: SEQ_START_TIMER(), SEQ_STOP_TIMER(), SEQ_CONTINUE_TIMER(),
SEQ_SONGPOS().
    Fra un evento e l’altro il driver inserisce in coda anche l’evento marcatempo
relativo a SEQ_WAIT_TIME(), con l’attesa fra un evento e l’altro espressa in tick;
la risoluzione temporale dipende dalla predisposizione che si era data al timer
tramite SNDCTL_TMR_TIMEBASE. Se due eventi arrivano in sequenza, ovvero al di
sotto della risoluzione temporale di un tick, fra di essi non sar` inserito alcun
                                                                    a
evento marcatempo; ad esempio, se sulla tastiera MIDI si premono simultanea-
mente tre tasti, dalla coda di input si potranno estrarre tre SEQ_START_NOTE()
senza eventi marcatempo fra essi.
    Per interpretare gli eventi letti dalla coda basta un semplice loop:


for (i=0; i<10; i++)
{    /* Interpreta dieci eventi, input bloccante */
     read(seqfd, buf, 8);
     if (buf[0] == EV_TIMING)
          /* Eventi di temporizzazione */
          switch (buf[1]) {
              case TMR_WAIT_ABS: ...
                                  break;
              case TMR_START:     ...
                      .
                      .
                      .
          }
     else { /* Eventi EV_CHN_VOICE e EV_CHN_COMMON */
             nporta = buf[1];
             canale = buf[3];
             switch (buf[2]) {
                 case MIDI_NOTEON: ...
                                    break;
                 case MIDI_NOTEOFF: ...
                          .
                          .
                          .
             }
     }
}

                                       96
CAPITOLO 4. SINTETIZZATORI E MIDI                      4.9. NUOVE CARATTERISTICHE




4.8.2    Eventi per /dev/sequencer
Per questo device file la read(seqfd, buf, 4) pu` ritornare solo tre tipi di
                                                       o
evento, che sono elencati alla fine della Sezione 4.7. Essi sono rispettivamente
l’input di un byte MIDI, l’evento marcatempo e l’evento di sincronizzazione con
la coda di output; a differenza di /dev/music, le sequenze system exclusive non
sono ignorate, in quanto lette sotto forma di byte MIDI.
     La risoluzione temporale del timer ` fissa ed ` pari a quella del kernel; si
                                           e         e
pu` ricorrere a SNDCTL_SEQ_CTRLRATE per determinare la frequenza. Si hanno a
    o
disposizione solo 24 bit per rappresentare i tick.
     L’interpretazione degli eventi letti dalla coda ` pi` semplice di quella vista
                                                     e u
precedentemente per /dev/music:

for (i=0; i<10; i++)
{    /* Interpreta dieci eventi, input bloccante */
     read(seqfd, buf, 4);
     switch (buf[0]) {
         case SEQ_WAIT:       tick = *(int *)&buf[0] >> 8;
                              ...
                              break;
         case SEQ_MIDIPUTC: midi_byte = buf[1];
                              nporta = buf[2];
                              ...
                              break;
         case SEQ_ECHO:       codice = *(int *)&buf[0] >> 8;
                              ...
                              break;
               default: /* Gestione dell’errore */
                          exit(-1);
     }
}


4.9     Nuove caratteristiche
Le versioni di OSS successive alla 3.6 hanno introdotto parecchie novit`, fra
                                                                          a
le quali nuove chiamate e qualche ritocco cosmetico all’interfaccia di program-
mazione:

   • la seguente chiamata ritorna il valore in tick del timer (interno o esterno)
     del sequencer:

      int ntick;
      ioctl(seqfd, SNDCTL_SEQ_GETTIME, &ntick);

                                        97
4.9. NUOVE CARATTERISTICHE                     CAPITOLO 4. SINTETIZZATORI E MIDI




   • c’` una nuova chiamata funzionalmente equivalente a SNDCTL_SYNTH_INFO,
       e
     salvo il fatto che nel campo name della struct synth_info ` ritornata una
                                                                    e
     stringa descrittrice del tipo di chip sintetizzatore nella scheda audio:

     struct synth_info idinfo;
     idinfo.device = <numero dispositivo>;
     ioctl(seqfd, SNDCTL_SYNTH_ID, &idinfo);

     le stringhe ritornate in name possono essere:
      "PSS"           Echo Personal Sound System (ESC614)
      "PSSMPU"        Personal Sound System + MPU–401
      "PSSMSS"        Personal Sound System (MSS)
      "GUS"           Gravis UltraSound
      "GUS16"         Gravis UltraSound 16
      "GUSPNP"        Gravis UltraSound PnP
      "IWAVE"         Gravis UltraSound wavetable
      "MSS"           Microsoft Sound System
      "DESKPROXL"     Compaq DeskPro XL
      "MAD16"         MAD16/Mozart (MSS)
      "MAD16MPU"      MAD16/Mozart + MPU–401
      "CS4232"        CS4232
      "CS4232MPU"     CS4232 + MPU–401
      "OPL3"          OPL-2/OPL-3 FM
      "PAS16"         Pro Audio Spectrum 16
      "MPU401"        Roland MPU–401
      "UART401"       MPU–401 (UART)
      "MAUI"          Turtle Beach Maui
      "MIDI6850"      6860 UART MIDI
      "SBLAST"        Sound Blaster
      "SBPNP"         Sound Blaster PnP
      "SBMPU"         Sound Blaster + MPU–401
      "SSCAPE"        Ensoniq SoundScape
      "SSCAPEMSS"     Microsoft Sound System (SoundScape)
      "OPL3SA"        Yamaha OPL3-SA
      "OPL3SASB"      OPL3-SA (modo Sound Blaster)
      "OPL3SAMPU"     OPL3-SA MIDI
      "TRXPRO"        MediaTrix AudioTrix Pro
      "TRXPROSB"      AudioTrix (modo Sound Blaster)
      "TRXPROMPU"     AudioTrix + MPU–401
      "SOFTSYN"       SoftOSS Virtual Wavetable
      "SoftOSS"       SoftOSS

   • per quanto riguarda la struct synth_info, si consiglia l’uso del nuovo
     identificatore SAMPLE_TYPE_BASIC al posto di SAMPLE_TYPE_GUS

                                       98
CAPITOLO 4. SINTETIZZATORI E MIDI                    4.9. NUOVE CARATTERISTICHE




   • nel caso il sintetizzatore sia programmabile, la chiamata seguente consente
     di passargli comandi e ricevere dati:

     synth_control sctl;
     sctl.devno = <numero sintetizzatore>;
     /* Inizializzare anche sctl.data[] */
     if (ioctl(seqfd, SNDCTL_SYNTH_CONTROL, &sctl) == -1)
         errore("SNDCTL_SYNTH_CONTROL");

     la struct synth_control (di cui synth_control ` un typedef) ` cos`
                                                   e             e    ı
     composta:
     int devno numero del sintetizzatore
     char data[4000 ] vettore di byte ove sono inseriti i comandi specifici per il
         sintetizzatore e in cui ` ritornata un’eventuale risposta
                                 e
     ` obbligatorio inizializzare entrambi i campi prima della chiamata
     e
   • la seguente chiamata consente di rimuovere dalla memoria una patch wavetable
     precedentemente caricata:

     remove_sample rsample;
     rsample.devno = <numero sintetizzatore>;
     rsample.bankno = <numero instrument bank>;
     rsample.instrno = <numero preset>;
     if (ioctl(seqfd, SNDCTL_SYNTH_REMOVESAMPLE, &rsample) == -1)
         errore("SNDCTL_SYNTH_REMOVESAMPLE");

     la struct remove_sample (di cui remove_sample ` un typedef) ` cos`
                                                   e             e    ı
     composta:
     int devno numero del sintetizzatore
     int bankno numero del bank MIDI (porre uguale a 0 se General MIDI)
     int instrno numero del preset timbrico
     ` obbligatorio inizializzare tutti i campi prima della chiamata
     e
   • in struct patch_info l’identificatore WAVE_PATCH sostituisce GUS_PATCH,
     che resta comunque disponibile per compatibilit` all’indietro, evidenzian-
                                                      a
     do l’architettura pi` indipendente dal dispositivo che ora ha OSS per la
                         u
     gestione delle patch; per il campo mode ` stato aggiunto l’identificatore
                                               e
     WAVE_FAST_RELEASE, che consente lo spegnimento immediato di una nota
     al Note OFF invece di un rilascio graduale
   • la macro SEQ_SET_PATCH() ` stata rinominata SEQ_PGM_CHANGE(), che ri-
                                   e
     corda meglio la funzione MIDI relativa; SEQ_SET_PATCH() diventa un alias
     per compatibilit` all’indietro
                     a

                                       99
4.9. NUOVE CARATTERISTICHE                             CAPITOLO 4. SINTETIZZATORI E MIDI




4.9.1      SoftOSS
SoftOSS ` un modulo di OSS che permette di emulare la presenza di un sintetizza-
          e
tore wavetable con 32 voci simultanee (o pi` ) tramite una CPU sufficientemente
                                               u
veloce e una normale scheda audio a 16 bit. Le capacit` di tale sintetizzatore “vir-
                                                          a
                                                         6
tuale” coincidono con quelle della Gravis UltraSound (GUS), per cui ` garantita
                                                                           e
la compatibilit` all’indietro con le applicazioni per questa sviluppate.
               a
    Il nuovo sintetizzatore ` visibile nella lista dei dispositivi di sintesi ottenuta
                            e
con cat /dev/sndstat; ad esempio:
    .
    .
    .

Synth devices:
0: Yamaha OPL-3
1: SoftOSS
    .
    .
    .
    Una particolarit` da tenere presente ` che, quando sono aperti /dev/music o
                     a                      e
/dev/sequencer, SoftOSS alloca per s´ /dev/dsp (che ridiventa disponibile chiu-
                                        e
dendo i device file del sequencer); non ` quindi possibile campionare o riprodurre
                                         e
in concorrenza con il sequencer nei sistemi con una sola scheda audio.
    SoftOSS ` un modulo del kernel, per cui le altre attivit` di elaborazione non
              e                                             a
risentono particolarmente della miscelazione delle waveform, la quale risulta d’al-
tro canto temporalmente precisa e affidabile; gli altri processi girano solo pi`    u
lentamente.
    Se la CPU ` abbastanza veloce, la potenza di calcolo a questa sottratta rende
                e
la qualit` dell’audio ottenuto paragonabile a quella ottenibile su un sistema leg-
         a
germente caricato; anche se ` possibile utilizzare un processore di classe 486
                                e
diminuendo la frequenza di campionamento, ` raccomandato l’utilizzo di un pro-
                                              e
cessore dal Pentium 133MHz7 in su. Al crescere della potenza di calcolo ` possibile
                                                                        e
utilizzare 32 voci simultanee a 44.1kHz, ma mantenendo basso il numero di vo-
ci simultanee (da quattro a otto) questa frequenza di campionamento dovrebbe
essere utilizzabile con qualsiasi processore.
    Se la CPU ` troppo lenta il suono ` distorto e il sistema rallenta parecchio
                 e                         e
per un alto numero di voci simultanee; la situazione migliora diminuendone il
numero e ovviamente ritorna normale fermando l’esecuzione. Ad esempio, con
un processore lento la musica in background pu` far rallentare un gioco fino a
                                                  o
renderlo ingiocabile.
    Le patch sono quelle utilizzate dalla GUS (file GF1), che non necessariamente
devono essere strumenti musicali: SoftOSS pu` essere usato per la creazione di
                                                o
   6
     Ovviamente se si dispone gi` di una GUS o di un’altra scheda con capacit` di sintesi
                                  a                                                 a
wavetable SoftOSS diventa pressoch´ inutile e lo si pu` disabilitare: esse fanno in hardware,
                                      e                  o
quindi pi` efficientemente, ci` che SoftOSS emula in software
         u                    o
   7
     Con tale processore si possono ottenere 32 voci simultanee per frequenze di campionamento
di 32kHz); SoftOSS beneficia inoltre di processori MMX, anche se non sfrutta queste estensioni

                                            100
CAPITOLO 4. SINTETIZZATORI E MIDI                       4.9. NUOVE CARATTERISTICHE




effetti sonori nei giochi, per la riproduzione di messaggi preregistrati e di segnali
d’allarme, applicazioni per le quali il sistema non risulta particolarmente caricato.
    Attualmente la memoria per le patch ` limitata a max 8MB; per il sistema in
                                             e
s´ sono richiesti 16MB di RAM (32MB raccomandati), anche se ` possibile usare
 e                                                                   e
SoftOSS con una quantit` di RAM inferiore ai 16MB se non si caricano molte
                           a
patch.


4.9.2     OSSlib
OSSlib ` una libreria che supporta il caricamento di patch General MIDI per
         e
OPL–3 e Gravis UltraSound (quindi anche per SoftOSS) in maniera indipendente
dal dispositivo; essa ` intesa per l’uso con software MIDI da parte di programma-
                      e
tori MIDI, consentendo di scrivere programmi che funzionino senza cambiamenti
con qualsiasi dispositivo sintetizzatore che venga introdotto in futuro. OSSlib `
                                                                                e
disponibile anche per OSS/Free dalla versione 3.8β2.
    La libreria ` attivata definendo la macro vuota OSSLIB prima dell’inclusione
                e
di soundcard.h, purch´ essa sia stata correttamente compilata e installata; ad
                          e
esempio:
    .
    .
    .

#define OSSLIB
#include <sys/soundcard.h>

#ifndef OSSLIB
void seqbuf_dump() /* Deve essere usata solo se non c’e’ OSSLIB */
{
    if (_seqbufptr && (write(seqfd, _seqbuf, _seqbufptr) == -1))
        errore("/dev/music");
    _seqbufptr = 0;
}
#endif

  .
  .
  .
  Dopo l’apertura del device file si inizializza la libreria con il seguente fram-
mento di codice:

#ifdef OSSLIB
    if (OSS_init(seqfd, DIM_BUFFER) != 0)
        errore("OSS_init()");
#endif

ove 32≤ DIM_BUFFER ≤2048 ` la dimensione del buffer degli eventi, che con ques-
                            e
ta chiamata viene allocato in memoria dinamica; SEQ_DEFINEBUF() non ` pi`e u

                                        101
4.10. ESEMPIO DI PROGRAMMA                        CAPITOLO 4. SINTETIZZATORI E MIDI




necessaria con OSSlib, ma usarla rende il codice compilabile con e senza la libre-
ria (nel primo caso la dimensione desiderata del buffer viene immagazzinata in
static int _requested_seqbuflen).
    OSSlib introduce due nuove macro per il caricamento delle patch su richiesta
(patch caching):

SEQ_LOAD_GMINSTR(<dev>, <numero patch>);
SEQ_LOAD_GMDRUM(<dev>, <numero patch>);

che consentono di precaricare un preset timbrico <numero patch> per il sin-
tetizzatore <dev>, rispettivamente per la sezione strumentale e ritmica, senza
che un eventuale ritardo in tale operazione introduca errori di ritmo nell’ese-
cuzione di una partitura (il caricamento ` fatto prima di far partire il timer).
                                           e
SEQ_PGM_CHANGE() effettua anche il caricamento automatico di una patch se
questa non ` presente in memoria.
            e
    Tutte le altre macro rimangono inalterate per il programmatore, anche se
OSSlib le ridefinisce dietro le quinte in maniera tale che siano pi` sicure.
                                                                  u


4.10       Esempio di programma
Il programma che segue effettua l’input di eventi da una porta MIDI e li redirige in
output a un dispositivo sintetizzatore specificato da linea di comando; le note ven-
gono suonate con un preset timbrico, specificato anch’esso da linea di comando,
sul canale 0.
    Nel caso dei sintetizzatori interni alla scheda audio si presuppone che i preset
timbrici da usare siano gi` stati caricati; ad esempio:
                           a

~>sint 0 3

suona le note provenienti dalla porta MIDI usando il dispositivo 0 (come da lista
dei “Synth devices:” con cat /dev/sndstat) usando la patch numero 3 (Electric
Grand Piano, se le patch sono state caricate seguendo lo standard General MIDI).
    Gli eventi suonati sono visualizzati a video; il Song Position Pointer, gli eventi
marcatempo e il Program Change sono ignorati. Si esce dal programma premendo
contemporaneamente i tasti Ctrl e C.

/*
 *     sint.c - Usa un device di OSS per suonare con un dato preset
 *              timbrico le note che arrivano dalla porta MIDI
 */

#include   <stdio.h>
#include   <stdlib.h>
#include   <unistd.h>
#include   <fcntl.h>

                                         102
CAPITOLO 4. SINTETIZZATORI E MIDI                    4.10. ESEMPIO DI PROGRAMMA




#include <sys/soundcard.h>

int seqfd;                          /* Dichiarazioni globali file */
SEQ_DEFINEBUF(128);                 /* descriptor e buffer eventi */

/* Utilizza il canale 0 per suonare le note */
const int CANALE = 0;


void errore(const char *msgerr)         /* Gestione degli errori */
{
   perror(msgerr);
   exit(-1);
}

void seqbuf_dump()        /* Dump del buffer degli eventi */
{
    if (_seqbufptr && (write(seqfd, _seqbuf, _seqbufptr) == -1))
        errore("/dev/music");
    _seqbufptr = 0;
}


int main(int argc, char *argv[])
{
   int dev, npatch, nsint, ppqn = 48, bpm = 60;
   struct synth_info sinfo;
   unsigned char buf[8];

   if (argc != 3) { /* Numero di argomenti non adeguato */
       printf("Uso: %s <# sintetizzatore> <# patch>n", argv[0]);
       exit(-1);
   }
   /* Input da MIDI, output sul device selezionato */
   if ((seqfd = open("/dev/music", O_RDWR)) == -1)
       errore("/dev/music");

   /* Il device dato da linea di comando e’ OK? */
   dev = atoi(argv[1]);
   ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &nsint);
   if (dev > (nsint-1)) { /* Device non OK */
       printf("Non esiste il device #%d!n", dev);
       exit(-1);
   }
   /* Se il device e’ un OPL-3 attiva il modo 4OP */
   sinfo.device = dev;


                                        103
4.10. ESEMPIO DI PROGRAMMA                CAPITOLO 4. SINTETIZZATORI E MIDI




   ioctl(seqfd, SNDCTL_SYNTH_INFO, &sinfo);
   if (sinfo.synth_subtype == FM_TYPE_OPL3)
       ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &dev);

   /* Limita il numero della patch fra 0 e 127 */
   npatch = atoi(argv[2]);
   if (npatch < 0)   npatch = 0;
   if (npatch > 127) npatch = 127;

   /* Sincronizzazione timebase: PPQN e BPM */
   ioctl(seqfd, SNDCTL_TMR_TIMEBASE, &ppqn);
   ioctl(seqfd, SNDCTL_TMR_TEMPO, &bpm);

   /* Attiva il timer, seleziona il preset e regola il volume */
   ioctl(seqfd, SNDCTL_TMR_START);
   SEQ_SET_PATCH(dev, CANALE, npatch);
   SEQ_CONTROL(dev, CANALE, CTL_MAIN_VOLUME, 127);
   SEQ_DUMPBUF();

   /* Riproduzione degli eventi in input dalla porta MIDI */
   while (1) {          /* Da questo ciclo si esce con ^C */
       if (read(seqfd, buf, 8) == -1) /* Input bloccante */
           errore("MIDI read()");

       if (buf[0] == EV_TIMING)
           switch (buf[1]) { /* Eventi di temporizzazione */
                case TMR_START:
                        puts("<Start Timer>");
                        break;
                case TMR_STOP:
                        puts("<Stop Timer>");
                        break;
                case TMR_CONTINUE:
                        puts("<Continue Timer>");
                        break;
                case TMR_SPP:       /* DA IGNORARE */
                case TMR_WAIT_ABS: /* DA IGNORARE */
                        goto FineCiclo;
                default:
           }
       else { /* Eventi per EV_CHN_VOICE e EV_CHN_COMMON */
              buf[1] = dev;
              buf[3] = CANALE;
              switch (buf[2]) {
                  case MIDI_NOTEON:
                        printf("     Note ON: nota %d, vel %dn",


                                  104
CAPITOLO 4. SINTETIZZATORI E MIDI                      4.10. ESEMPIO DI PROGRAMMA




                                    buf[4], buf[5]);
                          break;
                    case MIDI_NOTEOFF:
                          printf("     Note OFF: nota %d, vel %dn",
                                 buf[4], buf[5]);
                          break;
                    case MIDI_KEY_PRESSURE:
                          printf("Polyphonic Key Pressure: nota %d, pres %dn",
                                 buf[4], buf[5]);
                          break;
                    case MIDI_CHN_PRESSURE:
                          printf("Channel Pressure: pres %dn", buf[4]);
                          break;
                    case MIDI_CTL_CHANGE:
                          printf("Control Change: controller %d, val %dn",
                                 buf[4], *(short *)&buf[6]);
                          break;
                    case MIDI_PITCH_BEND:
                          printf("Pitch Bend Change: val %dn",
                                 *(short *)&buf[6]);
                          break;
                    case MIDI_PGM_CHANGE: /* DA IGNORARE */
                             goto FineCiclo;
                    default:
               }
        }

        /* Scrittura sul device selezionato */
        if (write(seqfd, buf, 8) == -1)
            errore("Out device write()");
    FineCiclo:
    }

    close(seqfd);
    return 0;
}




                                       105
Capitolo 5

IL MIDI A BASSO LIVELLO

5.1       Descrizione dei device file MIDI
OSS compie un buon lavoro incapsulando i sintetizzatori interni alle schede audio
e le porte MIDI, presentandone tramite i device file del sequencer una visione
unitaria per le applicazioni rivolte alla musica.
    La semplicit` e la potenza di questo tipo di gestione si paga con l’astrazione dal
                 a
protocollo dei messaggi MIDI vero e proprio: a tal scopo sono presenti i device
file /dev/midin, i quali rappresentano un’interfaccia di accesso nuda e cruda
alle porte MIDI per la realizzazione di applicazioni particolari, che sarebbero di
difficile (o addirittura impossibile) realizzazione con il sequencer.
    Nell’intenzione dell’autore del driver, i device file MIDI sono presenti per
spedire e ricevere sequenze system exclusive, tali da realizzare patch editor 1 e
librarian 2 per gli strumenti sintetizzatori connessi alle porte, o per applicazioni
come i light controllers (MIDI Show Control ).
    I device file MIDI non hanno le capacit` di temporizzazione tipiche dei device
                                             a
file del sequencer, per cui si comportano come /dev/tty in raw mode: qualsi-
asi cosa sia scritta su un device file ` trasferita quanto prima alla porta MIDI
                                        e
         3
relativa ; tutti i byte che arrivano alla porta MIDI possono essere letti dal de-
vice file, tranne l’Active Sensing (0xFE) che ` automaticamente ignorato (invece
                                               e
OSS/Linux non lo ignora).
    C’` un device file per ogni porta: il primo per la porta 0 (come da lista dei
       e
“Midi devices:” con cat /dev/sndstat) sar` /dev/midi00, il secondo sar`
                                                   a                                 a

   1
      Sono programmi che consentono la lettura e la modifica a video di tutti i parametri di uno
strumento MIDI, particolarmente le caratteristiche dei timbri (possono anche esserne creati di
nuovi); dopo le modifiche questi possono essere rispediti allo strumento
    2
      Pur non disponendo delle potenzialit` di modifica proprie degli editor, i librarian svolgono
                                             a
il compito di database per i timbri, potendo essere ivi memorizzati e ricercati in base ad alcune
caratteristiche comuni
    3
      I ritardi sono dovuti alla bufferizzazione: un byte MIDI impiega 320 µs per essere trasmesso,
per cui il driver mantiene una coda di I/O per rendere pi` efficienti i trasferimenti di dati
                                                            u

                                              106
CAPITOLO 5. IL MIDI A BASSO LIVELLO                           5.2. LETTURA DALLA PORTA MIDI




/dev/midi01, e cos` via fino a un massimo teorico4 di sedici porte MIDI.
                    ı
    Ogni device file ` bufferizzato: per default la coda di input e la coda di output
                    e
sono due buffer della dimensione di 4000 byte l’uno; ` consigliato di non cambiare
                                                     e
tale valore.
    L’apertura di un device file pu` avvenire in O_WRONLY, O_RDONLY e O_RDWR,
                                   o
con il supporto per O_NONBLOCK; ad esempio:

int midifd;
if ((midifd = open("/dev/midi00", O_RDWR)) == -1)
    errore("/dev/midi00");

utilizzando il flag O_NONBLOCK la lettura da una coda vuota o la scrittura su una
coda piena provoca un errore, con errno==EAGAIN.
    Sui device file MIDI Linux supporta select(); ` possibile effettuare I/O non
                                                  e
bloccante facendo a meno di questa tramite l’apertura del device file con il flag
O_NONBLOCK, tuttavia quest’approccio ritarda la read() o la write() di un tick5 ,
dovendo essere richiamato dallo stato di wait il modulo del kernel responsabile
della gestione della porta MIDI.


5.2       Lettura dalla porta MIDI
Per default la lettura da un device file MIDI ` bloccante se la coda di input `
                                               e                              e
vuota (per OSS/Linux ` sempre non bloccante); se per` arriva anche un solo byte
                        e                            o
il processo utente sar` sbloccato, indipendentemente da quanti byte erano stati
                      a
richiesti tramite read():

int nricevuti, nrichiesti = <numero byte>;
unsigned char buf[<dim. buffer>];
nricevuti = read(midifd, buf, nrichiesti);
/* Generalmente nricevuti <= nrichiesti; per */
/* OSS/Linux nricevuti==0 se la coda e’ vuota */

     Se la coda di input si riempie, ovvero il processo utente ` pi` lento nell’estrarre
                                                                e u
i byte da essa rispetto alla loro velocit` di arrivo in coda, gli ulteriori byte in arrivo
                                         a
saranno persi.
     Tra l’arrivo del primo byte e il ritorno della read() pu` accadere che passino
                                                                 o
parecchi ms; in questo lasso di tempo parecchi byte MIDI possono essere ricevuti,
cos` non ` inusuale che con una singola lettura possano essere ritornate decine di
    ı      e
byte se il computer ` lento o il sistema ` molto carico.
                       e                    e
     ` possibile predisporre un timeout per il tempo massimo che la read() pu`
     E                                                                                   o
attendere l’arrivo del primo byte tramite la seguente chiamata:
   4
     Il teorico ` dovuto al fatto che pu` esser stato introdotto un limite artificiale nei sorgenti
                e                       o
del driver per il massimo numero di porte gestibili — ad esempio, sei per OSS/Free 3.8.2
   5
     Circa 10ms per i sistemi non Alpha, meno di 1ms per questi ultimi

                                              107
5.2. LETTURA DALLA PORTA MIDI                   CAPITOLO 5. IL MIDI A BASSO LIVELLO




int timeout = <valore>;
ioctl(midifd, SNDCTL_MIDI_PRETIME, &timeout);

il conteggio del timeout si basa sul timer del kernel, ed ` determinato per multipli
                                                          e
di dieci volte il quanto temporale minimo (<valore> ` espresso in decimi di
                                                            e
secondo, se il timer ha frequenza 100 Hz). Se timeout==0 l’input risulta non
bloccante.
    Se si vuole un timeout preciso ` meglio rilevare la frequenza del timer del
                                       e
kernel tramite l’utilizzo di SNDCTL_SEQ_CTRLRATE su /dev/sequencer; ad esem-
pio, Linux/Alpha ha un clock di default di 1024 Hz, quindi per questo sistema la
risoluzione del timeout sarebbe nell’ordine della decina di millisecondi.
    Tramite l’uso in apertura del flag O_NONBLOCK ` possibile effettuare input non
                                                    e
bloccante anche nel modo seguente:

if ((nricevuti = read(midifd, buf, nrichiesti)) == -1) {
     if (errno == EAGAIN) {
         /* La coda di input e’ vuota */
     }
     else errore("read()");
}
else if (nricevuti == 0) {
         /* Si verifica con OSS/Linux se la coda e’ vuota */
     }

questo metodo implica la perdita di un tick di tempo di CPU per quanto detto
precedentemente; ci` costituisce uno spreco notevole nel caso i messaggi MIDI
                     o
costituiscano l’input a un sistema di sintesi in tempo reale.
    Un modo alternativo di gestire l’input non bloccante, che non spreca molto
tempo, implica l’utilizzo di select() per fare il polling del device file. La seguente
funzione ritorna 1 se ` riuscita ad estrarre un byte dalla coda di input, ritorna 0
                       e
se la coda era vuota e -1 se si ` verificato un errore:
                                e

int MIDI_read(int fd, unsigned char *pch)
{
    fd_set readset, exceptset;
    struct timeval tval;

     /* Inizializzazione insiemi di file descriptor */
     FD_ZERO(&readset);
     FD_ZERO(&exceptset);
     FD_SET(fd, &readset);
     FD_SET(fd, &exceptset);
     tval.tv_sec = tval.tv_usec = 0;         /* Attesa nulla */

     if (select(fd+1, &readset, NULL, &exceptset, &tval)) {

                                        108
CAPITOLO 5. IL MIDI A BASSO LIVELLO                 5.3. SCRITTURA SULLA PORTA MIDI




          if (FD_ISSET(fd, &exceptset))
              /* Si e’ verificato un errore */
              return -1;
          else return (read(fd, pch, 1));
     }
     else return 0;                    /* La coda di input e’ vuota */
}


5.3      Scrittura sulla porta MIDI
Il comportamento di default per la scrittura su un device file MIDI ` di non  e
bloccare il processo utente se la quantit` di byte da scrivere ` inferiore allo spazio
                                         a                     e
libero nella coda di output; se il numero di byte ` superiore il processo sar`
                                                       e                             a
bloccato. Si ritorna dalla write() solo quando nella coda di output restano
meno di cento byte: ci` equivale a un’attesa minima di circa 1.25 secondi per il
                        o
processo utente.
    Si pu` realizzare output non bloccante con l’apertura del device file tramite
         o
il flag O_NONBLOCK; in tal caso:
if (write(midifd, buf, <numero byte da scrivere>) == -1) {
     if (errno == EAGAIN) {
         /* La coda di output e’ piena */
     }
     else errore("write()");
}
con questo metodo si consiglia di scrivere un solo byte per volta, altrimenti se la
coda si riempie e write() ritorna con -1 non si ` in grado di sapere quanti byte
                                                   e
siano stati effettivamente trasferiti in output (ci` provocherebbe una perdita di
                                                   o
sincronia tra l’applicazione e i dispositivi MIDI indirizzati). Come per read(), `e
perso un tick; si consiglia l’utilizzo di select() per effettuare il polling.


5.4      Programmazione della MPU–401
In OSS due chiamate sono specificamente rivolte alla Roland MPU–401, poich´      e
originariamente sfruttate per studiare in modo pi` semplice l’I/O di questa sche-
                                                  u
da MIDI; possono tuttavia essere ancora utili per rendere facile il porting di
applicazioni sviluppate in altri ambienti specificamente per essa.
    La MPU–401 ha due modalit` di funzionamento:
                                  a
Modo UART (pass–through) l’interfaccia non compie servizi, limitandosi a
   trasferire tutto ci` che riceve senza modifiche o interpretazioni dalla porta
                      o
   MIDI al computer; ` la modalit` di default in cui si trova la porta dopo
                         e            a
   l’apertura del relativo device file e l’unico comando che l’MPU–401 accetta
   in questo caso ` il reset al modo intelligente
                   e

                                         109
5.5. ESEMPIO DI PROGRAMMA                     CAPITOLO 5. IL MIDI A BASSO LIVELLO




Modo “intelligente” (coprocessor ) l’interfaccia interpreta e gestisce i dati,
   in input accodando loro delle informazioni di temporizzazione, codici MPU
   e messaggi, e in output gestendo il loro invio; /dev/music emula quasi tutte
   le caratteristiche dell’MPU–401 in maniera portabile
   Il modo UART ` quello che la maggior parte delle schede audio emula; le
                     e
caratteristiche dell’MPU–401 sono disponibili solo con schede che supportano
specificamente il modo intelligente.
    `
   E possibile passare da un modo all’altro con la seguente chiamata:
int modo = <0 oppure 1>;
if (ioctl(midifd, SNDCTL_MIDI_MPUMODE, &modo) == -1)
    errore("SNDCTL_MIDI_MPUMODE");
se modo==0 la scheda viene posta in modo UART, mentre se modo==1 viene posta
in modo intelligente. Se questo non ` supportato dalla porta MIDI la chiamata
                                     e
ritorna errno==EINVAL.
    La chiamata seguente ` usata per spedire alla MPU–401, solo in modo intel-
                          e
ligente, un comando e i suoi parametri, ricevendo eventualmente in risposta dei
dati:
mpu_command_rec comando;
/* Inizializzare i campi di comando */
if (ioctl(midifd, SNDCTL_MIDI_MPUCMD, &comando) == -1)
    errore("SNDCTL_MIDI_MPUCMD");
ove mpu_command_rec ` una typedef struct i cui campi, da inizializzare prima
                      e
della chiamata, hanno il significato:
unsigned char cmd comando per la MPU–401

char nr args numero di argomenti del comando

char nr returns numero di byte ritornati in risposta

unsigned char data[30 ] ospita gli argomenti del comando e i dati ritornati in
     risposta
   Particolare attenzione deve essere posta nel predisporre i valori di nr_args e
nr_returns, per evitare che un errore metta fuori sincronia il driver e la porta.


5.5     Esempio di programma
In questa Sezione ` mostrato un programma d’esempio che implementa una fun-
                  e
zione MIDI_readmsg() in grado di estrarre dalla coda di input i byte MIDI e
“impacchettarli” per formare un messaggio come da struct midi_msg data in
argomento:

                                      110
CAPITOLO 5. IL MIDI A BASSO LIVELLO                      5.5. ESEMPIO DI PROGRAMMA




unsigned char status status byte; per i Channel Message ` solo il nibble supe-
                                                        e
     riore, mentre per i System Message ` non modificato
                                        e

unsigned char chn numero del canale se ` un Channel Message, altrimenti 0
                                       e

int lundata numero di data byte per un messaggio che non sia un System
      Exclusive, altrimenti numero di sysex byte meno uno

unsigned char data[2 ] primo e secondo data byte

unsigned char *psysex puntatore a un’area di memoria (di massimo 128 byte)
     che immagazzina la sysex
la funzione si usa cos`
                      ı:
struct midi_msg msg;
int codice = MIDI_readmsg(&msg);
/* codice dice se il messaggio e’ completo o no */
essa supporta la regola del running status.
    Tale funzione ` in grado di effettuare sia input bloccante che non bloccante,
                  e
secondo come si ` aperto il device file MIDI con la funzione di supporto MIDI_open(),
                e
la quale ha un uso simile a open(), salvo il fatto che non ha un valore di ritorno.
`
E supportata l’apertura di una sola porta MIDI per volta.
    MIDI_readmsg() ha tre possibili valori di ritorno:
RET MSG ERR (-1) ` ritornato se si verifica un errore in input; ad esem-
                       e
   pio, potrebbe essere un errore derivante dall’aver fatto riempire la coda di
   input, perdendo un po’ di sincronia con il flusso dei messaggi, o (meno
   probabilmente) un errore hardware

RET NO MSG (0) se si ` richiesto di effettuare input non bloccante, questo `
                           e                                                    e
   il valore ritornato nel caso la coda di input sia vuota e il messaggio non sia
   completo; bisogna chiamare nuovamente la funzione dopo un po’ di tempo,
   fino ad avere successo nel formare il messaggio

RET MSG OK (1) indica che il messaggio in argomento ` completo
                                                    e
    Il corpo principale del programma sfrutta la funzione per effettuare il dump
dei messaggi MIDI in input, ovvero li visualizza in una rappresentazione leggibile
agli umani, tramite una lunga switch().
    Al principio del listato sono definiti degli identificatori MIDI per vari messaggi,
a complemento degli altri presenti in soundcard.h, per migliorare la leggibilit`   a
complessiva del programma. Sempre a complemento, anche se ivi non utilizzata,
` stata scritta una funzione che effettua l’output di messaggi MIDI (in maniera
e
bloccante o non bloccante) aventi il formato sopra descritto.
    MIDI_writemsg() ritorna RET_MSG_OK se l’intero messaggio ` stato spedito
                                                                     e
in output, mentre se l’output ` non bloccante c’` la possibilit` che sia ritornato
                                 e                   e           a

                                        111
5.5. ESEMPIO DI PROGRAMMA                     CAPITOLO 5. IL MIDI A BASSO LIVELLO




RET_NO_MSG: in tal caso basta richiamare pi` volte la funzione, finch´ ` ritornato
                                           u                        ee
il primo valore. L’uso ` simile a quello di MIDI_readmsg(), salvo il fatto che
                           e
prima di chiamarla il messaggio msg deve essere riempito con valori adeguati.
Questa soluzione non permette di inframezzare ai normali messaggi dei Real Time
Message, tuttavia ` sempre possibile usare la write() nuda e cruda se si hanno
                    e
dei requisiti particolari.
/*
 *    dumpmidi.c - Legge i byte che arrivano alla porta MIDI e visualizza
 *                 i corrispondenti messaggi MIDI
 */

#include   <stdio.h>
#include   <stdlib.h>
#include   <string.h>
#include   <errno.h>
#include   <unistd.h>
#include   <fcntl.h>
#include   <sys/soundcard.h>


/* Codici MIDI di supporto a quelli presenti in soundcard.h */
/* System Common Messages */
#define MIDI_QUARTER_FRAME 0xF1
#define MIDI_SONG_POSITION 0xF2
#define MIDI_SONG_SELECT     0xF3
#define MIDI_TUNE_REQUEST    0xF6
/* System Exclusive */
#define MIDI_END_SYSEX       0xF7
/* Real Time Messages */
#define MIDI_TIMING_CLOCK    0xF8
#define MIDI_START           0xFA
#define MIDI_CONTINUE        0xFB
#define MIDI_STOP            0xFC
#define MIDI_ACTIVE_SENSING 0xFE
#define MIDI_SYSTEM_RESET    0xFF

/* Codici per la macchina a stati che interpreta i byte MIDI */
#define ST_INIZIO_MSG 0
#define ST_DATA_BYTE   1
#define ST_SYSEX       2
#define RET_MSG_ERR    -1
#define RET_NO_MSG     0
#define RET_MSG_OK     1

/* Tipo di un messaggio MIDI */
struct midi_msg {

                                      112
CAPITOLO 5. IL MIDI A BASSO LIVELLO                 5.5. ESEMPIO DI PROGRAMMA




            unsigned char   status;
            unsigned char   chn;
            int lundata;
            unsigned char   data[2];
            unsigned char   *psysex;
};
const int MAX_SYSEX_DIM = 128;         /* Max dim. blocco sysex */

/* Variabili globali per read/write msg */
int midifd;
int non_bloccante = 0;


void errore(const char *msgerr)        /* Gestione degli errori */
{
   perror(msgerr);
   exit(-1);
}

int lun_midi_data(const unsigned char status_byte)
{
   switch (status_byte & 0xF0) { /* Ritorna il # data byte/messaggio */
       case MIDI_NOTEOFF:
       case MIDI_NOTEON:
       case MIDI_KEY_PRESSURE:
       case MIDI_CTL_CHANGE:
       case MIDI_PITCH_BEND:
                                 return 2;
       case MIDI_PGM_CHANGE:
       case MIDI_CHN_PRESSURE:
                                 return 1;
       case MIDI_SYSTEM_PREFIX:
                switch (status_byte) {
                    case MIDI_SONG_POSITION:
                                              return 2;
                    case MIDI_QUARTER_FRAME:
                    case MIDI_SONG_SELECT:
                                              return 1;
                    case MIDI_TUNE_REQUEST:
                                              return 0;
                    default:
                }
       default:
                                 return -1;     /* Msg sconosciuto */
   }
}


                                       113
5.5. ESEMPIO DI PROGRAMMA                CAPITOLO 5. IL MIDI A BASSO LIVELLO




void MIDI_open(const char *porta, const int flag)
{                                            /* Apre la porta MIDI */
   if (flag & O_NONBLOCK)
       non_bloccante = 1;
   if ((midifd = open(porta, flag)) == -1)
       errore(porta);
}

int MIDI_readmsg(struct midi_msg *pmsg)      /* Legge un messaggio */
{
   unsigned char midi_byte;
   int nch;
   static int stato_macchina = ST_INIZIO_MSG;
   static struct midi_msg tmpmsg = {MIDI_NOTEOFF, 0 , 0, 0, 0, NULL};
   static int ldata = 0;


#define FINE_MIDI_MSG() { 
            memcpy((void *)pmsg, (void *)&tmpmsg, sizeof(tmpmsg)); 
            stato_macchina = ST_INIZIO_MSG; 
            return RET_MSG_OK; }


   while (1) { /* Cicla fino alla lettura di un messaggio completo */
       if ((nch = read(midifd, &midi_byte, 1)) == -1) {
           if (non_bloccante && (errno == EAGAIN))
               return RET_NO_MSG;
           else return RET_MSG_ERR;
       }
       else if (nch == 0) goto fine_ciclo;            /* Per OSS/Linux */

       if ((midi_byte > 0x7F) && (stato_macchina > ST_INIZIO_MSG)) {
           /* Controlla se e’ un Real Time Message */
           switch (midi_byte) {
               case MIDI_TIMING_CLOCK:
               case MIDI_START:
               case MIDI_CONTINUE:
               case MIDI_STOP:
               case MIDI_ACTIVE_SENSING:
               case MIDI_SYSTEM_RESET:
                        pmsg->status = midi_byte;
                        pmsg->chn = 0;
                        pmsg->lundata = 0;
                        pmsg->data[0] = pmsg->data[1] = 0;
                        pmsg->psysex = NULL;


                                  114
CAPITOLO 5. IL MIDI A BASSO LIVELLO              5.5. ESEMPIO DI PROGRAMMA




                            return RET_MSG_OK;
                 default:
            }
        }

        switch (stato_macchina) {
            case ST_INIZIO_MSG: /* Inizio di un messaggio */
                   stato_macchina = ST_DATA_BYTE;
                   tmpmsg.lundata = 0;
                   free(tmpmsg.psysex);
                   if (midi_byte > 0x7F) { /* Status byte */
                       if ((midi_byte & 0xF0) == MIDI_SYSTEM_PREFIX) {
                           /* System Message */
                           tmpmsg.status = midi_byte;
                           tmpmsg.chn = 0;
                           if (midi_byte == MIDI_SYSTEM_PREFIX) {
                               /* System Exclusive */
                               stato_macchina = ST_SYSEX;
                               tmpmsg.psysex = (unsigned char *)
                                                     malloc(MAX_SYSEX_DIM);
                               if (tmpmsg.psysex == NULL)
                                    return RET_MSG_ERR;
                           }
                       }
                       else { /* Channel message */
                              tmpmsg.status = midi_byte & 0xF0;
                              tmpmsg.chn = midi_byte & 0x0F;
                              ldata = lun_midi_data(midi_byte);
                              if (ldata == 0)
                                   FINE_MIDI_MSG();
                       }
                       break;
                   }
            case ST_DATA_BYTE:    /* Data byte */
                   tmpmsg.data[tmpmsg.lundata++] = midi_byte;
                   if (tmpmsg.lundata == ldata)
                       FINE_MIDI_MSG();
                   break;
            case ST_SYSEX:        /* System Exclusive */
                   if ((tmpmsg.lundata == MAX_SYSEX_DIM) &&
                                      (midi_byte != MIDI_END_SYSEX))
                       return RET_MSG_ERR;
                   *(tmpmsg.psysex + tmpmsg.lundata) = midi_byte;
                   tmpmsg.lundata++;
                   if (midi_byte == MIDI_END_SYSEX)
                       FINE_MIDI_MSG();


                                      115
5.5. ESEMPIO DI PROGRAMMA                 CAPITOLO 5. IL MIDI A BASSO LIVELLO




            default:
        }
        fine_ciclo: /* Serve per accomodare OSS/Linux */
    }
}

int MIDI_writemsg(const struct midi_msg *pmsg) /* Scrive un messaggio */
{
   static int stato_scrittura = 0;
   static int i = 0;
   unsigned char status_byte = pmsg->status | pmsg->chn;


#define SCRIVI_MIDI_BYTE(b) {
              if ((write(midifd, &(b), 1) == -1) && (errno == EAGAIN))
                  return RET_NO_MSG;}


    if (non_bloccante) {
        switch (stato_scrittura) {
            case 0:   /* Status byte */
                      SCRIVI_MIDI_BYTE(status_byte);
                      stato_scrittura++;
            case 1:   /* Sysex o data byte */
                      if (pmsg->status == MIDI_SYSTEM_PREFIX) {
                          /* Scrive la sysex */
                          while (i < pmsg->lundata) {
                               SCRIVI_MIDI_BYTE(*(pmsg->psysex + i));
                               i++;
                          }
                      }
                      else { /* Scrive i data byte */
                             while (i < pmsg->lundata) {
                                  SCRIVI_MIDI_BYTE(pmsg->data[i]);
                                  i++;
                             }
                      }
            default: /* Fine messaggio MIDI */
                      stato_scrittura = i = 0;
                      return RET_MSG_OK;
        }
    }
    else { /* La scrittura e’ bloccante */
           write(midifd, &status_byte, 1);
           if (pmsg->status == MIDI_SYSTEM_PREFIX)
               write(midifd, pmsg->psysex, pmsg->lundata);


                                   116
CAPITOLO 5. IL MIDI A BASSO LIVELLO                  5.5. ESEMPIO DI PROGRAMMA




              else write(midifd, pmsg->data, pmsg->lundata);
              return RET_MSG_OK;
    }
}


int main(int argc, char *argv[])
{
   char PORTA_MIDI[12];
   int seqfd, nporte, porta, n;
   struct midi_msg msg;

        /* Prende la porta MIDI da linea di comando */
        if (argc != 2) {
            printf("Uso: %s <# porta MIDI>n", argv[0]);
            exit(-1);
        }
        porta = atoi(argv[1]);
        if ((seqfd = open("/dev/sequencer", O_WRONLY)) == -1)
            errore("/dev/sequencer");
        ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &nporte);
        close(seqfd);
        if (porta > (nporte-1)) {
            printf("Porta MIDI #%d non valida; # porte installate %d.n",
                   porta, nporte);
            exit(-1);
        }
        sprintf(PORTA_MIDI, "/dev/midi%2.2d", porta);
        /* Apertura porta MIDI - bloccante */
        MIDI_open(PORTA_MIDI, O_RDONLY);
        printf("Dump input porta MIDI #%d...nn", porta);

        while (1) {                            /* Si esce dal ciclo con ^C */
            if (MIDI_readmsg(&msg) == -1) {
                puts(">>> Errore di accesso alla porta MIDI!");
                exit(-1);
            }

            /* Interpreta i messaggi MIDI */
            switch (msg.status) {
                /* Channel Messages */
                case MIDI_NOTEOFF:
                         printf("    Note OFF: chn = %2d, key = %3d, vel = %dn",
                                msg.chn+1, msg.data[0], msg.data[1]);
                         break;
                case MIDI_NOTEON:


                                       117
5.5. ESEMPIO DI PROGRAMMA                  CAPITOLO 5. IL MIDI A BASSO LIVELLO




                      printf("     Note ON: chn = %2d, key = %3d, vel = %dn",
                             msg.chn+1, msg.data[0], msg.data[1]);
                      break;
           case   MIDI_KEY_PRESSURE:
                      printf("Key Pressure: chn = %2d, key = %3d, aft = %dn",
                             msg.chn+1, msg.data[0], msg.data[1]);
                      break;
           case   MIDI_CTL_CHANGE:
                      switch (msg.data[0]) {
                          /* Channel Mode msg */
                          case 0x79:
                                      puts("Reset All Controllers");
                                      break;
                          case 0x7A:
                                      printf("Local Control ");
                                      if (msg.data[1] == 0)
                                          puts("OFF");
                                      else puts("ON");
                                      break;
                          case 0x7B:
                                      puts("All Notes OFF");
                                      break;
                          case 0x7C:
                                      puts("Omni Mode OFF");
                                      break;
                          case 0x7D:
                                      puts("Omni Mode ON");
                                      break;
                          case 0x7E:
                            printf("Mono Mode ON: base chn = %3d, #chn = %dn",
                                   msg.chn+1, msg.data[1]);
                                      break;
                          case 0x7F:
                                      puts("Poly Mode ON");
                                      break;
                          default:    /* Control Change normale */
                    printf("Control Change: chn = %2d, ctl = %3d, aft = %dn",
                           msg.chn+1, msg.data[0], msg.data[1]);
                      }
                      break;
           case   MIDI_PGM_CHANGE:
                      printf("Program Change: chn = %2d, preset = %dn",
                             msg.chn+1, msg.data[0]);
                      break;
           case   MIDI_CHN_PRESSURE:
                      printf("Channel Pressure: chn = %2d, aft = %dn",


                                    118
CAPITOLO 5. IL MIDI A BASSO LIVELLO                 5.5. ESEMPIO DI PROGRAMMA




                               msg.chn+1, msg.data[0]);
                     break;
            case MIDI_PITCH_BEND:
                     printf("Pitch Bend Change: chn = %2d, pos = %dn",
                            msg.chn+1, (msg.data[0]<<7) | msg.data[1]);
                     break;

            /* System Exclusive Message */
            case MIDI_SYSTEM_PREFIX:
                     printf ("Sysex: F0t");
                     for (n = 0; n < msg.lundata; n++)
                          printf("%Xt", *(msg.psysex + n));
                     printf("n");
                     break;

            /* System Common Messages */
            case MIDI_QUARTER_FRAME:
                     printf("MTC Quarter Frame: type = %d, data = %dn",
                            (msg.data[0]>>4) & 0x0F, msg.data[1] & 0x0F);
                     break;
            case MIDI_SONG_POSITION:
                     printf("Song Position: pointer = %dn",
                            (msg.data[0]<<7) | msg.data[1]);
                     break;
            case MIDI_SONG_SELECT:
                     printf("Song Select: #song = %dn", msg.data[0]);
                     break;
            case MIDI_TUNE_REQUEST:
                     puts("Tune Request");
                     break;

            /* System Real Time Messages */
            case MIDI_TIMING_CLOCK:
                     /* Ignorato */
                     break;
            case MIDI_START:
                     puts("<Start>");
                     break;
            case MIDI_CONTINUE:
                     puts("<Continue>");
                     break;
            case MIDI_STOP:
                     puts("<Stop>");
                     break;
            case MIDI_ACTIVE_SENSING:
                     puts("<Active Sensing>");


                                      119
5.5. ESEMPIO DI PROGRAMMA                          CAPITOLO 5. IL MIDI A BASSO LIVELLO




                     break;
            case MIDI_SYSTEM_RESET:
                     puts("<System Reset>");
                     break;

            default: puts(">>> Messaggio non riconosciuto!");
        }
    }

    close(midifd);
    return 0;
}

    Il programma dumpmidi richiede come argomento da linea di comando il nu-
mero della porta MIDI di cui effettuare il dump; se non lo si fornisce ` segnalato
                                                                      e
l’errore:

~>dumpmidi
Uso: dumpmidi <# porta MIDI>
    `
    E segnalato errore anche se non ` fornito il numero di una porta MIDI esistente
                                    e
nel sistema (tale numero segue la convenzione dei “Midi devices:” ottenuti con
cat /dev/sndstat):

~>dumpmidi 1
Porta MIDI #1 non valida; # porte installate 1.

   Se si fornisce il giusto numero di porta MIDI, il programma comincia ad
effettuare il dump dei messaggi in input; per interrompere il ciclo ` necessario
                                                                    e
premere contemporaneamente i tasti Ctrl e C; di seguito ` riportato un esempio:
                                                        e

~>dumpmidi 0
Dump input porta MIDI #0...

     Note ON:     chn   =   1,   key   =   60,   vel   =   96
Key Pressure:     chn   =   1,   key   =   60,   aft   =   98
Key Pressure:     chn   =   1,   key   =   60,   aft   =   66
     Note ON:     chn   =   1,   key   =   60,   vel   =   0
   .
   .
   .
   Si osservi come il numero del canale segua la convenzione MIDI, cio` msg.chn+1,
                                                                      e
per riportarsi nell’intervallo 1÷16.




                                           120
Appendice A

GENERAL MIDI

A.1       Timbri strumentali
In questa Sezione sono elencati i numeri di preset (scalati di uno, affinch´ li si pos-
                                                                         e
sa utilizzare direttamente con SEQ_SET_PATCH()), le famiglie di timbri e i nomi
degli strumenti dello standard General MIDI level 1, il tutto in correlazione con
i nomi delle patch wavetable della Gravis UltraSound (file GF1 con estensione
.pat). Di questi ultimi esiste una versione omonima e public domain chiamata
MIDIA [8], in quanto i primi sono copyright della Voice Crystal e non liber-
amente utilizzabili con OSS (per i timbri MIDIA originali non giudicati buoni
sono elencati dei sostituti fra parentesi, con l’estensione dei primi rinominata a
.pat.sav).


  Preset   Famiglia            Nome degli strumenti         Nome patch (.pat)
    0                          Acoustic Grand Piano         acpiano
    1                          Bright Acoustic Piano        britepno
    2                          Electric Grand Piano         synpiano (acpiano)
    3       PIANO              Honky-Tonk Piano             honky
    4                          Electric Piano 1             epiano1
    5                          Electric Piano 2             epiano2
    6                          Harpsichord                  hrpschrd (acpiano)
    7                          Clavinet                     clavinet (acpiano)
    8                          Celesta                      celeste
    9                          Glockenspiel                 glocken
    10                         Music Box                    musicbox
    11   CHROMATIC             Vibraphone                   vibes
    12   PERCUSSION            Marimba                      marimba
    13                         Xylophone                    xylophon
    14                         Tubular Bells                tubebell
    15                         Dulcimer                     santur

                                        121
A.1. TIMBRI STRUMENTALI                            APPENDICE A. GENERAL MIDI



   Preset  Famiglia       Nome degli strumenti      Nome patch (.pat)
     16                   Drawbar Organ             homeorg (rockorg)
     17                   Percussive Organ          percorg
     18                   Rock Organ                rockorg
     19     ORGAN         Church Organ              church
     20                   Reed Organ                reedorg (rockorg)
     21                   Accordion                 accordn
     22                   Harmonica                 harmonca
     23                   Tango Accordion           concrtna
     24                   Acoustic Nylon Guitar     nyguitar
     25                   Acoustic Steel Guitar     acguitar
     26                   Electric Jazz Guitar      jazzgtr
     27    GUITAR         Electric Clean Guitar     cleangtr
     28                   Electric Muted Guitar     mutegtr
     29                   Overdriven Guitar         odguitar (rockgtr)
     30                   Distortion Guitar         distgtr
     31                   Guitar Harmonics          gtrharm
     32                   Acoustic Bass             acbass
     33                   Electric Bass Fingered    fngrbass
     34                   Electric Bass Picked      pickbass
     35      BASS         Fretless Bass             fretless
     36                   Slap Bass 1               slapbas1
     37                   Slap Bass 2               slapbas2
     38                   Synth Bass 1              synbass1
     39                   Synth Bass 2              synbass2
     40                   Violin                    violin
     41                   Viola                     viola
     42                   Cello                     cello
     43    STRINGS        Contrabass                contraba
     44                   Tremolo Strings           marcato
     45                   Pizzicato Strings         pizzcato
     46                   Orchestral Harp           harp
     47                   Timpani                   timpani
     48                   String Ensemble 1         marcato (slowstr)
     49                   String Ensemble 2         slowstr
     50                   Synth Strings 1           synstr1
     51   ENSEMBLE        Synth Strings 2           synstr2
     52                   Choir Aahs                choir
     53                   Voice Oohs                doo
     54                   Synth Voice               voices
     55                   Orchestra Hit             orchhit




                                  122
APPENDICE A. GENERAL MIDI                            A.1. TIMBRI STRUMENTALI



 Preset Famiglia      Nome degli strumenti           Nome patch (.pat)
   56                 Trumpet                        trumpet
   57                 Trombone                       trombone
   58                 Tuba                           tuba
   59    BRASS        Muted Trumpet                  mutetrum
   60                 French Horn                    frenchrn
   61                 Brass Section                  hitbrass
   62                 Synth Brass 1                  synbras1
   63                 Synth Brass 2                  synbras2
   64                 Soprano Sax                    sprnosax
   65                 Alto Sax                       altosax
   66                 Tenor Sax                      tenorsax
   67    REEDS        Baritone Sax                   barisax
   68                 Oboe                           oboe
   69                 English Horn                   englhorn
   70                 Bassoon                        bassoon
   71                 Clarinet                       clarinet
   72                 Piccolo                        piccolo
   73                 Flute                          flute
   74                 Recorder                       recorder
   75    PIPES        Pan Flute                      woodflut
   76                 Bottle Blow                    bottle
   77                 Shakuhashi                     shakazul
   78                 Whistle                        whistle
   79                 Ocarina                        ocarina
   80                 Synth lead 1—Square wave       sqrwave
   81                 Synth lead 2—Sawtooth Wave     sawwave
   82                 Synth lead 3—Calliope          calliope
   83   SYNTH         Synth lead 4—Chiff              chiflead
   84    LEAD         Synth lead 5—Charang           voxlead
   85                 Synth lead 6—Solo Voice        voxlead
   86                 Synth lead 7—Bright Saw Wave   lead5th
   87                 Synth lead 8—Brass and Lead    basslead
   88                 Synth pad 1—Fantasia           fantasia
   89                 Synth pad 2—Warm               warmpad
   90                 Synth pad 3—Poly Synth         polysyn
   91   SYNTH         Synth pad 4—Space Voice        ghostie
   92     PAD         Synth pad 5—Bowed Glass        bowglass
   93                 Synth pad 6—Metal              metalpad
   94                 Synth pad 7—Halo               halopad
   95                 Synth pad 8—Sweep              sweeper




                                   123
A.1. TIMBRI STRUMENTALI                       APPENDICE A. GENERAL MIDI



 Preset   Famiglia        Nome degli strumenti     Nome patch (.pat)
   96                     Synth SFX 1—Ice Rain     aurora
   97                     Synth SFX 2—Soundtrack   soundtrk
   98                     Synth SFX 3—Crystal      crystal
   99     SYNTH           Synth SFX 4—Atmosphere   atmosphr
  100    EFFECTS          Synth SFX 5—Brightness   freshair
  101                     Synth SFX 6—Goblin       unicorn
  102                     Synth SFX 7—Echo drops   sweeper
  103                     Synth SFX 8—Star Theme   startrak
  104                     Sitar                    sitar
  105                     Banjo                    banjo
  106                     Shamisen                 shamisen
  107     ETHNIC          Koto                     koto
  108                     Kalimba                  kalimba
  109                     Bagpipe                  bagpipes
  110                     Fiddle                   fiddle
  111                     Shanai                   shannai
  112                     Tinkle Bell              carillon
  113                     Agogo                    agogo
  114                     Steel Drums              steeldrm
  115   PERCUSSIVE        Woodblock                woodblk
  116                     Taiko Drum               taiko
  117                     Melodic Tom              toms
  118                     Synth Drum               syntom
  119                     Reverse Cymbal           revcym
  120                     Guitar Fret Noise        fx-fret
  121                     Breath Noise             fx-blow
  122                     Seashore                 seashore
  123     SOUND           Bird Tweet               jungle
  124    EFFECTS          Telephone Ring           telephon
  125                     Helicopter               helicptr
  126                     Applause                 applause
  127                     Gunshot                  pistol




                                  124
APPENDICE A. GENERAL MIDI                          A.2. TIMBRI DELLE PERCUSSIONI




A.2      Timbri delle percussioni

Di seguito ` riportata una codifica standard dei timbri percussivi, ricavata dalle
            e
batterie elettroniche di Roland e Sequential; il canale MIDI numero 10 (<voce>==9)
` per default assegnato alla sezione ritmica, con ogni patch che ` identificata da
e                                                                 e
un numero di nota. Prima del nome della patch ` riportato anche il suo numero
                                                    e
d’ordine nella tabella interna del driver.




        Nota    Timbro o Effetto No patch           Nome patch (.pat)
         28     Slap               156             slap
         29     Mute Scratch       157             scratch1
         30     Open Scratch       158             scratch2
         31     Sticks             159             sticks
         32     Square Click       160             sqrclick
         33     Metal Click        161             metclick
         34     Metal Bell         162             metbell
         35     Kick               163             kick1
         36     Sude Kick          164             kick2
         37     Stick Rim          165             stickrim
         38     Acoustic Snare     166             snare1
         39     Hand Clap          167             claps
         40     Electric Snare     168             snare2
         41     Low–floor Tom       169             tomlo2
         42     Clodes Hi–Hat      170             hihatcl
         43     High–floor Tom      171             tomlo1
         44     Pedal Hi–hat       172             hihatpd
         45     Low Tom            173             tommid2
         46     Open Hi–hat        174             hihatop
         47     Low–middle Tom     175             tommid1
         48     Hi–middle Tom      176             tomhi2
         49     Crash cymbal 1     177             cymcrsh1
         50     High Tom           178             tomhi1
         51     Ride Cymbal 1      179             cymride1
         52     Chinese Cymbal     180             cymchina
         53     Ride Bell          181             cymbell
         54     Tambourine         182             tamborin
         55     Splash Cymbal      183             cymsplsh
         56     Cowbell            184             cowbell
         57     Crash Cymbal 2     185             cymcrsh2
         58     Vibraslap          186             vibslap
         59     Ride Cymbal 2      187             cymride2

                                      125
A.2. TIMBRI DELLE PERCUSSIONI                APPENDICE A. GENERAL MIDI



        Nota    Timbro o Effetto No patch   Nome patch (.pat)
         60     High Bongo         188     bongohi
         61     Low Bongo          189     bongolo
         62     Mute High Conga    190     congahi1
         63     Open High Conga    191     congahi2
         64     Low Conga          192     congalo
         65     High Timbale       193     timbaleh
         66     Low Timbale        194     timbalel
         67     High Agogo         195     agogohi
         68     Low Agogo          196     agogolo
         69     Cabasa             197     cabasa
         70     Maracas            198     maracas
         71     Short Whistle      199     whistle1
         72     Long Whistle       200     whistle2
         73     Short Guiro        201     guiro1
         74     Long Guiro         202     guiro2
         75     Claves             203     clave
         76     High Woodblock     204     woodblk1
         77     Long Woodblock     205     woodblk2
         78     Mute Cuica         206     cuica1
         79     Open Cuica         207     cuica2
         80     Mute Triangle      208     triangl1
         81     Open Triangle      209     triangl2
         82     Shaker             210     shaker
         83     Jingles            211     jingles
         84     Belltree           212     belltree
         85     Castinet           213     castinet
         86     Mute Surdo         214     surdo1
         87     Open Surdo         215     surdo2




                                126
Elenco delle figure

 1.1   Modello come mixer della scheda audio in riproduzione . . . . . .          6
 1.2   Modello come mixer della scheda audio in campionamento . . . .             6
 1.3   Visione della scheda audio in riproduzione che OSS d` al program-
                                                               a
       matore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   7
 1.4   Visione della scheda audio in campionamento che OSS d` al pro-
                                                                    a
       grammatore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .      7
 1.5   Schema di utilizzo del multi–buffering . . . . . . . . . . . . . . . .      17
 1.6   Codifica del minor number di un device file di OSS . . . . . . . .           20

 2.1   Rappresentazione del volume di un canale stereo . . . . . . . . . .        28

 4.1   Schema di funzionamento del sequencer . . . . . . . . . . . . . . .        67
 4.2   Organizzazione degli eventi come i messaggi standard MIDI . . .            86




                                       127
Bibliografia

[1] 4Front Technologies,   OSS Programmer’s           Guide,      USA,   1997,
    http://www.4front-tech.com/pguide/

[2] H. Savolainen, Hacker’s Guide to VoxWare 2.4, second draft, Helsinki, 1994

[3] J. Tranter, LINUX MULTIMEDIA GUIDE, O’Reilly & Associates, Inc., 1996,
    Cap. 14

[4] J. Tranter, The Linux Sound HOWTO, Linux Documentation Project,
    http://ildp.psy.unipd.it/LDP/HOWTO/Sound-HOWTO.html

[5] Hannu Savolainen, Alan Cox et al., Sorgenti di OSS/Free, Helsinki, 1993–97,
    /usr/src/linux/drivers/sound/

[6] Hannu Savolainen, Collezione di esempi di programmazione per OSS, Helsinki,
    1993–97, http://www.tux.org/pub/devel/ossfree/snd-util-3.8.tar.gz

[7] Hannu Savolainen, Esempi riguardanti mmap() e /dev/music, Helsinki, 1997,
    http://www.tux.org/pub/devel/ossfree/samples.tar.gz

[8] MIDIA: collezione public domain di patch wavetable GUS compatibili,
    http://www.4front-tech.com/softoss.html

[9] Brandon S. Higa, The Semi Official Turtle               Beach   Maui   Page,
    http://www.lava.net/~bhiga/Maui/info.html

[10] Steve D Pate, UNIX Internals – A Practical Approach, Addison–Wesley,
    USA, 1996, pp. 19–21, pp. 43-46, pp. 51–63

[11] Giovanni Perotti, MIDI Computer Immagine e Suono, Jackson Libri, Italia,
    1998




                                     128

Il Linux OpenSound System

  • 1.
    IL LINUX OPENSOUND SYSTEM ANTONIO TRINGALI maggio 1999
  • 2.
  • 3.
    Indice 1 LA SCHEDA AUDIO E OSS 4 1.1 Modello della scheda audio . . . . . . . . . . . . . . . . . . . . . . 4 1.2 L’interfaccia di programmazione di OSS . . . . . . . . . . . . . . 6 1.3 Inizializzazione di OSS . . . . . . . . . . . . . . . . . . . . . . . . 9 1.3.1 Gestione degli errori . . . . . . . . . . . . . . . . . . . . . 10 1.3.2 L’exit handler . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.3.3 Il signal handler . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4 Scrittura di codice portabile . . . . . . . . . . . . . . . . . . . . . 14 1.5 Anatomia del driver . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5.1 Scrittura sul buffer DMA . . . . . . . . . . . . . . . . . . . 17 1.5.2 Lettura dal buffer DMA . . . . . . . . . . . . . . . . . . . 18 1.6 Schede audio ISA e PCI . . . . . . . . . . . . . . . . . . . . . . . 19 1.7 File system e device file . . . . . . . . . . . . . . . . . . . . . . . . 20 1.8 Le versioni di OSS . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2 IL MIXER E LA GESTIONE DEI CANALI 23 2.1 Descrizione di /dev/mixer . . . . . . . . . . . . . . . . . . . . . . 23 2.2 I canali del mixer . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.2.1 Lettura della configurazione . . . . . . . . . . . . . . . . . 26 2.2.2 Selezione del canale da campionare . . . . . . . . . . . . . 27 2.3 Livelli di volume dei canali . . . . . . . . . . . . . . . . . . . . . . 28 2.4 Dipendenza dall’hardware . . . . . . . . . . . . . . . . . . . . . . 29 2.5 Nuove caratteristiche . . . . . . . . . . . . . . . . . . . . . . . . . 30 2.6 Esempio di programma . . . . . . . . . . . . . . . . . . . . . . . . 31 3 CAMPIONAMENTO E RIPRODUZIONE 36 3.1 I device file audio . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.2 Il buffer audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.3 Parametri di campionamento . . . . . . . . . . . . . . . . . . . . . 37 3.3.1 Reset dei parametri . . . . . . . . . . . . . . . . . . . . . . 38 3.3.2 Pause nelle operazioni . . . . . . . . . . . . . . . . . . . . 39 3.4 Il campionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.5 La riproduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.6 Il formato audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 iii
  • 4.
    3.6.1 Little ebig endian . . . . . . . . . . . . . . . . . . . . . . 42 3.6.2 La codifica lineare . . . . . . . . . . . . . . . . . . . . . . 43 3.7 Il tempo reale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.7.1 Le capacit` del driver . . . . . a . . . . . . . . . . . . . . . . 45 3.7.2 Gestione del buffer DMA . . . . . . . . . . . . . . . . . . . 46 3.7.3 I/O non bloccante . . . . . . . . . . . . . . . . . . . . . . 48 3.7.4 La sincronizzazione . . . . . . . . . . . . . . . . . . . . . . 52 3.7.5 Accesso diretto al buffer DMA . . . . . . . . . . . . . . . . 53 3.8 Il full duplex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.9 Uso di coprocessori . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.10 Nuove caratteristiche . . . . . . . . . . . . . . . . . . . . . . . . . 58 3.11 Esempio di programma . . . . . . . . . . . . . . . . . . . . . . . . 59 4 SINTETIZZATORI E MIDI 64 4.1 I device file del sequencer . . . . . . . . . . . . . . . . . . . . . . . 64 4.1.1 I chip sintetizzatori . . . . . . . . . . . . . . . . . . . . . . 65 4.1.2 I sintetizzatori MIDI . . . . . . . . . . . . . . . . . . . . . 66 4.2 Il buffer degli eventi . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.3 Lettura della configurazione . . . . . . . . . . . . . . . . . . . . . 69 4.3.1 Parametri generali . . . . . . . . . . . . . . . . . . . . . . 70 4.3.2 Le porte MIDI . . . . . . . . . . . . . . . . . . . . . . . . 71 4.4 Predisposizione dei chip sintetizzatori . . . . . . . . . . . . . . . . 72 4.4.1 Caricamento degli algoritmi FM . . . . . . . . . . . . . . . 73 4.4.2 Caricamento delle patch wavetable . . . . . . . . . . . . . 76 4.4.3 Caricamento delle sysex patch . . . . . . . . . . . . . . . . 81 4.5 La temporizzazione . . . . . . . . . . . . . . . . . . . . . . . . . . 81 4.5.1 La sincronizzazione per /dev/music . . . . . . . . . . . . . 83 4.5.2 La sincronizzazione per /dev/sequencer . . . . . . . . . . . 85 4.6 Output degli eventi . . . . . . . . . . . . . . . . . . . . . . . . . . 86 4.6.1 Messaggi di canale . . . . . . . . . . . . . . . . . . . . . . 86 4.6.2 Messaggi di sistema . . . . . . . . . . . . . . . . . . . . . . 89 4.6.3 Controllo della coda degli eventi . . . . . . . . . . . . . . . 91 4.7 Formato degli eventi . . . . . . . . . . . . . . . . . . . . . . . . . 92 4.8 Input degli eventi . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 4.8.1 Eventi per /dev/music . . . . . . . . . . . . . . . . . . . . 95 4.8.2 Eventi per /dev/sequencer . . . . . . . . . . . . . . . . . . 97 4.9 Nuove caratteristiche . . . . . . . . . . . . . . . . . . . . . . . . . 97 4.9.1 SoftOSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 4.9.2 OSSlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 4.10 Esempio di programma . . . . . . . . . . . . . . . . . . . . . . . . 102 iv
  • 5.
    5 IL MIDIA BASSO LIVELLO 106 5.1 Descrizione dei device file MIDI . . . . . . . . . . . . . . . . . . . 106 5.2 Lettura dalla porta MIDI . . . . . . . . . . . . . . . . . . . . . . . 107 5.3 Scrittura sulla porta MIDI . . . . . . . . . . . . . . . . . . . . . . 109 5.4 Programmazione della MPU–401 . . . . . . . . . . . . . . . . . . 109 5.5 Esempio di programma . . . . . . . . . . . . . . . . . . . . . . . . 110 A GENERAL MIDI 121 A.1 Timbri strumentali . . . . . . . . . . . . . . . . . . . . . . . . . . 121 A.2 Timbri delle percussioni . . . . . . . . . . . . . . . . . . . . . . . 125 1
  • 6.
    Sommario Questo lavoro sipropone come tutorial per la programmazione della scheda audio tramite l’Open Sound System (OSS) per il sistema operativo Linux, ponendo particolare enfasi sulle strategie di programmazione pi` opportune per la sintesi u audio in tempo reale. Capitolo 1 Si introduce un modello “medio” delle varie schede audio presenti sul mercato; dopo sono descritte la visione orientata a UNIX che ne ha OSS (in particolare alla fine descrivendo il funzionamento interno del driver), la sua inizializzazione e la gestione delle uscite da programma e dei segnali in un ambiente multiutente e multiprogrammato, con qualche riferimento alla scrittura di programmi portabili Capitolo 2 Il mixer gestisce i canali di ingresso/uscita facenti capo alla scheda audio; ` spiegato come scoprire le capacit` di quest’ultima, selezionare i e a canali e regolare i livelli di volume per campionamento e riproduzione ` Capitolo 3 E spiegato come campionare da un qualsiasi canale del mixer e ripro- durre i campioni sintetizzati o immagazzinati su un file, eventualmente in maniera non bloccante Capitolo 4 Ci si occupa della programmazione ad alto livello, cio` in maniera e indipendente dai dispositivi, del chip sintetizzatore interno alla scheda audio e delle porte MIDI Capitolo 5 Si tratta della programmazione MIDI a basso livello, cio` a livello e di I/O di byte dei messaggi ` Appendice A E riportata una tabella di corrispondenza fra i preset timbrici definiti nello standard General MIDI level 1 e i nomi delle patch wavetable per la Gravis UltraSound
  • 7.
    INTRODUZIONE ALL’OPEN SOUNDSYSTEM All’inizio degli anni ’90 Hannu Savolainen scrisse la prima versione di un driver per la scheda audio Sound Blaster 1.5 sotto Minix–386; nel 1992 ne fece il porting sotto Linux: era nato il Linux Sound Driver (LSD). Al crescere del numero di versione e delle funzionalit` implementate, nonch´ a e all’aumentare del numero di porting ad altre variet` di UNIX, il driver cambi` a o il nome in VoxWare; sfortunatamente era omonimo della VoxWare Incorporat- ed, quindi per problemi di copyright per un certo periodo rest` il Temporarily o Anonymous Sound Driver (TASD). Poco dopo la 4Front Technologies, con la collaborazione dello stesso Hannu Savolainen, svilupp` l’Unix Sound System (USS), avente qualche caratteristica in o pi` rispetto al driver freeware e un maggior numero di schede audio supportate; u lo stesso Savolainen continuava indipendentemente a sviluppare il driver freeware (il cui codice sorgente era differente da quello di USS), noto ora come USS/Lite. In ossequio a POSIX e alla gran variet` di sistemi di tipo UNIX al quale ` a e stato portato, il nome ` cambiato nuovamente e il driver ` commercializzato dalla e e 4Front Technologies come Open Sound System, mentre nella versione freeware ` noto come OSS/Free. Di questo Savolainen non mantiene pi` il codice: la e u responsabilit` ` passata a Alan Cox dagli inizi del 1998. ae D’ora in poi ci si riferir` a questi driver globalmente come OSS, sottinten- a dendo che ci` che sar` detto vale per entrambi (evidenziando, ove necessario, o a le differenze); la versione assunta come “riferimento” ` la 3.5.4. A seguire dal e Capitolo 2, la penultima Sezione di ogni Capitolo elenca le differenze di program- mazione introdotte dalla versione 3.6 in poi e l’ultima Sezione ospita un breve programma di esempio in C che illustra le caratteristiche di OSS introdotte nel Capitolo stesso. Delle chiamate alla libreria di OSS elencate in soundcard.h, si ` scelto di riportare solo quelle pienamente supportate. e OSS ` in pratica il primo tentativo di unificare l’architettura di audio digitale e per UNIX: alle capacit` di campionamento e riproduzione (piuttosto articolate a rispetto a quanto prima disponibile per questo ambiente) sono affiancate le possi- bilit` del MIDI, con il supporto dell’audio sincronizzato alla riproduzione video. a ` E allo stato attuale un insieme modulare di device driver che garantiscono un’in- terfaccia di programmazione uniforme e compatibile a livello di codice sorgente per tutte le piattaforme su cui ` stato portato, tanto che per queste ` valido la e e quasi totalit` di ci` che ` scritto nel seguito di questo lavoro. a o e Una panoramica generica delle caratteristiche supportate sia dall’OSS com- merciale (OSS/Linux) che da OSS/Free ` la seguente: e • supporto di vari formati audio per i campioni (8 e 16 bit signed e unsigned, 8 bit µ–Law e A-Law, IMA–ADPCM) • supporto di canali stereo e mono • frequenze di campionamento comprese tra 4kHz e 48kHz 2
  • 8.
    • supporto halfe full duplex (con apposite schede audio) • possibilit` di accesso diretto al buffer audio DMA (per applicazioni con a richieste temporali pi` stringenti, come i giochi) u • possibilit` di accesso indipendente dall’hardware a MIDI e a sintetizzatori a FM o wavetable sulla scheda audio • caricamento delle patch in maniera indipendente dall’hardware • supporto per SMPTE/MTC • supporto per UART di tipo MP–401, Sound Blaster MIDI e XG MIDI • supporto di IBM/Motorola PowerPC (bus ISA e PCI), 386/486/Pentium (bus ISA, EISA e PCI), Sun SPARC e DEC Alpha/AXP (bus ISA e PCI) Rispetto a OSS/Free, OSS/Linux ha in pi` le seguenti caratteristiche: u • supporto diretto dello standard PnP (senza dover inizializzare precedente- mente le schede con il comando isapnp) • supporto di un maggior numero di schede audio • non si deve ricompilare il kernel ad ogni cambio di configurazione o driver (questo ` un modulo separato) e • supporto delle schede Sound Blaster 16/32/64/AWE in full duplex • supporto delle patch wavetable E–mu SoundFont 2.0 • librerie DirectAudio per l’accesso diretto ai chip sintetizzatori FM e alle porte MIDI si ` scelto di non trattare gli ultimi due punti, in quanto di interesse marginale e per la sintesi in tempo reale. CONVENZIONI TIPOGRAFICHE Per tutto il testo sono seguite per le parole le seguenti convenzioni: corsivo indica una sigla o un termine tecnico rilevante introdotto per la prima volta neretto indica un identificatore o una variabile (con specificato il tipo) per l’interfaccia di programmazione di OSS o di Linux spaziatura fissa indica una parola chiave appartenente alla libreria di OSS o codice sorgente di esempio; se le parole sono contenute tra parentesi an- golate, come ad esempio per <valore>, si intende che il programmatore debba sostituirvi un adeguato valore numerico 3
  • 9.
    Capitolo 1 LA SCHEDAAUDIO E OSS 1.1 Modello della scheda audio Una scheda audio ha diverse funzioni: converte i suoni immagazzinati nel sistema (ad esempio, su file) dalla forma digitale all’analogica affinch´ li si possa udire e o registrare, converte da opportuni canali di ingresso (Line–In1 , microfono, CD audio) i segnali da analogico a digitale affinch´ possano essere immagazzinati o e manipolati dal computer, pu` consentire essa stessa di creare nuovi suoni tramite o eventuali sintetizzatori interni. Secondo le specifiche MPC2 deve anche essere dotata di una porta MIDI perch´ e il computer possa controllare strumenti musicali o sintetizzatori esterni con tali capacit`, invece di generare da s´ i suoni. Il PC diventa in pratica un sequencer, a e cio` un direttore d’orchestra allo stato solido; virtualmente non ` pi` di una e e u memoria e un sistema di messaggi dalla e alla strumentistica, con capacit` di a editing. L’OSS cerca di offrire una visione idealizzata della scheda audio al program- matore, nascondendo le differenze tecniche fra le varie schede presenti sul mercato; essa pu` essere vista come un mixer, di cui il programmatore (il Disc Jockey) ha o possibilit` di controllare ogni canale3 . a Per la riproduzione (conversione D/A) la sorgente ` da file (brano digitaliz- e zato o MIDI) o i campioni sono creati con un opportuno algoritmo di sintesi, da CD audio4 (il segnale ` semplicemente spedito alla sezione analogica della scheda e 1` E un ingresso con livelli simili a quelli d’ingresso per un amplificatore HI–FI, utile per campionare il segnale proveniente da un registratore analogico o da un CD player esterni 2 Specifiche rilasciate da Microsoft e Intel nel 1991 per definire una configurazione hardware minima affinch´ un computer potesse essere definito “multimediale”: si doveva cio` disporre e e almeno di un PC 386SX/16MHz con 4MB di RAM e 40MB di hard disk, una VGA a colori, un mouse, una scheda audio e un CD–ROM player 3 Indipendentemente o meno dagli altri; relativamente alle capacit` della scheda audio stessa, a con il programmatore che pu` interrogare OSS sulle capacit` di quest’ultima o a 4 Non ` presa in considerazione la programmazione del drive CD in questo tutorial, solo come e si fa a campionare o riprodurre il segnale che ` presente sull’eventuale canale CD Audio In della e scheda audio 4
  • 10.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.1. MODELLO DELLA SCHEDA AUDIO audio senza che sia stato campionato prima), da Line–In e da microfono. In cam- pionamento (conversione A/D) si pu` acquisire dagli ingressi CD Audio, Line–In o e microfono o si possono acquisire eventi MIDI. La coppia di convertitori A/D e D/A viene spesso chiamata PCM, DSP, CODEC o ADC/DAC ; nella Tabella seguente sono elencate le massime frequenze di campionamento5 per le varie generazioni di schede audio che OSS supporta (la minima ` sempre intorno ai 5 kHz); la fc ` generata dividendo l’alta frequenza di e e un oscillatore di riferimento, per cui non ` possibile ottenere tutte le frequenze e dell’intervallo: le differenze di qualche percento dovrebbero essere ignorate perch´ e solitamente non avvertibili (OSS cerca di ottenere dalla scheda audio la frequenza pi` vicina a quella richiesta). La risoluzione di campionamento pu` essere di 8 o u o 16 bit, in mono o stereo. Generazione Max fc Note 1a 22.05 kHz in riproduzione 11.025 kHz in campionamento 2a 44.1 kHz mono 22.05 kHz stereo 3a 44.1 kHz qualit` audio CD a 48 kHz qualit` DAT a 4a 96 kHz Ultra Hi–Fi Oltre alle succitate possibilit` di sintesi FM6 e di gestione dei dispositivi MI- a DI, alcune schede consentono la wavetable synthesis e la possibilit` di caricare a campioni di uno strumento (patch) in una speciale memoria della scheda stessa. In figura 1.1 ` evidenziata la visione che possiamo dare della nostra scheda e audio idealizzata come output mixer per quanto riguarda la riproduzione, che ben si conf` alle caratteristiche pi` comuni riscontrabili nelle schede audio attualmente a u presenti sul mercato (1999). In figura 1.2 ` invece schematizzato l’input mixer a e cui fare riferimento per quanto riguarda il campionamento. Infatti dentro una scheda audio possono esserci realmente due mixer che gestis- cono separatamente i casi di campionamento e riproduzione, ma OSS gestisce automaticamente il passaggio dall’uno all’altro in base ai comandi del program- matore di lettura o scrittura dalla/alla scheda audio. In particolare, se tali attivit` a sono implementate dalla scheda audio come mutuamente esclusive ` definita half e duplex, mentre ` definita full duplex se possono aver luogo contemporaneamente. e 5 Alcune vecchie schede permettono di scegliere solo fra frequenze di campionamento fisse: 11025 Hz, 22050 Hz, 32000 Hz e 44100 Hz 6` E sfruttata la tecnica degli operatori per produrre pi` voci: i chip “classici” sono lo Yamaha u OPL–2 (a due operatori, nove voci con timbri non realistici) e OPL–3 (a quattro operatori, diciotto voci): aumentando il numero di operatori per voce migliora la qualit` della sintesi a 5
  • 11.
    1.2. L’INTERFACCIA DIPROGRAMMAZIONE DI OSS CAPITOLO 1. LA SCHEDA AUDIO E OSS Memoria del computer o Hard-Disk LINE-OUT ALTOPARLANTI L R L R L R L R L R L R L R STEREO MONO STEREO MONO TREBLE BASS RECORD CD Audio LINE-IN MIC MASTER DAT CD Audio LINE-IN MIC MIDI ON/OFF VOLUME ON/OFF ON/OFF ON/OFF MIDI ON/OFF VOLUME ON/OFF ON/OFF ON/OFF ON/OFF Figura 1.1: Modello come mixer della Figura 1.2: Modello come mixer scheda audio in riproduzione della scheda audio in campiona- mento 1.2 L’interfaccia di programmazione di OSS Per gestire il “mixer virtuale” delle figure 1.1 e 1.2 OSS sfrutta la visione orientata al file–system che Linux ha di ogni device 7 . I canali del mixer, campionamento e riproduzione possono quindi essere gestiti manipolando degli speciali file che si trovano nella directory /dev tramite le primitive di sistema open(), ioctl(), read(), write() e close(). In figura 1.3 sono evidenziati i device file relativi alla manipolazione dei vari canali per la riproduzione, mentre in figura 1.4 c’` lo schema equivalente per il e campionamento. Quest’organizzazione ` conveniente, poich´ in tal modo sono schermate sia le e e complessit` dell’hardware e del software sottostante (la scheda audio, ma anche a la gestione del DMA, della memoria virtuale e del multitasking), sia le diversit` a fra le varie schede audio in commercio. Si pu` ora procedere alla descrizione di ogni device file: o /dev/mixer Questa ` l’interfaccia di accesso alle funzioni del mixer e pu` e o essere un link simbolico a /dev/mixer0; se ` presente un secondo mixer, ci e si riferisce ad esso come /dev/mixer1 /dev/dsp Identifica il DSP della scheda (per default con codifica lineare 8 bit unsigned) e pu` essere un link simbolico a /dev/dsp0; in genere ` presente o e 7` E un’interfaccia a un dispositivo hardware (dischi, linee seriali, etc.) o a “entit`” a cui a non corrisponde un vero e proprio dispositivo hardware (memoria di sistema, kernel, etc.); a un device fisico — come la scheda audio — corrisponde un device driver — come OSS — che ha il compito di pilotarlo 6
  • 12.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.2. L’INTERFACCIA DI PROGRAMMAZIONE DI OSS = Link simbolico OSS driver OSS driver Memoria del computer o Hard-Disk /dev/dsp /dev/dsp0 /dev/dsp1 MIDI in /dev/dsp /dev/dspW /dev/dspW0 /dev/dspW MIDI file (out) /dev/dspW1 /dev/audio /dev/audio /dev/audio0 CD Audio /dev/music /dev/audio1 /dev/sequencer CD Audio /dev/midi00 Memoria del computer /dev/midi01 o Hard-Disk /dev/sndstat /dev/sndstat /dev/midi02 /dev/sndproc Line-In /dev/music /dev/sndproc /dev/midi03 Line-In /dev/sequencer /dev/midi00 /dev/midi01 MIXER MIXER /dev/midi02 /dev/midi03 Mic Mic /dev/mixer /dev/mixer0 /dev/mixer /dev/mixer0 /dev/mixer1 /dev/mixer1 Figura 1.3: Visione della scheda au- Figura 1.4: Visione della scheda dio in riproduzione che OSS d` al audio in campionamento che OSS a programmatore d` al programmatore a anche /dev/dsp1, che pu` identificare un secondo DSP o lo stesso con una o diversa funzione /dev/dspW Se presente (dipende dalla versione di OSS), si riferisce al DSP con codifica lineare 16 bit signed e little endian8 ; pu` essere un link simbolico o a /dev/dspW0 ed eventualmente ` presente anche /dev/dspW1 e ` /dev/audio E presente per limitata compatibilit` con le workstation Sun (non a sono supportati cambiamenti da 8 kHz/mono/8 bit) e utilizza la codifica µ–Law9 ; ` disponibile in mutua esclusione con /dev/dsp e generalmente ` e e un link a /dev/audio0, potendo essere presente anche /dev/audio1 /dev/music Permette di accedere al sintetizzatore interno alla scheda audio e alle porte MIDI in maniera indipendente dal dispositivo (sono trattati allo stesso modo dal punto di vista dell’interfaccia di programmazione), con modalit` di temporizzazione piuttosto articolate; pu` essere presente il link a o simbolico ad esso /dev/sequencer2 (obsoleto) ` /dev/sequencer E un device file a pi` basso livello di /dev/music, rispetto al u quale ha capacit` limitate di temporizzazione, sincronizzazione e gestione a automatica del chip sintetizzatore interno alla scheda audio 8 Little endian e big endian si riferiscono a come sono conservati i campioni in memoria dalla CPU: per il primo l’indirizzo del dato corrisponde all’indirizzo del byte meno significativo (Intel, Alpha), per il secondo al byte pi` significativo (Motorola, Sparc, PowerPC, HP–PA) u 9 Un campione a 12 o 16 bit ` compresso logaritmicamente a 8 bit: OSS in riproduzione e non effettua l’operazione opposta, ma converte in un campione a 8 bit lineare prima di inviare al device audio (sono introdotti un overhead per il calcolo e della distorsione); ` un formato e derivante dalla tecnologia telefonica digitale 7
  • 13.
    1.2. L’INTERFACCIA DIPROGRAMMAZIONE DI OSS CAPITOLO 1. LA SCHEDA AUDIO E OSS ` /dev/midi00 E un’interfaccia a basso livello al canale MIDI, orientata ai carat- teri come tty (raw mode) e indirizzata ad applicazioni che non richiedono una sincronizzazione in tempo reale come i sequencer (pu` essere usata o per inviare sysex, per caricare campioni sugli strumenti o per effettuare il dump dei canali); se presenti, possono essere gestite altre porte MIDI con /dev/midi01, /dev/midi02 e /dev/midi03 /dev/sndproc Rappresenta l’interfaccia interna di accesso a un eventuale co- processore presente sulla scheda audio; pu` essere una soluzione tempo- o ranea, da eliminare in seguito /dev/sndstat Questo, a differenza degli altri, ` un device file a sola lettura: e stampa informazioni diagnostiche riguardo la configurazione di OSS in for- ma leggibile agli umani, ma se ne sconsiglia l’utilizzo da parte dei programmi perch` in futuro il formato delle informazioni da esso fornite potrebbe cam- e biare; non ci si far` pi` riferimento d’ora in poi. L’output ha una sezione a u per ogni categoria di dispositivi, e questi sono numerati nell’ordine in cui il driver li inizializza (che non ` fisso, per cui ` meglio non fare assunzioni a e e priori); un esempio del suo utilizzo ` il seguente: e ~>cat /dev/sndstat Sound Driver:3.5.4-960630 Kernel: Linux papo 2.0.32 #1 Wed Nov 19 00:46:45 EST 1997 i486 Config options: 0 Installed drivers: Card config: Audio devices: 0: Sound Blaster 16 (4.13) Synth devices: 0: Yamaha OPL-3 Midi devices: Timers: 0: System clock Mixers: 0: Sound Blaster OSS consente di avere pi` schede audio installate nel computer, il che si u traduce in un maggior numero di device file indirizzabili, elencati nell’output 8
  • 14.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.3. INIZIALIZZAZIONE DI OSS di /dev/sndstat: l’esempio sopra rivela che il computer ha solo /dev/dsp0, ma in generale con pi` DSP li si potrebbe indirizzare operando su /dev/dspn; u analogamente per il mixer su /dev/mixern, etc. 1.3 Inizializzazione di OSS Il minimo insieme di header file da includere ` rappresentato da stdio.h (per le e funzioni di libreria tipo printf() e perror()), unistd.h, fcntl.h (per open(), ioctl(), read(), write() e close()) e sys/soundcard.h (le definizioni vere e proprie per la libreria di OSS). Adesso si riporter` uno scheletro di codice C per l’apertura di un device file di a OSS (nell’esempio ` /dev/dsp, ma potrebbe essere /dev/mixer, . . . ); nei succes- e sivi Capitoli lo si dar` come sottinteso man mano che si introducono le primitive a per una gestione vieppi` sofisticata della scheda audio. u #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/soundcard.h> int main() { int dspfd; /* File descriptor per /dev/dsp */ dspfd = open("/dev/dsp", O_WRONLY); /* Apre il device */ if (dspfd == -1) { perror("/dev/dsp"); /* Gestione errore */ exit(-1); } /* Qui ci puo’ andare tutto il codice per sfruttare il */ /* device file, con le ioctl() necessarie a configurare */ /* il driver poste prima delle read() o write() */ close(dspfd); /* Chiusura del device file */ return 0; } Il secondo argomento di open() ` la modalit` di accesso al device file, che nel e a caso di OSS pu` essere una fra le seguenti: o O RDONLY Accesso in sola lettura (read() o ioctl()) O WRONLY Accesso in sola scrittura (write() o ioctl()) O RDWR Accesso in lettura/scrittura (read(), write() o ioctl()) 9
  • 15.
    1.3. INIZIALIZZAZIONE DIOSS CAPITOLO 1. LA SCHEDA AUDIO E OSS Per read() e write() i flag O_NDELAY o O_NONBLOCK, affinch´ le operazioni di e I/O sul device file non blocchino il processo chiamante, non hanno effetto: se il processo effettua una read() e i campioni richiesti non sono ancora disponibili, questo viene messo in wait finch´ l’operazione ` completata (analogamente con e e una write(), se il buffer del driver non ha spazio sufficiente per i campioni che si vogliono riprodurre). Per la open() il funzionamento ` sempre del tipo O_NDELAY; se fallisce il e file descriptor ` posto uguale a -1 e la macro errno ` impostata a un oppor- e e tuno valore (questa ` messa a disposizione del programmatore per mezzo di e #include <errno.h>). 1.3.1 Gestione degli errori I codici di errore pi` comuni riportati da errno sono i seguenti: u ENOENT Il device file che si ` tentato di aprire non ` presente in /dev e e ENODEV Esiste il device file in /dev, ma il driver non ` stato caricato dal e kernel (si pu` controllare con cat /dev/sndstat o dmesg | more) o ENOSPC Esiste il device file in /dev, ma il driver non ` stato in grado di e allocare la memoria per il buffer DMA tramite sound_mem_init() durante il boot del sistema; il modulo del driver dovrebbe essere uno dei primi ad esser caricato dal kernel, in modo da garantire tale operazione anche in caso di poca memoria installata ENXIO Esiste il device file in /dev e il driver ` presente nel kernel, ma non e esiste il dispositivo hardware che si tenta di indirizzare (ad esempio, perch´ e la configurazione del driver non corrisponde all’hardware audio) EINVAL Uno degli argomenti della chiamata a una funzione non ha un valore valido EBADF Il file descriptor non si riferisce a un device file aperto, una read() ` e stata rivolta a un device file aperto con O_WRONLY o una write() ` stata e rivolta a un device file aperto con O_RDONLY EBUSY Solo un processo alla volta pu` gestire /dev/dspn e /dev/audion, per o cui si verifica se un altro processo (anche appartenente allo stesso utente) tenta di accedervi (si verifica anche se l’IRQ o il canale DMA sono occupati); pu` essere gestito dal programma tentando di riaprire il device file che ha o causato l’errore dopo qualche tempo, ma non ` garantito che esso divenga e mai disponibile ` EINTR E ritornato da read() o write() qualora queste siano risultate bloccan- ti e durante lo stato di wait il processo utente abbia ricevuto un signal() 10
  • 16.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.3. INIZIALIZZAZIONE DI OSS EAGAIN La risorsa a cui si ` cercato di accedere ` temporaneamente non e e disponibile (ad esempio, si ` cercato di scrivere su un buffer pieno o leggere e da un buffer vuoto con il device file aperto in O_NONBLOCK) EACCES Per i device /dev/dspn e /dev/audion l’accesso ` consentito solo al e root (a meno che il sistema sia stato configurato in altro modo), e questo errore si verifica se un utente normale cerca di accedere a tali device file; questa ` una misura di sicurezza per impedire che, ad esempio, qualora il e computer sia connesso in rete e abbia un microfono qualcuno possa ascoltare remotamente le conversazioni che si svolgono nella stanza ove si trova il computer Un error handler un po’ pi` sofisticato potrebbe rassomigliare a: u if ((dspfd = open("/dev/dsp2", O_WRONLY)) == -1) { switch (errno) { case ENOENT: perror("/dev/dsp2"); exit(-1); case EBUSY: close(dspfd); /* Aspetta un po’ per riaprire */ sleep(10); if (dspfd = open("/dev/dsp2", O_WRONLY)) == -1) { perror("/dev/dsp2"); exit(-1); } break; default: perror("Altro tipo di errore"); fprintf(stderr, "Errore numero: %dn", errno); exit(-1); } } Per quanto invece riguarda il seguito di questo lavoro, si demander` la gestione a degli errori a una semplice routine del tipo: void errore(const char *msgerr) { perror(msgerr); exit(-1); } Sarebbe corretto mettere un error handler non solo dopo una open(), ma an- che dopo read() o write() per verificare se siano stati letti o scritti il numero corretto di byte; tuttavia nella sintesi in tempo reale ci` tende a rappresentare o cicli di CPU sprecati. Invece dopo una ioctl() conviene controllare quasi ob- bligatoriamente il valore ritornato dal driver nell’ultimo dei suoi argomenti, per vedere cosa si riesce a ottenere rispetto a quanto richiesto dal programmatore. 11
  • 17.
    1.3. INIZIALIZZAZIONE DIOSS CAPITOLO 1. LA SCHEDA AUDIO E OSS Un altro buon accorgimento di programmazione ` di installare un exit handler e e/o un signal handler subito dopo una open() riuscita: il primo pu` essere utile o 10 per operazioni di routine alla chiusura del programma (regolare o in seguito a exit()), il secondo pu` gestire i segnali impostati da altri processi, dal kernel o o dal processo stesso. Per una dettagliata descrizione di questi argomenti si veda [10]. 1.3.2 L’exit handler Un exit handler richiede la funzione di libreria atexit(), disponibile in segui- to a #include <stdlib.h>. Essa registra le funzioni di tipo void f() date in argomento come exit handler (max 32), venendo richiamate nell’ordine inverso rispetto a quello con cui sono state registrate; atexit() restituisce 0 se la regis- trazione ` stata possibile, altrimenti -1 con errno==ENOMEM (memoria insufficiente e per aggiungere la funzione). Nell’esempio seguente si dimostra l’utilizzo di atexit() chiudendo un device file all’uscita dal programma (anche se ci` non ` strettamente necessario per o e quanto prima affermato): #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/soundcard.h> int dspfd; /* Variabile globale */ void Messaggio() /* Chiamata per prima */ { puts("Premi <Invio> per chiudere /dev/dsp..."); getchar(); } void ChiudiTutto() /* Chiamata per ultima */ { close(dspfd); puts("Fine del programma"); } void errore(const char *msgerr) /* Gestione degli errori */ { perror(msgerr); 10 Linux svuota i buffer di I/O e chiude automaticamente i device file rimasti eventualmente aperti prima che il processo termini, tramite librerie a livello utente o tramite kernel 12
  • 18.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.3. INIZIALIZZAZIONE DI OSS exit(-1); } int main() { if ((dspfd = open("/dev/dsp", O_RDONLY)) == -1) errore("/dev/dsp"); if (atexit(ChiudiTutto) == -1) /* Registrata per prima */ errore("ChiudiTutto()"); if (atexit(Messaggio) == -1) /* Registrata per ultima */ errore("Messaggio()"); return 0; /* Uscita dal programma */ } L’exit handler ` richiamato in seguito ad exit(), abort()11 , return o alla e consegna di un segnale la cui azione di default ` di uccidere il processo; l’output e del programma sarebbe: Premi <Invio> per chiudere /dev/dsp... Fine del programma Per evitare la chiamata all’exit handler lo standard POSIX.1 (1990) prevede _exit(), che inoltre evita lo svuotamento dei buffer prima della chiusura dei file. 1.3.3 Il signal handler Un signal handler funziona in modo molto simile ad un exit handler: previo #include <signal.h>, si usa la funzione di libreria signal() per registrare delle funzioni (non di libreria) che hanno il compito di reagire a segnali provenienti da altri processi o dallo stesso processo12 (rispettivamente generati tramite le funzioni di libreria kill() e raise()). In signum.h sono definiti una trentina di segnali che si conformano ad ANSI, POSIX, System V e BSD 4.2, per i quali esistono delle disposizioni di default (ci` equivale a signal(<segnale>, SIG_DFL)); se si vuole ignorare un partico- o lare segnale si pu` porre nel codice signal(<segnale>, SIG_IGN). La signal() o ritorna il valore precedente del signal handler o SIG_ERR se si verifica un errore. 11 Viene impostato un segnale SIGABRT al processo chiamante: non ` ignorabile o bloccabile e da un signal handler 12 Un segnale ` definibile come un’interruzione asincrona software nel flusso di un processo: e esso ` “impostato” dal processo che lo genera ed ` “consegnato” al processo che lo riceve; il e e modo in cui quest’ultimo reagisce al segnale si chiama “disposizione” 13
  • 19.
    1.4. SCRITTURA DICODICE PORTABILE CAPITOLO 1. LA SCHEDA AUDIO E OSS Per esempio, se si vuole che il processo reagisca a un segnale di interruzione (SIGINT) e a un segnale di terminazione del programma (SIGTERM) con una disposizione diversa dalla predefinita, all’inizio di main() si potr` porre: a signal(SIGINT, SignalHandler); /* Le funzioni possono essere */ signal(SIGTERM, SignalHandler); /* uguali o diverse */ ove SignalHandler ` una funzione del tipo: e void SignalHandler(int segnale) /* Il segnale invocante */ { /* e’ passato all’handler */ switch (segnale) { case SIGINT: case SIGTERM: puts("SIGINT o SIGTERM"); exit(0); default: } } il processo relativo termina se da shell di comando si digita kill -s SIGINT <PID> o kill -s SIGTERM <PID>, ove <PID> ` l’identificatore del processo (visualizz- e abile tramite il comando ps). Sono da tenere presenti i seguenti fatti: • non possono essere variate le disposizioni di SIGKILL e SIGSTOP (signal() ritorna EINVAL) • il comportamento di un programma ` indefinito se sono ignorati SIGFPE, e SIGILL o SIGSEGV • ignorare il segnale derivante dalla divisione intera per zero ha un compor- tamento indefinito, che potrebbe condurre a un blocco del computer • per certi segnali la disposizione di default implica la terminazione del pro- cesso e il salvataggio dell’area dati e heap in un file core a fini di debug; altri provocano la semplice terminazione del processo o sono ignorati • a differenza di BSD, Linux non ridispone il signal handler a SIG_DFL dopo la consegna di un segnale Si veda man 7 signal per una dettagliata descrizione dei segnali e della loro disposizione di default, e in ogni caso [10] per una gestione pi` sofisticata. u 1.4 Scrittura di codice portabile Di seguito sono elencati alcuni consigli per la scrittura di programmi che siano portabili sotto i vari sistemi operativi per cui sia stato portato anche OSS: 14
  • 20.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.4. SCRITTURA DI CODICE PORTABILE • Come si vedr` nei successivi Capitoli, conviene usare delle specifiche macro a per impostare dei parametri, ad esempio del mixer, tramite ioctl() (queste schermano i dettagli dell’implementazione dei parametri per le future ver- sioni del driver); in particolare bisognerebbe controllare se ` adeguato il e valore ritornato nell’ultimo argomento (per le chiamate che lo prevedono) ` • E meglio riferirsi a un link simbolico per un device piuttosto che utilizzare un riferimento assoluto, ad esempio usando /dev/dsp al posto di /dev/dsp0; ci` d` flessibilit` all’utente per poter far puntare i link simbolici ad altri o a a device file, se questi garantiscono migliori risultati (i programmi dovrebbero sfruttare i nomi “veri” solo se resi facilmente configurabili) • Non bisogna sfruttare delle caratteristiche non documentate (in quanto obsolete — scompariranno in futuro — o non ancora ben testate) • L’appesantimento di un programma con caratteristiche al di fuori dell’essen- ziale o con “trucchi” pu` compromettere la compatibilit` del codice con le o a future versioni del driver • Se si utilizza la risoluzione a 16 bit bisogna fare attenzione che la CPU memorizzi i campioni come il DSP della scheda audio, cio` coincida per e entrambi la codifica big endian o little endian; in tal senso bisogna evitare di accedere ai campioni a 16 bit ciecamente come signed short • Non bisogna fidarsi delle impostazioni di default di un device (anche perch` e potrebbero essere state modificate da un precedente processo); ad esempio, anche se il default per /dev/dsp ` 8 kHz/8 bit unsigned/mono, molte schede e non supportano la frequenza di campionamento di 8 kHz e si corre il rischio di ottenere solo rumore con le future schede audio a 24 bit; analogamente, non bisogna assumere per /dev/sequencer il clock di default di 100 Hz (Linux/Alpha ha un clock di 1024 Hz), mentre non ci sono valori di de- fault per /dev/music (bisogna sempre impostare per primi i parametri di temporizzazione) • Non si devono scrivere programmi che funzionano solo a 16 bit, poich´ molte e schede vecchie sono a 8 bit: campioni da 16 bit riprodotti su queste danno luogo solo a rumore ad alto volume • Non si deve dare per scontato che per ogni scheda audio ci sia /dev/mixer (non lo possiedono le pi` vecchie, o quelle non ancora pienamente support- u ate, o le schede audio completamente digitali); non tutti i mixer hanno un controllo di master volume (ma se ce l’hanno bisogna tenere presente che questo influenza il volume di tutti i canali), inoltre si deve sempre testare la presenza di un canale prima di cercare di indirizzarlo (ad esempio, non tutte le schede possiedono un sintetizzatore interno e/o una porta MIDI) 15
  • 21.
    1.5. ANATOMIA DELDRIVER CAPITOLO 1. LA SCHEDA AUDIO E OSS • Non si deve usare il full duplex senza prima controllare che la scheda audio supporti tale modalit`a • I device audio non devono essere tenuti aperti quando non sono richiesti, altrimenti altri programmi non vi possono accedere; in tal senso un pro- gramma dovrebbe gestire flessibilmente le situazioni di EBUSY, ad esempio riprovando ad accedere dopo qualche tempo al device file 1.5 Anatomia del driver OSS sfrutta il Direct Memory Access (DMA) per trasferire i campioni dalla scheda audio a un’opportuna area di RAM e viceversa: questa in genere non coincide con il buffer del processo che li elabora, per cui il driver deve copiare i campioni dal/al buffer DMA a/da quest’ultimo13 . Nei PC–compatibili della copia se ne occupa la CPU, dal momendo che il DMA Controller (DMAC) compatibile Intel 8237 ha dei pesanti limiti: non pu` o effettuare copie fra le porte di I/O o fra memoria e memoria; inoltre il buffer DMA deve risiedere al di sotto dei primi 16 MB di RAM per le schede audio ISA (non per le PCI), poich´ questo ` il limite di indirizzamento del bus, e deve e e essere un blocco di memoria non frammentato che inizia e finisce nella stessa pagina DMA. Quest’ultima ha dimensione di 64 kB per i canali 0÷3 a 8 bit e 128 kB per i canali 5÷7 a 16 bit: ci` rende difficile usare direttamente il buffer o locale del processo come buffer DMA con le schede ISA e pi` di 16 MB, poich´ u e dovrebbe risiedere al di sotto del limite dei 16 MB; tuttavia i nuovi controller nelle periferiche bypassano l’Intel 8237 completamente (fly–by), per cui in particolari condizioni si pu` arrivare a mappare il buffer DMA all’interno dell’area dati del o processo (ci` ` sempre possibile con le schede PCI). oe L’elaborazione dei campioni da parte del processo deve avvenire almeno un po’ pi` velocemente del ritmo al quale il DMAC trasferisce i campioni, affinch´ u e non ci siano pause in fase di campionamento o riproduzione. Se ci` si verifica o bisogner` usare una frequenza di campionamento inferiore all’attuale, o usare un a formato audio pi` “compatto” per i campioni, in modo da ridurre la quantit` di u a byte trasferiti dal DMAC. Linux ha il “problema” di essere multiutente e multiprogrammato, per cui i processi competono per l’utilizzo della CPU e un processo a pi` alta pri- u orit` potrebbe porre il processo che sfrutta i servizi di OSS in stato di wait a per diversi ms: in tal modo il tempo che questo ha per elaborare i campioni 13 Alcune schede possono auto–iniziare il trasferimento senza attendere risposta dal driver, usando il DMAC in modalit` auto–restart (ci` non ` supportato da tutti i sistemi operativi, ad a o e esempio da BSD) 16
  • 22.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.5. ANATOMIA DEL DRIVER diminuisce. In pratica ci deve essere abbastanza spazio nel buffer DMA per garantire l’operativit` per il tempo di wait14 . a OSS gestisce il buffer DMA con la tecnica del multi–buffering: in pratica questo ` diviso in frammenti di uguale dimensione, per default calcolata dal driver e in modo tale che la latenza15 sia attorno a 0.5s per la riproduzione e attorno a 0.1s per il campionamento. In tal modo ` possibile aumentare la dimensione del buffer e senza influire sulla latenza stessa poich´ il DMAC lavora solo su un frammento e per volta, mentre l’applicazione legge o scrive sul resto del buffer. memoria wait Scheda audio Kernel Processo 1111 0000 DMAC 1111 0000 bus ISA o PCI OSS buffer audio driver read() o write() buffer DMA Figura 1.5: Schema di utilizzo del multi–buffering 1.5.1 Scrittura sul buffer DMA In fase di riproduzione, quando il programma chiama write() per la prima volta dopo l’apertura del device, si verificano i seguenti eventi: • il driver programma la scheda audio con i parametri di campionamento predisposti (risoluzione, numero di canali e frequenza) • di default ` calcolata la dimensione adeguata per un frammento, se tramite e un’opportuna chiamata ioctl() non se ne ` stabilita un’altra e • viene iniziato il riempimento del primo frammento del buffer con i dati passati dalla write() • se il primo frammento ` stato riempito completamente, il DMAC ne inizia e il trasferimento alla scheda audio 14 Alcune schede dispongono di RAM locale per la riproduzione, ma prima che questa possa avvenire i campioni devono esservi trasferiti (per la Gravis UltraSound ci sono 256 kB per canale, ovvero sono “coperti” 2.9 s di suono continuo in modalit` 16 bit/stereo/44.1 kHz) a 15 La latenza ` il tempo che il processo deve aspettare per avere accesso a un frammento e in campionamento o perch´ questo venga suonato in riproduzione quando il buffer ` pieno; e e essa dipende dal data rate, che ` la quantit` di dati che il DMAC deve trasferire nell’unit` di e a a tempo (per esempio, con un campionamento 16 bit/stereo/44.1 kHz il data rate ` 2 · 2 · 44.1 = e 176.4 kB/s), nonch´ dalla dimensione del frammento e 17
  • 23.
    1.5. ANATOMIA DELDRIVER CAPITOLO 1. LA SCHEDA AUDIO E OSS • il driver copia il resto dei dati nel buffer, eventualmente riempiendo altri frammenti; se tutti i frammenti del buffer sono stati riempiti il processo relativo al programma che ha chiamato la write() ` messo in stato di wait e finch´ non ` libero almeno un frammento (condizione di overrun) e e Alle successive chiamate di write() i dati sono immagazzinati nel buffer secondo la disponibilit` di frammenti liberi. a L’overrun si verifica normalmente per un processo che scriva i campioni nel buffer pi` velocemente di quanto vengano riprodotti. Se al contrario il processo u ` leggermente pi` lento a scrivere i campioni rispetto alla velocit` con la quale e u a sono riprodotti si verifica la condizione di underrun, per uno dei seguenti motivi: • l’applicazione ` troppo lenta nell’elaborazione dei campioni (perch´ la CPU e e ` troppo lenta rispetto al data rate richiesto o ci sono troppi processi in e esecuzione che competono per la CPU) • ci sono leggere variazioni nel tempo di CPU ricevuto (un’applicazione gen- eralmente ben funzionante pu` occasionalmente andare in underrun) o • l’applicazione tenta di lavorare troppo in tempo reale (frammenti pi` piccoli u decrescono la latenza, tuttavia bisogna sempre scrivere altri campioni prima che il buffer si svuoti) Un underrun provoca in genere un difetto udibile nel segnale riprodotto: pu`o essere una breve pausa, un “click” o la ripetizione di una parte del segnale (looping); se quest’ultima si verifica con frequenza uniforme si avvertir` un tono a sovrapposto al segnale riprodotto, con frequenza pari a quella con cui si verifica l’underrun. 1.5.2 Lettura dal buffer DMA In fase di campionamento, quando il programma chiama read() per la prima volta dopo l’apertura del device, si verificano i seguenti eventi: • il driver programma la scheda audio con i parametri di campionamento predisposti (risoluzione, numero di canali e frequenza) • di default ` calcolata la dimensione adeguata per un frammento, se tramite e un’opportuna chiamata ioctl() non se ne ` stabilita un’altra e • sono attivati il processo di campionamento da parte della scheda audio e il trasferimento dei campioni nel primo frammento del buffer • il processo ` messo in wait finch´ non ` riempito un numero di frammenti e e e che forniscono globalmente una quantit` di campioni maggiore o uguale a a quella richiesta 18
  • 24.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.6. SCHEDE AUDIO ISA E PCI • i campioni richiesti sono copiati nel buffer del processo; gli eventuali cam- pioni in pi` rimangono nel buffer DMA u Le read() successive funzionano come sopra, senza che sia necessario ripredis- porre la scheda audio. Un overrun in campionamento si verifica se il buffer ` completamente riempito: e in tal caso gli ulteriori campioni sono scartati; le ragioni per cui si verifica sono simili a quelle per cui si verifica in riproduzione. 1.6 Schede audio ISA e PCI L’approccio seguito in questo lavoro ` di essere il pi` indipendenti possibile dal- e u l’hardware, in modo da poter creare dei programmi che girino su ogni piattaforma per cui ` stato portato OSS con tutt’al pi` una semplice ricompilata del codice e u sorgente. Tuttavia si vogliono elencare i motivi per i quali le schede audio PCI sono superiori alle schede audio ISA (al di l` della qualit` audio); al momento in a a cui si scrive (1999) la quasi totalit` delle schede audio in commercio sono PCI, ma a fino a un anno fa erano quasi tutte ISA: un musicista professionista dovrebbe pren- dere in considerazione l’acquisto di una scheda audio PCI, anche se attualmente OSS non supporta del tutto le nuove caratteristiche, come l’audio 3D. Si elencheranno ora le differenze fra ISA e PCI pertinenti le schede audio: • Il bus ISA ha un clock nominale di 8 MHz, il che darebbe un throughput teorico di 16 MB/s; in realt`, a causa di overhead vari nella gestione dei seg- a nali, nonch´ il fatto che sono richiesti due cicli di clock per il trasferimento e dei dati, il throughput si aggira attorno ai 5 MB/s. Se si guarda la quantit` a di dati da trasferire per una scheda che campiona a 16 bit/stereo/44.1 kHz (circa 176 kB/s) questo throughput appare adeguato, ma si pu` verificare o che il DMA blocchi l’accesso della CPU al bus durante il trasferimento dei campioni o se la richiesta d’interrupt ` occupata (con ISA le IRQ non sono e condivisibili), il che pu` causare click nel suono in sistemi pesantemente o caricati. La capacit` di indirizzamento massima per ISA ` di 16 MB, per a e cui sono difficilmente applicabili le tecniche di allocazione del buffer DMA nel buffer del processo, che dovrebbe risiedere al di sotto di tale limite anche per sistemi con pi` RAM. Pu` inoltre risultare difficile la configurazione di u o una scheda audio, soprattutto con OSS/Free se ` PnP.e • Le specifiche PCI 2.1 consentono un clock sul bus fino a 66 MHz, con un throughput teorico di 264 MB/s, mentre in pratica ci si aggira intorno ai 108 MB/s; con un clock sul bus di 33 MHz questi valori si dimezzano. Gli interrupt sono condivisibili, per cui con la tecnica dell’interrupt binding, se si verificano pi` interrupt contemporaneamente, questi vengono raggrup- u pati e serviti in base alla priorit` (e Linux distingue fra fast interrupt — a quelli che richiedono un salvataggio del contesto parziale, come la richiesta 19
  • 25.
    1.7. FILE SYSTEME DEVICE FILE CAPITOLO 1. LA SCHEDA AUDIO E OSS di un servizio DMA — e gli interrupt normali, con salvataggio completo del contesto — riducendo in tal modo l’overhead). Il PCI consente il busmas- tering multiplo (con due busmaster), ovvero due arbitri nella gestione del bus, il che si traduce nell’accesso contemporaneo di CPU e DMA al bus se le aree di memoria interessate non coincidono; l’indirizzamento ` a 32 bit, e per cui sono applicabili le tecniche di allocazione del buffer DMA nel buffer ` del processo. E inoltre pi` semplice la configurazione delle periferiche PCI, u in quanto dopo il boot queste negoziano fra loro, in pratica autoallocandosi. Queste ed altre considerazioni, come la tendenza delle nuove schede audio ad incrementare la frequenza di campionamento, spingono a concludere che una scheda audio PCI pu` risultare fino a dieci volte pi` efficiente di una scheda ISA. o u 1.7 File system e device file Come si ` avuto occasione di affermare precedentemente, OSS ` un driver che e e fornisce al programmatore la possibilit` di gestire audio e MIDI tramite opportuni a device file inseriti nella struttura dell’albero monolitico del file system di Linux. Ogni device file ` caratterizzato da un major number e da un minor number : il e primo ` utilizzato come indice in una tabella del kernel per identificare il tipo e di driver che deve gestire un dispositivo hardware, il secondo ` passato al driver e stesso per identificare l’unit` su cui agire (classe del device). a In Linux il major number per OSS ` 14, ma per altri sistemi operativi potrebbe e essere diverso. Il minor number pu` essere codificato tramite un solo byte: in o tal caso, come da figura 1.6, i quattro bit meno significativi identificano la classe del dispositivo, mentre i quattro bit pi` significativi identificano un dispositivo u all’interno di una stessa classe; ne consegue che ci possono essere fino a sedici dispositivi dello stesso tipo per ogni classe. Tipo device Classe mixer 0 sequencer 1 midi 2 7 4 3 0 dsp 3 num. device classe audio 4 dspW 5 1 byte sndstat 6 riservato 7 music 8 sndproc 9 Figura 1.6: Codifica del minor number di un device file di OSS 20
  • 26.
    CAPITOLO 1. LASCHEDA AUDIO E OSS 1.8. LE VERSIONI DI OSS Ad esempio, /dev/midi00 identifica il primo dispositivo di classe 2 (MIDI), per cui ` il numero del device ` 0: ci` implica che il minor number per il device file e e o relativo ` 0x02 (2 in decimale). Analogamente per /dev/midi01 il minor number e per il device file relativo sar` 0x12 (18 in decimale), per /dev/midi02 sar` 0x22 a a (34 in decimale) e per /dev/midi03 sar` 0x32 (50 in decimale). a Anche se nelle odierne distribuzioni di Linux non ci dovrebbero essere problemi del genere, se un device file dovesse mancare ` possibile crearlo effettuando il login e come root e dando il seguente comando: mknod -m <permessi> <nome device file> c 14 <classe del device> ad esempio, il nome del device file potrebbe essere /dev/music e la classe sarebbe 8; <permessi> ` un numero ottale che predispone i permessi di accesso al file, che e pu` essere posto pari a 666 per accesso in lettura/scrittura da parte di tutti gli o utenti (vedere man chmod). 1.8 Le versioni di OSS Il riconoscimento della versione di OSS in uso varia secondo che si stia utilizzando una versione precedente o successiva alla 3.6. Ad esempio, la versione 3.5.4 di soundcard.h definisce le seguenti macro: #define SOUND_VERSION 350 #define UNIX_SOUND_SYSTEM mentre nella versione 3.8.2 sono definite le seguenti altre macro: #define SOUND_VERSION 0x030802 #define OPEN_SOUND_SYSTEM SOUND_VERSION contiene il numero di versione, con formato che varia secondo che sia definito UNIX_SOUND_SYSTEM o OPEN_SOUND_SYSTEM. Dalla versione 3.6 in poi ` possibile usare il seguente frammento di codice, che e interroga direttamente il driver per ricavare il numero di versione (` pi` affidabile e u che ricavarlo da SOUND_VERSION): int versione; if (ioctl(dspfd, OSS_GETVERSION, &versione) == -1) { /* Versione precedente alla 3.6: errno==EINVAL */ } Il seguente frammento di codice ricava i numeri di versione e release indipen- dentemente dalla versione di OSS utilizzata: int versione, release1, release2, tmpver; #ifdef UNIX_SOUND_SYSTEM 21
  • 27.
    1.8. LE VERSIONIDI OSS CAPITOLO 1. LA SCHEDA AUDIO E OSS versione = SOUND_VERSION / 100; release1 = (SOUND_VERSION - versione*100) / 10; release2 = SOUND_VERSION - versione*100 - release1*10; #else /* e’ definito OPEN_SOUND_SYSTEM */ if (ioctl(dspfd, OSS_GETVERSION, &tmpver) == -1) errore("OSS_GETVERSION"); versione = (tmpver & 0x00ff0000) >> 16; release1 = (tmpver & 0x0000ff00) >> 8; release2 = tmpver & 0x000000ff; #endif 22
  • 28.
    Capitolo 2 IL MIXERE LA GESTIONE DEI CANALI 2.1 Descrizione di /dev/mixer Non tutte le schede audio possiedono un mixer: nella fattispecie possono non averlo le schede pi` vecchie, quelle non ancora pienamente supportate, quelle u professionali e le completamente digitali. Anche se il mixer ` mancante si pu` e o sempre aprire /dev/mixer, ma un’eventuale ioctl() ritorna errno==ENXIO. Nella Sezione 1.3 si ` fornito uno scheletro di codice C per l’apertura e la e chiusura di un device file; si ` scritto che fra la open() e la close() di questo e ci possono essere delle read(), write() o ioctl(). /dev/mixer ` un device file e atipico, in quanto non accetta operazioni di read() o write() e l’unica primitiva utilizzabile ` ioctl(): infatti il mixer svolge solo un lavoro di gestione dei canali e cambiando la configurazione della scheda audio, praticamente non impiegando risorse di calcolo per operare. A differenza di /dev/dsp e /dev/audio, pi` di un processo alla volta pu` u o aprire /dev/mixer; generalmente si usa O_RDONLY come argomento di open(). Le modifiche effettuate alla configurazione del mixer permangono anche dopo la chiusura dell’ultimo processo modificante, fino a quando un eventuale altro processo non effettuer` nuovi cambiamenti o fino al reboot del computer. All’atto a del boot ` il kernel che si occupa di configurare la scheda audio con dei valori e di default ragionevoli, ma che non dovrebbero comunque essere dati per scontati per non creare programmi inaffidabili. Solo per il primo mixer, l’uso delle ioctl() per /dev/mixer su questo o su un altro device di OSS sono equivalenti: ad esempio, se si ` aperto /dev/sequencer ` e e inutile aprire anche /dev/mixer per variare la configurazione della scheda audio, basta usare gli ioctl() che si sarebbero utilizzati col secondo direttamente col primo. ` E importante verificare le capacit` del mixer prima di usarne i canali, in mo- a do da creare programmi che siano portabili per quasi tutte le schede audio. Le 23
  • 29.
    2.2. I CANALIDEL MIXER CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI differenze di comportamento riscontrate dovrebbero essere indicate nella docu- mentazione, evitando descrizioni troppo specifiche nei confronti dei canali, che possono avere caratteristiche diverse con schede diverse. 2.2 I canali del mixer Per i canali del mixer ci si pu` rifare alla metafora delle figure 1.1 e 1.2, per cui o un canale identifica la classe del dispositivo (CD, microfono, . . . ) che a questo ` connesso; il programmatore pu` effettuarne la selezione per la riproduzione o e o il campionamento, regolandone il livello di volume (se il dispositivo ` stereo ci e sono due livelli di volume indipendentemente controllabili, il che consente di real- izzare il balance). Il volume principale pu` essere mancante (Gravis UltraSound, o Microsoft Sound System). Sono definiti SOUND_MIXER_NRDEVICES canali, a cui ` associato un numero da e 0 a SOUND_MIXER_NRDEVICES-1; un programma non dovrebbe cercare di accedere a numeri di canale superiori a quest’ultimo. OSS mette a disposizione del programmatore dei nomi simbolici per ogni canale; quelli definiti in soundcard.h versione 3.5.4 si trovano nella tabella della prossima pagina. Ad ogni canale ` associato un int, nella cui rappresentazione in binario (bit- e mask ) ` posto a 1 il bit di posizione corrispondente al numero del canale; le e bitmask sono utili con i comandi di gestione della configurazione del mixer. Sempre in soundcard.h sono definiti dei nomi simbolici da dare ai canali tramite le macro: #define SOUND_DEVICE_LABELS {"Vol ", "Bass ", "Trebl", "Synth", "Pcm ", "Spkr ", "Line ", "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", "Line1", "Line2", "Line3"} #define SOUND_DEVICE_NAMES {"vol", "bass", "treble", "synth", "pcm", "speaker", "line", "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", "line1", "line2", "line3"} la differenza fra i due ` che il primo formato ` adatto per la stampa dei nomi a e e video quali etichette dei canali, mentre del secondo formato ` pi` adatto l’utilizzo e u quando i nomi dei device audio sono forniti sulla riga di comando di una shell. 24
  • 30.
    CAPITOLO 2. ILMIXER E LA GESTIONE DEI CANALI 2.2. I CANALI DEL MIXER Nomi dei canali Bitmask associate Descrizione SOUND_MIXER_VOLUME SOUND_MASK_VOLUME Livello volume principale SOUND_MIXER_BASS SOUND_MASK_BASS Regolazione toni bassi principale SOUND_MIXER_TREBLE SOUND_MASK_TREBLE Regolazione toni acuti principale SOUND_MIXER_SYNTH SOUND_MASK_SYNTH Livello di uscita sintetizzatore interno (FM, wavetable); per alcune schede ne controlla anche il livello di campionamento SOUND_MIXER_PCM SOUND_MASK_PCM Livello di uscita di /dev/dsp e /dev/audio SOUND_MIXER_SPEAKER SOUND_MASK_SPEAKER Livello di uscita del segnale all’altoparlantino nel PC (se connesso alla scheda audio); su altre schede pu` essere un o generico ingresso mono con qualche altra funzione SOUND_MIXER_LINE SOUND_MASK_LINE Livello di ingresso per Line–In SOUND_MIXER_MIC SOUND_MASK_MIC Livello del segnale microfonico in campionamento o inviato a cuffie e Line–Out; qualche volta il microfono non ` connesso a questo canale, ma e a un line level input della scheda SOUND_MIXER_CD SOUND_MASK_CD Livello del CD Audio–In SOUND_MIXER_IMIX SOUND_MASK_IMIX Recording monitor campionamento; durante tale fase, su alcune schede controlla il volume delle cuffie SOUND_MIXER_ALTPCM SOUND_MASK_ALTPCM Livello di un DSP secondario; nella Pro Audio Spectrum 16 ` il canale e dell’emulazione della Sound Blaster SOUND_MIXER_RECLEV SOUND_MASK_RECLEV Livello di campionamento per tutti i canali (nella Sound Blaster 16 si hanno solo quattro livelli possibili) SOUND_MIXER_IGAIN SOUND_MASK_IGAIN Livello di guadagno di ingresso SOUND_MIXER_OGAIN SOUND_MASK_OGAIN Livello di guadagno di uscita SOUND_MIXER_LINE1 SOUND_MASK_LINE1 Canale generico 1 (aux1); i codec AD1848 e compatibili hanno tre line level input a cui diversi costruttori assegnano funzioni diverse, per cui tali nomi si usano quando il significato preciso di un canale fisico ` sconosciuto e SOUND_MIXER_LINE2 SOUND_MASK_LINE2 Canale generico 2 (aux2) SOUND_MIXER_LINE3 SOUND_MASK_LINE3 Canale generico 3 (line) 25
  • 31.
    2.2. I CANALIDEL MIXER CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI Se in un programma si effettuano le assegnazioni: const char *nome_canale[] = SOUND_DEVICE_LABELS; const char *nome_cmline[] = SOUND_DEVICE_NAMES; allora, ad esempio, nome_canale[SOUND_MIXER_CD] corrisponde a “CD ”, men- tre nome_cmline[SOUND_MIXER_CD] corrisponde a “cd”. Il mixer consente la selezione dei canali da cui effettuare il campionamento, che per buona parte delle schede avviene in mutua esclusione. Il canale di default dopo il boot ` quello del microfono, ma non dovrebbe e essere dato per scontato, poich´ il driver non altera le predisposizioni del mixer e a meno di un comando da programma; un qualche altro processo dopo il boot potrebbe averle modificate, con tali modifiche che permangono anche dopo la sua terminazione. L’insieme di canali disponibili non ` fisso, ma dipende dalla scheda audio; si e pu` verificare che ai canali dello stesso chip mixer costruttori diversi assegnino o funzioni diverse, per cui bisogner` verificarne caso per caso il reale significato. a Sarebbe meglio non includere funzionalit` di mixer nei programmi se si vuole a la massima portabilit` del proprio codice, demandandole a programmi specializ- a zati per le varie schede audio. Nel caso si volesse realizzare un tale programma mixer bisogna ben documentare le sue capacit` se ` sviluppato per una precisa a e scheda, altrimenti ` meglio evitare di essere troppo specifici nella documentazione e per non trarre in inganno gli utenti: potrebbero credere che la propria scheda audio sia diversa da come ` realmente, basandosi su ci` che il programma fa e o vedere. 2.2.1 Lettura della configurazione Come ` stato visto sopra, converrebbe effettuare il controllo sia delle capacit` e a della scheda audio che dell’esistenza o meno dei canali di interesse prima di in- traprendere qualsiasi altra azione; un’operazione di ioctl() fallita (ad esempio, perch´ il canale non esiste) ritorna -1 e errno==EINVAL. e Per effettuare la lettura della configurazione dei canali del mixer il codice ` e simile per tutti i comandi a disposizione: int bitmask; if (ioctl(mixfd, SOUND_MIXER_READ_****, &bitmask) == -1) { /* Il mixer e’ mancante - errno==ENXIO */ } ove SOUND_MIXER_READ_**** ` l’identificatore del comando di lettura che ritorna e in bitmask una maschera di bit; questa pu` essere esaminata per determinare le o capacit` di un canale o del mixer in base al comando dato. a 26
  • 32.
    CAPITOLO 2. ILMIXER E LA GESTIONE DEI CANALI 2.2. I CANALI DEL MIXER Per i canali il controllo si pu` effettuare con: o if (bitmask & (1 << numero_canale)) { /* Il canale possiede la capacita’ in esame */ } ove numero_canale ` un intero tra 0 e SOUND_MIXER_NRDEVICES-1 o il nome e mnemonico del canale; al posto di (1 << numero_canale) si pu` utilizzare di- o rettamente la bitmask ad esso associata. I comandi a disposizione per la lettura della configurazione sono: Comandi Richiesta a OSS SOUND_MIXER_READ_DEVMASK Quali canali sono presenti? SOUND_MIXER_READ_STEREODEVS Quali canali sono stereo? SOUND_MIXER_READ_RECMASK Quali sono i canali campionabili? SOUND_MIXER_READ_RECSRC Qual ` il canale campionabile attivo? e SOUND_MIXER_READ_CAPS Si pu` campionare solo un canale per volta? o Con l’ultimo comando si testa se si possono campionare i canali solo in mutua esclusione; il controllo si effettua con: if (bitmask & SOUND_CAP_EXCL_INPUT) { /* Campionamento solo in mutua esclusione */ } La differenza fra SOUND_MIXER_READ_RECMASK e SOUND_MIXER_READ_RECSRC ` che il primo comando ritorna una bitmask con un bit a 1 per ogni canale per e cui la scheda audio possiede la capacit` di campionamento, mentre il secondo a ritorna una bitmask con un bit a 1 per ogni canale attualmente selezionato per il campionamento (se questo ` possibile in mutua esclusione solo un bit in tutta e la bitmask pu` essere a 1). o 2.2.2 Selezione del canale da campionare Per selezionare il canale da cui campionare basta il seguente frammento di codice: int bitmask = SOUND_MASK_****; if (ioctl(mixfd, SOUND_MIXER_WRITE_RECSRC, &bitmask) == -1) { /* Non c’e’ il mixer o il canale */ /* errno==ENXIO oppure EINVAL */ } ove SOUND_MASK_**** ` la bitmask associata al canale desiderato. e Nel caso fosse possibile campionare da pi` canali contemporaneamente, li si u seleziona ponendo in bitmask un OR aritmetico delle bitmask associate ai canali; ad esempio, per selezionare il campionamento simultaneo da CD e da microfono: 27
  • 33.
    2.3. LIVELLI DIVOLUME DEI CANALI CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI bitmask = SOUND_MASK_CD | SOUND_MASK_MIC; Se nessun bit ` posto a 1 (bitmask==0x00000000), il driver seleziona il canale e del microfono. 2.3 Livelli di volume dei canali Le schede audio rappresentano il livello di un canale con un numero di bit vari- abile: 3, 8 e anche 16 bit. L’architettura di OSS svincola il programmatore dalla conoscenza dei livelli assoluti di volume introducendo una rappresentazione in percentuale: il volume di un canale pu` variare fra 0 (spento) e 100 (massimo); o se il canale ` stereo si hanno due di queste percentuali, che possono essere uguali e o meno per realizzare il balance. Per leggere il livello attuale di volume per un canale si sfrutta il seguente frammento di codice: int volume; if ((ioctl(mixfd, MIXER_READ(numero_canale), &volume) == -1) { /* Non c’e’ il mixer o il canale */ /* errno==ENXIO oppure EINVAL */ } ove numero_canale ` un numero compreso tra 0 e SOUND_MIXER_NRDEVICES-1, e oppure il nome mnemonico del canale. Al posto di MIXER_READ(numero_canale) si pu` utilizzare anche SOUND_MIXER_READ_****, con **** nome del canale; ad o esempio, per il microfono ` SOUND_MIXER_READ_MIC. e Il volume ` codificato come in figura 2.1: se il dispositivo ` stereo la codifica dei e e canali destro e sinistro si trova nella parola meno significativa di volume (intero a 32 bit); i 16 bit della parola pi` significativa sono indefiniti e dovrebbero essere u ignorati. Nel byte pi` significativo della LSW c’` il volume del canale destro, nel u e byte meno significativo il volume del canale sinistro; per i canali mono ` valido e solo il byte meno significativo, essendo l’MSB posto uguale all’LSB dal driver. 31 16 15 8 7 0 XXXXXXXX Destro Sinistro MSW MSB LSB (da ignorare) LSW Figura 2.1: Rappresentazione del volume di un canale stereo 28
  • 34.
    CAPITOLO 2. ILMIXER E LA GESTIONE DEI CANALI 2.4. DIPENDENZA DALL’HARDWARE Per estrarre il volume dei canali destro e sinistro si possono sfruttare le seguenti linee di codice: int volume, vol_sinistro, vol_destro; vol_sinistro = volume & 0x000000ff; vol_destro = (volume & 0x0000ff00) >> 8; Inversamente, per costituire una parola di volume: int volume; volume = (vol_destro << 8) | vol_sinistro; Per cambiare il volume si utilizza il seguente frammento di codice: int volume = <valore>; if ((ioctl(fd, MIXER_WRITE(numero_canale), &volume) == -1) { /* Non c’e’ il mixer o il canale */ /* errno==ENXIO oppure EINVAL */ } ove numero_canale ` un numero compreso tra 0 e SOUND_MIXER_NRDEVICES-1, e oppure il nome mnemonico del canale. Al posto di MIXER_WRITE(numero_canale) si pu` utilizzare anche SOUND_MIXER_WRITE_****, con **** nome del canale; ad o esempio, per il CD ` SOUND_MIXER_WRITE_CD. e Dopo il cambiamento bisognerebbe verificare se il livello ritornato in volume risulta di proprio gradimento, dal momento che ` di solito pi` piccolo di quanto e u richiesto; sequenze di scrittura/lettura ripetute (senza cambiare tale variabile) ` possono portare al suo azzeramento. E conveniente effettuare la predisposizione del volume durante l’inizializzazione del programma, ignorando poi il volume ritornato in seguito. 2.4 Dipendenza dall’hardware Per ottenere dal mixer il nome della scheda audio si pu` utilizzare il seguente o frammento di codice: mixer_info info; if ((ioctl(fd, SOUND_MIXER_INFO, &info) == -1) { /* Non c’e’ il mixer - errno==ENXIO */ } ove mixer_info ` una struct cos` composta: e ı char id[16 ] identificatore della scheda audio (in genere un paio di caratteri) char name[32 ] nome per esteso della scheda Ecco un output di esempio per SOUND_MIXER_INFO con una scheda Sound Blaster 16: SB Sound Blaster 29
  • 35.
    2.5. NUOVE CARATTERISTICHE CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI 2.5 Nuove caratteristiche In questa Sezione ci si occuper` di descrivere le nuove caratteristiche di OSS a riguardanti il mixer introdotte dalla versione 3.6 in poi. • ` stata implementata la possibilit` di gestire dei canali in pi` : e a u Nomi dei canali Bitmask associate Descrizione SOUND_MIXER_DIGITAL1 SOUND_MASK_DIGITAL1 Ingresso digitale 1 SOUND_MIXER_DIGITAL2 SOUND_MASK_DIGITAL2 Ingresso digitale 2 SOUND_MIXER_DIGITAL3 SOUND_MASK_DIGITAL3 Ingresso digitale 3 SOUND_MIXER_PHONEIN SOUND_MASK_PHONEIN Ingresso livello fono SOUND_MIXER_PHONEOUT SOUND_MASK_PHONEOUT Uscita livello fono SOUND_MIXER_VIDEO SOUND_MASK_VIDEO Ingresso audio per video/TV SOUND_MIXER_RADIO SOUND_MASK_RADIO Ingresso radio SOUND_MIXER_MONITOR SOUND_MASK_MONITOR Volume monitor (di solito il microfono) • le macro SOUND_DEVICE_LABELS e SOUND_DEVICE_NAMES risultano di con- seguenza arricchite rispettivamente delle etichette e dei nomi da linea di comando dei nuovi canali: #define SOUND_DEVICE_LABELS {"Vol ", "Bass ", "Trebl", "Synth", "Pcm ", "Spkr ", "Line ", "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", "Line1", "Line2", "Line3", "Digital1", "Digital2", "Digital3", "PhoneIn", "PhoneOut", "Video", "Radio", "Monitor"} #define SOUND_DEVICE_NAMES {"vol", "bass", "treble", "synth", "pcm", "speaker", "line", "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", "line1", "line2", "line3", "dig1", "dig2", "dig3", "phin", "phout", "video", "radio", "monitor"} • ` stata modificata mixer_info; la sua nuova struttura ` la seguente: e e char id[16 ] identificatore della scheda audio (in genere un paio di caratteri) char name[32 ] nome per esteso della scheda int modify counter numero di modifiche int fillers[10 ] “riempitivi” per evoluzioni future 30
  • 36.
    CAPITOLO 2. ILMIXER E LA GESTIONE DEI CANALI 2.6. ESEMPIO DI PROGRAMMA la vecchia struct mixer_info ` stata rinominata _old_mixer_info e ad e essa si accede con la chiamata SOUND_OLD_MIXER_INFO • le nuove chiamate SOUND_MIXER_GETLEVELS e SOUND_MIXER_SETLEVELS ser- vono rispettivamente per interrogare il driver sulle predisposizioni dei livelli di volume di default e per impostare dei nuovi livelli subito dopo open(), prima di attivare il driver con read()/write(); esse sono definite solo per uso interno degli sviluppatori del codice del driver, di conseguenza i normali programmatori dovrebbero astenersi dal loro utilizzo: mixer_vol_table voltab; if (ioctl(mixfd, SOUND_MIXER_****, &voltab) == -1) { /* Non c’e’ il mixer o il canale */ /* errno==ENXIO oppure EINVAL */ } ove la struct mixer_vol_table ` cos` definita: e ı int num indice alla tabella dei volumi char name[32 ] nome mixer int levels[32 ] livelli di volume in percentuale 2.6 Esempio di programma Il programma seguente implementa un mixer molto semplice: se il comando mixer ` dato da linea di comando senza argomenti si limita a listare i canali della scheda e audio disponibili, riferendo se un canale ` campionabile, se ` selezionato, se ` e e e stereo e i livelli di volume in percentuale. Se il comando ` dato nella forma mixer <canale> [livSx] [livDx] senza e specificare i livelli ` reso attivo <canale> per il campionamento (se era gi` attivo e a lo deseleziona); se ` fornito anche il livello di volume in percentuale livSx l’altro ` e e posto uguale per i canali stereo, altrimenti il primo si riferisce al canale sinistro e il secondo al canale destro. Per un canale mono l’eventuale livDx verr` ignorato. a /* * mixer.c - Implementa un mixer command-line (solo /dev/mixer) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/soundcard.h> 31
  • 37.
    2.6. ESEMPIO DIPROGRAMMA CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI /* Indici per la matrice caratteristiche[][] */ const int NCANALE = 0; const int CAMPIONABILE = 1; const int CAN_CAMP_ATT = 2; const int CAN_STEREO = 3; const int VOL_SINISTRO = 4; const int VOL_DESTRO = 5; /* Qualche funzione utile per la stampa delle caratteristiche */ void SI_NO(int si) { si ? printf(" SI ") : printf(" NO "); } void STEREO_MONO(int stereo, int vol_sx, int vol_dx) { stereo ? printf("Stereo %d %dn", vol_sx, vol_dx) : printf(" Mono %dn", vol_sx); } void errore(const char *msgerr) /* Gestione degli errori */ { perror(msgerr); exit(-1); } int limita(int valore) /* Limita valore fra 0 e 100 */ { valore = (valore < 0) ? 0 : valore; return (valore > 100) ? 100 : valore; } int main(int argc, char *argv[]) { int mixfd; mixer_info info; int canale, volume, num_canali = 0, num_camp = 0; int c_bmask, dev_bmask, canst_bmask, camp_bmask, catt_bmask; int caratteristiche[SOUND_MIXER_NRDEVICES][6]; const char *nome[] = SOUND_DEVICE_LABELS; /* Nomi dei canali */ const char *cm_nome[] = SOUND_DEVICE_NAMES; if ((mixfd = open("/dev/mixer", O_RDONLY)) == -1) errore("/dev/mixer"); 32
  • 38.
    CAPITOLO 2. ILMIXER E LA GESTIONE DEI CANALI 2.6. ESEMPIO DI PROGRAMMA /* Dopo l’apertura del device, raccolta di informazioni */ if (ioctl(mixfd, SOUND_MIXER_INFO, &info) == -1) errore("SOUND_MIXER_INFO"); if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &dev_bmask) == -1) errore("SOUND_MIXER_READ_DEVMASK"); if (ioctl(mixfd, SOUND_MIXER_READ_STEREODEVS, &canst_bmask) == -1) errore("SOUND_MIXER_READ_STEREODEVS"); if (ioctl(mixfd, SOUND_MIXER_READ_RECMASK, &camp_bmask) == -1) errore("SOUND_MIXER_READ_RECMASK"); if (ioctl(mixfd, SOUND_MIXER_READ_RECSRC, &catt_bmask) == -1) errore("SOUND_MIXER_READ_RECSRC"); for (canale=0; canale<SOUND_MIXER_NRDEVICES; canale++) { c_bmask = 1 << canale; if (dev_bmask & c_bmask) { /* Canale presente? */ if (ioctl(mixfd, MIXER_READ(canale), &volume) == -1) errore("MIXER_READ(canale)"); caratteristiche[num_canali][NCANALE] = canale; caratteristiche[num_canali][CAMPIONABILE] = camp_bmask & c_bmask; caratteristiche[num_canali][CAN_CAMP_ATT] = catt_bmask & c_bmask; caratteristiche[num_canali][CAN_STEREO] = canst_bmask & c_bmask; caratteristiche[num_canali][VOL_SINISTRO] = volume & 0x000000ff; caratteristiche[num_canali][VOL_DESTRO] = (volume & 0x0000ff00) >> 8; if (caratteristiche[num_canali][CAMPIONABILE]) num_camp++; /* Numero dei canali campionabili */ num_canali++; } } /* In base al numero di argomenti varia il comportamento del mixer */ switch (argc) { /* mixer invocato senza argomenti - visualizza informazioni */ case 1: if (ioctl(mixfd, SOUND_MIXER_READ_CAPS, &c_bmask) == -1) errore("SOUND_MIXER_READ_CAPS"); printf("nScheda %s, %d canali campionabili ", info.name,num_camp); (c_bmask & SOUND_CAP_EXCL_INPUT) ? puts("in mutua esclusione.") : puts("simultaneamente."); printf("nCanale Nome Campionabile " "Selezionato Tipo VolSx VolDxn"); printf("--------------------------------" "--------------------------------n"); for (canale=0; canale<num_canali; canale++) { printf("%-9.9s", nome[caratteristiche[canale][NCANALE]]); printf("%-9.9s", cm_nome[caratteristiche[canale][NCANALE]]); SI_NO(caratteristiche[canale][CAMPIONABILE]); SI_NO(caratteristiche[canale][CAN_CAMP_ATT]); 33
  • 39.
    2.6. ESEMPIO DIPROGRAMMA CAPITOLO 2. IL MIXER E LA GESTIONE DEI CANALI STEREO_MONO(caratteristiche[canale][CAN_STEREO], caratteristiche[canale][VOL_SINISTRO], caratteristiche[canale][VOL_DESTRO]); } break; /* mixer invocato solo col nome del canale */ case 2: canale = 0; do { /* Ricerca il nome del canale fra quelli disponibili */ if (!strcmp(argv[1], cm_nome[caratteristiche[canale][NCANALE]])) break; } while (canale++ < num_canali-1); if (canale == num_canali) /* Nome non trovato */ puts("Canale non disponibile."); else { /* Nome trovato */ if (!caratteristiche[canale][CAMPIONABILE]) { puts("Il canale non e’ fra quelli campionabili."); break; /* Per selezionarlo deve essere campionabile */ } c_bmask = 1 << caratteristiche[canale][NCANALE]; catt_bmask ^= c_bmask; /* Setta o resetta il bit */ if (ioctl(mixfd, SOUND_MIXER_WRITE_RECSRC, &catt_bmask) == -1) errore("SOUND_MIXER_WRITE_RECSRC"); /* catt_bmask variata */ printf("%s ", cm_nome[canale]); (catt_bmask & c_bmask) ? puts("selezionato.") : puts("deselezionato."); } break; /* mixer invocato col nome del canale e uno o due livelli di volume */ case 3: case 4: canale = 0; do { /* Ricerca il nome del canale fra quelli disponibili */ if (!strcmp(argv[1], cm_nome[caratteristiche[canale][NCANALE]])) break; } while (canale++ < num_canali-1); if (canale == num_canali) /* Nome non trovato */ puts("Canale non disponibile."); else { /* Nome trovato */ volume = limita(atoi(argv[2])); if ((argc == 4) && caratteristiche[canale][CAN_STEREO]) volume |= limita(atoi(argv[3])) << 8; /* Se stereo */ else volume |= volume << 8; /* Se 2 argomenti o mono */ if (ioctl(mixfd, MIXER_WRITE(canale), &volume) == -1) errore("MIXER_WRITE(canale)"); printf("Volume di %s a ", argv[1]); /* Stampa nuovi volumi */ 34
  • 40.
    CAPITOLO 2. ILMIXER E LA GESTIONE DEI CANALI 2.6. ESEMPIO DI PROGRAMMA if (caratteristiche[canale][CAN_STEREO]) printf("%d% / %d%n", volume & 0x000000ff, (volume & 0x0000ff00) >> 8); else printf("%d%n", volume & 0x000000ff); } break; /* Numero sbagliato di argomenti */ default: printf("uso: %s <canale> [livSx%] [livDx%]n", argv[0]); } close(mixfd); return 0; } Di seguito ` riportato un esempio di utilizzo del programma: e $ mixer ? Canale non disponibile. $ mixer ? ? ? ? ? uso: mixer <canale> [livSx%] [livDx%] $ mixer Scheda Sound Blaster, 4 canali campionabili simultaneamente. Canale Nome Campionabile Selezionato Tipo VolSx VolDx ---------------------------------------------------------------- Vol vol NO NO Stereo 75 75 Bass bass NO NO Stereo 75 75 Trebl treble NO NO Stereo 75 75 Synth synth SI NO Stereo 75 75 Pcm pcm NO NO Stereo 75 75 Spkr speaker NO NO Mono 75 Line line SI NO Stereo 75 75 Mic mic SI SI Mono 16 CD cd SI SI Stereo 75 75 IGain igain NO NO Stereo 75 75 OGain ogain NO NO Stereo 75 75 $ mixer cd cd deselezionato. $ mixer mic 0 Volume di mic a 0% $ mixer cd 75 40 Volume di cd a 75% / 40% $ mixer cd 80 Volume di cd a 80% / 80% $ mixer cd cd selezionato. 35
  • 41.
    Capitolo 3 CAMPIONAMENTO E RIPRODUZIONE 3.1 I device file audio Nel Capitolo precedente si ` appreso come selezionare un canale e impostarne il e livello di volume per il campionamento e la riproduzione. L’interfaccia di accesso al DSP ` rappresentata dai seguenti device file (possono essere link simbolici): e /dev/dsp codifica 8 bit unsigned /dev/dspW codifica 16 bit signed /dev/audio codifica 8 bit µ–Law la frequenza di campionamento predefinita ` di 8 kHz/mono (per /dev/audio e ` l’unica possibile), il canale predefinito ` il microfono (con le solite avvertenze e e riguardo i parametri di default). Un metodo alternativo ` di procedere predispo- e nendo il formato opportuno per i campioni tramite ioctl() su /dev/dsp, ma l’uso di uno di questi device file equivale a sceglierlo automaticamente. I device file dovrebbero essere aperti in read–only (O_RDONLY) o in write– only (O_WRONLY) per motivi di efficienza di OSS; per le schede audio che non supportano il full duplex aprire in read–only il device file, richiudere e riaprire in write–only, e cos` via, ` pi` efficiente dell’apertura in read–write (O_RDWR). ı e u 3.2 Il buffer audio Il campionamento effettuato da una scheda audio ` uniforme; il risultato ` una e e sequenza di campioni (ampiezze del segnale in ingresso al canale negli istanti di campionamento, opportunamente codificate), che costituisce una quantit` di byte a 36
  • 42.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.3. PARAMETRI DI CAMPIONAMENTO dipendente dalla risoluzione di campionamento (8 o 16 bit), dal numero di canali coinvolti1 (mono o stereo) e dalla frequenza di campionamento. In generale, bisogna predisporre un opportuno buffer per ospitare i campioni da riprodurre o letti da un canale; un esempio di codice per dichiarare un buffer di dimensione DIM_BUF ` il seguente: e const int DIM_BUF = 4096; unsigned char audio_buffer[DIM_BUF]; ove si presupponga di utilizzare /dev/dsp o /dev/audio, poich´ il buffer ` di e e unsigned char. ` E possibile ridurre l’overhead nell’I/O aumentando DIM_BUF, quindi passando pi` campioni nelle write() o richiedendone di pi` nelle read(). Buffer pi` corti u u u danno migliori risultati nel caso di elaborazione simultanea al campionamento dei dati: per un uso “normale”2 una dimensione indicativa per DIM_BUF ` compresae fra 1024 e 4096 per un campionamento mono a 8 bit (tali limiti raddoppiano in caso di campionamento a 16 bit e quadruplicano se ` anche stereo). e Se il campionamento ` da un canale stereo i campioni per ambo i canali si e presentano alternati in sequenza nel buffer con prima il canale sinistro (L) del destro (R): LRLRLRLRLRLR. . . , ove L o R avranno dimensione di un byte se il campionamento ` a 8 bit o di due byte se il campionamento ` a 16 bit. Quanto e e detto fino ad ora ` da tenere presente anche nel caso di riproduzione di campioni, e per cui il buffer deve essere riempito con le stesse regole. Si deve decidere se in un’applicazione sia effettivamente il caso di campionare a 16 bit invece che a 8 bit: in generale ci sono molti casi in cui i primi non portano a un miglioramento evidente della qualit` audio, a fronte invece di un raddoppio a dei dati trasferiti dal DMA nel buffer. Un modo per vedere quali dei due sia meglio utilizzare (oltre l’ascolto diretto) ` di guardare i bit meno significativi e dei campioni: se pi` di quattro sono sempre nulli, il campionamento a 8 bit ` u e preferibile a quello a 16 bit. 3.3 Parametri di campionamento Il default per OSS ` un campionamento a 8kHz/mono/8 bit, ma ` ovvio che possa e e non essere soddisfacente per tutte le applicazioni. I parametri di campionamento e riproduzione possono essere cambiati con delle opportune chiamate a ioctl() dopo l’apertura del device audio, ma prima di qualsiasi read() o write() a quest’ultimo, altrimenti il comportamento della scheda audio ` indefinito. Inoltre e i parametri devono essere predisposti nel seguente ordine: • risoluzione di campionamento (8 o 16 bit) 1 La prossima generazione di schede audio (1999) sar` a 24 bit, con 4 o 6 canali di uscita per a il surround; la massima frequenza di campionamento ` di 96 kHz (Ultra Hi–Fidelity) e 2 Il normale ` qui inteso nel senso di utilizzare una frequenza di campionamento relativamente e bassa, ove il programma non abbia stringenti requisiti di elaborazione in tempo reale 37
  • 43.
    3.3. PARAMETRI DICAMPIONAMENTO CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE • numero di canali (1 per mono, 2 per stereo) • frequenza di campionamento (in Hz) utilizzando il seguente frammento di codice: int parametro = <valore>; ioctl(dspfd, SNDCTL_DSP_****, &parametro); if ( /* parametro e’ molto diverso da <valore> */) { /* la scheda audio non puo’ supportare */ /* il valore richiesto per parametro */ } infatti il driver ritorna in parametro il valore che pu` supportare rispetto a quanto o richiesto; SNDCTL_DSP_**** ` uno dei seguenti: e Comandi Alias Per cambiare. . . SNDCTL_DSP_SAMPLESIZE SOUND_PCM_WRITE_BITS Risoluzione (8, 16) SNDCTL_DSP_SETFMT SOUND_PCM_SETFMT SNDCTL_DSP_CHANNELS SOUND_PCM_WRITE_CHANNELS Numero di canali (1, 2) SNDCTL_DSP_SPEED SOUND_PCM_WRITE_RATE Frequenza (Hz) per scegliere se il campionamento deve essere mono o stereo esiste anche un altro modo: si usa SNDCTL_DSP_STEREO, con parametro posto a 0 se si vuole mono o a 1 se si vuole stereo. Al solito la ioctl() restituisce -1 e cambia errno se non ` stata in grado di e portare a termine l’azione richiesta. Il programmatore dovrebbe inoltre verificare se parametro, dopo la chiamata a ioctl(), soddisfa i requisiti voluti3 . Per interrogare il driver riguardo il valore per un parametro di campionamento, con significato analogo ai relativi comandi di predisposizione, si pu` usare il o seguente frammento di codice: int parametro; ioctl(dspfd, SOUND_PCM_READ_****, &parametro); ove SOUND_PCM_READ_**** ` uno fra i seguenti comandi: SOUND_PCM_READ_BITS, e SOUND_PCM_READ_CHANNELS e SOUND_PCM_READ_RATE. 3.3.1 Reset dei parametri Dopo la prima read() o write() i parametri di campionamento non sono pi` u modificabili a meno di resettare il DSP interno alla scheda; il metodo per comu- nicare a OSS quest’azione ` il seguente: e 3 Ad esempio, il divisore del clock interno alla scheda audio pu` non supportare una certa o frequenza di campionamento, per cui il driver ne sceglier` una il pi` vicino possibile; possono a u non essere supportati anche il campionamento a 16 bit o i canali stereo per le schede pi` vecchie u 38
  • 44.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.4. IL CAMPIONAMENTO ioctl(dspfd, SNDCTL_DSP_****); ove SNDCTL_DSP_**** ` uno fra i comandi: e Comandi Alias Azione SNDCTL_DSP_SYNC SOUND_PCM_SYNC Svuota i buffer e resetta SNDCTL_DSP_RESET SOUND_PCM_RESET Resetta senza svuotare i buffer Il primo comando fa attendere l’applicazione finch´ l’ultimo byte scritto sul e device ` stato riprodotto (il che pu` richiedere diversi secondi, in funzione della e o quantit` di campioni nel buffer non ancora riprodotti); dopo il controllo ritorna a al programma chiamante. SNDCTL_DSP_SYNC ` invocata automaticamente dalla e close() del device audio. Il secondo comando ferma immediatamente il device, ponendolo nello stato in cui pu` essere ripredisposto, se in fase di riproduzione; in campionamento, dopo o l’ultima read(), ` utile se non si ha intenzione di chiudere immediatamente il e device, in modo che si prevenga il driver dalla visualizzazione di un messaggio di errore non necessario riguardo la condizione di overrun in campionamento. Entrambe queste chiamate possono essere utilizzate per passare “al volo” da campionamento a riproduzione; in tal caso il device audio dovr` essere aperto a con O_RDWR. Si ricorda tuttavia, per le schede audio che non supportano il full duplex (vedi Sezione 3.8), che per passare da campionamento a riproduzione una gestione pi` efficiente del buffer di I/O ` ottenuta chiudendo il device (che era u e aperto in modalit` O_RDONLY) e riaprendolo nella modalit` O_WRONLY, e viceversa. a a 3.3.2 Pause nelle operazioni Qualora sia necessario effettuare una pausa relativamente lunga nell’output con- tinuo dei campioni, ` possibile “informare” di ci` il driver affinch´ la gestione e o e di quest’evento sia effettuata in maniera pi` intelligente; ci` si effettua con il u o seguente comando: ioctl(dspfd, SNDCTL_DSP_POST); (l’alias ` SOUND_PCM_POST) che ` una versione pi` leggera di SNDCTL_DSP_SYNC. e e u Le circostanze nelle quali questo comando risulta utile sono quelle in cui si ` e riprodotta una serie di campioni (come un effetto sonoro in un gioco) e non si vuole riprodurre immediatamente un’altra serie, oppure prima di iniziare operazioni molto lunghe (come l’I/O da tastiera o da disco). Si veda la Sezione 3.10 per ulteriori informazioni su quest’argomento. 3.4 Il campionamento Per campionare da un certo canale (che si suppone sia stato precedentemente selezionato tramite il mixer, nonch´ di cui sia stato fissato un certo livello di e campionamento) si pu` usare il seguente frammento di codice: o 39
  • 45.
    3.5. LA RIPRODUZIONE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE int num_ric = <valore>, num_camp; if ((num_camp = read(dspfd, audio_buffer, num_ric)) == -1) errore("read()"); ove num_camp ` il numero di byte che vengono effettivamente campionati rispetto e al numero richiesto num_ric; quest’ultimo dev’essere un multiplo intero della dimensione in byte del campione (ci` fa funzionare meglio il buffer interno usato o n da OSS): num_ric = 2 , con num_ric <= sizeof(audio_buffer). Per un device file audio in campionamento (aperto con O_RDONLY o O_RDWR) non esiste condizione di End Of File (EOF), per cui se read() ritorna -1 ` pos- e sibile che ci sia un problema hardware permanente o che il programma abbia tentato di fare qualcosa di impossibile; per rimuovere la condizione d’errore a volte basta chiudere e riaprire il device file. Nella sintesi in tempo reale si ha la necessit` di dover creare un algoritmo il pi` a u efficiente possibile; ci` spesso va a collidere con il tipo di codice sopra e in tal caso ` o e meglio che non venga effettuato alcun tipo di controllo sulla quantit` di byte ritor- a nati da read(), riducendo il tutto a read(dspfd, audio_buffer, num_ric);. Il campionamento risulta inoltre pi` efficiente se ` letta una quantit` di byte pari u e a alla dimensione di un frammento (vedi le Sezioni 3.7.2 e 3.7.3). A condizione che il processo campionante non venga posto in stato di wait per periodi di tempo relativamente lunghi, il numero di byte campionati pu` essere o utilizzato per misurare il tempo trascorso durante il campionamento in maniera abbastanza precisa: infatti il data rate (byte/secondo) dipende dalla frequenza di campionamento fc (in Hz), dalla dimensione del campione dc (1 o 2 byte) e dal numero di canali utilizzati c (1 se mono, 2 se stereo), da cui: o no byte campionati n byte campionati = fc · dc · c · tempo ⇒ tempo = fc · dc · c ad esempio, campionare 1024 byte a 8 kHz, stereo (2 canali) e a 16 bit (2 1024 byte/campione) equivale a un tempo trascorso di circa: 8000·2·2 = 32ms. 3.5 La riproduzione Per riprodurre i campioni presenti nel buffer audio (su Line–Out o in cuffia, eventualmente avendo precedentemente regolato i livelli di volume e i controlli di tono tramite il mixer) si pu` far uso del seguente frammento di codice: o int num_ric = <valore>, num_ripr; if ((num_ripr = write(dspfd, audio_buffer, num_ric)) == -1) errore("write()"); ove num_ric ` la quantit` di byte da scrivere sul device audio (aperto in O_WRONLY e a o O_RDWR) e num_ripr ` la quantit` effettivamente scritta. e a Le considerazioni da fare per la riproduzione sono del tutto analoghe a quelle gi` fatte per quanto riguarda il campionamento. a 40
  • 46.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.6. IL FORMATO AUDIO 3.6 Il formato audio Per formato audio si intende la codifica con la quale i campioni sono rappresentati; ` un parametro che ha diretta conseguenza sia sulla qualit` del segnale audio e a campionato o riprodotto che sul data rate. Scegliendo il formato in pratica si scelgono la risoluzione del campione (numero di bit), il tipo di legge con cui lo si rappresenta (lineare, logaritmica A–Law o µ–Law, IMA ADPCM4 ) e la sua codifica nel buffer audio (signed o unsigned, little o big endian); per predisporlo si pu` usare il seguente codice: o int formato = <formato audio>; ioctl(dspfd, SNDCTL_DSP_SETFMT, &formato); if (formato != <formato audio>) { /* Il formato audio non e’ supportato dall’hardware */ /* o dal driver in software */ } SOUND_PCM_SETFMT ` un alias di SNDCTL_DSP_SETFMT; <formato audio> ` uno e e dei seguenti: Identificatore No bit Formato audio AFMT_MU_LAW 8 µ–Law (logaritmica) AFMT_A_LAW 8 A–Law (logaritmica) AFMT_IMA_ADPCM ≈4 campioni a 16 bit compressi mediamente 4:1 AFMT_U8 8 lineare unsigned AFMT_S16_LE 16 lineare signed little endian AFMT_S16_BE 16 lineare signed big endian AFMT_S8 8 lineare signed AFMT_U16_LE 16 lineare unsigned little endian AFMT_U16_BE 16 lineare unsigned big endian AFMT_MPEG ? audio MPEG 2 Per interrogare OSS sul formato audio attualmente in uso si pu` usare: o int formato = AFMT_QUERY; ioctl(dspfd, SNDCTL_DSP_SETFMT, &formato); in formato ` ritornato l’identificatore relativo. e Se il formato che si vuole utilizzare non ` supportato dalla scheda audio o e dal driver si pu` decidere di uscire dal programma, si pu` utilizzare il formato o o ritornato da OSS in formato (` quello che pi` si avvicina a quanto richiesto) o e u si pu` eseguire una conversione all’interno del proprio programma. Quest’ultima o opzione aggiunge dell’overhead sulla CPU, come l’emulazione in software di un formato da parte del driver, per cui sarebbe meglio utilizzare i formati diretta- mente supportati in hardware quanto pi` ` possibile; per sapere quali questi siano ue si pu` utilizzare il seguente frammento di codice: o 4 IMA ` l’acronimo di Interactive Multimedia Association; il formato ADPCM da questa e proposto ` incompatibile con quello supportato dalla Sound Blaster 16 e 41
  • 47.
    3.6. IL FORMATOAUDIO CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE int formato; ioctl(dspfd, SNDCTL_DSP_GETFMTS, &formato); if (formato & <formato audio>) { /* La scheda audio supporta il formato */ } ove <formato audio> ` uno degli identificatori della tabella precedente; la chia- e mata SOUND_PCM_GETFMTS ` un alias di SNDCTL_DSP_GETFMTS. e ` bene tenere presente una cosa: la riproduzione di un formato non supportato E consiste in genere solo in rumore ad alto volume, che pu` danneggiare altoparlanti o e orecchie. 3.6.1 Little e big endian Con il campionamento a 16 bit bisogna fare attenzione, in quanto la rappresen- tazione del campione non ` portabile. Per molti microprocessori e schede audio e ` in little endian (ad esempio, per un processore della famiglia Intel x86 e una e Sound Blaster compatibile), il che non crea problemi; per altri processori ` bige endian (PowerPC, Sparc, HP–PA), per cui ` possibile avere una scheda audio e che codifica i campioni in little endian su una macchina big endian: la ripro- duzione di campioni in tali situazioni “miste” conduce allo stesso risultato che si ha riproducendo un formato non supportato. In generale, siccome si ` gi` visto come verificare il supporto dei formati per e a quanto riguarda il driver, sorge la necessit` di individuare che tipo di architettura a sia quella per la quale si sta compilando il proprio sorgente, little o big endian: • se si sta utilizzando come compilatore il gcc (il che ` molto probabile se si e ha Linux, ma non ` detto con altri sistemi operativi), nella distribuzione e di questo ` incluso l’header file endian.h, il quale definisce il tipo di ar- e chitettura della macchina sulla quale si sta compilando; il suo utilizzo ` il e seguente: #ifdef __GLIBC__ /* con la GNU C library */ #include <endian.h> #endif #if __BYTE_ORDER == __LITTLE_ENDIAN /* Architettura little endian */ #else /* Architettura big endian */ #endif • si pu` creare una funzione come quella che segue: o int isLittleEndian() /* Ritorna TRUE se little endian */ { 42
  • 48.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.6. IL FORMATO AUDIO int numero = 0x12345678; char *pnumero = (char *)&numero; return(*pnumero == 0x78); } 3.6.2 La codifica lineare Dal campionamento di un segnale audio (supposto per il momento mono/8 bit) si ottiene una sequenza di numeri rappresentante i livelli del segnale negli istanti di campionamento. Nel formato AFMT_U85 tali ampiezze sono codificate come unsigned char: ci` implica che il livello zero ` a 128, il massimo a 255 e il o e minimo a 0. Per convertire in signed char si pu` usare: o unsigned char campione_unsigned = <valore unsigned>; signed char campione_signed = campione_unsigned ^ 128; per fare la trasformazione inversa: signed char campione_signed = <valore signed>; unsigned char campione_unsigned = campione_signed ^ 128; sono operazioni equivalenti rispettivamente sommare 128 a campione_unsigned e sottrarre 128 a campione_signed. I campioni a 16 bit sono attualmente rappresentabili come signed short (nei PC, ma ci` potrebbe non essere pi` vero su altre macchine o in futuro con architet- o u ture a 64 bit); nel caso si volesse una rappresentazione come unsigned short si dovrebbe fare: signed short campione_signed = <valore signed>; unsigned short campione_unsigned = campione_signed ^ 0x00008000; per fare la trasformazione inversa: unsigned short campione_unsigned = <valore unsigned>; signed short campione_signed = campione_unsigned ^ 0x00008000; sono operazioni equivalenti rispettivamente sommare 32768 a campione_unsigned e sottrarre 32768 a campione_signed. Nel caso un algoritmo di sintesi abbia prodotto una sequenza di campioni a 16 bit (che si supporranno short), bisogna porli nel buffer audio con la codifica AFMT_S16_LE; un esempio di codice che fa ci`, supponendo il buffer stereo ` il o e seguente: 5 Questo ` il formato “standard Sound Blaster”, nel senso che la maggior parte delle schede e audio lo supporta in hardware di default; altre schede supportano solo il formato a 16 bit 43
  • 49.
    3.7. IL TEMPOREALE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE short campioni[DIM_BUF]; /* Campioni sintetizzati */ unsigned char audio_buffer[DIM_BUF * 2]; /* Buffer audio */ int i, j = 0; /* Calcola i campioni e li mette in campioni[], */ /* disposti LRLRLR... se il buffer e’ stereo */ for (i=0; i<DIM_BUF; i++) { audio_buffer[j++] = (unsigned char)(campioni[i] & 0x00ff); audio_buffer[j++] = (unsigned char)((campioni[i] >> 8) & 0x00ff); } write(dspfd, audio_buffer, DIM_BUF * 2); /* Riproduzione */ ove DIM_BUF ` una dimensione per il buffer secondo i suggerimenti della Sezione 3.2. e Uno stralcio di codice che compie l’operazione opposta ` invece: e read(dspfd, audio_buffer, DIM_BUF * 2); /* Campionamento */ j = 0; for (i=0; i<DIM_BUF; i++) campioni[i] = audio_buffer[j++]|((audio_buffer[j++]<<8)&0xff00); i campioni si ritrovano disposti LRLRLR. . . in campioni[] se il campionamento ` stereo. e 3.7 Il tempo reale In questo contesto per “sistema in tempo reale” s’intende un elaboratore con capacit` di acquisire tramite il dispositivo audio dei dati (campioni o eventi MI- a DI), di compiere delle elaborazioni in base a questi e intraprendere delle azioni (ad esempio, riproduzione di campioni o output di eventi MIDI) in un tempo prevedibile (ancorch´ breve). e Linux ` strutturato abbastanza bene da avere tempi di reazione adeguata- e mente corti per quanto riguarda le capacit` di I/O6 , anche se esistono altri tipi a di sistemi operativi studiati appositamente per i compiti in tempo reale. In campo musicale anche ritardi di poche decine di millisecondi sono avvertibili da un musicista che sta suonando, il che falsa le sensazioni che ha in ritorno dal suo strumento (in questo caso il sistema elaboratore pi` dispositivo di input MIDI), u rendendolo probabilmente “fastidioso” dal punto di vista esecutivo. Al di l` di fare in modo che non ci siano troppi processi che competano per a la CPU, per migliorare la risposta del sistema si possono implementare delle opportune strategie nell’utilizzo della libreria di OSS, in modo tale che il processo 6` E in fase di sviluppo una variante del kernel di Linux meglio strutturata per le attivit` in a tempo reale 44
  • 50.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.7. IL TEMPO REALE che ne sfrutta i servizi non sia frequentemente bloccato e possa massimizzare l’efficienza del suo algoritmo; si vedr` anche come in un certo caso sia possibile a mappare il buffer DMA nel buffer del processo per evitare operazioni di copia dei campioni fra l’uno e l’altro. 3.7.1 Le capacit` del driver a Molte delle caratteristiche in seguito introdotte non funzionano con tutte le schede audio, per cui prima di applicarle bisogna testare se sono supportate dal driver (il comportamento ` altrimenti indefinito). Ci` si pu` fare con il seguente frammento e o o di codice: int cap; ioctl(dspfd, SNDCTL_DSP_GETCAPS, &cap); if (cap & <id. capacita’>) { /* Il driver supporta tale capacita’ */ } SOUND_PCM_GETCAPS ` un alias di SNDCTL_DSP_GETCAPS; <id. capacita’> ` uno e e degli identificatori seguenti: Identificatore Capacit` di. . . a DSP_CAP_DUPLEX full duplex: bisogna abilitarlo prima, altrimenti il driver potrebbe ritornare che non ` supportato (vedi Sezione 3.8) e DSP_CAP_REALTIME tempo reale: l’hardware o Linux hanno una precisione attorno a un paio di campioni nel riportare la posizione del puntatore al buffer DMA in riproduzione usando SNDCTL_DSP_GETOPTR; altrimenti essa ` precisa e attorno a un frammento DSP_CAP_BATCH la scheda audio ha un buffer locale in campionamento e/o riproduzione: risulta inaccurata SNDCTL_DSP_GETxPTR DSP_CAP_COPROC coprocessore programmabile (potrebbe essere un DSP): attualmente questo bit ` riservato per uso futuro e DSP_CAP_TRIGGER triggering: sincronizzazione fra il campionamento e la riproduzione o fra audio e MIDI DSP_CAP_MMAP supporto di mmap(): accesso diretto al buffer DMA in riproduzione e/o campionamento Per determinare la versione di questa chiamata si usa: int versione = cap & DSP_CAP_REVISION; in versione dovrebbe essere ritornato un numero tra 0 e 255; attualmente ` e ritornato versione==1. 45
  • 51.
    3.7. IL TEMPOREALE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE 3.7.2 Gestione del buffer DMA Come ` stato visto nel Capitolo 1, il buffer DMA agisce dietro le quinte per e adattare la velocit` di campionamento/riproduzione all’I/O effettuato sui device a file dal processo che sfrutta i servizi di OSS. In base a ci` in casi “normali” non o ci si cura del numero di campioni scritti o letti, ma se ci sono stringenti requisiti di elaborazione in tempo reale, il driver pu` funzionare meglio se ` letto o scritto o e un frammento per volta. Per determinare la dimensione in byte di un frammento ` possibile usare il e codice seguente: int dim_frammento; ioctl(dspfd, SNDCTL_DSP_GETBLKSIZE, &dim_frammento); Questa chiamata calcola la dimensione del frammento nel caso non fosse stato fatto prima, per cui la si deve usare solo dopo aver predisposto i parametri di campionamento, o esplicitamente la dimensione dei frammenti (nonch´ il loro e numero nel buffer DMA) con la seguente: int frammento = 0xNNNNDDDD; ioctl(dspfd, SNDCTL_DSP_SETFRAGMENT, &frammento); ove frammento ha nella word pi` significativa (NNNN) il massimo numero di fram- u menti voluti nel buffer (minimo 2, massimo 0x7fff), mentre nella word meno significativa (DDDD) si ha codificato l’esponente a cui si deve elevare 2 per avere la dimensione in byte del frammento: min 16 byte (DDDD = 4) 2DDDD = max dim. totale buffer 2 in frammento il driver ritorna i valori che esso ` stato capace di fornire rispetto e a quanto richiesto. Esiste un’altra chiamata, oramai obsoleta rispetto alla precedente, con lo scopo di stabilire la dimensione/numero dei frammenti nel buffer DMA; ques- ta richiede come parametro di ioctl() un valore di divisore, che consente di predisporre la dimensione del frammento da un massimo di 4 kB a un minimo di 1 kB, se il kernel ` stato configurato per una dimensione del buffer DMA di e 64 kB (il numero dei frammenti varia di conseguenza da 16 a 64). La chiamata ha la forma: int div = <valore>; ioctl(dspfd, SNDCTL_DSP_SUBDIVIDE, &div); ove SOUND_PCM_SUBDIVIDE ` un alias di SNDCTL_DSP_SUBDIVIDE, mentre <valore> e pu` essere 1, 2, 4. o Il primo uso di read() o write() blocca il numero di frammenti nel buffer e la loro dimensione; per cambiarli ` necessario chiudere e riaprire il device audio, e 46
  • 52.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.7. IL TEMPO REALE per cui SNDCTL_DSP_SETFRAGMENT o SNDCTL_DSP_SUBDIVIDE dovrebbero essere poste subito dopo la open(). Non c’` un metodo universale per calcolare la dimensione di un frammento, e a parte la prova sul campo; nella maggior parte dei casi potrebbe essere utile la seguente formula empirica: numero canali · byte/campione · fc dim. frammento [byte] = eventi/s ove eventi/s ` il numero di eventi che bisognerebbe gestire nell’unit` di tempo: e a nella simulazione di uno strumento musicale questi potrebbero essere gli eventi MIDI, a cui bisogna prontamente rispondere con la sintesi dei campioni relativi, ad esempio, a un NOTE ON o a un NOTE OFF. Anche se con una macchina veloce si pu` scendere alla dimensione minima di 16 byte, un frammento non o dovrebbe essere cos` piccolo da rischiare di incorrere in condizioni di underrun: ı indicativamente con meno di 128 o 256 byte i frammenti diventano critici da gestire con una CPU lenta o con molti processi che competono per questa. Per ottenere informazioni sull’evoluzione della situazione di I/O nel buffer (frammenti e byte elaborati) si possono utilizzare le seguenti chiamate: audio_buf_info IO_info; ioctl(dspfd, SNDCTL_DSP_GETISPACE, &IO_info); per il buffer di input, e analogamente per il buffer di output: audio_buf_info IO_info; ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &IO_info); SOUND_PCM_GETISPACE e SOUND_PCM_GETOSPACE sono alias rispettivamente di SNDCTL_DSP_GETISPACE e SNDCTL_DSP_GETOSPACE; la struct audio_buf_info ` cos` composta: e ı int fragments numero di frammenti che possono essere letti/scritti senza bloc- care (sono esclusi i parzialmente riempiti); questo campo ` affidabile solo e se l’applicazione legge/scrive interi frammenti per volta int fragstotal numero totale di frammenti allocati per il buffer int fragsize dimensione di un frammento in byte (valore ritornato dalla chiama- ta SNDCTL_DSP_GETBLKSIZE) int bytes numero di byte che possono essere letti/scritti immediatamente sen- za bloccare, tenendo conto anche dei frammenti parzialmente riempiti; di conseguenza si pu` verificare che bytes>fragments*fragsize o queste chiamate possono essere sfruttate per scrivere applicazioni non bloccanti. La dimensione del buffer (secondo la chiamata, di input o di output) ` pari a e IO_info.fragstotal * IO_info.fragsize. 47
  • 53.
    3.7. IL TEMPOREALE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE Per avere informazioni sull’evoluzione dinamica dei puntatori DMA al buffer sono disponibili due chiamate con forma analoga alle SNDCTL_DSP_GETxSPACE; i valori ritornati hanno una precisione attorno a un paio di campioni in difetto, quando funzionano: infatti buffer locali nelle schede audio e imprecisioni nel- l’hardware possono contribuire a ridurre la precisione attorno a un frammento, e inoltre alcuni sistemi operativi non consentono di ottenere i valori dei puntatori DMA7 . Le chiamate sono: count_info cinfo; ioctl(dspfd, SNDCTL_DSP_GETIPTR, &cinfo); per il buffer di input, e analogamente per il buffer di output: count_info cinfo; ioctl(dspfd, SNDCTL_DSP_GETOPTR, &cinfo); ove le SOUND_PCM_GETIPTR e SOUND_PCM_GETOPTR sono alias rispettivamente di SNDCTL_DSP_GETIPTR e SNDCTL_DSP_GETOPTR; la struct count_info ` cos` com- e ı posta: int bytes numero di byte elaborati dall’apertura del device; ` un valore preciso e se ` preciso il valore determinabile del puntatore DMA e int blocks numero di transizioni di frammento (interrupt hardware) dalla prece- dente chiamata a SNDCTL_DSP_GETxPTR (il valore di questo campo ` azzer- e ato dopo quest’ultima ed ` valido solo se si usa l’accesso diretto al buffer e DMA); potrebbe essere usato per rilevare condizioni di underrun int ptr puntatore in byte alla posizione corrente nel buffer DMA dal suo inizio; nel caso non sia possibile accedere al puntatore DMA attuale questo valore ` troncato al bordo del frammento e bytes · 1000 fornisce un timer (in ms) abbastanza preciso, numero canali·byte/campione fc in teoria: fatte salve le considerazioni riguardo alla disponibilit` dei puntatori a DMA, la precisione ` compromessa dal verificarsi di condizioni di overrun, under- e run e dalle chiamate a SNDCTL_DSP_RESET, SNDCTL_DSP_POST e SNDCTL_DSP_SYNC. 3.7.3 I/O non bloccante La possibilit` di effettuare l’I/O non bloccante sui device audio ` una caratter- a e istica fondamentale ove si voglia creare un sistema efficiente di sintesi in tempo reale: infatti quel tempo che il processo eventualmente avrebbe perso in stato di wait per ottenere la disponibilit` del device, pu` essere impiegato utilmente in a o altre attivit`, ad esempio calcolando nuovi campioni. a Essenzialmente ci sono tre metodi per gestire l’I/O non bloccante: 7 Queste chiamate possono di conseguenza essere non portabili fra i vari sistemi operativi, n´ compatibili con tutte le schede audio e 48
  • 54.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.7. IL TEMPO REALE • uso della chiamata SNDCTL_DSP_NONBLOCK, per la riproduzione di piccoli effetti audio • uso delle chiamate a SNDCTL_DSP_GETxSPACE o a SNDCTL_DSP_GETxPTR, per poter calibrare accuratamente la quantit` di byte da porre in I/O tramite a read()/write() • uso della funzione di libreria select(), per effettuare un polling con time– out sui device audio Il primo metodo funziona solo in riproduzione, la read() ritorna sempre senza aver eseguito il campionamento; nel flusso di un programma pu` essere posta o ovunque la chiamata: ioctl(dspfd, SNDCTL_DSP_NONBLOCK); ove SOUND_PCM_NONBLOCK ` un alias di SNDCTL_DSP_NONBLOCK; essa istruisce il e driver a far ritornare immediatamente la write() facendo trasferire nel buffer DMA una quantit` massima di campioni pari allo spazio attualmente disponibile. a Ci` implica che una sequenza troppo ravvicinata di write(), o la scrittura di o troppi campioni, potrebbe saturare il buffer DMA8 ; in tal caso la scrittura che provoca quest’evento ` troncata secondo lo spazio disponibile nel buffer, mentre e le seguenti sono ignorate finch´ ` disponibile nuovo spazio libero. ee Il secondo metodo consiste nell’ottenere informazioni dal driver sull’evoluzione della situazione nel buffer DMA; il trasferimento dei campioni pu` essere eseguito o secondo la quantit` di spazio disponibile nel buffer: a int dspfd; unsigned char buf[DIM_BUF]; /* DIM_BUF qualunque */ unsigned char *p_buf = buf; const unsigned char *p_fine_buf = buf + sizeof(buf); audio_buf_info ainfo; /* Apre /dev/dsp e riempie il buffer */ /* del processo buf[] di campioni */ while (p_buf < p_fine_buf) { ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo); if (ainfo.bytes) { /* C’e’ spazio disponibile */ if (ainfo.bytes > (p_fine_buf - p_buf)) ainfo.bytes = p_fine_buf - p_buf; write(dspfd, p_buf, ainfo.bytes); 8 Questa chiamata ` quindi usata in quei casi in cui bisogna riprodurre piccoli effetti sonori e abbastanza distanziati l’uno dall’altro in termini temporali; l’“abbastanza” dipende dalla frequenza di campionamento, dallo spazio nel buffer DMA e dalla quantit` di campioni scritti a 49
  • 55.
    3.7. IL TEMPOREALE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE p_buf += ainfo.bytes; } else { /* Fa qualcos’altro - il device e’ occupato */ } } Un altro modo consiste nel trasferire un frammento per volta: int dspfd, nfram = 0; unsigned char buf[DIM_BUF]; /* DIM_BUF dim. di un frammento */ audio_buf_info ainfo; /* Apre /dev/dsp e predispone la dimensione */ /* di un frammento a DIM_BUF */ while (nfram < NUM_FRAMMENTI) { ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo); if (ainfo.fragments && <buf[] riempito>) { write(dspfd, buf, DIM_BUF); /* Scrive un frammento */ nfram++; } else { /* Fa qualcos’altro - il device e’ occupato */ /* oppure non ci sono campioni in buf[] */ } } ove NUM_FRAMMENTI ` il numero di frammenti di dimensione DIM_BUF da ripro- e durre. Gli esempi per il campionamento sono analoghi, sostituendo alla chiamata SNDCTL_DSP_GETOSPACE la SNDCTL_DSP_GETISPACE. In generale ` possibile affermare che la prima metodologia ` preferibile quando e e bisogna trasferire una grande quantit` di campioni gi` pronti al buffer DMA per a a la riproduzione, in quanto verranno generate meno interruzioni del flusso del programma (viene copiata inizialmente una gran quantit` di dati, poi quanto a basta per tenere il buffer pieno); la seconda metodologia ` preferibile quando i e campioni siano sintetizzati in tempo reale (specie se la dimensione dei frammenti diventa piccola), in quanto la prima metodologia potrebbe generare dei frammenti solo parzialmente riempiti, con uno sfruttamento non ottimale del buffer DMA. Questi discorsi rimangono validi anche per il campionamento, con gli opportuni adattamenti del caso. La gestione di campionamento/riproduzione per frammenti impegna di pi` la u CPU di quanto faccia la prima metodologia, con questa tendenza che aumenta al diminuire della dimensione dei frammenti; tuttavia essa garantisce al sistema una risposta pi` pronta, a patto che l’intero processo di consumazione/creazione di u campioni sia abbastanza veloce da non generare condizioni di overrun/underrun. 50
  • 56.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.7. IL TEMPO REALE Il terzo metodo implica l’utilizzo di select() per effettuare il polling (bloc- cante o meno fino a un determinato tempo massimo) sui device file di OSS al fine di testare la disponibilit` alla lettura e/o scrittura oppure al fine di rilevare a l’insorgere di eccezioni. Volendo sfruttare il time–out ` necessario includere gli e header file time.h e types.h; un esempio di utilizzo ` il seguente: e int retval; retval = select(fd+1, &readset, &writeset, &exceptset, &timeout); essa opera su degli insiemi di file descriptor, cio` pu` essere usata per controllare e o pi` device file contemporaneamente: readset raccoglie quelli aperti in lettura, u writeset quelli aperti in scrittura e exceptset quelli per i quali si voglia cat- turare l’insorgere di un’eccezione. Quest’ultima ` pi` facile che insorga su file e u descriptor che si riferiscano a pipe o socket, non a device audio. Se uno o due dei tre insiemi sono mancanti, vi si pu` sostituire NULL. Nel seguente esempio di o codice ` mostrato come aggiungere il file descriptor dspfd, aperto in lettura, a e readset: fd_set readset; FD_ZERO(&readset); /* Azzera l’insieme */ FD_SET(dspfd, &readset); /* Aggiunge dspfd */ nel caso dspfd sia aperto con O_RDWR, readset ` influenzato solo nel caso di e disponibilit` di campioni nel buffer di input, separato da quello di output; per a testare la disponibilit` alla scrittura su quest’ultimo bisogner` aggiungere dspfd a a a un eventuale writeset. Quando un device file ` causa di un evento che fa ritornare select() la e macro FD_ISSET(filedes, &filedes_set) ritorna un valore diverso da zero; in tal caso retval ` il numero di file descriptor che hanno cambiato stato all’uscita e da select(), mentre fd ` il massimo fra i file descriptor dei tre insiemi. La macro e FD_CLR(filedes, &filedes_set) rimuove il file descriptor filedes dall’insieme filedes_set. Il tempo massimo che select() attende per il verificarsi di un evento su uno degli insiemi ` stabilito dalla struct timeout come nel seguente esempio: e struct timeval timeout; timeout.tv_sec = 1; /* secondi */ timeout.tv_usec = 500; /* microsecondi */ in questo caso select() attenderebbe al massimo 1.5 secondi prima di ritornare (con retval==0) non verificandosi un evento. In Linux la struct timeout ` e modificata all’uscita con la quantit` di tempo non atteso: questo non ` il com- a e portamento della versione POSIX di select(), quindi per produrre programmi portabili bisogner` assumere che timeout abbia un valore indefinito all’uscita da a questo, reinizializzandola prima del riutilizzo. Un NULL al posto di &timeout fa attendere indefinitamente il verificarsi di un evento sugli insiemi, mentre il polling ` non bloccante ponendo: e 51
  • 57.
    3.7. IL TEMPOREALE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE timeout.tv_sec = timeout.tv_usec = 0; Un esempio dell’utilizzo di select() ` riportato a pagina 108. e 3.7.4 La sincronizzazione Per sincronizzazione si intende la possibilit` di fare andare in sincrono eventi a audio con altri eventi, esterni o interni a OSS: ad esempio I/O da disco, eventi MIDI, campionamento e riproduzione. Prima di applicare le capacit` di sincronizzazione (triggering) del driver, a bisogna controllare se queste siano supportate o meno testando DSP_CAP_TRIGGER, altrimenti il comportamento di OSS ` indefinito. Nel caso particolare del full du- e plex, se campionamento e riproduzione devono andare in sincrono, bisogna testare anche DSP_CAP_DUPLEX come descritto nella Sezione 3.8. Per impostare il trigger si usa il seguente frammento di codice: int trigger = <enable bits>; if (ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger) == -1) errore("SNDCTL_DSP_SETTRIGGER"); ove SOUND_PCM_SETTRIGGER ` un alias di SNDCTL_DSP_SETTRIGGER; la bitmask e <enable bits> ` una fra PCM_ENABLE_INPUT e PCM_ENABLE_OUTPUT, per abil- e itare rispettivamente campionamento e riproduzione. Un OR di queste due abili- ta contemporaneamente campionamento e riproduzione nel caso di full duplex, mentre se trigger ` posto a 0 vengono disabilitate entrambe; per disabilitarle e singolarmente si usano ~PCM_ENABLE_INPUT e ~PCM_ENABLE_OUTPUT. Un esempio di utilizzo ` di disabilitare la riproduzione, scrivere sul device e audio dei campioni e riabilitarla, con write() che ritorna -1 (errno==EAGAIN) se il buffer DMA si riempie; la read() ritorna sempre -1 (errno==EAGAIN) se il campionamento non ` abilitato. Dopo l’apertura del device audio con O_RDWR e campionamento e riproduzione sono abilitate per default. Per conoscere lo stato di abilitazione di campionamento e riproduzione ` e disponibile la seguente chiamata: int trigger; ioctl(dspfd, SNDCTL_DSP_GETTRIGGER, &trigger); if (trigger & <enable bits>) { /* Sono abilitati campionamento o riproduzione */ /* secondo l’identificatore <enable bits> */ } SOUND_PCM_GETTRIGGER ` un alias di SNDCTL_DSP_GETTRIGGER e <enable bits> e ` uno fra gli identificatori PCM_ENABLE_INPUT, PCM_ENABLE_OUTPUT o un OR e aritmetico di questi. Per sincronizzare campionamento e/o riproduzione con gli eventi MIDI out si pu` sfruttare la seguente chiamata: o 52
  • 58.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.7. IL TEMPO REALE ioctl(dspfd, SNDCTL_DSP_SETSYNCRO); ove SOUND_PCM_SETSYNCRO ` un alias di SNDCTL_DSP_SETSYNCRO; questa disat- e tiva gli eventi audio, che vengono riattivati solo quando ` utilizzata la macro e SEQ_PLAYAUDIO(). Sfortunatamente quest’ultima, pur essendo elencata in soundcard.h, non ` ancora stata implementata. e 3.7.5 Accesso diretto al buffer DMA Mappare il buffer DMA nell’area di memoria del processo ` una tecnica che pu` e o ottimizzare i tempi del driver: soprattutto se i campioni sono stati prodotti da un algoritmo di sintesi in tempo reale, pu` essere pi` efficiente eliminarne la copia o u dal/al buffer del processo. In generale ` da evitarne l’uso, a meno di avere dei e buoni motivi; infatti questa tecnica ` poco portabile fra varie schede audio, in e quanto queste devono rendere il buffer DMA accessibile dalla CPU (ad esempio, quelle con il chip CS4232) e dev’essere supportata mmap() per la mappatura in memoria del device file9 (includendo l’header file sys/mman.h). Altri requisiti sono il supporto del triggering e che la scheda audio non abbia memoria locale che falsi l’utilizzo delle chiamate a SNDCTL_DSP_GETxPTR. Per sfruttare l’accesso diretto al buffer DMA ` necessario compiere una se- e quenza di preliminari: • si deve aprire il device audio, tenendo presente che la modalit` O_RDWR a implica l’adozione di due buffer DMA separati per la riproduzione e il campionamento • affinch´ la tecnica sia applicabile, ` essenziale testare le capacit` del driver e e a con DSP_CAP_TRIGGER e DSP_CAP_MMAP (la precisione del puntatore al buffer DMA ` influenzata da DSP_CAP_REALTIME e DSP_CAP_BATCH); usando il full e duplex si deve testare anche DSP_CAP_DUPLEX • si deve predisporre la frequenza di campionamento con SNDCTL_DSP_SPEED • si seleziona la dimensione del frammento con SNDCTL_DSP_SETFRAGMENT; ci` influisce sulla velocit` da tenere nella sintesi dei campioni e su quanto o a spesso un’eventuale select() ritorna • si calcola la dimensione del buffer DMA con: audio_buf_info ainfo; int dim_buf_DMA; ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo); dim_buf_DMA = ainfo.fragstotal * ainfo.fragsize; 9 Solo sotto Linux per versioni del driver superiori alla 3.5β7 e sotto FreeBSD, BSD/OS, UnixWare e Solaris per versioni del driver superiori alla 3.8 53
  • 59.
    3.7. IL TEMPOREALE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE • si alloca in memoria il device file audio con una chiamata a mmap(); uti- lizzando il full duplex ci sono due buffer separati per l’I/O, per cui le allocazioni da eseguire diventano due caddr_t p_buf; /* Inizio del buffer in memoria */ if ((p_buf = mmap(NULL, dim_buf_DMA, prot, MAP_FILE|MAP_SHARED, dspfd, 0)) == (caddr_t) -1) errore("mmap()"); il primo parametro di mmap() ` un suggerimento sull’inizio dell’area di e memoria, l’ultimo ` un offset rispetto a questo: di solito si pongono rispeti- e vamente a NULL e 0; l’argomento prot seleziona il tipo di buffer da allocare: PROT READ buffer di input PROT WRITE buffer di output PROT READ|PROT WRITE buffer di output nel caso di BSD, men- tre in Linux tale combinazione ` ammessa per versioni di OSS superiori e alla 3.8β16 (in BSD il solo PROT_WRITE causerebbe un errore di seg- mentazione/bus); nel caso di apertura del device file non in O_RDWR questa combinazione ha un risultato indefinito • poich´ il driver non permette l’uso di read()/write() con il device file e allocato in memoria, per iniziare le operazioni si devono resettare i bit di enable per la sincronizzazione: int trigger = 0; ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger); nel caso si debba fare in sincrono output di eventi MIDI si sfrutta invece: ioctl(dspfd, SNDCTL_DSP_SETSYNCRO); • devono essere scritti dei campioni nel buffer prima di iniziare la ripro- duzione, per evitare che l’equivalente di una condizione di underrun si verifichi subito; quando si ` pronti si avvia il driver con: e int trigger = PCM_ENABLE_OUTPUT; /* | PCM_ENABLE_INPUT */ ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger); L’accesso al buffer ` possibile solo tramite puntatori: l’inizio dell’area di e memoria ` p_buf, mentre la fine ` data da p_buf+(caddr_t)(dim_buf_DMA-1); la e e posizione del puntatore DMA di riproduzione o campionamento ` ritornata dalle e chiamate SNDCTL_DSP_GETxPTR, fatte salve le considerazioni della Sezione 3.7.2 sulla sua precisione. In riproduzione risulta efficiente scrivere nel buffer un frammento per volta e parte del frammento seguente (play ahead ), per fare in modo che non ci siano 54
  • 60.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.8. IL FULL DUPLEX “buchi” nella riproduzione in casi di rallentamento nella produzione di campioni da parte del processo. Prima della sua scrittura ci si deve allineare ai bordi del frammento nel buffer, tenendo cura di non eccedere i limiti dell’area di memoria allocata: un errore di scrittura/lettura anche di un solo byte al di fuori causa un errore di segmentazione/bus; per allinearsi ai bordi del frammento si pu` usare il o codice seguente: count_info cinfo; /* Buffer di output */ ioctl(dspfd, SNDCTL_DSP_GETOPTR, &cinfo); cinfo.ptr = (cinfo.ptr / dim_frammento) * dim_frammento; ove dim_frammento ` la dimensione di un frammento in byte. e Nell’utilizzo normale del driver (non allocando il device file in memoria) dopo che un frammento ` stato riprodotto esso ` azzerato automaticamente, in modo e e tale da evitare la riproduzione di campioni “vecchi” (looping) nel caso di pause o rallentamenti nella produzione di campioni: ` il caso di considerare se non sia e conveniente effettuare una tale operazione anche con l’accesso diretto, poich´ se e non si aggiorna il buffer i suoi contenuti saranno riprodotti a ciclo continuo a meno di fermare il driver con SNDCTL_DSP_TRIGGER. Un’altra differenza rispetto al normale utilizzo ` data dal fatto che i campioni e vengono presi dal buffer e sono inviati direttamente alla scheda audio, senza che siano effettuate conversioni di formato intermedie: di conseguenza i campioni devono essere in un formato supportato direttamente in hardware dalla scheda. 3.8 Il full duplex Con le schede audio che non lo supportano in hardware ` possibile solo un’ap- e prossimazione di full duplex: conviene campionare e alternativamente riprodurre, ogni volta chiudendo e riaprendo il device audio rispettivamente in O_RDONLY e O_WRONLY, invece di aprirlo in O_RDWR (ci` ottimizza il comportamento di OSS). o ` anche possibile avere due schede audio half duplex installate nel comput- E er, usandone una in campionamento e l’altra in riproduzione10 : ci` a patto che o entrambe supportino precisamente la stessa frequenza di campionamento e la capacit` di triggering. a Se la scheda audio ` per` una vera full duplex ed ` supportata questa capacit` e o e a dal driver, prima di sfruttarla bisogna compiere i seguenti preliminari: • si deve aprire il device audio con O_RDWR • si attiva il full duplex con ioctl(dspfd, SNDCTL_DSP_SETDUPLEX,0); se non si effettua quest’operazione prima, testando DSP_CAP_DUPLEX verr`a riportato che il driver non supporta il full duplex 10 Volendo sottilizzare, in questo caso non si tratterebbe di full duplex vero e proprio, ma di uso simultaneo di due device audio 55
  • 61.
    3.9. USO DICOPROCESSORI CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE • si predispongono i parametri di campionamento e/o la dimensione dei fram- menti A questo punto si pu` leggere/scrivere sul device audio, a patto che queste o operazioni siano sincronizzate e non bloccanti: infatti leggendo e scrivendo la stessa quantit` di campioni, nonch´ evitando condizioni di underrun e overrun, a e si mantiene un preciso riferimento temporale nell’I/O. 3.9 Uso di coprocessori OSS supporta un modello di programmazione per eventuali coprocessori o DSP aggiuntivi su schede audio high–end; ad esempio, questi potrebbero essere imp- iegati per la creazione di effetti di spazializzazione. Il test di DSP_CAP_COPROC rivela se un coprocessore ` presente sulla scheda. e Per resettare il coprocessore si pu` utilizzare la seguente chiamata: o ioctl(dspfd, SNDCTL_COPR_RESET); Il coprocessore ` programmabile, e come tale dispone di memoria locale per e dati e programmi; OSS definisce un comando per effettuare il caricamento di questi nella memoria: copr_buffer cbuf; if (ioctl(dspfd, SNDCTL_COPR_LOAD, &cbuf) == -1) errore("SNDCTL_COPR_LOAD"); ove la struct copr_buffer ` cos` fatta: e ı int command se non si usa ` posto a 0 e int flags il caricamento di un programma si spezza in blocchi da 4000 byte qualo- ra sia pi` lungo di questa dimensione: il blocco iniziale lo si indica ponendo u flags=CPF_FIRST, il blocco finale lo si indica con flags=CPF_LAST, gli even- tuali blocchi in mezzo con flags=CPF_NONE; se un blocco di programma ` di e dimensione inferiore a 4000 byte, e come tale pu` essere caricato in un’unica o soluzione, lo si indica con flags=CPF_FIRST|CPF_LAST int len lunghezza in byte del blocco di programma (max 4000) int offs il blocco di programma potrebbe avere in testa un’area dati, e in tal caso offs dice al coprocessore dove sia l’effettivo punto d’inizio del programma, in byte dall’inizio; ` dipendente dal coprocessore e di solito si pone a 0 se e non ` usato e unsigned char data[4000 ] in questo array viene posto il blocco di programma Il programma per il coprocessore ` di solito generato a parte utilizzando un e cross–assembler; segue un esempio di caricamento: 56
  • 62.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.9. USO DI COPROCESSORI int dspfd, c_fd; copr_buffer cbuf; /* Si suppone che il codice risieda su disco e che */ /* si sia gia’ aperto il file che lo contiene, con */ /* file descriptor c_fd; in quest’esempio il codice */ /* e’ lungo meno di 4000 byte */ cbuf.command = cbuf.offs = 0; cbuf.flags = CPF_FIRST | CPF_LAST; /* Unico blocco */ cbuf.len = read(c_fd, cbuf.data, 4000); /* Carica da disco */ if ((cbuf.len == -1) || (cbuf.len == 4000)) errore("Caricamento programma coprocessore"); if (ioctl(dspfd, SNDCTL_COPR_LOAD, &cbuf) == -1) errore("SNDCTL_COPR_LOAD"); Sono definiti dei comandi per il debug dei programmi del coprocessore, che hanno il seguente prototipo di utilizzo: copr_debug_buf dbuf; if (ioctl(dspfd, SNDCTL_COPR_****, &dbuf) == -1) errore("SNDCTL_COPR_****"); ove la struct copr_debug_buf ` cos` fatta: e ı int command si pone a 0, essendo usato internamente dal driver int parm1 1o parametro; se usato in coppia con parm2 generalmente specifica un indirizzo nella memoria del coprocessore int parm2 2o parametro; generalmente ` un dato da scrivere (o letto) in memoria e del coprocessore int flags eventuale registro dei flag del coprocessore int len lunghezza di parm2 in byte I comandi SNDCTL_COPR_**** per il debug sono: Comandi parm1 parm2 Azione SNDCTL_COPR_RDATA indirizzo parola Ritorna una parola dati dall’indirizzo SNDCTL_COPR_RCODE indirizzo parola Ritorna una parola di codice macchina dall’indirizzo SNDCTL_COPR_RUN — — Esegue un programma SNDCTL_COPR_HALT — — Arresta un programma SNDCTL_COPR_WDATA indirizzo parola Scrive una parola dati all’indirizzo SNDCTL_COPR_WCODE indirizzo parola Scrive una parola di codice macchina all’indirizzo 57
  • 63.
    3.10. NUOVE CARATTERISTICHE CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE Nel caso il coprocessore lo consenta, ` previsto un modo per inviare/ricevere e 11 messaggi al/dal coprocessore: copr_msg cmsg; if (ioctl(dspfd, SNDCTL_COPR_****, &cmsg) == -1) errore("SNDCTL_COPR_****"); i comandi sono rispettivamente SNDCTL_COPR_SENDMSG e SNDCTL_COPR_RCVMSG; la struct copr_msg ` cos` fatta: e ı int len lunghezza in byte del messaggio (max 4000) unsigned char data [4000 ] messaggio arbitrario, con significato dipendente dal coprocessore 3.10 Nuove caratteristiche • ` stato aggiunto l’identificatore di formato audio AFMT_S16_NE, che assume e il valore AFMT_S16_LE se il computer su cui ` compilato il codice sorgente e ` little endian, mentre assume il valore AFMT_S16_BE se il computer ` big e e endian: if (AFMT_S16_NE == AFMT_S16_LE) { /* Architettura little endian */ /* Intel, Alpha */ } else { /* Architettura big endian */ /* Sparc, HP-PA, PowerPC */ } • ` stata aggiunta la chiamata SNDCTL_DSP_GETODELAY, che ha lo scopo di e ritornare la quantit` di campioni in byte non riprodotti attualmente nel a buffer di output: int nbyte; ioctl(dspfd, SNDCTL_DSP_GETODELAY, &nbyte); per la versione 3.5.4 di OSS questa chiamata ` approssimativamente ripro- e ducibile con il seguente codice: 11 Per messaggio si intende un pacchetto di dati scambiato tra programma e coprocessore; ad esempio, potrebbe essere una copia dei registri di quest’ultimo, in modo da “fotografare” l’evoluzione attuale del codice macchina 58
  • 64.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.11. ESEMPIO DI PROGRAMMA audio_buf_info ainfo; int nbyte; ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo); nbyte = ainfo.fragstotal*ainfo.fragsize - ainfo.bytes; • la chiamata SNDCTL_DSP_PROFILE consente di definire il profilo di un’ap- plicazione, ovvero il modo in cui eventuali condizioni di underrun in ripro- duzione dovrebbero essere gestite dal driver; queste sono classificabili con i seguenti identificatori: APF NORMAL applicazioni “normali” (default), che producono campi- oni pi` velocemente di quanto il driver riesca a riprodurne; quest’ulti- u mo azzera il frammento quando ` stato riprodotto, per cui sono pre- e venute le situazioni di looping a fronte di un aumento del tempo per la gestione del buffer da parte della CPU APF NETWORK gli underrun sono causati da eventi esterni; ad esem- pio, si sta riproducendo dell’audio mentre lo si scarica dalla rete, per cui il flusso dei pacchetti potrebbe essere discontinuo APF CPUINTENS gli underrun sono sporadicamente causati da pro- grammi che sfruttano parecchio la CPU, quindi la produzione di cam- pioni ` meno veloce della riproduzione; viene disabilitato l’azzeramento e del buffer, per cui si possono verificare situazioni di looping in seguito alle quali ne ` riprodotto il precedente contenuto e la chiamata opera nel modo seguente: int profilo = APF_****; /* Uno dei tre profili */ if (ioctl(dspfd, SNDCTL_DSP_PROFILE, &profilo) == -1) errore("SNDCTL_DSP_PROFILE"); 3.11 Esempio di programma Il programma seguente dimostra l’accesso diretto al buffer DMA in campiona- mento, scaricando su un file l’audio campionato mono/8 bit da uno dei canali ` del mixer (hard disk recording). E consentita la scelta della frequenza di campi- onamento e sono stampate informazioni diagnostiche (precisione del puntatore, dimensione dei frammenti e del buffer, durata del campionamento). Il ciclo principale che implementa il campionamento lo fa in maniera non bloccante, per una durata complessiva di dieci secondi. All’uscita da questo ` e effettuata una riproduzione normale (write(), utilizzata in maniera bloccante). Sia in campionamento che in riproduzione ` lasciata al driver la scelta della e dimensione dei frammenti da utilizzare nel buffer. 59
  • 65.
    3.11. ESEMPIO DIPROGRAMMA CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE /* * dirbuf.c - Esempio di accesso diretto al buffer DMA in * campionamento (mono/8 bit), registrando su hard disk * per dieci secondi, poi riproducendo i campioni * caricati da hard disk */ #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/soundcard.h> #include <sys/mman.h> #include <time.h> /* Definizione di alcune costanti simboliche */ #define FILE_CAMP "camp.raw" const int T_CAMP = 10; const float N_CANALI = 1.0; const float BYTE_CAMP = 1.0: const int DIM_BUF = 16384; void errore(const char *msgerr) /* Gestione degli errori */ { perror(msgerr); exit(-1); } int main() { int dspfd, camp_fd; audio_buf_info ainfo; count_info cinfo; caddr_t p_buf; int i, cap, freq_camp, dim_buf_DMA, trigger, buf_ptr; time_t tempo_inizio; unsigned char out_buf[DIM_BUF]; /* 1^ fase, campionamento: aprire in read-only e poi write-only e’ piu’ efficiente che aprire il device in read-write nel caso che la scheda non sia full duplex o il driver non la supporti */ if ((dspfd = open("/dev/dsp", O_RDONLY)) == -1) errore("/dev/dsp input"); 60
  • 66.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.11. ESEMPIO DI PROGRAMMA /* Test capacita’ del driver: se non e’ passato, inutile continuare */ if (ioctl(dspfd, SNDCTL_DSP_GETCAPS, &cap) == -1) errore("SNDCTL_DSP_GETCAPS"); if (!(cap & DSP_CAP_TRIGGER) || !(cap & DSP_CAP_MMAP)) errore("Accesso diretto non supportato!"); if (!(cap & DSP_CAP_REALTIME)) puts("Il puntatore al buffer DMA non e’ preciso."); if (cap & DSP_CAP_BATCH) puts("La scheda audio ha memoria locale che " "falsa la posizione del puntatore."); /* Immissione della frequenza di campionamento */ printf("Immettere la frequenza di campionamento [Hz]: "); scanf("%d", &freq_camp); if (ioctl(dspfd, SNDCTL_DSP_SPEED, &freq_camp) == -1) errore("SNDCTL_DSP_SPEED"); printf("Frequenza di campionamento selezionata: %d Hzn", freq_camp); /* Calcola la dimensione del buffer DMA di input */ if (ioctl(dspfd, SNDCTL_DSP_GETISPACE, &ainfo) == -1) errore("SNDCTL_DSP_GETISPACE"); dim_buf_DMA = ainfo.fragstotal * ainfo.fragsize; printf("Buffer di input: %d frammenti da %d byte, " "per un totale di % d byte.n", ainfo.fragstotal, ainfo.fragsize, dim_buf_DMA); /* Allocazione in memoria del device file audio in input */ if ((p_buf = mmap(NULL, dim_buf_DMA, PROT_READ, MAP_FILE|MAP_SHARED, dspfd, 0)) == (caddr_t) -1) errore("mmap() input buffer"); /* Usa il trigger per disattivare l’input, per abilitarlo in seguito all’apertura del file dei campioni su disco */ trigger = 0; if (ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger) == -1) errore("SNDCTL_DSP_SETTRIGGER"); if ((camp_fd = open(FILE_CAMP, O_WRONLY|O_CREAT|O_TRUNC|O_NONBLOCK, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)) == -1) errore("Creazione file dei campioni " FILE_CAMP); trigger = PCM_ENABLE_INPUT; /* Abilita input */ ioctl(dspfd, SNDCTL_DSP_SETTRIGGER, &trigger); printf("Campionamento mono/8 bit avviato per %d secondi...n", T_CAMP); /* Ciclo di lettura del buffer DMA e scrittura su disco dei campioni */ 61
  • 67.
    3.11. ESEMPIO DIPROGRAMMA CAPITOLO 3. CAMPIONAMENTO E RIPRODUZIONE buf_ptr = 0; time(&tempo_inizio); while (1) { ioctl(dspfd, SNDCTL_DSP_GETIPTR, &cinfo); if ((cinfo.ptr - buf_ptr) >= ainfo.fragsize) { /* Scrive il frammento su hard disk */ write(camp_fd, p_buf + buf_ptr, ainfo.fragsize); buf_ptr += ainfo.fragsize; if (buf_ptr == dim_buf_DMA) /* Raggiunta la fine buffer */ buf_ptr = 0; printf("* "); /* Visualizza * per ogni frammento scritto */ fflush(stdout); } else /* Controlla se sono passati T_CAMP secondi */ if ((time(NULL) - tempo_inizio) >= T_CAMP) break; } printf("nElaborati %d byte per %.2f secondi.nn", cinfo.bytes, cinfo.bytes / (N_CANALI * BYTE_CAMP * freq_camp)); /* 2^ fase: riproduzione del contenuto del file dei campioni su disco */ close(dspfd); close(camp_fd); if ((dspfd = open("/dev/dsp", O_WRONLY)) == -1) errore("/dev/dsp output"); if ((camp_fd = open(FILE_CAMP, O_RDONLY)) == -1) errore("Apertura file dei campioni " FILE_CAMP); /* Predisposizione della frequenza con visualizzazione parametri buffer */ ioctl(dspfd, SNDCTL_DSP_SPEED, &freq_camp); /* Fc uguale all’input */ ioctl(dspfd, SNDCTL_DSP_GETOSPACE, &ainfo); dim_buf_DMA = ainfo.fragstotal * ainfo.fragsize; printf("Buffer di output: %d frammenti da %d byte, " "per un totale di % d byte.n", ainfo.fragstotal, ainfo.fragsize, dim_buf_DMA); printf("Avvio riproduzione mono/8 bit a %d Hz...n", freq_camp); /* Ciclo di scrittura dei campioni prelevati da disco sul buffer DMA */ while (read(camp_fd, out_buf, DIM_BUF) > 0) write(dspfd, out_buf, DIM_BUF); close(dspfd); close(camp_fd); return 0; 62
  • 68.
    CAPITOLO 3. CAMPIONAMENTOE RIPRODUZIONE 3.11. ESEMPIO DI PROGRAMMA } Ci` che segue ` un esempio di output del programma; si noti che esso riporta o e l’imprecisione del puntatore al buffer DMA per la scheda audio utilizzata e che per la frequenza selezionata il driver sceglie di utilizzare frammenti da 8 kB per ` il campionamento e da 32 kB per la riproduzione. E visualizzato un * per ogni frammento trasferito su hard disk. Il puntatore al buffer DMA non e’ preciso. Immettere la frequenza di campionamento [Hz]: 48000 Frequenza di campionamento selezionata: 44100 Hz Buffer di input: 8 frammenti da 8192 byte, per un totale di 65536 byte. Campionamento mono/8 bit avviato per 10 secondi... * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Elaborati 463286 byte per 10.51 secondi. Buffer di output: 2 frammenti da 32768 byte, per un totale di 65536 byte. Avvio riproduzione mono/8 bit a 44100 Hz... Si pu` anche notare come il driver scelga la massima frequenza che la scheda o audio del sistema su cui ` stato eseguito il programma pu` supportare (44100 Hz), e o rispetto alla frequenza richiesta (48000 Hz), cercando di fornire il valore pi`u vicino. 63
  • 69.
    Capitolo 4 SINTETIZZATORI EMIDI 4.1 I device file del sequencer L’interfaccia di programmazione di OSS consente di controllare chip sintetizzatori interni alla scheda audio e porte MIDI in maniera indipendente dal dispositivo e con una temporizzazione precisa. OSS mette a disposizione due device file: /dev/music e /dev/sequencer, con il secondo che ha capacit` inferiori rispetto al a primo per quanto riguarda le caratteristiche di temporizzazione, sincronizzazione con eventi esterni e indipendenza dal dispositivo. Nelle intenzioni dell’autore di OSS /dev/sequencer si presta alla realizzazione di module player (ovvero di esecutori dei file .MOD nati in ambiente Amiga con il programma NoiseTracker), mentre /dev/music ` rivolto alla realizzazione di sequencer generici. e L’indipendenza dal dispositivo ` ottenuta scrivendo sui device file un flusso e di eventi che il driver interpreta secondo i dispositivi indirizzati, come coman- di per i primi e come messaggi MIDI per gli strumenti connessi ai canali delle seconde. Entrambi, in risposta agli eventi, producono output audio o cambi di configurazione. Un evento ` un breve record (di 4 o 8 byte), formattato opportunamente e dalle macro che fanno parte dell’interfaccia di programmazione di OSS, recante il messaggio per un dispositivo e i suoi parametri. Ogni messaggio ` capace di e compiere singole transazioni: ad esempio, pu` selezionare un timbro o iniziare a o suonare una nota. In analogia alla scrittura dei campioni su /dev/dsp, la scrittura degli eventi su /dev/music o /dev/sequencer li accoda in un buffer del driver che generalmente ` in grado di ospitare 1024 eventi. A differenza di /dev/dsp lo svuotamento e del buffer, l’equivalente della condizione di underrun per il buffer audio DMA, provoca un errore di ritmo nell’esecuzione e non click o looping del suono; gli eventi di per s´ non generano campioni, si limitano a pilotare sintetizzatori. e La capacit` di realizzare un sequencer ` ottenuta tramite la possibilit` di acco- a e a dare nel buffer degli eventi marcatempo, i quali forniscono un preciso riferimento ` temporale al driver per l’esecuzione degli altri tipi di eventi. E quindi normale che un evento possa essere eseguito diversi secondi dopo la scrittura sul device file 64
  • 70.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.1. I DEVICE FILE DEL SEQUENCER (il ritardo potrebbe anche essere di minuti), per cui l’applicazione deve tenerne conto se si vuole gestire qualcos’altro in concorrenza con la musica sintetizzata. La forma degli eventi ` simile a quella dei messaggi standard MIDI; si prenda e ad esempio l’evento creato dalla macro SEQ_START_NOTE(), equivalente al NOTE ON del MIDI: SEQ_START_NOTE(<device>, <voce>, <nota>, <volume>); ↓ ↓ ↓ ↓ NOTE ON [porta] → <canale>, <nota>, <velocit`> a Il <device> identifica il numero del chip sintetizzatore o della porta MIDI: per il primo tipo di dispositivo questo ` il numero che appare a lato della lista dei e “Synth devices:” ottenuta tramite cat /dev/sndstat, mentre per il secondo tipo ` il numero nella lista dei “Midi devices:” sommato alla quantit` di synth e a device presenti nel sistema. Ad esempio, si consideri il seguente stralcio di output di cat /dev/sndstat: . . . Synth devices: 0: Yamaha OPL-3 Midi devices: 0: SoundBlaster16 MIDI 1: MQX-32M MIDI Interface #1 . . . In base a ci` che ` scritto in questa lista il chip sintetizzatore ` uno solo e lo o e e si indirizza sostituendo 0 a <device>, mentre le porte MIDI si indirizzerebbero con 1 e 2 (1 synth device + 0 e 1). Solo con /dev/sequencer ` possibile utilizzare la macro SEQ_MIDIOUT(), che e segue invece la numerazione di “Midi devices:”, per poter effettuare l’output di byte MIDI arbitrari (vedere Sezione 4.6). Per la definizione del parametro <voce> si veda la Sezione 4.1.2, mentre <nota> e <volume> sono entrambi definiti nell’intervallo 0 ÷ 127; per il MIDI l’ultimo parametro corrisponde al key velocity. 4.1.1 I chip sintetizzatori I chip sintetizzatori disponibili nelle schede audio generalmente operano mediante sintesi FM o wavetable: nel primo caso i timbri sono prodotti mediante l’impiego di oscillatori1 (il suono ottenuto non risulta “naturale” all’ascolto), nel secondo 1 Ogni oscillatore ` noto come operatore: miscelando le forme d’onda prodotte tramite con- e nessioni diverse fra gli operatori (algoritmi) si ottengono timbri “pi` ricchi”, per cui il suono u diventa complessivamente migliore 65
  • 71.
    4.1. I DEVICEFILE DEL SEQUENCER CAPITOLO 4. SINTETIZZATORI E MIDI caso si ha la riproduzione di timbri campionati da strumenti reali con diverse velocit` (per cui il suono risulta pi` reale, anche se non completamente reale). a u Il chip pi` famoso utilizzante la prima tecnica ` l’Yamaha YM3812 (OPL–2), u e con nove voci2 fisse a disposizione e un output monofonico anche per le schede audio stereo. Le nove voci (ciascuna ottenuta con due operatori — 2OP) pos- sono essere sfruttate direttamente, ma ` anche disponibile una modalit` ritmo e a che “sacrificando” tre voci ricava cinque strumenti a percussione: Bass Drum, Snare Drum, Tom–Tom, Cymbal e Hi–Hat. Il primo ` ottenuto con 2OP, gli al- e tri quattro con 1OP; dei primi tre pu` essere decisa la frequenza, gli ultimi due o hanno frequenza fissa. All’YM3812 ` seguito l’YMF262 (OPL–3), che supporta di default la modalit` e a OPL–2. In modalit` OPL–3 il chip supporta diciotto voci 2OP (per un totale di a trentasei operatori), oppure pu` fornire sei voci a quattro operatori (4OP) e sei o voci a 2OP; nella modalit` ritmo di nuovo vengono supportati cinque strumenti a a percussione sottraendo tre voci a 2OP, quindi oltre a questi possono essere fornite quindici voci 2 OP oppure sei voci 4OP e tre voci 2OP. L’OPL–3 ` stereoe nel senso che di ogni voce pu` essere deciso se mandarla al canale sinistro, destro o o a entrambi per centrarla. In seguito la Yamaha ha prodotto l’OPL–4: questo chip supporta solo la modalit` OPL–3, ma ha capacit` di sintesi wavetable. I chip pi` famosi sono a a u Yamaha, ma altre schede (Gravis UltraSound) incorporano chip come il Crystal Sound CS4232, che secondo la modalit` consente di ottenere da 14 a 32 voci a con sintesi FM o la sintesi wavetable. Di ogni voce ` possibile decidere meglio e dell’OPL–3 la localizzazione spaziale fra i canali stereo sinistro e destro, in quanto sono previste sedici posizioni intermedie. 4.1.2 I sintetizzatori MIDI Una porta MIDI pu` gestire fino a sedici canali: su ognuno di essi pi` dispositivi o u MIDI possono essere in ascolto, siano essi expander, master keyboard, sequencer o interfacce per pilotare le luci di scena. Questi obbediranno ognuno con la propria logica: ad esempio, se si ha un messaggio di NOTE ON rivolto a un sintetizzatore, questo potrebbe smettere di suonare la nota precedente o eseguire quella nuova in polifonia3 . I sintetizzatori nei dispositivi MIDI tendono ad essere pi` sofisticati di u quelli disponibili nelle schede audio, anche se la differenza qualitativa nel tempo si va sempre pi` assottigliando. u La differenza fondamentale nell’uso di /dev/music rispetto a /dev/sequencer consiste nel fatto che per il primo la gestione delle voci per i chip sintetizzatori ` automatica, come fanno i dispositivi MIDI una volta fissati l’opportuno pre- e 2 Convenzionalmente con voce si intende una forma d’onda generata da uno o pi` operatori; u una nota pu` essere composta da una o pi` voci o u 3 La polifonia ` data dal numero massimo di note che un sintetizzatore riesce a suonare e contemporaneamente, per cui generalmente tale numero ` variabile in base al numero di voci e che si impiegano per ogni nota 66
  • 72.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.2. IL BUFFER DEGLI EVENTI set timbrico e/o il modo (OMNI ON/OFF e POLY/MONO); in altri termini, /dev/music cerca di emulare una porta MIDI per ogni chip sintetizzatore: ci sono sedici canali (<voce> va da 0 a 15), con <voce>==9 (ovvero il canale 10 MIDI) per default dedicato alle percussioni (si seleziona un preset timbrico con il valore di <nota>). Per /dev/sequencer ` invece responsabilit` del programmatore allocare e e a deallocare le singole voci: se una nota ` associata a una voce, l’inizio di una e nuova nota termina la precedente (effetti particolari, come il legato, implicano un impegno di programmazione pi` elevato). Il parametro <voce> va da 1 fino al u massimo numero di voci ammesse per la modalit` corrente. a memoria wait Comandi Scheda audio Kernel sintetizzatore o porta MIDI 1111 0000 1 1 0 0 11 00 coda degli eventi OPL3 1111 0000 1 1 0 0 11 00 1111 0000 1 1 0 0 11 00 UART Processo byte OSS MIDI buffer degli eventi driver SEQ_DUMPBUF() INT Sincronizzazione esterna Controller interrupt CLOCK di sistema Figura 4.1: Schema di funzionamento del sequencer 4.2 Il buffer degli eventi In analogia al buffer dei campioni dichiarato in un programma per /dev/dsp, ` e necessario dichiarare un buffer degli eventi per /dev/music o /dev/sequencer; ci` si effettua ponendo le seguenti dichiarazioni subito dopo gli #include: o int seqfd; SEQ_DEFINEBUF(<dimensione buffer>); ove <dimensione buffer> ` la dimensione del buffer utente in byte; ` consigli- e e abile adoperare multipli di 1024 byte, anche se 1024 ` pi` che sufficiente per la e u maggior parte delle applicazioni. Il file descriptor deve chiamarsi proprio seqfd, in quanto ci sono delle macro nell’interfaccia di programmazione che vi fanno riferimento. Con l’attuale struttura di soundcard.h la SEQ_DEFINEBUF() equivale a: unsigned char seqbuf[<dimensione buffer> ] il buffer ` un vettore lungo e <dimensione buffer> byte 67
  • 73.
    4.2. IL BUFFERDEGLI EVENTI CAPITOLO 4. SINTETIZZATORI E MIDI int seqbuflen=<dimensione buffer> conserva la lunghezza del vettore int seqbufptr=0 posiziona al principio il cursore all’interno del buffer L’apertura dei device file si effettua come per /dev/dsp; ad esempio: if ((seqfd = open("/dev/music", O_WRONLY)) == -1) errore("/dev/music"); a differenza di /dev/dsp, l’uso di O_NONBLOCK ` efficace: il processo utente non e viene bloccato in lettura/scrittura nella coda degli eventi in condizioni normali, ma se si legge e la coda ` vuota o se si scrive e la coda ` piena verr` ritornato e e a l’errore EAGAIN rispettivamente da read() e write(). Per un programma che effettui solo sintesi si consiglia di usare O_WRONLY, in quanto l’apertura in O_RDWR abilita anche l’input dalle porte MIDI; per il solo input di eventi MIDI ` possibile e l’apertura del device file con O_RDONLY. Dopo che nel buffer utente ` stato posto un qualche evento, con macro come e SEQ_START_NOTE() che saranno esaminate in seguito, ` cura del programmatore e spedirne i contenuti al driver tramite la macro: SEQ_DUMPBUF(); affinch´ questa abbia effetto, nel solo file principale deve essere dichiarata una e funzione void seqbuf_dump() come la seguente: void seqbuf_dump() { if (_seqbufptr && (write(seqfd, _seqbuf, _seqbufptr) == -1)) errore("/dev/music"); _seqbufptr = 0; } Con la funzione sopra riportata la SEQ_DUMPBUF() risulter` bloccante per il a processo utente se il buffer interno al driver si riempie; quando quest’ultimo si svuota per met` il driver lo toglier` dallo stato di wait. Siccome l’esecuzione di a a un evento pu` richiedere parecchi secondi, ` da tenere presente che il processo o e utente pu` rimanere bloccato per parecchio tempo. o Nel caso il programma fosse diviso in pi` file sorgente compilati separatamente u che sfruttano /dev/music o /dev/sequencer, solo nel file principale andranno effettuate le precedenti dichiarazioni; per tutti gli altri file dopo gli #include si mettono: SEQ_USE_EXTBUF(); extern int seqfd; extern void seqbuf_dump(); al posto di SEQ_USE_EXTBUF() si pu` usare SEQ_DECLAREBUF(); con l’attuale o struttura di soundcard.h, tali macro equivalgono alle seguenti dichiarazioni: 68
  • 74.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.3. LETTURA DELLA CONFIGURAZIONE extern unsigned char _seqbuf[]; extern int _seqbuflen; extern int _seqbufptr; Tutte le macro descritte nella Sezione 4.6 fanno riferimento alle seguenti due macro per gestire l’allocazione degli eventi all’interno del buffer: SEQ NEEDBUF(<numero>) verifica la possibilit` di allocare all’interno del a buffer <numero> byte; se non si pu` lo svuota o SEQ ADVBUF(<numero>) fa avanzare l’indice del buffer _seqbufptr di <numero> byte fra queste macro vengono poste le assegnazioni di valori all’interno del buffer che costituiscono gli eventi stessi. Se si hanno delle necessit` particolari ` possibile definire il buffer in modo tale a e da inserirvi un solo evento per volta; per far ci` bisogna definire la macro vuota o USE_SIMPLE_MACROS prima dell’inclusione di soundcard.h: #define USE_SIMPLE_MACROS #include <soundcard.h> in tal caso ` lasciata al programmatore la responsabilit` di definire subito dopo e a queste due righe le seguenti macro: seqbuf nome del buffer (unsigned char[]) seqbufptr nome del cursore all’interno del buffer o 0 se non richiesto SEQ ADVBUF(<lunghezza>) da usare se l’applicazione sfrutta eventi di <lunghezza> diversa, altrimenti deve essere definita vuota 4.3 Lettura della configurazione Se si vuole rilevare qual ` la configurazione hardware incapsulata da OSS si hanno e a disposizione delle chiamate che interrogano il driver riguardo vari parametri. Il seguente frammento di codice ritorna in ndevice la somma del numero di chip sintetizzatori e di porte MIDI presenti nel sistema: int ndevice; ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &ndevice); Si ` gi` detto che /dev/music consente una maggiore indipendenza dall’hard- e a ware di quanto invece consenta /dev/sequencer: ne consegue che il primo device file vede tutti i dispositivi (chip o porte) allo stesso modo, mentre il secondo con- sente anche di considerare le porte MIDI come separate. Per evidenziare questa differenza di comportamento nella Sezione seguente sono listate le chiamate che per entrambi i device file si comportano identicamente, mentre nella Sezione dopo sono elencate le chiamate peculiari per il solo /dev/sequencer. 69
  • 75.
    4.3. LETTURA DELLACONFIGURAZIONE CAPITOLO 4. SINTETIZZATORI E MIDI 4.3.1 Parametri generali Il driver ` in grado di caratterizzare ogni dispositivo sintetizzatore presente nel e sistema tramite la seguente chiamata: struct synth_info sinfo; sinfo.device = <numero device>; ioctl(seqfd, SNDCTL_SYNTH_INFO, &sinfo); ove <numero device> ` il numero identificatore del dispositivo di cui interes- e sa rilevare informazioni, compreso tra 0 e ndevice-1. I campi ritornati nella struct synth_info hanno il significato: char name[30 ] nome del dispositivo (lo stesso riportato con cat /dev/sndstat) int device numero del dispositivo, da inizializzare prima della chiamata int synth type tipo del dispositivo, pu` essere uno dei seguenti identificatori: o SYNTH_TYPE_FM sintetizzatore FM SYNTH_TYPE_SAMPLE sintetizzatore wavetable SYNTH_TYPE_MIDI porta MIDI int synth subtype qualifica ulteriormente il dispositivo, pu` essere uno dei o seguenti identificatori: FM_TYPE_ADLIB compatibilit` OPL–2 a FM_TYPE_OPL3 compatibilit` OPL–3 a MIDI_TYPE_MPU401 UART compatibile MPU–401 SAMPLE_TYPE_GUS wavetable di tipo Gravis UltraSound int nr voices massimo numero di voci supportate dal dispositivo int instr bank size numero di strumenti FM caricabili contemporaneamente dal driver unsigned int capabilities specifica capacit` supplementari del dispositivo tramite a i seguenti identificatori: SYNTH_CAP_OPL3 supporto OPL–3 SYNTH_CAP_INPUT il dispositivo pu` effettuare input MIDI o Se il sintetizzatore ` wavetable (sinfo.synth_type==SYNTH_TYPE_SAMPLE) e e consente il caricamento delle patch, esiste una chiamata che ritorna la quantit` a di memoria in byte ancora disponibile a tal scopo: int mem_disp = <numero device>; ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &mem_disp); ove <numero device> ` il numero identificatore del sintetizzatore; questa chia- e mata ritorna mem_disp==0x7FFFFFFF se rivolta a un sintetizzatore FM o a una porta MIDI. 70
  • 76.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.3. LETTURA DELLA CONFIGURAZIONE 4.3.2 Le porte MIDI Quanto detto in questa Sezione ` valido per il solo /dev/sequencer. e Se si vuole conoscere il numero delle sole porte MIDI installate nel sistema ` e disponibile la seguente chiamata: int nporte; ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &nporte); per cui il numero di chip sintetizzatori interni alla scheda audio sar` pari a a ndevice-nporte. Per quanto detto in precedenza, se questa chiamata ` effettuata e per /dev/music si avr` sempre 0 ritornato in nporte. a Si pu` riconoscere il tipo delle porte MIDI tramite la seguente chiamata: o struct midi_info minfo; minfo.device = <numero porta>; ioctl(seqfd, SNDCTL_MIDI_INFO, &minfo); ove <numero porta> ` il numero della porta MIDI, compreso tra 0 e nporte-1. e I campi ritornati nella struct midi_info hanno il significato: char name[30 ] nome del dispositivo (lo stesso riportato con cat /dev/sndstat) int device numero della porta MIDI, da inizializzare prima della chiamata a SNDCTL_MIDI_INFO int dev type identificatore del tipo di scheda audio ove ` installata la porta e MIDI, pu` essere uno dei seguenti: o SNDCARD_ADLIB Adlib SNDCARD_SB Sound Blaster SNDCARD_PAS Pro Audio Spectrum SNDCARD_GUS Gravis UltraSound SNDCARD_MPU401 MPU–401 SNDCARD_SB16 Sound Blaster 16 SNDCARD_SB16MIDI Sound Blaster 16 MIDI SNDCARD_UART6850 6850 UART SNDCARD_GUS16 Gravis UltraSound 16 SNDCARD_MSS Microsoft Sound System SNDCARD_PSS Personal Sound System SNDCARD_SSCAPE Ensoniq SoundScape SNDCARD_PSS_MPU Personal Sound System + MPU–401 SNDCARD_PSS_MSS Personal Sound System/Microsoft Sound System SNDCARD_SSCAPE_MSS Ensoniq SoundScape/Microsoft Sound System SNDCARD_TRXPRO Mediatrix PRO SNDCARD_TRXPRO_SB Mediatrix PRO/Sound Blaster SNDCARD_TRXPRO_MPU Mediatrix PRO + MPU–401 71
  • 77.
    4.4. PREDISPOSIZIONE DEICHIP SINTETIZZATORI CAPITOLO 4. SINTETIZZATORI E MIDI SNDCARD_MAD16 MAD 16 SNDCARD_MAD16_MPU MAD 16 + MPU–401 SNDCARD_CS4232 CS4232 SNDCARD_CS4232_MPU CS4232 + MPU–401 SNDCARD_MAUI Turtle Beach Maui SNDCARD_PSEUDO_MSS Pseudo Microsoft Sound System SNDCARD_GUSPNP Gravis UltraSound PnP SNDCARD_UART401 UART MPU–401 (pass–through) Un esempio interessante dell’applicazione di questa chiamata ` il rilevamento e della presenza di una WaveBlaster4 : si esegue un ciclo fra 0 e nporte-1, ferman- dosi quando minfo.dev_type==SNDCARD_SB16_MIDI; se non lo si trova si pu` o ripiegare su minfo.dev_type==SNDCARD_SB16, ma per entrambi non ` ancora e garantita la presenza della WaveBlaster. Questa c’` se risponde alla sequenza di e inizializzazione del Proteus SoundEngine (il processore di cui ` dotata), da inviare e alla porta come una sequenza sysex di byte MIDI: 0xF0, 0x18, 0x04, 0x00, 0x23, 0xF7. Per testare se una porta MIDI ` disponibile e funzionante si dispone della e chiamata: int nporta = <numero porta>; if (ioctl(seqfd, SNDCTL_SEQ_TESTMIDI, &nporta) == -1) { /* Gestione dell’errore */ } ove <numero porta> ` compreso tra 0 e nporte-1. Essa verifica prima di tutto e che la porta esista; in tal caso, se non ` gi` aperta, la apre in I/O, restituendo un e a codice di errore in errno se l’apertura non ` andata a buon fine. e 4.4 Predisposizione dei chip sintetizzatori I chip sintetizzatori, siano essi FM o wavetable, per produrre un qualsiasi suono necessitano di una programmazione dei propri registri che specifica nel primo caso come collegare gli operatori per produrre dei determinati timbri (algoritmi) e nel secondo caso come riprodurre le waveform (patch). Per comodit` di trattazione, a d’ora in poi si chiameranno patch sia gli algoritmi che le waveform. I preset timbrici a disposizione in genere sono gi` pronti sotto forma di file, a e nelle due Sezioni seguenti si illustra come caricarli per i due casi in questione. Questo ` un passo necessario, in quanto ogni sintetizzatore parte senza alcuna e programmazione dopo che il sequencer inizializza i dispositivi. I preset timbrici sono definiti secondo lo standard General MIDI level 1, di conseguenza il programmatore sa gi` che a una certa patch corrisponde un dato a 4 La WaveBlaster ` una schedina di espansione della Sound Blaster 16, visibile al resto del e sistema come un dispositivo connesso a un canale della porta MIDI di cui questa ` dotata, e e che si comporta come un expander multitimbrico con sintesi wavetable 72
  • 78.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI strumento; se questo ` mancante ` prevista una patch sostitutiva. Ci` non fa al- e e o tro che rimarcare l’architettura indipendente dal dispositivo che ha il sequencer, per cui un dato brano MIDI pu` essere eseguito col giusto preset timbrico indif- o ferentemente su un costoso expander o su una scheda audio poco costosa senza praticamente alcuna modifica. OSS prevede una chiamata per il reset dell’intero sequencer, che ha l’effetto di ripristinare le condizioni di default per ogni dispositivo: ioctl(seqfd, SNDCTL_SEQ_RESET); con /dev/music per ogni porta MIDI sono spediti i messaggi All Notes OFF e Bender OFF, nonch´ sono azzerati tutti i controller; con /dev/sequencer si cerca e di ottenere questi effetti spedendo i messaggi di Active Sensing 5 per le porte e All Notes OFF per ogni canale di queste. Sono inoltre azzerate tutte le patch. ` E disponibile un’altra chiamata, che in teoria dovrebbe essere l’equivalente del Panic Button (termina tutte le note in “sospeso” nel caso di blocco del sistema) presente sulle tastiere MIDI: ioctl(seqfd, SNDCTL_SEQ_PANIC); allo stato attuale dello sviluppo del driver (1999), essa si comporta allo stesso mo- do di SNDCTL_SEQ_RESET; in futuro dovrebbe prevedere il Note OFF sistematico per tutte le note di ogni canale di ogni porta MIDI, giusto per quei dispositivi che non riconoscono l’Active Sensing con /dev/sequencer. 4.4.1 Caricamento degli algoritmi FM In un sistema Linux ` altamente probabile che i file contenenti gli algoritmi per i e sintetizzatori FM Yamaha si trovino in /etc; i loro nomi sono std.o3 e drums.o3, che rispettivamente definiscono strumenti e percussioni per l’OPL–3 (4OP). Sem- pre in /etc si possono trovare std.sb e drums.sb per l’OPL–2 (2OP); questi possono essere utilizzati dall’OPL–3, poich´ esso parte in modalit` 2OP e per e a passare alla 4OP bisogna richiederlo esplicitamente invocando la chiamata: int nsint = <numero sintetizzatore>; if (ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &nsint) == -1) errore("SNDCTL_FM_4OP_ENABLE"); i numeri di patch fra 0 e 127 sono dedicati alla sezione strumentale, quelli fra 128 e 255 alla sezione ritmica. Di seguito ` l’elenco dei passi che bisogna compiere per poter caricare le e patch FM: 5 Quando ` spedito il primo messaggio di questo tipo il dispositivo MIDI si aspetta di ricevere e un altro messaggio MIDI entro 300 ms, al limite un altro Active Sensing; se ci` non avviene il o dispositivo si pone nello stato di MIDI error e disattiva tutte le sue funzioni 73
  • 79.
    4.4. PREDISPOSIZIONE DEICHIP SINTETIZZATORI CAPITOLO 4. SINTETIZZATORI E MIDI • in relazione al tipo di sintetizzatore (OPL–2 o OPL–3), si aprono in lettura rispettivamente i file std.sb e drums.sb oppure std.o3 e drums.o3 • si inizializza la struct sbi_instrument, i cui campi hanno il significato: unsigned short key specifica il tipo di patch FM e pu` essere uno dei o seguenti identificatori: FM_PATCH sintetizzatore OPL–2 OPL3_PATCH sintetizzatore OPL–3 short device numero del sintetizzatore int channel numero della patch (invocata dalla macro SEQ_SET_PATCH()) unsigned char operators[32 ] valori dei registri che programmano il sin- tetizzatore per la patch • si posiziona il cursore del file (std o drums, secondo che il numero della patch sia inferiore di 128, oppure maggiore o uguale tale valore) al numero della patch moltiplicata per 60 e si leggono sessanta byte se il sintetizzatore ` OPL–3; per un OPL–2 il procedimento ` lo stesso ma il numero della e e patch ` moltiplicato per 52 e i byte da leggere sono appunto cinquantadue e • si mettono 22 byte in operators[] a partire dalla posizione 36 del buffer • si scrive la struct sbi_instrument patch tramite la macro: SEQ_WRPATCH(&patch, sizeof(patch)); la quale controlla se il buffer contiene qualche evento: se s` ne fa il dump ı e scrive la patch con controllo d’errore, venendo passata direttamente al driver del sintetizzatore senza che sia accodata (l’esecuzione ` immediata); e ` disponibile un’altra macro, che effettua quanto sopra senza il controllo e d’errore: SEQ_WRPATCH2(&patch, sizeof(patch)); Di seguito ` riportata una semplice funzione per caricare le patch FM; il suo e primo argomento ` il numero del sintetizzatore interessato, il suo secondo argo- e mento ` il tipo di patch da caricare (FM_TYPE_ADLIB per OPL–2, FM_TYPE_OPL3 e per OPL–3): /* Path per i file che contengono gli algoritmi FM */ char STD_OPL3[] = "/etc/std.o3"; /* OPL-3 */ char DRUM_OPL3[] = "/etc/drums.o3"; char STD_OPL2[] = "/etc/std.sb"; /* OPL-2 */ char DRUM_OPL2[] = "/etc/drums.sb"; 74
  • 80.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI void FM_patch_load(int ndev, int tiposint) { int stdfd, drumfd, preset, i, lunpatch; unsigned char buf[100]; char *STD_FILE, *DRUM_FILE; struct sbi_instrument patch; if (tiposint == FM_TYPE_OPL3) { /* YMF262 */ STD_FILE = STD_OPL3; DRUM_FILE = DRUM_OPL3; patch.key = OPL3_PATCH; lunpatch = 60; } else { /* YM3812 */ STD_FILE = STD_OPL2; DRUM_FILE = DRUM_OPL2; patch.key = FM_PATCH; lunpatch = 52; } /* Apertura dei file degli algoritmi in lettura */ if ((stdfd = open(STD_FILE, O_RDONLY)) == -1) errore(STD_FILE); if ((drumfd = open(DRUM_FILE, O_RDONLY)) == -1) errore(DRUM_FILE); patch.device = ndev; /* Numero sint. */ for (preset = 0; preset < 255; preset++) /* 255 preset */ { patch.channel = preset; /* Numero del preset */ if (preset < 128) { /* Algoritmi degli strumenti */ if ((lseek(stdfd, preset*60, SEEK_SET) == -1) || (read(stdfd, buf, lunpatch) != lunpatch)) errore(STD_FILE); } else { /* Algoritmi della sezione ritmica */ if ((lseek(drumfd, (preset-128)*60, SEEK_SET) == -1) || (read(drumfd, buf, lunpatch) != lunpatch)) errore(DRUM_FILE); } /* Scrittura della patch */ for (i = 0; i < 22; i++) patch.operators[i] = buf[i+36]; SEQ_WRPATCH(&patch, sizeof(patch)); } 75
  • 81.
    4.4. PREDISPOSIZIONE DEICHIP SINTETIZZATORI CAPITOLO 4. SINTETIZZATORI E MIDI close(stdfd); close(drumfd); } 4.4.2 Caricamento delle patch wavetable OSS consente il caricamento delle patch wavetable di tipo GF1 per la Gravis UltraSound (GUS); queste sono file che possono contenere campioni di diversi strumenti o dello stesso strumento per frequenze diverse. Un file GF1 ha un header che contiene delle informazioni generali, seguito da una o pi` sezioni di campioni (waveform), ciascuna delle quali ha un suo u header con le caratteristiche della waveform stessa. Essi hanno estensione .PAT, e nell’Appendice A si pu` trovare la corrispondenza fra il loro nome e i preset o timbrici General MIDI. L’insieme dei file GF1 distribuito con la GUS ` copyright e della Voice Crystal, ma ` disponibile il patchset public domain MIDIA in [8]. e OSS fornisce una chiamata per azzerare la memoria ove sono caricate le patch per il sintetizzatore wavetable: int nsint = <numero sintetizzatore>; if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &nsint) == -1) errore("SNDCTL_SEQ_RESETSAMPLES"); pu` essere utilizzata in fase di inizializzazione, prima di caricare qualsiasi patch. o Di seguito ` l’elenco dei passi che bisogna compiere per poter caricare le patch e wavetable: • si apre in lettura il file .PAT relativo a un dato preset timbrico • si controllano alcuni campi dell’header del file GF1, in modo tale da attes- tarne la validit` come patch a • si esegue un ciclo per il caricamento di tutte le waveform presenti nel file, il quale prevede al suo interno l’allocazione di memoria dinamica per poter ospitare ogni waveform e la struttura dati che la caratterizza; quest’ultima ` la struct patch_info descritta di seguito: e unsigned short key si inizializza con GUS_PATCH short device no numero del sintetizzatore wavetable short instr no numero del preset timbrico; se ` seguito l’ordine dato in e Appendice A, i preset saranno conformi allo standard General MIDI unsigned int mode specifica il tipo di waveform e il modo di riprodurla; ` un OR aritmetico dei seguenti identificatori: e 76
  • 82.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI WAVE_16_BITS campioni della waveform a 16 bit WAVE_UNSIGNED la codifica ` unsigned e WAVE_LOOPING abilita il looping per la riproduzione della porzione centrale della waveform WAVE_BIDIR_LOOP effettua il looping in entrambi i versi (dall’inizio della porzione alla fine e dalla fine all’inizio); per default ` e effettuato solo dall’inizio alla fine WAVE_LOOP_BACK il looping procede solo dalla fine all’inizio della porzione WAVE_SUSTAIN_ON abilita il sustain (inviluppo ADSR) WAVE_ENVELOPES consente di sagomare l’inviluppo della waveform con una forma particolare WAVE_VIBRATO abilita una lieve modulazione di frequenza WAVE_TREMOLO abilita una lieve modulazione d’ampiezza WAVE_SCALE abilita la scalatura delle armoniche superiori WAVE_FRACTIONS suddivide la porzione di waveform su cui effettuare il looping in ulteriori porzioni per la modulazione in ampiezza gli identificatori da WAVE_VIBRATO in poi sono specifici di OSS, mentre gli altri seguono le convenzioni dei file GF1 int len dimensione della waveform in byte int loop start posizione di inizio del looping in byte, rispetto all’inizio della waveform loop end posizione di fine del looping in byte, rispetto all’inizio della waveform unsigned int base freq, base note quest’ultima ` l’altezza della nota e che ` udita quando si usa base_freq come frequenza di riproduzione; e la frequenza di base_note ` moltiplicata per mille (ad esempio, il LA e ` 440000) e unsigned int high note, low note rispettivamente definiscono la massi- ma e la minima frequenza delle note per le quali la waveform ` valida, e in quanto ` possibile associare pi` waveform a un dato preset timbrico e e u questi campi consentono di discriminare quella pi` adatta per la ripro- u duzione di una nota; entrambi contengono le frequenze moltiplicate per mille int panning consente il posizionamento di uno strumento fra i canali stereo sinistro e destro; -128≤ panning ≤+127, con -128 corrispondente al solo canale sinistro, +127 solo al destro e 0 al centro int detuning sposta in alto o in basso la frequenza di riproduzione, con- sentendo l’alterazione del timbro 77
  • 83.
    4.4. PREDISPOSIZIONE DEICHIP SINTETIZZATORI CAPITOLO 4. SINTETIZZATORI E MIDI unsigned char env rate[6 ], env offset[6] descrittori per il filtro di invilup- po della waveform; i valori per ogni byte si mappano in percentuali, con 0==0% e 255==100%, per ogni fase dell’inviluppo unsigned char tremolo sweep, tremolo rate, tremolo depth parametri per la lieve modulazione d’ampiezza unsigned char vibrato sweep, vibrato rate, vibrato depth parametri per la lieve modulazione di frequenza int scale frequency frequenza per la scalatura delle armoniche superiori della waveform unsigned int scale factor decide la quantit` di scalatura delle armoniche a superiori da applicare; questo parametro varia da 0 a 2048 o da 0 a 2 int volume intensit` alla quale si deve riprodurre la waveform a fractions numero di sottoporzioni in cui ` divisa la porzione di waveform e su cui si effettua il looping char data[1 ] posizione del primo campione della waveform • si esegue la scrittura della patch tramite SEQ_WRPATCH() o SEQ_WRPATCH2(); secondo la dimensione delle waveform l’intera operazione di caricamento delle waveform pu` richiedere anche dei secondi, per cui ` meglio caricare le o e patch prima che inizi l’esecuzione della partitura in modo da non provocare errori di ritmo Di seguito ` riportata una semplice funzione per caricare le patch wavetable; e il suo primo argomento ` il numero del sintetizzatore, il suo secondo argomento ` e e il preset timbrico della patch. WAVE_patch_load() fa riferimento a un vettore di stringhe patch_names[], che contiene i nomi dei file .PAT associati al preset tim- brico secondo lo standard General MIDI; ` disponibile con l’inclusione dell’header e file gmidi.h, il quale si trova in [6] nella directory sndkit/OSSlib. void WAVE_patch_load(int ndev, int preset) { int patfd, i, offset, lun_waveform; unsigned char buf[256]; unsigned short nwaveform, master_volume; struct patch_info *patch; /* Macro per la manipolazione di short e int (mem. little endian) */ #define uSHORT(b) ((unsigned short)((*(b+1)<<8)|*b)) #define SHORT(b) ((short)((*(b+1)<<8)|*b)) #define INT(b) ((int)((*(b+3)<<24)|(*(b+2)<<16)|(*(b+1)<<8)|*b)) if (patch_names[preset][0] = ’0’) { /* La patch e’ definita? */ fprintf(stderr, "Preset %d: patch non definita!n", preset); return; 78
  • 84.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.4. PREDISPOSIZIONE DEI CHIP SINTETIZZATORI } else { /* La patch e’ definita: prova ad aprirla in lettura */ strcat(NOME_PATCH, patch_names[preset]); strcat(NOME_PATCH, ".pat"); if ((patfd = open(NOME_PATCH, O_RDONLY)) == -1) errore(NOME_PATCH); } /* Controlli sulla validita’ della patch */ if (read(patfd, buf, 0xEF) != 0xEF) { /* Lettura del patch header */ fprintf(stderr, "Patch %d (%s): file troppo corto!n", preset, NOME_PATCH); return; } if (strncmp(buf, "GF1PATCH110", 12)) { /* ID patch GF1 */ fprintf(stderr, "%s: non e’ un patch file!n", NOME_PATCH); return; } if (strncmp(&buf[12], "ID#000002", 10)) { /* ID creatore */ fprintf(stderr, "%s: versione patch incompatibile!n", NOME_PATCH); return; } nwaveform = uSHORT(&buf[85]); /* Numero di waveform */ master_volume = uSHORT(&buf[87]); /* Volume per tutti i campioni */ offset = 0xEF; /* Posizione primo instr. header */ /* Ciclo per il caricamento delle waveform dal patch file */ for (i = 0; i < nwaveform; i++) { if (lseek(patfd, offset, SEEK_SET) == -1) errore(NOME_PATCH); if (read(patfd, buf, 64) != 64) { /* Lettura instrument header */ fprintf(stderr, "Patch %d (%s): file troppo corto!n", preset, NOME_PATCH); return; } offset += 96; lun_waveform = INT(&buf[8]); /* Lunghezza waveform in byte */ if ((patch = (struct patch_info *) malloc(sizeof(*patch) + lun_waveform)) == NULL) { fprintf(stderr, "Patch %d (%s): non sono riuscito ad " "allocare %d byte!n", preset, NOME_PATCH, sizeof(*patch) + lun_waveform); return; } 79
  • 85.
    4.4. PREDISPOSIZIONE DEICHIP SINTETIZZATORI CAPITOLO 4. SINTETIZZATORI E MIDI /* Inizializzazione dei campi della struct patch_info *patch */ patch->key = GUS_PATCH; patch->instr_no = preset; patch->mode = (unsigned char)buf[55] | WAVE_TREMOLO | WAVE_VIBRATO | WAVE_SCALE; patch->len = lun_waveform; patch->loop_start = INT(&buf[12]); patch->loop_end = INT(&buf[16]); patch->base_freq = uSHORT(&buf[20]); patch->low_note = INT(&buf[22]); patch->high_note = INT(&buf[26]); patch->base_note = INT(&buf[30]); patch->detuning = SHORT(&buf[34]); patch->panning = ((unsigned char)buf[36] - 7) * 16; memcpy(patch->env_rate, &buf[37], 6); memcpy(patch->env_offset, &buf[43], 6); patch->tremolo_sweep = (unsigned char)buf[49]; patch->tremolo_rate = (unsigned char)buf[50]; patch->tremolo_depth = (unsigned char)buf[51]; patch->vibrato_sweep = (unsigned char)buf[52]; patch->vibrato_rate = (unsigned char)buf[53]; patch->vibrato_depth = (unsigned char)buf[54]; patch->scale_frequency = SHORT(&buf[56]); patch->scale_factor = uSHORT(&buf[58]); patch->volume = master_volume; /* Caricamento della waveform dal patch file */ if (lseek(patfd, offset, SEEK_SET) == -1) errore(NOME_PATCH); if (read(patfd, patch->data, lun_waveform) != lun_waveform) { fprintf(stderr, "Patch %d (%s): file troppo corto!n", preset, NOME_PATCH); return; } /* Passa la patch al driver e poi libera la memoria allocata */ SEQ_WRPATCH(patch, sizeof(*patch) + lun_waveform); offset += lun_waveform; free(patch); } close(patfd); } 80
  • 86.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.5. LA TEMPORIZZAZIONE 4.4.3 Caricamento delle sysex patch Ci sono dei sintetizzatori wavetable che accettano il caricamento di patch tramite delle system exclusive; uno di questi si trova nella scheda audio Turtle Beach Maui. OSS supporta questo meccanismo tramite la struct sysex_info, i cui campi hanno il seguente significato: short key questo campo ` da inizializzare con l’identificatore SYSEX_PATCH o con e MAUI_PATCH, rispettivamente se si deve trasferire una sysex patch generica o una Maui patch short device no numero del sintetizzatore int len dimensione dei dati della patch in byte unsigned char data[1 ] la sysex patch inizia qui: il primo byte deve valere 0xF0 e l’ultimo 0xF7; ci` che sta in mezzo ` responsabilit` del programmatore o e a (una descrizione del formato delle sysex Maui pu` essere trovato in [9]) o Il seguente frammento di codice evidenzia il modo in cui il caricamento viene effettuato: struct sysex_info *patch; int lun_patch = <dimensione della sysex patch in byte>; if ((patch = (struct sysex_info *) malloc(sizeof(*patch) + lun_patch)) == NULL) { fprintf(stderr, "Sysex patch: non sono riuscito ad allocare " "%d byte!n", sizeof(*patch) + lun_patch); exit(-1); } /* Inizializzazione dei campi puntati da patch */ patch->key = SYSEX_PATCH; /* o MAUI_PATCH */ patch->device_no = <numero sintetizzatore o porta>; patch->len = lun_patch; /* Si mette la sysex patch a partire da patch->data */ /* ad esempio con read(patfd, patch->data, lun_patch) */ /* primo byte: MIDI_SYSTEM_PREFIX (0xF0), ultimo: 0xF7 */ SEQ_WRPATCH(patch, sizeof(*patch) + lun_patch); 4.5 La temporizzazione Si ` gi` avuto modo di dire che la coda degli eventi ` interpretata dal driver sec- e a e ondo la temporizzazione fornita da eventi marcatempo, inseriti nel buffer tramite 81
  • 87.
    4.5. LA TEMPORIZZAZIONE CAPITOLO 4. SINTETIZZATORI E MIDI opportune macro. Quando il driver ne incontra uno aspetta per il tempo indica- to dall’evento prima di riprendere l’interpretazione degli altri eventi in coda, che possono essere di esecuzione o di cambiamento di configurazione; questi ultimi vir- tualmente non introducono ritardi: ad esempio, tre NOTE ON di seguito possono costituire un accordo, mentre interponendo fra ogni nota un evento marcatempo si otterr` un arpeggio. a L’attesa ` espressa in tick : il timer inizia a contare sequenzialmente dalla sua e attivazione, per cui si pu` aspettare fino a un tempo espresso in termini assoluti o tramite la macro seguente: SEQ_WAIT_TIME(<tick>); ad esempio, SEQ_WAIT_TIME(60) inserisce in coda un evento marcatempo che provoca l’attesa della routine che scandisce la coda degli eventi finch´ il timer in- e terno non ha raggiunto il valore di 60 tick; se dopo fosse seguito da SEQ_WAIT_TIME(40) tale evento non avrebbe effetto, in quanto si ` gi` passato il conteggio di 40 tick. e a Gli eventi marcatempo possono essere espressi anche in termini relativi con la seguente macro: SEQ_DELTA_TIME(<tick>); in tal caso l’evento costringe ad aspettare la routine che scandisce la coda degli eventi per il numero di tick sopra specificato. Questo metodo per esprimere le attese ` il pi` delle volte preferibile, in quanto la durata delle note risulta e u meglio “manipolabile” in termini relativi piuttosto che assoluti. Ad esempio, un Note ON seguito da SEQ_DELTA_TIME(100) potrebbe esprimere una nota da un quarto; con un’attesa assoluta, sapendo che il timer era arrivato magari a un conteggio di 50 tick, lo stesso effetto lo si otterrebbe col Note ON seguito da SEQ_WAIT_TIME(150). L’inserimento di una nuova nota implica il ricalcolo di tutte le attese se queste sono espresse in termini assoluti, cosa che non avviene esprimendole in termini relativi. Se si vogliono eseguire delle variazioni di tempo ` prevista la macro seguente: e SEQ_SET_TEMPO(<bpm>); ove <bpm> sono le battute/minuto (BPM), comprese tra 8 e 360 (60 per default dopo l’apertura del device file); ogni battuta corrisponde a una nota da un quarto e dura un certo numero di tick secondo il tempo selezionato. Ad esempio, un dimezzamento del tempo di esecuzione di una sequenza di eventi lo si ottiene con SEQ_SET_TEMPO(30). Il timer pu` esser rispettivamente fatto partire (azzerato), fermato e fatto o continuare dalla posizione in cui era arrivato con le seguenti chiamate: ioctl(seqfd, SNDCTL_TMR_START); ioctl(seqfd, SNDCTL_TMR_STOP); ioctl(seqfd, SNDCTL_TMR_CONTINUE); 82
  • 88.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.5. LA TEMPORIZZAZIONE Ci sono delle macro che generano eventi con effetti equivalenti a quelli delle chiamate sopra elencate: SEQ_START_TIMER(); SEQ_STOP_TIMER(); SEQ_CONTINUE_TIMER(); la differenza ` che l’effetto di queste si avr` quando il driver raggiunger` l’evento e a a relativo nella coda degli eventi, poich´ l’elaborazione di quest’ultima ` sincrona e e e sequenziale come per l’esecuzione di una partitura musicale. Le chiamate sono invece asincrone, cio` ottengono il loro scopo nel momento in cui il flusso del e programma le raggiunge. ` E d’obbligo far notare una differenza fra i due device file: per /dev/music la macro SEQ_START_TIMER() deve essere chiamata prima di tutte le altre macro, al- trimenti il timer non parte e l’applicazione aspetta infinitamente. Per /dev/sequencer non ` obbligatorio chiamarla, poich´ il timer si avvia automaticamente al primo e e SEQ_DUMPBUF(). 4.5.1 La sincronizzazione per /dev/music Con /dev/music esiste la possibilit` di variare la risoluzione del timer; ci` si a o effettua con la seguente chiamata: int frequenza = <frequenza in Hz>; if (ioctl(seqfd, SNDCTL_SEQ_CTRLRATE, &frequenza) == -1) errore("SNDCTL_SEQ_CTRLRATE"); 1000 di conseguenza la risoluzione temporale sar` di <frequenza in Hz> ms; se si pone a frequenza pari a 0 la chiamata restituisce la lettura della risoluzione attuale. ` E stata introdotta la possibilit` di esprimere la durata delle note in termini a relativi usufruendo della Sincronizzazione Timebase, proposta originariamente da Roland: il metodo ` basato sull’assegnazione di una certa durata in tick alla e nota da un quarto; questa a sua volta corrisponde a una battuta del metronomo. Tempo e attese sono ancora manipolati rispettivamente da SEQ_SET_TEMPO() e da SEQ_WAIT_TIME() o SEQ_DELTA_TIME(). Originariamente si adottarono 24 tick/battuta (Pulse Per Quarter Note — PPQN o PPQ), ma sono normali anche 48 o 96 PPQN (in certi sequencer si arriva anche a 480 o 960). La ragione per cui sono adottati questi valori ` presto e detta: sono tutti numeri divisibili per 2, 3 o 4; ci` permette l’assegnazione di o un numero intero di note ad ogni duina, terzina o quartina. Le assegnazioni per quintine e settimine risultano invece inadeguate, in quanto i numeri non sono divisibili per 5 e per 7. L’indicazione temporale ` completa quando sia specificato anche il numero di e battute al minuto: ad esempio, se inizialmente si ` fissato un tempo di 60 BPM, il e raddoppio della velocit` di esecuzione ` ottenuto portando il tempo a 120 BPM. a e 83
  • 89.
    4.5. LA TEMPORIZZAZIONE CAPITOLO 4. SINTETIZZATORI E MIDI Infatti la variazione delle battute/minuto non varia i tick/battuta, per cui rad- doppia anche la frequenza di svolgimento dei tick pur permanendo invariato il valore della nota. Per ricavare la durata assoluta di una nota dai PPQN e BPM, se si conosce la sua durata espressa in tick, si pu` applicare la seguente formula: o 60000 durata della nota in ms = (durata espressa in tick) · PPQN · BPM La definizione dei PPQN e dei BPM si pu` effettuare subito dopo l’apertura o del device file tramite le seguenti chiamate: int ppqn = <tick>, bpm = <bpm>; if (ioctl(seqfd, SNDCTL_TMR_TIMEBASE, &ppqn) == -1) errore("SNDCTL_TMR_TIMEBASE"); if (ioctl(seqfd, SNDCTL_TMR_TEMPO, &bpm) == -1) errore("SNDCTL_TMR_TEMPO"); ove 1 ≤ppqn≤ 1000 e 8 ≤bpm≤ 250. La Roland MPU–401 Con /dev/music ` stata anche introdotta la possibilit` di poter gestire certe e a caratteristiche della MPU–401, una scheda MIDI che ha sofisticate capacit` di a gestione dei dati MIDI e dei sincronismi esterni: questi possono essere segnali ana- logici provenienti da registratori multitraccia o da videoregistratori (ad esempio, FSK o SMPTE). La chiamata che consente di scegliere fra i due tipi di timer ` la seguente: e int modo_timer = <identificatore modo>; if (ioctl(seqfd, SNDCTL_TMR_SOURCE, &modo_timer) == -1) errore("SNDCTL_TMR_SOURCE"); ove <identificatore modo> ` rispettivamente TMR_INTERNAL (clock di sistema) e o TMR_EXTERNAL (sincronismo esterno). Nel caso la scheda supporti il sincronismo esterno si pu` sceglierne il tipo facendo un OR aritmetico fra TMR_EXTERNAL e o uno dei seguenti identificatori: Modo sincronismo esterno Per selezionare... TMR_MODE_MIDI MIDI Timing Clock (MIDI SYNC), associabile a messaggi SPP (Song Position Pointer) TMR_MODE_FSK Frequency–Shift Keying (FSK), riferimento di clock proveniente da un registratore TMR_MODE_CLS Clear Screen (CLS), generato a ogni screen blank (25 o circa 30 Hz) TMR_MODE_SMPTE Society for Motion Picture and Television Engineers (SMPTE), riferimento assoluto proveniente da un videoregistratore 84
  • 90.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.5. LA TEMPORIZZAZIONE in modo_timer il driver ritorna <identificatore modo> solo se ` in grado di e supportarlo. Ad esempio, se si vuole selezionare come sincronismo esterno il MIDI SYNC, si pu` utilizzare il seguente frammento di codice: o int modo_timer = TMR_EXTERNAL | TMR_MODE_MIDI; ioctl(seqfd, SNDCTL_TMR_SOURCE, &modo_timer); if (modo_timer != (TMR_EXTERNAL | TMR_MODE_MIDI)) { /* Il driver non supporta tale sincronizzazione */ } Una caratteristica della MPU–401 ` che non supporta tutti i possibili valori e di ppqn, per cui il driver effettua una conversione per adattare ci` che la scheda o riesce a fornire rispetto a quanto richiesto; in particolare questo valore ` limitato e fra 48 e 1000. Il cosiddetto intelligent mode della MPU–401 consente di poter gestire la temporizzazione con la quale la scheda invia i byte MIDI; la chiamata seguente predispone i valori temporali per una misura: int numeratore = <num>, denominatore = <den>; int ppqn = <ppqn>, trent_b = <tb>; int misura = (numeratore<<24)|(denominatore<<16)|(ppqn<<8)|trent_b; ioctl(seqfd, SNDCTL_TMR_METRONOME, &misura); <num> e <den> costituiscono numeratore e denominatore della frazione che es- prime il tempo per una misura, mentre <ppqn> sono i tick che esprimono la durata della nota da un quarto e <tb> sono i trentaduesimi di battuta. Alla scheda sono inviati ppqn e le battute/misura=(4*numeratore)>>denominatore, abilitando la funzione metronomo senza accenti. Se misura==0, questa funzione ` disabilitata. e La macro seguente genera un evento equivalente alla chiamata precedente: SEQ_TIME_SIGNATURE(<misura>); ove <misura> ha lo stesso formato della variabile misura. 4.5.2 La sincronizzazione per /dev/sequencer Con /dev/sequencer non si ha la possibilit` di variare la risoluzione del timer a n´ di usufruire della Sincronizzazione Timebase, per cui le possibilit` di temporiz- e a zazione sono quelle elencate all’inizio: si usa SEQ_WAIT_TIME() o SEQ_DELTA_TIME(), variando il tempo dell’esecuzione con SEQ_SET_TEMPO(). Il timer su cui si basa /dev/sequencer ` quello del kernel, che dovrebbe avere e una frequenza fissa di 100 Hz per tutte le architetture su cui ` stato portato OSS. e Ci` tuttavia non ` garantito (ad esempio, il timer del kernel per Linux/Alpha ha o e una frequenza di 1024 Hz), quindi se si vuole avere la sicurezza si pu` utilizzare o la chiamata SNDCTL_SEQ_CTRLRATE con frequenza=0 (un altro valore causer` a errore). 85
  • 91.
    4.6. OUTPUT DEGLIEVENTI CAPITOLO 4. SINTETIZZATORI E MIDI 4.6 Output degli eventi Come si ` sin qui visto, /dev/music ha caratteristiche superiori per indipenden- e za dal dispositivo rispetto a /dev/sequencer; quest’ultimo tuttavia consente la possibilit` di accesso a pi` basso livello a una porta MIDI tramite la seguente a u macro: SEQ_MIDIOUT(<numero porta MIDI>, <byte>); essa spedisce alla porta <numero porta MIDI> un arbitrario <byte>, inframez- zandolo con i normali eventi posti nel buffer dalle altre macro; costituisce un evento lungo quattro byte. Tutte le altre macro che verranno in seguito descritte si comportano allo stesso modo sia per /dev/music che per /dev/sequencer, salvo il fatto che per il pri- mo device file l’allocazione delle voci ` automatica (il chip sintetizzatore sembra e comportarsi come una porta MIDI). In figura 4.2 ` visibile l’organizzazione degli eventi, che ricalca fedelmente e quella con cui sono organizzati i messaggi Standard MIDI (nella descrizione delle macro che li creano sono evidenziate le differenze rispetto a questi). System Common Voice MESSAGGI MESSAGGI Real Time DI CANALE DI SISTEMA Mode System Exclusive Figura 4.2: Organizzazione degli eventi come i messaggi standard MIDI Come raccomandazione, l’ultimo evento scritto nel buffer prima della chiusura del device file dovrebbe essere un’attesa, per evitare che lo svuotamento del buffer provocato da close() tronchi l’esecuzione dell’ultima nota. Alternativamente si pu` utilizzare la seguente chiamata: o ioctl(seqfd, SNDCTL_SEQ_SYNC); che risulta bloccante per il processo chiamante finch´ la coda degli eventi non si e ` svuotata. e 4.6.1 Messaggi di canale CHANNEL VOICE MESSAGES Note ON: SEQ_START_NOTE(<dev>, <can>, <nota>, <vol>); 86
  • 92.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.6. OUTPUT DEGLI EVENTI inizia a suonare <nota> con volume o key velocity <vol> sul canale <can> del dispositivo <dev>; per i sintetizzatori interni se nota==255 il driver non inizia una nuova nota, ma regola il volume di quella attuale (questa carat- teristica pu` non funzionare con tutte le schede audio), mentre se vol==255 o il driver user` per <nota> il volume della nota precedente e, solo per a /dev/music, SEQ_START_NOTE() con vol==0 equivale a SEQ_STOP_NOTE(<dev>, <can>, Note OFF: SEQ_STOP_NOTE(<dev>, <can>, <nota>, <vol>); termina <nota> in esecuzione sul canale <can>, ma non ha effetto se questa ` gi` decaduta o non ` mai iniziata, e se non si conosce <vol> si pu` utiliz- e a e o zare 64; generalemente il Note OFF non termina la nota immediatamente, poich´ il tempo di rilascio dipende dalle caratteristiche del preset timbrico e selezionato (potrebbe essere pi` di dieci secondi) u Polyphonic key pressure: SEQ_KEY_PRESSURE(<dev>, <can>, <nota>, <pres>); modula l’aftertouch <pres> per la singola <nota> in esecuzione sul canale <can>; per un chip sintetizzatore interno alla scheda audio quest’evento si traduce in genere in una lieve modulazione di frequenza del suono (OPL–3) Channel pressure/Aftertouch: SEQ_CHN_PRESSURE(<dev>, <can>, <pres>); modula l’aftertouch <pres> per tutte le note in esecuzione sul canale <can>; con /dev/sequencer il canale corrisponde a una voce per il chip sinte- tizzatore interno, per cui quest’evento ` corrispondente al Polyphonic key e pressure Control change: SEQ_CONTROL(<dev>, <can>, <ctl>, <valore>); predispone il valore di un controller MIDI (prima o dopo che la nota sia suonata); nella pagina dopo ` riportata la lista (1997) dei controller <ctl> e supportati da OSS: con /dev/music sono specifici per un canale (ne in- fluenzano le note) e i valori rimangono in effetto finch´ sono esplicitamente e cambiati con un altro Control change, mentre con /dev/sequencer sono specifici per una voce e sono resettati al loro default dopo un Note OFF (devono essere ripredisposti prima di un Note ON, con il loro range numerico che differisce dal MIDI) Program change: SEQ_SET_PATCH(<dev>, <can>, <preset>); cambia il <preset> timbrico per il canale <can>; la patch relativa deve essere gi` stata caricata per quanto riguarda il sintetizzatore interno alla a scheda audio, altrimenti il driver produrr` un messaggio su stderr e non a si udir` alcun suono a 87
  • 93.
    4.6. OUTPUT DEGLIEVENTI CAPITOLO 4. SINTETIZZATORI E MIDI Numero Tipo Nome Controller MIDI Identificatore OSS 0x00 Bank Select CTL_BANK_SELECT 0x01 Modulation Wheel or Lever CTL_MODWHEEL 0x02 Breath Controller CTL_BREATH 0x03 undefined 0x04 Foot Controller CTL_FOOT 0x05 Portamento Time CTL_PORTAMENTO_TIME 0x06 Data Entry MSB CTL_DATA_ENTRY 0x07 Main Volume CTL_MAIN_VOLUME 0x08 MSB Continui Balance CTL_BALANCE 0x09 di Base undefined 0x0A PAN CTL_PAN 0x0B Expression Controller CTL_EXPRESSION 0x0C÷0x0F undefined 0x10 General Purpose #1 CTL_GENERAL_PURPOSE1 0x11 General Purpose #2 CTL_GENERAL_PURPOSE2 0x12 General Purpose #3 CTL_GENERAL_PURPOSE3 0x13 General Purpose #4 CTL_GENERAL_PURPOSE4 0x14÷0x1F undefined 0x20÷0x3F LSB Cont. di Base non definiti 0x40 Damper Pedal (Sustain) CTL_DAMPER_PEDAL CTL_SUSTAIN o CTL_HOLD 0x41 Switch Portamento CTL_PORTAMENTO 0x42 (ON/OFF) Sostenuto CTL_SOSTENUTO 0x43 Soft Pedal CTL_SOFT_PEDAL 0x44 Legato Footswitch non definito 0x45 Hold 2 CTL_HOLD2 0x46÷0x4F Sound Controller #1–10 non definiti 0x50 General Purpose #5 CTL_GENERAL_PURPOSE5 0x51 General Purpose #6 CTL_GENERAL_PURPOSE6 0x52 General Purpose #7 CTL_GENERAL_PURPOSE7 0x53 General Purpose #8 CTL_GENERAL_PURPOSE8 0x54÷0x5A undefined 0x5B Effect 1: Ext Effect Depth CTL_EXT_EFF_DEPTH 0x5C Continui Effect 2: Tremolo Depth CTL_TREMOLO_DEPTH 0x5D addizionali Effect 3: Chorus Depth CTL_CHORUS_DEPTH 0x5E Effect 4: Detune Depth CTL_DETUNE_DEPTH (Celeste Depth) CTL_CELESTE_DEPTH 0x5F Effect 5: Phaser Depth CTL_PHASER_DEPTH 0x60 Data Increment CTL_DATA_INCREMENT 0x61 Data Decrement CTL_DATA_DECREMENT 0x62 Non Reg. Par. Number LSB CTL_NONREG_PARM_NUM_LSB 0x63 Non Reg. Par. Number MSB CTL_NONREG_PARM_NUM_MSB 0x64 Regist. Par. Number LSB CTL_REGIST_PARM_NUM_LSB 0x65 Regist. Par. Number MSB CTL_REGIST_PARM_NUM_MSB 0x66÷0x78 undefined 88
  • 94.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.6. OUTPUT DEGLI EVENTI Pitch bend change: SEQ_BENDER(<dev>, <can>, <valore>); fa le funzioni di pitch bender per il canale <can>: -8192≤ <valore> ≤+8191, si pu` applicare prima o dopo l’inizio di una nota e al Note OFF ritorna a o zero (default); l’intervallo del bender ` ±2 semitoni e CHANNEL MODE MESSAGES Sono effettivi solo per il MIDI e si inviano usando: SEQ_CONTROL(<dev>, <can>, <oxXX>, <0xYY>); Reset all controllers: 0xXX==0x79, 0xYY==0x00 Local control ON/OFF: 0xXX==0x7A, 0xYY==0x00 (OFF) oppure 0xYY==0x80 (ON) All notes OFF: 0xXX==0x78, 0xYY==0x00 Omni mode OFF: 0xXX==0x7C, 0xYY==0x00 Omni mode ON: 0xXX==0x7D, 0xYY==0x00 Mono mode ON: 0xXX==0x7E, 0xYY==0x0m (riserva i canali MIDI da <can> a <can>+m-1) Poly mode ON: 0xXX==0x7F, 0xYY==0x00 4.6.2 Messaggi di sistema SYSTEM COMMON MESSAGES Espletano particolari funzioni MIDI: MIDI Time Code quarter frame: non c’` una macro che generi un evento e corrispondente; pu` essere gestito automaticamente da una scheda MIDI o come la MPU–401 Song position pointer: SEQ_SONGPOS(<pos>); se il timer ` esterno utilizzando una MPU–401, l’evento corrispondente a e questa macro ` depositato nel buffer di input; non ha effetto in output e Song select: pu` essere utilizzata SEQ_MIDIOUT() per spedire i byte MIDI 0xF3 o e 0xXX, con 0x00≤ 0xXX ≤0x7F Tune request: pu` essere utilizzata SEQ_MIDIOUT() per spedire il byte MIDI o 0xF6 89
  • 95.
    4.6. OUTPUT DEGLIEVENTI CAPITOLO 4. SINTETIZZATORI E MIDI REAL TIME MESSAGES Sono costituiti da un solo byte e possono essere inviati in qualsiasi momento, anche inframezzandosi ai byte MIDI di altri messaggi; le macro dell’interfaccia di programmazione del sequencer non consentono di far ci`, tuttavia le chiamate del o tipo SNDCTL_TMR_**** possono emulare tale comportamento in quanto asincrone rispetto alla gestione della coda degli eventi effettuata dalla routine del driver. Timing clock: ` generato automaticamente da schede MIDI come la MPU–401 e Start: SEQ_START_TIMER(); la controparte asincrona ` la chiamata SNDCTL_TMR_START; pu` essere sped- e o ito anche col SEQ_MIDIOUT() di 0xFA Stop: SEQ_STOP_TIMER(); la controparte asincrona ` la chiamata SNDCTL_TMR_STOP; pu` essere spedito e o anche col SEQ_MIDIOUT() di 0xFC Continue: SEQ_CONTINUE_TIMER(); la controparte asincrona ` la chiamata SNDCTL_TMR_CONTINUE; pu` essere e o spedito anche col SEQ_MIDIOUT() di 0xFB Active sensing: questo messaggio ` gestito automaticamente in output dalla e MPU–401, mentre il sequencer lo ignora in input System reset: pu` essere spedito col SEQ_MIDIOUT() di 0xFF o SYSTEM EXCLUSIVE MESSAGES SEQ_SYSEX(<dev>, <buf>, <lun>); Permette di spedire dei sysex, ma non dei byte MIDI arbitrari (ci` causerebbe o dei problemi con l’intelligent mode dell’MPU–401), e il messaggio deve essere spezzato in blocchi da sei byte l’uno (cio` SEQ_SYSEX() deve essere richiamato e pi` volte per messaggi pi` lunghi di sei byte): <buf>, che ospita tali byte, ` quindi u u e un vettore di sei unsigned char e <lun> ` la lunghezza di un singolo blocco e (lun==6 per ogni blocco, tranne l’ultimo). Il primo blocco ha per primo byte 0xF0 (corrispondente all’identificatore MIDI_SYSTEM_PREFIX) e l’ultimo blocco ` ha nell’ultimo byte 0xF7. E un evento solo di output. In soundcard.h versione 3.5.4 SEQ_SYSEX() ha un errore, che ` stato corretto e nelle versioni successive: nel byte numero 1 del record andrebbe immagazzinato <dev>, cosa che non avviene. Il rimedio a ci` ` spedire il primo blocco della sysex oe con il primo byte uguale a <dev> e il resto dei byte normalmente definiti, oppure si pu` modificare SEQ_SYSEX() in soundcard.h come segue: o 90
  • 96.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.6. OUTPUT DEGLI EVENTI #define SEQ_SYSEX(dev, buf, len) {int i, l=(len); if (l>6)l=6; _SEQ_NEEDBUF(8); _seqbuf[_seqbufptr] = EV_SYSEX; _seqbuf[_seqbufptr+1] = (dev); for(i=0;i<l;i++)_seqbuf[_seqbufptr+i+2] = (buf)[i]; for(i=l;i<6;i++)_seqbuf[_seqbufptr+i+2] = 0xFF; _SEQ_ADVBUF(8);} 4.6.3 Controllo della coda degli eventi Come si ` avuto modo di dire in precedenza, la struttura del sequencer ruo- e ta attorno alla coda degli eventi: questa ha il compito principale di prevenire l’esecuzione in ritardo degli eventi stessi, ed ` gestita dal driver con una tempo- e rizzazione abbastanza precisa. Tuttavia c’` una grossa limitazione legata a questa e architettura: la coda introduce una grossa latenza nell’elaborazione degli eventi. Per cercare di aggirare questo problema ` disponibile la seguente chiamata: e struct seq_event_rec evento; /* Si riempie evento.arr[] (8 byte) */ ioctl(seqfd, SNDCTL_SEQ_OUTOFBAND, &evento); essa esegue immediatamente l’output di evento, effettuando il by–pass di altri eventi eventualmente in coda; la struct seq_event_rec ha un unico campo: unsigned char arr[8 ] vettore in grado di ospitare un evento da otto byte, sia per /dev/music che per /dev/sequencer; il formato di ogni evento che pu`o contenere ` descritto nella prossima Sezione 4.7 e questa chiamata non ` comunque adatta alla spedizione di eventi di tipo sys- e tem exclusive (se non quelli da sei byte al massimo), poich´ gli eventi spediti e da SNDCTL_SEQ_OUTOFBAND si inframezzano a quelli nella coda, mentre i sysex devono essere contigui; inoltre, proprio perch´ il driver non tiene conto di quali e eventi siano in esecuzione quando ` effettuato l’output di evento, questa chiama- e ta potrebbe avere l’effetto collaterale di far perdere traccia delle note che stanno suonando. Un altro problema della coda degli eventi ` di poter risultare bloccante per e il processo se essa di riempie, qualora non si sia usato O_NONBLOCK nell’apertura del device file: in tal caso il comportamento di default del driver ` di sbloccare il e processo quando la coda si ` svuotata per met`. Ad esempio, se la coda di output e a pu` ospitare 1024 eventi (che ` la dimensione di default, se uno non ricompila i o e sorgenti del driver con un valore diverso), quando si avranno 512 posti liberi per gli eventi da porre in coda (soglia di output) il processo sar` sbloccato. a Una chiamata consente di modificare il valore della soglia di output: int soglia = <valore>; ioctl(seqfd, SNDCTL_SEQ_THRESHOLD, &soglia); 91
  • 97.
    4.7. FORMATO DEGLIEVENTI CAPITOLO 4. SINTETIZZATORI E MIDI ove soglia ` compreso tra 0 e il massimo numero di eventi in coda meno uno; se e la soglia di output cresce il processo sar` bloccato pi` a lungo. a u Esiste la possibilit` di realizzare output non bloccante avvalendosi della seguente a chiamata: int neventi; ioctl(seqfd, SNDCTL_SEQ_GETOUTCOUNT, &neventi); essa ritorna in neventi il numero di eventi che possono essere ancora inseriti in coda prima che l’inserimento di un ulteriore evento risulti bloccante per il processo utente. Questa chiamata, effettuata quando la coda ` vuota (ad esempio, quando e ancora non ` stato effettuato alcun output di eventi), ritorna il massimo numero e di eventi ospitabili in coda; se il device file ` stato aperto con O_RDONLY essa e ritorna neventi==0. In pratica, affinch´ il processo non sia bloccato, si deve sempre controllare e che il numero di eventi da accodare con SEQ_DUMPBUF() (tenendo quindi conto del numero di eventi nel buffer utente) sia sempre inferiore a neventi. Un’altra possibilit` ` aprire il device file con O_NONBLOCK e scrivere un evento per volta con ae write(); quando ` restituito -1 e errno==EAGAIN la coda degli eventi ` piena: e e if (write(seqfd, buf, <dim. evento>) == -1) { /* Se errno==EAGAIN la coda di output e’ piena */ } Sull’output degli eventi c’` da dire un’ultima cosa, ovvero che l’interfaccia di e programmazione di OSS rende disponibile una macro per la sincronizzazione del processo utente con la coda degli eventi: SEQ_ECHO_BACK(<codice>); essa mette nella coda di output un evento che, alla sua esecuzione da parte del driver, provoca l’inserimento nella coda di input di un evento uguale (se il device file ` stato aperto con O_RDWR). Il processo utente ` cos` in grado di sapere, quan- e e ı do tale evento ` letto dalla coda di input, che la sequenza di output degli eventi e ha raggiunto il punto in cui esso era stato inserito; <codice> reca 32 bit di in- formazione arbitraria se si sta usando /dev/music o 24 bit per /dev/sequencer (come si vedr` in Sezione 4.8). Ad esempio, <codice> potrebbe essere un conta- a tore progressivo inviato ad ogni fine misura, per sincronizzare il processo utente con l’esecuzione della partitura. 4.7 Formato degli eventi Nel buffer o nella coda degli eventi, un evento ` un record di quattro o otto e byte formattato dalle macro precedentemente descritte. Adesso li si esaminer` ina dettaglio, anche se l’approccio sin qui seguito ha volutamente trascurato i dettagli 92
  • 98.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.7. FORMATO DEGLI EVENTI d’implementazione, perch´ ` necessario sapere come gli eventi sono composti se ee si vuole utilizzare la chiamata SNDCTL_SEQ_OUTOFBAND o se si vuole effettuare l’input di eventi. Per quanto riguarda /dev/music, gli eventi sono cos` composti: ı • il primo byte descrive il tipo, per cui ` uno dei seguenti identificatori: e EV_CHN_VOICE, EV_CHN_COMMON, EV_TIMING, EV_SYSEX; il primo ` un even- e to di sistema (riguarda la temporizzazione), gli altri sono eventi a livello di dispositivo (canale o porta) • per EV_CHN_VOICE e EV_CHN_COMMON il secondo byte ` il numero del dis- e positivo, mentre per EV_TIMING ` il sottotipo di evento di temporizzazione: e TMR_START, TMR_STOP, TMR_CONTINUE, TMR_WAIT_ABS, TMR_WAIT_REL, TMR_ECHO, TMR_TEMPO, TMR_SPP, TMR_TIMESIG • per EV_CHN_VOICE e EV_CHN_COMMON il terzo byte esprime l’azione da in- traprendere; pu` essere uno dei seguenti identificatori MIDI: MIDI_NOTEON, o MIDI_NOTEOFF, MIDI_KEY_PRESSURE, MIDI_CHN_PRESSURE, MIDI_CTL_CHANGE, MIDI_PGM_CHANGE, MIDI_PITCH_BEND Nelle seguenti tabelle si riporta come sono composti i record per ogni evento, raggruppati secondo il tipo; se il record ` memorizzato in unsigned char buf[8], e le posizioni che seguono il nome della macro per l’evento corrispondono ai byte contenuti in buf[]. Eventi per le voci 0 1 2 3 4 5 6 7 SEQ_START_NOTE() EV_CHN_VOICE dev MIDI_NOTEON can nota vol 0 0 SEQ_STOP_NOTE() EV_CHN_VOICE dev MIDI_NOTEOFF can nota vol 0 0 SEQ_KEY_PRESSURE() EV_CHN_VOICE dev MIDI_KEY_PRESSURE can nota pres 0 0 Eventi per i canali 0 1 2 3 4 5 6 7 SEQ_CHN_PRESSURE() EV_CHN_COMMON dev MIDI_CHN_PRESSURE can pres 0 0 0 SEQ_CONTROL() EV_CHN_COMMON dev MIDI_CTL_CHANGE can ctl 0 LSB val MSB val SEQ_SET_PATCH() EV_CHN_COMMON dev MIDI_PGM_CHANGE can patch 0 0 0 SEQ_BENDER() EV_CHN_COMMON dev MIDI_PITCH_BEND can 0 0 LSB val MSB val NOTA: per SEQ_CONTROL() e SEQ_BENDER() si accede allo short immagazzinato nei byte 6 e 7 con *(short *)&buf[6]; la rappresentazione in tabella ` per e un’architettura little endian 93
  • 99.
    4.8. INPUT DEGLIEVENTI CAPITOLO 4. SINTETIZZATORI E MIDI Eventi temporali 0 1 2 3 4 5 6 7 SEQ_START_TIMER() EV_TIMING TMR_START 0 0 0 SEQ_STOP_TIMER() EV_TIMING TMR_STOP 0 0 0 SEQ_CONTINUE_TIMER() EV_TIMING TMR_CONTINUE 0 0 0 SEQ_WAIT_TIME() EV_TIMING TMR_WAIT_ABS 0 0 tick SEQ_DELTA_TIME() EV_TIMING TMR_WAIT_REL 0 0 tick SEQ_ECHO_BACK() EV_TIMING TMR_ECHO 0 0 codice SEQ_SET_TEMPO() EV_TIMING TMR_TEMPO 0 0 bpm SEQ_SONGPOS() EV_TIMING TMR_SPP 0 0 pos SEQ_TIME_SIGNATURE() EV_TIMING TMR_TIMESIG 0 0 misura NOTA: il valore del parametro per gli ultimi sei eventi ` un unsigned int immagazz- e inato nei byte dal 4 al 7 con *(unsigned int *)&buf[4] System exclusive 0 1 2 3 4 5 6 7 SEQ_SYSEX() EV_SYSEX dev byte byte byte byte byte byte NOTA: il primo byte inviato nel primo blocco della sysex deve essere uguale a 0xF0, mentre l’ultimo blocco ha l’ultimo byte pari a 0xF7; se l’ultimo blocco non ` e riempito (len==6), i byte rimanenti sono riempiti con 0xFF /dev/sequencer accetta in output eventi da quattro (quello generato da SEQ_MIDIOUT()) e otto byte (quelli per /dev/music), ma per quanto riguarda l’input ci sono solo tre eventi, da quattro byte ciascuno: Eventi corti 0 1 2 3 SEQ_MIDIOUT() SEQ_MIDIPUTC byte nporta 0 — SEQ_WAIT tick — SEQ_ECHO codice NOTA: negli ultimi due eventi tick e codice sono entrambi da 24 bit 4.8 Input degli eventi L’input di un evento si effettua in maniera molto semplice: unsigned char buf[<dim. evento>]; if (read(seqfd, buf, <dim. evento>) != <dim. evento>) errore("Lettura dal sequencer"); ove <dim. evento> ` la dimensione di un evento: per /dev/music tutti gli eventi e di input sono di otto byte, mentre per /dev/sequencer sono da quattro byte. Se il buffer di input si riempie (as esempio, se il processo utente non ` abbastanza e veloce ad estrarre gli eventi) ulteriori eventi in arrivo verranno persi. OSS mette a disposizione una chiamata per conoscere il numero di eventi che attendono nella coda di input: 94
  • 100.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.8. INPUT DEGLI EVENTI int neventi; ioctl(seqfd, SNDCTL_SEQ_GETINCOUNT, &neventi); questa chiamata ritorna neventi==0 se non c’` alcun evento in coda oppure se il e device file ` stato aperto con O_WRONLY. e Per default la read() risulta bloccante per il processo utente se non ci sono eventi in coda o se si ` cercato di leggere un numero di byte superiori a neventi*<dim. evento>. e Con la chiamata precedente ` possibile realizzare input non bloccante controllan- e do il numero di eventi in coda prima della lettura: ioctl(seqfd, SNDCTL_SEQ_GETINCOUNT, &neventi); if ((neventi > 0) && (read(seqfd, buf, <dim. evento>) != <dim. evento>)) errore("Lettura dal sequencer"); L’apertura del device file con O_NONBLOCK fa ritornare alla read() -1 con errno==EAGAIN se si ` cercato di leggere un numero di byte superiore a quelli e presenti in coda; quindi un altro modo per realizzare l’input non bloccante `: e if (read(seqfd, buf, <dim. evento>) == -1) { /* Se errno==EAGAIN la coda di input e’ vuota */ } questo metodo implica la perdita di un tick di tempo di CPU, poich´ deve essere e “svegliato” dallo stato di wait il modulo del kernel che si occupa della gestione della porta MIDI. Nel caso di Alpha ci` corrisponde a circa un millisecondo, o mentre per gli altri a circa dieci millisecondi. C’` anche la possibilit` di effettuare un’attesa con timeout da parte di read() e a tramite la seguente chiamata: int timeout = <valore>; ioctl(seqfd, SNDCTL_MIDI_PRETIME, &valore); ove <valore> ` dieci volte la risoluzione temporale del timer del kernel. Ad e esempio, se il timer del kernel ha una frequenza di 100 Hz la risoluzione temporale ` di 10ms e l’attesa ` per multipli di 100ms; se timeout==10 l’attesa da parte di e e read() per un evento di input dura al massimo un secondo. Si consiglia, se si vuole precisione nell’attesa, di controllare la frequenza del timer prima di impostare il timeout; ci` si effettua usando SNDCTL_SEQ_CTRLRATE o con /dev/sequencer. Incidentalmente, se timeout==0 la read() esegue un in- put non bloccante, cio` se ci sono dei byte in coda li estrae, altrimenti ritorna e immediatamente senza attendere. 4.8.1 Eventi per /dev/music Per questo device file la read(seqfd, buf, 8) ritorner` uno fra gli eventi rela- a tivi alle seguenti macro (il cui formato ` nella Sezione 4.7): SEQ_START_NOTE(), e 95
  • 101.
    4.8. INPUT DEGLIEVENTI CAPITOLO 4. SINTETIZZATORI E MIDI SEQ_STOP_NOTE(), SEQ_KEY_PRESSURE(), SEQ_CONTROL(), SEQ_SET_PATCH(), SEQ_BENDER(), SEQ_CHN_PRESSURE(), SEQ_ECHO_BACK(). Il driver ignora le se- quenze system exclusive e gli Active Sensing. Inoltre, se la gestione del timer ` esterna (ad esempio, demandata al modo e intelligente della Roland MPU–401), possono essere ritornati anche gli eventi rela- tivi alle macro: SEQ_START_TIMER(), SEQ_STOP_TIMER(), SEQ_CONTINUE_TIMER(), SEQ_SONGPOS(). Fra un evento e l’altro il driver inserisce in coda anche l’evento marcatempo relativo a SEQ_WAIT_TIME(), con l’attesa fra un evento e l’altro espressa in tick; la risoluzione temporale dipende dalla predisposizione che si era data al timer tramite SNDCTL_TMR_TIMEBASE. Se due eventi arrivano in sequenza, ovvero al di sotto della risoluzione temporale di un tick, fra di essi non sar` inserito alcun a evento marcatempo; ad esempio, se sulla tastiera MIDI si premono simultanea- mente tre tasti, dalla coda di input si potranno estrarre tre SEQ_START_NOTE() senza eventi marcatempo fra essi. Per interpretare gli eventi letti dalla coda basta un semplice loop: for (i=0; i<10; i++) { /* Interpreta dieci eventi, input bloccante */ read(seqfd, buf, 8); if (buf[0] == EV_TIMING) /* Eventi di temporizzazione */ switch (buf[1]) { case TMR_WAIT_ABS: ... break; case TMR_START: ... . . . } else { /* Eventi EV_CHN_VOICE e EV_CHN_COMMON */ nporta = buf[1]; canale = buf[3]; switch (buf[2]) { case MIDI_NOTEON: ... break; case MIDI_NOTEOFF: ... . . . } } } 96
  • 102.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.9. NUOVE CARATTERISTICHE 4.8.2 Eventi per /dev/sequencer Per questo device file la read(seqfd, buf, 4) pu` ritornare solo tre tipi di o evento, che sono elencati alla fine della Sezione 4.7. Essi sono rispettivamente l’input di un byte MIDI, l’evento marcatempo e l’evento di sincronizzazione con la coda di output; a differenza di /dev/music, le sequenze system exclusive non sono ignorate, in quanto lette sotto forma di byte MIDI. La risoluzione temporale del timer ` fissa ed ` pari a quella del kernel; si e e pu` ricorrere a SNDCTL_SEQ_CTRLRATE per determinare la frequenza. Si hanno a o disposizione solo 24 bit per rappresentare i tick. L’interpretazione degli eventi letti dalla coda ` pi` semplice di quella vista e u precedentemente per /dev/music: for (i=0; i<10; i++) { /* Interpreta dieci eventi, input bloccante */ read(seqfd, buf, 4); switch (buf[0]) { case SEQ_WAIT: tick = *(int *)&buf[0] >> 8; ... break; case SEQ_MIDIPUTC: midi_byte = buf[1]; nporta = buf[2]; ... break; case SEQ_ECHO: codice = *(int *)&buf[0] >> 8; ... break; default: /* Gestione dell’errore */ exit(-1); } } 4.9 Nuove caratteristiche Le versioni di OSS successive alla 3.6 hanno introdotto parecchie novit`, fra a le quali nuove chiamate e qualche ritocco cosmetico all’interfaccia di program- mazione: • la seguente chiamata ritorna il valore in tick del timer (interno o esterno) del sequencer: int ntick; ioctl(seqfd, SNDCTL_SEQ_GETTIME, &ntick); 97
  • 103.
    4.9. NUOVE CARATTERISTICHE CAPITOLO 4. SINTETIZZATORI E MIDI • c’` una nuova chiamata funzionalmente equivalente a SNDCTL_SYNTH_INFO, e salvo il fatto che nel campo name della struct synth_info ` ritornata una e stringa descrittrice del tipo di chip sintetizzatore nella scheda audio: struct synth_info idinfo; idinfo.device = <numero dispositivo>; ioctl(seqfd, SNDCTL_SYNTH_ID, &idinfo); le stringhe ritornate in name possono essere: "PSS" Echo Personal Sound System (ESC614) "PSSMPU" Personal Sound System + MPU–401 "PSSMSS" Personal Sound System (MSS) "GUS" Gravis UltraSound "GUS16" Gravis UltraSound 16 "GUSPNP" Gravis UltraSound PnP "IWAVE" Gravis UltraSound wavetable "MSS" Microsoft Sound System "DESKPROXL" Compaq DeskPro XL "MAD16" MAD16/Mozart (MSS) "MAD16MPU" MAD16/Mozart + MPU–401 "CS4232" CS4232 "CS4232MPU" CS4232 + MPU–401 "OPL3" OPL-2/OPL-3 FM "PAS16" Pro Audio Spectrum 16 "MPU401" Roland MPU–401 "UART401" MPU–401 (UART) "MAUI" Turtle Beach Maui "MIDI6850" 6860 UART MIDI "SBLAST" Sound Blaster "SBPNP" Sound Blaster PnP "SBMPU" Sound Blaster + MPU–401 "SSCAPE" Ensoniq SoundScape "SSCAPEMSS" Microsoft Sound System (SoundScape) "OPL3SA" Yamaha OPL3-SA "OPL3SASB" OPL3-SA (modo Sound Blaster) "OPL3SAMPU" OPL3-SA MIDI "TRXPRO" MediaTrix AudioTrix Pro "TRXPROSB" AudioTrix (modo Sound Blaster) "TRXPROMPU" AudioTrix + MPU–401 "SOFTSYN" SoftOSS Virtual Wavetable "SoftOSS" SoftOSS • per quanto riguarda la struct synth_info, si consiglia l’uso del nuovo identificatore SAMPLE_TYPE_BASIC al posto di SAMPLE_TYPE_GUS 98
  • 104.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.9. NUOVE CARATTERISTICHE • nel caso il sintetizzatore sia programmabile, la chiamata seguente consente di passargli comandi e ricevere dati: synth_control sctl; sctl.devno = <numero sintetizzatore>; /* Inizializzare anche sctl.data[] */ if (ioctl(seqfd, SNDCTL_SYNTH_CONTROL, &sctl) == -1) errore("SNDCTL_SYNTH_CONTROL"); la struct synth_control (di cui synth_control ` un typedef) ` cos` e e ı composta: int devno numero del sintetizzatore char data[4000 ] vettore di byte ove sono inseriti i comandi specifici per il sintetizzatore e in cui ` ritornata un’eventuale risposta e ` obbligatorio inizializzare entrambi i campi prima della chiamata e • la seguente chiamata consente di rimuovere dalla memoria una patch wavetable precedentemente caricata: remove_sample rsample; rsample.devno = <numero sintetizzatore>; rsample.bankno = <numero instrument bank>; rsample.instrno = <numero preset>; if (ioctl(seqfd, SNDCTL_SYNTH_REMOVESAMPLE, &rsample) == -1) errore("SNDCTL_SYNTH_REMOVESAMPLE"); la struct remove_sample (di cui remove_sample ` un typedef) ` cos` e e ı composta: int devno numero del sintetizzatore int bankno numero del bank MIDI (porre uguale a 0 se General MIDI) int instrno numero del preset timbrico ` obbligatorio inizializzare tutti i campi prima della chiamata e • in struct patch_info l’identificatore WAVE_PATCH sostituisce GUS_PATCH, che resta comunque disponibile per compatibilit` all’indietro, evidenzian- a do l’architettura pi` indipendente dal dispositivo che ora ha OSS per la u gestione delle patch; per il campo mode ` stato aggiunto l’identificatore e WAVE_FAST_RELEASE, che consente lo spegnimento immediato di una nota al Note OFF invece di un rilascio graduale • la macro SEQ_SET_PATCH() ` stata rinominata SEQ_PGM_CHANGE(), che ri- e corda meglio la funzione MIDI relativa; SEQ_SET_PATCH() diventa un alias per compatibilit` all’indietro a 99
  • 105.
    4.9. NUOVE CARATTERISTICHE CAPITOLO 4. SINTETIZZATORI E MIDI 4.9.1 SoftOSS SoftOSS ` un modulo di OSS che permette di emulare la presenza di un sintetizza- e tore wavetable con 32 voci simultanee (o pi` ) tramite una CPU sufficientemente u veloce e una normale scheda audio a 16 bit. Le capacit` di tale sintetizzatore “vir- a 6 tuale” coincidono con quelle della Gravis UltraSound (GUS), per cui ` garantita e la compatibilit` all’indietro con le applicazioni per questa sviluppate. a Il nuovo sintetizzatore ` visibile nella lista dei dispositivi di sintesi ottenuta e con cat /dev/sndstat; ad esempio: . . . Synth devices: 0: Yamaha OPL-3 1: SoftOSS . . . Una particolarit` da tenere presente ` che, quando sono aperti /dev/music o a e /dev/sequencer, SoftOSS alloca per s´ /dev/dsp (che ridiventa disponibile chiu- e dendo i device file del sequencer); non ` quindi possibile campionare o riprodurre e in concorrenza con il sequencer nei sistemi con una sola scheda audio. SoftOSS ` un modulo del kernel, per cui le altre attivit` di elaborazione non e a risentono particolarmente della miscelazione delle waveform, la quale risulta d’al- tro canto temporalmente precisa e affidabile; gli altri processi girano solo pi` u lentamente. Se la CPU ` abbastanza veloce, la potenza di calcolo a questa sottratta rende e la qualit` dell’audio ottenuto paragonabile a quella ottenibile su un sistema leg- a germente caricato; anche se ` possibile utilizzare un processore di classe 486 e diminuendo la frequenza di campionamento, ` raccomandato l’utilizzo di un pro- e cessore dal Pentium 133MHz7 in su. Al crescere della potenza di calcolo ` possibile e utilizzare 32 voci simultanee a 44.1kHz, ma mantenendo basso il numero di vo- ci simultanee (da quattro a otto) questa frequenza di campionamento dovrebbe essere utilizzabile con qualsiasi processore. Se la CPU ` troppo lenta il suono ` distorto e il sistema rallenta parecchio e e per un alto numero di voci simultanee; la situazione migliora diminuendone il numero e ovviamente ritorna normale fermando l’esecuzione. Ad esempio, con un processore lento la musica in background pu` far rallentare un gioco fino a o renderlo ingiocabile. Le patch sono quelle utilizzate dalla GUS (file GF1), che non necessariamente devono essere strumenti musicali: SoftOSS pu` essere usato per la creazione di o 6 Ovviamente se si dispone gi` di una GUS o di un’altra scheda con capacit` di sintesi a a wavetable SoftOSS diventa pressoch´ inutile e lo si pu` disabilitare: esse fanno in hardware, e o quindi pi` efficientemente, ci` che SoftOSS emula in software u o 7 Con tale processore si possono ottenere 32 voci simultanee per frequenze di campionamento di 32kHz); SoftOSS beneficia inoltre di processori MMX, anche se non sfrutta queste estensioni 100
  • 106.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.9. NUOVE CARATTERISTICHE effetti sonori nei giochi, per la riproduzione di messaggi preregistrati e di segnali d’allarme, applicazioni per le quali il sistema non risulta particolarmente caricato. Attualmente la memoria per le patch ` limitata a max 8MB; per il sistema in e s´ sono richiesti 16MB di RAM (32MB raccomandati), anche se ` possibile usare e e SoftOSS con una quantit` di RAM inferiore ai 16MB se non si caricano molte a patch. 4.9.2 OSSlib OSSlib ` una libreria che supporta il caricamento di patch General MIDI per e OPL–3 e Gravis UltraSound (quindi anche per SoftOSS) in maniera indipendente dal dispositivo; essa ` intesa per l’uso con software MIDI da parte di programma- e tori MIDI, consentendo di scrivere programmi che funzionino senza cambiamenti con qualsiasi dispositivo sintetizzatore che venga introdotto in futuro. OSSlib ` e disponibile anche per OSS/Free dalla versione 3.8β2. La libreria ` attivata definendo la macro vuota OSSLIB prima dell’inclusione e di soundcard.h, purch´ essa sia stata correttamente compilata e installata; ad e esempio: . . . #define OSSLIB #include <sys/soundcard.h> #ifndef OSSLIB void seqbuf_dump() /* Deve essere usata solo se non c’e’ OSSLIB */ { if (_seqbufptr && (write(seqfd, _seqbuf, _seqbufptr) == -1)) errore("/dev/music"); _seqbufptr = 0; } #endif . . . Dopo l’apertura del device file si inizializza la libreria con il seguente fram- mento di codice: #ifdef OSSLIB if (OSS_init(seqfd, DIM_BUFFER) != 0) errore("OSS_init()"); #endif ove 32≤ DIM_BUFFER ≤2048 ` la dimensione del buffer degli eventi, che con ques- e ta chiamata viene allocato in memoria dinamica; SEQ_DEFINEBUF() non ` pi`e u 101
  • 107.
    4.10. ESEMPIO DIPROGRAMMA CAPITOLO 4. SINTETIZZATORI E MIDI necessaria con OSSlib, ma usarla rende il codice compilabile con e senza la libre- ria (nel primo caso la dimensione desiderata del buffer viene immagazzinata in static int _requested_seqbuflen). OSSlib introduce due nuove macro per il caricamento delle patch su richiesta (patch caching): SEQ_LOAD_GMINSTR(<dev>, <numero patch>); SEQ_LOAD_GMDRUM(<dev>, <numero patch>); che consentono di precaricare un preset timbrico <numero patch> per il sin- tetizzatore <dev>, rispettivamente per la sezione strumentale e ritmica, senza che un eventuale ritardo in tale operazione introduca errori di ritmo nell’ese- cuzione di una partitura (il caricamento ` fatto prima di far partire il timer). e SEQ_PGM_CHANGE() effettua anche il caricamento automatico di una patch se questa non ` presente in memoria. e Tutte le altre macro rimangono inalterate per il programmatore, anche se OSSlib le ridefinisce dietro le quinte in maniera tale che siano pi` sicure. u 4.10 Esempio di programma Il programma che segue effettua l’input di eventi da una porta MIDI e li redirige in output a un dispositivo sintetizzatore specificato da linea di comando; le note ven- gono suonate con un preset timbrico, specificato anch’esso da linea di comando, sul canale 0. Nel caso dei sintetizzatori interni alla scheda audio si presuppone che i preset timbrici da usare siano gi` stati caricati; ad esempio: a ~>sint 0 3 suona le note provenienti dalla porta MIDI usando il dispositivo 0 (come da lista dei “Synth devices:” con cat /dev/sndstat) usando la patch numero 3 (Electric Grand Piano, se le patch sono state caricate seguendo lo standard General MIDI). Gli eventi suonati sono visualizzati a video; il Song Position Pointer, gli eventi marcatempo e il Program Change sono ignorati. Si esce dal programma premendo contemporaneamente i tasti Ctrl e C. /* * sint.c - Usa un device di OSS per suonare con un dato preset * timbrico le note che arrivano dalla porta MIDI */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> 102
  • 108.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.10. ESEMPIO DI PROGRAMMA #include <sys/soundcard.h> int seqfd; /* Dichiarazioni globali file */ SEQ_DEFINEBUF(128); /* descriptor e buffer eventi */ /* Utilizza il canale 0 per suonare le note */ const int CANALE = 0; void errore(const char *msgerr) /* Gestione degli errori */ { perror(msgerr); exit(-1); } void seqbuf_dump() /* Dump del buffer degli eventi */ { if (_seqbufptr && (write(seqfd, _seqbuf, _seqbufptr) == -1)) errore("/dev/music"); _seqbufptr = 0; } int main(int argc, char *argv[]) { int dev, npatch, nsint, ppqn = 48, bpm = 60; struct synth_info sinfo; unsigned char buf[8]; if (argc != 3) { /* Numero di argomenti non adeguato */ printf("Uso: %s <# sintetizzatore> <# patch>n", argv[0]); exit(-1); } /* Input da MIDI, output sul device selezionato */ if ((seqfd = open("/dev/music", O_RDWR)) == -1) errore("/dev/music"); /* Il device dato da linea di comando e’ OK? */ dev = atoi(argv[1]); ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &nsint); if (dev > (nsint-1)) { /* Device non OK */ printf("Non esiste il device #%d!n", dev); exit(-1); } /* Se il device e’ un OPL-3 attiva il modo 4OP */ sinfo.device = dev; 103
  • 109.
    4.10. ESEMPIO DIPROGRAMMA CAPITOLO 4. SINTETIZZATORI E MIDI ioctl(seqfd, SNDCTL_SYNTH_INFO, &sinfo); if (sinfo.synth_subtype == FM_TYPE_OPL3) ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &dev); /* Limita il numero della patch fra 0 e 127 */ npatch = atoi(argv[2]); if (npatch < 0) npatch = 0; if (npatch > 127) npatch = 127; /* Sincronizzazione timebase: PPQN e BPM */ ioctl(seqfd, SNDCTL_TMR_TIMEBASE, &ppqn); ioctl(seqfd, SNDCTL_TMR_TEMPO, &bpm); /* Attiva il timer, seleziona il preset e regola il volume */ ioctl(seqfd, SNDCTL_TMR_START); SEQ_SET_PATCH(dev, CANALE, npatch); SEQ_CONTROL(dev, CANALE, CTL_MAIN_VOLUME, 127); SEQ_DUMPBUF(); /* Riproduzione degli eventi in input dalla porta MIDI */ while (1) { /* Da questo ciclo si esce con ^C */ if (read(seqfd, buf, 8) == -1) /* Input bloccante */ errore("MIDI read()"); if (buf[0] == EV_TIMING) switch (buf[1]) { /* Eventi di temporizzazione */ case TMR_START: puts("<Start Timer>"); break; case TMR_STOP: puts("<Stop Timer>"); break; case TMR_CONTINUE: puts("<Continue Timer>"); break; case TMR_SPP: /* DA IGNORARE */ case TMR_WAIT_ABS: /* DA IGNORARE */ goto FineCiclo; default: } else { /* Eventi per EV_CHN_VOICE e EV_CHN_COMMON */ buf[1] = dev; buf[3] = CANALE; switch (buf[2]) { case MIDI_NOTEON: printf(" Note ON: nota %d, vel %dn", 104
  • 110.
    CAPITOLO 4. SINTETIZZATORIE MIDI 4.10. ESEMPIO DI PROGRAMMA buf[4], buf[5]); break; case MIDI_NOTEOFF: printf(" Note OFF: nota %d, vel %dn", buf[4], buf[5]); break; case MIDI_KEY_PRESSURE: printf("Polyphonic Key Pressure: nota %d, pres %dn", buf[4], buf[5]); break; case MIDI_CHN_PRESSURE: printf("Channel Pressure: pres %dn", buf[4]); break; case MIDI_CTL_CHANGE: printf("Control Change: controller %d, val %dn", buf[4], *(short *)&buf[6]); break; case MIDI_PITCH_BEND: printf("Pitch Bend Change: val %dn", *(short *)&buf[6]); break; case MIDI_PGM_CHANGE: /* DA IGNORARE */ goto FineCiclo; default: } } /* Scrittura sul device selezionato */ if (write(seqfd, buf, 8) == -1) errore("Out device write()"); FineCiclo: } close(seqfd); return 0; } 105
  • 111.
    Capitolo 5 IL MIDIA BASSO LIVELLO 5.1 Descrizione dei device file MIDI OSS compie un buon lavoro incapsulando i sintetizzatori interni alle schede audio e le porte MIDI, presentandone tramite i device file del sequencer una visione unitaria per le applicazioni rivolte alla musica. La semplicit` e la potenza di questo tipo di gestione si paga con l’astrazione dal a protocollo dei messaggi MIDI vero e proprio: a tal scopo sono presenti i device file /dev/midin, i quali rappresentano un’interfaccia di accesso nuda e cruda alle porte MIDI per la realizzazione di applicazioni particolari, che sarebbero di difficile (o addirittura impossibile) realizzazione con il sequencer. Nell’intenzione dell’autore del driver, i device file MIDI sono presenti per spedire e ricevere sequenze system exclusive, tali da realizzare patch editor 1 e librarian 2 per gli strumenti sintetizzatori connessi alle porte, o per applicazioni come i light controllers (MIDI Show Control ). I device file MIDI non hanno le capacit` di temporizzazione tipiche dei device a file del sequencer, per cui si comportano come /dev/tty in raw mode: qualsi- asi cosa sia scritta su un device file ` trasferita quanto prima alla porta MIDI e 3 relativa ; tutti i byte che arrivano alla porta MIDI possono essere letti dal de- vice file, tranne l’Active Sensing (0xFE) che ` automaticamente ignorato (invece e OSS/Linux non lo ignora). C’` un device file per ogni porta: il primo per la porta 0 (come da lista dei e “Midi devices:” con cat /dev/sndstat) sar` /dev/midi00, il secondo sar` a a 1 Sono programmi che consentono la lettura e la modifica a video di tutti i parametri di uno strumento MIDI, particolarmente le caratteristiche dei timbri (possono anche esserne creati di nuovi); dopo le modifiche questi possono essere rispediti allo strumento 2 Pur non disponendo delle potenzialit` di modifica proprie degli editor, i librarian svolgono a il compito di database per i timbri, potendo essere ivi memorizzati e ricercati in base ad alcune caratteristiche comuni 3 I ritardi sono dovuti alla bufferizzazione: un byte MIDI impiega 320 µs per essere trasmesso, per cui il driver mantiene una coda di I/O per rendere pi` efficienti i trasferimenti di dati u 106
  • 112.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.2. LETTURA DALLA PORTA MIDI /dev/midi01, e cos` via fino a un massimo teorico4 di sedici porte MIDI. ı Ogni device file ` bufferizzato: per default la coda di input e la coda di output e sono due buffer della dimensione di 4000 byte l’uno; ` consigliato di non cambiare e tale valore. L’apertura di un device file pu` avvenire in O_WRONLY, O_RDONLY e O_RDWR, o con il supporto per O_NONBLOCK; ad esempio: int midifd; if ((midifd = open("/dev/midi00", O_RDWR)) == -1) errore("/dev/midi00"); utilizzando il flag O_NONBLOCK la lettura da una coda vuota o la scrittura su una coda piena provoca un errore, con errno==EAGAIN. Sui device file MIDI Linux supporta select(); ` possibile effettuare I/O non e bloccante facendo a meno di questa tramite l’apertura del device file con il flag O_NONBLOCK, tuttavia quest’approccio ritarda la read() o la write() di un tick5 , dovendo essere richiamato dallo stato di wait il modulo del kernel responsabile della gestione della porta MIDI. 5.2 Lettura dalla porta MIDI Per default la lettura da un device file MIDI ` bloccante se la coda di input ` e e vuota (per OSS/Linux ` sempre non bloccante); se per` arriva anche un solo byte e o il processo utente sar` sbloccato, indipendentemente da quanti byte erano stati a richiesti tramite read(): int nricevuti, nrichiesti = <numero byte>; unsigned char buf[<dim. buffer>]; nricevuti = read(midifd, buf, nrichiesti); /* Generalmente nricevuti <= nrichiesti; per */ /* OSS/Linux nricevuti==0 se la coda e’ vuota */ Se la coda di input si riempie, ovvero il processo utente ` pi` lento nell’estrarre e u i byte da essa rispetto alla loro velocit` di arrivo in coda, gli ulteriori byte in arrivo a saranno persi. Tra l’arrivo del primo byte e il ritorno della read() pu` accadere che passino o parecchi ms; in questo lasso di tempo parecchi byte MIDI possono essere ricevuti, cos` non ` inusuale che con una singola lettura possano essere ritornate decine di ı e byte se il computer ` lento o il sistema ` molto carico. e e ` possibile predisporre un timeout per il tempo massimo che la read() pu` E o attendere l’arrivo del primo byte tramite la seguente chiamata: 4 Il teorico ` dovuto al fatto che pu` esser stato introdotto un limite artificiale nei sorgenti e o del driver per il massimo numero di porte gestibili — ad esempio, sei per OSS/Free 3.8.2 5 Circa 10ms per i sistemi non Alpha, meno di 1ms per questi ultimi 107
  • 113.
    5.2. LETTURA DALLAPORTA MIDI CAPITOLO 5. IL MIDI A BASSO LIVELLO int timeout = <valore>; ioctl(midifd, SNDCTL_MIDI_PRETIME, &timeout); il conteggio del timeout si basa sul timer del kernel, ed ` determinato per multipli e di dieci volte il quanto temporale minimo (<valore> ` espresso in decimi di e secondo, se il timer ha frequenza 100 Hz). Se timeout==0 l’input risulta non bloccante. Se si vuole un timeout preciso ` meglio rilevare la frequenza del timer del e kernel tramite l’utilizzo di SNDCTL_SEQ_CTRLRATE su /dev/sequencer; ad esem- pio, Linux/Alpha ha un clock di default di 1024 Hz, quindi per questo sistema la risoluzione del timeout sarebbe nell’ordine della decina di millisecondi. Tramite l’uso in apertura del flag O_NONBLOCK ` possibile effettuare input non e bloccante anche nel modo seguente: if ((nricevuti = read(midifd, buf, nrichiesti)) == -1) { if (errno == EAGAIN) { /* La coda di input e’ vuota */ } else errore("read()"); } else if (nricevuti == 0) { /* Si verifica con OSS/Linux se la coda e’ vuota */ } questo metodo implica la perdita di un tick di tempo di CPU per quanto detto precedentemente; ci` costituisce uno spreco notevole nel caso i messaggi MIDI o costituiscano l’input a un sistema di sintesi in tempo reale. Un modo alternativo di gestire l’input non bloccante, che non spreca molto tempo, implica l’utilizzo di select() per fare il polling del device file. La seguente funzione ritorna 1 se ` riuscita ad estrarre un byte dalla coda di input, ritorna 0 e se la coda era vuota e -1 se si ` verificato un errore: e int MIDI_read(int fd, unsigned char *pch) { fd_set readset, exceptset; struct timeval tval; /* Inizializzazione insiemi di file descriptor */ FD_ZERO(&readset); FD_ZERO(&exceptset); FD_SET(fd, &readset); FD_SET(fd, &exceptset); tval.tv_sec = tval.tv_usec = 0; /* Attesa nulla */ if (select(fd+1, &readset, NULL, &exceptset, &tval)) { 108
  • 114.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.3. SCRITTURA SULLA PORTA MIDI if (FD_ISSET(fd, &exceptset)) /* Si e’ verificato un errore */ return -1; else return (read(fd, pch, 1)); } else return 0; /* La coda di input e’ vuota */ } 5.3 Scrittura sulla porta MIDI Il comportamento di default per la scrittura su un device file MIDI ` di non e bloccare il processo utente se la quantit` di byte da scrivere ` inferiore allo spazio a e libero nella coda di output; se il numero di byte ` superiore il processo sar` e a bloccato. Si ritorna dalla write() solo quando nella coda di output restano meno di cento byte: ci` equivale a un’attesa minima di circa 1.25 secondi per il o processo utente. Si pu` realizzare output non bloccante con l’apertura del device file tramite o il flag O_NONBLOCK; in tal caso: if (write(midifd, buf, <numero byte da scrivere>) == -1) { if (errno == EAGAIN) { /* La coda di output e’ piena */ } else errore("write()"); } con questo metodo si consiglia di scrivere un solo byte per volta, altrimenti se la coda si riempie e write() ritorna con -1 non si ` in grado di sapere quanti byte e siano stati effettivamente trasferiti in output (ci` provocherebbe una perdita di o sincronia tra l’applicazione e i dispositivi MIDI indirizzati). Come per read(), `e perso un tick; si consiglia l’utilizzo di select() per effettuare il polling. 5.4 Programmazione della MPU–401 In OSS due chiamate sono specificamente rivolte alla Roland MPU–401, poich´ e originariamente sfruttate per studiare in modo pi` semplice l’I/O di questa sche- u da MIDI; possono tuttavia essere ancora utili per rendere facile il porting di applicazioni sviluppate in altri ambienti specificamente per essa. La MPU–401 ha due modalit` di funzionamento: a Modo UART (pass–through) l’interfaccia non compie servizi, limitandosi a trasferire tutto ci` che riceve senza modifiche o interpretazioni dalla porta o MIDI al computer; ` la modalit` di default in cui si trova la porta dopo e a l’apertura del relativo device file e l’unico comando che l’MPU–401 accetta in questo caso ` il reset al modo intelligente e 109
  • 115.
    5.5. ESEMPIO DIPROGRAMMA CAPITOLO 5. IL MIDI A BASSO LIVELLO Modo “intelligente” (coprocessor ) l’interfaccia interpreta e gestisce i dati, in input accodando loro delle informazioni di temporizzazione, codici MPU e messaggi, e in output gestendo il loro invio; /dev/music emula quasi tutte le caratteristiche dell’MPU–401 in maniera portabile Il modo UART ` quello che la maggior parte delle schede audio emula; le e caratteristiche dell’MPU–401 sono disponibili solo con schede che supportano specificamente il modo intelligente. ` E possibile passare da un modo all’altro con la seguente chiamata: int modo = <0 oppure 1>; if (ioctl(midifd, SNDCTL_MIDI_MPUMODE, &modo) == -1) errore("SNDCTL_MIDI_MPUMODE"); se modo==0 la scheda viene posta in modo UART, mentre se modo==1 viene posta in modo intelligente. Se questo non ` supportato dalla porta MIDI la chiamata e ritorna errno==EINVAL. La chiamata seguente ` usata per spedire alla MPU–401, solo in modo intel- e ligente, un comando e i suoi parametri, ricevendo eventualmente in risposta dei dati: mpu_command_rec comando; /* Inizializzare i campi di comando */ if (ioctl(midifd, SNDCTL_MIDI_MPUCMD, &comando) == -1) errore("SNDCTL_MIDI_MPUCMD"); ove mpu_command_rec ` una typedef struct i cui campi, da inizializzare prima e della chiamata, hanno il significato: unsigned char cmd comando per la MPU–401 char nr args numero di argomenti del comando char nr returns numero di byte ritornati in risposta unsigned char data[30 ] ospita gli argomenti del comando e i dati ritornati in risposta Particolare attenzione deve essere posta nel predisporre i valori di nr_args e nr_returns, per evitare che un errore metta fuori sincronia il driver e la porta. 5.5 Esempio di programma In questa Sezione ` mostrato un programma d’esempio che implementa una fun- e zione MIDI_readmsg() in grado di estrarre dalla coda di input i byte MIDI e “impacchettarli” per formare un messaggio come da struct midi_msg data in argomento: 110
  • 116.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.5. ESEMPIO DI PROGRAMMA unsigned char status status byte; per i Channel Message ` solo il nibble supe- e riore, mentre per i System Message ` non modificato e unsigned char chn numero del canale se ` un Channel Message, altrimenti 0 e int lundata numero di data byte per un messaggio che non sia un System Exclusive, altrimenti numero di sysex byte meno uno unsigned char data[2 ] primo e secondo data byte unsigned char *psysex puntatore a un’area di memoria (di massimo 128 byte) che immagazzina la sysex la funzione si usa cos` ı: struct midi_msg msg; int codice = MIDI_readmsg(&msg); /* codice dice se il messaggio e’ completo o no */ essa supporta la regola del running status. Tale funzione ` in grado di effettuare sia input bloccante che non bloccante, e secondo come si ` aperto il device file MIDI con la funzione di supporto MIDI_open(), e la quale ha un uso simile a open(), salvo il fatto che non ha un valore di ritorno. ` E supportata l’apertura di una sola porta MIDI per volta. MIDI_readmsg() ha tre possibili valori di ritorno: RET MSG ERR (-1) ` ritornato se si verifica un errore in input; ad esem- e pio, potrebbe essere un errore derivante dall’aver fatto riempire la coda di input, perdendo un po’ di sincronia con il flusso dei messaggi, o (meno probabilmente) un errore hardware RET NO MSG (0) se si ` richiesto di effettuare input non bloccante, questo ` e e il valore ritornato nel caso la coda di input sia vuota e il messaggio non sia completo; bisogna chiamare nuovamente la funzione dopo un po’ di tempo, fino ad avere successo nel formare il messaggio RET MSG OK (1) indica che il messaggio in argomento ` completo e Il corpo principale del programma sfrutta la funzione per effettuare il dump dei messaggi MIDI in input, ovvero li visualizza in una rappresentazione leggibile agli umani, tramite una lunga switch(). Al principio del listato sono definiti degli identificatori MIDI per vari messaggi, a complemento degli altri presenti in soundcard.h, per migliorare la leggibilit` a complessiva del programma. Sempre a complemento, anche se ivi non utilizzata, ` stata scritta una funzione che effettua l’output di messaggi MIDI (in maniera e bloccante o non bloccante) aventi il formato sopra descritto. MIDI_writemsg() ritorna RET_MSG_OK se l’intero messaggio ` stato spedito e in output, mentre se l’output ` non bloccante c’` la possibilit` che sia ritornato e e a 111
  • 117.
    5.5. ESEMPIO DIPROGRAMMA CAPITOLO 5. IL MIDI A BASSO LIVELLO RET_NO_MSG: in tal caso basta richiamare pi` volte la funzione, finch´ ` ritornato u ee il primo valore. L’uso ` simile a quello di MIDI_readmsg(), salvo il fatto che e prima di chiamarla il messaggio msg deve essere riempito con valori adeguati. Questa soluzione non permette di inframezzare ai normali messaggi dei Real Time Message, tuttavia ` sempre possibile usare la write() nuda e cruda se si hanno e dei requisiti particolari. /* * dumpmidi.c - Legge i byte che arrivano alla porta MIDI e visualizza * i corrispondenti messaggi MIDI */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <sys/soundcard.h> /* Codici MIDI di supporto a quelli presenti in soundcard.h */ /* System Common Messages */ #define MIDI_QUARTER_FRAME 0xF1 #define MIDI_SONG_POSITION 0xF2 #define MIDI_SONG_SELECT 0xF3 #define MIDI_TUNE_REQUEST 0xF6 /* System Exclusive */ #define MIDI_END_SYSEX 0xF7 /* Real Time Messages */ #define MIDI_TIMING_CLOCK 0xF8 #define MIDI_START 0xFA #define MIDI_CONTINUE 0xFB #define MIDI_STOP 0xFC #define MIDI_ACTIVE_SENSING 0xFE #define MIDI_SYSTEM_RESET 0xFF /* Codici per la macchina a stati che interpreta i byte MIDI */ #define ST_INIZIO_MSG 0 #define ST_DATA_BYTE 1 #define ST_SYSEX 2 #define RET_MSG_ERR -1 #define RET_NO_MSG 0 #define RET_MSG_OK 1 /* Tipo di un messaggio MIDI */ struct midi_msg { 112
  • 118.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.5. ESEMPIO DI PROGRAMMA unsigned char status; unsigned char chn; int lundata; unsigned char data[2]; unsigned char *psysex; }; const int MAX_SYSEX_DIM = 128; /* Max dim. blocco sysex */ /* Variabili globali per read/write msg */ int midifd; int non_bloccante = 0; void errore(const char *msgerr) /* Gestione degli errori */ { perror(msgerr); exit(-1); } int lun_midi_data(const unsigned char status_byte) { switch (status_byte & 0xF0) { /* Ritorna il # data byte/messaggio */ case MIDI_NOTEOFF: case MIDI_NOTEON: case MIDI_KEY_PRESSURE: case MIDI_CTL_CHANGE: case MIDI_PITCH_BEND: return 2; case MIDI_PGM_CHANGE: case MIDI_CHN_PRESSURE: return 1; case MIDI_SYSTEM_PREFIX: switch (status_byte) { case MIDI_SONG_POSITION: return 2; case MIDI_QUARTER_FRAME: case MIDI_SONG_SELECT: return 1; case MIDI_TUNE_REQUEST: return 0; default: } default: return -1; /* Msg sconosciuto */ } } 113
  • 119.
    5.5. ESEMPIO DIPROGRAMMA CAPITOLO 5. IL MIDI A BASSO LIVELLO void MIDI_open(const char *porta, const int flag) { /* Apre la porta MIDI */ if (flag & O_NONBLOCK) non_bloccante = 1; if ((midifd = open(porta, flag)) == -1) errore(porta); } int MIDI_readmsg(struct midi_msg *pmsg) /* Legge un messaggio */ { unsigned char midi_byte; int nch; static int stato_macchina = ST_INIZIO_MSG; static struct midi_msg tmpmsg = {MIDI_NOTEOFF, 0 , 0, 0, 0, NULL}; static int ldata = 0; #define FINE_MIDI_MSG() { memcpy((void *)pmsg, (void *)&tmpmsg, sizeof(tmpmsg)); stato_macchina = ST_INIZIO_MSG; return RET_MSG_OK; } while (1) { /* Cicla fino alla lettura di un messaggio completo */ if ((nch = read(midifd, &midi_byte, 1)) == -1) { if (non_bloccante && (errno == EAGAIN)) return RET_NO_MSG; else return RET_MSG_ERR; } else if (nch == 0) goto fine_ciclo; /* Per OSS/Linux */ if ((midi_byte > 0x7F) && (stato_macchina > ST_INIZIO_MSG)) { /* Controlla se e’ un Real Time Message */ switch (midi_byte) { case MIDI_TIMING_CLOCK: case MIDI_START: case MIDI_CONTINUE: case MIDI_STOP: case MIDI_ACTIVE_SENSING: case MIDI_SYSTEM_RESET: pmsg->status = midi_byte; pmsg->chn = 0; pmsg->lundata = 0; pmsg->data[0] = pmsg->data[1] = 0; pmsg->psysex = NULL; 114
  • 120.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.5. ESEMPIO DI PROGRAMMA return RET_MSG_OK; default: } } switch (stato_macchina) { case ST_INIZIO_MSG: /* Inizio di un messaggio */ stato_macchina = ST_DATA_BYTE; tmpmsg.lundata = 0; free(tmpmsg.psysex); if (midi_byte > 0x7F) { /* Status byte */ if ((midi_byte & 0xF0) == MIDI_SYSTEM_PREFIX) { /* System Message */ tmpmsg.status = midi_byte; tmpmsg.chn = 0; if (midi_byte == MIDI_SYSTEM_PREFIX) { /* System Exclusive */ stato_macchina = ST_SYSEX; tmpmsg.psysex = (unsigned char *) malloc(MAX_SYSEX_DIM); if (tmpmsg.psysex == NULL) return RET_MSG_ERR; } } else { /* Channel message */ tmpmsg.status = midi_byte & 0xF0; tmpmsg.chn = midi_byte & 0x0F; ldata = lun_midi_data(midi_byte); if (ldata == 0) FINE_MIDI_MSG(); } break; } case ST_DATA_BYTE: /* Data byte */ tmpmsg.data[tmpmsg.lundata++] = midi_byte; if (tmpmsg.lundata == ldata) FINE_MIDI_MSG(); break; case ST_SYSEX: /* System Exclusive */ if ((tmpmsg.lundata == MAX_SYSEX_DIM) && (midi_byte != MIDI_END_SYSEX)) return RET_MSG_ERR; *(tmpmsg.psysex + tmpmsg.lundata) = midi_byte; tmpmsg.lundata++; if (midi_byte == MIDI_END_SYSEX) FINE_MIDI_MSG(); 115
  • 121.
    5.5. ESEMPIO DIPROGRAMMA CAPITOLO 5. IL MIDI A BASSO LIVELLO default: } fine_ciclo: /* Serve per accomodare OSS/Linux */ } } int MIDI_writemsg(const struct midi_msg *pmsg) /* Scrive un messaggio */ { static int stato_scrittura = 0; static int i = 0; unsigned char status_byte = pmsg->status | pmsg->chn; #define SCRIVI_MIDI_BYTE(b) { if ((write(midifd, &(b), 1) == -1) && (errno == EAGAIN)) return RET_NO_MSG;} if (non_bloccante) { switch (stato_scrittura) { case 0: /* Status byte */ SCRIVI_MIDI_BYTE(status_byte); stato_scrittura++; case 1: /* Sysex o data byte */ if (pmsg->status == MIDI_SYSTEM_PREFIX) { /* Scrive la sysex */ while (i < pmsg->lundata) { SCRIVI_MIDI_BYTE(*(pmsg->psysex + i)); i++; } } else { /* Scrive i data byte */ while (i < pmsg->lundata) { SCRIVI_MIDI_BYTE(pmsg->data[i]); i++; } } default: /* Fine messaggio MIDI */ stato_scrittura = i = 0; return RET_MSG_OK; } } else { /* La scrittura e’ bloccante */ write(midifd, &status_byte, 1); if (pmsg->status == MIDI_SYSTEM_PREFIX) write(midifd, pmsg->psysex, pmsg->lundata); 116
  • 122.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.5. ESEMPIO DI PROGRAMMA else write(midifd, pmsg->data, pmsg->lundata); return RET_MSG_OK; } } int main(int argc, char *argv[]) { char PORTA_MIDI[12]; int seqfd, nporte, porta, n; struct midi_msg msg; /* Prende la porta MIDI da linea di comando */ if (argc != 2) { printf("Uso: %s <# porta MIDI>n", argv[0]); exit(-1); } porta = atoi(argv[1]); if ((seqfd = open("/dev/sequencer", O_WRONLY)) == -1) errore("/dev/sequencer"); ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &nporte); close(seqfd); if (porta > (nporte-1)) { printf("Porta MIDI #%d non valida; # porte installate %d.n", porta, nporte); exit(-1); } sprintf(PORTA_MIDI, "/dev/midi%2.2d", porta); /* Apertura porta MIDI - bloccante */ MIDI_open(PORTA_MIDI, O_RDONLY); printf("Dump input porta MIDI #%d...nn", porta); while (1) { /* Si esce dal ciclo con ^C */ if (MIDI_readmsg(&msg) == -1) { puts(">>> Errore di accesso alla porta MIDI!"); exit(-1); } /* Interpreta i messaggi MIDI */ switch (msg.status) { /* Channel Messages */ case MIDI_NOTEOFF: printf(" Note OFF: chn = %2d, key = %3d, vel = %dn", msg.chn+1, msg.data[0], msg.data[1]); break; case MIDI_NOTEON: 117
  • 123.
    5.5. ESEMPIO DIPROGRAMMA CAPITOLO 5. IL MIDI A BASSO LIVELLO printf(" Note ON: chn = %2d, key = %3d, vel = %dn", msg.chn+1, msg.data[0], msg.data[1]); break; case MIDI_KEY_PRESSURE: printf("Key Pressure: chn = %2d, key = %3d, aft = %dn", msg.chn+1, msg.data[0], msg.data[1]); break; case MIDI_CTL_CHANGE: switch (msg.data[0]) { /* Channel Mode msg */ case 0x79: puts("Reset All Controllers"); break; case 0x7A: printf("Local Control "); if (msg.data[1] == 0) puts("OFF"); else puts("ON"); break; case 0x7B: puts("All Notes OFF"); break; case 0x7C: puts("Omni Mode OFF"); break; case 0x7D: puts("Omni Mode ON"); break; case 0x7E: printf("Mono Mode ON: base chn = %3d, #chn = %dn", msg.chn+1, msg.data[1]); break; case 0x7F: puts("Poly Mode ON"); break; default: /* Control Change normale */ printf("Control Change: chn = %2d, ctl = %3d, aft = %dn", msg.chn+1, msg.data[0], msg.data[1]); } break; case MIDI_PGM_CHANGE: printf("Program Change: chn = %2d, preset = %dn", msg.chn+1, msg.data[0]); break; case MIDI_CHN_PRESSURE: printf("Channel Pressure: chn = %2d, aft = %dn", 118
  • 124.
    CAPITOLO 5. ILMIDI A BASSO LIVELLO 5.5. ESEMPIO DI PROGRAMMA msg.chn+1, msg.data[0]); break; case MIDI_PITCH_BEND: printf("Pitch Bend Change: chn = %2d, pos = %dn", msg.chn+1, (msg.data[0]<<7) | msg.data[1]); break; /* System Exclusive Message */ case MIDI_SYSTEM_PREFIX: printf ("Sysex: F0t"); for (n = 0; n < msg.lundata; n++) printf("%Xt", *(msg.psysex + n)); printf("n"); break; /* System Common Messages */ case MIDI_QUARTER_FRAME: printf("MTC Quarter Frame: type = %d, data = %dn", (msg.data[0]>>4) & 0x0F, msg.data[1] & 0x0F); break; case MIDI_SONG_POSITION: printf("Song Position: pointer = %dn", (msg.data[0]<<7) | msg.data[1]); break; case MIDI_SONG_SELECT: printf("Song Select: #song = %dn", msg.data[0]); break; case MIDI_TUNE_REQUEST: puts("Tune Request"); break; /* System Real Time Messages */ case MIDI_TIMING_CLOCK: /* Ignorato */ break; case MIDI_START: puts("<Start>"); break; case MIDI_CONTINUE: puts("<Continue>"); break; case MIDI_STOP: puts("<Stop>"); break; case MIDI_ACTIVE_SENSING: puts("<Active Sensing>"); 119
  • 125.
    5.5. ESEMPIO DIPROGRAMMA CAPITOLO 5. IL MIDI A BASSO LIVELLO break; case MIDI_SYSTEM_RESET: puts("<System Reset>"); break; default: puts(">>> Messaggio non riconosciuto!"); } } close(midifd); return 0; } Il programma dumpmidi richiede come argomento da linea di comando il nu- mero della porta MIDI di cui effettuare il dump; se non lo si fornisce ` segnalato e l’errore: ~>dumpmidi Uso: dumpmidi <# porta MIDI> ` E segnalato errore anche se non ` fornito il numero di una porta MIDI esistente e nel sistema (tale numero segue la convenzione dei “Midi devices:” ottenuti con cat /dev/sndstat): ~>dumpmidi 1 Porta MIDI #1 non valida; # porte installate 1. Se si fornisce il giusto numero di porta MIDI, il programma comincia ad effettuare il dump dei messaggi in input; per interrompere il ciclo ` necessario e premere contemporaneamente i tasti Ctrl e C; di seguito ` riportato un esempio: e ~>dumpmidi 0 Dump input porta MIDI #0... Note ON: chn = 1, key = 60, vel = 96 Key Pressure: chn = 1, key = 60, aft = 98 Key Pressure: chn = 1, key = 60, aft = 66 Note ON: chn = 1, key = 60, vel = 0 . . . Si osservi come il numero del canale segua la convenzione MIDI, cio` msg.chn+1, e per riportarsi nell’intervallo 1÷16. 120
  • 126.
    Appendice A GENERAL MIDI A.1 Timbri strumentali In questa Sezione sono elencati i numeri di preset (scalati di uno, affinch´ li si pos- e sa utilizzare direttamente con SEQ_SET_PATCH()), le famiglie di timbri e i nomi degli strumenti dello standard General MIDI level 1, il tutto in correlazione con i nomi delle patch wavetable della Gravis UltraSound (file GF1 con estensione .pat). Di questi ultimi esiste una versione omonima e public domain chiamata MIDIA [8], in quanto i primi sono copyright della Voice Crystal e non liber- amente utilizzabili con OSS (per i timbri MIDIA originali non giudicati buoni sono elencati dei sostituti fra parentesi, con l’estensione dei primi rinominata a .pat.sav). Preset Famiglia Nome degli strumenti Nome patch (.pat) 0 Acoustic Grand Piano acpiano 1 Bright Acoustic Piano britepno 2 Electric Grand Piano synpiano (acpiano) 3 PIANO Honky-Tonk Piano honky 4 Electric Piano 1 epiano1 5 Electric Piano 2 epiano2 6 Harpsichord hrpschrd (acpiano) 7 Clavinet clavinet (acpiano) 8 Celesta celeste 9 Glockenspiel glocken 10 Music Box musicbox 11 CHROMATIC Vibraphone vibes 12 PERCUSSION Marimba marimba 13 Xylophone xylophon 14 Tubular Bells tubebell 15 Dulcimer santur 121
  • 127.
    A.1. TIMBRI STRUMENTALI APPENDICE A. GENERAL MIDI Preset Famiglia Nome degli strumenti Nome patch (.pat) 16 Drawbar Organ homeorg (rockorg) 17 Percussive Organ percorg 18 Rock Organ rockorg 19 ORGAN Church Organ church 20 Reed Organ reedorg (rockorg) 21 Accordion accordn 22 Harmonica harmonca 23 Tango Accordion concrtna 24 Acoustic Nylon Guitar nyguitar 25 Acoustic Steel Guitar acguitar 26 Electric Jazz Guitar jazzgtr 27 GUITAR Electric Clean Guitar cleangtr 28 Electric Muted Guitar mutegtr 29 Overdriven Guitar odguitar (rockgtr) 30 Distortion Guitar distgtr 31 Guitar Harmonics gtrharm 32 Acoustic Bass acbass 33 Electric Bass Fingered fngrbass 34 Electric Bass Picked pickbass 35 BASS Fretless Bass fretless 36 Slap Bass 1 slapbas1 37 Slap Bass 2 slapbas2 38 Synth Bass 1 synbass1 39 Synth Bass 2 synbass2 40 Violin violin 41 Viola viola 42 Cello cello 43 STRINGS Contrabass contraba 44 Tremolo Strings marcato 45 Pizzicato Strings pizzcato 46 Orchestral Harp harp 47 Timpani timpani 48 String Ensemble 1 marcato (slowstr) 49 String Ensemble 2 slowstr 50 Synth Strings 1 synstr1 51 ENSEMBLE Synth Strings 2 synstr2 52 Choir Aahs choir 53 Voice Oohs doo 54 Synth Voice voices 55 Orchestra Hit orchhit 122
  • 128.
    APPENDICE A. GENERALMIDI A.1. TIMBRI STRUMENTALI Preset Famiglia Nome degli strumenti Nome patch (.pat) 56 Trumpet trumpet 57 Trombone trombone 58 Tuba tuba 59 BRASS Muted Trumpet mutetrum 60 French Horn frenchrn 61 Brass Section hitbrass 62 Synth Brass 1 synbras1 63 Synth Brass 2 synbras2 64 Soprano Sax sprnosax 65 Alto Sax altosax 66 Tenor Sax tenorsax 67 REEDS Baritone Sax barisax 68 Oboe oboe 69 English Horn englhorn 70 Bassoon bassoon 71 Clarinet clarinet 72 Piccolo piccolo 73 Flute flute 74 Recorder recorder 75 PIPES Pan Flute woodflut 76 Bottle Blow bottle 77 Shakuhashi shakazul 78 Whistle whistle 79 Ocarina ocarina 80 Synth lead 1—Square wave sqrwave 81 Synth lead 2—Sawtooth Wave sawwave 82 Synth lead 3—Calliope calliope 83 SYNTH Synth lead 4—Chiff chiflead 84 LEAD Synth lead 5—Charang voxlead 85 Synth lead 6—Solo Voice voxlead 86 Synth lead 7—Bright Saw Wave lead5th 87 Synth lead 8—Brass and Lead basslead 88 Synth pad 1—Fantasia fantasia 89 Synth pad 2—Warm warmpad 90 Synth pad 3—Poly Synth polysyn 91 SYNTH Synth pad 4—Space Voice ghostie 92 PAD Synth pad 5—Bowed Glass bowglass 93 Synth pad 6—Metal metalpad 94 Synth pad 7—Halo halopad 95 Synth pad 8—Sweep sweeper 123
  • 129.
    A.1. TIMBRI STRUMENTALI APPENDICE A. GENERAL MIDI Preset Famiglia Nome degli strumenti Nome patch (.pat) 96 Synth SFX 1—Ice Rain aurora 97 Synth SFX 2—Soundtrack soundtrk 98 Synth SFX 3—Crystal crystal 99 SYNTH Synth SFX 4—Atmosphere atmosphr 100 EFFECTS Synth SFX 5—Brightness freshair 101 Synth SFX 6—Goblin unicorn 102 Synth SFX 7—Echo drops sweeper 103 Synth SFX 8—Star Theme startrak 104 Sitar sitar 105 Banjo banjo 106 Shamisen shamisen 107 ETHNIC Koto koto 108 Kalimba kalimba 109 Bagpipe bagpipes 110 Fiddle fiddle 111 Shanai shannai 112 Tinkle Bell carillon 113 Agogo agogo 114 Steel Drums steeldrm 115 PERCUSSIVE Woodblock woodblk 116 Taiko Drum taiko 117 Melodic Tom toms 118 Synth Drum syntom 119 Reverse Cymbal revcym 120 Guitar Fret Noise fx-fret 121 Breath Noise fx-blow 122 Seashore seashore 123 SOUND Bird Tweet jungle 124 EFFECTS Telephone Ring telephon 125 Helicopter helicptr 126 Applause applause 127 Gunshot pistol 124
  • 130.
    APPENDICE A. GENERALMIDI A.2. TIMBRI DELLE PERCUSSIONI A.2 Timbri delle percussioni Di seguito ` riportata una codifica standard dei timbri percussivi, ricavata dalle e batterie elettroniche di Roland e Sequential; il canale MIDI numero 10 (<voce>==9) ` per default assegnato alla sezione ritmica, con ogni patch che ` identificata da e e un numero di nota. Prima del nome della patch ` riportato anche il suo numero e d’ordine nella tabella interna del driver. Nota Timbro o Effetto No patch Nome patch (.pat) 28 Slap 156 slap 29 Mute Scratch 157 scratch1 30 Open Scratch 158 scratch2 31 Sticks 159 sticks 32 Square Click 160 sqrclick 33 Metal Click 161 metclick 34 Metal Bell 162 metbell 35 Kick 163 kick1 36 Sude Kick 164 kick2 37 Stick Rim 165 stickrim 38 Acoustic Snare 166 snare1 39 Hand Clap 167 claps 40 Electric Snare 168 snare2 41 Low–floor Tom 169 tomlo2 42 Clodes Hi–Hat 170 hihatcl 43 High–floor Tom 171 tomlo1 44 Pedal Hi–hat 172 hihatpd 45 Low Tom 173 tommid2 46 Open Hi–hat 174 hihatop 47 Low–middle Tom 175 tommid1 48 Hi–middle Tom 176 tomhi2 49 Crash cymbal 1 177 cymcrsh1 50 High Tom 178 tomhi1 51 Ride Cymbal 1 179 cymride1 52 Chinese Cymbal 180 cymchina 53 Ride Bell 181 cymbell 54 Tambourine 182 tamborin 55 Splash Cymbal 183 cymsplsh 56 Cowbell 184 cowbell 57 Crash Cymbal 2 185 cymcrsh2 58 Vibraslap 186 vibslap 59 Ride Cymbal 2 187 cymride2 125
  • 131.
    A.2. TIMBRI DELLEPERCUSSIONI APPENDICE A. GENERAL MIDI Nota Timbro o Effetto No patch Nome patch (.pat) 60 High Bongo 188 bongohi 61 Low Bongo 189 bongolo 62 Mute High Conga 190 congahi1 63 Open High Conga 191 congahi2 64 Low Conga 192 congalo 65 High Timbale 193 timbaleh 66 Low Timbale 194 timbalel 67 High Agogo 195 agogohi 68 Low Agogo 196 agogolo 69 Cabasa 197 cabasa 70 Maracas 198 maracas 71 Short Whistle 199 whistle1 72 Long Whistle 200 whistle2 73 Short Guiro 201 guiro1 74 Long Guiro 202 guiro2 75 Claves 203 clave 76 High Woodblock 204 woodblk1 77 Long Woodblock 205 woodblk2 78 Mute Cuica 206 cuica1 79 Open Cuica 207 cuica2 80 Mute Triangle 208 triangl1 81 Open Triangle 209 triangl2 82 Shaker 210 shaker 83 Jingles 211 jingles 84 Belltree 212 belltree 85 Castinet 213 castinet 86 Mute Surdo 214 surdo1 87 Open Surdo 215 surdo2 126
  • 132.
    Elenco delle figure 1.1 Modello come mixer della scheda audio in riproduzione . . . . . . 6 1.2 Modello come mixer della scheda audio in campionamento . . . . 6 1.3 Visione della scheda audio in riproduzione che OSS d` al program- a matore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.4 Visione della scheda audio in campionamento che OSS d` al pro- a grammatore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.5 Schema di utilizzo del multi–buffering . . . . . . . . . . . . . . . . 17 1.6 Codifica del minor number di un device file di OSS . . . . . . . . 20 2.1 Rappresentazione del volume di un canale stereo . . . . . . . . . . 28 4.1 Schema di funzionamento del sequencer . . . . . . . . . . . . . . . 67 4.2 Organizzazione degli eventi come i messaggi standard MIDI . . . 86 127
  • 133.
    Bibliografia [1] 4Front Technologies, OSS Programmer’s Guide, USA, 1997, http://www.4front-tech.com/pguide/ [2] H. Savolainen, Hacker’s Guide to VoxWare 2.4, second draft, Helsinki, 1994 [3] J. Tranter, LINUX MULTIMEDIA GUIDE, O’Reilly & Associates, Inc., 1996, Cap. 14 [4] J. Tranter, The Linux Sound HOWTO, Linux Documentation Project, http://ildp.psy.unipd.it/LDP/HOWTO/Sound-HOWTO.html [5] Hannu Savolainen, Alan Cox et al., Sorgenti di OSS/Free, Helsinki, 1993–97, /usr/src/linux/drivers/sound/ [6] Hannu Savolainen, Collezione di esempi di programmazione per OSS, Helsinki, 1993–97, http://www.tux.org/pub/devel/ossfree/snd-util-3.8.tar.gz [7] Hannu Savolainen, Esempi riguardanti mmap() e /dev/music, Helsinki, 1997, http://www.tux.org/pub/devel/ossfree/samples.tar.gz [8] MIDIA: collezione public domain di patch wavetable GUS compatibili, http://www.4front-tech.com/softoss.html [9] Brandon S. Higa, The Semi Official Turtle Beach Maui Page, http://www.lava.net/~bhiga/Maui/info.html [10] Steve D Pate, UNIX Internals – A Practical Approach, Addison–Wesley, USA, 1996, pp. 19–21, pp. 43-46, pp. 51–63 [11] Giovanni Perotti, MIDI Computer Immagine e Suono, Jackson Libri, Italia, 1998 128