Mercurial > hg > beatroot
changeset 2:4c3f5bc01c97
* Import BeatRoot v0.5.7
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/audio/Convert.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,50 @@ +/* + 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.audio; + +public class Convert { + + public static void monoShortToInt(byte[] in, int[] out,boolean isBigEndian){ + monoShortToInt(in, 0, in.length, out, 0, isBigEndian); + } // monoShortToInt()/3 + + public static void monoShortToInt(byte[] in, int inIndex, int bytes, + int[] out, int outIndex, boolean isBigEndian){ + if (isBigEndian) + for ( ; inIndex < bytes; inIndex += 2) + out[outIndex++] = (in[inIndex+1] & 0xff) | (in[inIndex] << 8); + else + for ( ; inIndex < bytes; inIndex += 2) + out[outIndex++] = (in[inIndex] & 0xff) | (in[inIndex+1] << 8); + } // monoShortToInt() + + public static void monoShortToDouble(byte[] in, double[] out, + boolean isBigEndian) { + int j = 0; + if (isBigEndian) + for (int i = 0; i < in.length; i += 2) + out[j++] = ((in[i+1] & 0xff) | (in[i] << 8)) / 32768.0; + else + for (int i = 0; i < in.length; i += 2) + out[j++] = ((in[i] & 0xff) | (in[i+1] << 8)) / 32768.0; + } // monoShortToDouble() + +} // class Convert
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/audio/FFT.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,372 @@ +/* + 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.audio; + +/** Class for computing a windowed fast Fourier transform. + * Implements some of the window functions for the STFT from + * Harris (1978), Proc. IEEE, 66, 1, 51-83. + */ +public class FFT { + + /** used in {@link FFT#fft(double[], double[], int)} to specify + * a forward Fourier transform */ + public static final int FORWARD = -1; + /** used in {@link FFT#fft(double[], double[], int)} to specify + * an inverse Fourier transform */ + public static final int REVERSE = 1; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * rectangular window function */ + public static final int RECT = 0; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * Hamming window function */ + public static final int HAMMING = 1; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * 61-dB 3-sample Blackman-Harris window function */ + public static final int BH3 = 2; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * 74-dB 4-sample Blackman-Harris window function */ + public static final int BH4 = 3; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * minimum 3-sample Blackman-Harris window function */ + public static final int BH3MIN = 4; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * minimum 4-sample Blackman-Harris window function */ + public static final int BH4MIN = 5; + /** used in {@link FFT#makeWindow(int,int,int)} to specify a + * Gaussian window function */ + public static final int GAUSS = 6; + static final double twoPI = 2 * Math.PI; + + /** The FFT method. Calculation is inline, for complex data stored + * in 2 separate arrays. Length of input data must be a power of two. + * @param re the real part of the complex input and output data + * @param im the imaginary part of the complex input and output data + * @param direction the direction of the Fourier transform (FORWARD or + * REVERSE) + * @throws IllegalArgumentException if the length of the input data is + * not a power of 2 + */ + public static void fft(double re[], double im[], int direction) { + int n = re.length; + int bits = (int)Math.rint(Math.log(n) / Math.log(2)); + if (n != (1 << bits)) + throw new IllegalArgumentException("FFT data must be power of 2"); + int localN; + int j = 0; + for (int i = 0; i < n-1; i++) { + if (i < j) { + double temp = re[j]; + re[j] = re[i]; + re[i] = temp; + temp = im[j]; + im[j] = im[i]; + im[i] = temp; + } + int k = n / 2; + while ((k >= 1) && (k - 1 < j)) { + j = j - k; + k = k / 2; + } + j = j + k; + } + for(int m = 1; m <= bits; m++) { + localN = 1 << m; + double Wjk_r = 1; + double Wjk_i = 0; + double theta = twoPI / localN; + double Wj_r = Math.cos(theta); + double Wj_i = direction * Math.sin(theta); + int nby2 = localN / 2; + for (j = 0; j < nby2; j++) { + for (int k = j; k < n; k += localN) { + int id = k + nby2; + double tempr = Wjk_r * re[id] - Wjk_i * im[id]; + double tempi = Wjk_r * im[id] + Wjk_i * re[id]; + re[id] = re[k] - tempr; + im[id] = im[k] - tempi; + re[k] += tempr; + im[k] += tempi; + } + double wtemp = Wjk_r; + Wjk_r = Wj_r * Wjk_r - Wj_i * Wjk_i; + Wjk_i = Wj_r * Wjk_i + Wj_i * wtemp; + } + } + } // fft() + + /** Computes the power spectrum of a real sequence (in place). + * @param re the real input and output data; length must be a power of 2 + */ + public static void powerFFT(double[] re) { + double[] im = new double[re.length]; + fft(re, im, FORWARD); + for (int i = 0; i < re.length; i++) + re[i] = re[i] * re[i] + im[i] * im[i]; + } // powerFFT() + + /** Converts a real power sequence from to magnitude representation, + * by computing the square root of each value. + * @param re the real input (power) and output (magnitude) data; length + * must be a power of 2 + */ + public static void toMagnitude(double[] re) { + for (int i = 0; i < re.length; i++) + re[i] = Math.sqrt(re[i]); + } // toMagnitude() + + /** Computes the magnitude spectrum of a real sequence (in place). + * @param re the real input and output data; length must be a power of 2 + */ + public static void magnitudeFFT(double[] re) { + powerFFT(re); + toMagnitude(re); + } // magnitudeFFT() + + /** Computes a complex (or real if im[] == {0,...}) FFT and converts + * the results to polar coordinates (power and phase). Both arrays + * must be the same length, which is a power of 2. + * @param re the real part of the input data and the power of the output + * data + * @param im the imaginary part of the input data and the phase of the + * output data + */ + public static void powerPhaseFFT(double[] re, double[] im) { + fft(re, im, FORWARD); + for (int i = 0; i < re.length; i++) { + double pow = re[i] * re[i] + im[i] * im[i]; + im[i] = Math.atan2(im[i], re[i]); + re[i] = pow; + } + } // powerPhaseFFT() + + /** Inline computation of the inverse FFT given spectral input data + * in polar coordinates (power and phase). + * Both arrays must be the same length, which is a power of 2. + * @param pow the power of the spectral input data (and real part of the + * output data) + * @param ph the phase of the spectral input data (and the imaginary part + * of the output data) + */ + public static void powerPhaseIFFT(double[] pow, double[] ph) { + toMagnitude(pow); + for (int i = 0; i < pow.length; i++) { + double re = pow[i] * Math.cos(ph[i]); + ph[i] = pow[i] * Math.sin(ph[i]); + pow[i] = re; + } + fft(pow, ph, REVERSE); + } // powerPhaseIFFT() + + /** Computes a complex (or real if im[] == {0,...}) FFT and converts + * the results to polar coordinates (magnitude and phase). Both arrays + * must be the same length, which is a power of 2. + * @param re the real part of the input data and the magnitude of the + * output data + * @param im the imaginary part of the input data and the phase of the + * output data + */ + public static void magnitudePhaseFFT(double[] re, double[] im) { + powerPhaseFFT(re, im); + toMagnitude(re); + } // magnitudePhaseFFT() + + + /** Fill an array with the values of a standard Hamming window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void hamming(double[] data, int size) { + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + double scale = 1.0 / (double)size / 0.54; + double factor = twoPI / (double)size; + for (int i = 0; start < stop; start++, i++) + data[i] = scale * (25.0/46.0 - 21.0/46.0 * Math.cos(factor * i)); + } // hamming() + + /** Fill an array with the values of a minimum 4-sample Blackman-Harris + * window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void blackmanHarris4sMin(double[] data, int size) { + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + double scale = 1.0 / (double)size / 0.36; + for (int i = 0; start < stop; start++, i++) + data[i] = scale * ( 0.35875 - + 0.48829 * Math.cos(twoPI * i / size) + + 0.14128 * Math.cos(2 * twoPI * i / size) - + 0.01168 * Math.cos(3 * twoPI * i / size)); + } // blackmanHarris4sMin() + + /** Fill an array with the values of a 74-dB 4-sample Blackman-Harris + * window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void blackmanHarris4s(double[] data, int size) { + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + double scale = 1.0 / (double)size / 0.4; + for (int i = 0; start < stop; start++, i++) + data[i] = scale * ( 0.40217 - + 0.49703 * Math.cos(twoPI * i / size) + + 0.09392 * Math.cos(2 * twoPI * i / size) - + 0.00183 * Math.cos(3 * twoPI * i / size)); + } // blackmanHarris4s() + + /** Fill an array with the values of a minimum 3-sample Blackman-Harris + * window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void blackmanHarris3sMin(double[] data, int size) { + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + double scale = 1.0 / (double) size / 0.42; + for (int i = 0; start < stop; start++, i++) + data[i] = scale * ( 0.42323 - + 0.49755 * Math.cos(twoPI * i / size) + + 0.07922 * Math.cos(2 * twoPI * i / size)); + } // blackmanHarris3sMin() + + /** Fill an array with the values of a 61-dB 3-sample Blackman-Harris + * window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void blackmanHarris3s(double[] data, int size) { + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + double scale = 1.0 / (double) size / 0.45; + for (int i = 0; start < stop; start++, i++) + data[i] = scale * ( 0.44959 - + 0.49364 * Math.cos(twoPI * i / size) + + 0.05677 * Math.cos(2 * twoPI * i / size)); + } // blackmanHarris3s() + + /** Fill an array with the values of a Gaussian window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void gauss(double[] data, int size) { // ?? between 61/3 and 74/4 BHW + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + double delta = 5.0 / size; + double x = (1 - size) / 2.0 * delta; + double c = -Math.PI * Math.exp(1.0) / 10.0; + double sum = 0; + for (int i = start; i < stop; i++) { + data[i] = Math.exp(c * x * x); + x += delta; + sum += data[i]; + } + for (int i = start; i < stop; i++) + data[i] /= sum; + } // gauss() + + /** Fill an array with the values of a rectangular window function + * @param data the array to be filled + * @param size the number of non zero values; if the array is larger than + * this, it is zero-padded symmetrically at both ends + */ + static void rectangle(double[] data, int size) { + int start = (data.length - size) / 2; + int stop = (data.length + size) / 2; + for (int i = start; i < stop; i++) + data[i] = 1.0 / (double) size; + } // rectangle() + + /** Returns an array of values of a normalised smooth window function, + * as used for performing a short time Fourier transform (STFT). + * All functions are normalised by length and coherent gain. + * More information on characteristics of these functions can be found + * in F.J. Harris (1978), On the Use of Windows for Harmonic Analysis + * with the Discrete Fourier Transform, <em>Proceedings of the IEEE</em>, + * 66, 1, 51-83. + * @param choice the choice of window function, one of the constants + * defined above + * @param size the size of the returned array + * @param support the number of non-zero values in the array + * @return the array containing the values of the window function + */ + public static double[] makeWindow(int choice, int size, int support) { + double[] data = new double[size]; + if (support > size) + support = size; + switch (choice) { + case RECT: rectangle(data, support); break; + case HAMMING: hamming(data, support); break; + case BH3: blackmanHarris3s(data, support); break; + case BH4: blackmanHarris4s(data, support); break; + case BH3MIN: blackmanHarris3sMin(data, support); break; + case BH4MIN: blackmanHarris4sMin(data, support); break; + case GAUSS: gauss(data, support); break; + default: rectangle(data, support); break; + } + return data; + } // makeWindow() + + /** Applies a window function to an array of data, storing the result in + * the data array. + * Performs a dot product of the data and window arrays. + * @param data the array of input data, also used for output + * @param window the values of the window function to be applied to data + */ + public static void applyWindow(double[] data, double[] window) { + for (int i = 0; i < data.length; i++) + data[i] *= window[i]; + } // applyWindow() + + /** Unit test of the FFT class. + * Performs a forward and inverse FFT on a 1MB array of random values + * and checks how closely the values are preserved. + * @param args ignored + */ + public static void main(String[] args) { + final int SZ = 1024 * 1024; + double[] r1 = new double[SZ]; + double[] i1 = new double[SZ]; + double[] r2 = new double[SZ]; + double[] i2 = new double[SZ]; + for (int j = 0; j < SZ; j++) { + r1[j] = r2[j] = Math.random(); + i1[j] = i2[j] = Math.random(); + } + System.out.println("start"); + fft(r2, i2, FORWARD); + System.out.println("reverse"); + fft(r2, i2, REVERSE); + System.out.println("result"); + double err = 0; + for (int j = 0; j < SZ; j++) + err += Math.abs(r1[j] - r2[j] / SZ) + Math.abs(i1[j] - i2[j] / SZ); + System.out.printf( "Err: %12.10f Av: %12.10f\n", err, err / SZ); + } // main() + +} // class FFT
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/audio/Util.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,56 @@ +/* + 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.audio; + +public class Util { + + public static double rms(double[] d) { + double sum = 0; + for (int i=0; i < d.length; i++) + sum += d[i] * d[i]; + return Math.sqrt(sum / d.length); + } + + public static double min(double[] d) { + double min = d[0]; + for (int i=1; i < d.length; i++) + if (d[i] < min) + min = d[i]; + return min; + } + + public static double max(double[] d) { + double max = d[0]; + for (int i=1; i < d.length; i++) + if (d[i] > max) + max = d[i]; + return max; + } + + public static double threshold(double value, double min, double max) { + if (value < min) + return min; + if (value > max) + return max; + return value; + } + +} // class Util
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/audio/WavWrite.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,79 @@ +/* + 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.audio; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +public class WavWrite { + + public static void toByte(byte[] out, String data, int offset) { + try { + byte[] b = data.getBytes("US-ASCII"); + for (int i = 0; i < b.length; i++) + out[offset++] = b[i]; + } catch (UnsupportedEncodingException e) { + System.err.println(e); + } + } // toByte() + + public static void toByte(byte[] out, long data,int offset,int len){ + for (int stop = offset + len; offset < stop; offset++) { + out[offset] = (byte)data; + data >>= 8; + } + } // toByte() + + /** Opens a file output stream and writes a WAV file header to it */ + public static FileOutputStream open(String fileName, int byteLength, + int channels, int rate, int audioSize) { + FileOutputStream out; + try { + out = new FileOutputStream(new File(fileName)); + byte[] wavHeader = new byte[44]; + toByte(wavHeader, "RIFF", 0); + toByte(wavHeader, byteLength+36, 4, 4); + toByte(wavHeader, "WAVEfmt ", 8); + toByte(wavHeader, 16, 16, 4); // chunk length + toByte(wavHeader, 1, 20, 2); // PCM encoding + toByte(wavHeader, channels, 22, 2); // channels + toByte(wavHeader, rate, 24, 4); // sampling rate + toByte(wavHeader, audioSize * channels * rate, 28, 4);// bytes per s + toByte(wavHeader, audioSize * channels, 32, 2); // block alignment + toByte(wavHeader, 8 * audioSize, 34, 2); // bits per sample + toByte(wavHeader, "data", 36); + toByte(wavHeader, byteLength, 40, 4); + out.write(wavHeader); + } catch (FileNotFoundException e) { + System.err.println("WavWrite: Error opening output file: "+ + fileName + "\n" + e); + return null; + } catch (IOException e) { + System.err.println("Error writing output file header\n"+e); + return null; + } + return out; + } // open() + +} // class WavWrite()
--- a/at/ofai/music/beatroot/GUI.java Fri Oct 08 16:09:10 2010 +0100 +++ b/at/ofai/music/beatroot/GUI.java Fri Oct 08 16:11:06 2010 +0100 @@ -95,7 +95,7 @@ /** Version number of program - displayed as part of window title. * DO NOT EDIT: This line is also used in creating the file name of the jar file. */ - public static final String version = "0.5.6"; + public static final String version = "0.5.7"; /** Strings displayed on menus and buttons */ public static final String LOAD_AUDIO = "Load Audio Data";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/ArrayMap.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,178 @@ +/* + 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.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +// An implementation of the Map interface, backed by an ArrayList, which +// preserves the elements in the order that they are added to the map. +// Operations will take linear rather than constant time (as for the efficient +// implementations of Map). Operations are not synchronized; caveat programmer! +// Used by class Parameters +// Updated to use generics; demonstrates that generics do not necessarily make +// programs more readable, simple, safe, etc. +class ArrayMap implements Map<String,Object> { + + protected ArrayList<Entry> entries; + + protected class Entry implements Map.Entry<String,Object>, + Comparable<Object> { + protected String key; + protected Object value; + protected Entry(String k, Object v) { + key = k; + value = v; + } // constructor + public boolean equals(Object o) { + return (o instanceof Entry) && key.equals(((Entry)o).key) && + value.equals(((Entry)o).value); + } // equals() + public String getKey() { return key; } + public Object getValue() { return value; } + public Object setValue(Object newValue) { + Object oldValue = value; + value = newValue; + return oldValue; + } // setValue() + public int hashCode() { + return (key==null? 0 : key.hashCode()) ^ + (value==null? 0 : value.hashCode()); + } // hashCode() + public int compareTo(Object o) { + return key.compareTo(((Entry)o).key); + } // compareTo() + } // inner class Entry + + public ArrayMap() { entries = new ArrayList<Entry>(); }//default constructor + public ArrayMap(Map<String,Object> m) { this(); putAll(m); } // copy constructor + + // Returns the index of an entry, given its key, or -1 if it is not in map. + // Note that ArrayList.indexOf() can't be used, because it doesn't call + // ArrayMap$Entry.equals() [bug?? or does it call key.equals(entry)??] + public int indexOf(String key) { + for (int i = 0; i < size(); i++) + if (key.equals(entries.get(i).key)) + return i; + return -1; + } // indexOf() + + // Returns the map entry at the given index + public Entry getEntry(int i) { return entries.get(i); } + + // Removes all mappings from this map (optional operation). + public void clear() { entries.clear(); } + + // Returns true if this map contains a mapping for the specified key. + public boolean containsKey(Object key) { + return indexOf((String)key) >= 0; + } // containsKey() + + // Returns true if this map maps one or more keys to the specified value. + public boolean containsValue(Object value) { + for (int i = 0; i < size(); i++) + if (value.equals(entries.get(i).value)) + return true; + return false; + } // containsValue() + + // Returns a set view of the mappings contained in this map. + public Set<Map.Entry<String,Object>> entrySet() { + TreeSet<Map.Entry<String,Object>> s = + new TreeSet<Map.Entry<String,Object>>(); + for (int i = 0; i < size(); i++) + s.add(entries.get(i)); + return s; + } // entrySet() + + // Compares the specified object with this map for equality. + public boolean equals(Object o) { return (o == this); } + + // Returns the value to which this map maps the specified key. + public Object get(Object key) { + int i = indexOf((String)key); + if (i == -1) + return null; + return entries.get(i).value; + } // get() + + // Returns the hash code value for this map. + public int hashCode() { + int h = 0; + for (int i = 0; i < size(); i++) + h ^= entries.get(i).hashCode(); + return h; + } // hashCode() + + // Returns true if this map contains no key-value mappings. + public boolean isEmpty() { return entries.isEmpty(); } + + // Returns a set view of the keys contained in this map. + public Set<String> keySet() { + TreeSet<String> s = new TreeSet<String>(); + for (int i = 0; i < size(); i++) + s.add(entries.get(i).key); + return s; + } // keySet() + + // Associates the specified value with the specified key in this map + public Object put(String key, Object value) { + int i = indexOf(key); + if (i < 0) { + entries.add(new Entry(key, value)); + return null; + } else + return entries.get(i).setValue(value); + } // put() + + // Copies all of the mappings from the specified map to this map + public void putAll(Map m) { + // The following warning seems to be unavoidable: + // warning: [unchecked] unchecked conversion + Map<String,Object> m1 = (Map<String,Object>)m; + for (Map.Entry<String,Object> me : m1.entrySet()) { + put(me.getKey(), me.getValue()); + } + } // putAll() + + // Removes the mapping for this key from this map if present + public Object remove(Object key) { + int i = indexOf((String)key); + if (i < 0) + return null; + return entries.remove(i); + } // remove() + + // Returns the number of key-value mappings in this map. + public int size() { return entries.size(); } + + // Returns a collection view of the values contained in this map. + public Collection<Object> values() { + ArrayList<Object> s = new ArrayList<Object>(); + for (int i = 0; i < size(); i++) + s.add(entries.get(i).value); + return s; + } // values() + +} // class ArrayMap
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/ArrayPrint.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,45 @@ +/* + 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.util; + +public class ArrayPrint { + + public static void show(String s, double[] arr, int line) { + System.out.println(s + " (length = " + arr.length + ")"); + for (int i = 0; i < arr.length; i++) { + System.out.printf("%7.3f ", arr[i]); + if (i % line == line - 1) + System.out.println(); + } + System.out.println(); + } // show() + + void show(String s, int[] arr, int line) { + System.out.println(s + " (length = " + arr.length + ")"); + for (int i = 0; i < arr.length; i++) { + System.out.printf("%7d ", arr[i]); + if (i % line == line - 1) + System.out.println(); + } + System.out.println(); + } // show() + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Colors.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,30 @@ +/* + 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.util; + +import java.awt.Color; + +public interface Colors { + Color getBackground(); + Color getForeground(); + Color getButton(); + Color getButtonText(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/ConstantTempoMap.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,56 @@ +/* + 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.util; + +public class ConstantTempoMap implements TempoMap { + + protected double interBeatInterval; + + public ConstantTempoMap(double bpm) { + interBeatInterval = 60 / bpm; + } // constructor + + public ConstantTempoMap() { + this(120); + } // default constructor + + public void add(double time, double tempo) { + throw new RuntimeException("ConstantTempoMap: cannot change tempo"); + } // add() + + public double toRealTime(double value) { + return value * interBeatInterval; + } // toRealTime() + + public double toScoreTime(double value) { + return value / interBeatInterval; + } // toScoreTime() + + public static void main(String[] args) { // unit test + TempoMap mtm = new ConstantTempoMap(100); + System.out.println(mtm.toRealTime(1)); + System.out.println(mtm.toScoreTime(mtm.toRealTime(1))); + System.out.println(mtm.toScoreTime(4)); + System.out.println(mtm.toRealTime(mtm.toScoreTime(4))); + mtm.add(5, 120); + } // main() + +} // ConstantTempoMap
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Event.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,98 @@ +/* + 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.util; + +public class Event implements Comparable, Cloneable, java.io.Serializable { + + public double keyDown, keyUp, pedalUp, scoreBeat, scoreDuration, salience; + public int midiPitch, midiVelocity, flags, midiCommand, midiChannel, + midiTrack; + //public String label; + + public Event(double onset, double offset, double eOffset, int pitch, + int velocity, double beat, double duration, int eventFlags, + int command, int channel, int track) { + this(onset, offset, eOffset, pitch, velocity, beat,duration,eventFlags); + midiCommand = command; + midiChannel = channel; + midiTrack = track; + } // constructor + + public Event(double onset, double offset, double eOffset, int pitch, + int velocity, double beat, double duration, int eventFlags) { + keyDown = onset; + keyUp = offset; + pedalUp = eOffset; + midiPitch = pitch; + midiVelocity = velocity; + scoreBeat = beat; + scoreDuration = duration; + flags = eventFlags; + midiCommand = javax.sound.midi.ShortMessage.NOTE_ON; + midiChannel = 1; + midiTrack = 0; + salience = 0; + } // constructor + + public Event clone() { + return new Event(keyDown, keyUp, pedalUp, midiPitch, midiVelocity, + scoreBeat, scoreDuration, flags, midiCommand, midiChannel, + midiTrack); + } // clone() + + // Interface Comparable + public int compareTo(Object o) { + Event e = (Event) o; + return (int)Math.signum(keyDown - e.keyDown); + } // compareTo() + + public String toString() { + return "n=" + midiPitch + " v=" + midiVelocity + " t=" + keyDown + + " to " + keyUp + " (" + pedalUp + ")"; + } // toString() + + public void print(Flags f) { + System.out.printf("Event:\n"); + System.out.printf("\tkeyDown / Up / pedalUp: %5.3f / %5.3f / %5.3f\n", + keyDown, keyUp, pedalUp); + //System.out.printf("\tkeyUp: %5.3f\n", keyUp); + //System.out.printf("\tpedalUp: %5.3f\n", pedalUp); + System.out.printf("\tmidiPitch: %d\n", midiPitch); + System.out.printf("\tmidiVelocity: %d\n", midiVelocity); + System.out.printf("\tmidiCommand: %02x\t", midiCommand | midiChannel); + //System.out.printf("\tmidiChannel: %d\n", midiChannel); + System.out.printf("\tmidiTrack: %d\n", midiTrack); + System.out.printf("\tsalience: %5.3f\t", salience); + System.out.printf("\tscoreBeat: %5.3f\t", scoreBeat); + System.out.printf("\tscoreDuration: %5.3f\n", scoreDuration); + System.out.printf("\tflags: %X", flags); + if (f != null) { + int ff = flags; + for (int i=0; ff != 0; i++) { + if (ff % 2 == 1) + System.out.print(" " + f.getLabel(i)); + ff >>>= 1; + } + } + System.out.print("\n\n"); + } // print() + +} // class Event
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/EventList.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,843 @@ +/* + 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.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MetaMessage; +import javax.sound.midi.MidiEvent; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.Sequence; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.Track; + +import at.ofai.music.worm.Worm; +import at.ofai.music.worm.WormFile; +import at.ofai.music.worm.WormParameters; + +// Adapted from eventList::readMatchFile in beatroot/src/eventMidi.cpp + +// Reads in a Prolog score+performance (.match) file; returns it as an eventList +// Lines in the match file can be of the form: +// hammer_bounce-PlayedNote. +// info(Attribute, Value). +// insertion-PlayedNote. +// ornament(Anchor)-PlayedNote. +// ScoreNote-deletion. +// ScoreNote-PlayedNote. +// ScoreNote-trailing_score_note. +// trailing_played_note-PlayedNote. +// trill(Anchor)-PlayedNote. +// where ScoreNote is of the form +// snote(Anchor,[NoteName,Modifier],Octave,Bar:Beat,Offset,Duration, +// BeatNumber,DurationInBeats,ScoreAttributesList) +// e.g. snote(n1,[b,b],5,1:1,0,3/16,0,0.75,[s]) +// and PlayedNote is of the form +// note(Number,[NoteName,Modifier],Octave,Onset,Offset,AdjOffset,Velocity) +// e.g. note(1,[a,#],5,5054,6362,6768,53) + +class WormFileParseException extends RuntimeException { + + static final long serialVersionUID = 0; + public WormFileParseException(String s) { + super(s); + } // constructor + +} // class WormFileParseException + +class MatchFileParseException extends RuntimeException { + + static final long serialVersionUID = 0; + public MatchFileParseException(String s) { + super(s); + } // constructor + +} // class MatchFileParseException + +class BTFileParseException extends RuntimeException { + + static final long serialVersionUID = 0; + public BTFileParseException(String s) { + super(s); + } // constructor + +} // class BTFileParseException + + +// Process the strings which label extra features of notes in match files. +// We assume no more than 32 distinct labels in a file. +class Flags { + + String[] labels = new String[32]; + int size = 0; + + int getFlag(String s) { + if ((s == null) || s.equals("")) + return 0; + //int val = 1; + for (int i = 0; i < size; i++) + if (s.equals(labels[i])) + return 1 << i; + if (size == 32) { + System.err.println("Overflow: Too many flags: " + s); + size--; + } + labels[size] = s; + return 1 << size++; + } // getFlag() + + String getLabel(int i) { + if (i >= size) + return "ERROR: Unknown flag"; + return labels[i]; + } // getLabel() + +} // class Flags + + +// A score/match/midi file is represented as an EventList object, +// which contains pointers to the head and tail links, and some +// class-wide parameters. Parameters are class-wide, as it is +// assumed that the Worm has only one input file at a time. +public class EventList implements Serializable { + + public LinkedList<Event> l; + + protected static boolean timingCorrection = false; + protected static double timingDisplacement = 0; + protected static int clockUnits = 480; + protected static int clockRate = 500000; + protected static double metricalLevel = 0; + public static final double UNKNOWN = Double.NaN; + protected static boolean noMelody = false; + protected static boolean onlyMelody = false; + protected static Flags flags = new Flags(); + + public EventList() { + l = new LinkedList<Event>(); + } // constructor + + public EventList(EventList e) { + this(); + ListIterator<Event> it = e.listIterator(); + while (it.hasNext()) + add(it.next()); + } // constructor + + public EventList(Event[] e) { + this(); + for (int i=0; i < e.length; i++) + add(e[i]); + } // constructor + + public void add(Event e) { + l.add(e); + } // add() + + public void add(EventList ev) { + l.addAll(ev.l); + } // add() + + public void insert(Event newEvent, boolean uniqueTimes) { + ListIterator<Event> li = l.listIterator(); + while (li.hasNext()) { + int sgn = newEvent.compareTo(li.next()); + if (sgn < 0) { + li.previous(); + break; + } else if (uniqueTimes && (sgn == 0)) { + li.remove(); + break; + } + } + li.add(newEvent); + } // insert() + + public ListIterator<Event> listIterator() { + return l.listIterator(); + } // listIterator() + + public Iterator<Event> iterator() { + return l.iterator(); + } // iterator() + + public int size() { + return l.size(); + } // size() + + public Event[] toArray() { + return toArray(0); + } // toArray() + + public double[] toOnsetArray() { + double[] d = new double[l.size()]; + int i = 0; + for (Iterator<Event> it = l.iterator(); it.hasNext(); i++) + d[i] = it.next().keyDown; + return d; + } // toOnsetArray() + + public Event[] toArray(int match) { + int count = 0; + for (Event e : l) + if ((match == 0) || (e.midiCommand == match)) + count++; + Event[] a = new Event[count]; + int i = 0; + for (Event e : l) + if ((match == 0) || (e.midiCommand == match)) + a[i++] = e; + return a; + } // toArray() + + public void writeBinary(String fileName) { + try { + ObjectOutputStream oos = new ObjectOutputStream( + new FileOutputStream(fileName)); + oos.writeObject(this); + oos.close(); + } catch (IOException e) { + System.err.println(e); + } + } // writeBinary() + + public static EventList readBinary(String fileName) { + try { + ObjectInputStream ois = new ObjectInputStream( + new FileInputStream(fileName)); + EventList e = (EventList) ois.readObject(); + ois.close(); + return e; + } catch (IOException e) { + System.err.println(e); + return null; + } catch (ClassNotFoundException e) { + System.err.println(e); + return null; + } + } // readBinary() + + public void writeMIDI(String fileName) { + writeMIDI(fileName, null); + } // writeMIDI() + + public void writeMIDI(String fileName, EventList pedal) { + try { + MidiSystem.write(toMIDI(pedal), 1, new File(fileName)); + } catch (Exception e) { + System.err.println("Error: Unable to write MIDI file " + fileName); + e.printStackTrace(); + } + } // writeMIDI() + + public Sequence toMIDI(EventList pedal) throws InvalidMidiDataException { + final int midiTempo = 1000000; + Sequence s = new Sequence(Sequence.PPQ, 1000); + Track[] tr = new Track[16]; + tr[0] = s.createTrack(); + MetaMessage mm = new MetaMessage(); + byte[] b = new byte[3]; + b[0] = (byte)((midiTempo >> 16) & 0xFF); + b[1] = (byte)((midiTempo >> 8) & 0xFF); + b[2] = (byte)(midiTempo & 0xFF); + mm.setMessage(0x51, b, 3); + tr[0].add(new MidiEvent(mm, 0L)); + for (Event e : l) { // from match or beatTrack file + if (e.midiCommand == 0) // skip beatTrack file + break; + if (tr[e.midiTrack] == null) + tr[e.midiTrack] = s.createTrack(); + //switch (e.midiCommand) + //case ShortMessage.NOTE_ON: + //case ShortMessage.POLY_PRESSURE: + //case ShortMessage.CONTROL_CHANGE: + //case ShortMessage.PROGRAM_CHANGE: + //case ShortMessage.CHANNEL_PRESSURE: + //case ShortMessage.PITCH_BEND: + ShortMessage sm = new ShortMessage(); + sm.setMessage(e.midiCommand, e.midiChannel, + e.midiPitch, e.midiVelocity); + tr[e.midiTrack].add(new MidiEvent(sm, + (long)Math.round(1000 * e.keyDown))); + if (e.midiCommand == ShortMessage.NOTE_ON) { + sm = new ShortMessage(); + sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, e.midiPitch, 0); + tr[e.midiTrack].add(new MidiEvent(sm, (long)Math.round(1000 * e.keyUp))); + } + } + if (pedal != null) { // from MIDI file + // if (t.size() > 0) // otherwise beatTrack files leave an empty trk + // t = s.createTrack(); + for (Event e : pedal.l) { + if (tr[e.midiTrack] == null) + tr[e.midiTrack] = s.createTrack(); + ShortMessage sm = new ShortMessage(); + sm.setMessage(e.midiCommand, e.midiChannel, + e.midiPitch, e.midiVelocity); + tr[e.midiTrack].add(new MidiEvent(sm, + (long)Math.round(1000 * e.keyDown))); + if (e.midiCommand == ShortMessage.NOTE_ON) { + sm = new ShortMessage(); + sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, + e.midiPitch,e.midiVelocity); + tr[e.midiTrack].add(new MidiEvent(sm, + (long)Math.round(1000 * e.keyUp))); + } + //catch (InvalidMidiDataException exception) {} + } + } + return s; + } // toMIDI() + + public static EventList readMidiFile(String fileName) { + return readMidiFile(fileName, 0); + } // readMidiFile() + + public static EventList readMidiFile(String fileName, int skipTrackFlag) { + EventList list = new EventList(); + Sequence s; + try { + s = MidiSystem.getSequence(new File(fileName)); + } catch (Exception e) { + e.printStackTrace(); + return list; + } + double midiTempo = 500000; + double tempoFactor = midiTempo / s.getResolution() / 1000000.0; + // System.err.println(tempoFactor); + Event[][] noteOns = new Event[128][16]; + Track[] tracks = s.getTracks(); + for (int t = 0; t < tracks.length; t++, skipTrackFlag >>= 1) { + if ((skipTrackFlag & 1) == 1) + continue; + for (int e = 0; e < tracks[t].size(); e++) { + MidiEvent me = tracks[t].get(e); + MidiMessage mm = me.getMessage(); + double time = me.getTick() * tempoFactor; + byte[] mesg = mm.getMessage(); + int channel = mesg[0] & 0x0F; + int command = mesg[0] & 0xF0; + if (command == ShortMessage.NOTE_ON) { + int pitch = mesg[1] & 0x7F; + int velocity = mesg[2] & 0x7F; + if (noteOns[pitch][channel] != null) { + if (velocity == 0) { // NOTE_OFF in disguise :( + noteOns[pitch][channel].keyUp = time; + noteOns[pitch][channel].pedalUp = time; + noteOns[pitch][channel] = null; + } else + System.err.println("Double note on: n=" + pitch + + " c=" + channel + + " t1=" + noteOns[pitch][channel] + + " t2=" + time); + } else { + Event n = new Event(time, 0, 0, pitch, velocity, -1, -1, + 0, ShortMessage.NOTE_ON, channel, t); + noteOns[pitch][channel] = n; + list.add(n); + } + } else if (command == ShortMessage.NOTE_OFF) { + int pitch = mesg[1] & 0x7F; + noteOns[pitch][channel].keyUp = time; + noteOns[pitch][channel].pedalUp = time; + noteOns[pitch][channel] = null; + } else if (command == 0xF0) { + if ((channel == 0x0F) && (mesg[1] == 0x51)) { + midiTempo = (mesg[5] & 0xFF) | + ((mesg[4] & 0xFF) << 8) | + ((mesg[3] & 0xFF) << 16); + tempoFactor = midiTempo / s.getResolution() / 1000000.0; + // System.err.println("Info: Tempo change: " + midiTempo + + // " tf=" + tempoFactor); + } + } else if (mesg.length > 3) { + System.err.println("midi message too long: " + mesg.length); + System.err.println("\tFirst byte: " + mesg[0]); + } else { + int b0 = mesg[0] & 0xFF; + int b1 = -1; + int b2 = -1; + if (mesg.length > 1) + b1 = mesg[1] & 0xFF; + if (mesg.length > 2) + b2 = mesg[2] & 0xFF; + list.add(new Event(time, time, -1, b1, b2, -1, -1, 0, + b0 & 0xF0, b0 & 0x0F, t)); + } + } + } + for (int pitch = 0; pitch < 128; pitch++) + for (int channel = 0; channel < 16; channel++) + if (noteOns[pitch][channel] != null) + System.err.println("Missing note off: n=" + + noteOns[pitch][channel].midiPitch + " t=" + + noteOns[pitch][channel].keyDown); + return list; + } // readMidiFile() + + public void print() { + for (Iterator<Event> i = l.iterator(); i.hasNext(); ) + i.next().print(flags); + } // print() + + public static void setTimingCorrection(double corr) { + timingCorrection = corr >= 0; + timingDisplacement = corr; + } // setTimingCorrection() + + public static EventList readBeatsAsText(String fileName) throws Exception { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + String s = inputFile.readLine(); + if (s.startsWith("###")) + return readLabelFile(fileName); + int beats = 0; + int pitch = 56; + int vol = 80; + int ch = 10; + int track = 0; + int fl = 1; + while (s != null) { + int ind = s.indexOf(','); + if (ind < 0) + ind = s.indexOf(' '); + double time = 0; + if (ind >= 0) { + String tmp = s.substring(0,ind).trim(); + if (tmp.length() == 0) { + s = inputFile.readLine(); + continue; + } + time = Double.parseDouble(tmp); + s = s.substring(ind+1); + } else { + String tmp = s.trim(); + if (tmp.length() > 0) + time = Double.parseDouble(tmp); + s = inputFile.readLine(); + } + list.add(new Event(time, time, time, pitch, vol, ++beats, + 1.0, fl, ShortMessage.NOTE_ON, ch, track)); + } + return list; + } // readBeatsAsText() + + public static EventList readBeatTrackFile(String fileName) throws Exception{ + if (!fileName.endsWith(".tmf")) // || fileName.endsWith(".csv")) + return readBeatsAsText(fileName); + else { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + Matcher s = new Matcher(inputFile.readLine()); + if (!s.matchString("MFile")) + throw new BTFileParseException("Header not found"); + s.getInt(); // skip fileType + int tracks = s.getInt(); + int div = s.getInt(); + int tempo = 500000; // default tempo + double tf = 1e6 / tempo * div; + int lineCount = 1; + int beats = 0; + for (int track = 0; track < tracks; track++) { + s.set(inputFile.readLine()); + lineCount++; + if (!s.matchString("MTrk")) + throw new BTFileParseException("MTrk not found"); + s.set(inputFile.readLine()); + lineCount++; + while (!s.matchString("TrkEnd")) { + double time = s.getInt() / tf; + s.trimSpace(); + if (s.matchString("Tempo")) { + tempo = s.getInt(); + tf = 1e6 / tempo * div; + } else if (s.matchString("On")) { + s.trimSpace(); + s.matchString("ch="); + int ch = s.getInt(); + s.trimSpace(); + if (!s.matchString("n=")) + s.matchString("note="); + int pitch = s.getInt(); + s.trimSpace(); + if (!s.matchString("v=")) + s.matchString("vol="); + int vol = s.getInt(); + s.set(inputFile.readLine()); + lineCount++; + s.getInt(); + s.trimSpace(); + s.matchString("Off"); + s.skip('v'); + s.matchString("ol"); + s.matchString("="); + int flags = s.getInt(); + list.add(new Event(time, time, time, pitch, vol, ++beats, + 1.0, flags, ShortMessage.NOTE_ON, ch, track)); + } else if (!s.matchString("Meta TrkEnd")) { + System.err.println("Unmatched text on line " + lineCount + + ": " + s.get()); + } + s.set(inputFile.readLine()); + lineCount++; + } + } + return list; + } + } // readBeatTrackFile() + + public void writeBeatsAsText(String fileName) throws Exception { + PrintStream out = new PrintStream(new File(fileName)); + char separator = '\n'; + if (fileName.endsWith(".csv")) + separator = ','; + for (Iterator<Event> it = iterator(); it.hasNext(); ) { + Event e = it.next(); + out.printf("%5.3f%c", e.keyDown, it.hasNext()? separator: '\n'); + } + out.close(); + } // writeBeatsAsText() + + public void writeBeatTrackFile(String fileName) throws Exception { + if (fileName.endsWith(".txt") || fileName.endsWith(".csv")) + writeBeatsAsText(fileName); + else { + PrintStream out = new PrintStream(new File(fileName)); + out.println("MFile 0 1 500"); + out.println("MTrk"); + out.println(" 0 Tempo 500000"); + int time = 0; + for (Iterator<Event> it = iterator(); it.hasNext(); ) { + Event e = it.next(); + time = (int) Math.round(1000 * e.keyDown); + out.printf("%6d On ch=%3d n=%3d v=%3d\n", + time, e.midiChannel, e.midiPitch, e.midiVelocity); + out.printf("%6d Off ch=%3d n=%3d v=%3d\n", + time, e.midiChannel, e.midiPitch, e.flags); + } + out.printf("%6d Meta TrkEnd\nTrkEnd\n", time); + out.close(); + } + } // writeBeatTrackFile() + + /** Reads a file containing time,String pairs into an EventList. */ + public static EventList readLabelFile(String fileName) throws Exception { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + Matcher s = new Matcher(inputFile.readLine()); + int prevBar = 0; + int beats = 0; + int pitch = 56; + int vol = 80; + int ch = 10; + int track = 0; + while (s.hasData()) { + if (!s.matchString("#")) { + double time = s.getDouble(); + String label = s.get().trim(); + int colon = label.indexOf(':'); + int beat = 0; + if (colon < 0) + colon = label.length(); + else + beat = Integer.parseInt(label.substring(colon+1)); + int bar = Integer.parseInt(label.substring(0, colon)); + int flags = WormFile.BEAT; + if (bar != prevBar) { + flags |= WormFile.BAR; + prevBar = bar; + } + WormEvent ev = new WormEvent(time, time, time, pitch, vol, + ++beats,1.0,flags, ShortMessage.NOTE_ON, ch, track); + ev.label = label; + list.add(ev); +// System.out.println(time + " " + label); + } + s.set(inputFile.readLine()); + } + return list; + } // readLabelFile() + + public void writeLabelFile(String fileName) throws Exception { + PrintStream out = new PrintStream(new File(fileName)); + out.printf("###Created automatically\n"); + for (Event ev : l) + out.printf("%5.3f\t%s\n", ev.keyDown, ((WormEvent)ev).label); + out.close(); + } // writeLabelFile() + + public static EventList readWormFile(String fileName) throws Exception { + EventList list = new EventList(); + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + Matcher s = new Matcher(inputFile.readLine()); + int lineCount = 1; + if (!s.matchString("WORM Version:")) + throw new WormFileParseException("WORM format: header not found"); + if (s.getDouble() < 1.01) + throw new WormFileParseException("WORM format: v1.0 not supported"); + int dataCountDown = -1; + int beat = 0; + while (true) { + s.set(inputFile.readLine()); + lineCount++; + if (dataCountDown == 0) { + if (s.hasData()) + System.err.println("Ignoring trailing data past line " + + lineCount); + return list; + } else if (!s.hasData()) + throw new WormFileParseException("Unexpected EOF"); + if (dataCountDown < 0) { + if (s.matchString("Length:")) + dataCountDown = s.getInt(); + } else { + double time = s.getDouble(); + double tempo = s.getDouble(); + double loudness = s.getDouble(); + int flags = s.getInt(); + if ((flags & WormFile.TRACK) != 0) + beat++; // i.e. always, as index for comparing files + list.add(new WormEvent(time, tempo, loudness, beat, flags)); + dataCountDown--; + } + } + } // readWormFile() + + public static String getAudioFileFromWormFile(String wormFile) { + return getWormFileAttribute(wormFile, "AudioFile"); + } // getAudioFileFromWormFile() + + public static double getTrackLevelFromWormFile(String wormFile) { + String level = getWormFileAttribute(wormFile,WormParameters.TRACKLEVEL); + try { + int i = level.indexOf("/"); + if (i >= 0) + return Double.parseDouble(level.substring(0,i)) / + Double.parseDouble(level.substring(i+1)); + else + return Double.parseDouble(level); + } catch (Exception e) { + System.err.println("Error getting TrackLevel:\n" + e); + return 1; + } + } // getTrackLevelFromWormFile() + + public static String getWormFileAttribute(String wormFile, String attr) { + try { + BufferedReader r = new BufferedReader(new FileReader(wormFile)); + String line = r.readLine(); + attr += ":"; + while (line != null) { + if (line.startsWith(attr)) + return line.substring(attr.length()).trim(); + line = r.readLine(); + } + } catch (Exception e) { + System.err.println(e); + } + return null; + } // getWormFileAttribute() + + public static EventList readMatchFile(String fileName) throws Exception { + EventList list = new EventList(); + boolean startNote = timingCorrection; + int eventFlags, numerator, denominator; + String element; + BufferedReader inputFile = new BufferedReader(new FileReader(fileName)); + double versionNumber = 1.0; + double onset, offset, eOffset, beat, duration; + int velocity, pitch, octave; + int lineCount = 1; + Matcher s = new Matcher(inputFile.readLine()); + while (s.hasData()) { + eventFlags = 0; + beat = UNKNOWN; + duration = UNKNOWN; + // System.out.println("Processing line " + lineCount); + if (s.matchString("info(")) { // meta-data + if (s.matchString("timeSignature,")) { + numerator = s.getInt(); + // ss1 << "beatsPerBar=" << numerator << ends; + s.skip('/'); + denominator = s.getInt(); + // ss2 << "beatUnits=" << denominator; + } else if (s.matchString("beatSubdivision,")) { + // strcpy(buf, "beatSubdivisions="); + // int i = strlen(buf); + // f.getline(buf+i, SZ-i, ']'); + // strcat(buf, "]"); + // parameters->add(buf); + s.skip(']'); + } else if (s.matchString("matchFileVersion,")) { + versionNumber = s.getDouble(); + } else if (s.matchString("midiClockUnits,")) { + clockUnits = s.getInt(); + } else if (s.matchString("midiClockRate,")) { + clockRate = s.getInt(); + } + s.set("%"); // don't expect the second half of the Prolog term + } else if (s.matchString("snote(")) { + s.skip(','); // identifier + s.skip(']'); // note name + s.skip(','); // ',' after note name + s.skip(','); // octave + s.skip(','); // onset time (in beats, integer part, bar:beat) + boolean isBt = s.matchString("0"); + s.skip(','); // onset time (in beats, fractional part) + s.skip(','); // duration (in beats, fraction) + try { + beat = s.getDouble(); + } catch (NumberFormatException e) { + System.err.println("Bad beat number on line " + lineCount); + beat = UNKNOWN; + } + if ((beat == Math.rint(beat)) != isBt) + System.err.println("Inconsistent beats on line "+lineCount); + s.skip(','); // onset time (in beats, decimal) + try { + duration = s.getDouble() - beat; + } catch (NumberFormatException e) { + System.err.println("Bad duration on line " + lineCount); + duration = UNKNOWN; + } + s.skip(','); // offset time (in beats, decimal) + s.skip('['); // additional info (e.g. melody/arpeggio/grace) + do { + element = s.getString(); + eventFlags |= flags.getFlag(element); + } while (s.matchString(",")); + s.skip('-'); + } else if (s.matchString("trill(")) { + eventFlags |= flags.getFlag("trill"); + s.skip('-'); + } else if (s.matchString("ornament(")) { + eventFlags |= flags.getFlag("ornament"); + s.skip('-'); + } else if (s.matchString("trailing_played_note-") || + s.matchString("hammer_bounce-") || + s.matchString("no_score_note-") || + s.matchString("insertion-")) { + eventFlags |= flags.getFlag("unscored"); + } else if (!s.matchString("%")) { // Prolog comment + throw new MatchFileParseException("error 4; line "+lineCount); + } + // READ 2nd term of Prolog expression + if (s.matchString("note(")) { + s.skip('['); // skip identifier + String note = s.getString(); + switch(Character.toUpperCase(note.charAt(0))) { + case 'A': pitch = 9; break; + case 'B': pitch = 11; break; + case 'C': pitch = 0; break; + case 'D': pitch = 2; break; + case 'E': pitch = 4; break; + case 'F': pitch = 5; break; + case 'G': pitch = 7; break; + default: throw new MatchFileParseException( + "Bad note on line " + lineCount); + } + s.skip(','); + String mod = s.getString(); + for (int i = 0; i < mod.length(); i++) { + switch (mod.charAt(i)) { + case '#': pitch++; break; + case 'b': pitch--; break; + case 'n': break; + default: throw new MatchFileParseException("error 5 " + + lineCount); + } + } + s.skip(','); + octave = s.getInt(); + pitch += 12 * octave; + s.skip(','); + onset = s.getInt(); + s.skip(','); + offset = s.getInt(); + if (versionNumber > 1.0) { + s.skip(','); + eOffset = s.getInt(); + } else + eOffset = offset; + s.skip(','); + velocity = s.getInt(); + onset /= clockUnits * 1000000.0 / clockRate; + offset /= clockUnits * 1000000.0 / clockRate; + eOffset /= clockUnits * 1000000.0 / clockRate; + if (timingCorrection) { + if (startNote) { + timingDisplacement = onset - timingDisplacement; + startNote = false; + } + onset -= timingDisplacement; + offset -= timingDisplacement; + eOffset -= timingDisplacement; + } + int m = flags.getFlag("s"); + if ((((eventFlags & m) != 0) && !noMelody) || + (((eventFlags & m) == 0) && !onlyMelody)) { + Event e = new Event(onset, offset, eOffset, pitch, velocity, + beat, duration, eventFlags); + list.add(e); + } + } else if (!s.matchString("no_played_note.") && + !s.matchString("trailing_score_note.") && + !s.matchString("deletion.") && + !s.matchString("%")) + throw new MatchFileParseException("error 6; line " + lineCount); + s.set(inputFile.readLine()); + lineCount++; + } + return list; + } // readMatchFile() + + public static void main(String[] args) throws Exception { // quick test + //System.out.println("Test"); + //readLabelFile(args[0]).writeLabelFile("tmp.txt"); + readLabelFile(args[0]).print(); + System.exit(0); + EventList el = readMatchFile(args[0]); + WormFile wf = new WormFile(null, el); + if (args.length >= 2) { + double sm = Double.parseDouble(args[1]); + wf.smooth(Worm.FULL_GAUSS, sm, sm, 0); + } else + wf.smooth(Worm.NONE, 0, 0, 0); + wf.write("worm.out"); + if (args.length == 3) + el.print(); + } // main() + +} // class EventList
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Format.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,201 @@ +/* + 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.util; + +import java.text.NumberFormat; +import java.io.*; + +/** A simple utility class for easier formatted output of numeric data. + * Formatting is controlled by static objects, so that number widths and + * precisions can be set once, and used multiple times. + */ +public class Format { + + /** The object which performs formatting of integers */ + protected static NumberFormat intFormat = NumberFormat.getInstance(); + + /** The object which performs formatting of doubles */ + protected static NumberFormat doubleFormat = NumberFormat.getInstance(); + + /** The preferred notation for positive numbers (default is no '+' sign) */ + protected static char plusSign = ' '; + + /** Set the number of digits to appear after the decimal point + * @param dp the number of characters after the decimal point + */ + public static void setPostDigits(int dp) { + doubleFormat.setMinimumFractionDigits(dp); + doubleFormat.setMaximumFractionDigits(dp); + } // setPostDigits() + + /** Set the number of digits to appear before the decimal point. + * If the number does not require this many digits, it will be + * padded with spaces. + * @param dp the number of characters before the decimal point + */ + public static void setPreDigits(int dp) { + doubleFormat.setMinimumIntegerDigits(dp); + } // setPreDigits() + + /** Set the number of digits for displaying integers. + * If the number does not require this many digits, it will be + * padded with spaces. + * @param dp the number of digits for displaying integers + */ + public static void setIntDigits(int dp) { + intFormat.setMinimumIntegerDigits(dp); + intFormat.setMinimumFractionDigits(0); + intFormat.setMaximumFractionDigits(0); + } // setIntegerDigits() + + /** Set whether digits should be grouped in 3's as in 12,000,000. + * @param flag true if grouping should be used, false if not + */ + public static void setGroupingUsed(boolean flag) { + doubleFormat.setGroupingUsed(flag); + intFormat.setGroupingUsed(flag); + } // setGroupingUsed() + + /** Sets the initial character for positive numbers (usually blank or '+') + * @param c the character to prefix to positive numbers + */ + public static void setPlusSign(char c) { + plusSign = c; + } // setPlusSign() + + /** Initialise the formatting objects with the desired settings. + * @param id the number of characters for displaying integers + * @param did the number of characters before the decimal point + * @param dfd the number of characters after the decimal point + * @param grouping true if grouping should be used, false if not + */ + public static void init(int id, int did, int dfd, boolean grouping) { + setIntDigits(id); + setPreDigits(did); + setPostDigits(dfd); + setGroupingUsed(grouping); + } // init() + + /** Convert a double to a String with a set number of decimal places and + * padding to the desired minimum number of characters before the decimal + * point. + * @param n the number to convert + * @param id the number of characters before the decimal point + * @param fd the number of characters after the decimal point + */ + public static String d(double n, int id, int fd) { + setPreDigits(id); + return d(n, fd); + } // d() + + /** Convert a double to a String with a set number of decimal places + * @param n the number to convert + * @param fd the number of characters after the decimal point + */ + public static String d(double n, int fd) { + setPostDigits(fd); + return d(n); + } // d() + + /** Convert a double to a String. The number of decimal places and + * characters before the decimal point are stored in the static members + * of this class. + * @param n the number to convert + */ + public static String d(double n) { + String s; + if (Double.isNaN(n)) + return "NaN"; + s = doubleFormat.format(n); + if (n >= 0) + s = plusSign + s; + char[] c = s.toCharArray(); + int i; + for (i = 1; (i < c.length-1) && (c[i] == '0') && (c[i+1] != '.'); i++) { + c[i] = c[i-1]; + c[i-1] = ' '; + } + if (i > 1) + s = new String(c); + return s; + } // d() + + /** Convert an integer to a String with padding to the desired minimum width + * @param n the number to convert + * @param id the desired minimum width + */ + public static String i(int n, int id) { + setIntDigits(id); + return i(n); + } // i() + + /** Convert an integer to a String with padding to the desired minimum width + * @param n the number to convert + */ + public static String i(int n) { + return (n < 0)? intFormat.format(n): plusSign+intFormat.format(n); + } // i() + + /** Output an array to file as an assignment statement for input to Matlab + * @param data the values to print to 4 decimal places + * @param name the variable name in the Matlab assignment statement; the + * file name is the same name with ".m" appended, or standard out if + * unable to open this file + */ + public static void matlab(double[] data, String name) { + matlab(data, name, 4); + } // matlab() + + /** Output an array to file as an assignment statement for input to Matlab + * @param data the values to print + * @param name the variable name in the Matlab assignment statement; the + * file name is the same name with ".m" appended, or standard out if + * unable to open this file + * @param dp the number of decimal places to print + */ + public static void matlab(double[] data, String name, int dp) { + setPostDigits(dp); + PrintStream out; + try { + out = new PrintStream(new FileOutputStream(name+".m")); + } catch (FileNotFoundException e) { + out = System.out; + } + matlab(data, name, out); + if (out != System.out) + out.close(); + } // matlab + + /** Output an array to a printstream as an assignment statement for input to + * Matlab + * @param data the values to print + * @param name the variable name in the Matlab assignment statement + * @param out the output stream to print to + */ + public static void matlab(double[] data, String name, PrintStream out) { + out.println(name + " = ["); + setGroupingUsed(false); + for (int i=0; i < data.length; i++) + out.println(d(data[i])); + out.println("];"); + } // matlab() + +} // class Format
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/FrameMargins.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,97 @@ +/* + 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.util; + +import java.awt.*; +import javax.swing.*; + +/** Java has problems communicating with window managers like fvwm2. + * This class is a workaround to find the size of borders of JFrames in + * the current environment, so that programs can size their JFrames correctly. + * Since we don't know how big borders are until after the window is created, + * we create a dummy JFrame, query the size of its borders, and destroy it. + * The values are saved for later calls to this class, so that the dummy JFrame + * is only created once. + */ +public class FrameMargins { + + protected static Insets i = null; + protected static Dimension insetsWithMenu = null; + protected static Dimension insetsWithoutMenu = null; + protected static Dimension topLeftWithMenu = null; + protected static Dimension topLeftWithoutMenu = null; + + /** Returns the total size of the insets of a JFrame, that is the size of + * the title bar, menu bar (if requested) and the borders of the JFrame. + * In other words, the return value is the difference in size between the + * JFrame itself and its content pane. + * @param withMenuFlag indicates whether a menu bar should be included in + * the calculations + * @return the height and width of the insets of a JFrame, unless + * <code>getInsets()</code> returns a ridiculously large value, in which + * case we gracefully return a guess of (30,20). + */ + public static Dimension get(boolean withMenuFlag) { + if (i == null) { + JFrame f = new JFrame("Get size of window borders"); + JMenuBar mb = new JMenuBar(); + f.setJMenuBar(mb); + mb.add(new JMenu("OK")); + f.setVisible(true); + i = f.getInsets(); + f.dispose(); + if ((i.left>100) || (i.right>100) || (i.top>100) || (i.bottom>100)){ + i.left = 10; // Code around a bug in getInsets() + i.right = 10; // - don't believe ridiculously high values + i.top = 20; + i.bottom = 10; + } + insetsWithMenu = new Dimension(i.left + i.right, + i.top + i.bottom + mb.getHeight()); + insetsWithoutMenu = new Dimension(i.left + i.right, i.top+i.bottom); + topLeftWithoutMenu = new Dimension(i.left, i.top); + topLeftWithMenu = new Dimension(i.left, i.top + mb.getHeight()); + } + return withMenuFlag? insetsWithMenu: insetsWithoutMenu; + } // get() + + /** Returns the location of the content pane with respect to its JFrame. + * @param withMenuFlag indicates whether a menu bar should be included in + * the calculations + * @return the x and y offsets of the top left corner of the content pane + * from the top left corner of the JFrame + */ + public static Dimension getOrigin(boolean withMenuFlag) { + if (i == null) + get(withMenuFlag); + return withMenuFlag? topLeftWithMenu: topLeftWithoutMenu; + } // getOrigin() + + /** Returns the Insets object for a JFrame. + * @return the Insets object measuring the size of the borders of a JFrame + */ + public static Insets getFrameInsets() { + if (i == null) + get(false); + return i; + } // getFrameInsets() + +} // class FrameMargins
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/MIDI2Matlab.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,52 @@ +/* + 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.util; + +import java.util.ListIterator; + +class MIDI2Matlab { + + public static void main(String[] args) { + EventList e = EventList.readMidiFile(args[0]); + Event[] events = e.toArray(0x90); + int len = events.length; + double[] pitch = new double[len]; + double[] vel = new double[len]; + double[] onset = new double[len]; + double[] offset = new double[len]; + for (int i=0; i<len; i++) { + pitch[i] = events[i].midiPitch; + vel[i] = events[i].midiVelocity; + onset[i] = events[i].keyDown; + offset[i] = events[i].keyUp; + } + Format.init(1,5,3,false); + System.out.println("notes = zeros(" + len + ",4);"); + Format.matlab(onset, "notes(:,1)", System.out); + Format.matlab(offset, "notes(:,2)", System.out); + Format.matlab(pitch, "notes(:,3)", System.out); + Format.matlab(vel, "notes(:,4)", System.out); + // if (event.midiCommand == 0x90) + // count++; + // else + // System.out.println("*** Other command: " + event.midiCommand); + } // main() +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/MatchTempoMap.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,175 @@ +/* + 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.util; + +import at.ofai.music.util.Format; + +// Stores a list of corresponding performance (real) times and +// notated (score or MIDI) times. Automatically resizes when necessary. +// Very inefficient if the list is edited after being used. +// Assumes a monotonic mapping (t1 > t2 <=> s1 > s2) +public class MatchTempoMap implements TempoMap { + + protected double[] realTime; // in seconds + protected double[] scoreTime; // in beats or MIDI units + protected int[] repeats; // for calculating the average if needed + protected int size; // number of entries being used + + public MatchTempoMap() { + this(5000); // Mozart files are up to 3251 notes + } // default constructor + + public MatchTempoMap(int sz) { + realTime = new double[sz]; + scoreTime = new double[sz]; + repeats = new int[sz]; + size = 0; + } // constructor + + protected void makeSpace() { + if (size == realTime.length) + resize(new MatchTempoMap(2 * size)); + } // makeSpace() + + protected void closeList() { + if (size != realTime.length) + resize(new MatchTempoMap(size)); + } // closeList() + + protected void resize(MatchTempoMap newList) { + for (int i = 0; i < size; i++) { + newList.realTime[i] = realTime[i]; + newList.scoreTime[i] = scoreTime[i]; + newList.repeats[i] = repeats[i]; + } + realTime = newList.realTime; + scoreTime = newList.scoreTime; + repeats = newList.repeats; + } // resize() + + public double toRealTime(double sTime) { + closeList(); + return lookup(sTime, scoreTime, realTime); + } // toRealTime() + + public double toScoreTime(double rTime) { + closeList(); + return lookup(rTime, realTime, scoreTime); + } // toScoreTime() + + public double lookup(double value, double[] domain, double[] range) { + int index = java.util.Arrays.binarySearch(domain, value); + if (index >= 0) + return range[index]; + if ((size == 0) || ((size == 1) && + ((range[0] == 0) || (domain[0] == 0)))) + throw new RuntimeException("Insufficient entries in tempo map"); + if (size == 1) + return value * range[0] / domain[0]; + index = -1 - index; // do linear interpolation + if (index == 0) // unless at ends, where it is extrapolation + index++; + else if (index == size) + index--; + return (range[index] * (value - domain[index - 1]) + + range[index - 1] * (domain[index] - value)) / + (domain[index] - domain[index - 1]); + } // lookup() + + public void add(double rTime, double sTime) { + if (Double.isNaN(sTime)) + return; + makeSpace(); + int index; + for (index = 0; index < size; index++) + if (sTime <= scoreTime[index]) + break; + if ((index == size) || (sTime != scoreTime[index])) { + for (int j = size; j > index; j--) { + scoreTime[j] = scoreTime[j-1]; + realTime[j] = realTime[j-1]; + repeats[j] = repeats[j-1]; + } + size++; + scoreTime[index] = sTime; + realTime[index] = rTime; + repeats[index] = 1; + } else { // average time of multiple nominally simultaneous notes + realTime[index] = (repeats[index] * realTime[index] + rTime) / + (repeats[index] + 1); + repeats[index]++; + } + } // add() + + public void dump(double[] tempo, double step) { + if (size < 2) { + System.err.println("dump() failed: Empty tempo map"); + return; + } + double[] tmp = new double[tempo.length]; + int i = 0; + for (int j = 1; j < size; j++) + for ( ; i * step < realTime[j]; i++) + tmp[i] = (realTime[j] - realTime[j - 1]) / + (scoreTime[j] - scoreTime[j - 1]); + for ( ; i < tmp.length; i++) + tmp[i] = (realTime[size - 1] - realTime[size - 2]) / + (scoreTime[size - 1] - scoreTime[size - 2]); + int window = (int)(0.1 / step); // smooth over 2.0 second window + double sum = 0; + for (i = 0; i < tmp.length; i++) { + sum += tmp[i]; + if (i >= window) { + sum -= tmp[i - window]; + tempo[i] = sum / window; + } else + tempo[i] = sum / (i + 1); + // System.out.println(i + " " + Format.d(tmp[i],3) + + // " " + Format.d(tempo[i], 3)); + if (tempo[i] != 0) + tempo[i] = 60.0 / tempo[i]; + } + } // dump + + public void print() { + System.out.println("Score | Perf.\n-------+-------"); + for (int i = 0; i < size; i++) + System.out.println(Format.d(scoreTime[i], 3) + " | " + + Format.d(realTime[i], 3)); + } // print() + + public static void main(String[] args) { // unit test + TempoMap mtm = new MatchTempoMap(); + mtm.add(0.6, 1); + mtm.add(0.8, 2); + mtm.add(0.95, 2.5); + mtm.add(1.0, 3); + double[] st = {0, 1, 2, 3, 4}; + for (int i = 0 ; i < st.length; i++) + System.out.println(st[i] + " -> " + mtm.toRealTime(st[i]) + + " -> " + mtm.toScoreTime(mtm.toRealTime(st[i]))); + double[] rt = {0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1}; + for (int i = 0 ; i < rt.length; i++) + System.out.println(rt[i] + " => " + mtm.toScoreTime(rt[i]) + + " => " + mtm.toRealTime(mtm.toScoreTime(rt[i]))); + } // main() + +} // class MatchTempoMap
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Matcher.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,242 @@ +/* + 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.util; + +/** A simple parser for Prolog-type notation, but only handling the subset of + * Prolog used in "match" files. + */ +public class Matcher { + + /** The unparsed part of the current line of text */ + protected String s; + + /** The constructor is initialised with the input line of text for parsing*/ + public Matcher(String data) { s = data; } + + /** Reinitialise the parser with a new line of input */ + public void set(String data) { s = data; } + + /** Return the unparsed part of the input line */ + public String get() { return s; } + + /** Returns true if there is input data remaining */ + public boolean hasData() { + return (s != null) && (s.length() > 0); + } // hasData() + + /** Matches a String with the unparsed input data. + * If the complete String occurs at the beginning of the unparsed data, + * the unparsed data is advanced to the end of the String; otherwise + * the data is left unchanged. + * + * @param m the String to match + * @return true if m matches the beginning of the unparsed data + */ + public boolean matchString(String m) { + if (s.startsWith(m)) { + s = s.substring(m.length()); + return true; + } + return false; + } // matchString() + + /** Skips input up to and including the next instance of a given character. + * It is an error for the character not to occur in the data. + * @param c the character to skip to + */ + public void skip(char c) { + int index = s.indexOf(c); + if (index >= 0) + s = s.substring(index + 1); + else + throw new RuntimeException("Parse error in skip(), expecting " + c); + } // skip() + + /** Removes whitespace from the beginning and end of the line. + */ + public void trimSpace() { + s = s.trim(); + } // trimSpace() + + /** Returns and consumes the next character of unparsed data. */ + public char getChar() { + char c = s.charAt(0); + s = s.substring(1); + return c; + } // getChar() + + /** Returns and consumes an int value from the head of the unparsed data. */ + public int getInt() { + int sz = 0; + trimSpace(); + while ((sz < s.length()) && (Character.isDigit(s.charAt(sz)) || + ((sz==0) && (s.charAt(sz) == '-')))) + sz++; + int val = Integer.parseInt(s.substring(0, sz)); + s = s.substring(sz); + return val; + } // getInt() + + /** Returns and consumes a double value, with two limitations: + * 1) exponents are ignored e.g. 5.4e-3 is read as 5.4; + * 2) a value terminated by a 2nd "." causes an Exception to be thrown + */ + public double getDouble() { + int sz = 0; + trimSpace(); + while ((sz < s.length()) && (Character.isDigit(s.charAt(sz)) || + ((sz==0)&&(s.charAt(sz) == '-')) || (s.charAt(sz) == '.'))) + sz++; + double val = Double.parseDouble(s.substring(0, sz)); + s = s.substring(sz); + return val; + } // getDouble() + + /** Returns and consumes a string terminated by the first comma, + * parenthesis, bracket or brace. Equivalent to getString(false). + */ + public String getString() { + return getString(false); + } // getString() + + /** + * Returns and consumes a string terminated by various punctuation symbols. + * Terminators include: '(', '[', '{', ',', '}', ']' and ')'. + * An Exception is thrown if no terminator is found. + * + * @param extraPunctuation Specifies whether '-' and '.' are terminators + */ + public String getString(boolean extraPunctuation) { + char[] stoppers = {'(','[','{',',','}',']',')','-','.'}; + int index1 = s.indexOf(stoppers[0]); + for (int i = 1; i < stoppers.length - (extraPunctuation? 0:2); i++) { + int index2 = s.indexOf(stoppers[i]); + if (index1 >= 0) { + if ((index2 >= 0) && (index1 > index2)) + index1 = index2; + } else + index1 = index2; + } + if (index1 < 0) + throw new RuntimeException("getString(): no terminator: " + s); + String val = s.substring(0, index1); + s = s.substring(index1); + return val; + } // getString() + + /** Returns and consumes a comma-separated list of terms, surrounded by a + * matching set of parentheses, brackets or braces. + * The list may have any number of levels of recursion. + * @return The return value is a linked list of the terms + * (which themselves may be lists or String values) + */ + public ListTerm getList() { + if ("([{".indexOf(s.charAt(0)) >= 0) + return new ListTerm(getChar()); + return null; + } // getList() + + /** Returns and consumes a Prolog-style predicate, consisting of a functor + * followed by an optional list of arguments in parentheses. + */ + public Predicate getPredicate() { + return new Predicate(); + } // getPredicate() + + class Predicate { + + String head; + ListTerm args; + + protected Predicate() { + head = getString(true); + args = getList(); + } + + public Object arg(int index) { + ListTerm t = args; + for (int i = 0; i < index; i++) + t = t.next; + return t.term; + } // arg + + public String toString() { + return (args == null)? head: head + args; + } + + } // inner class Predicate + + class ListTerm { + + Object term; + ListTerm next; + char opener, closer; + + protected ListTerm(char c) { + opener = c; + term = null; + next = null; + if (hasData()) { + switch(s.charAt(0)) { + case '(': + case '[': + case '{': + term = new ListTerm(getChar()); + break; + default: + term = getString(); + break; + } + } + if (hasData()) { + closer = getChar(); + switch(closer) { + case ')': + if (opener == '(') + return; + break; + case ']': + if (opener == '[') + return; + break; + case '}': + if (opener == '{') + return; + break; + case ',': + next = new ListTerm(opener); + return; + } + } + throw new RuntimeException("Parse error in ListTerm(): " + s); + } // constructor + + public String toString() { + String s = "" + opener; + for (ListTerm ptr = this; ptr != null; ptr = ptr.next) + s += ptr.term.toString() + ptr.closer; + return s; + } // toString() + + } // inner class ListTerm + +} // class Matcher +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/PSPrinter.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,171 @@ +/* + 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.util; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +import at.ofai.music.util.Format; + +/** A utility class for converting graphical user interface components to + * PostScript, which can be sent directly to a printer or printed to a file. + * This gives much higher quality illustrations for articles + * than if a screenshot is used, since scaling should not reduce quality. + * The only requirement is that the component to be printed has a + * <code>paint(Graphics)</code> method. + * <p>There are some bugs in this code which require manual editing of + * the PostScript file. First, there doesn't seem to be any way to include + * the bounding box, although it is possible to calculate it. Second, the + * cliprect produced in the PostScript output is wrong. + * (Check: has this been fixed in more recent Java versions? + * Apparently not, as of 1.5.0, but if scaling is not performed, the cliprect + * is OK and the bounding box correct.) + * See {@link PSPrinter#print(Graphics, PageFormat, int)} + */ +public class PSPrinter implements Printable { + + /** the component to be converted */ + Component component; + /** the desired graphical resolution in pixels per inch */ + int resolution; // can't work out how to ask the system + + /** Print a GUI component to a PostScript printer or file. + * The 2 forms of this method are the normal ways of accessing this class. + * This form has problems printing some components. It is recommended to + * use the other version. + * @param c the component to be rendered in PostScript + * @param r the resolution of the printer in pixels per inch + */ + public static void print(Component c, int r) { + new PSPrinter(c, r).doPrint(); + } + + /** Print a GUI component to a PostScript printer or file. + * The 2 forms of this method are the normal ways of accessing this class. + * If no resolution is given, the picture is not scaled, and the cliprect + * is then correct. This is the recommended version to use. + * @param c the component to be rendered in PostScript + */ + public static void print(Component c) { + new PSPrinter(c, -1).doPrint(); + } + + /** Constructs a PSPrinter for a given graphical component and resolution. + * @param c the component to be rendered in PostScript + * @param res the resolution of the printer in pixels per inch; set res to + * -1 for no scaling (avoids the apparently buggy cliprect) + */ + public PSPrinter(Component c, int res) { + component = c; + resolution = res; + } // constructor + + /** Produces a print dialog and executes the requested print job. + * The print job performs its task by callback of the + * {@link PSPrinter#print(Graphics, PageFormat, int)} method. + */ + public void doPrint() { + PrinterJob printJob = PrinterJob.getPrinterJob(); + printJob.setPrintable(this); // tell it where the rendering code is + if (printJob.printDialog()) { + try { + printJob.print(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } // doPrint() + + /** The callback method for performing the printing / Postscript conversion. + * The resulting PostScript file requires some post-editing. + * In particular, there are two problems to be dealt with: + * <p>1) The file has no bounding box. This method prints the correct + * bounding box to stardard output, and it must then be cut and pasted + * into the PostScript file. (There must be a better way!) + * <p>2) The cliprect is wrong (but only if the resolution is specified). + * This is solved by using resolution = -1 or by deleting the lines + * in the PostScript file from <code>newpath</code> to <code>clip</code>. + * (I don't know if this causes problems for components that try to draw + * outside of their area.) + * @param g the graphics object used for painting + * @param f the requested page format (e.g. A4) + * @param pg the page number (must be 0, or we report an error) + * @return the error status; if the page is successfully rendered, + * Printable.PAGE_EXISTS is returned, otherwise if a page number greater + * than 0 is requested, Printable.NO_SUCH_PAGE is returned + * @throws PrinterException thrown when the print job is terminated + */ + public int print(Graphics g, PageFormat f, int pg) throws PrinterException { + if (pg >= 1) + return Printable.NO_SUCH_PAGE; + Graphics2D g2 = (Graphics2D) g; + double wd = component.getWidth(); + double ht = component.getHeight(); + double imwd = f.getImageableWidth(); + double imht = f.getImageableHeight(); + double corr = resolution / 72.0; + double scaleFactor = corr * Math.min(imwd / wd, imht / ht); + double xmin = f.getImageableX(); + double ymin = f.getImageableY(); + AffineTransform scale = new AffineTransform(scaleFactor, 0, + 0, scaleFactor, + corr * xmin, corr * ymin); + Format.setGroupingUsed(false); + double pgHt = f.getHeight(); + if (resolution > 0) { + g2.setTransform(scale); + System.out.println("%%BoundingBox: " + + Format.d(xmin, 0) + " " + + Format.d(pgHt - ymin - ht * scaleFactor / corr, 0) + " " + + Format.d(xmin + wd * scaleFactor / corr, 0) + " " + + Format.d(pgHt - ymin, 0)); + } else { + g2.setClip(0, 0, (int)wd, (int)ht); + System.out.println("%%BoundingBox: " + + Format.d(0, 0) + " " + + Format.d(pgHt - ht, 0) + " " + + Format.d(wd, 0) + " " + + Format.d(pgHt, 0)); + } + + // System.out.println(f.getWidth() + " " + f.getHeight() + " " + + // f.getImageableX() + " " + f.getImageableY() + " " + + // f.getImageableWidth() + " " + f.getImageableHeight()); + // Letter = 8.5x11" 612x792pt DEFAULT + // A4 = 210x297mm 595x842pt + + // AffineTransform scale = new AffineTransform(2.5, 0, 0, 2.5, 200, 1000); + // g2.setTransform(scale); // The figures need some fiddling. + // In particular, the PostScript file has: + // 1) no bounding box (add manually) + // 2) wrong cliprect (delete lines from + // "newpath" to "clip") + component.printAll(g2); + return Printable.PAGE_EXISTS; + } // print() + +} // class PSPrinter
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Parameters.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,328 @@ +/* + 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.util; + +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import at.ofai.music.util.FrameMargins; + +public class Parameters extends JDialog implements ActionListener { + + abstract class Value { + protected JComponent component; + abstract protected Object getValue(); + abstract protected void update(); + } // abstract class Value + + + class ChoiceValue extends Value { + + String[] choices; + int currentChoice; + + protected ChoiceValue(String[] values) { this(values, 0); } + protected ChoiceValue(String[] values, int init) { + choices = values; + currentChoice = init; + component = new JComboBox(values); + ((JComboBox)component).setSelectedIndex(currentChoice); + component.setBackground(colors.getBackground()); + component.setForeground(colors.getForeground()); + } // constructor + + protected Object getValue() { return choices[currentChoice]; } + public String toString() { return choices[currentChoice]; } + + protected void update() { + int tmp = ((JComboBox)component).getSelectedIndex(); + if (tmp >= 0) + currentChoice = tmp; + } // update() + + } // class ChoiceValue + + + class StringValue extends Value { + + String currentValue; + + protected StringValue() { this(""); } + protected StringValue(String init) { + currentValue = init; + component = new JTextField(currentValue); + component.setBackground(colors.getBackground()); + component.setForeground(colors.getForeground()); + } // constructor + + protected Object getValue() { return currentValue; } + public String toString() { return currentValue; } + + protected void update() { + currentValue = ((JTextField)component).getText(); + } // update() + + } // class StringValue + + + class DoubleValue extends Value { + + double currentValue; + + protected DoubleValue() { this(0); } + protected DoubleValue(double init) { + currentValue = init; + component = new JTextField(Double.toString(currentValue)); + component.setBackground(colors.getBackground()); + component.setForeground(colors.getForeground()); + } // constructor + + protected Object getValue() { return new Double(currentValue); } + public String toString() { return "" + currentValue; } + + protected void update() { + try { + double tmp = + Double.parseDouble(((JTextField)component).getText()); + currentValue = tmp; + } catch (NumberFormatException e) {} + } // update() + + } // class DoubleValue + + + class IntegerValue extends Value { + + int currentValue; + + protected IntegerValue() { this(0); } + protected IntegerValue(int init) { + currentValue = init; + component = new JTextField(Integer.toString(currentValue)); + component.setBackground(colors.getBackground()); + component.setForeground(colors.getForeground()); + } // constructor + + protected Object getValue() { return new Integer(currentValue); } + public String toString() { return "" + currentValue; } + + protected void update() { + try { + int tmp = Integer.parseInt(((JTextField)component).getText()); + currentValue = tmp; + } catch (NumberFormatException e) {} + } // update() + + } // class IntegerValue + + + class BooleanValue extends ChoiceValue { + + boolean currentValue; + + protected BooleanValue() { this(true); } + protected BooleanValue(boolean init) { + super(new String[]{"True", "False"}, init? 0: 1); + currentValue = init; + } // constructor + + protected Object getValue() { return new Boolean(currentValue); } + public String toString() { return "" + currentValue; } + + protected void update() { + super.update(); + currentValue = (currentChoice == 0); + } // update() + + } // class BooleanValue + + + protected ArrayMap map; + protected Frame parent; + protected JLabel[] keyFields; + protected JComponent[] valueFields; + protected int sz; + protected Colors colors; + protected JPanel panel1, panel2; + protected JButton okButton, cancelButton; + protected boolean cancelled; + static final long serialVersionUID = 0; + + public Parameters(Frame f, String name) { + this(f, name, new Colors() { + public Color getBackground() { return Color.white; } + public Color getForeground() { return Color.black; } + public Color getButton() { return Color.white; } + public Color getButtonText() { return Color.black; } + }); + } // constructor + + public Parameters(Frame f, String name, Colors c) { + super(f, name, true); + colors = c; + setLocationRelativeTo(f); + Container pane = getContentPane(); + pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); + panel1 = new JPanel(); + panel2 = new JPanel(); + pane.add(panel1); + pane.add(panel2); + panel1.setBackground(colors.getBackground()); + panel2.setBackground(colors.getBackground()); + getRootPane().setBorder( + BorderFactory.createLineBorder(colors.getBackground(), 10)); + map = new ArrayMap(); + okButton = new JButton("OK"); + okButton.setBackground(colors.getButton()); + okButton.setForeground(colors.getButtonText()); + okButton.addActionListener(this); + cancelButton = new JButton("Cancel"); + cancelButton.setBackground(colors.getButton()); + cancelButton.setForeground(colors.getButtonText()); + cancelButton.addActionListener(this); + parent = f; + cancelled = false; + setVisible(false); + } // constructor + + public void print() { + sz = map.size(); + System.out.println("at.ofai.music.util.Parameters: size = " + sz); + for (int i = 0; i < sz; i++) { + ArrayMap.Entry e = map.getEntry(i); + System.out.println(e.getKey() + " : " + e.getValue()); + } + } // print() + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == okButton) { + for (int i = 0; i < sz; i++) + ((Value)map.getEntry(i).getValue()).update(); + cancelled = false; + } else + cancelled = true; + setVisible(false); + } + + public boolean wasCancelled() { + return cancelled; + } + + public void setVisible(boolean flag) { + if (!flag) { + super.setVisible(false); + return; + } + sz = map.size(); + keyFields = new JLabel[sz]; + valueFields = new JComponent[sz]; + panel1.removeAll(); + panel2.removeAll(); + panel1.setLayout(new GridLayout(sz + 1, 1, 10, 5)); + panel2.setLayout(new GridLayout(sz + 1, 1, 10, 5)); + for (int i = 0; i < sz; i++) { + ArrayMap.Entry e = map.getEntry(i); + keyFields[i] = new JLabel((String) e.getKey()); + panel1.add(keyFields[i]); + valueFields[i] = (JComponent) ((Value)e.getValue()).component; + panel2.add(valueFields[i]); + } + panel1.add(okButton); + panel2.add(cancelButton); + pack(); + Dimension dim = getContentPane().getSize(); + Dimension margins = FrameMargins.get(false); + int wd = dim.width + margins.width + 20; + int ht = dim.height + margins.height + 20; + int x = 0; + int y = 0; + if (parent != null) { + x = parent.getLocation().x + (parent.getWidth() - wd) / 2; + y = parent.getLocation().y + (parent.getHeight() - ht) / 2; + } + // System.out.println("wd=" + wd + " ht=" + ht + " loc=" + x + "," + y); + // java version "1.3.0rc1" has bugs in location/size with fvwm2 + // super.setLocation(-wd/2, -ht/2); // x, y); + super.setLocation(x, y); + super.setSize(wd, ht); + super.setVisible(true); + } // setVisible() + + public boolean contains(String key) { + return map.containsKey(key); + } // contains() + + public String getString(String key) { + return ((StringValue)map.get(key)).currentValue; + } // getString() + + public double getDouble(String key) { + return ((DoubleValue)map.get(key)).currentValue; + } // getDouble() + + public int getInt(String key) { + return ((IntegerValue)map.get(key)).currentValue; + } // getInt() + + public boolean getBoolean(String key) { + return ((BooleanValue)map.get(key)).currentValue; + } // getBoolean() + + public String getChoice(String key) { + return (String) ((ChoiceValue)map.get(key)).getValue(); + } // getChoice() + + public void setString(String key, String value) { + map.put(key, new StringValue(value)); + } // setString() + + public void setDouble(String key, double value) { + map.put(key, new DoubleValue(value)); + } // setDouble() + + public void setInt(String key, int value) { + map.put(key, new IntegerValue(value)); + } // setInt() + + public void setBoolean(String key, boolean value) { + map.put(key, new BooleanValue(value)); + } // setBoolean() + + public void setChoice(String key, String[] choices, int value) { + map.put(key, new ChoiceValue(choices, value)); + } // setChoice() + +} // class Parameters
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Peaks.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,227 @@ +/* + 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.util; + +import java.util.LinkedList; + +public class Peaks { + + public static boolean debug = false; + public static int pre = 3; + public static int post = 1; + + /** General peak picking method for finding n local maxima in an array + * @param data input data + * @param peaks list of peak indexes + * @param width minimum distance between peaks + */ + public static int findPeaks(double[] data, int[] peaks, int width) { + int peakCount = 0; + int maxp = 0; + int mid = 0; + int end = data.length; + while (mid < end) { + int i = mid - width; + if (i < 0) + i = 0; + int stop = mid + width + 1; + if (stop > data.length) + stop = data.length; + maxp = i; + for (i++; i < stop; i++) + if (data[i] > data[maxp]) + maxp = i; + if (maxp == mid) { + int j; + for (j = peakCount; j > 0; j--) { + if (data[maxp] <= data[peaks[j-1]]) + break; + else if (j < peaks.length) + peaks[j] = peaks[j-1]; + } + if (j != peaks.length) + peaks[j] = maxp; + if (peakCount != peaks.length) + peakCount++; + } + mid++; + } + return peakCount; + } // findPeaks() + + /** General peak picking method for finding local maxima in an array + * @param data input data + * @param width minimum distance between peaks + * @param threshold minimum value of peaks + * @return list of peak indexes + */ + public static LinkedList<Integer> findPeaks(double[] data, int width, + double threshold) { + return findPeaks(data, width, threshold, 0, false); + } // findPeaks() + + /** General peak picking method for finding local maxima in an array + * @param data input data + * @param width minimum distance between peaks + * @param threshold minimum value of peaks + * @param decayRate how quickly previous peaks are forgotten + * @param isRelative minimum value of peaks is relative to local average + * @return list of peak indexes + */ + public static LinkedList<Integer> findPeaks(double[] data, int width, + double threshold, double decayRate, boolean isRelative) { + LinkedList<Integer> peaks = new LinkedList<Integer>(); + int maxp = 0; + int mid = 0; + int end = data.length; + double av = data[0]; + while (mid < end) { + av = decayRate * av + (1 - decayRate) * data[mid]; + if (av < data[mid]) + av = data[mid]; + int i = mid - width; + if (i < 0) + i = 0; + int stop = mid + width + 1; + if (stop > data.length) + stop = data.length; + maxp = i; + for (i++; i < stop; i++) + if (data[i] > data[maxp]) + maxp = i; + if (maxp == mid) { + if (overThreshold(data, maxp, width, threshold, isRelative,av)){ + if (debug) + System.out.println(" peak"); + peaks.add(new Integer(maxp)); + } else if (debug) + System.out.println(); + } + mid++; + } + return peaks; + } // findPeaks() + + public static double expDecayWithHold(double av, double decayRate, + double[] data, int start, int stop) { + while (start < stop) { + av = decayRate * av + (1 - decayRate) * data[start]; + if (av < data[start]) + av = data[start]; + start++; + } + return av; + } // expDecayWithHold() + + public static boolean overThreshold(double[] data, int index, int width, + double threshold, boolean isRelative, + double av) { + if (debug) + System.out.printf("%4d : %6.3f Av1: %6.3f ", + index, data[index], av); + if (data[index] < av) + return false; + if (isRelative) { + int iStart = index - pre * width; + if (iStart < 0) + iStart = 0; + int iStop = index + post * width; + if (iStop > data.length) + iStop = data.length; + double sum = 0; + int count = iStop - iStart; + while (iStart < iStop) + sum += data[iStart++]; + if (debug) + System.out.printf(" %6.3f %6.3f ", sum / count, + data[index] - sum / count - threshold); + return (data[index] > sum / count + threshold); + } else + return (data[index] > threshold); + } // overThreshold() + + public static void normalise(double[] data) { + double sx = 0; + double sxx = 0; + for (int i = 0; i < data.length; i++) { + sx += data[i]; + sxx += data[i] * data[i]; + } + double mean = sx / data.length; + double sd = Math.sqrt((sxx - sx * mean) / data.length); + if (sd == 0) + sd = 1; // all data[i] == mean -> 0; avoids div by 0 + for (int i = 0; i < data.length; i++) { + data[i] = (data[i] - mean) / sd; + } + } // normalise() + + /** Uses an n-point linear regression to estimate the slope of data. + * @param data input data + * @param hop spacing of data points + * @param n length of linear regression + * @param slope output data + */ + public static void getSlope(double[] data, double hop, int n, + double[] slope) { + int i = 0, j = 0; + double t; + double sx = 0, sxx = 0, sy = 0, sxy = 0; + for ( ; i < n; i++) { + t = i * hop; + sx += t; + sxx += t * t; + sy += data[i]; + sxy += t * data[i]; + } + double delta = n * sxx - sx * sx; + for ( ; j < n / 2; j++) + slope[j] = (n * sxy - sx * sy) / delta; + for ( ; j < data.length - (n + 1) / 2; j++, i++) { + slope[j] = (n * sxy - sx * sy) / delta; + sy += data[i] - data[i - n]; + sxy += hop * (n * data[i] - sy); + } + for ( ; j < data.length; j++) + slope[j] = (n * sxy - sx * sy) / delta; + } // getSlope() + + public static double min(double[] arr) { return arr[imin(arr)]; } + + public static double max(double[] arr) { return arr[imax(arr)]; } + + public static int imin(double[] arr) { + int i = 0; + for (int j = 1; j < arr.length; j++) + if (arr[j] < arr[i]) + i = j; + return i; + } // imin() + + public static int imax(double[] arr) { + int i = 0; + for (int j = 1; j < arr.length; j++) + if (arr[j] > arr[i]) + i = j; + return i; + } // imax() + +} // class Peaks
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/Profile.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,61 @@ +/* + 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.util; + +public class Profile { + + public static final int MAX_SIZE = 20; + private static long[] tmin = new long[MAX_SIZE]; + private static long[] tmax = new long[MAX_SIZE]; + private static long[] tsum = new long[MAX_SIZE]; + private static long[] tprev = new long[MAX_SIZE]; + private static int[] tcount = new int[MAX_SIZE]; + + public static void report(int i) { + if ((i < 0) || (i >= MAX_SIZE) || (tcount[i] == 0)) + return; + System.err.println("Profile " + i + ": " + tcount[i] + " calls; " + + (tmin[i]/1000.0) + " - " + (tmax[i]/1000.0) + "; Av: " + + (tsum[i] / tcount[i] / 1000.0)); + } // report() + + public static void report() { + for (int i = 0; i < MAX_SIZE; i++) + report(i); + } // report() + + public static void start(int i) { + tprev[i] = System.nanoTime(); + } // start() + + public static void log(int i) { + long tmp = System.nanoTime(); + long t = (tmp - tprev[i]) / 1000; + tprev[i] = tmp; + tsum[i] += t; + if ((tcount[i] == 0) || (t > tmax[i])) + tmax[i] = t; + if ((tcount[i] == 0) || (t < tmin[i])) + tmin[i] = t; + tcount[i]++; + } // log() + +} // class Profile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/RandomAccessInputStream.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,128 @@ +/* + 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.util; +import java.io.*; + +public class RandomAccessInputStream extends InputStream { + + protected RandomAccessFile r; + protected long markPosition = 0; + + public RandomAccessInputStream(String name) throws FileNotFoundException { + r = new RandomAccessFile(name, "r"); + } // constructor + + public RandomAccessInputStream(File f) throws FileNotFoundException { + r = new RandomAccessFile(f, "r"); + } // constructor + + /** Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ + public int available() throws IOException { + long availableBytes = r.length() - r.getFilePointer(); + if (availableBytes > Integer.MAX_VALUE) + return Integer.MAX_VALUE; + else + return (int)availableBytes; + } // available() + + /** Closes this input stream and releases any system resources associated + * with the stream. + */ + public void close() throws IOException { + r.close(); + } // close() + + /** Marks the current position in this input stream. + * Warning: Use mark() instead of mark(int). + * IOExceptions are caught, because InputStream doesn't allow them to be + * thrown. The exception is printed and the mark position invalidated. + * @param readlimit Ignored + */ + public void mark(int readlimit) { + try { + mark(); + } catch (IOException e) { + e.printStackTrace(); + markPosition = -1; + } + } // mark() + + /** Marks the current position in this input stream. + */ + public void mark() throws IOException { + markPosition = r.getFilePointer(); + } // mark() + + /** This input stream supports the mark and reset methods. + * @return true + */ + public boolean markSupported() { + return true; + } // markSupported() + + /** Reads the next byte of data from the input stream. + */ + public int read() throws IOException { + return r.read(); + } // read() + + /** Reads some number of bytes from the input stream and stores them into + * the buffer array b. + */ + public int read(byte[] b) throws IOException { + return r.read(b); + } // read() + + /** Reads up to len bytes of data from the input stream into an array of + * bytes. + */ + public int read(byte[] b, int off, int len) throws IOException { + return r.read(b, off, len); + } // read() + + /** Repositions this stream to the position at the time the mark method + * was last called on this input stream. + */ + public void reset() throws IOException { + if (markPosition < 0) + throw new IOException("reset(): invalid mark position"); + r.seek(markPosition); + } // reset() + + /** Skips over and discards n bytes of data from this input stream. + */ + public long skip(long n) throws IOException { + long pos = r.getFilePointer(); + r.seek(n + pos); + return r.getFilePointer() - pos; + } // skip() + + /** Seek to a position n bytes after the mark. + */ + public long seekFromMark(long n) throws IOException { + r.seek(markPosition + n); + return r.getFilePointer() - markPosition; + } // seekFromMark() + +} // class RandomAccessInputStream
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/TempoMap.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,27 @@ +/* + 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.util; + +interface TempoMap { + double toRealTime(double scoreTime); + double toScoreTime(double realTime); + void add(double time, double value); +} // TempoMap
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/util/WormEvent.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,44 @@ +/* + 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.util; + +public class WormEvent extends Event { + + public double tempo; + public double loudness; + public String label; + + public WormEvent(double on, double off, double eoff, int pitch, int vel, + double beat, double dur, int flags, int cmd, int ch, int tr) { + super(on, off, eoff, pitch, vel, beat, dur, flags, cmd, ch, tr); + tempo = -1; + loudness = -1; + label = null; + } // WormEvent + + public WormEvent(double time, double t, double l, double beat, int flags) { + super(time, 0, 0, 0, 0, beat, 0, flags); + tempo = t; + loudness = l; + label = null; + } // constructor + +} // class WormEvent
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/AudioWorm.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,658 @@ +/* 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.FileNotFoundException; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.TargetDataLine; +import javax.sound.sampled.UnsupportedAudioFileException; + +import java.net.URL; +import at.ofai.music.util.Format; +import at.ofai.music.util.EventList; + +/** AudioWorm is the class that does the hard work. + * The constructor initialises the audio objects which process the data. + * Each call to nextBlock() reads in a new block of data and sends it to + * the output device, as well as processing it and sending a new tempo + * estimate to the Worm object. If the output buffer is not too low, the + * the Worm object is asked to update its display and the audio processing + * sleeps for 75ms to allow this to happen. + **/ +public class AudioWorm { + + Worm gui; // The object that displays the data + AudioInputStream in; // Input stream (from WAV file or sound card) + AudioInputStream orig = null; + boolean isConverting = false; + TargetDataLine targetDataLine; + boolean isFileInput; + AudioFormat inputFormat; + SourceDataLine out; + AudioFormat outputFormat; + int outputBufferSize; + int frameSize; + double frameRate; + int channels; + int sampleSizeInBytes; + static final float defaultSampleRate = 44100; + // for kiefer's sound card: 44101.0F instead of 44100 + static double windowTime = 0.010; + static int averageCount = 10; + static int fileDelay = 180; // for liszt; 120 for bach; 70 for Schubert + int windowSize; + double normalise; + byte[] inputBuffer; + int bytesRead; + int blockCount; + TempoInducer ti; + WormFile wormData; + String audioFileName, audioFilePath; + File audioFile; + URL audioURL; + long bytePosition; // Number of bytes that have been read from input file + long jumpPosition; // Requested new bytePosition (or -1 for none) + long fileLength; // Length of input file in bytes + + public AudioWorm(Worm w) { + gui = w; + jumpPosition = -1; + targetDataLine = null; + // Input from audio file, with optional matchFile data + String matchFile = w.getMatchFile(); + if ((matchFile != null) && !matchFile.equals("")) { + try { + EventList.setTimingCorrection(w.getTimingOffset()); + wormData = new WormFile(w, EventList.readMatchFile(matchFile)); + if (Math.abs(windowTime * averageCount - + wormData.outFramePeriod) > 1e-5) + throw new Exception("Incompatible parameters in AudioWorm"); + wormData.smooth(Worm.FULL_GAUSS, 1, 1, 0); + } catch (Exception e) { + e.printStackTrace(); + wormData = null; + } + } else { + wormData = w.getWormFile(); + } + ti = new TempoInducer(windowTime); + audioFile = null; + audioURL = null; + audioFileName = w.getInputFile(); + audioFilePath = w.getInputPath(); + if (audioFileName == null) + audioFileName = ""; + if (audioFilePath == null) + audioFilePath = ""; + isFileInput = (!audioFileName.equals("")); + if (!isFileInput) { + initSoundCardInput(w); + return; + } + if (audioFileName.startsWith("http:")) + try { + audioURL = new URL(audioFileName); + } catch (java.net.MalformedURLException e) { + e.printStackTrace(); + } + else { + audioFile = new File(audioFileName); + if (!audioFile.isFile()) + audioFile = new File(audioFilePath + audioFileName); + if (!audioFile.isFile()) // local hacks for UNC file names + Windows + audioFile = new File("//fichte" + audioFileName); + if (!audioFile.isFile()) + audioFile = new File("//fichte" + audioFilePath +audioFileName); + } + resetAudioFile(); + init(gui); + if ((wormData != null) && (wormData.time[0] > 0.5)) + skipTo(wormData.time[0] - 0.5); + } // AudioWorm constructor + + protected void resetAudioFile() { + try { + isConverting = false; + orig = null; + if (audioFile == null) + in = AudioSystem.getAudioInputStream(audioURL); + else { + if (!audioFile.isFile()) + throw(new FileNotFoundException("No file: "+audioFileName)); + in = AudioSystem.getAudioInputStream(audioFile); + } + inputFormat = in.getFormat(); + if (inputFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) { + AudioFormat desiredFormat = new AudioFormat( + AudioFormat.Encoding.PCM_SIGNED, + inputFormat.getSampleRate(), + 16, + inputFormat.getChannels(), + inputFormat.getChannels() * 2, + inputFormat.getSampleRate(), + false); + // System.out.println("Old Format\n" + inputFormat + "\n\n" + + // "Desired Format\n" + desiredFormat); + orig = in; + in = AudioSystem.getAudioInputStream(desiredFormat, orig); + inputFormat = in.getFormat(); + // System.out.println("New Format\n" + inputFormat); + isConverting = true; + } + fileLength = in.available(); // note this is 0 for mp3 files + // System.out.println("Bytes available: " + fileLength); + bytePosition = 0; + bytesRead = 0; + blockCount = 0; + } catch (IOException e) { // includes FileNotFoundException + e.printStackTrace(); + in = null; + } catch (IllegalArgumentException e) { // conversion not supported + e.printStackTrace(); + in = null; + } catch (UnsupportedAudioFileException e) { + e.printStackTrace(); + in = null; + } + } // resetAudioFile() + + // Live input from microphone, line in, etc (selected by external mixer) + protected void initSoundCardInput(Worm w) { + //System.out.println("Entering initSoundCardInput()"); + if (in != null) { + try { + in.close(); + //System.out.println("in CLOSED"); + } catch (Exception e) {} + in = null; + } + if (targetDataLine != null) { + try { + targetDataLine.close(); + //System.out.println("targetDataLine CLOSED"); + } catch (Exception e) {} + targetDataLine = null; + } + inputFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, + defaultSampleRate, 16, 2, 4, defaultSampleRate, false); + Mixer.Info[] mInfo = AudioSystem.getMixerInfo(); + System.out.println("Number of mixers: " + mInfo.length); + for (int i = 0; i < mInfo.length; i++) { + System.out.println("Mixer info : " + mInfo[i]); + Mixer t = AudioSystem.getMixer(mInfo[i]); + Line.Info[] li = t.getTargetLineInfo(); // get input devices + Class c; + System.out.println("Number of target lines: " + li.length); + for (int j = 0; j < li.length; j++) { // find one that matches + System.out.println("Line info: " + li[j]); + c = li[j].getLineClass(); + AudioFormat[] af = ((DataLine.Info)li[j]).getFormats(); + for (int k = 0; k < af.length; k++) { + // some sound cards support 44101 Hz but not 44100 Hz! + double err = checkAudioFormats(af[k], inputFormat); + if (err < 0.01) { // match OK (allow up to 1% error) + if (err >= 0) // sample rate is within 1% + inputFormat = af[k]; + // else device accepts any sample rate; use default + DataLine.Info info = new DataLine.Info(c, inputFormat); + try { + System.out.println("Getting line with " + info); + if (AudioSystem.getLine(info) + instanceof TargetDataLine){ + targetDataLine = null; + targetDataLine = + (TargetDataLine)AudioSystem.getLine(info); + System.out.println("Opening line ... "); + targetDataLine.open(inputFormat); // , 16384); + // buffer size request ignored + // default size: bach 65536; kiefer 16384 + // System.out.println("Buffer: " + + // targetDataLine.getBufferSize()); + System.out.println("Creating AudioInputStream"); + in = new AudioInputStream(targetDataLine); + init(w); + return; + } + } catch (Exception e) { + System.err.println("Unable to open input line"); + e.printStackTrace(); + System.exit(1); + } + } + } + } + } + throw new RuntimeException("No suitable input line found"); + } // initSoundCardInput() + + /** Normal initialisation of AudioWorm for reading from a PCM file + or for reading compressed data which is uncompressed by AudioSystem. + **/ + protected void init(Worm w) { + if (out != null) + out.close(); + gui = w; + gui.setDelay(isFileInput? fileDelay / averageCount: 0); + gui.setFramePeriod(averageCount * windowTime); + try { + if (inputFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) + throw new UnsupportedAudioFileException("Not PCM_SIGNED but " + + // System.out.println("Input format: " + + inputFormat.getEncoding()); + frameSize = inputFormat.getFrameSize(); + frameRate = inputFormat.getFrameRate(); + channels = inputFormat.getChannels(); + sampleSizeInBytes = frameSize / channels; + windowSize = (int)(windowTime * frameRate); + normalise = (double) channels * windowSize * + (1 << (inputFormat.getSampleSizeInBits() - 1)); + inputBuffer = new byte[windowSize * frameSize]; + bytePosition = 0; + bytesRead = 0; + blockCount = 0; + if (!isFileInput) + return; + Mixer.Info[] mInfo = AudioSystem.getMixerInfo(); + for (int i = 0; i < mInfo.length; i++) { + Mixer t = AudioSystem.getMixer(mInfo[i]); + Line.Info[] li = t.getSourceLineInfo(); // get output devices + Class c; + for (int j = 0; j < li.length; j++) { // find one that matches + c = li[j].getLineClass(); + AudioFormat[] af = ((DataLine.Info)li[j]).getFormats(); + for (int k = 0; k < af.length; k++) { + // some sound cards support 44101 Hz but not 44100 Hz! + double err = checkAudioFormats(af[k], inputFormat); + if (err < 0.01) { // match OK (allow up to 1% error) + if (err < 0) // device accepts any sample rate + outputFormat = inputFormat; + else // sample rate is within 1% + outputFormat = af[k]; + outputBufferSize = (int)outputFormat.getFrameRate() + * frameSize * 1; // 1 sec bf + DataLine.Info info = new DataLine.Info(c, + outputFormat, outputBufferSize); + if (AudioSystem.getLine(info) instanceof + SourceDataLine) { + out = (SourceDataLine)AudioSystem.getLine(info); + out.open(); + return; // accept the first one we find + } + } + } + } + } + throw new LineUnavailableException("Unable to find output device" + + " matching:\n\t" + inputFormat); + } catch (LineUnavailableException e) { + e.printStackTrace(); + System.exit(1); + } catch (UnsupportedAudioFileException e) { + e.printStackTrace(); + System.exit(1); + } + } // init() + + public void start() { + //System.out.println("Start called");//DEBUG + if (isFileInput) { + gui.setDelay(fileDelay / averageCount); + out.start(); + } else { + //System.out.println("Flushing targetDataLine");//DEBUG + targetDataLine.flush(); + //System.out.println("Restarting targetDataLine");//DEBUG + targetDataLine.start(); + } + //System.out.println("Start completed");//DEBUG + } // start() + + public void pause() { + //System.out.println("Pause called");//DEBUG + if (isFileInput) + out.stop(); + else + targetDataLine.stop(); + //System.out.println("Pause completed");//DEBUG + } // pause() + + public void stop() { + //System.out.println("Stop called");//DEBUG + if (isFileInput) { + out.stop(); + out.flush(); + } else { + targetDataLine.stop(); + //System.out.println("Flushing targetDataLine");//DEBUG + targetDataLine.flush(); + } + //System.out.println("Stop completed");//DEBUG + } // stop() + + public boolean nextBlock() throws IOException { + double rms = 0, tempo = 0; + for (int i = 0; i < averageCount; i++) { // 5 => 20 FPS + int waitCount = 1;//D + while ((in.available() < inputBuffer.length) && !isFileInput) { + try { + int before = in.available(); + //System.out.print("WAIT: "+(waitCount++)+" "+before);//DEBG + Thread.sleep((int)(1000.0 * windowTime)); + int after = in.available(); + //System.out.println(" " + after + + // "; input line active/running: " + + // targetDataLine.isActive() + "/" + + // targetDataLine.isRunning()); + if ((waitCount > 5) && (before == after)) { + // bytesRead = in.read(inputBuffer); // HANGS HERE + // System.out.println("Read(): " + bytesRead + + // " " + in.available()); + break; + } + if ((waitCount > 3) && (!targetDataLine.isActive() || + !targetDataLine.isRunning())) + return false; + } catch (InterruptedException e) {} + } + long avail = (isConverting? orig.available(): in.available()); + // This isn't really correct for uncompressing, since the number + // of bytes will be different after uncompression, but it is a + // hack to get around the fact that in.available() returns 0. + if (avail >= inputBuffer.length) { + double tmp = processWindow(); + if (bytesRead < 0) { + System.err.println("nextBlock(): Audio read error"); + return false; + } + rms += tmp * tmp; + if (wormData == null) + tempo = ti.getTempo(tmp); // rms amp or dB? + // tempo = ti.getTempo(120 + 20 / Math.log(10) * Math.log(tmp)); + if (isFileInput) { + if (ti.onset) // mark detected onsets with a click + for (int j = 0; j < 882; ) { + inputBuffer[j++] = (byte)(100.0 * + Math.sin(2.0*Math.PI*j/441.0)); + inputBuffer[j++] = 0; + } + int chk = out.write(inputBuffer,0,bytesRead); + // if (chk != bytesRead) { // shouldn't happen; write() blocks + // System.err.println("Problem writing " + bytesRead + + // " bytes. Only " + chk +" written."); + // } + } + blockCount++; + } else { + System.err.println("nextBlock(): Audio data not available"); + return false; + } + } + double dB = Math.max(0, 120 + 20 / Math.log(10) * + Math.log(Math.sqrt(rms / averageCount))); + int index = (blockCount - 1) / averageCount; + if (wormData != null) { + gui.scrollBar.setValueNoFeedback( // don't want to call skipAudio() + (int)(1000 * bytePosition / fileLength)); + if (index >= wormData.outTempo.length) { + // System.err.println("DEBUG: end of wormfile"); + // System.err.println("DEBUG: " + blockCount + + // " " + index + + // " " + wormData.outIntensity[index]); + return false; + } + gui.addPoint(wormData.outTempo[index], wormData.outIntensity[index], + wormData.label[index]); + } else { + gui.addPoint(60 / tempo, dB, Format.d((blockCount-1)*windowTime,1)); + // System.out.println(Format.d(tempo,3) + " " + Format.d(dB, 3)); + } + if (!isFileInput) { + gui.repaint(); + return true; + } + int space = out.available(); + double buffContents = (double)(outputBufferSize - space) * + windowTime / inputBuffer.length; + // System.out.println("dB = " + Format.d(dB) + + // " In buffer = " + Format.d(buffContents) + + // " sec Space left = " + space + + // " bytes Size = " + outputBufferSize); + if (buffContents > 0.1) { // shouldn't starve? + gui.repaint(); + try { // Give it some time to paint. + Thread.sleep(75); + } catch (InterruptedException e) {} + // According to System.currentTimeMillis(), paint() takes ~16ms, + // but it updates much smoother (i.e. calls paint() more frequently) + // when the value is higher. Not sure how to convince it otherwise. + } + return true; + } // nextBlock() + + /** Reads a block of audio data, summing the channels and returning the + * normalised (in the range 0.0-1.0) RMS average of the sample values. + * Assumes sufficient data is queued, or else blocks while it waits + * for the buffer to fill sufficiently. + **/ + protected double processWindow() throws IOException { + if (jumpPosition >= 0) + skipAudio(); + bytesRead = in.read(inputBuffer); + bytePosition += bytesRead; + // System.out.println("read(): " + bytePosition);//DEBUG + if (wormData != null) // only need to play audio; no calculation + return 0; + long sample; + double sum = 0; + if (sampleSizeInBytes == 1) { // 8 bit samples + if (channels == 1) { + for (int i = 0; i < bytesRead; i += frameSize) { + sample = ((int)inputBuffer[i]); + sum += (double)(sample * sample); + } + } else if (channels == 2) { + for (int i = 0; i < bytesRead; i += frameSize) { + sample = ((int)inputBuffer[i]) + + ((int)inputBuffer[i+1]); + sum += (double)(sample * sample); + } + } else { + for (int i = 0; i < bytesRead; ) { + sample = 0; + for (int c = 0; c < channels; c++, i++) + sample += ((int)inputBuffer[i]); + sum += (double)(sample * sample); + } + } + } else if (sampleSizeInBytes == 2) { // 16 bit samples + if (inputFormat.isBigEndian()) { + if (channels == 1) { + for (int i = 0; i < bytesRead; i += frameSize) { + sample = ((int)inputBuffer[i] << 8) | + ((int)inputBuffer[i+1] & 0xFF); + sum += (double)(sample * sample); + } + } else if (channels == 2) { + for (int i = 0; i < bytesRead; i += frameSize) { + sample = (((int)inputBuffer[i] << 8) | + ((int)inputBuffer[i+1] & 0xFF)) + + (((int)inputBuffer[i+2] << 8) | + ((int)inputBuffer[i+3] & 0xFF)); + sum += (double)(sample * sample); + } + } else { + for (int i = 0; i < bytesRead; ) { + sample = 0; + for (int c = 0; c < channels; c++) { + sample += ((int)inputBuffer[i] << 8) | + ((int)inputBuffer[i+1] & 0xFF); + i += 2; + } + sum += (double)(sample * sample); + } + } + } else { // little-endian + if (channels == 1) { + for (int i = 0; i < bytesRead; i += frameSize) { + sample = ((int)inputBuffer[i+1] << 8) | + ((int)inputBuffer[i] & 0xFF); + sum += (double)(sample * sample); + } + } else if (channels == 2) { + for (int i = 0; i < bytesRead; i += frameSize) { + sample = (((int)inputBuffer[i+1] << 8) | + ((int)inputBuffer[i] & 0xFF)) + + (((int)inputBuffer[i+3] << 8) | + ((int)inputBuffer[i+2] & 0xFF)); + sum += (double)(sample * sample); + } + } else { + for (int i = 0; i < bytesRead; ) { + sample = 0; + for (int c = 0; c < channels; c++) { + sample += ((int)inputBuffer[i+1] << 8) | + ((int)inputBuffer[i] & 0xFF); + i += 2; + } + sum += (double)(sample * sample); + } + } + } + } else { // not 8-bit or 16-bit samples + for (int i = 0; i < bytesRead; ) { + long longSample = 0; + for (int c = 0; c < channels; c++) { + if (inputFormat.isBigEndian()) { + sample = (int)inputBuffer[i++]; + for (int b = 1; b < sampleSizeInBytes; b++, i++) + sample = (sample<<8) | ((int)inputBuffer[i] & 0xFF); + } else { + sample = 0; + int b; + for (b = 0; b < sampleSizeInBytes-1; b++, i++) + sample |= ((int)inputBuffer[i] & 0xFF) << (b * 8); + sample |= ((int)inputBuffer[i++]) << (b * 8); + } + longSample += sample; + } + sum += (double)longSample * (double)longSample; + } + } + return Math.sqrt(sum) / normalise; + } // processWindow() + + /** Returns the relative difference |a-b| / ((a+b)/2) in sample rates, + * or 2.0 if the formats do not match in some other characteristic, + * or -1 if AudioFormat `out' allows all sampling rates; + * normal return value is in [0,2). + **/ + public static double checkAudioFormats(AudioFormat out, AudioFormat in) { + if ((out.getChannels() != in.getChannels()) || + (out.getSampleSizeInBits() != in.getSampleSizeInBits()) || + (out.getEncoding() != in.getEncoding()) || + (out.isBigEndian() != in.isBigEndian()) || + (out.getSampleRate() == 0.0F)) + return 2.0; + if (out.getSampleRate() < 0) + return -1.0; + return Math.abs(2.0 * (out.getSampleRate() - in.getSampleRate()) / + (out.getSampleRate() + in.getSampleRate())); + } // checkAudioFormats() + + /** Returns the RMS average of an array of double. + **/ + public static double rms(double[] data) { + double sum = 0; + for (int i = 0; i < data.length; i++) + sum += data[i] * data[i]; + return Math.sqrt(sum / (double)data.length); + } // rms() + + /** Reposition the audio file input to a new point in seconds. + **/ + public void skipTo(double time) { + jumpPosition = Math.round(time * frameRate) * frameSize; + if (jumpPosition > fileLength) + jumpPosition = fileLength; + } // skipTo() + + /** Reposition the audio file input (from Scrollbar) on a scale of + * 0 (beginning of file) to 1000 (end of file). + **/ + public void skipTo(int thousandths) { + jumpPosition = fileLength / frameSize * thousandths / 1000 * frameSize; + } // skipTo() + + /** For input from an audio file, performs repositioning within the file, + * after a call to the public method skipTo(). + **/ + protected void skipAudio() { + // gui.pause(); + long toSkip = jumpPosition; + long hasSkipped = 0; + if (jumpPosition >= bytePosition) // fast forward + toSkip -= bytePosition; + else if (jumpPosition < bytePosition) // rewind + resetAudioFile(); + try { + while (toSkip > hasSkipped) { // due to buffering, skip() must be + // called multiple times before it reaches the requested spot + // (see JavaSound mailing list Item #4658 (5 Nov 2000 20:58)) + long skipped = in.skip(toSkip - hasSkipped); + if (skipped <= 0) + throw new IOException("skip() error: "+skipped+" returned"); + hasSkipped += skipped; + } + } catch (IOException e) { + e.printStackTrace(); + } + bytePosition += hasSkipped; + if (out != null) + out.flush(); + int currentPoint = (blockCount - 1) / averageCount; + blockCount = (int) (bytePosition / (long)inputBuffer.length); + if (wormData != null) { + int stop = Math.min(wormData.outTempo.length, + (blockCount - 1) / averageCount); + int start = Math.max(stop - WormConstants.wormLength, 0); + if (currentPoint < stop) + start = Math.max(start, currentPoint); + else + gui.clear(); + for (int index = start; index < stop; index++) + gui.addPoint(wormData.outTempo[index], + wormData.outIntensity[index], wormData.label[index]); + gui.repaint(); + } + jumpPosition = -1; + } // skipAudio() + +} // class AudioWorm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/MyFileChooser.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,65 @@ +/* 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 javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import java.awt.TextField; +import java.io.File; +import javax.swing.filechooser.FileFilter; + +public class MyFileChooser extends JFileChooser { + + static final long serialVersionUID = 0; // silence compiler warning + + public MyFileChooser() { + super("."); + addChoosableFileFilter(MyFileFilter.mp3Filter); + addChoosableFileFilter(MyFileFilter.waveFilter); + addChoosableFileFilter(MyFileFilter.matchFilter); + addChoosableFileFilter(MyFileFilter.wormFilter); + addChoosableFileFilter(getAcceptAllFileFilter()); + } // constructor + + public String browseOpen(TextField t, FileFilter ff) { + setSelectedFile(new File(t.getText())); + setFileFilter(ff); + if (showOpenDialog(null) == JFileChooser.APPROVE_OPTION) + t.setText(getSelectedFile().getAbsolutePath()); + return t.getText(); + } // browseOpen() + + public String browseSave() { + // setSelectedFile(s); + setFileFilter(MyFileFilter.wormFilter); + if (showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { + if (!getSelectedFile().exists() || ( + JOptionPane.showConfirmDialog(null, "File " + + getSelectedFile().getAbsolutePath() + + " exists.\nDo you want to replace it?", + "Are you sure?", JOptionPane.YES_NO_OPTION) == + JOptionPane.YES_OPTION)) + return getSelectedFile().getAbsolutePath(); + } + return null; + } // browseSave() + +} // class MyFileChooser
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/MyFileFilter.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,51 @@ +/* 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 javax.swing.filechooser.FileFilter; + +class MyFileFilter extends FileFilter { + + public static final MyFileFilter mp3Filter = + new MyFileFilter(".mp3", "MPEG1 level 3"); + public static final MyFileFilter waveFilter = + new MyFileFilter(".wav", "Wave"); + public static final MyFileFilter matchFilter = + new MyFileFilter(".match", "Match"); + public static final MyFileFilter wormFilter = + new MyFileFilter(".worm", "Worm"); + + protected String suffix; + protected String description; + + public MyFileFilter(String suff, String desc) { + suffix = suff; + description = desc + " files (*" + suff + ")"; + } // constructor + + public boolean accept(File f) { + return (f != null) && (f.isDirectory() || f.getName().endsWith(suffix)); + } // accept() + + public String getDescription() { return description; } + +} // class MyFileFilter
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/Plot.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,105 @@ +/* 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.awt.Color; +import java.awt.Dimension; +import java.awt.GraphicsConfiguration; +import java.awt.Rectangle; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.BoxLayout; +import javax.swing.JFrame; +import javax.swing.WindowConstants; + +import at.ofai.music.util.FrameMargins; + +public class Plot extends JFrame { + + static final long serialVersionUID = 0; // silence compiler warning + + JFrame frame; + public PlotPanel panel; + + public Plot(double[] xData, double[] yData) { + this(); + panel.addPlot(xData, yData); + } + + public Plot() { + frame = new JFrame(); + panel = new PlotPanel(frame); + frame.getContentPane().setBackground(WormConstants.backgroundColor); + frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), + BoxLayout.Y_AXIS)); + frame.getContentPane().add(panel); + panel.addHierarchyBoundsListener(panel); + Dimension borderSize = FrameMargins.get(false); + frame.setSize(panel.getWidth() + borderSize.width, + panel.getHeight() + borderSize.height); + // + WormConstants.cpHeight); + GraphicsConfiguration gc = frame.getGraphicsConfiguration(); + Rectangle bounds = gc.getBounds(); // [x=0 y=0 w=1280 h=1024] + frame.setLocation(bounds.x + (bounds.width - frame.getWidth()) / 2, + bounds.height - frame.getHeight()); + // bounds.y + (bounds.height - frame.getHeight()) / 2); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.setVisible(true); + frame.setIconImage(WormIcon.getWormIcon(1, frame)); + } + + public void setTitle(String s) { panel.setTitle(s); } + public void setAxis(String s) { panel.setAxis(s); } + public void setXAxis(double min, double max) { panel.setXAxis(min, max); } + public void setYAxis(double min, double max) { panel.setYAxis(min, max); } + public void setLength(int i, int l) { panel.setLength(i, l); } + public void rotateCurrent() { panel.rotateCurrent(); } + public void update() { panel.update(); } + public void fitAxes() { panel.fitAxes(); } + public void fitAxes(int current) { panel.fitAxes(current); } + public void setMode(int m) { panel.setMode(m); } + public void clear() { panel.clear(); } + public void close() { frame.setVisible(false); } + public void addPlot(double[] x, double[] y) { + addPlot(x, y, Color.blue); + } + public void addPlot(double[] x, double[] y, Color c) { + addPlot(x, y, c, PlotPanel.IMPULSE | PlotPanel.HOLLOW); + } + public void addPlot(double[] x, double[] y, Color c, int mode) { + panel.addPlot(x, y, c, mode); + } + + public static void main(String[] args) { // simple test of this class + double[] x = new double[100]; + double[] y = new double[100]; + for (int i = 0; i < 100; i++) { + x[i] = i; + y[i] = Math.sin(2 * Math.PI * i / 50); + } + Plot testPlot = new Plot(x, y); + testPlot.panel.xAxis.test(); //SD: remove + testPlot.frame.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent e) { System.exit(0); } }); + } // main() + +} // class Plot
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/PlotListener.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,72 @@ +/* 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.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +class PlotListener implements KeyListener { + PlotPanel callback; + public PlotListener(PlotPanel p) { callback = p; } + public void keyTyped(KeyEvent e) {} // doesn't get all keys; keyCode == 0 + public void keyPressed(KeyEvent e) {} + public void keyReleased(KeyEvent e) { + if ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0) // CTRL + key + switch (e.getKeyCode()) { + case KeyEvent.VK_UP: callback.yZoom(true, false); break; + case KeyEvent.VK_DOWN: callback.yZoom(false, false); break; + case KeyEvent.VK_LEFT: callback.xZoom(false, false); break; + case KeyEvent.VK_RIGHT: callback.xZoom(true, false); break; + } + else if ((e.getModifiers() & KeyEvent.SHIFT_MASK) != 0) // SHIFT + key + switch (e.getKeyCode()) { + case KeyEvent.VK_P: callback.print(600); break; + case KeyEvent.VK_UP: callback.yZoom(false, true); break; + case KeyEvent.VK_DOWN: callback.yZoom(true, true); break; + case KeyEvent.VK_LEFT: callback.xZoom(true, true); break; + case KeyEvent.VK_RIGHT: callback.xZoom(false, true); break; + } + else // normal key + switch (e.getKeyCode()) { + case KeyEvent.VK_0: callback.setMode(0); break; + case KeyEvent.VK_1: callback.setMode(1); break; + case KeyEvent.VK_2: callback.setMode(2); break; + case KeyEvent.VK_3: callback.setMode(3); break; + case KeyEvent.VK_4: callback.setMode(4); break; + case KeyEvent.VK_5: callback.setMode(5); break; + case KeyEvent.VK_6: callback.setMode(6); break; + case KeyEvent.VK_7: callback.setMode(7); break; + case KeyEvent.VK_8: callback.setMode(8); break; + case KeyEvent.VK_9: callback.setMode(9); break; + case KeyEvent.VK_F: callback.fitAxes(); break; + case KeyEvent.VK_N: callback.rotateCurrent(); break; + case KeyEvent.VK_P: callback.print(300); break; + case KeyEvent.VK_R: callback.resize(); break; + case KeyEvent.VK_U: callback.update(); break; + case KeyEvent.VK_Q: callback.close(); break; + case KeyEvent.VK_UP: callback.yMoveDown(false); break; + case KeyEvent.VK_DOWN: callback.yMoveDown(true); break; + case KeyEvent.VK_LEFT: callback.xMoveRight(false); break; + case KeyEvent.VK_RIGHT: callback.xMoveRight(true); break; + } + } // keyReleased() + +} // class PlotListener
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/PlotPanel.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,471 @@ +/* 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.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Shape; +import java.awt.event.HierarchyBoundsListener; +import java.awt.event.HierarchyEvent; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import javax.swing.JFrame; +import javax.swing.JPanel; + +import at.ofai.music.util.Format; +import at.ofai.music.util.PSPrinter; + +class PlotPanel extends JPanel implements HierarchyBoundsListener { + + static final long serialVersionUID = 0; + + JFrame theFrame; // Parent container of this panel + String title; + public static final int NONE = 0, + HOLLOW = 1, + FILLED = 2, + JOIN = 4, + IMPULSE = 8, + STEP = 16; + int plots; + PlotData[] data; + PlotData current; + int currentPtr; + AxisData xAxis, yAxis; + boolean initialised; + FontMetrics fm; // of the font used for axis labels + int ht; // the height of this font + + class AxisData { + double min, max, maxValue, minValue, scale; + int ticks, digits, margin, size, maxTicks; + String label; + boolean isVertical; + + public AxisData(boolean v) { + isVertical = v; + margin = isVertical? WormConstants.footMargin: + WormConstants.sideMargin; + size = isVertical? WormConstants.Y_SZ: WormConstants.X_SZ; + setMaxTicks(10,10); + ticks = maxTicks; + digits = 2; + label = ""; + } + + void test() { + try { + java.io.BufferedReader b = + new java.io.BufferedReader( + new java.io.InputStreamReader(System.in)); + while (true) { + StringTokenizer t = new StringTokenizer(b.readLine()); + double mn = Double.parseDouble(t.nextToken()); + double mx = Double.parseDouble(t.nextToken()); + setBounds(mn, mx); + } + } catch (Exception e) { + System.out.println(e); + } + } // test() + + void setMaxTicks(double mn, double mx) { + if (fm == null) + maxTicks = 10; + else if (isVertical) + maxTicks = Math.max(3, Math.min(10, size / ht * 2 / 3)); + else { + int wd = Math.max(fm.stringWidth(Format.d(mn, digits)), + fm.stringWidth(Format.d(mx, digits))); + maxTicks = Math.max(3, Math.min(10, size / wd * 2 / 3)); + } + } // setMaxTicks() + + void setBounds(double mn, double mx) { + if (mn > mx) { + double tmp = mn; + mn = mx; + mx = tmp; + } + setMaxTicks(mn, mx); + double diff = (mx - mn) / maxTicks; + double base = Math.pow(10, Math.floor(Math.log(diff)/Math.log(10))); + int head = (int)Math.ceil(diff / base); + if (head > 5) + diff = 10 * base; + else if (head > 2) + diff = 5 * base; + else // head = 1 or 2 + diff = head * base; + int low = (int) Math.floor(mn / diff); + int high = (int) Math.ceil(mx / diff); + if (high - low > maxTicks) { + if (head > 5) + diff = 20 * base; + else if (head > 2) + diff = 10 * base; + else if (head == 2) + diff = 5 * base; + else + diff = 2 * base; + low = (int) Math.floor(mn / diff); + high = (int) Math.ceil(mx / diff); + } + int pow = (int)Math.floor(Math.log(diff)/Math.log(10)); + if (pow < 0) + digits = -pow; + else + digits = 0; + ticks = high - low; // alt. code (below) replaces this line + rescale(low * diff, high * diff); + return; + } // setBounds() + +// Alternative code which keeps the number of ticks fixed. +// int empty = maxTicks - high + low; +// int[] next = {5,4,4}; +// int count = 0; +// for (int n = 2; empty > 0; ) { +// System.out.println("need to shift bounds outwards: " +// +low * diff+ " " +high * diff+ " "+empty); +// int rem = low % n; +// if (rem < 0) +// rem += n; +// int rem1 = high % n; +// if (rem1 < 0) +// rem1 += n; +// System.out.println(low + " % " + n + " = " + rem); +// System.out.println(high + " % " + n + " = " + rem1); +// if ((rem != 0) && (rem <= empty)) +// low -= rem; +// else if ((rem1 != 0) && (n - rem1 <= empty)) +// high += n - rem1; +// else if (n >= maxTicks) // force exit +// high += empty; +// else +// n = n * next[count++ % 3] / 2; +// empty = maxTicks - high + low; +// } +// rescale(low * diff, high * diff); + + void translate(boolean positive) { + double diff = (max - min) / (positive? -4: 4); + rescale(min - diff, max - diff); + } // translate() + + void zoom(boolean in, boolean positive) { + double diff = (max - min) / (in? -2: 1); + if (positive) + setBounds(min, max + diff); + else + setBounds(min - diff, max); + } // zoom() + + void rescale(double newMin, double newMax) { + size = isVertical? getHeight(): getWidth(); + min = newMin; + max = newMax; + scale = (double)(size - 2 * margin) / (max - min); + repaint(); + } // rescale() + + } // class AxisData + + class PlotData { + double[] x; // x-coordinates + double[] y; // y-coordinates + Color pointColour, joinColour; // Colours of plot + int pointSize; // Size of a point + int mode; + int length; + + protected PlotData(double[] xData, double[] yData) { + this(xData, yData, xData.length); + } // constructor + + protected PlotData(double[] xData, double[] yData, int len) { + x = xData; + y = yData; + length = len; + pointColour = Color.blue; + joinColour = Color.red; + pointSize = 6; + mode = IMPULSE | HOLLOW; + if ((xData != null) && (yData != null)) { + if (plots < data.length) { + currentPtr = plots; + data[plots++] = this; + } else + throw new RuntimeException("Too many plots"); + } + } // constructor + + } // plotData + + public PlotPanel(JFrame frame) { + this(frame, null, null); + } // constructor + + public PlotPanel(JFrame frame, double[] xData, double[] yData) { + theFrame = frame; + title = ""; + data = new PlotData[10]; + currentPtr = plots = 0; + initialised = false; + if ((xData != null) && (yData != null)) + current = new PlotData(xData, yData); + xAxis = new AxisData(false); + yAxis = new AxisData(true); + setSize(xAxis.size, yAxis.size); + fitAxes(); + frame.addKeyListener(new PlotListener(this)); + } // constructor + + public void fitAxes(int current) { + setCurrent(current); + fitAxes(); + } // fitAxes() + + public void fitAxes() { + if (current != null) { + xAxis.setBounds(min(current.x, current.length), max(current.x, current.length)); + yAxis.setBounds(min(current.y, current.length), max(current.y, current.length)); + } + } // fitAxes() + + void resize() { + xAxis.setBounds(xAxis.min, xAxis.max); + yAxis.setBounds(yAxis.min, yAxis.max); + } // resize() + + void update() { + repaint(); + } // update() + + void setCurrent(int c) { + if (c < plots) { + currentPtr = c; + current = data[currentPtr]; + } + } // setCurrent() + + void rotateCurrent() { + if (plots != 0) { + currentPtr = (currentPtr + 1) % plots; + current = data[currentPtr]; + } + } // rotateCurrent() + + void clear() { + plots = 0; + current = null; + } // clear + + // Allows multiple plots, similar to Matlab's "hold on" + boolean addPlot(double[] xData, double[] yData, Color c, int m) { + if (addPlot(xData, yData)) { + current.pointColour = c; + current.joinColour = c; + current.mode = m; + return true; + } + return false; + } // addPlot() + + boolean addPlot(double[] xData, double[] yData) { + if ((xData != null) && (yData != null) && (plots < data.length)) { + current = new PlotData(xData, yData); + if (!initialised) { + resize(); + initialised = true; + } + return true; + } + return false; + } // addPlot() + + void setLength(int index, int len) { + data[index].length = len; + } // setLength() + + void setTitle(String t) { title = t; theFrame.setTitle(t); } + + void setXLabel(String s) { xAxis.label = s; } + void setYLabel(String s) { yAxis.label = s; } + + void setAxis(String s) { + StringTokenizer tk = new StringTokenizer(s); + try { + xAxis.rescale(Double.parseDouble(tk.nextToken()), + Double.parseDouble(tk.nextToken())); + yAxis.rescale(Double.parseDouble(tk.nextToken()), + Double.parseDouble(tk.nextToken())); + } catch (NoSuchElementException e) { // ignore illegal values + System.err.println("Illegal axes specification: " + e); + } catch (NumberFormatException e) { // ignore illegal values + System.err.println("Illegal axes specification: " + e); + } + } // setAxis() + + void setXAxis(double min, double max) { xAxis.rescale(min, max); } + void setYAxis(double min, double max) { yAxis.rescale(min, max); } + + void setMode(int m) { if (current != null) { current.mode = m; repaint(); }} + + void close() { theFrame.dispose(); } + + public void print(int res) { + PSPrinter.print(this, res); + } // print() + + public void paint(Graphics g) { + if (fm == null) { + fm = g.getFontMetrics(); + ht = fm.getHeight(); + } + // Paint background + g.setColor(WormConstants.backgroundColor); + g.fillRect(0, 0, xAxis.size, yAxis.size); + // Plot points + int xprev = 0, yprev = 0; + int yZero = yAxis.margin + (int)(yAxis.max * yAxis.scale); + Shape saveClip = g.getClip(); + g.clipRect(xAxis.margin, yAxis.margin, xAxis.size - 2 * xAxis.margin, + yAxis.size - 2 * yAxis.margin); + for (int j = 0; j < plots; j++) { + for (int i = 0; i < data[j].x.length; i++) { + int xx = xAxis.margin + + (int)((data[j].x[i] - xAxis.min) * xAxis.scale); + int yy = yAxis.margin + + (int)((yAxis.max - data[j].y[i]) * yAxis.scale); + int sz = data[j].pointSize; + g.setColor(data[j].pointColour); + if ((data[j].mode & HOLLOW) != 0) + g.drawOval(xx - sz / 2, yy - sz / 2, sz, sz); + if ((data[j].mode & FILLED) != 0) + g.fillOval(xx - sz / 2, yy - sz / 2, sz, sz); + g.setColor(data[j].joinColour); + if ((data[j].mode & IMPULSE) != 0) + g.drawLine(xx, yy, xx, yZero); + else if (((data[j].mode & JOIN) != 0) && (i != 0)) { + if ((data[j].mode & STEP) != 0) { + g.drawLine(xx, yy, xx, yprev); // join with step fn + g.drawLine(xx, yprev, xprev, yprev); + } else + g.drawLine(xx, yy, xprev, yprev); // join directly + } + xprev = xx; + yprev = yy; + } + } + g.setClip(saveClip); + // Draw axes and labels + g.setColor(WormConstants.axesColor); + g.drawRect(xAxis.margin, yAxis.margin, xAxis.size - 2 * xAxis.margin, + yAxis.size - 2 * yAxis.margin); + for (int i = 1; i < xAxis.ticks; i++) { + int z = xAxis.margin + + i * (xAxis.size - 2 * xAxis.margin) / xAxis.ticks; + String label = Format.d(xAxis.min + i * (xAxis.max - xAxis.min) / + xAxis.ticks, xAxis.digits); + int wd = fm.stringWidth(label); + g.drawString(label, z - wd / 2, yAxis.size - 5); + g.drawLine(z, yAxis.size - yAxis.margin - 2, + z, yAxis.size - yAxis.margin + 2); + g.drawLine(z, yAxis.margin - 2, + z, yAxis.margin + 2); + } + for (int i = 1; i < yAxis.ticks; i++) { + int z = yAxis.margin + i*(yAxis.size-2 * yAxis.margin)/yAxis.ticks; + String label = Format.d(yAxis.max - i * (yAxis.max - yAxis.min) / + yAxis.ticks, yAxis.digits); + int wd = fm.stringWidth(label); + g.drawString(label, xAxis.margin - wd - 5, z + ht / 2); + g.drawLine(xAxis.margin - 2, z, + xAxis.margin + 2, z); + g.drawLine(xAxis.size - xAxis.margin - 2, z, + xAxis.size - xAxis.margin + 2, z); + } + int wd = fm.stringWidth(yAxis.label); + g.drawString(yAxis.label, xAxis.margin - wd - 5, yAxis.margin + ht / 2); + wd = fm.stringWidth(xAxis.label); + g.drawString(xAxis.label, xAxis.size - xAxis.margin-wd/2, yAxis.size-5); + } // paint() + + public void ancestorMoved(HierarchyEvent e) {} + + public void ancestorResized(HierarchyEvent e) { + if ((xAxis.size == getWidth()) && (yAxis.size == getHeight())) + return; + xAxis.setBounds(xAxis.min, xAxis.max); + yAxis.setBounds(yAxis.min, yAxis.max); + xAxis.setBounds(xAxis.min, xAxis.max); // recalculate to fix labels + yAxis.setBounds(yAxis.min, yAxis.max); + } // ancestorResized() + + void xMoveRight(boolean right) { xAxis.translate(right); } + void xZoom(boolean in, boolean right) { xAxis.zoom(in, right); } + void yMoveDown(boolean down) { yAxis.translate(down); } + void yZoom(boolean in, boolean down) { yAxis.zoom(in, down); } + + static double min(double[] d) { + if ((d == null) || (d.length == 0)) + return 0; + return min(d, d.length); + } // max() + + static double min(double[] d, int len) { + if (d == null) + return 0; + if (d.length < len) + len = d.length; + if (len == 0) + return 0; + double m = d[0]; + for (int i = 1; i < d.length; i++) + if ((m == Double.NaN) || (d[i] < m)) + m = d[i]; + return m; + } // min() + + static double max(double[] d) { + if ((d == null) || (d.length == 0)) + return 0; + return max(d, d.length); + } // max() + + static double max(double[] d, int len) { + if (d == null) + return 0; + if (d.length < len) + len = d.length; + if (len == 0) + return 0; + double m = d[0]; + for (int i = 1; i < len; i++) + if ((m == Double.NaN) || (d[i] > m)) + m = d[i]; + return m; + } // max() + +} // class PlotPanel
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/TempoInducer.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,448 @@ +/* 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.FileWriter; +import java.io.IOException; + +import at.ofai.music.util.Format; + +/** CLASS TempoInducer finds tempo (rate) but not beat times (phase) of + * a performance. + * It implements a real-time incremental tempo induction algorithm, + * keeps track of multiple hypotheses, and allows switching between these + * hypotheses by the user. + * It assumes time is measured on a discrete scale (the timeBase), + * where an amplitude (rms?) value is supplied for each time point and + * the estimate of the tempo at this point is returned (method getTempo()). + * Constants define the maximum, minimum and default tempos in terms of the + * corresponding interbeat interval (IBI), and the length of time over which + * tempo is induced (MEMORY). + */ +public class TempoInducer { + + double timeBase; + double[] envelope; + double[] slope; + double[] peakTime; + double[] peakSPL; + double[] iois; + double[] cluster; + double[] clusterWgt; + double[] newCluster; + double[] newClusterWgt; + double[] best; + double[] bestWgt; + boolean[] bestUsed; + int peakHead; // position of next peak (i.e. currently empty) + int peakTail; // position before first peak (i.e. currently empty) + int bestCount; + double tempo; + int counter; + double longTermSum; + double recentSum; + double[] prevAmp; + static final double MEMORY = 8.0; // time in seconds used for IOI calcns +// static final double MEMORY = 1000.0; //SD WAS 8 - changed for dance test + static final double MIN_IOI = 0.1; + static final double MAX_IOI = 2.5; +// static final double MAX_IOI = 5; //SD was 2.5 - changed for dance test + static final double MIN_IBI = 0.200; // 300 BPM = 0.2 + static final double LOW_IBI = 0.400; // 150 BPM = 0.4 + static final double DEF_IBI = 0.800; // 100 BPM = 0.6 + static final double HI_IBI = 1.200; // 75 BPM = 0.8 + static final double MAX_IBI = 1.500; // 40 BPM = 1.5 +// static final double MIN_IBI = 0.100; +// static final double LOW_IBI = 0.100; +// static final double DEF_IBI = 0.800; // 100 BPM = 0.6 +// static final double HI_IBI = 5.000; +// static final double MAX_IBI = 5.000; //SD all changed for dance test + static final double HYP_CHANGE_FACTOR = (1 - 0.4); //WAS 1-.4 + static final double AMP_MEM_FACTOR = 0.95; + static final double DECAY_BEST = 0.9; // weight of best[0] decreases 10%/s + static final double DECAY_OTHER = 0.8; // weight of best[i] decreases 20%/s + static final double RATIO_ERROR = 0.1; // 1.9-2.1, 2.9-3.1, ...,7.9-8.1 + static final int CLUSTER_WIDTH = 8; //WAS 4 + static final int CLUSTER_FACTOR = 30; //WAS 10 + static final int CLUSTER_POINTS = 10; //WAS 20 +// static final int CLUSTER_POINTS = 20; //SD dance test WAS 10 + static final int REGRESSION_SIZE = 4; + static final int SLOPE_POINTS = 15; + static final int MID_POINT = SLOPE_POINTS / 2; + static final int PEAK_POINTS = (int)MEMORY * 20; + static final int OVERLAP = 4; + int ioiPoints; + public boolean onset; + static Plot plot = null; + static boolean plotFlag = false; + double[] xplot, yplot, xplot2, yplot2, xplot3, yplot3; + + public TempoInducer(double tb) { + // System.err.println("Using infinite memory...."); //SD for dance test + timeBase = tb; + ioiPoints = (int)Math.ceil(MAX_IOI / tb) + 1; + envelope = new double[SLOPE_POINTS]; + slope = new double[SLOPE_POINTS]; + peakTime = new double[PEAK_POINTS]; + peakSPL = new double[PEAK_POINTS]; + iois = new double[ioiPoints]; + cluster = new double[CLUSTER_POINTS]; + clusterWgt = new double[CLUSTER_POINTS]; + newCluster = new double[CLUSTER_POINTS]; + newClusterWgt = new double[CLUSTER_POINTS]; + best = new double[CLUSTER_POINTS]; + bestWgt = new double[CLUSTER_POINTS]; + bestUsed = new boolean[CLUSTER_POINTS]; + bestCount = 0; + peakHead = 0; + peakTail = PEAK_POINTS - 1; + tempo = DEF_IBI; + counter = 0; + longTermSum = 0; + recentSum = 0; + prevAmp = new double[OVERLAP]; + xplot = new double[ioiPoints]; + for (int i = 0; i < ioiPoints; i++) + xplot[i] = timeBase * i; + yplot = new double[ioiPoints]; + xplot2 = new double[CLUSTER_POINTS]; + yplot2 = new double[CLUSTER_POINTS]; + xplot3 = new double[CLUSTER_POINTS]; + yplot3 = new double[CLUSTER_POINTS]; + if (plotFlag) + makePlot(); + } // constructor + + void makePlot() { + System.out.println("makePlot() " + (plot == null));//DEBUG + if (plot == null) + plot = new Plot(); + else + plot.clear(); + plot.addPlot(xplot, yplot, java.awt.Color.blue, PlotPanel.IMPULSE); + plot.addPlot(xplot2, yplot2, java.awt.Color.green); + plot.addPlot(xplot3, yplot3, java.awt.Color.red); + plot.setTitle("Tempo Tracking Histogram and Clusters"); + } // constructor + + public double getTempo(double amp) { + int i; + counter++; + for (i = 0; i < SLOPE_POINTS - 1; i++) { + envelope[i] = envelope[i+1]; + slope[i] = slope[i+1]; + } + envelope[i] = amp; + for (int j = 0; j < OVERLAP; j++) { // overlap windows + envelope[i] += prevAmp[j]; + if (j == OVERLAP - 1) + prevAmp[j] = amp; + else + prevAmp[j] = prevAmp[j+1]; + } + longTermSum += amp; + if (recentSum == 0) + recentSum = amp; + else + recentSum = AMP_MEM_FACTOR * recentSum + (1 - AMP_MEM_FACTOR) * amp; + slope[i] = getSlope(); // slope of points up to i (i.e. env[i-3 : i]) + for (i = 0; i < SLOPE_POINTS; i++) + if ((i != MID_POINT) && (slope[MID_POINT] <= slope[i])) + break; + // System.out.println(i + " " + Format.d(envelope[MID_POINT], 5) + " " + + // Format.d(slope[MID_POINT],5) + " " + + // Format.d(longTermSum / (counter*4*REGRESSION_SIZE*timeBase),3)); + // System.out.println(i + " " + Format.d(1000*recentSum,3) + " " + + // Format.d(1000*longTermSum / counter, 3)); + if ((i == SLOPE_POINTS) && + (envelope[MID_POINT] > recentSum / 5) && + (slope[MID_POINT] > recentSum / + (2 * REGRESSION_SIZE * timeBase))) { + // onset = true; // click on onsets (for debugging) + addPeak(timeBase * (counter - MID_POINT), envelope[MID_POINT]); + } else + onset = false; + return tempo; + } // getTempo + + public void switchLevels(boolean faster) { + for (int i = 1; i < bestCount; i++) + if ((best[i] < best[0]) == faster) { + double tmp = best[0]; + best[0] = best[i]; + best[i] = tmp; + break; + } + } // switchLevels() + + static int next(int p) { + return (p == PEAK_POINTS - 1)? 0: p + 1; + } // next() + + static int prev(int p) { + return (p == 0)? PEAK_POINTS - 1: p - 1; + } // prev() + + static int top(int p) { // Required nearness of IOI's for clustering + return p + p / CLUSTER_FACTOR + CLUSTER_WIDTH; // was 2*0.010=20ms (3pt) + } // top() + + // Updates tempo based on new peak + void addPeak(double t, double dB) { + java.util.Arrays.fill(iois, 0); + // add new peak + peakTime[peakHead] = t; + peakSPL[peakHead] = dB; + peakHead = next(peakHead); + if (peakHead == peakTail) + System.err.println("Overflow: too many peaks"); + // delete old peaks + int loPtr = next(peakTail); // first peak + while (t - peakTime[loPtr] > MEMORY) { + peakTail = loPtr; + loPtr = next(peakTail); + } + // get all IOIs + int hiPtr; + for ( ; loPtr != peakHead; loPtr = next(loPtr)) + for (hiPtr = next(loPtr); hiPtr != peakHead; hiPtr = next(hiPtr)) { + double ioi = peakTime[hiPtr] - peakTime[loPtr]; + if (ioi >= MAX_IOI) + break; + iois[(int)Math.rint(ioi / timeBase)] += // 1 + // better?? + Math.sqrt(peakSPL[hiPtr] * peakSPL[loPtr]); + } + for (int i = 0; i < iois.length; i++) + yplot[i] = iois[i]; // copy values before they are destroyed + // System.out.println("x = ["); + // for (int p = 0; p < 200; p++) + // System.out.println(Format.d(1000*iois[p], 3)); + // System.out.println("];"); + // make clusters (with width defined by top()) + int clusterCount = 0; + for ( ; clusterCount < CLUSTER_POINTS; clusterCount++) { + double sum = 0; + double max = 0; + int maxIndex = 0; + hiPtr = (int)(MIN_IOI / timeBase); // ignore < 100ms + loPtr = hiPtr; + while (hiPtr < ioiPoints) { // find window with greatest average + if (hiPtr >= top(loPtr)) + sum -= iois[loPtr++]; + else { + sum += iois[hiPtr++]; + if (sum / (top(loPtr) - loPtr) > max) { + max = sum / (top(loPtr) - loPtr); + maxIndex = loPtr; + } + } + } + if (max == 0) + break; + hiPtr = top(maxIndex); + if (hiPtr > ioiPoints) + hiPtr = ioiPoints; + sum = 0; + double weights = 0; + for (loPtr = maxIndex; loPtr < hiPtr; loPtr++) { + sum += loPtr * iois[loPtr]; + weights += iois[loPtr]; + iois[loPtr] = 0; // use each value once + } + cluster[clusterCount] = sum / weights * timeBase; // Weighted av (s) + clusterWgt[clusterCount] = max; + // System.out.println(Format.d(cluster[clusterCount], 3) + " " + + // Format.d(clusterWgt[clusterCount], 3) + " " + + // Format.d(weights, 3) + " " + maxIndex); + } + // re-weight clusters using related clusters + for (int i = 0; i < clusterCount; i++) { + newCluster[i] = cluster[i] * clusterWgt[i]; + newClusterWgt[i] = clusterWgt[i]; + for (int j = 0; j < clusterCount; j++) { + if (i != j) { + int ratio = getRatio(cluster[i], cluster[j]); + // newCluster = sum[(val{*|/}rat)*wgt/rat] / sum[wgt/rat] + if (ratio > 0) { + newCluster[i] += cluster[j] * clusterWgt[j]; + newClusterWgt[i] += clusterWgt[j] / ratio; + } else if (ratio < 0) { + newCluster[i] += cluster[j] * clusterWgt[j] / + (ratio * ratio); + newClusterWgt[i] += clusterWgt[j] / -ratio; + } + } + } + newCluster[i] /= newClusterWgt[i]; + } + for (int i = 0; i < CLUSTER_POINTS; i++) { + if (i < clusterCount) { + xplot2[i] = cluster[i]; + yplot2[i] = clusterWgt[i] * 3; + xplot3[i] = newCluster[i]; + yplot3[i] = newClusterWgt[i] * 1.5; + } else + xplot2[i] = yplot2[i] = xplot3[i] = yplot3[i] = 0; + } + if (plotFlag) { + if (plot == null) + makePlot(); + plot.update(); + } else if (plot != null) { + plot.close(); + plot = null; + } + // update best clusters; smooth over time + double dt = t - peakTime[prev(prev(peakHead))]; + // System.out.println("CURRENT IOI = " + Format.d(dt,3)); + //if (false) //SD for dance tests + for (int i = 0; i < bestCount; i++) { + bestUsed[i] = false; + if (i != 0) + bestWgt[i] *= Math.pow(DECAY_OTHER, dt); // memory decay 20%/s + else + bestWgt[i] *= Math.pow(DECAY_BEST, dt); // memory decay 10%/s + if (best[i] < LOW_IBI) // penalise if too fast ... + bestWgt[i] *= 1 - Math.pow((LOW_IBI - best[i]) / + (2 * (LOW_IBI - MIN_IBI)), 3); + else if (best[i] > HI_IBI) // ... or too slow + bestWgt[i] *= 1 - Math.pow((best[i] - HI_IBI) / + (2 * (MAX_IBI - HI_IBI)), 3); + } + for (int i = 0; i < clusterCount; i++) { + if ((newCluster[i] < MIN_IBI) || (newCluster[i] > MAX_IBI)) + continue; + double dMax = newCluster[i]/CLUSTER_FACTOR + CLUSTER_WIDTH*timeBase; + double dMin = dMax / 2; // NEW:"/2"; don't allow values too far away + int index = -1; + for (int j = 0; j < bestCount; j++) { // find the nearest match + double diff = Math.abs(newCluster[i] - best[j]); + if (diff < dMin) { + dMin = diff; + index = j; + } + } + if (index >= 0) { // match found; update best + if (bestUsed[index]) + continue; + // update is equivalent to exp.-decaying memory used in paint() + best[index] += (newCluster[i] - best[index]) *HYP_CHANGE_FACTOR; + // * newClusterWgt[i] * (1 - dMin / dMax) / bestWgt[index]; + bestWgt[index] += newClusterWgt[i] * (1 - dMin / dMax); + // bestUsed[index] = true; + } else if (bestCount < CLUSTER_POINTS) { // not full yet; add + best[bestCount] = newCluster[i]; + bestWgt[bestCount++] = newClusterWgt[i]; + } else if (bestWgt[bestCount-1] < newClusterWgt[i]) { // best full; + best[bestCount-1] = newCluster[i]; // add if better + bestWgt[bestCount-1] = newClusterWgt[i]; + } + } + for (int i = 0; i < bestCount; i++) // merge clusters + for (int j = i + 1; j < bestCount; j++) + if (Math.abs(best[i] - best[j]) < + CLUSTER_WIDTH * timeBase / 2) { + best[i] = (best[i] * bestWgt[i] + + best[j] * bestWgt[j]) / + (bestWgt[i] + bestWgt[j]); + bestWgt[i] += bestWgt[j]; + for (int k = j + 1; k < bestCount; k++) { + best[k-1] = best[k]; + bestWgt[k-1] = bestWgt[k]; + } + bestCount--; + j--; + } + boolean change = true; // bubble sort, since almost sorted + while (change) { + change = false; + for (int i = bestCount-1; i > 0; i--) // sort by weight + if (bestWgt[i] > bestWgt[i-1]) { + change = true; + double tmp = bestWgt[i]; + bestWgt[i] = bestWgt[i - 1]; + bestWgt[i - 1] = tmp; + tmp = best[i]; + best[i] = best[i - 1]; + best[i - 1] = tmp; + } + } + if (bestCount > 0) { + // int maxIndex = 0; + // for (int i = 1; i < clusterCount; i++) + // if (newClusterWgt[i] > newClusterWgt[maxIndex]) + // maxIndex = i; + tempo = best[0]; + // System.out.println("Best: " + Format.d(best[0], 3)); + } + } // addPeak() + + void showTime() { + System.out.println("Time = " + + Format.d(timeBase * (counter - MID_POINT), 3) + "\n"); + } // showTime() + + void saveHist() { //SD for dance test + try { + Format.init(1,1,3,false); + FileWriter outfile = new FileWriter(new File("worm-dance.tmp")); + for (int i = 0; i < CLUSTER_POINTS; i++) + outfile.write(// Format.d(cluster[i], 3) + " : " + + // Format.d(clusterWgt[i], 3) + " " + + // Format.d(newCluster[i], 3) + " : " + + // Format.d(newClusterWgt[i], 3) + " " + + ((i < bestCount)? (Format.d(best[i], 3) + " "+ + Format.d(bestWgt[i], 3)) : "") + + "\n"); + outfile.write("\n"); + outfile.close(); + System.exit(0); + } catch (IOException e) { + System.err.println("Exception in saveHist(): " + e); + } + } // saveHist() + + int getRatio(double a, double b) { + double r = a / b; + if (r < 1) + r = -1 / r; + int round = (int)Math.rint(r); + if ((Math.abs((r - round) / r) < RATIO_ERROR) && + (Math.abs(round) >= 2) && (Math.abs(round) <= 8)) + return round; + return 0; + } // getRatio() + + // Uses an n-point linear regression to estimate the slope of envelope + double getSlope() { + int start = SLOPE_POINTS - REGRESSION_SIZE; + double sx = 0, sxx = 0, sy = 0, sxy = 0; + for (int i = 0; i < REGRESSION_SIZE; i++) { + sx += i; + sxx += i * i; + sy += envelope[start+i]; + sxy += i * envelope[start+i]; + } + return (4 * sxy - sx * sy) / (4 * sxx - sx * sx) / timeBase; + } // getSlope() + +} // class TempoInducer
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/Worm.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,719 @@ +/* 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.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.Rectangle; +import java.awt.event.HierarchyBoundsListener; +import java.awt.event.HierarchyEvent; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.WindowConstants; + +import at.ofai.music.util.FrameMargins; +import at.ofai.music.util.Format; + +public class Worm extends JPanel implements Runnable, HierarchyBoundsListener { + static final long serialVersionUID = 0; + double[] x; // Circular buffer of x-coordinates (in BPM) + double[] y; // Circular buffer of y-coordinates (in dB) + String[] labels; // Circular buffer of labels + int tail; // pointer for circular buffer + private double xmin, xmax, ymin, ymax, // Extremes of plotable display + xSum, xCount, // For autoScale() on x-axis + xScale, yScale; // To convert values to pixels + int xSize, ySize; // Size of this panel + Color[] rimColours, bodyColours; // Range of colours of worm + static final int STOP = 0, PLAY = 1, PAUSE = 2; + int state; + static final int MIN_WORM_SIZE = 4; // Diameter of worm + static final int WORM_SIZE = 24; // Diameter of worm + static final double X_MIN_DEF = 60; // Default is always + static final double X_MAX_DEF = 120; // on the screen + static final double Y_MIN_DEF = 35; + static final double Y_MAX_DEF = 85; + double[] xmem; // memory of past values for smoothing + double[] ymem; + int memSize; // number of points in xmem and ymem + int smoothMode; + public static final int NONE=0, EXPONENTIAL=1, HALF_GAUSS=2, FULL_GAUSS=3; + public static final String[] smoothLabels = { + "No Smoothing", "Exponential", "Half Gaussian", "Full Gaussian"}; + static final int DEFAULT_MODE = HALF_GAUSS; + static final double sDecay = 0.97; // for automatic scaling + static final double xDecay = 0.98; // for exponential smoothing + static final double yDecay = 0.97; + // Decay constants: 0.5000 -> -6dB / 1pt 2 ^ (-1 / k) + // 0.7071 -> -6dB / 2pt + // 0.7937 -> -6dB / 3pt + // 0.8409 -> -6dB / 4pt + // 0.8706 -> -6dB / 5pt + // 0.8909 -> -6dB / 6pt + // 0.9389 -> -6dB / 11pt + // 0.9576 -> -6dB / 16pt + // 0.9675 -> -6dB / 21pt + // 0.9737 -> -6dB / 26pt + // 0.9779 -> -6dB / 31pt + boolean autoScaleMode; + JButton playButton; + JCheckBoxMenuItem autoIndicator; + JRadioButtonMenuItem[] smoothButtons; + AudioWorm audio; + int wait; // Number of points to hold back (due to audio buffer size) + double framePeriod; // Time between successive points + String inputPath, inputFile, matchFile, wormFileName, + loudnessUnits, tempoUnits; + double timingOffset; + JFrame theFrame; + WormControlPanel controlPanel; + WormScrollBar scrollBar; + WormFile wormFile; + Thread playThread; + + public Worm(JFrame f) { + x = new double[WormConstants.wormLength]; + y = new double[WormConstants.wormLength]; + labels = new String[WormConstants.wormLength]; + xmem = new double[WormConstants.wormLength]; + ymem = new double[WormConstants.wormLength]; + rimColours = new Color[WormConstants.wormLength]; + bodyColours = new Color[WormConstants.wormLength]; + smoothMode = DEFAULT_MODE; + autoScaleMode = true; + setGlow(false); + loudnessUnits = "dB"; + tempoUnits = "BPM"; + init(); + theFrame = f; + playThread = new Thread(this); + playThread.start(); + xSize = WormConstants.X_SZ; + ySize = WormConstants.Y_SZ; + setSize(xSize, ySize); + repaint(); + } // default constructor + + public void setGlow(boolean flag) { + if (flag) + WormConstants.setNightColours(); + else + WormConstants.setDayColours(); + int r1 = WormConstants.wormHeadColor.getRed(); + int r2 = WormConstants.wormTailColor.getRed(); + int r3 = WormConstants.wormHeadRimColor.getRed(); + int r4 = WormConstants.wormTailRimColor.getRed(); + int g1 = WormConstants.wormHeadColor.getGreen(); + int g2 = WormConstants.wormTailColor.getGreen(); + int g3 = WormConstants.wormHeadRimColor.getGreen(); + int g4 = WormConstants.wormTailRimColor.getGreen(); + int b1 = WormConstants.wormHeadColor.getBlue(); + int b2 = WormConstants.wormTailColor.getBlue(); + int b3 = WormConstants.wormHeadRimColor.getBlue(); + int b4 = WormConstants.wormTailRimColor.getBlue(); + for (int i = 0; i < WormConstants.wormLength; i++) { + bodyColours[i] = new Color( + (r1 * i + r2 * (WormConstants.wormLength - i)) / WormConstants.wormLength, + (g1 * i + g2 * (WormConstants.wormLength - i)) / WormConstants.wormLength, + (b1 * i + b2 * (WormConstants.wormLength - i)) / WormConstants.wormLength); + rimColours[i] = new Color( + (r3 * i + r4 * (WormConstants.wormLength - i)) / WormConstants.wormLength, + (g3 * i + g4 * (WormConstants.wormLength - i)) / WormConstants.wormLength, + (b3 * i + b4 * (WormConstants.wormLength - i)) / WormConstants.wormLength); + } + if (controlPanel != null) { + Component[] c = controlPanel.getComponents(); + for (int i = 0; i < c.length; i++) { + c[i].setForeground(WormConstants.buttonTextColor); + c[i].setBackground(WormConstants.buttonColor); + } + } + if (scrollBar != null) + scrollBar.setBackground(WormConstants.backgroundColor); + repaint(); + } // setGlow() + + void init() { + state = STOP; + clear(); + xRescale(X_MIN_DEF, X_MAX_DEF); + yRescale(Y_MIN_DEF, Y_MAX_DEF); + wait = 0; + framePeriod = 0; + timingOffset = 0; + xSum = 0.600; + } // init() + + public void clearWithoutRepaint() { + tail = 0; + memSize = 0; + for (int i=0; i < WormConstants.wormLength; i++) + x[i] = -1; + xCount = 0; + } // clearWithoutRepaint() + + public void clear() { + clearWithoutRepaint(); + repaint(); + } // clear() + + void editParameters() { + if (wormFile != null) + wormFile.editParameters(); + } // editParameters() + + void save(String s) { + if ((wormFile != null) && (s != null)) + wormFile.write(s); + } + void setInputFile(String s) { inputFile = s; } + void setInputFile(String path, String file) { + inputPath = path; + if (!path.endsWith("/")) + inputPath += '/'; + inputFile = file; + } + String getInputFile() { return inputFile; } + String getInputPath() { return inputPath; } + void setMatchFile(String s) { matchFile = s; } + String getMatchFile() { return matchFile; } + void clearWormFile() { + clear(); + wormFileName = null; + wormFile = null; + } + void setWormFile(String s) { + if ((s != null) && (s.length() > 0)) { + clear(); + wormFileName = s; + wormFile = new WormFile(this, s); + } + } + public WormFile getWormFile() { return wormFile; } + public String getWormFileName() { return wormFileName; } + void setPlayButton(JButton b) { playButton = b; } + void setAutoButton(JCheckBoxMenuItem b) { + autoIndicator = b; + autoIndicator.setSelected(autoScaleMode); + } + void setSmoothButtons(JRadioButtonMenuItem[] sb) { + smoothButtons = sb; + smoothButtons[smoothMode].setSelected(true); + } + public void setDelay(int t) { wait = t; } + public void setFramePeriod(double t) { framePeriod = t; } + void setSmoothMode(int mode) { + smoothMode = mode; + if (smoothButtons != null) + smoothButtons[mode].setSelected(true); + } + int getSmoothMode() { return smoothMode; } + public void smooth() { + if (wormFile != null) { + new WormSmoothDialog(this, wormFile); + } + } + void setFileDelay(int d) { AudioWorm.fileDelay = d; } + void setFileDelayString(String s) { + try { + AudioWorm.fileDelay = Integer.parseInt(s); + } catch (NumberFormatException e) { + System.err.println("Invalid delay: " + s); + } + } + int getFileDelay() { return AudioWorm.fileDelay; } + String getFileDelayString() { return "" + AudioWorm.fileDelay; } + void setTimingOffset(double d) { timingOffset = d; } + void setTimingOffsetString(String s) { + try { + timingOffset = Double.parseDouble(s); + } catch (NumberFormatException e) { + System.err.println("Invalid offset: " + s); + } + } + String getTimingOffsetString() { return Format.d(timingOffset, 4); } + double getTimingOffset() { return timingOffset; } + void setControlPanel(WormControlPanel p) { controlPanel = p; } + void setScrollBar(WormScrollBar p) { scrollBar = p; } + void setTitle(String t) { theFrame.setTitle(t); } + void setLoudnessUnits(String s) { loudnessUnits = s; } + void setTempoUnits(String s) { tempoUnits = s; } + void setAxis(String s) { + StringTokenizer tk = new StringTokenizer(s); + try { + xRescale(Double.parseDouble(tk.nextToken()), + Double.parseDouble(tk.nextToken())); + yRescale(Double.parseDouble(tk.nextToken()), + Double.parseDouble(tk.nextToken())); + setAutoScaleMode(false); + } catch (NoSuchElementException e) { // ignore illegal values + System.err.println("Illegal axes specification: " + e); + } catch (NumberFormatException e) { // ignore illegal values + System.err.println("Illegal axes specification: " + e); + } + } // setAxis() + + // Starts/continues playing in a separate thread; immediately returns + void play() { + playButton.setText("Pause"); + playButton.repaint(); + if (state == STOP) { + clear(); + audio = new AudioWorm(this); + } + state = PLAY; + audio.start(); + synchronized(this) { // run() can now enter loop to process audio blocks + notify(); // informs playThread that it can start playing + } + } // play() + + public void run() { // Code for play, executed as separate thread + while (true) { // This is always running + try { + synchronized(this) { // wait until there is something to play + wait(); + } + Thread.sleep(200); // wait 0.2s + try { + while ((state == PLAY) && audio.nextBlock()) + ; + } catch (ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + audio.ti.showTime(); + } + //System.out.println("loop ended " + (state != PLAY)); + if (state == PLAY) { // end of file; let audio drain + // audio.ti.saveHist(); //SD for dance test + for ( ; wait > 0; wait--) { + // System.err.println("DEBUG: wait = " + wait); + repaint(); + Thread.sleep((int)(AudioWorm.averageCount * + AudioWorm.windowTime * 1000)); // wait 1.2s + } + //System.err.println("DEBUG: wait = " + wait); + repaint(); + stop(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } // run() + + void pause() { + if (state == PLAY) { + state = PAUSE; + playButton.setText("Cont"); + playButton.repaint(); + audio.pause(); + } + } // pause() + + void stop() { + state = STOP; + playButton.setText("Play"); + playButton.repaint(); + if (audio != null) + audio.stop(); + } // stop() + + public void paint(Graphics g) { + FontMetrics fm = g.getFontMetrics(); + int ht = fm.getHeight(); + // Paint background + g.setColor(WormConstants.backgroundColor); + g.fillRect(0, 0, xSize, ySize); + if (xCount == 0) { + int x = xSize / 2; + int y = ySize / 2; + g.drawImage(WormIcon.getWormIcon(2,theFrame), x - 150, y - 50, null); + g.setColor(WormConstants.axesColor); + g.drawString(WormConstants.title, + x - fm.stringWidth(WormConstants.title) / 2, y + 70); + return; + } + // Draw worm itself + int labelLeft = xSize - WormConstants.sideMargin - + fm.stringWidth("Time:9999.9m"); + int labelRight = xSize - WormConstants.sideMargin - fm.stringWidth("m"); + int labelHeight = WormConstants.footMargin + ht + 10; + String barLabel = null, beatLabel = null, trackLabel = null, + timeLabel = null, prev = "0"; + for (int i = 0; i < WormConstants.wormLength - wait; i++) { + int ind = (tail + i) % WormConstants.wormLength; + int d = MIN_WORM_SIZE + + WORM_SIZE * i / (WormConstants.wormLength - wait); + int xx = WormConstants.sideMargin + (int)((x[ind] - xmin) * xScale); + int yy = WormConstants.footMargin + (int)((ymax - y[ind]) * yScale); + if (x[ind] >= 0) { // if there is data, draw a circle + int e = 0; + if (labels[ind].indexOf(':') < 0) + timeLabel = labels[ind]; + else { + StringTokenizer st = new StringTokenizer(labels[ind], ":"); + barLabel = st.nextToken(); + beatLabel = st.nextToken(); + trackLabel = st.nextToken(); + timeLabel = st.nextToken(); + if (!barLabel.equals(prev)) { + prev = barLabel; + e = 4; + } + } + g.setColor(rimColours[i]); // add a dark rim + g.fillOval(xx - (d+e) / 2, yy - (d+e) / 2, d+e, d+e); + if (e == 0) { + g.setColor(bodyColours[i]); + g.fillOval(xx - (d-2) / 2, yy - (d-2) / 2, d-2, d-2); + } + if (i == WormConstants.wormLength - wait - 1) { // draw face + g.setColor(WormConstants.axesColor); + g.drawString("Time: ", labelLeft, labelHeight); + g.drawString(timeLabel, + labelRight - fm.stringWidth(timeLabel), + labelHeight); + if (barLabel != null) { + g.drawString("Bar: ", labelLeft, labelHeight + ht); + g.drawString(barLabel, + labelRight - fm.stringWidth(barLabel), + labelHeight + ht); + } + if ((beatLabel != null) && (beatLabel.length() > 0)) { + g.drawString("Beat: ", labelLeft, labelHeight + 2 * ht); + g.drawString(beatLabel, + labelRight - fm.stringWidth(beatLabel), + labelHeight + 2 * ht); + } + if (e == 0) + g.setColor(WormConstants.wormFaceColor); + else + g.setColor(WormConstants.altFaceColor); + if (barLabel != null) { + int wd = fm.stringWidth(barLabel); + g.drawString(barLabel, xx - wd / 2, yy + ht / 2); + } else { // draw face :) + g.fillOval(xx -d / 5 -2, yy - d / 5, 5, 2); // l eye + g.fillOval(xx +d / 5 -2, yy - d / 5, 5, 2); // r eye + g.fillOval(xx - 2, yy - 3, 3, 3); // nose + g.drawArc(xx - d / 4, yy - d / 4, d/2, d/2, 220, 100); + } + } + } + } + // Draw axes and labels + g.setColor(WormConstants.axesColor); + g.drawRect(WormConstants.sideMargin, WormConstants.footMargin, + xSize - 2 * WormConstants.sideMargin, + ySize - 2 * WormConstants.footMargin); + for (int i = 1; i < 10; i++) { + int z = WormConstants.sideMargin + + i * (xSize - 2 * WormConstants.sideMargin) / 10; + String label = Format.d(xmin + i * (xmax - xmin) / 10, 1); // xlabel + int wd = fm.stringWidth(label); + g.drawString(label, z - wd / 2, ySize - 5); + g.drawLine(z, ySize - WormConstants.footMargin - 2, + z, ySize - WormConstants.footMargin + 2); + g.drawLine(z, WormConstants.footMargin - 2, + z, WormConstants.footMargin + 2); + z = WormConstants.footMargin + + i * (ySize - 2 * WormConstants.footMargin) / 10; + label = Format.d(ymax - i * (ymax - ymin) / 10, 1); // ylabel + wd = fm.stringWidth(label); + g.drawString(label, WormConstants.sideMargin - wd - 5, z + ht / 2); + g.drawLine(WormConstants.sideMargin - 2, z, + WormConstants.sideMargin + 2, z); + g.drawLine(xSize - WormConstants.sideMargin - 2, z, + xSize - WormConstants.sideMargin + 2, z); + } + int wd = fm.stringWidth(loudnessUnits); + g.drawString(loudnessUnits, WormConstants.sideMargin - wd - 5, + WormConstants.footMargin + ht / 2); + wd = fm.stringWidth(tempoUnits); + g.drawString(tempoUnits, xSize - WormConstants.sideMargin - wd / 2, + ySize - 5); + } // paint() + + public void print() { + for (int i = 0; i < WormConstants.wormLength; i++) { + int ind = (tail + i) % WormConstants.wormLength; + System.out.println(i+" ["+ind+"] = ("+x[ind]+", "+y[ind]+")"); + } + System.out.println("Tail = " + tail); + } // print() + + public void setPoints(double[] x1, double[] y1, String[] flags, + int start, int len) { + if (start < len - WormConstants.wormLength) + start = len - WormConstants.wormLength; + // int bar = 0; + // int beat = 0; + // int track = 0; + int i = start - 10; // try 10 steps back for smoothing + if (i < 0) + i = 0; + double smoothx = x1[i]; + double smoothy = y1[i]; + double decay = 0.95; + // System.err.println("*************************************************"); + for ( ; i < len; i++) { + // System.err.println(i + ": " + x1[i] + " " + y1[i] + " " + flags[i]); + // if ((flags[i] & WormFile.BAR) != 0) + // bar++; + // if ((flags[i] & WormFile.BEAT) != 0) + // beat++; + // if ((flags[i] & WormFile.TRACK) != 0) + // track++; + smoothx = smoothx * decay + x1[i] * (1 - decay); + smoothy = smoothy * decay + y1[i] * (1 - decay); + if (i >= start) { + tail = i % WormConstants.wormLength; + x[tail] = smoothx; + y[tail] = smoothy; + // labels[tail] = bar + ":" + beat + ":" + track + ":" + + labels[tail] = flags[i] + Format.d(i * framePeriod, 1); + } + } + xCount = len; + repaint(); + } // setPoints() + + void addPoint(double newx, double newy, String theLabel) { + if ((smoothMode == NONE) || (memSize == 0)) { // no smooth or 1st pt + xmem[0] = newx; + ymem[0] = newy; + memSize = 1; + } else if (smoothMode == EXPONENTIAL) { // exp decaying average (IIR) + xmem[0] = xDecay * xmem[0] + (1 - xDecay) * newx; + ymem[0] = yDecay * ymem[0] + (1 - yDecay) * newy; + memSize = 1; + newx = xmem[0]; + newy = ymem[0]; + } else if (smoothMode == HALF_GAUSS) { // Gaussian smoothing + double r = 120 / newx / framePeriod; // 2 beats == half bar?? + int k = (int)Math.ceil(4 * r); // cut off values <<< 0.01 + if (memSize == WormConstants.wormLength) + memSize--; + for (int i = memSize; i > 0; i--) { + xmem[i] = xmem[i-1]; + ymem[i] = ymem[i-1]; + } + xmem[0] = newx; + ymem[0] = newy; + memSize++; + double xTotal = 0; + double yTotal = 0; + double eTotal = 0; + for (int i = 0; (i < memSize) && (i <= k); i++) { + double e = (double)i / r; + e = Math.exp(-e * e / 2); + xTotal += e * xmem[i]; + yTotal += e * ymem[i]; + eTotal += e; + } + if (eTotal != 0) { + xTotal /= eTotal; + yTotal /= eTotal; + } + newx = xTotal; + newy = yTotal; + } else { // (smoothMode == FULL_GAUSS) {} // Retrospective smoothing + double r = 120 / newx / framePeriod; // 2 beats == half bar?? + int k = (int)Math.ceil(3 * r); // cut off values << 0.01 + if (k > memSize / 2) + k = memSize / 2; + if (memSize == WormConstants.wormLength) + memSize--; + for (int i = memSize; i > 0; i--) { + xmem[i] = xmem[i-1]; + ymem[i] = ymem[i-1]; + } + xmem[0] = newx; + ymem[0] = newy; + memSize++; + for (int start = k; start <= 2 * k; start++) { + double xTotal = 0; + double yTotal = 0; + double eTotal = 0; + for (int i = start - k; (i < memSize) && (i <= 2 * k); i++) { + double e = (double)(i - start) / r; + e = Math.exp(-e * e / 2); + xTotal += e * xmem[2 * k - i]; + yTotal += e * ymem[2 * k - i]; + eTotal += e; + } + if (eTotal != 0) { + xTotal /= eTotal; + yTotal /= eTotal; + } + newx = xTotal; + newy = yTotal; + int index = (tail - (2 * k - start) + + WormConstants.wormLength) % WormConstants.wormLength; + x[index] = newx; + y[index] = newy; + } + } + // Calculate x-axis bounds (with decaying average +-10%) and rescale + xSum = xSum * sDecay + newx; + xCount++; + if (autoScaleMode && ( + (newx < xmin) || (newx > xmax) || + (xmin > xSum * (1 - sDecay) * 0.97) || + (xmax < xSum * (1 - sDecay) * 1.03))) { + autoScale(); + } + // Update for all methods + x[tail] = newx; + y[tail] = newy; + labels[tail] = theLabel; + tail = (tail + 1) % WormConstants.wormLength; + } // addPoint() + + public void ancestorMoved(HierarchyEvent e) {} + + public void ancestorResized(HierarchyEvent e) { + if ((xSize == getWidth()) && (ySize == getHeight())) + return; + xSize = getWidth(); + ySize = getHeight(); + xRescale(xmin, xmax); + yRescale(ymin, ymax); + } // ancestorResized() + + void xMoveRight(boolean right) { + setAutoScaleMode(false); + double diff = (xmax - xmin) / (right? -2: 2); + // if (xmin < diff) + // diff = xmin; + xRescale(xmin - diff, xmax - diff); + } // xMoveRight() + + void xZoom(boolean in) { + setAutoScaleMode(false); + double diff = (xmax - xmin) / (in? -4: 2); + // if (xmin < diff) + // xRescale(0, xmax + diff); + // else + xRescale(xmin - diff, xmax + diff); + } // xZoom() + + void xRescale(double min, double max) { + xSize = getWidth(); + xmin = min; + xmax = max; + xScale = (double)(xSize - 2 * WormConstants.sideMargin) / (xmax - xmin); + repaint(); + } // xRescale() + + void yRescale(double min, double max) { + ySize = getHeight(); + ymin = min; + ymax = max; + yScale = (double)(ySize - 2 * WormConstants.footMargin) / (ymax - ymin); + repaint(); + } // yRescale() + + void yMoveDown(boolean down) { + double diff = (ymax - ymin) / (down? 4: -4); + // if (ymin < diff) + // diff = ymin; + yRescale(ymin - diff, ymax - diff); + } // yMoveDown() + + void yZoom(boolean in) { + double diff = (ymax - ymin) / (in? -4: 2); + // if (ymin < diff) + // yRescale(0, ymax + diff); + // else + yRescale(ymin - diff, ymax + diff); + } // yZoom() + + void setAutoScaleMode(boolean set) { + autoScaleMode = set; + if (autoScaleMode) + autoScale(); + if (autoIndicator != null) + autoIndicator.setSelected(autoScaleMode); + } // setAutoScaleMode() + + void autoScale() { + double factor = 0.1; + xRescale(xSum * (1-sDecay) * (1-factor),xSum * (1-sDecay) * (1+factor)); + } // autoScale() + + public static void main(String[] args) { + createInFrame(args); + } // main() + + public static Worm createInFrame(String[] args) { + JFrame f = new JFrame(WormConstants.title); + Worm w = new Worm(f); + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("-d")) { + if (args[i].length() == 2) + AudioWorm.fileDelay = Integer.parseInt(args[++i]); + else + AudioWorm.fileDelay =Integer.parseInt(args[i].substring(2)); + } else if (args[i].endsWith(".wav") || args[i].endsWith(".mp3")) + w.setInputFile(args[i]); + else if (args[i].endsWith(".match")) + w.setMatchFile(args[i]); + else if (args[i].endsWith(".worm")) + w.setWormFile(args[i]); + else + w.setTimingOffsetString(args[i]); + } + f.getContentPane().setBackground(WormConstants.backgroundColor); + f.getContentPane().setLayout(new BoxLayout(f.getContentPane(), + BoxLayout.Y_AXIS)); + f.getContentPane().add(w); + f.getContentPane().add(new WormScrollBar(w)); + f.getContentPane().add(new WormControlPanel(w)); + w.addHierarchyBoundsListener(w); + // or f.getContentPane().addHier... -- both seem to work the same + Dimension borderSize = FrameMargins.get(false); + f.setSize(w.getWidth() + borderSize.width, + w.getHeight() + borderSize.height + WormConstants.cpHeight); + GraphicsConfiguration gc = f.getGraphicsConfiguration(); + Rectangle bounds = gc.getBounds(); + f.setLocation(bounds.x + (bounds.width - f.getWidth()) / 2, + bounds.y + (bounds.height - f.getHeight()) / 2); + f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + f.setVisible(true); + f.setIconImage(WormIcon.getWormIcon(1,f)); + if (args.length > 0) + w.play(); + return w; + } // createInFrame() + +} // class Worm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormConstants.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,86 @@ +/* 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.awt.Color; +import at.ofai.music.util.Colors; + +class WormConstants implements Colors { +public static String version = " 1.4-RC2 "; // DON'T EDIT THIS LINE; see make.sh + public static String title = "Performance Worm v" + version.substring(1) + + "(c) 2002 ofaiMusic <simon@ofai.at>"; + public static int X_SZ = 800; // Default x-size of Worm Panel + public static int Y_SZ = 500; // Default y-size of Worm Panel + public static int cpHeight = 50; // Height of control panel + public static int footMargin = 20; // Distance from x-axis to top/bottom + public static int sideMargin = 40; // Distance from y-axis to sides + public static int wormLength = 300; // Number of points in the worm + public static Color buttonTextColor = Color.black; + public static Color buttonColor = Color.white; + public static Color axesColor = Color.black; + public static Color backgroundColor = Color.white; + public static Color wormHeadColor = Color.red; + public static Color wormTailColor = Color.white;// new Color(255,240,240); + public static Color wormHeadRimColor = Color.black; + public static Color wormTailRimColor = Color.white; + public static Color wormFaceColor = Color.black; + public static Color altFaceColor = Color.white; + // BROWN version: new Color(255, 200, 160); + + public Color getBackground() { return backgroundColor; } + public Color getForeground() { return axesColor; } + public Color getButton() { return buttonColor; } + public Color getButtonText() { return buttonTextColor; } + + public static void setDayColours() { + buttonTextColor = Color.black; + buttonColor = Color.white; + axesColor = Color.black; + backgroundColor = Color.white; + wormHeadColor = Color.red; + wormTailColor = Color.white; + wormHeadRimColor = Color.black; + wormTailRimColor = Color.white; + wormFaceColor = Color.black; + altFaceColor = Color.white; + } // setDayColours() + + public static void setNightColours() { + buttonTextColor = Color.yellow; + buttonColor = Color.black; + axesColor = Color.yellow; + backgroundColor = Color.black; + wormHeadColor = Color.green; + wormTailColor = Color.black; + wormHeadRimColor = Color.yellow; + wormTailRimColor = Color.black; + wormFaceColor = Color.black; + altFaceColor = Color.black; + } // setNightColours() + + public static void setGlow(boolean flag) { + if (flag) + setNightColours(); + else + setDayColours(); + } // setGlow() + +} // class WormConstants
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormControlPanel.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,188 @@ +/* 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 javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JRadioButtonMenuItem; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.HierarchyBoundsListener; +import java.awt.event.HierarchyEvent; + +import at.ofai.music.util.PSPrinter; + + +class WormControlPanel extends JPanel + implements ActionListener, HierarchyBoundsListener { + + static final long serialVersionUID = 0; + protected Worm worm; + protected JPopupMenu flagMenu; + protected JRadioButtonMenuItem[] rb; + protected String[] buttonText = { + "<<x>>", "<<y>>", "^^^", "<<<", "ML >>", "Smooth", "Flags", "Play","Stop", + ">>x<<", ">>y<<", "vvv", ">>>", "<< ML", "Header", "Load", "Save","Quit"}; + + public WormControlPanel(Worm w) { + worm = w; + setBackground(WormConstants.buttonColor); + setLayout(new GridLayout(2, buttonText.length / 2)); + for (int i = 0; i < buttonText.length; i++) { + JButton theButton = new JButton(buttonText[i]); + theButton.setBackground(WormConstants.buttonColor); + theButton.setForeground(WormConstants.buttonTextColor); + theButton.addActionListener(this); + add(theButton); + if (buttonText[i].equals("Flags")) { + flagMenu = new JPopupMenu(buttonText[i]); + JCheckBoxMenuItem scale = new JCheckBoxMenuItem("AutoScale"); + scale.setBackground(WormConstants.buttonColor); + scale.setForeground(WormConstants.buttonTextColor); + scale.setSelected(true); + scale.addActionListener(this); + worm.setAutoButton(scale); + flagMenu.add(scale); + flagMenu.addSeparator(); + JCheckBoxMenuItem glow = new JCheckBoxMenuItem("Glow Worm"); + glow.setBackground(WormConstants.buttonColor); + glow.setForeground(WormConstants.buttonTextColor); + glow.setSelected(false); + glow.addActionListener(this); + flagMenu.add(glow); + flagMenu.addSeparator(); + JCheckBoxMenuItem plots = new JCheckBoxMenuItem("Histograms"); + plots.setBackground(WormConstants.buttonColor); + plots.setForeground(WormConstants.buttonTextColor); + plots.setSelected(false); + plots.addActionListener(this); + flagMenu.add(plots); + flagMenu.addSeparator(); + ButtonGroup bg = new ButtonGroup(); + rb = new JRadioButtonMenuItem[Worm.smoothLabels.length]; + for (int j = 0; j < Worm.smoothLabels.length; j++) { + rb[j] = new JRadioButtonMenuItem(Worm.smoothLabels[j]); + rb[j].setBackground(WormConstants.buttonColor); + rb[j].setForeground(WormConstants.buttonTextColor); + rb[j].addActionListener(this); + flagMenu.add(rb[j]); + bg.add(rb[j]); + } + rb[w.getSmoothMode()].setSelected(true); + worm.setSmoothButtons(rb); + flagMenu.setInvoker(theButton); + flagMenu.addSeparator(); + JMenuItem printButton = new JMenuItem("Print300"); + printButton.setBackground(WormConstants.buttonColor); + printButton.setForeground(WormConstants.buttonTextColor); + printButton.addActionListener(this); + flagMenu.add(printButton); + JMenuItem printButton2 = new JMenuItem("Print600"); + printButton2.setBackground(WormConstants.buttonColor); + printButton2.setForeground(WormConstants.buttonTextColor); + printButton2.addActionListener(this); + flagMenu.add(printButton2); + } else if (buttonText[i].equals("Play")) + worm.setPlayButton(theButton); + } + setSize(w.getWidth(), WormConstants.cpHeight); + setMaximumSize(new Dimension(w.getWidth(), WormConstants.cpHeight)); + addHierarchyBoundsListener(this); + worm.setControlPanel(this); // for callback + } // constructor + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("<<x>>")) + worm.xZoom(true); + else if (e.getActionCommand().equals(">>x<<")) + worm.xZoom(false); + else if (e.getActionCommand().equals("<<<")) + worm.xMoveRight(false); + else if (e.getActionCommand().equals(">>>")) + worm.xMoveRight(true); + else if (e.getActionCommand().equals("ML >>")) + worm.audio.ti.switchLevels(true); + else if (e.getActionCommand().equals("<< ML")) + worm.audio.ti.switchLevels(false); + else if (e.getActionCommand().equals("Play") || + e.getActionCommand().equals("Cont")) + worm.play(); + else if (e.getActionCommand().equals("Pause")) + worm.pause(); + else if (e.getActionCommand().equals("Stop")) + worm.stop(); + else if (e.getActionCommand().equals("<<y>>")) + worm.yZoom(true); + else if (e.getActionCommand().equals(">>y<<")) + worm.yZoom(false); + else if (e.getActionCommand().equals("vvv")) + worm.yMoveDown(true); + else if (e.getActionCommand().equals("^^^")) + worm.yMoveDown(false); + else if (e.getActionCommand().equals("Header")) { + worm.editParameters(); + } else if (e.getActionCommand().equals("Smooth")) + worm.smooth(); + else if (e.getActionCommand().equals("Flags")) { + flagMenu.setVisible(true); + flagMenu.setLocation( + ((Component)e.getSource()).getLocationOnScreen()); + } else if (e.getActionCommand().equals("Load")) { + new WormLoadDialog(worm); + } else if (e.getActionCommand().equals("Save")) { + worm.save(new MyFileChooser().browseSave()); + } else if (e.getActionCommand().equals("Quit")) + System.exit(0); + else if (e.getActionCommand().equals("AutoScale")) + worm.setAutoScaleMode( + ((JCheckBoxMenuItem)e.getSource()).isSelected()); + else if (e.getActionCommand().equals("Glow Worm")) + worm.setGlow(((JCheckBoxMenuItem)e.getSource()).isSelected()); + else if (e.getActionCommand().equals("Histograms")) + TempoInducer.plotFlag = + ((JCheckBoxMenuItem)e.getSource()).isSelected(); + else if (e.getActionCommand().equals("Print300")) + PSPrinter.print(worm, 300); + else if (e.getActionCommand().equals("Print600")) + PSPrinter.print(worm, 600); + else + for (int i = 0; i < Worm.smoothLabels.length; i++) + if (e.getActionCommand().equals(Worm.smoothLabels[i])) + worm.setSmoothMode(i); + } // actionPerformed + + public void ancestorMoved(HierarchyEvent e) {} + + public void ancestorResized(HierarchyEvent e) { + setMaximumSize(new Dimension(worm.getWidth(), WormConstants.cpHeight)); + setSize(worm.getWidth(), WormConstants.cpHeight); + repaint(); + } // ancestorResized() + +} // class WormControlPanel
--- /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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormIcon.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,125 @@ +/* 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.awt.Component; +import java.awt.Graphics; +import java.awt.Image; + +class WormIcon { + protected int xSize, ySize; + protected int[] dx; + protected int[] dy; + + public WormIcon(int type) { + switch (type) { + case 1: + xSize = 40; + ySize = 40; + dx = new int[]{3,-1,0,1,2,4, 4, 2, 1,1,2,4, 4, 2, 1, 0,-1}; + dy = new int[]{8, 5,5,4,3,2,-3,-4,-5,5,4,3,-2,-3,-4,-5,-5}; + break; + case 2: + xSize = 300; + ySize = 100; + dx = new int[] { + 10, 4, 3, 2, 0,-2,-3,-4,-3,-2,-1, 0, 0, 1, 1, 1, // P + 25, 4, 4, 2,-1,-3,-4,-4,-3,-1, 1, 3, 4, 4, 4, // e + 13, 0, 0, 0, 1, 3, 5, 3, 1, // r + 13, 0, 0, 0,-4, 8,-4, 0, 1, 3, 5, 3, 1, // f + 17, -5,-3,-1, 1, 3, 5, 5, 3, 1,-1,-3,-5, // o + 20, 0, 0, 0, 1, 3, 5, 3, 1, // r + 10, -1, 0, 1, 2, 4, 3, 1, 1, 3, 4, 2, 1, 0,-1, // m + 30, -1, 0,-1,-3,-5,-5,-3,-1, 1, 3, 5, 5, 3, 1, 0, 1, // a + 12, 0, 0, 0, 1, 3, 5, 3, 1, 0, 0, 0, // n + 28, -3,-5,-5,-3,-1, 1, 3, 5, 5, 3, // c + 17, 4, 4, 2,-1,-3,-4,-4,-3,-1, 1, 3, 4, 4, 4, // e + -180,-1, 0, 1, 2, 4, 4, 2, 1, 1, 2, 4, 4, 2, 1, 0,-1, // W + 20, -5,-3,-1, 1, 3, 5, 5, 3, 1,-1,-3,-5, // o + 20, 0, 0, 0, 1, 3, 5, 3, 1, // r + 10, -1, 0, 1, 2, 4, 3, 1, 1, 3, 4, 2, 1, 0,-1 // m + }; + dy = new int[] { + 20, 0,-2,-3,-5,-3,-2, 0, 2, 3, 4, 4, 4, 4, 5, 5, // P + -8, 0,-1,-3,-3,-2,-1, 2, 3, 4, 5, 4, 2, 0,-1, // e + 2, -4,-4,-4,-3,-1, 0, 1, 3, // r + 12, -4,-4,-4, 0, 0,-4,-4,-3,-2, 0, 2, 3, // f + 3, 1, 3, 5, 5, 3, 1,-1,-3,-5,-5,-3,-1, // o + 16, -4,-4,-4,-3,-1, 0, 1, 3, // r + 13, -4,-4,-4,-3, 1, 3, 4,-4,-3,-1, 3, 4, 4, 4, // m + -16, 5, 4, 4, 3, 1,-1,-3,-4,-4,-3,-1, 1, 3, 4, 4, 5, // a + 0, -4,-4,-4,-3,-1, 0, 1, 3, 4, 4, 4, // n + -14,-3,-1, 1, 3, 5, 5, 3, 1,-1,-3, // c + -5, 0,-1,-3,-3,-2,-1, 2, 3, 4, 5, 4, 2, 0,-1, // e + 20, 5, 5, 4, 3, 2,-3,-4,-5, 5, 4, 3,-2,-3,-4,-5,-5, // W + 4, 1, 3, 5, 5, 3, 1,-1,-3,-5,-5,-3,-1, // o + 16, -4,-4,-4,-3,-1, 0, 1, 3, // r + 13, -4,-4,-4,-3, 1, 3, 4,-4,-3,-1, 3, 4, 4, 4 // m + }; + break; + case 3: + xSize = 136; + ySize = 48; + dx = new int[] { + 9, -1, 0, 1, 2, 4, 4, 2, 1, 1, 2, 4, 4, 2, 1, 0,-1, + 20, -5,-3,-1, 1, 3, 5, 5, 3, 1,-1,-3,-5, + 20, 0, 0, 0, 1, 3, 5, 3, 1, + 10, -1, 0, 1, 2, 4, 3, 1, 1, 3, 4, 2, 1, 0,-1 + }; + dy = new int[] { + 12, 5, 5, 4, 3, 2,-3,-4,-5, 5, 4, 3,-2,-3,-4,-5,-5, + 4, 1, 3, 5, 5, 3, 1,-1,-3,-5,-5,-3,-1, + 16, -4,-4,-4,-3,-1, 0, 1, 3, + 13, -4,-4,-4,-3, 1, 3, 4,-4,-3,-1, 3, 4, 4, 4 + }; + break; + } + } + + public static Image getWormIcon(int type, Component c) { + return new WormIcon(type).getImage(c); + } + + public int getX() { return xSize; } + public int getY() { return ySize; } + + public Image getImage(Component c) { + Image img = c.createImage(xSize, ySize); + Graphics g = img.getGraphics(); + g.setColor(WormConstants.backgroundColor); + g.fillRect(0, 0, xSize, ySize); + int x = 0; + int y = 0; + for (int i = 0; i < dx.length; i++) { + x += dx[i]; + y += dy[i]; + g.setColor(WormConstants.wormHeadRimColor); + g.fillOval(x-1, y-1, 9, 9); + g.setColor(WormConstants.wormHeadColor); + g.fillOval(x, y, 7, 7); + } + g.setColor(WormConstants.wormHeadRimColor); + g.drawLine(x+2, y+2, x+2, y+2); + g.drawLine(x+4, y+2, x+4, y+2); + return img; + } // getImage() + +} // class WormIcon
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormLoadDialog.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,188 @@ +/* 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 javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.TextField; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.TextEvent; +import java.awt.event.TextListener; + +import at.ofai.music.util.FrameMargins; + +class WormLoadDialog extends JDialog { + + static final long serialVersionUID = 0; + + class MyButton extends JButton { + + static final long serialVersionUID = 0; + public MyButton(String text, ActionListener al) { + super(text); + setBackground(WormConstants.buttonColor); + setForeground(WormConstants.buttonTextColor); + addActionListener(al); + } + } // inner class MyButton + + class MyTextField extends TextField { + + static final long serialVersionUID = 0; + public MyTextField(String text, TextListener tl) { + super(text); + setBackground(WormConstants.buttonColor); + setForeground(WormConstants.buttonTextColor); + addTextListener(tl); + } + + } // inner class MyTextField + + class MyLabel extends JLabel { + + static final long serialVersionUID = 0; + public MyLabel(String text) { + super(text); + setForeground(WormConstants.buttonTextColor); + } + + } // inner class MyLabel + + Worm worm; + TextField inputFileField, matchFileField, wormFileField, timingOffsetField, + synchronisationField; + JButton inputFileButton, matchFileButton, wormFileButton, cancelButton, + okButton; + MyFileChooser chooser; + + public WormLoadDialog(Worm w) { + super(w.theFrame, "Input Data", true); + worm = w; + worm.stop(); + chooser = new MyFileChooser(); + inputFileField = new MyTextField(worm.getInputFile(),new TextListener(){ + public void textValueChanged(TextEvent e) { + worm.setInputFile(inputFileField.getText()); + worm.clearWormFile(); + } + }); + matchFileField = new MyTextField(worm.getMatchFile(),new TextListener(){ + public void textValueChanged(TextEvent e) { + worm.setMatchFile(matchFileField.getText()); + } + }); + wormFileField = new MyTextField(worm.getWormFileName(), + new TextListener() { + public void textValueChanged(TextEvent e) { + worm.setWormFile(wormFileField.getText()); + } + } + ); + timingOffsetField = new MyTextField(worm.getTimingOffsetString(), + new TextListener() { + public void textValueChanged(TextEvent e) { + worm.setTimingOffsetString(timingOffsetField.getText()); + } + } + ); + synchronisationField = new MyTextField(worm.getFileDelayString(), + new TextListener() { + public void textValueChanged(TextEvent e) { + worm.setFileDelayString(synchronisationField.getText()); + } + } + ); + inputFileButton = new MyButton("Browse", new ActionListener() { + public void actionPerformed(ActionEvent e) { + worm.setInputFile(chooser.browseOpen(inputFileField, + MyFileFilter.waveFilter)); + } + }); + matchFileButton = new MyButton("Browse", new ActionListener() { + public void actionPerformed(ActionEvent e) { + worm.setMatchFile(chooser.browseOpen(matchFileField, + MyFileFilter.matchFilter)); + } + }); + wormFileButton = new MyButton("Browse", new ActionListener() { + public void actionPerformed(ActionEvent e) { + worm.setWormFile(chooser.browseOpen(wormFileField, + MyFileFilter.wormFilter)); + } + }); + cancelButton = new MyButton("OK", new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + okButton = new MyButton("OK", new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + Container cp = getContentPane(); + cp.setLayout(null); + JPanel p1 = new JPanel(new GridLayout(5,1)); + JPanel p2 = new JPanel(new GridLayout(5,1)); + JPanel p3 = new JPanel(new GridLayout(5,1)); + JLabel inputLabel = new MyLabel("Input file: "); + JLabel matchLabel = new MyLabel("Match file: "); + JLabel wormLabel = new MyLabel("Worm file: "); + JLabel timingOffsetLabel = new MyLabel("Match/Audio Offset: "); + JLabel synchronisationLabel = new MyLabel("Synchronisation: "); + cp.setBackground(WormConstants.buttonColor); + p1.setBackground(WormConstants.buttonColor); + p1.add(inputLabel); + p2.add(inputFileField); + p3.add(inputFileButton); + p1.add(matchLabel); + p2.add(matchFileField); + p3.add(matchFileButton); + p1.add(wormLabel); + p2.add(wormFileField); + p3.add(wormFileButton); + p1.add(timingOffsetLabel); + p2.add(timingOffsetField); + p3.add(cancelButton); + p1.add(synchronisationLabel); + p2.add(synchronisationField); + p3.add(okButton); + p1.setBounds(10,5,130,150); + p2.setBounds(150,5,500,150); + p3.setBounds(660,5,100,150); + cp.add(p1); + cp.add(p2); + cp.add(p3); + Dimension d = FrameMargins.get(false); + setSize(770 + d.width, 160 + d.height); + int x = w.getLocationOnScreen().x + (w.getWidth() - getWidth()) / 2; + int y = w.getLocationOnScreen().y + (w.getHeight() - getHeight()) / 2; + setLocation(x, y); + setVisible(true); + } // constructor + +} // class WormLoadDialog
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormParameters.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,232 @@ +/* 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.PrintStream; +import java.io.BufferedReader; +import java.io.IOException; +import at.ofai.music.util.Parameters; + +public class WormParameters extends Parameters { + + static final long serialVersionUID = 0; + public static final String VERSION = "WORM Version"; + public static final String FRAMEPERIOD = "FrameLength"; + public static final String COMPOSER = "Composer"; + public static final String PIECE = "Piece"; + public static final String PERFORMER = "Performer"; + public static final String KEY = "Key"; + public static final String YEAR = "YearOfRecording"; + public static final String INDICATION = "Indication"; + public static final String BEATLEVEL = "BeatLevel"; + public static final String TRACKLEVEL = "TrackLevel"; + public static final String STARTBAR = "StartBarNumber"; + public static final String UPBEAT = "Upbeat"; + public static final String BEATSPERBAR = "BeatsPerBar"; + public static final String LENGTH = "Length"; + public static final String AUDIOPATH = "AudioPath"; + public static final String AUDIOFILE = "AudioFile"; + public static final String SMOOTHING = "Smoothing"; + public static final String AXIS = "Axis"; + public static final String RESOLUTION = "Time Resolution"; + public static final String UNITS = "LoudnessUnits"; + public static final String TEMPOLATE = "TempoLate"; + public static final String TITLE = "Edit Worm Parameters"; + public static final String SEP = ":\t"; + public static final char SEPCHAR = ':'; + + protected double framePeriod; + protected String trackLevel, loudnessUnits, version, composer, piece, + performer, key, year, indication, audioFile, audioPath, + smoothing, axis, beatLevel, upbeat, startBar, tempoLate; + protected int beatsPerBar, length; + + public WormParameters(java.awt.Frame f) { + super(f, TITLE); + composer = "Unknown composer"; + piece = "unknown piece"; + performer = "unknown performer"; + key = ""; + year = ""; + indication = ""; + beatLevel = "1/4"; + trackLevel = "1.0"; + upbeat = "0"; + startBar = "1"; + beatsPerBar = 4; + length = 0; + audioFile = ""; + audioPath = ""; + smoothing = ""; + axis = ""; + version = "1.0"; + loudnessUnits = "dB"; + tempoLate = ""; + framePeriod = WormFile.defaultFramePeriod; + } // constructor + + public void editParameters() { + editParameters(true); + } // editParameters() + + public void editParameters(boolean doEdit) { + setString(COMPOSER, composer); + setString(PIECE, piece); + setString(PERFORMER, performer); + setString(KEY, key); + setString(YEAR, year); + setString(INDICATION, indication); + setString(BEATLEVEL, beatLevel); // e.g. 3/8 + setString(TRACKLEVEL, trackLevel); + setString(UPBEAT, upbeat); + setString(STARTBAR, startBar); + setInt(BEATSPERBAR, beatsPerBar); + setInt(LENGTH, length); + setString(AUDIOPATH, audioPath); + setString(AUDIOFILE, audioFile); + setString(SMOOTHING, smoothing); + setString(AXIS, axis); + setString(VERSION, version); + setDouble(RESOLUTION, framePeriod); + setString(UNITS, loudnessUnits); + setString(TEMPOLATE, tempoLate); + setVisible(doEdit); + composer = getString(COMPOSER); + piece = getString(PIECE); + performer = getString(PERFORMER); + key = getString(KEY); + year = getString(YEAR); + indication = getString(INDICATION); + beatLevel = getString(BEATLEVEL); // e.g. 3/8 + trackLevel = getString(TRACKLEVEL); + upbeat = getString(UPBEAT); + startBar = getString(STARTBAR); + beatsPerBar = getInt(BEATSPERBAR); + length = getInt(LENGTH); + audioPath = getString(AUDIOPATH); + audioFile = getString(AUDIOFILE); + smoothing = getString(SMOOTHING); + axis = getString(AXIS); + version = getString(VERSION); + framePeriod = getDouble(RESOLUTION); + loudnessUnits = getString(UNITS); + tempoLate = getString(TEMPOLATE); + } // editParameters() + + public void write(PrintStream out, int length, double outFramePeriod) { + out.println(VERSION + SEP + version); + out.println(FRAMEPERIOD + SEP + outFramePeriod); + out.println(UNITS + SEP + loudnessUnits); + if ((audioPath.length() > 0) && !audioPath.endsWith("/")) + audioPath += "/"; + out.println(AUDIOFILE + SEP + audioPath + audioFile); + out.println(SMOOTHING + SEP + smoothing); + out.println(COMPOSER + SEP + composer); + out.println(PIECE + SEP + piece); + out.println(PERFORMER + SEP + performer); + out.println(BEATLEVEL + SEP + beatLevel); + out.println(TRACKLEVEL + SEP + trackLevel); + out.println(UPBEAT + SEP + upbeat); + out.println(STARTBAR + SEP + startBar); + out.println(BEATSPERBAR + SEP + beatsPerBar); + out.println(AXIS + SEP + axis); + out.println(TEMPOLATE + SEP + tempoLate); + out.println(LENGTH + SEP + length); + } // write() + + public String read(BufferedReader in) throws IOException { + String input = in.readLine(); + if (input == null) + throw new RuntimeException("Empty input file"); + if (!input.startsWith("WORM")) + throw new RuntimeException("Bad header format: not a WORM file"); + int delimiter = input.indexOf(SEPCHAR); + while (delimiter >= 0) { + String attribute = input.substring(0,delimiter).trim(); + String value = input.substring(delimiter+1).trim(); + if (attribute.equalsIgnoreCase(VERSION)) + version = value; + else if (attribute.equalsIgnoreCase(FRAMEPERIOD)) + framePeriod = Double.parseDouble(value); + else if (attribute.equalsIgnoreCase(UNITS)) + loudnessUnits = value; + else if (attribute.equalsIgnoreCase(LENGTH)) + length = Integer.parseInt(value); + else if (attribute.equalsIgnoreCase(AUDIOFILE)) { + int index = value.lastIndexOf('/'); + if (index >= 0) + audioPath = value.substring(0, index); + audioFile = value.substring(index + 1); + } else if (attribute.equalsIgnoreCase(SMOOTHING)) + smoothing = value; + else if (attribute.equalsIgnoreCase(COMPOSER)) + composer = value; + else if (attribute.equalsIgnoreCase(PIECE)) + piece = value; + else if (attribute.equalsIgnoreCase(PERFORMER)) + performer = value; + else if (attribute.equalsIgnoreCase(KEY)) + key = value; + else if (attribute.equalsIgnoreCase(INDICATION)) + indication = value; + else if (attribute.equalsIgnoreCase(YEAR)) + year = value; + else if (attribute.equalsIgnoreCase(BEATLEVEL)) + beatLevel = value; + else if (attribute.equalsIgnoreCase(TRACKLEVEL)) + trackLevel = value; + else if (attribute.equalsIgnoreCase(STARTBAR)) + startBar = value; + else if (attribute.equalsIgnoreCase(UPBEAT)) + upbeat = value; + else if (attribute.equalsIgnoreCase(BEATSPERBAR)) + beatsPerBar = Integer.parseInt(value); + else if (attribute.equalsIgnoreCase(AXIS)) + axis = value; + else if (attribute.equalsIgnoreCase(TEMPOLATE)) + tempoLate = value; + else + System.err.println("Warning: Unrecognised header data: " + + attribute + SEP + value); + input = in.readLine(); + if (input != null) + delimiter = input.indexOf(SEPCHAR); + else + break; + } + return input; + } // read() + + public double getTrackLevel() { + try { + int i = trackLevel.indexOf("/"); + if (i >= 0) + return Double.parseDouble(trackLevel.substring(0,i)) / + Double.parseDouble(trackLevel.substring(i+1)); + else + return Double.parseDouble(trackLevel); + } catch (Exception e) { + System.err.println("Error getting TrackLevel:\n" + e); + return 1; + } + } // getTrackLevel() + +} // WormParameters
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormScrollBar.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,66 @@ +/* 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 javax.swing.JScrollBar; +import java.awt.Adjustable; +import java.awt.Dimension; + +class WormScrollBar extends JScrollBar { + + static final long serialVersionUID = 0; + + protected Worm worm; + + public WormScrollBar(Worm w) { + super(Adjustable.HORIZONTAL, 0, 10, 0, 1010); + // setOrientation(Adjustable.HORIZONTAL); + // setVisibleAmount(...); + // setMinimum(...); + // setMaximum(...); + // setValue(...); + setUnitIncrement(10); + setBlockIncrement(100); + worm = w; + setBackground(WormConstants.buttonColor); + setPreferredSize(new Dimension(w.getWidth(), 17)); + worm.setScrollBar(this); + } // constructor + + public void setValue(int value) { + if (worm.audio != null) + worm.audio.skipTo(value); + super.setValue(value); + } // setValue() + + public void setValueNoFeedback(int value) { super.setValue(value); } + + // // MouseListener Interface + // public void mouseClicked(MouseEvent e) {} + // public void mouseEntered(MouseEvent e) {} + // public void mouseExited(MouseEvent e) {} + // public void mousePressed(MouseEvent e) {} + // public void mouseReleased(MouseEvent e) {} + // + // // AdjustmentListener interface + // public void adjustmentValueChanged(AdjustmentEvent e) {} + +} // class WormScrollBar
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/at/ofai/music/worm/WormSmoothDialog.java Fri Oct 08 16:11:06 2010 +0100 @@ -0,0 +1,52 @@ +/* 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 at.ofai.music.util.Parameters; + +class WormSmoothDialog extends Parameters { + + static final long serialVersionUID = 0; + + public WormSmoothDialog (Worm w, WormFile wf) { + super(w.theFrame, "Smoothing parameters"); + setDouble("Before", 1); + setDouble("After", 1); + String[] labels = new String[]{"Tracks","Beats","Bars","Seconds"}; + int[] levels = {WormFile.TRACK, WormFile.BEAT, WormFile.BAR, 0}; + setChoice("Units", labels, 1); + setVisible(true); + try { + double before = getDouble("Before"); + double after = getDouble("After"); + String smoothLevel = getChoice("Units"); + int smoothIndex = 0; + for (int i = 0; i < labels.length; i++) + if (smoothLevel.equals(labels[i])) + smoothIndex = i; + if ((after > 0) && (before > 0)) + wf.smooth(Worm.FULL_GAUSS, before, after, levels[smoothIndex]); + else + wf.smooth(Worm.NONE, 0, 0, 0); + } catch (NumberFormatException e) {} + } + +} // class WormSmoothDialog