EventList.java (12854B)
1 /* 2 * _______ _____ _____ _____ 3 * |__ __| | __ \ / ____| __ \ 4 * | | __ _ _ __ ___ ___ ___| | | | (___ | |__) | 5 * | |/ _` | '__/ __|/ _ \/ __| | | |\___ \| ___/ 6 * | | (_| | | \__ \ (_) \__ \ |__| |____) | | 7 * |_|\__,_|_| |___/\___/|___/_____/|_____/|_| 8 * 9 * ------------------------------------------------------------- 10 * 11 * TarsosDSP is developed by Joren Six at IPEM, University Ghent 12 * 13 * ------------------------------------------------------------- 14 * 15 * Info: http://0110.be/tag/TarsosDSP 16 * Github: https://github.com/JorenSix/TarsosDSP 17 * Releases: http://0110.be/releases/TarsosDSP/ 18 * 19 * TarsosDSP includes modified source code by various authors, 20 * for credits and info, see README. 21 * 22 */ 23 24 /* 25 Copyright (C) 2001, 2006 by Simon Dixon 26 27 This program is free software; you can redistribute it and/or modify 28 it under the terms of the GNU General Public License as published by 29 the Free Software Foundation; either version 2 of the License, or 30 (at your option) any later version. 31 32 This program is distributed in the hope that it will be useful, 33 but WITHOUT ANY WARRANTY; without even the implied warranty of 34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 GNU General Public License for more details. 36 37 You should have received a copy of the GNU General Public License along 38 with this program (the file gpl.txt); if not, download it from 39 http://www.gnu.org/licenses/gpl.txt or write to the 40 Free Software Foundation, Inc., 41 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 42 */ 43 44 package be.tarsos.dsp.beatroot; 45 46 import java.io.FileInputStream; 47 import java.io.FileOutputStream; 48 import java.io.IOException; 49 import java.io.ObjectInputStream; 50 import java.io.ObjectOutputStream; 51 import java.util.Iterator; 52 import java.util.LinkedList; 53 import java.util.ListIterator; 54 55 56 57 // Adapted from eventList::readMatchFile in beatroot/src/eventMidi.cpp 58 59 // Reads in a Prolog score+performance (.match) file; returns it as an eventList 60 // Lines in the match file can be of the form: 61 // hammer_bounce-PlayedNote. 62 // info(Attribute, Value). 63 // insertion-PlayedNote. 64 // ornament(Anchor)-PlayedNote. 65 // ScoreNote-deletion. 66 // ScoreNote-PlayedNote. 67 // ScoreNote-trailing_score_note. 68 // trailing_played_note-PlayedNote. 69 // trill(Anchor)-PlayedNote. 70 // where ScoreNote is of the form 71 // snote(Anchor,[NoteName,Modifier],Octave,Bar:Beat,Offset,Duration, 72 // BeatNumber,DurationInBeats,ScoreAttributesList) 73 // e.g. snote(n1,[b,b],5,1:1,0,3/16,0,0.75,[s]) 74 // and PlayedNote is of the form 75 // note(Number,[NoteName,Modifier],Octave,Onset,Offset,AdjOffset,Velocity) 76 // e.g. note(1,[a,#],5,5054,6362,6768,53) 77 78 class WormFileParseException extends RuntimeException { 79 80 static final long serialVersionUID = 0; 81 public WormFileParseException(String s) { 82 super(s); 83 } // constructor 84 85 } // class WormFileParseException 86 87 class MatchFileParseException extends RuntimeException { 88 89 static final long serialVersionUID = 0; 90 public MatchFileParseException(String s) { 91 super(s); 92 } // constructor 93 94 } // class MatchFileParseException 95 96 class BTFileParseException extends RuntimeException { 97 98 static final long serialVersionUID = 0; 99 public BTFileParseException(String s) { 100 super(s); 101 } // constructor 102 103 } // class BTFileParseException 104 105 106 // Process the strings which label extra features of notes in match files. 107 // We assume no more than 32 distinct labels in a file. 108 class Flags { 109 110 String[] labels = new String[32]; 111 int size = 0; 112 113 int getFlag(String s) { 114 if ((s == null) || s.equals("")) 115 return 0; 116 //int val = 1; 117 for (int i = 0; i < size; i++) 118 if (s.equals(labels[i])) 119 return 1 << i; 120 if (size == 32) { 121 System.err.println("Overflow: Too many flags: " + s); 122 size--; 123 } 124 labels[size] = s; 125 return 1 << size++; 126 } // getFlag() 127 128 String getLabel(int i) { 129 if (i >= size) 130 return "ERROR: Unknown flag"; 131 return labels[i]; 132 } // getLabel() 133 134 } // class Flags 135 136 137 // A score/match/midi file is represented as an EventList object, 138 // which contains pointers to the head and tail links, and some 139 // class-wide parameters. Parameters are class-wide, as it is 140 // assumed that the Worm has only one input file at a time. 141 public class EventList { 142 143 public LinkedList<Event> l; 144 145 protected static boolean timingCorrection = false; 146 protected static double timingDisplacement = 0; 147 protected static int clockUnits = 480; 148 protected static int clockRate = 500000; 149 protected static double metricalLevel = 0; 150 public static final double UNKNOWN = Double.NaN; 151 protected static boolean noMelody = false; 152 protected static boolean onlyMelody = false; 153 protected static Flags flags = new Flags(); 154 155 public EventList() { 156 l = new LinkedList<Event>(); 157 } // constructor 158 159 public EventList(EventList e) { 160 this(); 161 ListIterator<Event> it = e.listIterator(); 162 while (it.hasNext()) 163 add(it.next()); 164 } // constructor 165 166 public EventList(Event[] e) { 167 this(); 168 for (int i=0; i < e.length; i++) 169 add(e[i]); 170 } // constructor 171 172 public void add(Event e) { 173 l.add(e); 174 } // add() 175 176 public void add(EventList ev) { 177 l.addAll(ev.l); 178 } // add() 179 180 public void insert(Event newEvent, boolean uniqueTimes) { 181 ListIterator<Event> li = l.listIterator(); 182 while (li.hasNext()) { 183 int sgn = newEvent.compareTo(li.next()); 184 if (sgn < 0) { 185 li.previous(); 186 break; 187 } else if (uniqueTimes && (sgn == 0)) { 188 li.remove(); 189 break; 190 } 191 } 192 li.add(newEvent); 193 } // insert() 194 195 public ListIterator<Event> listIterator() { 196 return l.listIterator(); 197 } // listIterator() 198 199 public Iterator<Event> iterator() { 200 return l.iterator(); 201 } // iterator() 202 203 public int size() { 204 return l.size(); 205 } // size() 206 207 public Event[] toArray() { 208 return toArray(0); 209 } // toArray() 210 211 public double[] toOnsetArray() { 212 double[] d = new double[l.size()]; 213 int i = 0; 214 for (Iterator<Event> it = l.iterator(); it.hasNext(); i++) 215 d[i] = it.next().keyDown; 216 return d; 217 } // toOnsetArray() 218 219 public Event[] toArray(int match) { 220 int count = 0; 221 for (Event e : l) 222 if ((match == 0) || (e.midiCommand == match)) 223 count++; 224 Event[] a = new Event[count]; 225 int i = 0; 226 for (Event e : l) 227 if ((match == 0) || (e.midiCommand == match)) 228 a[i++] = e; 229 return a; 230 } // toArray() 231 232 public void writeBinary(String fileName) { 233 try { 234 ObjectOutputStream oos = new ObjectOutputStream( 235 new FileOutputStream(fileName)); 236 oos.writeObject(this); 237 oos.close(); 238 } catch (IOException e) { 239 System.err.println(e); 240 } 241 } // writeBinary() 242 243 public static EventList readBinary(String fileName) { 244 try { 245 ObjectInputStream ois = new ObjectInputStream( 246 new FileInputStream(fileName)); 247 EventList e = (EventList) ois.readObject(); 248 ois.close(); 249 return e; 250 } catch (IOException e) { 251 System.err.println(e); 252 return null; 253 } catch (ClassNotFoundException e) { 254 System.err.println(e); 255 return null; 256 } 257 } // readBinary() 258 259 /* 260 public void writeMIDI(String fileName) { 261 writeMIDI(fileName, null); 262 } // writeMIDI() 263 264 public void writeMIDI(String fileName, EventList pedal) { 265 try { 266 MidiSystem.write(toMIDI(pedal), 1, new File(fileName)); 267 } catch (Exception e) { 268 System.err.println("Error: Unable to write MIDI file " + fileName); 269 e.printStackTrace(); 270 } 271 } // writeMIDI() 272 273 public Sequence toMIDI(EventList pedal) throws InvalidMidiDataException { 274 final int midiTempo = 1000000; 275 Sequence s = new Sequence(Sequence.PPQ, 1000); 276 Track[] tr = new Track[16]; 277 tr[0] = s.createTrack(); 278 MetaMessage mm = new MetaMessage(); 279 byte[] b = new byte[3]; 280 b[0] = (byte)((midiTempo >> 16) & 0xFF); 281 b[1] = (byte)((midiTempo >> 8) & 0xFF); 282 b[2] = (byte)(midiTempo & 0xFF); 283 mm.setMessage(0x51, b, 3); 284 tr[0].add(new MidiEvent(mm, 0L)); 285 for (Event e : l) { // from match or beatTrack file 286 if (e.midiCommand == 0) // skip beatTrack file 287 break; 288 if (tr[e.midiTrack] == null) 289 tr[e.midiTrack] = s.createTrack(); 290 //switch (e.midiCommand) 291 //case ShortMessage.NOTE_ON: 292 //case ShortMessage.POLY_PRESSURE: 293 //case ShortMessage.CONTROL_CHANGE: 294 //case ShortMessage.PROGRAM_CHANGE: 295 //case ShortMessage.CHANNEL_PRESSURE: 296 //case ShortMessage.PITCH_BEND: 297 ShortMessage sm = new ShortMessage(); 298 sm.setMessage(e.midiCommand, e.midiChannel, 299 e.midiPitch, e.midiVelocity); 300 tr[e.midiTrack].add(new MidiEvent(sm, 301 (long)Math.round(1000 * e.keyDown))); 302 if (e.midiCommand == ShortMessage.NOTE_ON) { 303 sm = new ShortMessage(); 304 sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, e.midiPitch, 0); 305 tr[e.midiTrack].add(new MidiEvent(sm, (long)Math.round(1000 * e.keyUp))); 306 } 307 } 308 if (pedal != null) { // from MIDI file 309 // if (t.size() > 0) // otherwise beatTrack files leave an empty trk 310 // t = s.createTrack(); 311 for (Event e : pedal.l) { 312 if (tr[e.midiTrack] == null) 313 tr[e.midiTrack] = s.createTrack(); 314 ShortMessage sm = new ShortMessage(); 315 sm.setMessage(e.midiCommand, e.midiChannel, 316 e.midiPitch, e.midiVelocity); 317 tr[e.midiTrack].add(new MidiEvent(sm, 318 (long)Math.round(1000 * e.keyDown))); 319 if (e.midiCommand == ShortMessage.NOTE_ON) { 320 sm = new ShortMessage(); 321 sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, 322 e.midiPitch,e.midiVelocity); 323 tr[e.midiTrack].add(new MidiEvent(sm, 324 (long)Math.round(1000 * e.keyUp))); 325 } 326 //catch (InvalidMidiDataException exception) {} 327 } 328 } 329 return s; 330 } // toMIDI() 331 332 public static EventList readMidiFile(String fileName) { 333 return readMidiFile(fileName, 0); 334 } // readMidiFile() 335 336 public static EventList readMidiFile(String fileName, int skipTrackFlag) { 337 EventList list = new EventList(); 338 Sequence s; 339 try { 340 s = MidiSystem.getSequence(new File(fileName)); 341 } catch (Exception e) { 342 e.printStackTrace(); 343 return list; 344 } 345 double midiTempo = 500000; 346 double tempoFactor = midiTempo / s.getResolution() / 1000000.0; 347 // System.err.println(tempoFactor); 348 Event[][] noteOns = new Event[128][16]; 349 Track[] tracks = s.getTracks(); 350 for (int t = 0; t < tracks.length; t++, skipTrackFlag >>= 1) { 351 if ((skipTrackFlag & 1) == 1) 352 continue; 353 for (int e = 0; e < tracks[t].size(); e++) { 354 MidiEvent me = tracks[t].get(e); 355 MidiMessage mm = me.getMessage(); 356 double time = me.getTick() * tempoFactor; 357 byte[] mesg = mm.getMessage(); 358 int channel = mesg[0] & 0x0F; 359 int command = mesg[0] & 0xF0; 360 if (command == ShortMessage.NOTE_ON) { 361 int pitch = mesg[1] & 0x7F; 362 int velocity = mesg[2] & 0x7F; 363 if (noteOns[pitch][channel] != null) { 364 if (velocity == 0) { // NOTE_OFF in disguise :( 365 noteOns[pitch][channel].keyUp = time; 366 noteOns[pitch][channel].pedalUp = time; 367 noteOns[pitch][channel] = null; 368 } else 369 System.err.println("Double note on: n=" + pitch + 370 " c=" + channel + 371 " t1=" + noteOns[pitch][channel] + 372 " t2=" + time); 373 } else { 374 Event n = new Event(time, 0, 0, pitch, velocity, -1, -1, 375 0, ShortMessage.NOTE_ON, channel, t); 376 noteOns[pitch][channel] = n; 377 list.add(n); 378 } 379 } else if (command == ShortMessage.NOTE_OFF) { 380 int pitch = mesg[1] & 0x7F; 381 noteOns[pitch][channel].keyUp = time; 382 noteOns[pitch][channel].pedalUp = time; 383 noteOns[pitch][channel] = null; 384 } else if (command == 0xF0) { 385 if ((channel == 0x0F) && (mesg[1] == 0x51)) { 386 midiTempo = (mesg[5] & 0xFF) | 387 ((mesg[4] & 0xFF) << 8) | 388 ((mesg[3] & 0xFF) << 16); 389 tempoFactor = midiTempo / s.getResolution() / 1000000.0; 390 // System.err.println("Info: Tempo change: " + midiTempo + 391 // " tf=" + tempoFactor); 392 } 393 } else if (mesg.length > 3) { 394 System.err.println("midi message too long: " + mesg.length); 395 System.err.println("\tFirst byte: " + mesg[0]); 396 } else { 397 int b0 = mesg[0] & 0xFF; 398 int b1 = -1; 399 int b2 = -1; 400 if (mesg.length > 1) 401 b1 = mesg[1] & 0xFF; 402 if (mesg.length > 2) 403 b2 = mesg[2] & 0xFF; 404 list.add(new Event(time, time, -1, b1, b2, -1, -1, 0, 405 b0 & 0xF0, b0 & 0x0F, t)); 406 } 407 } 408 } 409 for (int pitch = 0; pitch < 128; pitch++) 410 for (int channel = 0; channel < 16; channel++) 411 if (noteOns[pitch][channel] != null) 412 System.err.println("Missing note off: n=" + 413 noteOns[pitch][channel].midiPitch + " t=" + 414 noteOns[pitch][channel].keyDown); 415 return list; 416 } // readMidiFile() 417 */ 418 public void print() { 419 for (Iterator<Event> i = l.iterator(); i.hasNext(); ) 420 i.next().print(flags); 421 } // print() 422 423 public static void setTimingCorrection(double corr) { 424 timingCorrection = corr >= 0; 425 timingDisplacement = corr; 426 } // setTimingCorrection() 427 428 429 430 431 432 } // class EventList