Mercurial > hg > beatroot
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormFile.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,325 @@ +/* Performance Worm: Visualisation of Expressive Musical Performance + 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.worm; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.io.FileReader; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.StringTokenizer; +import java.util.Iterator; +import java.awt.Frame; +import at.ofai.music.util.Event; +import at.ofai.music.util.EventList; +import at.ofai.music.util.Format; +import at.ofai.music.util.MatchTempoMap; + +// Read/write performance worm data +public class WormFile { + + Worm worm; + double outFramePeriod, inFramePeriod; + int length; + double[] time; + double[] inTempo, outTempo; + double[] inIntensity, outIntensity; + int[] inFlags, outFlags; + String[] label; + public static final int + TRACK=1, BEAT=2, BAR=4, SEG1=8, SEG2=16, SEG3=32, SEG4=64; + public static final double defaultFramePeriod = 0.1; // 10 FPS + WormParameters info; + + private WormFile(Frame f) { + info = new WormParameters(f); + inFramePeriod = defaultFramePeriod; + outFramePeriod = defaultFramePeriod; + } // shared constructor + + public WormFile(int size) { + this(null); + length = size; + init(); + } // constructor + + public WormFile(int size, double step) { + this(size); + inFramePeriod = step; + } // constructor + + public WormFile(EventList el, double step) { + this(null); + inFramePeriod = step; + convertList(el); + } // constructor + + public WormFile(Worm w, EventList el) { + this(w == null? null: w.theFrame); + worm = w; + convertList(el); + } // constructor + + public WormFile(Worm w, String fileName) { + this(w.theFrame); + worm = w; + read(fileName); + } // constructor + + public void init() { + inTempo = new double[length]; + inIntensity = new double[length]; + inFlags = new int[length]; + time = new double[length]; + } // init() + + public void smooth(int mode, double left, double right, int smoothLevel) { + if (worm != null) + worm.setSmoothMode(Worm.NONE); + info.smoothing = "None"; + if ((outFramePeriod == 0) || ((inFramePeriod == 0) && (time == null))) { + System.err.println("Error: smooth() frameLength unspecified"); + return; + } + if (inFramePeriod != 0) { + for (int i = 0; i < length; i++) + time[i] = inFramePeriod * i; + } + int outLength = 1+(int) Math.ceil(time[time.length-1] / outFramePeriod); + if ((outTempo == null) || (outTempo.length != outLength)) { + outTempo = new double[outLength]; + outIntensity = new double[outLength]; + outFlags = new int[outLength]; + label = new String[outLength]; + } + if (mode == Worm.NONE) { + int i = 0, o = 0; + while (o * outFramePeriod < time[0]) { + outTempo[o] = inTempo[0]; + outIntensity[o] = inIntensity[0]; + o++; + } + for ( ; i < time.length - 1; i++) { + while (o * outFramePeriod < time[i+1]) { + outTempo[o] = inTempo[i]; + outIntensity[o] = inIntensity[i]; + o++; + } + } + while (o < outLength) { + outTempo[o] = inTempo[i]; + outIntensity[o] = inIntensity[i]; + o++; + } + } else { + info.smoothing = "Gaussian" + "\t" + Format.d(left, 4) + + "\t" + Format.d(right, 4); + if (smoothLevel != 0) { + int count = 0; + double first = 0, last = 0; + for (int i = 0; i < time.length; i++) + if ((inFlags[i] & smoothLevel) != 0) { + if (count == 0) + first = time[i]; + else + last = time[i]; + count++; + } + if (count < 2) + System.err.println("Warning: Beat data not available"); + else { + double IBI = (last - first) / (count - 1); + left *= IBI; + right *= IBI; + info.smoothing += "\t" +Format.d(IBI,4) + "\t" +smoothLevel; + System.out.println("Smoothing parameters (seconds): pre=" + + Format.d(left,3) + " post=" + Format.d(right,3)); + } + } + int start = 0; + for (int o = 0; o < outLength; o++) { + double sum = 0, val = 0, tempo = 0, intensity = 0; + for (int i = start; i < time.length; i++) { + double d = o * outFramePeriod - time[i]; + if (d > 4 * left) { // average over 4 stddevs + start++; + continue; + } + if (d < -4 * right) + break; + if (d < 0) + val = Math.exp(-d*d/(left*left*2)); + else + val = Math.exp(-d*d/(right*right*2)); + sum += val; + tempo += val * inTempo[i]; + intensity += val * inIntensity[i]; + } + if (sum == 0) { // assume this only occurs at beginning + outTempo[o] = inTempo[0]; + outIntensity[o] = inIntensity[0]; + } else { + outTempo[o] = tempo / sum; + outIntensity[o] = intensity / sum; + } + } + } + for (int i = 0; i < outFlags.length; i++) + outFlags[i] = 0; + for (int i = 0; i < inFlags.length; i++) + outFlags[(int)Math.round(time[i] / outFramePeriod)] |= inFlags[i]; + int bar = 0; + int beat = 0; + int track = 0; + for (int i = 0; i < outFlags.length; i++) { + if ((outFlags[i] & BAR) != 0) + bar++; + if ((outFlags[i] & BEAT) != 0) + beat++; + if ((outFlags[i] & TRACK) != 0) + track++; + label[i] = bar + ":" + beat + ":" + track + ":" + + Format.d(i * outFramePeriod, 1); + } + } // smooth() + + public void editParameters() { + info.editParameters(); + update(); + } // editParameters() + + public void update() { + length = info.length; + inFramePeriod = info.framePeriod; + worm.setTitle(info.composer + ", " + info.piece + + ", played by " + info.performer); + // not used (?) : beatLevel trackLevel upbeat beatsPerBar + if ((inTempo == null) || (inTempo.length != length)) + init(); + worm.setInputFile(info.audioPath, info.audioFile); + worm.setSmoothMode(Worm.NONE); + if (info.axis.length() > 0) + worm.setAxis(info.axis); + worm.setFramePeriod(outFramePeriod); + worm.setLoudnessUnits(info.loudnessUnits); + } // update() + + public void convertList(EventList el) { + double tMax = 0; + int count = 0; + for (Iterator<Event> i = el.iterator(); i.hasNext(); ) { + double pedalUpTime = i.next().pedalUp; + if (pedalUpTime > tMax) + tMax = pedalUpTime; + count++; + } + length = (int)Math.ceil(tMax / inFramePeriod); + init(); + // double[] decayFactor = new double[128]; + // for (int i = 0; i < 128; i++) + // decayFactor[i] = Math.max(5.0, (i - 6.0) / 3.0) * inFramePeriod; + // // was Math.pow(0.1, inFramePeriod); // modify for pitch? + for (Iterator<Event> i = el.l.iterator(); i.hasNext(); ) { + Event e = i.next(); + double loudness = 30.29 * Math.pow(e.midiVelocity, 0.2609); + loudness += (e.midiPitch - 66.0) / 12.0; // +1dB / oct + int start = (int)Math.floor(e.keyDown / inFramePeriod); + if (start < 0) + start = 0; + int stop = (int)Math.ceil((e.pedalUp + 0.5) / inFramePeriod); + if (stop > inIntensity.length) + stop = inIntensity.length; + for (int t = start; t < stop; t++) { + if (loudness > inIntensity[t]) + inIntensity[t] = loudness; + loudness -= Math.max(5.0, (e.midiPitch - 6.0) / 3.0) * + inFramePeriod; + // was: mult by decay factor. But since vals are dB, we subtract + } + } + MatchTempoMap tMap = new MatchTempoMap(count); + for (Iterator<Event> i = el.l.iterator(); i.hasNext(); ) { + Event e = i.next(); + tMap.add(e.keyDown, e.scoreBeat); + } + // el.print(); + // tMap.print(); // for debugging + tMap.dump(inTempo, inFramePeriod); + } // convertList() + + public void write(String fileName) { + PrintStream out; + try { + out = new PrintStream(new FileOutputStream(fileName)); + } catch (FileNotFoundException e) { + System.err.println("Unable to open output file " + fileName); + return; + } + info.write(out, outTempo.length, outFramePeriod); + for (int i = 0; i < outTempo.length; i++) { + if (outFramePeriod == 0) + out.print(Format.d(time[i],3) + " "); + out.println(Format.d(outTempo[i],4) +" "+ + Format.d(outIntensity[i],4) +" "+outFlags[i]); + } + out.close(); + } // write() + + public void read(String fileName) { + try { + File f = new File(fileName); + if (!f.isFile()) // a local hack for UNC file names under Windows + f = new File("//fichte" + fileName); + if (!f.isFile()) + throw(new FileNotFoundException("Could not open " + fileName)); + BufferedReader in = new BufferedReader(new FileReader(f)); + String input = info.read(in); + update(); + int index = 0; + int bar = 0; + while ((input != null) && (index < length)) { + StringTokenizer tk = new StringTokenizer(input); + if (inFramePeriod != 0) + time[index] = Double.parseDouble(tk.nextToken()); + inTempo[index] = Double.parseDouble(tk.nextToken()); + inIntensity[index] = Double.parseDouble(tk.nextToken()); + if (tk.hasMoreTokens()) + inFlags[index] = Integer.parseInt(tk.nextToken()); + else + inFlags[index] = 0; + input = in.readLine(); + index++; + } + in.close(); + smooth(Worm.NONE, 0, 0, 0); + } catch (FileNotFoundException e) { + System.err.println(e); + } catch (IOException e) { + System.err.println("IOException reading " + fileName); + } catch (Exception e) { + System.err.println("Error parsing file " + fileName + ": " + e); + e.printStackTrace(); + } + } // read() + +} // class WormFile