Mercurial > hg > beatroot
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 |