1   package fi.jyu.mit.Music;
2   
3   import java.util.LinkedList;
4   import java.util.List;
5   
6   import java.util.Vector;
7   
8   /**
9    * Tarjoaa nuottien rinnakkaisen toistomahdollisuuden, jossa toistot tapahtuvat eri säikeissä, mikä mahdollistaa
10   * ns. ääniraitojen teon kutsumalla montaa eri play-komentoa samanaikaisesti.
11   */
12  public class ThreadedMidiPlayer extends MidiPlayer implements BasicMidiPlayer {
13      
14      private class SoundConsumer extends Thread {
15          public void run() {
16              Clock c = new Clock();
17              while (true) {
18                  Note n = null;
19                  synchronized(queue) {
20                      // odota pysäytettäviä nuotteja
21                      while (queue.size() == 0) {
22                          if (stop)
23                              break;
24                          try {
25                              queue.wait(); // Odotetaan lukkoa
26                          } catch (InterruptedException ie) {
27                              
28                          }
29                      }
30                      if (stop) 
31                          break;
32                      n = queue.remove(0);
33                      queue.notifyAll(); 
34                      
35                  }
36                  // Pysäytä nuotti. ei ole lukollinen
37                  message(String.format("%6d  Pysäytin   %s",c.getElapsed(),n.message()));
38                  channelOff(n.getNote());
39              }
40              
41          }
42      }
43      /**
44       * Laittaa nuotit pysäytysjonoon.
45       */
46      private class SoundProducer extends Thread {
47          private double noteLength;
48          private Vector<Note> notes;
49  
50          public SoundProducer() {
51              noteLength = 0.25;
52              notes = new Vector<Note>();
53          }
54  
55          public void addNote(Note note) {
56              notes.add(note);
57          }
58          
59          // Käynnistää nuotintuottaja-säikeen
60          public void run() {
61              Clock c = new Clock();
62              long breakLength = (long)((getBeatLength() * 4) * noteLength);
63              if (notes.size() == 0) 
64                  return;
65              Vector<Note> played = new Vector<Note>();
66              while (true) {
67                  if (notes.isEmpty())
68                      break;
69                  played.clear();
70                  while (!notes.isEmpty()) {
71                      Note n = notes.firstElement();
72                      if ( !n.isDelay() ) {
73                          message(String.format("%6d  Käynnistän %s",c.getElapsed(),n.message()));
74                          played.add(n);
75                          channelOn(n.getNote(), n.getVelocity());
76                      } else {
77                          notes.remove(0);
78                          break;
79                      }
80                      notes.remove(0);
81                  }
82                  
83                  // Tauko. Jos soitettiin yksi (tai useampi) nuotti niin odotetaan, tai jos tuli tauko.
84                  try {
85                      Thread.sleep(breakLength);
86                  } catch (InterruptedException ie) {
87                      
88                  }
89                      
90                  // Tämä edellyttää lukkoa
91                  for (Note n : played) {
92                      synchronized(queue) {         
93                          // Soiton jälkeen lisätään nuotti pysäytettävien nuottien jonoon
94                          // message(c.getElapsed() + " Laitan pysäytysjonoon " + n.note + ", kesto " + n.length + " ms");
95                          queue.add(new Note(n.getNote(), n.getLength(), n.getVelocity()));
96                          queue.notifyAll(); // nyt on nuotteja toistettavana
97                      }
98                  }
99              }
100         }
101         
102         private void setNoteLength(double noteLength) {
103             this.noteLength = noteLength;
104         }
105     }
106     class Clock {
107         long startMillis = 0;
108         public Clock() {
109             startMillis = System.currentTimeMillis();
110         }
111         public long getElapsed() {
112             return (System.currentTimeMillis() - startMillis);
113         }
114     }
115     private Vector<SoundProducer> producers;
116     
117     private LinkedList<Note> queue = new LinkedList<Note>();
118     
119     private SoundConsumer sc;
120     
121     private boolean stop = false; // lopetuslippu
122     
123     public ThreadedMidiPlayer() {
124         init();
125         sc = new SoundConsumer();    
126         producers = new Vector<SoundProducer>();
127     }
128     
129     /**
130      * Käynnistää soittajan. Tämän kutsuminen on välttämätäntä, jotta nuotit soisivat.
131      */
132     public void begin() {
133         sc.start();
134     }
135     
136     /**
137      * Lopettaa soiton ja pysäyttää kaikki säikeet.
138      */
139     public void end() {
140         synchronized(queue) {
141             stop = true;
142             queue.notifyAll();
143         }
144     }
145     
146     /**
147      * Toistaa saamansa nuotit samanaikaisesti. Parametrilla "AAA AAA" soitetaan ensiksi kolme samanaikaista A-nuottia
148      * jonka jälkeen on pituuden määräämä tauko, jonka jälkeen soitetaan loput AAA. Soitto aloitetaan heti.
149      */
150     @Override
151     public void play(String sequence, double length, int octave, int velocity) {
152        List<Note> notes = getNotes(sequence, length, octave, velocity);
153         SoundProducer sp = getFreeProducer();
154         for (Note n : notes)
155             sp.addNote(n);
156         sp.setNoteLength(length);
157         sp.start();
158     }
159     
160     
161     /**
162      * Toistaa kaiken samanaikaisesti kuten play-metodissa mutta tauot jätetään huomioimatta.
163      * @param chord Sointu, esim. "C5E5G5"
164      * @param length Soinnun pituus tahdin desimaaliosana (0.25 = neljäsosanuotti)
165      * @param octave Soinnun oletusoktaavi. Jos chord-parametrissä on nuotteja, joissa ei ole oktaavia määriteltynä, käyttää soitin tätä oktaavia.
166      * @param velocity voimakkuus
167      */
168     public void playChord(String chord, double length, int octave, int velocity) {
169         List<Note> notes = getNotes(chord, length, octave, velocity);
170         SoundProducer sp = getFreeProducer();
171         for (Note n : notes) {
172             // Hypätään taukojen yli    
173             if ( !n.isDelay() )                     
174                 sp.addNote(n);
175         }
176         sp.setNoteLength(length);
177         sp.start();
178         try {
179             Thread.sleep((long)((getBeatLength()*4) * length));
180         } catch (InterruptedException e) {
181             
182         }
183     }
184     
185     public void rest() {}
186     
187     protected SoundProducer getFreeProducer() {
188         // Poistetaan toimimattomat tuottajat
189         for (int i = 0; i < producers.size(); i++) {
190             if (!producers.elementAt(i).isAlive()) {
191                 producers.remove(i);
192             }
193         }
194         // Luodaan uusi
195         SoundProducer sp = new SoundProducer();
196         producers.addElement(sp);
197         return sp;
198     }
199 }
200