1   package fi.jyu.mit.Music;
2   
3   import java.io.BufferedInputStream;
4   import java.io.BufferedOutputStream;
5   import java.io.File;
6   import java.io.FileNotFoundException;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.net.HttpURLConnection;
10  import java.net.MalformedURLException;
11  import java.net.URL;
12  import java.util.ArrayList;
13  import java.util.Enumeration;
14  import java.util.List;
15  import java.util.Scanner;
16  import java.util.regex.Matcher;
17  import java.util.regex.Pattern;
18  import java.util.zip.ZipEntry;
19  import java.util.zip.ZipException;
20  import java.util.zip.ZipFile;
21  
22  import javax.sound.midi.*;
23  
24  /**
25   * MidiPlayer-luokka sisältää varsinaisen äänijärjestelmän yksinkertaisen MIDI-datan toistoa varten.
26   * äänijärjestelmä kykenee soittamaan yhdellä instrumentilla nuoteista koostuvaa soittoa. Kaikki GM-1 
27   * standardin tukemat instrumentit ovat käytässä. Standardin määritykset läytyvät <a href="http://www.midi.org/techspecs/gm1sound.php">spesifikaatiosta</a>.
28   *
29   * Oktaavi, ks: esim: http://fi.wikipedia.org/wiki/Oktaavi
30   * 
31   * @see BufferedMidiPlayer
32   */
33  public class MidiPlayer implements BasicMidiPlayer {  
34      /**
35       * Nuotin kestot tahdin desimaaliosissa. 
36       */
37      public enum NoteDuration {
38          DOUBLE(2.0),
39          EIGHTH(0.125),
40          HALF(0.5),
41          QUARTER(0.25),
42          SIXTEENTH(0.0625),
43          SIXTYFOURTH(0.015625),
44          THIRTYSECOND(0.03125),
45          WHOLE(1.0);
46          
47          private final double length;
48          
49          NoteDuration(double length) {
50              this.length = length;
51          }
52          
53          public double length() { return this.length; }
54      };
55      
56      public static final int DEFVELOCITY = 64;
57      public static final int DEFOCTAVE = 4;
58      public static final String DELAYST = "-";
59          
60      
61      /**
62       * Lista nuoteista.
63       */
64      public static final String[] ALLNOTES = {
65          "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "Bb", "B"
66      };
67      public static final String basicNotes  = "C D EF G A B";
68      public static final String basicNotes2 = "C D EF G A H";
69      
70      /**
71       * Tempo. Määräytyy BPM:n (beats per minute) mukaan, oletus on 120 eli 
72       * 120 iskua minuutissa, tällöin yhden iskun pituus on 500 ms.
73       */
74      private int beatLength = 500; // 120 BPM
75  
76      /**
77       * Nopeus jolla "kosketinta" lyödään, 64 on oletus
78       */
79      private int velocity = DEFVELOCITY; 
80      
81      /**
82       * Tulostetaanko debuggausviestejä. Oletuksena on, että ei, mutta toggleVerbosella sen voi muuttaa.
83       */
84      private boolean verbose = false;
85      
86      /**
87       * Äänipankkitiedosto latausta varten.
88       */
89      private final String remoteSoundbankUrl = "http://java.sun.com/products/java-media/sound/soundbank-min.gm.zip";
90      
91      /**
92       * Taulukko midikanavia varten.
93       */
94      protected MidiChannel[] channels;
95      
96      /**
97       * Nykyinen käytässä oleva kanava. Oletuksena 0.
98       */
99      protected int currentChannel = 0;
100     
101     /**
102      * Taulukko instrumentteja varten.
103      */
104     protected Instrument[] instruments;
105 
106     
107     /**
108      * Nykyinen instrumentti;
109      */
110     private int instrument = 1;
111     
112 
113     /**
114      * Iskuja minuutissa
115      */
116     private int bpm = 120;
117     
118     
119     /**
120      * Oletusoktaavi, josta nuotit soitetaan
121      */
122     private int defOctave = DEFOCTAVE;
123     
124     public int getDefOctave() {
125         return defOctave;
126     }
127 
128     public void setDefOctave(int defocatve) {
129         this.defOctave = defocatve;
130     }
131 
132     /**
133      * Syntetisaattori.
134      */
135     protected Synthesizer synth;
136     
137     /**
138      * Muodostaa äänijärjestelmän. Alustaa syntetisaattorin ym.
139      */
140     public MidiPlayer() {
141         this.init();
142     }
143     
144     /**
145      * Muodostaa äänijärjestelmän, kuitenkaan syntetisaattoria alustamatta. Tämä tarpeen jos ei
146      * halua viedä syntesaattoria järjestelmällä joissa niitä on vain yksi. 
147      * @param init Alustustehto. Jos true, niin syntetisaattori käynnistetään, jos false, niin ei.
148      */
149     public MidiPlayer(final boolean init) {
150         if (init)
151             this.init();
152     }
153        
154     /**
155      * Jäsentää merkkijonon nuotit & tauot yksittäisiksi nuotteiksi.
156      * Jonossa saa olla ylmääräisiä välilyäntejä tai | merkkejä selkeyttämässä jakoa
157      * ilman että ne sotkevat mitään. Esimerkkjä käytöstä ks alla olevat testit.
158      * 
159      * @param sequence Merkkijono nuoteista, esim. C#5 
160      * @param defaultOctave Oletusoktaavi, jossa nuotit soitetaan, jos niille ei annettu oktaavia
161      * @param length Yksittäisen nuotin pituus jos nuotille ei erikeen ole annettu pituutta
162      * @param velocity voimakkuus, 64 on perus
163      * @return 
164      * @example
165      * <pre name="test">
166      * #import java.util.*;
167      *   MidiPlayer mp = new MidiPlayer(); List<Note> notes;
168      *   
169      *   notes = mp.getNotes("-b -/4 -#*",0.25,4,64); // - = tauko, ylennys tai alennus ei haittaa 
170      *   notes.get(0).isDelay()   === true;
171      *   notes.get(1).isDelay()   === true;
172      *   notes.get(2).isDelay()   === true;
173      *   notes.get(0).getLength() === 500;
174      *   notes.get(1).getLength() === 125;
175      *   notes.get(2).getLength() === 1000;
176      *   
177      *   notes = mp.getNotes("C/E/4D*F*4A.F*1.2",0.25,4,64); //  /nro jakaa pituuden, *nro kertoo
178      *   notes.get(0).isDelay()   === false;      
179      *   notes.get(0).getLength() === 250;                   // / yksin on sama kuin /2
180      *   notes.get(1).getLength() === 125;                   // /4 jakaa pituuden 4:llä
181      *   notes.get(2).getLength() === 1000;                  // *  yksin on sama kuin *2
182      *   notes.get(3).getLength() === 2000;                  // *4 kertoo pituuden 4:llä
183      *   notes.get(4).getLength() ~~~ 500*1.5;               // kerrotaan pituus 1.5:lla
184      *   notes.get(5).getLength() ~~~ 500*1.2;               // ja 1.2lla
185      *
186      *   notes = mp.getNotes("C C# D D# E F F# G G# A Bb B",0.25,4,64); // oktaavin kaikki nuotit
187      *   for (int i=0; i<12; i++) {
188      *     notes.get(i).getNote() === 60 + i;
189      *   }
190      *      
191      *   notes = mp.getNotes("c c# d d# e f f# g g# a Bb B",0.25,4,64); // samat pienillä (paitsi B)
192      *   for (int i=0; i<12; i++) {
193      *     notes.get(i).getNote() === 60 + i;
194      *   }
195      *      
196      *   notes = mp.getNotes("C Db D Eb E F Gb | G Ab A A# B",0.25,4,64); // ylennetään "eritavalla"
197      *   for (int i=0; i<12; i++) {
198      *     notes.get(i).getNote() === 60 + i;
199      *   }   
200      *   
201      *   notes = mp.getNotes("HBhbBb",0.25,4,64);                         // H ja B ovat synonyymejä
202      *   notes.get(0).getNote() === notes.get(1).getNote()
203      *   notes.get(2).getNote() === notes.get(3).getNote()
204      *     
205      *   notes = mp.getNotes("C1C2C3C4C5C6C7C8",0.25,4,64); // kokeillaan C:tä eri oktaaveista
206      *   notes.get(0).getNote() === 60 - 3*12  
207      *   notes.get(1).getNote() === 60 - 2*12  
208      *   notes.get(2).getNote() === 60 - 1*12  
209      *   notes.get(3).getNote() === 60 - 0*12  
210      *   notes.get(4).getNote() === 60 + 1*12  
211      *   notes.get(5).getNote() === 60 + 2*12  
212      *   notes.get(6).getNote() === 60 + 3*12  
213      *   notes.get(7).getNote() === 60 + 4*12
214      *     
215      *   notes = mp.getNotes("E# F H# C5",0.25,4,64);          
216      *   notes.get(0).getNote() === notes.get(1).getNote()  
217      *   notes.get(2).getNote() === notes.get(3).getNote()
218      *     
219      *   notes = mp.getNotes("A#3* Hb3*",0.25,4,64);      // Ylennetty A on sama kuin alennettu H molemmat 2*
220      *   notes.get(0).getNote() === notes.get(1).getNote()  
221      *   notes.get(0).getLength() === notes.get(1).getLength()  
222      * </pre>
223      */
224     public final List<Note> getNotes(final String sequence, final double length, final int defaultOctave, final int velocity) {
225         List<Note> notes = new ArrayList<Note>();
226         final Pattern p = Pattern.compile("(([-acdefghACDEFGBH])([#b]?)([0-8]?)([/*.][0-8.]*)?)");
227         final Matcher m = p.matcher(sequence);
228         
229         Note n;
230         while (m.find()) {
231             // Nuotti on toinen täsmäysjoukko (ensimmäinen on tauko)
232             String note = m.group(2);
233             // Ylennys/madallusmerkki
234             String pitchMove = (m.group(3) != null) ? m.group(3) : "";
235             // Oktaavi
236             int octave = (!m.group(4).isEmpty()) ? Integer.parseInt(m.group(4)) : defaultOctave;
237             double factor = 1.0;
238             String sf = m.group(5);
239             char cf = 0;
240             if (sf != null && sf.length() > 0) cf = sf.charAt(0);
241             if (cf == '/') {
242                 factor = 0.5;
243                 String smult = sf.substring(1);
244                 if (smult.length() > 0) factor = 1.0 / Double.parseDouble(smult);
245             }
246             if (cf == '*') {
247                 factor = 2.0;
248                 String smult = sf.substring(1);
249                 if (smult.length() > 0) factor = Double.parseDouble(smult);
250             }
251             if (cf == '.')
252                 factor = 1.5;
253 
254             n = makeNote(note + pitchMove, length * factor, octave, velocity);
255             notes.add(n);
256         }
257         return notes;
258     }
259     
260     /** 
261      * Alustaa MIDI-syntetisaattorin.
262      * @throws MidiUnavailableException
263      * @throws InvalidMidiDataException
264      */
265     public void init() {
266         Soundbank s = null;
267         try {
268             synth = MidiSystem.getSynthesizer();
269             synth.open();
270             if ( verbose ) System.out.println("Ladattiin syntetisaattori " + synth);
271             s = synth.getDefaultSoundbank();
272             // Syntetisaattori ei saanut äänipankkia, joten ladataan sen etäpalvelimelta.
273             if (s == null) {
274                 synth.close();
275                 downloadSoundbank(remoteSoundbankUrl);
276                 synth.open();
277                 s = synth.getDefaultSoundbank();
278                 // Jos ei vieläkään
279                 if (s == null) {
280                     error("Lataaminen epäonnistui tai äänipankkitiedosto on epäkelpo. Ks. https://trac.cc.jyu.fi/projects/ohj1/wiki/music");
281                 }
282             }
283         } catch (MidiUnavailableException e) {
284             e.printStackTrace();
285         }
286         instruments = s.getInstruments();
287         if (instruments == null) {
288             error("Instrumenttien lataamisessa tapahtui virhe.");
289         }
290        
291         synth.loadInstrument(instruments[1]); // piano
292         channels = synth.getChannels();
293         selectChannel(1);
294     }
295     
296     /**
297      * Lataa äänipankin Sunin sivuilta.
298      * @param remote URL, josta ladataan
299      */
300     private void downloadSoundbank(String remote) {
301         HttpURLConnection source = null;
302         BufferedOutputStream bos = null;
303         BufferedInputStream bis = null;
304         final String zipFileName = "soundbank-min.gm.zip";
305         try {
306             File z = new File(zipFileName);
307             // Jos zip-pakettia ei löydy jo valmiiksi, ladataan se
308             if (!z.exists()) {
309                 URL url = new URL(remote);
310                 source = (HttpURLConnection) url.openConnection();
311                 source.connect();
312                 System.out.print("Haetaan äänipankki osoitteesta " + remote + " ... ");
313                 // Tarkista palvelimen vastaus
314                 String response = "HTTP " + source.getResponseCode() + " " + source.getResponseMessage();
315                 // Jos 404 (ei löydy)
316                 if (source.getResponseCode() != HttpURLConnection.HTTP_OK) {
317                     System.err.println(response);
318                     error("Äänipankkitiedoston latauksessa tapahtui virhe. Ks. https://trac.cc.jyu.fi/projects/ohj1/wiki/music");
319                 } 
320                 System.out.println(response);
321                 bis = new BufferedInputStream(source.getInputStream());
322                 bos = new BufferedOutputStream(new FileOutputStream(zipFileName));
323                 int count = 0;
324                 byte[] buf = new byte[8192]; // HTTP-puskurin suositeltu koko
325                 // Lataa tiedosto aina puskurin koko kerrallaan
326                 int fileSize = source.getContentLength(); // tiedoston koko palvelimella
327                 System.out.print("Ladataan " + zipFileName + " (" + fileSize/1000 + " kB) ... ");
328                 for (int bytesRead = bis.read(buf); bytesRead > 0; bytesRead = bis.read(buf)) {
329                     count += bytesRead;
330                     bos.write(buf, 0, bytesRead);
331                 }
332                 System.out.println(count/1000 + " kB ladattu");
333                 bis.close();
334                 bos.close();
335                 source.disconnect();
336             }
337             // Puretaan tiedosto
338             try {
339                 ZipFile pkg = new ZipFile(new File(zipFileName));
340                 // Hae paketin sisältö
341                 Enumeration<? extends ZipEntry> entries = pkg.entries();
342                 while (entries.hasMoreElements()) {
343                     ZipEntry ze = (ZipEntry)entries.nextElement();
344                     // Pura vain .gm-päätteiset tiedostot
345                     if (!ze.isDirectory() && ze.getName().endsWith(".gm")) {
346                         System.out.println("Puretaan äänipankki " + ze.getName());
347                         // Kopioi tiedoston sisältö
348                         BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(ze.getName()));
349                         BufferedInputStream in = new BufferedInputStream(pkg.getInputStream(ze));
350                         int data;
351                         while ((data = in.read()) != -1) {
352                             out.write((byte) data);
353                         }
354                         out.close();
355                         in.close();
356                     }
357                 }
358             } catch (ZipException ze) {
359                 System.err.println("Virhe avattaessa zip-tiedostoa: " + ze.getMessage());
360             }
361         }
362         catch (MalformedURLException mue) {
363             error("Virheellinen URL");
364         } catch (IOException e) {
365             // TODO Auto-generated catch block
366             e.printStackTrace();
367         }
368     }
369     
370     /**
371      * Muuntaa nuotin, oktaavin ja voimakkuuden Note-luokan alkioksi. <b>Huom.! Nuotin perään ei tule oktaavia.</b>
372      * Tauko on - -merkki.
373      * @param note Nuotti tekstimuodossa, esim. C# tai Bb 
374      * @param length Nuotin pituus tahdin kokonaisosina
375      * @param octave Oktaavi (0-8)
376      * @param velocity voimakkuus, 64 on normaali
377      * @return Muunnettu nuotti.
378      * @see Note
379      * @example
380      * <pre name="test">
381      *   MidiPlayer mp = new MidiPlayer();
382      *   Note note;
383      *   
384      *   note = mp.makeNote("C",0.5,4,64);
385      *   note.getLength() === 1000;
386      *   note.getNote() === 60;
387      *   
388      *   note = mp.makeNote("C#",0.5,4,64); // korotus
389      *   note.getNote() === 61;
390      *   
391      *   note = mp.makeNote("H#",0.5,4,64); // korotettu H on seuraava C
392      *   note.getNote() === 72;
393      *   
394      *   note = mp.makeNote("Bb",0.5,4,64); // Alennettu B (H)
395      *   note.getNote() === 70;
396      *   
397      *   note = mp.makeNote("-",0.5,4,64); // Tauko
398      *   note.isDelay() === true;
399      *   
400      *   note = mp.makeNote("-#",0.5,4,64); // Tauko, ylennys ei haittaa
401      *   note.isDelay() === true;
402      *   
403      *   note = mp.makeNote("-b",0.5,4,64); // Tauko, alennus ei haittaa
404      *   note.isDelay() === true;
405      * </pre>
406      */
407     public final Note makeNote(final String note, final double length, final int octave, final int velocity) {
408         final int noteLength = (int)((this.beatLength * 4) * length);
409         
410         if ( octave < 0  || note.length() < 1 || DELAYST.indexOf(note.charAt(0)) >= 0 ) 
411             return new Note.Delay(noteLength);
412         
413         char noteChar = Character.toUpperCase(note.charAt(0));
414         char pitchMove = 0;
415         if ( note.length() > 1 ) pitchMove = note.charAt(1);
416         
417         // Tarkistetaan että nuotit & oktaavit ovat oikein
418         
419         int noteIndex = basicNotes.indexOf(noteChar);
420         if ( noteIndex < 0 ) noteIndex = basicNotes2.indexOf(noteChar);
421         
422         if ( noteIndex < 0 ) {
423             error("Virheellinen nuotti: " + note);
424             return null;
425         } 
426         if (octave < 0 || octave > 8) {
427             error("Virheellinen oktaavi: " + octave);
428             return null;
429         }
430         if (length < 0.0625) {
431             error("Virheellinen nuotin kesto, " + length + " minimi: 0.0625 - 1");
432             return null;
433         }
434         int base = 24;
435         int resultNote = 0;
436         
437         if ( pitchMove == '#' ) noteIndex++;
438         if ( pitchMove == 'b' ) noteIndex--;
439         
440         // Valitaan oktaavi. Ensimmäisesä 0-oktaavissa on vain kolme nuottia (A, Bb, B), joten
441         // indeksin kaksi yli menevät (C-G#) tuottavat virheen.
442         if (octave == 0) {
443             if (noteIndex < 9) {
444                 error("Virhe: nollaoktaavissa soivat vain A, Bb ja B, koitit: " + note);
445             } else {
446                 resultNote = 21 + (noteIndex - 9);
447             }
448         } else {
449             resultNote = base + ((octave - 1) * 12) + noteIndex;
450         }
451         return new Note(resultNote, noteLength, velocity);
452     }
453     
454 
455     /**
456      * Muuntaa nuotin, oktaavin ja voimakkuuden Note-luokan alkioksi. <b>Huom.! Nuotin perään ei tule oktaavia.</b>
457      * @param note Nuotti tekstimuodossa, esim. "C#"
458      * @param length Nuotin pituus tahdin kokonaisosina
459      * @param octave Oktaavi (0-8)
460      * @return muutettu nuotti
461      */
462     public final Note makeNote(final String note, final double length, final int octave) {
463        return makeNote(note,length,octave,this.velocity); 
464     }
465     
466     /**
467      * Soittaa kanavalta MIDI-nuotin (21-108). 
468      * @param note Nuotti (21-108)
469      * @param length Kesto millisekunteina
470      * @param velocity Nuotin nk. painovoimakkuus. Oletus on 64.
471      */
472     public void midiSoundNote(final int note, final int length, final int velocity)
473     {
474         checkSynth("midiSoundNote");
475         try 
476         {
477             channelOn(note, velocity);
478             message("Soitin nuotin " + Note.message(note, length, velocity));
479             Thread.sleep(length);
480             channelOff(note);
481         }
482         catch (InterruptedException e)
483         {
484             channelOff(note);
485         }
486     }
487     
488     /**
489      * Aloittaa kanavalta MIDI-nuotin (21-108). 
490      * @param note Nuotti (21-108)
491      * @param length Kesto millisekunteina
492      * @param velocity Nuotin nk. painovoimakkuus. Oletus on 64.
493      */
494     public void midiStartNote(final int note, final int length, final int velocity)
495     {
496         checkSynth("midiStartNote");
497         channelOn(note, velocity);
498         message("Aloitin nuotin " + Note.message(note, length, velocity));
499     }
500     
501     /**
502      * Aloittaa kanavalta MIDI-nuotin (21-108). 
503      * @param note Nuotti (21-108)
504      * @param length Kesto millisekunteina
505      * @param velocity Nuotin nk. painovoimakkuus. Oletus on 64.
506      */
507     public void midiEndNote(final int note, final int length, final int velocity)
508     {
509         checkSynth("midiEndNote");
510         message("Lopetin nuotin " + Note.message(note, length, velocity));
511         channelOff(note);
512     }
513     
514     /**
515      * Toistaa nuotteja. Nuottien kesto oletetaan neljäsosanuoteiksi.
516      * @param sequence Nuotit
517      */
518     @Override
519     public void play(final String sequence){
520         play(sequence, 0.25, getDefOctave());
521     }
522     
523     /**
524      * Toistaa nuotteja tietyssä oktaavissa neljäsosanuotteina.
525      * @param sequence Nuotit
526      * @param octave Oktaavi
527      */
528     @Override
529     public void play(final String sequence, final double length) {
530         play(sequence, length, getDefOctave());
531     }
532     
533     /**
534      * Toistaa merkkijonon neljäsosanuotteja perusoktaavissa (4), esim. "C5CC5CBbAA#". Huomaa, että "Bb" on aina annettava
535      * sellaisenaan, sillä parseri ei osaa erottaa onko kyseessä "bb" vai "BB", parseri olettaa että tuolloin on kaksi
536      * B-nuottia.
537      * 
538      * 
539      * Syntaksi yksittäiselle nuotille on seuraava: ABCD, jossa
540      * <ul>
541      *     <li><i>A</i> on itse nuotti <b>(pakollinen)</b></li>
542      *     <li><i>B</i> on nuotille ylennysmerkki # tai alennusmerkki b</li>
543      *     <li><i>C</i> on nuotille oktaavi</li>
544      *     <li><i>D</i> on nuotin kesto suhteessa peruspituuteen joko /jakaja tai *kerroin. Jos jakaja tai kerroin puuttu, se on 2</li>
545      * </ul>
546      * Mikä tahansa osista B, C tai D voi puuttua.
547      * <pre>
548      *  Esim:
549      *    C#A   on ylennetty C ja A, oktaavista 4, kesto perusnuotin verran (yleensä siis 1/4 nuotti)
550      *    C5    C 5:stä oktaavista
551      *    C*2   on perusnuottia 2 pidempi nuotti (yleensä siis puolinuotti)
552      *    C/4   C jonka kesto on 1/4 perusnuotista (siis 1/16 nuotti yleensä)
553      *    A/    A jonka kesto on puoleta perusnuotista (yleensä siis 1/8 nuotti)
554      *    C#5*4 4 kertaa normaali pidempi ylennetty C 5. oktaavista    
555      *    D.    1.5 kertaa normaalia pidempi D, eli sama kuin D*1.5
556      * </pre> 
557      * @param sequence Nuotit merkkijonona, jonossa olevat välilyönnit ja | eivät haittaa
558      *                 kunhan niitä ei laiteta nuotin sisälle
559      * @param octave Oletusoktaavi.  Jos nuotille ei ole omaa oktaavia, käytettään tätä
560      * @param length Yksittäisen nuotin kesto, tähän suhteutetaan muut jonon nuotit
561      * @param velocity voimakkuus, 64 on normaali
562      */
563     @Override
564     public void play(final String sequence, final double length, final int octave, final int velocity) {
565         play(getNotes(sequence, length, octave, velocity));
566     }
567 
568     
569     @Override
570     public void play(final String sequence, final double length, final int octave) {
571         play(sequence, length, octave, this.velocity);
572     }
573     
574 
575     /**
576      * Soittaa nuotit nuottien listasta
577      * @param notes soitettavat nuotit
578      */
579     public void play(final List<Note> notes) {
580         for (Note n : notes) {
581             // Tauko
582             if (n.getNote() < 0 ) {
583                 rest(n.getLength());
584                 continue;
585             }
586             midiSoundNote(n.getNote(), n.getLength(), n.getVelocity());
587         }
588     }
589     
590     
591     /**
592      * Toistaa ASCII-tiedoston.
593      * @param filename
594      */
595     public void playAsciiFile(final String filename) {
596         try {
597             final Scanner sc = new Scanner(new File(filename));
598             // Toistetaan sisältä
599             String notes = sc.next();
600             play(notes);
601         } catch (FileNotFoundException e) {
602             error("Tiedostoa " + filename + " ei läytynyt!");
603         } 
604     }
605     
606     /**
607      * Toistaa MIDI-tiedoston niin että ohjelma vi jatkaa muita tehtäviä.
608      * Tälläin palautettu sequencer pitä ottaa vastaan ja sanoa sille
609      * joskus close.
610      * @param filename Tiedoston nimi, oltava midi-tiedosto
611      * @return seuencer jolla soitetaan, niin voi pysäyttää milloin haluaa
612      */
613     public Sequencer playMidiFileAsync(final String filename) {
614         try {
615             // Lataa MIDI-tiedosto 
616             final Sequence song = MidiSystem.getSequence(new File(filename));
617             // Lataa sekvensseri
618             Sequencer sequencer = MidiSystem.getSequencer();
619             sequencer.open();
620             sequencer.setSequence(song);
621             
622             if ( isVerbose() ) printSequence(song);
623             
624             // Aloita soitto
625             sequencer.start();
626             message("Soitetaan MIDI-tiedosto: " + filename);
627             return sequencer;
628         } catch (InvalidMidiDataException e) {
629             error("Virhe luettaessa MIDI-dataa! Annoithan MIDI-tiedoston? (.mid)");
630         } catch (IOException e) {
631             error("Virhe avattaessa MIDI-tiedostoa: " + e.getMessage());
632         } catch (MidiUnavailableException e) {
633             error("MIDI-soitinta ei voida alustaa.");
634         }
635         return null;
636     }
637     
638     /**
639      * Soittaa valitun midi-tiedoston ja odottaa soiton loppumista.
640      * @param filename
641      */
642     public void playMidiFile(final String filename) {
643         Sequencer sequencer = playMidiFileAsync(filename);
644         if ( sequencer == null ) return;
645         while ( sequencer.isRunning() ) {
646             try {
647                 Thread.sleep(200);
648             } catch (InterruptedException e) {
649             }
650         }
651         sequencer.close();
652     }
653 
654         
655     private String messageInfotoString(MidiMessage message) {
656         if (! ( message instanceof ShortMessage ) ) return message.toString();
657         ShortMessage m = (ShortMessage)message;
658         return String.format("Channel %d  command: %d data %d %d",m.getChannel(),m.getCommand(),m.getData1(), m.getData2());
659     }
660 
661 
662     
663     public void printSequence(Sequence song) {
664         // Print sequence information
665         System.out.println( " length: "   +  song.getTickLength() + " ticks" );
666         System.out.println( " duration: " +  song.getMicrosecondLength() +  " micro seconds" );
667         //System.out.println( " division type: " +  divisionTypeToString( song.getDivisionType() );
668         System.out.println( " division type: " +   song.getDivisionType() );
669         System.out.println( " resolution type: " +   song.getResolution() );
670 
671         // Print track information
672         Track[] tracks = song.getTracks();
673         if (tracks == null) return;
674         for (int i = 0; i < tracks.length; i++) {
675             System.out.println("Track " + i + ":");
676             Track track = tracks[i];
677             for (int j = 0; j < track.size(); j++) {
678                 MidiEvent event = track.get(j);
679                 System.out.printf(" tick %8d, %s%n",event.getTick(),messageInfotoString(event.getMessage()));
680             } // for
681         } // for
682     }
683     
684     /**
685      * Toistaa neljäsosanuotin neljännessä oktaavissa.
686      * @param note Nuotti (C-B)
687      * @throws InvalidMidiDataException
688      */
689     @Override
690     public void playSingleNote(final String note) {
691         playPreciseNote(note, 0.25, getDefOctave());
692     }
693     
694     /**
695      * Toistaa nuotin neljännessä oktaavissa.
696      * @param note Nuotti 
697      * @param octave Oktaavi
698      * @throws InvalidMidiDataException
699      */
700     @Override
701     public void playSingleNote(final String note, final double length) {
702         playPreciseNote(note, length, getDefOctave());
703     }
704   
705     /**
706      * Toistaa minkä tahansa kestoisen nuotin tietyssä oktaavissa.
707      * @param note Nuotti
708      * @param length Kesto
709      * @param octave Oktaavi
710      * @throws InvalidMidiDataException 
711      */
712     @Override
713     public void playSingleNote(String note, double length, int octave){
714         playPreciseNote(note, length, octave);
715     }
716     
717     /**
718      * Toistaa minkä tahansa kestoisen nuotin tietyssä oktaavissa.
719      * @param note Nuotti
720      * @param length Kesto
721      * @param octave Oktaavi
722      * @param velocity voimakkuus
723      * @throws InvalidMidiDataException 
724      */
725     @Override
726     public void playSingleNote(String note, double length, int octave, int velocity){
727         playPreciseNote(note,length, octave, this.velocity);
728     }
729     
730     
731     /** 
732      * Toistaa mielivaltaisen tauon.
733      * @param barLength tauon pituus ms
734      */
735     protected void rest(final int barLength) {
736         // Ei negatiivisia taukoja
737         if (barLength < 0)
738             return;
739         try {
740             int restLength = barLength;
741             message("Soitin nuotin " + Note.message(Note.DELAY, barLength, Note.DELAY));
742             Thread.sleep(restLength);
743         } catch (InterruptedException e) {
744             
745         }
746     }
747 
748     
749     /**
750      * Valitsee kanavan. Kanavia on MIDI-spesifikaatiossa 16.
751      * @param channel Kanava väliltä 1-16.
752      */
753     public void selectChannel(final int channel) {
754         if (channel < channels.length && channel > 0) {
755             currentChannel = channel;
756             message("Valittiin kanava: " + channels[currentChannel]);
757         } else {
758             System.err.println("Virheellinen kanava, täytyy olla väliltä 1-16! Annoit: " + channel);
759         }
760     }
761     
762     /**
763      * Vaihtaa instrumentin. Ks. <a href="http://www.midi.org/techspecs/gm1sound.php#instrument">spesifikaatio</a>.
764      * @param instrument Instrumentti väliltä 1-128.
765      */
766     public void setInstrument(final int instrument) {
767         checkSynth("setInstrument");
768         if ( instrument < 1 || instruments.length < instrument ) {
769             error(String.format("Instrumentti oltava väliltä 1-%d, annoit: %d ",instruments.length, instrument));
770             return;
771         } 
772         message("Ladattiin soitin " + instruments[instrument - 1]);
773         synth.loadInstrument(instruments[instrument - 1]);
774         this.instrument = instrument;
775         channels[currentChannel].programChange(instrument - 1);
776     }
777     
778     /**
779      * @return nykyinen instrumentti
780      */
781     public int getInstrument() {
782         return instrument;
783     }
784     
785     /**
786      * Vaihtaa instrumentin. Ks. <a href="http://www.midi.org/techspecs/gm1sound.php#instrument">spesifikaatio</a>.
787      * @param instrument Instrumentti väliltä 1-128.
788      * @param def Oletusarvo mitä käytetään jos annettu indeksi on väärin 
789      */
790     public void setInstrument(final int instrument, final int def) {
791         int i = instrument;
792         if ( i < 1 || instruments.length < i ) i = def;
793         setInstrument(i); 
794     }
795     
796     /**
797      * Asettaa tempon. BPM on iskuja minuutissa, oletus on 120. Tämä määrää syntetisaattorin tempon.
798      * @param bpm
799      */
800     @Override
801     public void setTempo(final int bpm) {
802         if (bpm > 0) {
803             beatLength = 60000 / bpm;
804             this.bpm = bpm; 
805         }
806     }
807 
808     
809     /**
810      * @return nykyinen tempo bmp
811      */
812     public int getTempo() {
813         return bpm;
814     }
815     
816     
817     /**
818      * Asetetaan nopeus jolla "kosketinta lyödään"
819      * @param velocity lyöntinopeus, 64 on normaali
820      */
821     public void setVelocity(int velocity) {
822         this.velocity = velocity;
823     }
824 
825     
826     /**
827      * @return nopeus jolla kosketinta lyödään
828      */
829     public int getVelocity() {
830         return velocity;
831     }
832     
833     
834     /**
835      * Debug-viestit päälle/pois
836      */
837     public void setVerbose(final boolean set) {
838         this.verbose = set;
839     }
840      
841     /**
842      * Toistaa yksittäisen nuotin instrumentilla. Standardissa MIDI 1.0-spesifikaatiossa on 86 eri nuottia,
843      * jotka menevät A'' - c'''' (A0 - C8). Toisin sanoen vajaa 8 oktaavia. Nuotit ovat
844      * A, Bb, B, C, C#, D, D#, E, F, F#, G, G#.
845      * 
846      * 
847      * Nuotin kestot ovat tahdin kestoja 1/16, 1/8, 1/4, 2/4, 3/4, 4/4 ... n desimaalimuodossa eli
848      * 0.0625, 0.125, 0.25, 0.5, 0.75, 1, ..., n. Tahdin pituus määräytyy tempon perusteella.
849      * 
850      * @param note Nuotti (ks. yllä)
851      * @param octave Oktaavi (0-8).
852      * @param duration Nuotin kesto iskuina.
853      * @param velocity voimakkuus
854      */
855     private void playPreciseNote(final String note, final double length, final int octave, final int velocity) {
856         Note n = makeNote(note, length, octave, velocity);
857         // Kaikki ok. Soitetaan!
858         midiSoundNote(n.getNote(), n.getLength(), n.getVelocity());
859     }
860     
861 
862     /**
863      * Soittaa nuotin 
864      * @param note Nuotti (ks. yllä)
865      * @param length Nuotin kesto iskuina.
866      * @param octave Oktaavi (0-8).
867      */
868     private void playPreciseNote(final String note, final double length, final int octave) {
869         playPreciseNote(note, length, octave, this.velocity);
870     }
871     
872     
873     /**
874      * Sulkee kanavalta nuotin.
875      * @param note nuotti
876      * @param velocity voimakkuus
877      */
878     protected void channelOff(final int note)
879     {
880         this.channels[currentChannel].noteOff(note);
881     }
882     
883     /**
884      * Avaa kanavalta nuotin.
885      * @param note nuotti
886      * @param velocity voimakkuus
887      */
888     protected void channelOn(final int note, final int velocity)
889     {
890         this.channels[currentChannel].noteOn(note, velocity);
891     }
892     
893     /**
894      * Tulostaa virheviestin. Tähän tarkoitukseen olisi sopinut kutsuvan metodin nimen onkiminen pinosta,
895      * mutta se on hidasta. 
896      * @param origin Kutsuva metodi
897      */
898     protected void checkSynth(final String origin) {
899         if (synth == null) {
900             error(origin + ": Syntetisaattori käynnistämättä!");
901         }
902     }
903     
904     /**
905      * Tulostaa virheviestin ja lopettaa ohjelman.
906      * @param msg Viesti
907      */
908     protected void error(final String msg) {
909         // Selvitä kutsuneen metodin nimi
910         final String callerName = Thread.currentThread().getStackTrace()[2].getMethodName();
911         System.err.println("Virhe: " + callerName + ": " + msg);
912         System.exit(0);
913     }
914     
915     protected final int getBeatLength() {
916         return beatLength;
917     }
918     
919     /**
920      * Tulostaa viestin mikäli viestintulostus on päällä. Oletus on, että järjestelmä on hiljaa.
921      * @param msg Viesti
922      */
923     protected void message(final String msg) {
924         if (verbose)
925             System.out.println(msg);
926     }
927     
928     /**
929      * Tulostaa kaikkien instrumenttien nimet
930      */
931     public void printInstruments() {
932         int n=1;
933         for (Instrument i:instruments)
934             System.out.printf("%3d %s%n",n++,i.getName());
935     }
936  
937     
938     /**
939      * Etsii ensimmäisen instrumentin jonka nimi täsmää hakuehtoon
940      * @param regexp 
941      * @return sen isntrumentin indkesi, joka täsmää hakuehtoon, -1 = ei löydy
942      */
943     public int getInstrumentIndex(String regexp) {
944         for (int i=0; i<instruments.length; i++) {
945             Instrument inst = instruments[i];
946             if ( inst.getName().matches(regexp)) return i+1;
947         }    
948         return -1;
949         
950     }
951 
952     /**
953      * @return onko aputulosteet päällä vai ei
954      */
955     public boolean isVerbose() {
956         return verbose;
957     }
958     
959 }
960