annotate at/ofai/music/worm/WormFile.java @ 2:4c3f5bc01c97

* Import BeatRoot v0.5.7
author Chris Cannam
date Fri, 08 Oct 2010 16:11:06 +0100
parents
children
rev   line source
Chris@2 1 /* Performance Worm: Visualisation of Expressive Musical Performance
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.worm;
Chris@2 22
Chris@2 23 import java.io.File;
Chris@2 24 import java.io.FileOutputStream;
Chris@2 25 import java.io.PrintStream;
Chris@2 26 import java.io.FileReader;
Chris@2 27 import java.io.BufferedReader;
Chris@2 28 import java.io.FileNotFoundException;
Chris@2 29 import java.io.IOException;
Chris@2 30 import java.util.StringTokenizer;
Chris@2 31 import java.util.Iterator;
Chris@2 32 import java.awt.Frame;
Chris@2 33 import at.ofai.music.util.Event;
Chris@2 34 import at.ofai.music.util.EventList;
Chris@2 35 import at.ofai.music.util.Format;
Chris@2 36 import at.ofai.music.util.MatchTempoMap;
Chris@2 37
Chris@2 38 // Read/write performance worm data
Chris@2 39 public class WormFile {
Chris@2 40
Chris@2 41 Worm worm;
Chris@2 42 double outFramePeriod, inFramePeriod;
Chris@2 43 int length;
Chris@2 44 double[] time;
Chris@2 45 double[] inTempo, outTempo;
Chris@2 46 double[] inIntensity, outIntensity;
Chris@2 47 int[] inFlags, outFlags;
Chris@2 48 String[] label;
Chris@2 49 public static final int
Chris@2 50 TRACK=1, BEAT=2, BAR=4, SEG1=8, SEG2=16, SEG3=32, SEG4=64;
Chris@2 51 public static final double defaultFramePeriod = 0.1; // 10 FPS
Chris@2 52 WormParameters info;
Chris@2 53
Chris@2 54 private WormFile(Frame f) {
Chris@2 55 info = new WormParameters(f);
Chris@2 56 inFramePeriod = defaultFramePeriod;
Chris@2 57 outFramePeriod = defaultFramePeriod;
Chris@2 58 } // shared constructor
Chris@2 59
Chris@2 60 public WormFile(int size) {
Chris@2 61 this(null);
Chris@2 62 length = size;
Chris@2 63 init();
Chris@2 64 } // constructor
Chris@2 65
Chris@2 66 public WormFile(int size, double step) {
Chris@2 67 this(size);
Chris@2 68 inFramePeriod = step;
Chris@2 69 } // constructor
Chris@2 70
Chris@2 71 public WormFile(EventList el, double step) {
Chris@2 72 this(null);
Chris@2 73 inFramePeriod = step;
Chris@2 74 convertList(el);
Chris@2 75 } // constructor
Chris@2 76
Chris@2 77 public WormFile(Worm w, EventList el) {
Chris@2 78 this(w == null? null: w.theFrame);
Chris@2 79 worm = w;
Chris@2 80 convertList(el);
Chris@2 81 } // constructor
Chris@2 82
Chris@2 83 public WormFile(Worm w, String fileName) {
Chris@2 84 this(w.theFrame);
Chris@2 85 worm = w;
Chris@2 86 read(fileName);
Chris@2 87 } // constructor
Chris@2 88
Chris@2 89 public void init() {
Chris@2 90 inTempo = new double[length];
Chris@2 91 inIntensity = new double[length];
Chris@2 92 inFlags = new int[length];
Chris@2 93 time = new double[length];
Chris@2 94 } // init()
Chris@2 95
Chris@2 96 public void smooth(int mode, double left, double right, int smoothLevel) {
Chris@2 97 if (worm != null)
Chris@2 98 worm.setSmoothMode(Worm.NONE);
Chris@2 99 info.smoothing = "None";
Chris@2 100 if ((outFramePeriod == 0) || ((inFramePeriod == 0) && (time == null))) {
Chris@2 101 System.err.println("Error: smooth() frameLength unspecified");
Chris@2 102 return;
Chris@2 103 }
Chris@2 104 if (inFramePeriod != 0) {
Chris@2 105 for (int i = 0; i < length; i++)
Chris@2 106 time[i] = inFramePeriod * i;
Chris@2 107 }
Chris@2 108 int outLength = 1+(int) Math.ceil(time[time.length-1] / outFramePeriod);
Chris@2 109 if ((outTempo == null) || (outTempo.length != outLength)) {
Chris@2 110 outTempo = new double[outLength];
Chris@2 111 outIntensity = new double[outLength];
Chris@2 112 outFlags = new int[outLength];
Chris@2 113 label = new String[outLength];
Chris@2 114 }
Chris@2 115 if (mode == Worm.NONE) {
Chris@2 116 int i = 0, o = 0;
Chris@2 117 while (o * outFramePeriod < time[0]) {
Chris@2 118 outTempo[o] = inTempo[0];
Chris@2 119 outIntensity[o] = inIntensity[0];
Chris@2 120 o++;
Chris@2 121 }
Chris@2 122 for ( ; i < time.length - 1; i++) {
Chris@2 123 while (o * outFramePeriod < time[i+1]) {
Chris@2 124 outTempo[o] = inTempo[i];
Chris@2 125 outIntensity[o] = inIntensity[i];
Chris@2 126 o++;
Chris@2 127 }
Chris@2 128 }
Chris@2 129 while (o < outLength) {
Chris@2 130 outTempo[o] = inTempo[i];
Chris@2 131 outIntensity[o] = inIntensity[i];
Chris@2 132 o++;
Chris@2 133 }
Chris@2 134 } else {
Chris@2 135 info.smoothing = "Gaussian" + "\t" + Format.d(left, 4) +
Chris@2 136 "\t" + Format.d(right, 4);
Chris@2 137 if (smoothLevel != 0) {
Chris@2 138 int count = 0;
Chris@2 139 double first = 0, last = 0;
Chris@2 140 for (int i = 0; i < time.length; i++)
Chris@2 141 if ((inFlags[i] & smoothLevel) != 0) {
Chris@2 142 if (count == 0)
Chris@2 143 first = time[i];
Chris@2 144 else
Chris@2 145 last = time[i];
Chris@2 146 count++;
Chris@2 147 }
Chris@2 148 if (count < 2)
Chris@2 149 System.err.println("Warning: Beat data not available");
Chris@2 150 else {
Chris@2 151 double IBI = (last - first) / (count - 1);
Chris@2 152 left *= IBI;
Chris@2 153 right *= IBI;
Chris@2 154 info.smoothing += "\t" +Format.d(IBI,4) + "\t" +smoothLevel;
Chris@2 155 System.out.println("Smoothing parameters (seconds): pre=" +
Chris@2 156 Format.d(left,3) + " post=" + Format.d(right,3));
Chris@2 157 }
Chris@2 158 }
Chris@2 159 int start = 0;
Chris@2 160 for (int o = 0; o < outLength; o++) {
Chris@2 161 double sum = 0, val = 0, tempo = 0, intensity = 0;
Chris@2 162 for (int i = start; i < time.length; i++) {
Chris@2 163 double d = o * outFramePeriod - time[i];
Chris@2 164 if (d > 4 * left) { // average over 4 stddevs
Chris@2 165 start++;
Chris@2 166 continue;
Chris@2 167 }
Chris@2 168 if (d < -4 * right)
Chris@2 169 break;
Chris@2 170 if (d < 0)
Chris@2 171 val = Math.exp(-d*d/(left*left*2));
Chris@2 172 else
Chris@2 173 val = Math.exp(-d*d/(right*right*2));
Chris@2 174 sum += val;
Chris@2 175 tempo += val * inTempo[i];
Chris@2 176 intensity += val * inIntensity[i];
Chris@2 177 }
Chris@2 178 if (sum == 0) { // assume this only occurs at beginning
Chris@2 179 outTempo[o] = inTempo[0];
Chris@2 180 outIntensity[o] = inIntensity[0];
Chris@2 181 } else {
Chris@2 182 outTempo[o] = tempo / sum;
Chris@2 183 outIntensity[o] = intensity / sum;
Chris@2 184 }
Chris@2 185 }
Chris@2 186 }
Chris@2 187 for (int i = 0; i < outFlags.length; i++)
Chris@2 188 outFlags[i] = 0;
Chris@2 189 for (int i = 0; i < inFlags.length; i++)
Chris@2 190 outFlags[(int)Math.round(time[i] / outFramePeriod)] |= inFlags[i];
Chris@2 191 int bar = 0;
Chris@2 192 int beat = 0;
Chris@2 193 int track = 0;
Chris@2 194 for (int i = 0; i < outFlags.length; i++) {
Chris@2 195 if ((outFlags[i] & BAR) != 0)
Chris@2 196 bar++;
Chris@2 197 if ((outFlags[i] & BEAT) != 0)
Chris@2 198 beat++;
Chris@2 199 if ((outFlags[i] & TRACK) != 0)
Chris@2 200 track++;
Chris@2 201 label[i] = bar + ":" + beat + ":" + track + ":" +
Chris@2 202 Format.d(i * outFramePeriod, 1);
Chris@2 203 }
Chris@2 204 } // smooth()
Chris@2 205
Chris@2 206 public void editParameters() {
Chris@2 207 info.editParameters();
Chris@2 208 update();
Chris@2 209 } // editParameters()
Chris@2 210
Chris@2 211 public void update() {
Chris@2 212 length = info.length;
Chris@2 213 inFramePeriod = info.framePeriod;
Chris@2 214 worm.setTitle(info.composer + ", " + info.piece +
Chris@2 215 ", played by " + info.performer);
Chris@2 216 // not used (?) : beatLevel trackLevel upbeat beatsPerBar
Chris@2 217 if ((inTempo == null) || (inTempo.length != length))
Chris@2 218 init();
Chris@2 219 worm.setInputFile(info.audioPath, info.audioFile);
Chris@2 220 worm.setSmoothMode(Worm.NONE);
Chris@2 221 if (info.axis.length() > 0)
Chris@2 222 worm.setAxis(info.axis);
Chris@2 223 worm.setFramePeriod(outFramePeriod);
Chris@2 224 worm.setLoudnessUnits(info.loudnessUnits);
Chris@2 225 } // update()
Chris@2 226
Chris@2 227 public void convertList(EventList el) {
Chris@2 228 double tMax = 0;
Chris@2 229 int count = 0;
Chris@2 230 for (Iterator<Event> i = el.iterator(); i.hasNext(); ) {
Chris@2 231 double pedalUpTime = i.next().pedalUp;
Chris@2 232 if (pedalUpTime > tMax)
Chris@2 233 tMax = pedalUpTime;
Chris@2 234 count++;
Chris@2 235 }
Chris@2 236 length = (int)Math.ceil(tMax / inFramePeriod);
Chris@2 237 init();
Chris@2 238 // double[] decayFactor = new double[128];
Chris@2 239 // for (int i = 0; i < 128; i++)
Chris@2 240 // decayFactor[i] = Math.max(5.0, (i - 6.0) / 3.0) * inFramePeriod;
Chris@2 241 // // was Math.pow(0.1, inFramePeriod); // modify for pitch?
Chris@2 242 for (Iterator<Event> i = el.l.iterator(); i.hasNext(); ) {
Chris@2 243 Event e = i.next();
Chris@2 244 double loudness = 30.29 * Math.pow(e.midiVelocity, 0.2609);
Chris@2 245 loudness += (e.midiPitch - 66.0) / 12.0; // +1dB / oct
Chris@2 246 int start = (int)Math.floor(e.keyDown / inFramePeriod);
Chris@2 247 if (start < 0)
Chris@2 248 start = 0;
Chris@2 249 int stop = (int)Math.ceil((e.pedalUp + 0.5) / inFramePeriod);
Chris@2 250 if (stop > inIntensity.length)
Chris@2 251 stop = inIntensity.length;
Chris@2 252 for (int t = start; t < stop; t++) {
Chris@2 253 if (loudness > inIntensity[t])
Chris@2 254 inIntensity[t] = loudness;
Chris@2 255 loudness -= Math.max(5.0, (e.midiPitch - 6.0) / 3.0) *
Chris@2 256 inFramePeriod;
Chris@2 257 // was: mult by decay factor. But since vals are dB, we subtract
Chris@2 258 }
Chris@2 259 }
Chris@2 260 MatchTempoMap tMap = new MatchTempoMap(count);
Chris@2 261 for (Iterator<Event> i = el.l.iterator(); i.hasNext(); ) {
Chris@2 262 Event e = i.next();
Chris@2 263 tMap.add(e.keyDown, e.scoreBeat);
Chris@2 264 }
Chris@2 265 // el.print();
Chris@2 266 // tMap.print(); // for debugging
Chris@2 267 tMap.dump(inTempo, inFramePeriod);
Chris@2 268 } // convertList()
Chris@2 269
Chris@2 270 public void write(String fileName) {
Chris@2 271 PrintStream out;
Chris@2 272 try {
Chris@2 273 out = new PrintStream(new FileOutputStream(fileName));
Chris@2 274 } catch (FileNotFoundException e) {
Chris@2 275 System.err.println("Unable to open output file " + fileName);
Chris@2 276 return;
Chris@2 277 }
Chris@2 278 info.write(out, outTempo.length, outFramePeriod);
Chris@2 279 for (int i = 0; i < outTempo.length; i++) {
Chris@2 280 if (outFramePeriod == 0)
Chris@2 281 out.print(Format.d(time[i],3) + " ");
Chris@2 282 out.println(Format.d(outTempo[i],4) +" "+
Chris@2 283 Format.d(outIntensity[i],4) +" "+outFlags[i]);
Chris@2 284 }
Chris@2 285 out.close();
Chris@2 286 } // write()
Chris@2 287
Chris@2 288 public void read(String fileName) {
Chris@2 289 try {
Chris@2 290 File f = new File(fileName);
Chris@2 291 if (!f.isFile()) // a local hack for UNC file names under Windows
Chris@2 292 f = new File("//fichte" + fileName);
Chris@2 293 if (!f.isFile())
Chris@2 294 throw(new FileNotFoundException("Could not open " + fileName));
Chris@2 295 BufferedReader in = new BufferedReader(new FileReader(f));
Chris@2 296 String input = info.read(in);
Chris@2 297 update();
Chris@2 298 int index = 0;
Chris@2 299 int bar = 0;
Chris@2 300 while ((input != null) && (index < length)) {
Chris@2 301 StringTokenizer tk = new StringTokenizer(input);
Chris@2 302 if (inFramePeriod != 0)
Chris@2 303 time[index] = Double.parseDouble(tk.nextToken());
Chris@2 304 inTempo[index] = Double.parseDouble(tk.nextToken());
Chris@2 305 inIntensity[index] = Double.parseDouble(tk.nextToken());
Chris@2 306 if (tk.hasMoreTokens())
Chris@2 307 inFlags[index] = Integer.parseInt(tk.nextToken());
Chris@2 308 else
Chris@2 309 inFlags[index] = 0;
Chris@2 310 input = in.readLine();
Chris@2 311 index++;
Chris@2 312 }
Chris@2 313 in.close();
Chris@2 314 smooth(Worm.NONE, 0, 0, 0);
Chris@2 315 } catch (FileNotFoundException e) {
Chris@2 316 System.err.println(e);
Chris@2 317 } catch (IOException e) {
Chris@2 318 System.err.println("IOException reading " + fileName);
Chris@2 319 } catch (Exception e) {
Chris@2 320 System.err.println("Error parsing file " + fileName + ": " + e);
Chris@2 321 e.printStackTrace();
Chris@2 322 }
Chris@2 323 } // read()
Chris@2 324
Chris@2 325 } // class WormFile