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