Il
MIDI (Musical Instrument Digital Interface) è lo standard adottato
dall’industria
musicale per il controllo di dispositivi come sintetizzatori e schede
audio. I file MIDI non contengono campioni audio, ma semplicemente una
sequenza di istruzioni in formato numerico da inviare direttamente ai dispositivi
audio (è sufficiente possedere una comunissima scheda audio
installata sul proprio PC), che saranno così in grado di generare
un certo brano musicale in base alle istruzioni ricevute.
Facendo
un semplice paragone, si potrebbe considerare una sequenza MIDI come un
vero e proprio spartito musicale, e la scheda audio come “l’orchestra”
che deve eseguire il brano musicale così assegnatogli.
Un
simile approccio presenta notevoli vantaggi dovuti da un lato alla estrema
leggerezza di tale formato (che ne fa un candidato ideale per applicazioni
su INTERNET), dall’altra alla maneggevolezza, dal momento che è
molto facile intervenire personalmente per modificare le caratteristiche
di un brano MIDI (come la successione delle note, gli strumenti musicali
utilizzati, particolari effetti e molto altro ancora).
La
mia innata passione per la programmazione, unita a quella per la
composizione musicale, mi hanno indotto ad approfondire lo studio di tale
package, tanto da dedicarvi una vera e propria tesina per il corso di Ing.
del Software intitolata “Introduzione al MIDI in Java”, presso l’Università
di Ing.Elettronica di Cagliari.
Ho
pensato dunque di riorganizzare il materiale di tale tesina in una serie
di articoli pratici, con l’obiettivo principale di fornire delle conoscenze
di base che consentano a chiunque di avvicinarsi gradualmente all’ambito
musicale-informatico del MIDI, tramite l’utilizzo del package del JDK 1.3
javax.sound.midi.
Per incominciare…
Per
incominciare, in questa prima puntata analizzeremo un semplice esempio
che ci consentirà di indagare sui dispositivi MIDI installati correntemente
sul nostro sistema e quindi di aprire ed ascoltare un file MIDI.
Qui
di seguito, per comodità, si riporta subito il codice dell’intero
programma, ridotto ai minimi termini per motivi di semplicità e
descritto in seguito nei dettagli.
import
java.io.File;
import
java.io.IOException;
import
javax.sound.midi.*;
public
class Midi1 {
public static void main (String[] args){
System.out.println("Esempio n.1 sul MIDI \n");
// Verifichiamo la corretta installazione dei dispositivi
// MIDI sul nostro sistema:
int num_device = MidiSystem.getMidiDeviceInfo().length;
// se non è installato almeno un
// dispositivo MIDI
// l'applicazione può dirsi conclusa:
if (num_device<1){
System.out.println("Il sistema non possiede alcun
dispositivo MIDI installato \n");
System.exit(0);
}
System.out.println("Elenco dei dispositivi MIDI
installati:\n");
// Indaghiamo sui dispositivi MIDI installati e
// visualizziamo il loro nome
for (int i=0;i<num_device;i++)
System.out.println(
MidiSystem.getMidiDeviceInfo()[i].getName());
System.out.println("\n" +
"Numero dei dispositivi installati:" +
String.valueOf(num_device) + "\n");
// Procediamo ora al caricamento del file MIDI
Sequence mySequence;
Sequencer mySequencer;
File myMidiFile;
try{
mySequencer = MidiSystem.getSequencer();
mySequencer.open();
myMidiFile = new File("trippygaia1.mid");
mySequence = MidiSystem.getSequence(myMidiFile);
mySequencer.setSequence(mySequence);
// fa iniziare l’esecuzione musicale
mySequencer.start();
System.out.println("Riproduzione della sequenza
musicale del ");
System.out.println("file:" + myMidiFile.getName()+
" in corso... \n");
System.out.println("Premi INVIO per terminare la
riproduzione del brano ");
System.in.read();
// fa terminare l’esecuzione musicale
mySequencer.stop();
System.out.print("Esecuzione terminata al secondo: ");
System.out.println(""+
Sequencer.getMicrosecondPosition()/1000000);
System.out.println();
System.out.println("La lunghezza della sequenza MIDI
era di ");
System.out.print(""+
Sequencer.getMicrosecondLength()/1000000));
System.out.println(" secondi. ");
// chiude il dispositivo precedentemente aperto
mySequencer.close();
mySequencer = null;
System.exit(0);
}
catch (MidiUnavailableException ex) {
System.out.println("Sequencer non disponibile \n");
System.exit(0);}
catch (IOException ex) {
System.out.println("Impossibile aprire il file
specificato \n");
System.exit(0);}
catch (InvalidMidiDataException ex){
System.out.println("Errore nel formato dei dati MIDI \n");
System.exit(0);}
}
}
Commento della
applicazione Midi1
La
prima parte di codice, come già anticipato, ha il fine di informare
l’utente sulle risorse MIDI correntemente installate nel proprio sistema.
Per
fare ciò, Java ci mette a disposizione la classe MidiSystem, come
specificato dalla documentazione fornita dalla Sun (si veda [2] per ulteriori
informazioni):
“The
MidiSystem class provides access to the installed MIDI system resources,
including devices such as synthesizers, sequencers, and MIDI input and
output ports.
A
typical simple MIDI application might begin by invoking one or more MidiSystem
methods to learn what devices are installed and to obtain the ones needed
in that application.
The
class also has methods for reading files, streams, and URLs that contain
standard MIDI file data or soundbanks. You can query the MidiSystem for
the format of a specified MIDI file.
You
cannot instantiate a MidiSystem; all the methods are static.”
Relativamente
al nostro caso, per il momento sarà nostro interesse accedere
a delle informazioni sui dispositivi MIDI installati , più che ai
dispositivi stessi.
Per
fare ciò occorrerà utilizzare il metodo
MidiDevice.Info
[] MidiSystem.getMidiDeviceInfo()
dove
appunto MidiDevice.Info [] è un array di oggetti MidiDevice.Info,
uno per ciascun dispositivo installato, che contiene informazioni di carattere
generale sul dispositivo MIDI in questione (come il nome, il venditore,
etc.).
Pertanto,
tramite l’assegnamento
int
num_device = MidiSystem.getMidiDeviceInfo().length;
la
variabile num_device conterrà il numero di dispositivi MIDI installati,
mentre
il
metodo
MidiSystem.getMidiDeviceInfo()[i].getName()
restituirà
una stringa contenente il nome dell’ i-esimo dispositivo installato.
L’applicazione
avrà subito termine nel caso non venga rilevato alcun dispositivo
MIDI (ossia nel caso risulti num_device = 0).
In
caso contrario, sarà possibile procedere nel tentativo di apertura
del nostro file MIDI.
Come
prima cosa, vengono dichiarate tutte le variabili necessarie per
il caricamento e l’ascolto del file MIDI:
File
myMidiFile;
Sequence
mySequence;
Sequencer
mySequencer;
dove
File
myMidiFile
dovrà
contenere il file MIDI da ascoltare e dove
Sequence
mySequence
costituirà
la nostra sequenza MIDI, il cui contenuto – relativamente al nostro esempio
– verrà a sua volta determinato dal contenuto del file myMidiFile
(tale punto verrà chiarito meglio fra breve).
Per
ora ci basterà sapere che mySequence (istanza della classe Sequence)
dovrà contenere tutte le informazioni musicali (informazioni sul
tempo ed una o più tracce MIDI) necessarie per la corretta esecuzione
del brano.
Per
essere eseguita musicalmente, una sequenza musicale (nel nostro caso mySequence),
dovrà essere indirizzata ad un Sequencer (dispositivo software o
hardware che consente l’esecuzione di un brano MIDI), da noi rappresentato
dalla variabile mySequencer.
In
Java, col termine Sequencer si fa riferimento ad una interfaccia che include
dei metodi che consentono, tra le altre cose, di (cfr.Interface Sequencer,
Java API Reference, relativamente alla voce [2]):
-
obtaining
a sequence from MIDI file data
-
starting
and stopping playback
-
moving
to an arbitrary position in the sequence
-
changing
the tempo (speed) of playback
-
synchronizing
playback to an internal clock or to received MIDI messages
-
controlling
the timing of another device
In addition,
the following operations are supported, either directly, or indirectly
through objects that the Sequencer has access to:
-
editing
the data by adding or deleting individual MIDI events or entire tracks
-
muting
or soloing individual tracks in the sequence
-
notifying
listener objects about any meta-events or control-change events encountered
while playing back the sequence.
Nel nostro
esempio, ci accontenteremo nell’eseguire le prime due operazioni, ovvero
il caricamento di una precisa sequenza sul nostro Sequencer (nel nostro
caso mySequencer) e l’esecuzione musicale della sequenza stessa.
Per
fare ciò, iniziamo a caricare la nostra sequenza MIDI scrivendo:
myMidiFile
= new File("trippygaia1.mid");
che
associa alla variabile myMidiFile il file chiamato “trippygaia1.mid”, e
quindi:
mySequence
= MidiSystem.getSequence(myMidiFile);
che
crea una nuova sequenza MIDI ottenuta dal file specificato mediante l’impiego
del metodo getSequence(File MidiFile) della già menzionata classe
MidiSystem.
A
questo punto è necessario inizializzare il nostro sequencer, attraverso
l’istruzione
mySequencer
= MidiSystem.getSequencer();
che
associa alla variabile mySequencer il Sequencer di default del sistema
specifico in uso.
Per
poter caricare la sequenza creata nel nuovo Sequencer, è prima necessario
aprire quest’ultimo, tramite il metodo open(), come segue:
mySequencer.open();
A questo
punto non ci rimane che caricare la nostra sequenza scrivendo:
mySequencer.setSequence(mySequence);
e
mandarla in esecuzione col metodo start(), come segue:
mySequencer.start();
Una
volta che la sequenza inizia ad essere riprodotta musicalmente dovrà
essere possibile, ad un certo punto, interromperla.
Ciò
è possibile mediante il metodo stop(), come segue:
mySequencer.stop();
Il
metodo
long
mySequencer.getMicrosecondPosition()
restituisce
la posizione corrente nella sequenza MIDI (espressa in microsecondi)
mentre
il metodo
long
mySequencer.getMicrosecondLength()
restituisce,
sempre in microsecondi, la durata totale della sequenza MIDI corrente.
E’
molto importante, alla fine della applicazione, chiudere tutti i dispositivi
MIDI aperti in precedenza, per evitare eventuali problemi che possono sorgere,
altrimenti, al riavvio della applicazione stessa o all’avvio di altre applicazioni.
Nel
nostro caso, sarà sufficiente chiudere il nostro Sequencer e farlo
puntare a null, come segue:
mySequencer.close();
mySequencer=null;
Per
concludere, diamo un rapido sguardo alla gestione delle eccezioni.
Nel
blocco
catch
(MidiUnavailableException ex) {
System.out.println("Sequencer non disponibile \n");
System.exit(0);
}
vengono
raccolte le eventuali eccezioni gestite dalla classe MidiUnavailableException,
che vengono lanciate quando un componente MIDI (nel nostro caso mySequencer)
non può essere aperto o creato poiché non è disponibile.
Nel
blocco
catch
(InvalidMidiDataException ex){
System.out.println("Errore
nel formato dei dati MIDI\n");
System.exit(0);
}
vengono
invece raccolte le eventuali eccezioni gestite dalla classe InvalidMidiDataException,
che vengono lanciate quando si incontrano dei dati in un formato diverso
da quello MIDI, e quindi non interpretabili dal dispositivo che tenta di
leggerli.
Relativamente
al nostro esempio, tale eccezione potrebbe essere sollevata in seguito
all’istruzione
mySequence
= MidiSystem.getSequence(myMidiFile);
qualora
myMidiFile non rappresenti un file in un formato MIDI valido, oppure in
seguito all’istruzione
mySequencer.setSequence(mySequence);
qualora
la variabile mySequence dovesse contenere una sequenza di dati MIDI in
un formato non corretto (o comunque non supportato dal Sequencer).
Osservazioni finali
Nel
caso di caricamento di un file MIDI su una Sequence, possiamo quindi notare
come per poter ascoltare il brano musicale non sia necessaria nessuna operazione
aggiuntiva se non quella di caricare la sequenza stessa sul Sequencer e
mandare quest’ultimo in esecuzione.
Questo
capita poiché la sequenza conterrà già in sé
tutte le informazioni necessarie (sul tempo, sulle note, sugli strumenti
da usare, ecc.) per la corretta esecuzione di un brano musicale, essendole
state direttamente fornite dal file MIDI stesso.
Nelle
prossime
puntate verrà spiegato come costruire da sé una sequenza
MIDI “partendo da zero” (senza cioè dover necessariamente ricorrere
ad un file MIDI per generarla) e come poi salvarla sotto forma di file
MIDI.
Quando
invece ci si vuole limitare a generare dei suoni, è possibile evitare
sia il ricorso alla Sequence che al Sequencer tramite l’uso di un sintetizzatore
Midi, supportato in Java dall’interfaccia Synthesizer.
Nella
prossima puntata verrà fornito un esempio pratico su come generare
i suoni col sintetizzatore MIDI tramite l’uso dell’interfaccia Synthesizer,
insieme alla descrizione delle classi ed interfacce ad esso correlate.
Bibliografia
[1]
S.Monni – “Introduzione al MIDI in Java”, Tesina di Ing. del Software
presso l’Università di Ing.Elettronica di Cagliari –
gennaio 2001
[2]
Documentazione fornita dalla Sun su Midi consultabile all’indirizzo:
http://
java.sun.com/j2se/1.3.0/docs/api/javax/sound/midi/package-summary.html
|