Mercurial > hg > beatroot
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/EventList.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,843 @@ +/* + Copyright (C) 2001, 2006 by Simon Dixon + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program (the file gpl.txt); if not, download it from + http://www.gnu.org/licenses/gpl.txt or write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package at.ofai.music.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MetaMessage; +import javax.sound.midi.MidiEvent; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.Sequence; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.Track; + +import at.ofai.music.worm.Worm; +import at.ofai.music.worm.WormFile; +import at.ofai.music.worm.WormParameters; + +// Adapted from eventList::readMatchFile in beatroot/src/eventMidi.cpp + +// Reads in a Prolog score+performance (.match) file; returns it as an eventList +// Lines in the match file can be of the form: +// hammer_bounce-PlayedNote. +// info(Attribute, Value). +// insertion-PlayedNote. +// ornament(Anchor)-PlayedNote. +// ScoreNote-deletion. +// ScoreNote-PlayedNote. +// ScoreNote-trailing_score_note. +// trailing_played_note-PlayedNote. +// trill(Anchor)-PlayedNote. +// where ScoreNote is of the form +// snote(Anchor,[NoteName,Modifier],Octave,Bar:Beat,Offset,Duration, +// BeatNumber,DurationInBeats,ScoreAttributesList) +// e.g. snote(n1,[b,b],5,1:1,0,3/16,0,0.75,[s]) +// and PlayedNote is of the form +// note(Number,[NoteName,Modifier],Octave,Onset,Offset,AdjOffset,Velocity) +// e.g. note(1,[a,#],5,5054,6362,6768,53) + +class WormFileParseException extends RuntimeException { + + static final long serialVersionUID = 0; + public WormFileParseException(String s) { + super(s); + } // constructor + +} // class WormFileParseException + +class MatchFileParseException extends RuntimeException { + + static final long serialVersionUID = 0; + public MatchFileParseException(String s) { + super(s); + } // constructor + +} // class MatchFileParseException + +class BTFileParseException extends RuntimeException { + + static final long serialVersionUID = 0; + public BTFileParseException(String s) { + super(s); + } // constructor + +} // class BTFileParseException + + +// Process the strings which label extra features of notes in match files. +// We assume no more than 32 distinct labels in a file. +class Flags { + + String[] labels = new String[32]; + int size = 0; + + int getFlag(String s) { + if ((s == null) || s.equals("")) + return 0; + //int val = 1; + for (int i = 0; i < size; i++) + if (s.equals(labels[i])) + return 1 << i; + if (size == 32) { + System.err.println("Overflow: Too many flags: " + s); + size--; + } + labels[size] = s; + return 1 << size++; + } // getFlag() + + String getLabel(int i) { + if (i >= size) + return "ERROR: Unknown flag"; + return labels[i]; + } // getLabel() + +} // class Flags + + +// A score/match/midi file is represented as an EventList object, +// which contains pointers to the head and tail links, and some +// class-wide parameters. Parameters are class-wide, as it is +// assumed that the Worm has only one input file at a time. +public class EventList implements Serializable { + + public LinkedList<Event> l; + + protected static boolean timingCorrection = false; + protected static double timingDisplacement = 0; + protected static int clockUnits = 480; + protected static int clockRate = 500000; + protected static double metricalLevel = 0; + public static final double UNKNOWN = Double.NaN; + protected static boolean noMelody = false; + protected static boolean onlyMelody = false; + protected static Flags flags = new Flags(); + + public EventList() { + l = new LinkedList<Event>(); + } // constructor + + public EventList(EventList e) { + this(); + ListIterator<Event> it = e.listIterator(); + while (it.hasNext()) + add(it.next()); + } // constructor + + public EventList(Event[] e) { + this(); + for (int i=0; i < e.length; i++) + add(e[i]); + } // constructor + + public void add(Event e) { + l.add(e); + } // add() + + public void add(EventList ev) { + l.addAll(ev.l); + } // add() + + public void insert(Event newEvent, boolean uniqueTimes) { + ListIterator<Event> li = l.listIterator(); + while (li.hasNext()) { + int sgn = newEvent.compareTo(li.next()); + if (sgn < 0) { + li.previous(); + break; + } else if (uniqueTimes && (sgn == 0)) { + li.remove(); + break; + } + } + li.add(newEvent); + } // insert() + + public ListIterator<Event> listIterator() { + return l.listIterator(); + } // listIterator() + + public Iterator<Event> iterator() { + return l.iterator(); + } // iterator() + + public int size() { + return l.size(); + } // size() + + public Event[] toArray() { + return toArray(0); + } // toArray() + + public double[] toOnsetArray() { + double[] d = new double[l.size()]; + int i = 0; + for (Iterator<Event> it = l.iterator(); it.hasNext(); i++) + d[i] = it.next().keyDown; + return d; + } // toOnsetArray() + + public Event[] toArray(int match) { + int count = 0; + for (Event e : l) + if ((match == 0) || (e.midiCommand == match)) + count++; + Event[] a = new Event[count]; + int i = 0; + for (Event e : l) + if ((match == 0) || (e.midiCommand == match)) + a[i++] = e; + return a; + } // toArray() + + public void writeBinary(String fileName) { + try { + ObjectOutputStream oos = new ObjectOutputStream( + new FileOutputStream(fileName)); + oos.writeObject(this); + oos.close(); + } catch (IOException e) { + System.err.println(e); + } + } // writeBinary() + + public static EventList readBinary(String fileName) { + try { + ObjectInputStream ois = new ObjectInputStream( + new FileInputStream(fileName)); + EventList e = (EventList) ois.readObject(); + ois.close(); + return e; + } catch (IOException e) { + System.err.println(e); + return null; + } catch (ClassNotFoundException e) { + System.err.println(e); + return null; + } + } // readBinary() + + public void writeMIDI(String fileName) { + writeMIDI(fileName, null); + } // writeMIDI() + + public void writeMIDI(String fileName, EventList pedal) { + try { + MidiSystem.write(toMIDI(pedal), 1, new File(fileName)); + } catch (Exception e) { + System.err.println("Error: Unable to write MIDI file " + fileName); + e.printStackTrace(); + } + } // writeMIDI() + + public Sequence toMIDI(EventList pedal) throws InvalidMidiDataException { + final int midiTempo = 1000000; + Sequence s = new Sequence(Sequence.PPQ, 1000); + Track[] tr = new Track[16]; + tr[0] = s.createTrack(); + MetaMessage mm = new MetaMessage(); + byte[] b = new byte[3]; + b[0] = (byte)((midiTempo >> 16) & 0xFF); + b[1] = (byte)((midiTempo >> 8) & 0xFF); + b[2] = (byte)(midiTempo & 0xFF); + mm.setMessage(0x51, b, 3); + tr[0].add(new MidiEvent(mm, 0L)); + for (Event e : l) { // from match or beatTrack file + if (e.midiCommand == 0) // skip beatTrack file + break; + if (tr[e.midiTrack] == null) + tr[e.midiTrack] = s.createTrack(); + //switch (e.midiCommand) + //case ShortMessage.NOTE_ON: + //case ShortMessage.POLY_PRESSURE: + //case ShortMessage.CONTROL_CHANGE: + //case ShortMessage.PROGRAM_CHANGE: + //case ShortMessage.CHANNEL_PRESSURE: + //case ShortMessage.PITCH_BEND: + ShortMessage sm = new ShortMessage(); + sm.setMessage(e.midiCommand, e.midiChannel, + e.midiPitch, e.midiVelocity); + tr[e.midiTrack].add(new MidiEvent(sm, + (long)Math.round(1000 * e.keyDown))); + if (e.midiCommand == ShortMessage.NOTE_ON) { + sm = new ShortMessage(); + sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, e.midiPitch, 0); + tr[e.midiTrack].add(new MidiEvent(sm, (long)Math.round(1000 * e.keyUp))); + } + } + if (pedal != null) { // from MIDI file + // if (t.size() > 0) // otherwise beatTrack files leave an empty trk + // t = s.createTrack(); + for (Event e : pedal.l) { + if (tr[e.midiTrack] == null) + tr[e.midiTrack] = s.createTrack(); + ShortMessage sm = new ShortMessage(); + sm.setMessage(e.midiCommand, e.midiChannel, + e.midiPitch, e.midiVelocity); + tr[e.midiTrack].add(new MidiEvent(sm, + (long)Math.round(1000 * e.keyDown))); + if (e.midiCommand == ShortMessage.NOTE_ON) { + sm = new ShortMessage(); + sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, + e.midiPitch,e.midiVelocity); + tr[e.midiTrack].add(new MidiEvent(sm, + (long)Math.round(1000 * e.keyUp))); + } + //catch (InvalidMidiDataException exception) {} + } + } + return s; + } // toMIDI() + + public static EventList readMidiFile(String fileName) { + return readMidiFile(fileName, 0); + } // readMidiFile() + + public static EventList readMidiFile(String fileName, int skipTrackFlag) { + EventList list = new EventList(); + Sequence s; + try { + s = MidiSystem.getSequence(new File(fileName)); + } catch (Exception e) { + e.printStackTrace(); + return list; + } + double midiTempo = 500000; + double tempoFactor = midiTempo / s.getResolution() / 1000000.0; + // System.err.println(tempoFactor); + Event[][] noteOns = new Event[128][16]; + Track[] tracks = s.getTracks(); + for (int t = 0; t < tracks.length; t++, skipTrackFlag >>= 1) { + if ((skipTrackFlag & 1) == 1) + continue; + for (int e = 0; e < tracks[t].size(); e++) { + MidiEvent me = tracks[t].get(e); + MidiMessage mm = me.getMessage(); + double time = me.getTick() * tempoFactor; + byte[] mesg = mm.getMessage(); + int channel = mesg[0] & 0x0F; + int command = mesg[0] & 0xF0; + if (command == ShortMessage.NOTE_ON) { + int pitch = mesg[1] & 0x7F; + int velocity = mesg[2] & 0x7F; + if (noteOns[pitch][channel] != null) { + if (velocity == 0) { // NOTE_OFF in disguise :( + noteOns[pitch][channel].keyUp = time; + noteOns[pitch][channel].pedalUp = time; + noteOns[pitch][channel] = null; + } else + System.err.println("Double note on: n=" + pitch + + " c=" + channel + + " t1=" + noteOns[pitch][channel] + + " t2=" + time); + } else { + Event n = new Event(time, 0, 0, pitch, velocity, -1, -1, + 0, ShortMessage.NOTE_ON, channel, t); + noteOns[pitch][channel] = n; + list.add(n); + } + } else if (command == ShortMessage.NOTE_OFF) { + int pitch = mesg[1] & 0x7F; + noteOns[pitch][channel].keyUp = time; + noteOns[pitch][channel].pedalUp = time; + noteOns[pitch][channel] = null; + } else if (command == 0xF0) { + if ((channel == 0x0F) && (mesg[1] == 0x51)) { + midiTempo = (mesg[5] & 0xFF) | + ((mesg[4] & 0xFF) << 8) | + ((mesg[3] & 0xFF) << 16); + tempoFactor = midiTempo / s.getResolution() / 1000000.0; + // System.err.println("Info: Tempo change: " + midiTempo + + // " tf=" + tempoFactor); + } + } else if (mesg.length > 3) { + System.err.println("midi message too long: " + mesg.length); + System.err.println("\tFirst byte: " + mesg[0]); + } else { + int b0 = mesg[0] & 0xFF; + int b1 = -1; + int b2 = -1; + if (mesg.length > 1) + b1 = mesg[1] & 0xFF; + if (mesg.length > 2) + b2 = mesg[2] & 0xFF; + list.add(new Event(time, time, -1, b1, b2, -1, -1, 0, + b0 & 0xF0, b0 & 0x0F, t)); + } + } + } + for (int pitch = 0; pitch < 128; pitch++) + for (int channel = 0; channel < 16; channel++) + if (noteOns[pitch][channel] != null) + System.err.println("Missing note off: n=" + + noteOns[pitch][channel].midiPitch + " t=" + + noteOns[pitch][channel].keyDown); + return list; + } // readMidiFile() + + public void print() { + for (Iterator<Event> i = l.iterator(); i.hasNext(); ) + i.next().print(flags); + } // print() + + public static void setTimingCorrection(double corr) { + timingCorrection = corr >= 0; + timingDisplacement = corr; + } // setTimingCorrection() + + public static EventList readBeatsAsText(String fileName) throws Exception { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + String s = inputFile.readLine(); + if (s.startsWith("###")) + return readLabelFile(fileName); + int beats = 0; + int pitch = 56; + int vol = 80; + int ch = 10; + int track = 0; + int fl = 1; + while (s != null) { + int ind = s.indexOf(','); + if (ind < 0) + ind = s.indexOf(' '); + double time = 0; + if (ind >= 0) { + String tmp = s.substring(0,ind).trim(); + if (tmp.length() == 0) { + s = inputFile.readLine(); + continue; + } + time = Double.parseDouble(tmp); + s = s.substring(ind+1); + } else { + String tmp = s.trim(); + if (tmp.length() > 0) + time = Double.parseDouble(tmp); + s = inputFile.readLine(); + } + list.add(new Event(time, time, time, pitch, vol, ++beats, + 1.0, fl, ShortMessage.NOTE_ON, ch, track)); + } + return list; + } // readBeatsAsText() + + public static EventList readBeatTrackFile(String fileName) throws Exception{ + if (!fileName.endsWith(".tmf")) // || fileName.endsWith(".csv")) + return readBeatsAsText(fileName); + else { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + Matcher s = new Matcher(inputFile.readLine()); + if (!s.matchString("MFile")) + throw new BTFileParseException("Header not found"); + s.getInt(); // skip fileType + int tracks = s.getInt(); + int div = s.getInt(); + int tempo = 500000; // default tempo + double tf = 1e6 / tempo * div; + int lineCount = 1; + int beats = 0; + for (int track = 0; track < tracks; track++) { + s.set(inputFile.readLine()); + lineCount++; + if (!s.matchString("MTrk")) + throw new BTFileParseException("MTrk not found"); + s.set(inputFile.readLine()); + lineCount++; + while (!s.matchString("TrkEnd")) { + double time = s.getInt() / tf; + s.trimSpace(); + if (s.matchString("Tempo")) { + tempo = s.getInt(); + tf = 1e6 / tempo * div; + } else if (s.matchString("On")) { + s.trimSpace(); + s.matchString("ch="); + int ch = s.getInt(); + s.trimSpace(); + if (!s.matchString("n=")) + s.matchString("note="); + int pitch = s.getInt(); + s.trimSpace(); + if (!s.matchString("v=")) + s.matchString("vol="); + int vol = s.getInt(); + s.set(inputFile.readLine()); + lineCount++; + s.getInt(); + s.trimSpace(); + s.matchString("Off"); + s.skip('v'); + s.matchString("ol"); + s.matchString("="); + int flags = s.getInt(); + list.add(new Event(time, time, time, pitch, vol, ++beats, + 1.0, flags, ShortMessage.NOTE_ON, ch, track)); + } else if (!s.matchString("Meta TrkEnd")) { + System.err.println("Unmatched text on line " + lineCount + + ": " + s.get()); + } + s.set(inputFile.readLine()); + lineCount++; + } + } + return list; + } + } // readBeatTrackFile() + + public void writeBeatsAsText(String fileName) throws Exception { + PrintStream out = new PrintStream(new File(fileName)); + char separator = '\n'; + if (fileName.endsWith(".csv")) + separator = ','; + for (Iterator<Event> it = iterator(); it.hasNext(); ) { + Event e = it.next(); + out.printf("%5.3f%c", e.keyDown, it.hasNext()? separator: '\n'); + } + out.close(); + } // writeBeatsAsText() + + public void writeBeatTrackFile(String fileName) throws Exception { + if (fileName.endsWith(".txt") || fileName.endsWith(".csv")) + writeBeatsAsText(fileName); + else { + PrintStream out = new PrintStream(new File(fileName)); + out.println("MFile 0 1 500"); + out.println("MTrk"); + out.println(" 0 Tempo 500000"); + int time = 0; + for (Iterator<Event> it = iterator(); it.hasNext(); ) { + Event e = it.next(); + time = (int) Math.round(1000 * e.keyDown); + out.printf("%6d On ch=%3d n=%3d v=%3d\n", + time, e.midiChannel, e.midiPitch, e.midiVelocity); + out.printf("%6d Off ch=%3d n=%3d v=%3d\n", + time, e.midiChannel, e.midiPitch, e.flags); + } + out.printf("%6d Meta TrkEnd\nTrkEnd\n", time); + out.close(); + } + } // writeBeatTrackFile() + + /** Reads a file containing time,String pairs into an EventList. */ + public static EventList readLabelFile(String fileName) throws Exception { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + Matcher s = new Matcher(inputFile.readLine()); + int prevBar = 0; + int beats = 0; + int pitch = 56; + int vol = 80; + int ch = 10; + int track = 0; + while (s.hasData()) { + if (!s.matchString("#")) { + double time = s.getDouble(); + String label = s.get().trim(); + int colon = label.indexOf(':'); + int beat = 0; + if (colon < 0) + colon = label.length(); + else + beat = Integer.parseInt(label.substring(colon+1)); + int bar = Integer.parseInt(label.substring(0, colon)); + int flags = WormFile.BEAT; + if (bar != prevBar) { + flags |= WormFile.BAR; + prevBar = bar; + } + WormEvent ev = new WormEvent(time, time, time, pitch, vol, + ++beats,1.0,flags, ShortMessage.NOTE_ON, ch, track); + ev.label = label; + list.add(ev); +// System.out.println(time + " " + label); + } + s.set(inputFile.readLine()); + } + return list; + } // readLabelFile() + + public void writeLabelFile(String fileName) throws Exception { + PrintStream out = new PrintStream(new File(fileName)); + out.printf("###Created automatically\n"); + for (Event ev : l) + out.printf("%5.3f\t%s\n", ev.keyDown, ((WormEvent)ev).label); + out.close(); + } // writeLabelFile() + + public static EventList readWormFile(String fileName) throws Exception { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + Matcher s = new Matcher(inputFile.readLine()); + int lineCount = 1; + if (!s.matchString("WORM Version:")) + throw new WormFileParseException("WORM format: header not found"); + if (s.getDouble() < 1.01) + throw new WormFileParseException("WORM format: v1.0 not supported"); + int dataCountDown = -1; + int beat = 0; + while (true) { + s.set(inputFile.readLine()); + lineCount++; + if (dataCountDown == 0) { + if (s.hasData()) + System.err.println("Ignoring trailing data past line " + + lineCount); + return list; + } else if (!s.hasData()) + throw new WormFileParseException("Unexpected EOF"); + if (dataCountDown < 0) { + if (s.matchString("Length:")) + dataCountDown = s.getInt(); + } else { + double time = s.getDouble(); + double tempo = s.getDouble(); + double loudness = s.getDouble(); + int flags = s.getInt(); + if ((flags & WormFile.TRACK) != 0) + beat++; // i.e. always, as index for comparing files + list.add(new WormEvent(time, tempo, loudness, beat, flags)); + dataCountDown--; + } + } + } // readWormFile() + + public static String getAudioFileFromWormFile(String wormFile) { + return getWormFileAttribute(wormFile, "AudioFile"); + } // getAudioFileFromWormFile() + + public static double getTrackLevelFromWormFile(String wormFile) { + String level = getWormFileAttribute(wormFile,WormParameters.TRACKLEVEL); + try { + int i = level.indexOf("/"); + if (i >= 0) + return Double.parseDouble(level.substring(0,i)) / + Double.parseDouble(level.substring(i+1)); + else + return Double.parseDouble(level); + } catch (Exception e) { + System.err.println("Error getting TrackLevel:\n" + e); + return 1; + } + } // getTrackLevelFromWormFile() + + public static String getWormFileAttribute(String wormFile, String attr) { + try { + BufferedReader r = new BufferedReader(new FileReader(wormFile)); + String line = r.readLine(); + attr += ":"; + while (line != null) { + if (line.startsWith(attr)) + return line.substring(attr.length()).trim(); + line = r.readLine(); + } + } catch (Exception e) { + System.err.println(e); + } + return null; + } // getWormFileAttribute() + + public static EventList readMatchFile(String fileName) throws Exception { + EventList list = new EventList(); + boolean startNote = timingCorrection; + int eventFlags, numerator, denominator; + String element; + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + double versionNumber = 1.0; + double onset, offset, eOffset, beat, duration; + int velocity, pitch, octave; + int lineCount = 1; + Matcher s = new Matcher(inputFile.readLine()); + while (s.hasData()) { + eventFlags = 0; + beat = UNKNOWN; + duration = UNKNOWN; + // System.out.println("Processing line " + lineCount); + if (s.matchString("info(")) { // meta-data + if (s.matchString("timeSignature,")) { + numerator = s.getInt(); + // ss1 << "beatsPerBar=" << numerator << ends; + s.skip('/'); + denominator = s.getInt(); + // ss2 << "beatUnits=" << denominator; + } else if (s.matchString("beatSubdivision,")) { + // strcpy(buf, "beatSubdivisions="); + // int i = strlen(buf); + // f.getline(buf+i, SZ-i, ']'); + // strcat(buf, "]"); + // parameters->add(buf); + s.skip(']'); + } else if (s.matchString("matchFileVersion,")) { + versionNumber = s.getDouble(); + } else if (s.matchString("midiClockUnits,")) { + clockUnits = s.getInt(); + } else if (s.matchString("midiClockRate,")) { + clockRate = s.getInt(); + } + s.set("%"); // don't expect the second half of the Prolog term + } else if (s.matchString("snote(")) { + s.skip(','); // identifier + s.skip(']'); // note name + s.skip(','); // ',' after note name + s.skip(','); // octave + s.skip(','); // onset time (in beats, integer part, bar:beat) + boolean isBt = s.matchString("0"); + s.skip(','); // onset time (in beats, fractional part) + s.skip(','); // duration (in beats, fraction) + try { + beat = s.getDouble(); + } catch (NumberFormatException e) { + System.err.println("Bad beat number on line " + lineCount); + beat = UNKNOWN; + } + if ((beat == Math.rint(beat)) != isBt) + System.err.println("Inconsistent beats on line "+lineCount); + s.skip(','); // onset time (in beats, decimal) + try { + duration = s.getDouble() - beat; + } catch (NumberFormatException e) { + System.err.println("Bad duration on line " + lineCount); + duration = UNKNOWN; + } + s.skip(','); // offset time (in beats, decimal) + s.skip('['); // additional info (e.g. melody/arpeggio/grace) + do { + element = s.getString(); + eventFlags |= flags.getFlag(element); + } while (s.matchString(",")); + s.skip('-'); + } else if (s.matchString("trill(")) { + eventFlags |= flags.getFlag("trill"); + s.skip('-'); + } else if (s.matchString("ornament(")) { + eventFlags |= flags.getFlag("ornament"); + s.skip('-'); + } else if (s.matchString("trailing_played_note-") || + s.matchString("hammer_bounce-") || + s.matchString("no_score_note-") || + s.matchString("insertion-")) { + eventFlags |= flags.getFlag("unscored"); + } else if (!s.matchString("%")) { // Prolog comment + throw new MatchFileParseException("error 4; line "+lineCount); + } + // READ 2nd term of Prolog expression + if (s.matchString("note(")) { + s.skip('['); // skip identifier + String note = s.getString(); + switch(Character.toUpperCase(note.charAt(0))) { + case 'A': pitch = 9; break; + case 'B': pitch = 11; break; + case 'C': pitch = 0; break; + case 'D': pitch = 2; break; + case 'E': pitch = 4; break; + case 'F': pitch = 5; break; + case 'G': pitch = 7; break; + default: throw new MatchFileParseException( + "Bad note on line " + lineCount); + } + s.skip(','); + String mod = s.getString(); + for (int i = 0; i < mod.length(); i++) { + switch (mod.charAt(i)) { + case '#': pitch++; break; + case 'b': pitch--; break; + case 'n': break; + default: throw new MatchFileParseException("error 5 " + + lineCount); + } + } + s.skip(','); + octave = s.getInt(); + pitch += 12 * octave; + s.skip(','); + onset = s.getInt(); + s.skip(','); + offset = s.getInt(); + if (versionNumber > 1.0) { + s.skip(','); + eOffset = s.getInt(); + } else + eOffset = offset; + s.skip(','); + velocity = s.getInt(); + onset /= clockUnits * 1000000.0 / clockRate; + offset /= clockUnits * 1000000.0 / clockRate; + eOffset /= clockUnits * 1000000.0 / clockRate; + if (timingCorrection) { + if (startNote) { + timingDisplacement = onset - timingDisplacement; + startNote = false; + } + onset -= timingDisplacement; + offset -= timingDisplacement; + eOffset -= timingDisplacement; + } + int m = flags.getFlag("s"); + if ((((eventFlags & m) != 0) && !noMelody) || + (((eventFlags & m) == 0) && !onlyMelody)) { + Event e = new Event(onset, offset, eOffset, pitch, velocity, + beat, duration, eventFlags); + list.add(e); + } + } else if (!s.matchString("no_played_note.") && + !s.matchString("trailing_score_note.") && + !s.matchString("deletion.") && + !s.matchString("%")) + throw new MatchFileParseException("error 6; line " + lineCount); + s.set(inputFile.readLine()); + lineCount++; + } + return list; + } // readMatchFile() + + public static void main(String[] args) throws Exception { // quick test + //System.out.println("Test"); + //readLabelFile(args[0]).writeLabelFile("tmp.txt"); + readLabelFile(args[0]).print(); + System.exit(0); + EventList el = readMatchFile(args[0]); + WormFile wf = new WormFile(null, el); + if (args.length >= 2) { + double sm = Double.parseDouble(args[1]); + wf.smooth(Worm.FULL_GAUSS, sm, sm, 0); + } else + wf.smooth(Worm.NONE, 0, 0, 0); + wf.write("worm.out"); + if (args.length == 3) + el.print(); + } // main() + +} // class EventList