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

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