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