Mercurial > hg > beatroot
comparison at/ofai/music/util/EventList.java @ 2:4c3f5bc01c97
* Import BeatRoot v0.5.7
author | Chris Cannam |
---|---|
date | Fri, 08 Oct 2010 16:11:06 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1:4de8d7f01bd4 | 2:4c3f5bc01c97 |
---|---|
1 /* | |
2 Copyright (C) 2001, 2006 by Simon Dixon | |
3 | |
4 This program is free software; you can redistribute it and/or modify | |
5 it under the terms of the GNU General Public License as published by | |
6 the Free Software Foundation; either version 2 of the License, or | |
7 (at your option) any later version. | |
8 | |
9 This program is distributed in the hope that it will be useful, | |
10 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 GNU General Public License for more details. | |
13 | |
14 You should have received a copy of the GNU General Public License along | |
15 with this program (the file gpl.txt); if not, download it from | |
16 http://www.gnu.org/licenses/gpl.txt or write to the | |
17 Free Software Foundation, Inc., | |
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
19 */ | |
20 | |
21 package at.ofai.music.util; | |
22 | |
23 import java.io.BufferedReader; | |
24 import java.io.File; | |
25 import java.io.FileInputStream; | |
26 import java.io.FileOutputStream; | |
27 import java.io.FileReader; | |
28 import java.io.IOException; | |
29 import java.io.ObjectInputStream; | |
30 import java.io.ObjectOutputStream; | |
31 import java.io.PrintStream; | |
32 import java.io.Serializable; | |
33 import java.util.Iterator; | |
34 import java.util.LinkedList; | |
35 import java.util.ListIterator; | |
36 | |
37 import javax.sound.midi.InvalidMidiDataException; | |
38 import javax.sound.midi.MetaMessage; | |
39 import javax.sound.midi.MidiEvent; | |
40 import javax.sound.midi.MidiMessage; | |
41 import javax.sound.midi.MidiSystem; | |
42 import javax.sound.midi.Sequence; | |
43 import javax.sound.midi.ShortMessage; | |
44 import javax.sound.midi.Track; | |
45 | |
46 import at.ofai.music.worm.Worm; | |
47 import at.ofai.music.worm.WormFile; | |
48 import at.ofai.music.worm.WormParameters; | |
49 | |
50 // Adapted from eventList::readMatchFile in beatroot/src/eventMidi.cpp | |
51 | |
52 // Reads in a Prolog score+performance (.match) file; returns it as an eventList | |
53 // Lines in the match file can be of the form: | |
54 // hammer_bounce-PlayedNote. | |
55 // info(Attribute, Value). | |
56 // insertion-PlayedNote. | |
57 // ornament(Anchor)-PlayedNote. | |
58 // ScoreNote-deletion. | |
59 // ScoreNote-PlayedNote. | |
60 // ScoreNote-trailing_score_note. | |
61 // trailing_played_note-PlayedNote. | |
62 // trill(Anchor)-PlayedNote. | |
63 // where ScoreNote is of the form | |
64 // snote(Anchor,[NoteName,Modifier],Octave,Bar:Beat,Offset,Duration, | |
65 // BeatNumber,DurationInBeats,ScoreAttributesList) | |
66 // e.g. snote(n1,[b,b],5,1:1,0,3/16,0,0.75,[s]) | |
67 // and PlayedNote is of the form | |
68 // note(Number,[NoteName,Modifier],Octave,Onset,Offset,AdjOffset,Velocity) | |
69 // e.g. note(1,[a,#],5,5054,6362,6768,53) | |
70 | |
71 class WormFileParseException extends RuntimeException { | |
72 | |
73 static final long serialVersionUID = 0; | |
74 public WormFileParseException(String s) { | |
75 super(s); | |
76 } // constructor | |
77 | |
78 } // class WormFileParseException | |
79 | |
80 class MatchFileParseException extends RuntimeException { | |
81 | |
82 static final long serialVersionUID = 0; | |
83 public MatchFileParseException(String s) { | |
84 super(s); | |
85 } // constructor | |
86 | |
87 } // class MatchFileParseException | |
88 | |
89 class BTFileParseException extends RuntimeException { | |
90 | |
91 static final long serialVersionUID = 0; | |
92 public BTFileParseException(String s) { | |
93 super(s); | |
94 } // constructor | |
95 | |
96 } // class BTFileParseException | |
97 | |
98 | |
99 // Process the strings which label extra features of notes in match files. | |
100 // We assume no more than 32 distinct labels in a file. | |
101 class Flags { | |
102 | |
103 String[] labels = new String[32]; | |
104 int size = 0; | |
105 | |
106 int getFlag(String s) { | |
107 if ((s == null) || s.equals("")) | |
108 return 0; | |
109 //int val = 1; | |
110 for (int i = 0; i < size; i++) | |
111 if (s.equals(labels[i])) | |
112 return 1 << i; | |
113 if (size == 32) { | |
114 System.err.println("Overflow: Too many flags: " + s); | |
115 size--; | |
116 } | |
117 labels[size] = s; | |
118 return 1 << size++; | |
119 } // getFlag() | |
120 | |
121 String getLabel(int i) { | |
122 if (i >= size) | |
123 return "ERROR: Unknown flag"; | |
124 return labels[i]; | |
125 } // getLabel() | |
126 | |
127 } // class Flags | |
128 | |
129 | |
130 // A score/match/midi file is represented as an EventList object, | |
131 // which contains pointers to the head and tail links, and some | |
132 // class-wide parameters. Parameters are class-wide, as it is | |
133 // assumed that the Worm has only one input file at a time. | |
134 public class EventList implements Serializable { | |
135 | |
136 public LinkedList<Event> l; | |
137 | |
138 protected static boolean timingCorrection = false; | |
139 protected static double timingDisplacement = 0; | |
140 protected static int clockUnits = 480; | |
141 protected static int clockRate = 500000; | |
142 protected static double metricalLevel = 0; | |
143 public static final double UNKNOWN = Double.NaN; | |
144 protected static boolean noMelody = false; | |
145 protected static boolean onlyMelody = false; | |
146 protected static Flags flags = new Flags(); | |
147 | |
148 public EventList() { | |
149 l = new LinkedList<Event>(); | |
150 } // constructor | |
151 | |
152 public EventList(EventList e) { | |
153 this(); | |
154 ListIterator<Event> it = e.listIterator(); | |
155 while (it.hasNext()) | |
156 add(it.next()); | |
157 } // constructor | |
158 | |
159 public EventList(Event[] e) { | |
160 this(); | |
161 for (int i=0; i < e.length; i++) | |
162 add(e[i]); | |
163 } // constructor | |
164 | |
165 public void add(Event e) { | |
166 l.add(e); | |
167 } // add() | |
168 | |
169 public void add(EventList ev) { | |
170 l.addAll(ev.l); | |
171 } // add() | |
172 | |
173 public void insert(Event newEvent, boolean uniqueTimes) { | |
174 ListIterator<Event> li = l.listIterator(); | |
175 while (li.hasNext()) { | |
176 int sgn = newEvent.compareTo(li.next()); | |
177 if (sgn < 0) { | |
178 li.previous(); | |
179 break; | |
180 } else if (uniqueTimes && (sgn == 0)) { | |
181 li.remove(); | |
182 break; | |
183 } | |
184 } | |
185 li.add(newEvent); | |
186 } // insert() | |
187 | |
188 public ListIterator<Event> listIterator() { | |
189 return l.listIterator(); | |
190 } // listIterator() | |
191 | |
192 public Iterator<Event> iterator() { | |
193 return l.iterator(); | |
194 } // iterator() | |
195 | |
196 public int size() { | |
197 return l.size(); | |
198 } // size() | |
199 | |
200 public Event[] toArray() { | |
201 return toArray(0); | |
202 } // toArray() | |
203 | |
204 public double[] toOnsetArray() { | |
205 double[] d = new double[l.size()]; | |
206 int i = 0; | |
207 for (Iterator<Event> it = l.iterator(); it.hasNext(); i++) | |
208 d[i] = it.next().keyDown; | |
209 return d; | |
210 } // toOnsetArray() | |
211 | |
212 public Event[] toArray(int match) { | |
213 int count = 0; | |
214 for (Event e : l) | |
215 if ((match == 0) || (e.midiCommand == match)) | |
216 count++; | |
217 Event[] a = new Event[count]; | |
218 int i = 0; | |
219 for (Event e : l) | |
220 if ((match == 0) || (e.midiCommand == match)) | |
221 a[i++] = e; | |
222 return a; | |
223 } // toArray() | |
224 | |
225 public void writeBinary(String fileName) { | |
226 try { | |
227 ObjectOutputStream oos = new ObjectOutputStream( | |
228 new FileOutputStream(fileName)); | |
229 oos.writeObject(this); | |
230 oos.close(); | |
231 } catch (IOException e) { | |
232 System.err.println(e); | |
233 } | |
234 } // writeBinary() | |
235 | |
236 public static EventList readBinary(String fileName) { | |
237 try { | |
238 ObjectInputStream ois = new ObjectInputStream( | |
239 new FileInputStream(fileName)); | |
240 EventList e = (EventList) ois.readObject(); | |
241 ois.close(); | |
242 return e; | |
243 } catch (IOException e) { | |
244 System.err.println(e); | |
245 return null; | |
246 } catch (ClassNotFoundException e) { | |
247 System.err.println(e); | |
248 return null; | |
249 } | |
250 } // readBinary() | |
251 | |
252 public void writeMIDI(String fileName) { | |
253 writeMIDI(fileName, null); | |
254 } // writeMIDI() | |
255 | |
256 public void writeMIDI(String fileName, EventList pedal) { | |
257 try { | |
258 MidiSystem.write(toMIDI(pedal), 1, new File(fileName)); | |
259 } catch (Exception e) { | |
260 System.err.println("Error: Unable to write MIDI file " + fileName); | |
261 e.printStackTrace(); | |
262 } | |
263 } // writeMIDI() | |
264 | |
265 public Sequence toMIDI(EventList pedal) throws InvalidMidiDataException { | |
266 final int midiTempo = 1000000; | |
267 Sequence s = new Sequence(Sequence.PPQ, 1000); | |
268 Track[] tr = new Track[16]; | |
269 tr[0] = s.createTrack(); | |
270 MetaMessage mm = new MetaMessage(); | |
271 byte[] b = new byte[3]; | |
272 b[0] = (byte)((midiTempo >> 16) & 0xFF); | |
273 b[1] = (byte)((midiTempo >> 8) & 0xFF); | |
274 b[2] = (byte)(midiTempo & 0xFF); | |
275 mm.setMessage(0x51, b, 3); | |
276 tr[0].add(new MidiEvent(mm, 0L)); | |
277 for (Event e : l) { // from match or beatTrack file | |
278 if (e.midiCommand == 0) // skip beatTrack file | |
279 break; | |
280 if (tr[e.midiTrack] == null) | |
281 tr[e.midiTrack] = s.createTrack(); | |
282 //switch (e.midiCommand) | |
283 //case ShortMessage.NOTE_ON: | |
284 //case ShortMessage.POLY_PRESSURE: | |
285 //case ShortMessage.CONTROL_CHANGE: | |
286 //case ShortMessage.PROGRAM_CHANGE: | |
287 //case ShortMessage.CHANNEL_PRESSURE: | |
288 //case ShortMessage.PITCH_BEND: | |
289 ShortMessage sm = new ShortMessage(); | |
290 sm.setMessage(e.midiCommand, e.midiChannel, | |
291 e.midiPitch, e.midiVelocity); | |
292 tr[e.midiTrack].add(new MidiEvent(sm, | |
293 (long)Math.round(1000 * e.keyDown))); | |
294 if (e.midiCommand == ShortMessage.NOTE_ON) { | |
295 sm = new ShortMessage(); | |
296 sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, e.midiPitch, 0); | |
297 tr[e.midiTrack].add(new MidiEvent(sm, (long)Math.round(1000 * e.keyUp))); | |
298 } | |
299 } | |
300 if (pedal != null) { // from MIDI file | |
301 // if (t.size() > 0) // otherwise beatTrack files leave an empty trk | |
302 // t = s.createTrack(); | |
303 for (Event e : pedal.l) { | |
304 if (tr[e.midiTrack] == null) | |
305 tr[e.midiTrack] = s.createTrack(); | |
306 ShortMessage sm = new ShortMessage(); | |
307 sm.setMessage(e.midiCommand, e.midiChannel, | |
308 e.midiPitch, e.midiVelocity); | |
309 tr[e.midiTrack].add(new MidiEvent(sm, | |
310 (long)Math.round(1000 * e.keyDown))); | |
311 if (e.midiCommand == ShortMessage.NOTE_ON) { | |
312 sm = new ShortMessage(); | |
313 sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, | |
314 e.midiPitch,e.midiVelocity); | |
315 tr[e.midiTrack].add(new MidiEvent(sm, | |
316 (long)Math.round(1000 * e.keyUp))); | |
317 } | |
318 //catch (InvalidMidiDataException exception) {} | |
319 } | |
320 } | |
321 return s; | |
322 } // toMIDI() | |
323 | |
324 public static EventList readMidiFile(String fileName) { | |
325 return readMidiFile(fileName, 0); | |
326 } // readMidiFile() | |
327 | |
328 public static EventList readMidiFile(String fileName, int skipTrackFlag) { | |
329 EventList list = new EventList(); | |
330 Sequence s; | |
331 try { | |
332 s = MidiSystem.getSequence(new File(fileName)); | |
333 } catch (Exception e) { | |
334 e.printStackTrace(); | |
335 return list; | |
336 } | |
337 double midiTempo = 500000; | |
338 double tempoFactor = midiTempo / s.getResolution() / 1000000.0; | |
339 // System.err.println(tempoFactor); | |
340 Event[][] noteOns = new Event[128][16]; | |
341 Track[] tracks = s.getTracks(); | |
342 for (int t = 0; t < tracks.length; t++, skipTrackFlag >>= 1) { | |
343 if ((skipTrackFlag & 1) == 1) | |
344 continue; | |
345 for (int e = 0; e < tracks[t].size(); e++) { | |
346 MidiEvent me = tracks[t].get(e); | |
347 MidiMessage mm = me.getMessage(); | |
348 double time = me.getTick() * tempoFactor; | |
349 byte[] mesg = mm.getMessage(); | |
350 int channel = mesg[0] & 0x0F; | |
351 int command = mesg[0] & 0xF0; | |
352 if (command == ShortMessage.NOTE_ON) { | |
353 int pitch = mesg[1] & 0x7F; | |
354 int velocity = mesg[2] & 0x7F; | |
355 if (noteOns[pitch][channel] != null) { | |
356 if (velocity == 0) { // NOTE_OFF in disguise :( | |
357 noteOns[pitch][channel].keyUp = time; | |
358 noteOns[pitch][channel].pedalUp = time; | |
359 noteOns[pitch][channel] = null; | |
360 } else | |
361 System.err.println("Double note on: n=" + pitch + | |
362 " c=" + channel + | |
363 " t1=" + noteOns[pitch][channel] + | |
364 " t2=" + time); | |
365 } else { | |
366 Event n = new Event(time, 0, 0, pitch, velocity, -1, -1, | |
367 0, ShortMessage.NOTE_ON, channel, t); | |
368 noteOns[pitch][channel] = n; | |
369 list.add(n); | |
370 } | |
371 } else if (command == ShortMessage.NOTE_OFF) { | |
372 int pitch = mesg[1] & 0x7F; | |
373 noteOns[pitch][channel].keyUp = time; | |
374 noteOns[pitch][channel].pedalUp = time; | |
375 noteOns[pitch][channel] = null; | |
376 } else if (command == 0xF0) { | |
377 if ((channel == 0x0F) && (mesg[1] == 0x51)) { | |
378 midiTempo = (mesg[5] & 0xFF) | | |
379 ((mesg[4] & 0xFF) << 8) | | |
380 ((mesg[3] & 0xFF) << 16); | |
381 tempoFactor = midiTempo / s.getResolution() / 1000000.0; | |
382 // System.err.println("Info: Tempo change: " + midiTempo + | |
383 // " tf=" + tempoFactor); | |
384 } | |
385 } else if (mesg.length > 3) { | |
386 System.err.println("midi message too long: " + mesg.length); | |
387 System.err.println("\tFirst byte: " + mesg[0]); | |
388 } else { | |
389 int b0 = mesg[0] & 0xFF; | |
390 int b1 = -1; | |
391 int b2 = -1; | |
392 if (mesg.length > 1) | |
393 b1 = mesg[1] & 0xFF; | |
394 if (mesg.length > 2) | |
395 b2 = mesg[2] & 0xFF; | |
396 list.add(new Event(time, time, -1, b1, b2, -1, -1, 0, | |
397 b0 & 0xF0, b0 & 0x0F, t)); | |
398 } | |
399 } | |
400 } | |
401 for (int pitch = 0; pitch < 128; pitch++) | |
402 for (int channel = 0; channel < 16; channel++) | |
403 if (noteOns[pitch][channel] != null) | |
404 System.err.println("Missing note off: n=" + | |
405 noteOns[pitch][channel].midiPitch + " t=" + | |
406 noteOns[pitch][channel].keyDown); | |
407 return list; | |
408 } // readMidiFile() | |
409 | |
410 public void print() { | |
411 for (Iterator<Event> i = l.iterator(); i.hasNext(); ) | |
412 i.next().print(flags); | |
413 } // print() | |
414 | |
415 public static void setTimingCorrection(double corr) { | |
416 timingCorrection = corr >= 0; | |
417 timingDisplacement = corr; | |
418 } // setTimingCorrection() | |
419 | |
420 public static EventList readBeatsAsText(String fileName) throws Exception { | |
421 EventList list = new EventList(); | |
422 BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); | |
423 String s = inputFile.readLine(); | |
424 if (s.startsWith("###")) | |
425 return readLabelFile(fileName); | |
426 int beats = 0; | |
427 int pitch = 56; | |
428 int vol = 80; | |
429 int ch = 10; | |
430 int track = 0; | |
431 int fl = 1; | |
432 while (s != null) { | |
433 int ind = s.indexOf(','); | |
434 if (ind < 0) | |
435 ind = s.indexOf(' '); | |
436 double time = 0; | |
437 if (ind >= 0) { | |
438 String tmp = s.substring(0,ind).trim(); | |
439 if (tmp.length() == 0) { | |
440 s = inputFile.readLine(); | |
441 continue; | |
442 } | |
443 time = Double.parseDouble(tmp); | |
444 s = s.substring(ind+1); | |
445 } else { | |
446 String tmp = s.trim(); | |
447 if (tmp.length() > 0) | |
448 time = Double.parseDouble(tmp); | |
449 s = inputFile.readLine(); | |
450 } | |
451 list.add(new Event(time, time, time, pitch, vol, ++beats, | |
452 1.0, fl, ShortMessage.NOTE_ON, ch, track)); | |
453 } | |
454 return list; | |
455 } // readBeatsAsText() | |
456 | |
457 public static EventList readBeatTrackFile(String fileName) throws Exception{ | |
458 if (!fileName.endsWith(".tmf")) // || fileName.endsWith(".csv")) | |
459 return readBeatsAsText(fileName); | |
460 else { | |
461 EventList list = new EventList(); | |
462 BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); | |
463 Matcher s = new Matcher(inputFile.readLine()); | |
464 if (!s.matchString("MFile")) | |
465 throw new BTFileParseException("Header not found"); | |
466 s.getInt(); // skip fileType | |
467 int tracks = s.getInt(); | |
468 int div = s.getInt(); | |
469 int tempo = 500000; // default tempo | |
470 double tf = 1e6 / tempo * div; | |
471 int lineCount = 1; | |
472 int beats = 0; | |
473 for (int track = 0; track < tracks; track++) { | |
474 s.set(inputFile.readLine()); | |
475 lineCount++; | |
476 if (!s.matchString("MTrk")) | |
477 throw new BTFileParseException("MTrk not found"); | |
478 s.set(inputFile.readLine()); | |
479 lineCount++; | |
480 while (!s.matchString("TrkEnd")) { | |
481 double time = s.getInt() / tf; | |
482 s.trimSpace(); | |
483 if (s.matchString("Tempo")) { | |
484 tempo = s.getInt(); | |
485 tf = 1e6 / tempo * div; | |
486 } else if (s.matchString("On")) { | |
487 s.trimSpace(); | |
488 s.matchString("ch="); | |
489 int ch = s.getInt(); | |
490 s.trimSpace(); | |
491 if (!s.matchString("n=")) | |
492 s.matchString("note="); | |
493 int pitch = s.getInt(); | |
494 s.trimSpace(); | |
495 if (!s.matchString("v=")) | |
496 s.matchString("vol="); | |
497 int vol = s.getInt(); | |
498 s.set(inputFile.readLine()); | |
499 lineCount++; | |
500 s.getInt(); | |
501 s.trimSpace(); | |
502 s.matchString("Off"); | |
503 s.skip('v'); | |
504 s.matchString("ol"); | |
505 s.matchString("="); | |
506 int flags = s.getInt(); | |
507 list.add(new Event(time, time, time, pitch, vol, ++beats, | |
508 1.0, flags, ShortMessage.NOTE_ON, ch, track)); | |
509 } else if (!s.matchString("Meta TrkEnd")) { | |
510 System.err.println("Unmatched text on line " + lineCount + | |
511 ": " + s.get()); | |
512 } | |
513 s.set(inputFile.readLine()); | |
514 lineCount++; | |
515 } | |
516 } | |
517 return list; | |
518 } | |
519 } // readBeatTrackFile() | |
520 | |
521 public void writeBeatsAsText(String fileName) throws Exception { | |
522 PrintStream out = new PrintStream(new File(fileName)); | |
523 char separator = '\n'; | |
524 if (fileName.endsWith(".csv")) | |
525 separator = ','; | |
526 for (Iterator<Event> it = iterator(); it.hasNext(); ) { | |
527 Event e = it.next(); | |
528 out.printf("%5.3f%c", e.keyDown, it.hasNext()? separator: '\n'); | |
529 } | |
530 out.close(); | |
531 } // writeBeatsAsText() | |
532 | |
533 public void writeBeatTrackFile(String fileName) throws Exception { | |
534 if (fileName.endsWith(".txt") || fileName.endsWith(".csv")) | |
535 writeBeatsAsText(fileName); | |
536 else { | |
537 PrintStream out = new PrintStream(new File(fileName)); | |
538 out.println("MFile 0 1 500"); | |
539 out.println("MTrk"); | |
540 out.println(" 0 Tempo 500000"); | |
541 int time = 0; | |
542 for (Iterator<Event> it = iterator(); it.hasNext(); ) { | |
543 Event e = it.next(); | |
544 time = (int) Math.round(1000 * e.keyDown); | |
545 out.printf("%6d On ch=%3d n=%3d v=%3d\n", | |
546 time, e.midiChannel, e.midiPitch, e.midiVelocity); | |
547 out.printf("%6d Off ch=%3d n=%3d v=%3d\n", | |
548 time, e.midiChannel, e.midiPitch, e.flags); | |
549 } | |
550 out.printf("%6d Meta TrkEnd\nTrkEnd\n", time); | |
551 out.close(); | |
552 } | |
553 } // writeBeatTrackFile() | |
554 | |
555 /** Reads a file containing time,String pairs into an EventList. */ | |
556 public static EventList readLabelFile(String fileName) throws Exception { | |
557 EventList list = new EventList(); | |
558 BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); | |
559 Matcher s = new Matcher(inputFile.readLine()); | |
560 int prevBar = 0; | |
561 int beats = 0; | |
562 int pitch = 56; | |
563 int vol = 80; | |
564 int ch = 10; | |
565 int track = 0; | |
566 while (s.hasData()) { | |
567 if (!s.matchString("#")) { | |
568 double time = s.getDouble(); | |
569 String label = s.get().trim(); | |
570 int colon = label.indexOf(':'); | |
571 int beat = 0; | |
572 if (colon < 0) | |
573 colon = label.length(); | |
574 else | |
575 beat = Integer.parseInt(label.substring(colon+1)); | |
576 int bar = Integer.parseInt(label.substring(0, colon)); | |
577 int flags = WormFile.BEAT; | |
578 if (bar != prevBar) { | |
579 flags |= WormFile.BAR; | |
580 prevBar = bar; | |
581 } | |
582 WormEvent ev = new WormEvent(time, time, time, pitch, vol, | |
583 ++beats,1.0,flags, ShortMessage.NOTE_ON, ch, track); | |
584 ev.label = label; | |
585 list.add(ev); | |
586 // System.out.println(time + " " + label); | |
587 } | |
588 s.set(inputFile.readLine()); | |
589 } | |
590 return list; | |
591 } // readLabelFile() | |
592 | |
593 public void writeLabelFile(String fileName) throws Exception { | |
594 PrintStream out = new PrintStream(new File(fileName)); | |
595 out.printf("###Created automatically\n"); | |
596 for (Event ev : l) | |
597 out.printf("%5.3f\t%s\n", ev.keyDown, ((WormEvent)ev).label); | |
598 out.close(); | |
599 } // writeLabelFile() | |
600 | |
601 public static EventList readWormFile(String fileName) throws Exception { | |
602 EventList list = new EventList(); | |
603 BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); | |
604 Matcher s = new Matcher(inputFile.readLine()); | |
605 int lineCount = 1; | |
606 if (!s.matchString("WORM Version:")) | |
607 throw new WormFileParseException("WORM format: header not found"); | |
608 if (s.getDouble() < 1.01) | |
609 throw new WormFileParseException("WORM format: v1.0 not supported"); | |
610 int dataCountDown = -1; | |
611 int beat = 0; | |
612 while (true) { | |
613 s.set(inputFile.readLine()); | |
614 lineCount++; | |
615 if (dataCountDown == 0) { | |
616 if (s.hasData()) | |
617 System.err.println("Ignoring trailing data past line " + | |
618 lineCount); | |
619 return list; | |
620 } else if (!s.hasData()) | |
621 throw new WormFileParseException("Unexpected EOF"); | |
622 if (dataCountDown < 0) { | |
623 if (s.matchString("Length:")) | |
624 dataCountDown = s.getInt(); | |
625 } else { | |
626 double time = s.getDouble(); | |
627 double tempo = s.getDouble(); | |
628 double loudness = s.getDouble(); | |
629 int flags = s.getInt(); | |
630 if ((flags & WormFile.TRACK) != 0) | |
631 beat++; // i.e. always, as index for comparing files | |
632 list.add(new WormEvent(time, tempo, loudness, beat, flags)); | |
633 dataCountDown--; | |
634 } | |
635 } | |
636 } // readWormFile() | |
637 | |
638 public static String getAudioFileFromWormFile(String wormFile) { | |
639 return getWormFileAttribute(wormFile, "AudioFile"); | |
640 } // getAudioFileFromWormFile() | |
641 | |
642 public static double getTrackLevelFromWormFile(String wormFile) { | |
643 String level = getWormFileAttribute(wormFile,WormParameters.TRACKLEVEL); | |
644 try { | |
645 int i = level.indexOf("/"); | |
646 if (i >= 0) | |
647 return Double.parseDouble(level.substring(0,i)) / | |
648 Double.parseDouble(level.substring(i+1)); | |
649 else | |
650 return Double.parseDouble(level); | |
651 } catch (Exception e) { | |
652 System.err.println("Error getting TrackLevel:\n" + e); | |
653 return 1; | |
654 } | |
655 } // getTrackLevelFromWormFile() | |
656 | |
657 public static String getWormFileAttribute(String wormFile, String attr) { | |
658 try { | |
659 BufferedReader r = new BufferedReader(new FileReader(wormFile)); | |
660 String line = r.readLine(); | |
661 attr += ":"; | |
662 while (line != null) { | |
663 if (line.startsWith(attr)) | |
664 return line.substring(attr.length()).trim(); | |
665 line = r.readLine(); | |
666 } | |
667 } catch (Exception e) { | |
668 System.err.println(e); | |
669 } | |
670 return null; | |
671 } // getWormFileAttribute() | |
672 | |
673 public static EventList readMatchFile(String fileName) throws Exception { | |
674 EventList list = new EventList(); | |
675 boolean startNote = timingCorrection; | |
676 int eventFlags, numerator, denominator; | |
677 String element; | |
678 BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); | |
679 double versionNumber = 1.0; | |
680 double onset, offset, eOffset, beat, duration; | |
681 int velocity, pitch, octave; | |
682 int lineCount = 1; | |
683 Matcher s = new Matcher(inputFile.readLine()); | |
684 while (s.hasData()) { | |
685 eventFlags = 0; | |
686 beat = UNKNOWN; | |
687 duration = UNKNOWN; | |
688 // System.out.println("Processing line " + lineCount); | |
689 if (s.matchString("info(")) { // meta-data | |
690 if (s.matchString("timeSignature,")) { | |
691 numerator = s.getInt(); | |
692 // ss1 << "beatsPerBar=" << numerator << ends; | |
693 s.skip('/'); | |
694 denominator = s.getInt(); | |
695 // ss2 << "beatUnits=" << denominator; | |
696 } else if (s.matchString("beatSubdivision,")) { | |
697 // strcpy(buf, "beatSubdivisions="); | |
698 // int i = strlen(buf); | |
699 // f.getline(buf+i, SZ-i, ']'); | |
700 // strcat(buf, "]"); | |
701 // parameters->add(buf); | |
702 s.skip(']'); | |
703 } else if (s.matchString("matchFileVersion,")) { | |
704 versionNumber = s.getDouble(); | |
705 } else if (s.matchString("midiClockUnits,")) { | |
706 clockUnits = s.getInt(); | |
707 } else if (s.matchString("midiClockRate,")) { | |
708 clockRate = s.getInt(); | |
709 } | |
710 s.set("%"); // don't expect the second half of the Prolog term | |
711 } else if (s.matchString("snote(")) { | |
712 s.skip(','); // identifier | |
713 s.skip(']'); // note name | |
714 s.skip(','); // ',' after note name | |
715 s.skip(','); // octave | |
716 s.skip(','); // onset time (in beats, integer part, bar:beat) | |
717 boolean isBt = s.matchString("0"); | |
718 s.skip(','); // onset time (in beats, fractional part) | |
719 s.skip(','); // duration (in beats, fraction) | |
720 try { | |
721 beat = s.getDouble(); | |
722 } catch (NumberFormatException e) { | |
723 System.err.println("Bad beat number on line " + lineCount); | |
724 beat = UNKNOWN; | |
725 } | |
726 if ((beat == Math.rint(beat)) != isBt) | |
727 System.err.println("Inconsistent beats on line "+lineCount); | |
728 s.skip(','); // onset time (in beats, decimal) | |
729 try { | |
730 duration = s.getDouble() - beat; | |
731 } catch (NumberFormatException e) { | |
732 System.err.println("Bad duration on line " + lineCount); | |
733 duration = UNKNOWN; | |
734 } | |
735 s.skip(','); // offset time (in beats, decimal) | |
736 s.skip('['); // additional info (e.g. melody/arpeggio/grace) | |
737 do { | |
738 element = s.getString(); | |
739 eventFlags |= flags.getFlag(element); | |
740 } while (s.matchString(",")); | |
741 s.skip('-'); | |
742 } else if (s.matchString("trill(")) { | |
743 eventFlags |= flags.getFlag("trill"); | |
744 s.skip('-'); | |
745 } else if (s.matchString("ornament(")) { | |
746 eventFlags |= flags.getFlag("ornament"); | |
747 s.skip('-'); | |
748 } else if (s.matchString("trailing_played_note-") || | |
749 s.matchString("hammer_bounce-") || | |
750 s.matchString("no_score_note-") || | |
751 s.matchString("insertion-")) { | |
752 eventFlags |= flags.getFlag("unscored"); | |
753 } else if (!s.matchString("%")) { // Prolog comment | |
754 throw new MatchFileParseException("error 4; line "+lineCount); | |
755 } | |
756 // READ 2nd term of Prolog expression | |
757 if (s.matchString("note(")) { | |
758 s.skip('['); // skip identifier | |
759 String note = s.getString(); | |
760 switch(Character.toUpperCase(note.charAt(0))) { | |
761 case 'A': pitch = 9; break; | |
762 case 'B': pitch = 11; break; | |
763 case 'C': pitch = 0; break; | |
764 case 'D': pitch = 2; break; | |
765 case 'E': pitch = 4; break; | |
766 case 'F': pitch = 5; break; | |
767 case 'G': pitch = 7; break; | |
768 default: throw new MatchFileParseException( | |
769 "Bad note on line " + lineCount); | |
770 } | |
771 s.skip(','); | |
772 String mod = s.getString(); | |
773 for (int i = 0; i < mod.length(); i++) { | |
774 switch (mod.charAt(i)) { | |
775 case '#': pitch++; break; | |
776 case 'b': pitch--; break; | |
777 case 'n': break; | |
778 default: throw new MatchFileParseException("error 5 " + | |
779 lineCount); | |
780 } | |
781 } | |
782 s.skip(','); | |
783 octave = s.getInt(); | |
784 pitch += 12 * octave; | |
785 s.skip(','); | |
786 onset = s.getInt(); | |
787 s.skip(','); | |
788 offset = s.getInt(); | |
789 if (versionNumber > 1.0) { | |
790 s.skip(','); | |
791 eOffset = s.getInt(); | |
792 } else | |
793 eOffset = offset; | |
794 s.skip(','); | |
795 velocity = s.getInt(); | |
796 onset /= clockUnits * 1000000.0 / clockRate; | |
797 offset /= clockUnits * 1000000.0 / clockRate; | |
798 eOffset /= clockUnits * 1000000.0 / clockRate; | |
799 if (timingCorrection) { | |
800 if (startNote) { | |
801 timingDisplacement = onset - timingDisplacement; | |
802 startNote = false; | |
803 } | |
804 onset -= timingDisplacement; | |
805 offset -= timingDisplacement; | |
806 eOffset -= timingDisplacement; | |
807 } | |
808 int m = flags.getFlag("s"); | |
809 if ((((eventFlags & m) != 0) && !noMelody) || | |
810 (((eventFlags & m) == 0) && !onlyMelody)) { | |
811 Event e = new Event(onset, offset, eOffset, pitch, velocity, | |
812 beat, duration, eventFlags); | |
813 list.add(e); | |
814 } | |
815 } else if (!s.matchString("no_played_note.") && | |
816 !s.matchString("trailing_score_note.") && | |
817 !s.matchString("deletion.") && | |
818 !s.matchString("%")) | |
819 throw new MatchFileParseException("error 6; line " + lineCount); | |
820 s.set(inputFile.readLine()); | |
821 lineCount++; | |
822 } | |
823 return list; | |
824 } // readMatchFile() | |
825 | |
826 public static void main(String[] args) throws Exception { // quick test | |
827 //System.out.println("Test"); | |
828 //readLabelFile(args[0]).writeLabelFile("tmp.txt"); | |
829 readLabelFile(args[0]).print(); | |
830 System.exit(0); | |
831 EventList el = readMatchFile(args[0]); | |
832 WormFile wf = new WormFile(null, el); | |
833 if (args.length >= 2) { | |
834 double sm = Double.parseDouble(args[1]); | |
835 wf.smooth(Worm.FULL_GAUSS, sm, sm, 0); | |
836 } else | |
837 wf.smooth(Worm.NONE, 0, 0, 0); | |
838 wf.write("worm.out"); | |
839 if (args.length == 3) | |
840 el.print(); | |
841 } // main() | |
842 | |
843 } // class EventList |