| ThreadedMidiPlayer.java |
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