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