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