annotate at/ofai/music/util/EventList.java @ 5:bcb4c9697967 tip

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