Introduzione
In questo capitolo verrà spiegato il modo in
cui realizzare da sé una sequenza MIDI per
poi salvarla sotto forma di file MIDI.
A questo scopo è necessario avere a disposizione,
oltre ad una Sequence (sulla quale andremo a scrivere
tutte le informazioni relative alla nostra composizione
musicale come le note, gli strumenti, etc) e ad un
Sequencer (che riprodurrà musicalmente la Sequence
stessa) i seguenti elementi:
-
Una o più tracce musicali (in Java rappresentate
dalla classe Track) , che costituiscono dei "pezzi"
di sequence e sono costituite, a loro volta, da
eventi Midi.
- Un
insieme di eventi Midi (in Java rappresentati dalla
classe MidiEvent(messaggio, istante di inizio))
generati attraverso la spedizione di messaggi (in
Java rappresentati dalla classe MidiMessage e dalle
sue sottoclassi) che specificano il tipo di evento
Midi che si vuole generare (risulterà tutto
più chiaro con il commento dell'esempio proposto
più avanti).
Schematizzando, possiamo rappresentare l'intera rete
di collegamenti come segue (fig.1):
L'esempio successivo riprenderà esattamente
lo schema riportato in fig1 (le voci inserite nelle
varie caselle di testo corrispondono alle omonime
classi e interfacce utilizzate in Java).
E' importante notare come la mancanza di anche una
sola delle classi e/o interfacce sopra menzionate
renderebbe vano qualsiasi tentativo di riproduzione
musicale di una sequenza Midi creata ex-novo.
Esempio n.3 (Come realizzare un
file MIDI)
import
javax.sound.midi.*;
import java.io.*;
/**
Informazioni di base per la gestione della
* durata di una nota musicale:
* given 120 bpm:
* (120 bpm)/(60 seconds per minute)= 2 beats per second
* 2 / 1000 beats per millisecond
* (2 * resolution) ticks per second
* (2 * resolution)/1000 ticks per millisecond, or
* (resolution / 500) ticks per millisecond
* ticks = milliseconds * resolution / 500
*/
class Midi3 implements MetaEventListener
{
final int PROGRAM = 192;
final int NOTEON = 144;
final int NOTEOFF = 128;
final int DO = 60;
final int MI = 64;
final int SOL = 67;
Track track;
Sequence mySequence;
Sequencer mySequencer;
int accordoDo[] = { DO,MI,SOL } ;
int velocity = 100; // volume delle note
int res=1; // Resolution
int numChan = 1; // numero di canale Midi
int numInstr = 0; // numero dello strumento
// musicale ( 0:pianoforte)
int tempoBPM = 120; // tempo espresso in battiti
// al minuto (metronomo)
boolean end_of_track = false;
public Midi3()
{
try {
mySequence = new Sequence(Sequence.PPQ,res);
// Resolution = res
mySequencer = MidiSystem.getSequencer();
mySequencer.open();
mySequencer.addMetaEventListener(this);
// Creiamo una traccia MIDI inizialmente vuota:
track = mySequence.createTrack();
// Creiamo un evento MIDI fornendo i seguenti
// parametri:
// tipo di comando ,canale, strumento,
// istante d'inizio dell'evento):
createEvent(PROGRAM,numChan,numInstr,0);
for (int i = 0;i < accordoDo.length;i++)
{
createEvent(NOTEON,numChan,accordoDo[i],i*1000); createEvent(NOTEOFF,numChan,accordoDo[i],4000);
}
mySequencer.setSequence(mySequence);
mySequencer.start();
mySequencer.setTempoInBPM(tempoBPM); // 120 bpm
System.out.println("Esecuzione
dell'accordo di DO Magg.");
System.out.println();
System.out.println("tempo del metronomo:"
+
String.valueOf(tempoBPM) + " battiti al minuto\n");
//
Attendiamo che il sequencer riproduca l'intera traccia:
while(!end_of_track())
{}
System.out.println("Esecuzione terminata.\n");
//
Creiamo il nostro file MIDI:
System.out.println("Inserisci
il nome del file da salvare:");
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
String name = in.readLine();
if (!name.equals(""))
{ if (!name.endsWith(".mid")) name += ".mid";
saveMidiFile(new File(name));
}
System.out.println("Applicazione terminata");
closeDevice();
System.exit(0); }
catch (Exception ex) { ex.printStackTrace(); }}
// Il seguente metodo crea un nuovo evento MIDI:
private
void createEvent(int type, int chan, int num,
long millis)
{
ShortMessage message = new ShortMessage();
try {
long tick = millis * mySequence.getResolution()/500;
message.setMessage(type, chan, num, velocity);
MidiEvent event = new MidiEvent( message, tick );
track.add(event);
} catch (Exception ex) { ex.printStackTrace();}
}
// Il seguente metodo crea un nuovo file MIDI:
public
void saveMidiFile(File file) {
try {
int[] fileTypes = MidiSystem.getMidiFileTypes(mySequence);
if (fileTypes.length == 0)
System.out.println("Non posso salvare la sequenza!!!");
else
if (MidiSystem.write(mySequence, fileTypes[0], file)
== -1) throw new IOException("Problemi nella
scrittura del file");
else
System.out.println("File MIDI salvato correttamente.");
} catch (Exception ex) {ex.printStackTrace();}
}
//
Il seguente metodo individua il metaEvent corrispondente
// alla fine di una traccia MIDI:
public
void meta(MetaMessage message) {
if (message.getType() == 47) // 47 è la fine
della traccia
{
System.out.println("MetaEvent: riscontrata la
fine della
traccia.\n");
mySequencer.stop();
end_of_track = true; // è stata raggiunta la
fine della
//traccia
}
}
public
void closeDevice()
{
if (mySequencer != null) mySequencer.close();
mySequencer=null;
System.out.println("Sequencer chiuso.");
System.out.println();
}
public static void main(String [] args)
{ Midi3 app = new Midi3();
}
}
Commento dell'applicazione Midi3
Il codice riportato nell'esempio n.3 consiste
in una applicazione Java che consente, - analogamente
all'esempio visto nella scorsa puntata - la riproduzione
musicale dell'accordo di Do Magg ,ma questa volta
le note dell'accordo vengono caricate su una sequenza
MIDI e quindi riprodotte musicalmente da un sequencer.
Al termine dell'esecuzione musicale della sequenza
(ossia dell'accordo stesso) verrà data all'utente
la possibilità di salvare la sequenza MIDI
appena creata sotto forma di file MIDI, dopo aver
specificato il nome da dare al file stesso. Infine,
verranno effettuate tutte le operazioni di chiusura
del dispositivo aperto in precedenza (nel nostro caso
il Sequencer) e l'applicazione avrà termine.
Analizziamo
ora il codice più nel dettaglio:
La
costante intera
final
int PROGRAM = 192;
definisce
il numero di comando utilizzato per impostare un determinato
strumento musicale su un determinato canale MIDI.
Il valore PROGRAM verrà infatti passato come
argomento alla procedura
createEvent(int
type, int chan, int num, long millis)
tramite il comando
createEvent(PROGRAM,numChan,numInstr,0);
dove
i vari argomenti indicano, rispettivamente:
-
PROGRAM: tipo (type) di comando da inviare all'evento
che si deve costruire;
- numChan:
numero di canale MIDI (valore compreso fra 0 e 15);
- numInstr:
relativamente al comando PROGRAM, esso indica lo
strumento
musicale che si intende usare;
- 0:
rappresenta l'istante temporale - rispetto all'inizio
della sequenza - in cui l'evento dovrà verificarsi.
Questo valore verrà passato all'argomento
'long millis'.
Le
costanti intere
final
int NOTEON = 144;
final int NOTEOFF = 128;
definiscono
i valori interi relativi ai comandi, rispettivamente,
per la generazione di una nota ed il suo "spegnimento".
Il
valore NOTEON (e analogamente NOTEOFF) verrà
passato come argomento alla procedura
createEvent(int
type, int chan, int num, long millis)
tramite il comando
createEvent(NOTEON,numChan,accordoDo[i],i*1000);
dove,
ancora una volta, i vari argomenti staranno ad indicare:
-
NOTEON: tipo (type) di comando da inviare all'evento
che si deve costruire;
- numChan:
numero del canale MIDI (valore compreso fra 0 e
15);
- accordoDo[i]:
relativamente al comando NOTEON, esso indica l'altezza
tonale
della nota musicale che si vuole generare;
- i*1000:
rappresenta l'istante di tempo - rispetto all'inizio
della sequenza -
in cui l'evento dovrà aver inizio. Questo
valore verrà passato
all'argomento 'long millis'.
Diamo ora un'occhiata al ciclo che chiama la procedura
per la creazione dei vari eventi MIDI:
for
(int i = 0;i < accordoDo.length;i++){
createEvent(NOTEON,numChan,accordoDo[i],i*1000);
createEvent(NOTEOFF,numChan,accordoDo[i],4000);
}
Esso
impone di generare (alla distanza di un secondo l'una
dall'altra) le tre note dell'accordo, a partire dall'inizio
della sequenza MIDI (ossia il DO a 0 millisec, il
MI a 1000 millisec, il SOL a 2000 millisec,) e quindi
di spegnerle tutte al quarto secondo (4000 millisec.).
A questo punto sarà però necessario
introdurre qualche fondamentale nozione sul tempo.
Gestione
del tempo di un evento MIDI
Dalla
teoria musicale, è noto che una intera composizione
musicale vada eseguita con un precisa velocità
di esecuzione, regolata dalla frequenza di oscillazione
di un metronomo.
Ora, relativamente al nostro caso, imporre al metronomo,
ad esempio, una scansione temporale di 120 battiti
al minuto (dove questi ultimi vengono indicati coi
termini "beats" o anche, relativamente al
nostro esempio, "ticks"), significherà
che verrà generato un battito (tick) ogni mezzo
secondo.
Ebbene,
il "tick" rappresenterà l'unità
di misura temporale dell'intera sequenza musicale.
Relativamente
al nostro caso, ciò significa che un qualsiasi
evento MIDI potrà essere generato all'inizio
della sequenza (tick=0), oppure in corrispondenza
del primo tick (tick=1, ovvero dopo mezzo secondo),
del secondo tick (tick=2, ovvero dopo un secondo)
e così via, ma mai, ad esempio, in corrispondenza
di ½ tick o ¾ tick.
Il
modo per impostare il tempo del metronomo desiderato
sul Sequencer è il seguente:
mySequencer.setTempoInBPM(tempoBPM);
// int tempoBPM = 120
Ragionando
in termini di ticks (e non di millisecondi) avremmo
potuto ottenere lo stesso risultato sonoro riportando
le seguenti modifiche:
1- Al
posto di
createEvent(NOTEON,numChan,accordoDo[i],i*1000);
// tempo espresso in millisecondi
createEvent(NOTEOFF,numChan,accordoDo[i],4000);
// sono passati 4000 ms
avremmo
dovuto scrivere
createEvent(NOTEON,numChan,accordoDo[i],i*2);
// tempo espresso in ticks
createEvent(NOTEOFF,numChan,accordoDo[i],8);
// sono passati 8 ticks = 4000 ms
(poiché
l'intervallo fra un tick ed il successivo è
, nel nostro caso, pari a mezzo secondo)
2 -
Avremmo poi dovuto eliminare la conversione dei millis
in tick, ovvero l'istruzione (commentata più
avanti):
long
tick = millis * mySequence.getResolution() / 500;
3 -
Cambiare l'intestazione della procedura
private
void createEvent(int type, int chan, int num, long
millis)
nella
nuova
private
void createEvent(int type, int chan, int num, long
tick)
A
questo punto potrebbe sorgere una domanda più
che giustificata:
se si volesse inserire una nota, ad esempio dopo un
tempo pari a 250 ms dall'inizio della sequenza, come
si potrebbe fare?
Ciò equivarrebbe al tentativo di inserimento
di una nota dopo un tempo pari a mezzo tick, il che
non è possibile poiché un tick è
sempre, per definizione, un valore intero.
In Java, questo problema è risolvibile in forma
molto comoda e pressochè immediata attraverso
il ricorso alla cosiddetta "risoluzione di sequenza",
che permette di impostare una distanza temporale fra
due ticks consecutivi arbitrariamente piccola.
L'assegnamento
della risoluzione temporale di una determinata sequenza
viene effettuata all'atto della creazione della sequenza
stessa. Nel nostro esempio:
int
res = 1; // Risoluzione: è sempre un valore
intero maggiore di 0
mySequence = new Sequence(Sequence.PPQ,res);
dove
il primo argomento, ovvero il campo PPQ della classe
Sequence, specifica che la risoluzione res va espressa
in ticks per quarto di nota (Per ulteriori modalità
di risoluzione si consulti la voce [2]).
Maggiore sarà la nostra risoluzione res, più
ravvicinati nel tempo saranno due ticks consecutivi,
e conseguentemente in maggior misura sarà possibile
ravvicinare due eventi MIDI consecutivi fra di loro.
Questo
fatto si comprende esaminando l'istruzione di conversione:
long
tick = millis * mySequence.getResolution() / 500;
dove
il metodo getResolution() è il metodo che restituisce
la risoluzione della sequenza ad esso relativa (nel
nostro esempio sarà res).
In particolare, si può notare che, ponendo
res=500, si ottenga proprio l'uguaglianza tick = millis,
il che significa avere una risoluzione tanto alta
da consentire la disposizione di due eventi MIDI consecutivi
fino alla distanza di 1 ms l'uno dall'altro.
ATTENZIONE:
Questo non significa che non sia possibile generare
più di un evento MIDI nello stesso istante:
la simultaneità fra due o più eventi
(che è realizzabile associando a tali eventi
lo stesso valore di tick) non ha niente a che vedere
con la distanza minima consentita fra due ticks consecutivi.
L'istruzione
di conversione è ricavata dalle osservazioni
effettuate nei commenti introduttivi del codice dell'esempio,
che si riporta qui sotto:
/*
Informazioni di base per la gestione della durata
di una nota musicale:
* given 120 bpm:
* (120 bpm) / (60 seconds per minute) = 2 beats per
second
* 2 / 1000 beats per millisecond
* 2 * (resolution) ticks per second
* (2 * resolution)/1000 ticks per millisecond, or
* (resolution / 500) ticks per millisecond
* ticks = milliseconds * resolution / 500
*/
Le
osservazioni da fare a tale proposito sono due:
1)
Il valore della risoluzione, e quindi della distanza
fra due ticks consecutivi non incide sulla velocità
di esecuzione della sequenza, regolata esclusivamente,
nel nostro esempio, dal valore di tempoBPM.
2)
Tale conversione assicura che, fissato un tempo di
metronomo pari a 120 beats per minuto il valore di
1 millis corrisponda effettivamente, nella realtà,
ad 1 millisecondo.
Inoltre,
dalle informazioni sopra fornite si può ricavare
una interessante relazione fra i beats ed i ticks,
che è la seguente:
tick
= beat / resolution
dove
in questo caso intendiamo, con la voce tick, la distanza
temporale fra due ticks consecutivi. Infatti, fissata
la durata di un beat (che è sempre pari alla
metà del periodo di oscillazione del metronomo)
, quella di un tick diminuirà all'aumentare
della risoluzione, e viceversa.
Dal momento che, nell'esempio considerato, si è
posto res =1 (cosicchè risultava l'equivalenza
tick = beat) all'inizio di tale paragrafo si è
preferito non precisare subito tale distinzione (sia
pure importante) per non appesantire troppo la trattazione.
Chiariti
questi punti chiave sul tempo, possiamo ritornare
a commentare il codice dal punto in cui l'abbiamo
interrotto.
Esaminiamo
ora nel dettaglio la procedura che si occupa di generare
un singolo evento MIDI:
private void createEvent(int type,int chan,int num,long
millis) {
ShortMessage
message = new ShortMessage();
try {
long tick = millis * mySequence.getResolution()/500;
message.setMessage(type, chan, num, velocity);
MidiEvent event = new MidiEvent( message, tick );
track.add(event);
} catch (Exception ex) { ex.printStackTrace(); }
}
In
Java, un evento MIDI viene identificato con due attributi:
il primo è un 'messaggio MIDI' (che conterrà
tutte le informazioni necessarie per caratterizzare
tale evento); il secondo è l'istante di tempo
in cui tale evento dovrà essere generato all'interno
della sequenza MIDI d'appartenenza.
Pertanto la prima cosa da fare è creare un
nuovo messaggio (inizialmente vuoto):
ShortMessage
message = new ShortMessage();
Ricordo
che ShortMessage è una sottoclasse di MidiMessage.
Un messaggio di questo tipo può contenere al
più due bytes di dati, oltre al suo byte di
stato (ossia quello che specifica il tipo di comando
da eseguire).
Nel nostro esempio risulta essere scritto:
message.setMessage(type,
chan, num, velocity);
i
cui argomenti sono descritti qui di seguito:
- type
: è il byte di stato, ovvero il tipo
di comando che deve essere eseguito.
Nel nostro esempio, il primo comando da impartire
(ovvero il primo evento
da generare) è PROGRAM, poiché esso
provvederà a selezionare lo
strumento musicale con cui sarà possibile
ascoltare le note musicali;
i comandi successivi saranno quelli relativi a NOTEON
e quindi NOTEOFF.
- chan:
indica il canale Midi cui spedire il comando 'type'.
num: indica il primo byte di dati: esso assumerà
un significato diverso a seconda
del tipo di comando type cui sarà associato
(per PROGRAM il numero di
strumento selezionato e per NOTEON e NOTEOFF l'altezza
della nota
d'interesse).
- velocity:
indica il volume della nota, relativamente ad un
tipo di comando
NOTEON o NOTEOFF. ( In generale rappresenta il secondo
byte di
informazione del messaggio stesso).
Nota:
Con messaggi del tipo ShortMessage è possibile,
in pratica, spedire qualsiasi tipo di messaggio, eccetto
messaggi esclusivi del sistema (gestiti dalla classe
SysexMessage) e quelli relativi ai cosiddetti meta-events
(gestiti dalla classe MetaEvent, descritta in seguito).
Una
volta realizzato il messaggio (message) desiderato,
è necessario passarlo come argomento all'evento
Midi che si vuole costruire:
MidiEvent
event = new MidiEvent(message, tick);
dove
il secondo argomento (tick) contiene le informazioni
relative all'istante in cui l'evento stesso dovrà
essere generato, come già ampiamente spiegato.
L'evento così creato dovrà essere inserito
in una traccia Midi, la quale a sua volta dovrà
appartenere ad una determinata sequenza.
Una volta creata una nuova sequenza con la già
commentata istruzione
Sequence
mySequence = new Sequence (Sequence.PPQ,res);
si
può creare una nuova traccia (track) da associare
alla sequenza appena creata:
Track
track = mySequence.createTrack(); // traccia inizialmente
vuota
C'è
da notare, a questo proposito, che la dichiarazione
della traccia avviene al di fuori della procedura
CreateEvent, di modo che tutti gli eventi generati
tramite una chiamata a quest'ultima vengano caricati
in una stessa traccia (nel nostro esempio, track)
.
A questo punto è possibile aggiungere alla
nuova traccia l'evento precedentemente creato:
track.add(event);
// aggiunge l'evento alla traccia
e
la procedura CreateEvent può così portare
a termine il proprio lavoro.
Una volta costruiti tutti gli eventi (uno per ogni
chiamata alla procedura CreateEvent) la nostra sequenza
è finalmente pronta per essere riprodotta dal
sequencer.
Le
istruzioni per eseguire tale operazione sono ormai
note dal primo esempio:
mySequencer.open();
mySequencer.setSequence(mySequence);
mySequencer.start();
L'esecuzione
musicale dovrà proseguire finchè non
verrà raggiunta la fine della sequenza musicale
(ovvero dell'unica traccia MIDI che la costituisce).
Per verificare ciò è possibile introdurre
un particolare ascoltatore di eventi, ovvero un ascoltatore
di MetaEvent. Così come gli ShortEvents sono
costituiti da ShortMessages, i MetaEvents sono costituiti
da MetaMessages. Come specificato dalla documentazione
della Sun:
"Un
MetaMessage è un MidiMessage che non è
significativo per i sintetizzatori, ma che può
essere immagazzinato in un file MIDI ed interpretato
da un Sequencer. [
]
Il formato Midi definisce vari tipi standard di meta-events,
come il numero di sequenza [
]", le specificazioni
sui nomi delle tracce , sulle indicazioni del tempo,
etc.
Per
maggiori informazioni sui meta-events (e sul MIDI
in generale!) si consiglia la consultazione del sito
(http://www.midi.org).
Il verificarsi del raggiungimento della fine di una
traccia MIDI è appunto un caso di meta-event.
Come per tutti i tipi di eventi in Java, anche questo
tipo di eventi potrà essere individuato da
un ascoltatore di eventi, che dovrà essere
aggiunto al Sequencer
in uso, come segue:
mySequencer.addMetaEventListener(this);
Non
ci si deve dimenticare che MetaEventListener è
un'interfaccia che andrà implementata. Pertanto
dovremo ricordarci all'inizio della classe, di specificare
l'interfaccia da implementare
class
Midi3 implements MetaEventListener
e
ricordarci di implementare l'unico metodo che la costituisce,
ovvero il metodo meta(MetaMessage meta)
come
segue:
public
void meta(MetaMessage message) {
if (message.getType() == 47) // 47 è la fine
della traccia
{
System.out.println("MetaEvent: riscontrata la
fine della
traccia.\n");
mySequencer.stop();
end_of_track = true; // è stata raggiunta la
fine della
//traccia
}
}
In
questo metodo, la condizione
if
(message.getType() == 47)
verifica
se il tipo di metaEvent riscontrato ha valore pari
a 47, ovvero a quel particolare valore che corrisponde
al raggiungimento della fine di una traccia (track).
In caso affermativo il Sequencer viene stoppato e
la variabile booleana
end_of_track
= true;
in
modo da consentire l'uscita dal ciclo while all'interno
del metodo midi3()
while(!end_of_track)
{}
Dopo
l'uscita da tale ciclo si può procedere alla
fase di salvataggio della sequenza sotto forma di
file MIDI. Tale compito è svolto dalla procedura
seguente:
public
void saveMidiFile(File file) {
try {
int[] fileTypes = MidiSystem.getMidiFileTypes(mySequence);
if (fileTypes.length == 0)
System.out.println("Non posso salvare la sequenza!!!");
else
if (MidiSystem.write(mySequence, fileTypes[0], file)
== -1) throw new IOException("Problemi nella
scrittura del file");
else
System.out.println("File MIDI salvato correttamente.");
} catch (Exception ex) {ex.printStackTrace();}
}
Con
la istruzione
int[]
fileTypes = MidiSystem.getMidiFileTypes(mySequence);
stiamo
in pratica richiedendo al nostro MIDISystem di fornirci
l'insieme dei tipi di formato MIDI (formato MIDI tipo
0,1,2) disponibili per la sequenza mySequence, che
è quella contenente la musica che vogliamo
salvare (si veda la voce [1] per ulteriori informazioni
sui vari formati MIDI).
Il metodo
public
static int[] getMidiFileTypes(Sequence sequence)
restituisce
un array di lunghezza nulla nel caso il nostro sistema
non supporti, relativamente a quella sequenza, alcun
tipo di formato MIDI.
Pertanto avremo le seguenti istruzioni
if
(fileTypes.length == 0)
System.out.println("Non posso salvare la sequenza!!!");
Se invece il nostro sistema riconosce nella nostra
sequenza un tipo di formato valido si procede al salvataggio
su file della sequenza stessa, come segue:
else
if (MidiSystem.write(mySequence,fileTypes[0], file)
== -1) throw new IOException("Problemi con la
scrittura del file");
Il
metodo
MidiSystem.write(mySequence,
fileTypes[0], file)
scrive
la sequenza 'mySequence' nel file 'file' utilizzando
il primo formato MIDI disponibile fra quelli che ha
trovato, ovvero 'fileTypes[0]'.
Tale metodo restituisce il numero di byte scritti
nel file. Un valore di ritorno pari a -1 indicherebbe
pertanto un errore nella scrittura del file MIDI,
facendo scattare l'eccezione corrispondente.
Una
volta salvato il nostro file MIDI, viene chiuso il
sequencer tramite il metodo
closeDevice()
e
l'applicazione ha termine.
Conclusioni
In questi primi tre appuntamenti sul MIDI con Java
abbiamo affrontato diversi argomenti: come ascoltare
un generico file MIDI, come utilizzare il sintetizzatore
MIDI per generare suoni e musiche da noi stessi ideate,
ed infine come realizzare con le nostre mani una piccola
composizione musicale in modo da poterla salvare sotto
forma di file MIDI. Ebbene, il nostro viaggio col
MIDI non si ferma ancora qui. Infatti nel prossimo
appuntamento, ormai esperti sull'argomento :-), saremo
più che pronti per analizzare un software (implementato
dal sottoscritto) completo per la creazione, riproduzione,
modifica, introduzione di effetti in tempo reale e
salvataggio di un file MIDI, il tutto, questa volta,
con il comodo ausilio delle Swing. Non mancate all'appuntamento!!!
Bibliografia
[1]
S.L.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 relativa al package
javax.sound.midi
consultabile all'indirizzo:
http:// java.sun.com/j2se/1.3.0/docs/api/javax/sound/midi/package-summary.html
|