andrew@0
|
1 /*
|
andrew@0
|
2 * RecordedMultipleAudio.cpp
|
andrew@2
|
3 * Drum Timing Analyser
|
andrew@0
|
4 *
|
andrew@0
|
5 * Created by Andrew on 31/01/2012.
|
andrew@0
|
6 * Copyright 2012 QMUL. All rights reserved.
|
andrew@0
|
7 *
|
andrew@0
|
8 */
|
andrew@0
|
9
|
andrew@0
|
10 #include "RecordedMultipleAudio.h"
|
andrew@0
|
11
|
andrew@2
|
12 const bool printExportInfo = false;
|
andrew@2
|
13
|
andrew@0
|
14 RecordedMultipleAudio::RecordedMultipleAudio(){
|
andrew@0
|
15
|
andrew@0
|
16 infoFilepath = "../../../data/errorData.txt";
|
andrew@2
|
17
|
andrew@2
|
18 exactOnsetFilePath = "../../../data/exactOnsetTimes.txt";
|
andrew@2
|
19 kickRelativeTempoFilePath = "../../../data/kickRelativeTempoTimes.txt";
|
andrew@2
|
20 kickRelativeErrorsFilePath = "../../../data/kickRelativeErrorTimes.txt";
|
andrew@2
|
21 kickRelativeClickFilePath = "../../../data/kickRelativeClickTimes.txt";
|
andrew@2
|
22
|
andrew@2
|
23 drumTimerTempoFilePath = "../../../data/drummerTimerTempoTimes.txt";
|
andrew@2
|
24 drumTimerErrorsFilePath = "../../../data/drummerTimerErrorTimes.txt";
|
andrew@2
|
25 drumTimerClickFilePath = "../../../data/drummerTimerClickTimes.txt";
|
andrew@2
|
26
|
andrew@0
|
27 //infoFilepath = "/Users/andrew/errorData.txt";
|
andrew@0
|
28
|
andrew@0
|
29 timingOffset = 0;
|
andrew@2
|
30 playPositionSeconds = 3.4;
|
andrew@0
|
31 }
|
andrew@0
|
32
|
andrew@0
|
33 void RecordedMultipleAudio::loadTestAudio(){
|
andrew@0
|
34
|
andrew@2
|
35 int multitrackToLoad = 7;
|
andrew@0
|
36
|
andrew@0
|
37 numberOfAudioTracks = 2;
|
andrew@0
|
38
|
andrew@0
|
39 printf("loaded max val is %f\n", loadedAudioFiles[0].fileLoader.onsetDetect.onsetDetector.maximumDetectionValue);
|
andrew@0
|
40
|
andrew@2
|
41
|
andrew@2
|
42
|
andrew@0
|
43 setDifferentMultitracks(multitrackToLoad);//command to load this set of audio files - see below
|
andrew@0
|
44
|
andrew@0
|
45 drumTimingAnalyser.phaseCost = 1000;//v high - i.e. dont do phase
|
andrew@0
|
46
|
andrew@2
|
47
|
andrew@0
|
48 drawWindow = 1;
|
andrew@0
|
49 trackScreenHeight = 0.25;
|
andrew@0
|
50
|
andrew@0
|
51
|
andrew@0
|
52
|
andrew@0
|
53 // printf("AFTER LOADING: \n");
|
andrew@0
|
54 // printInfo();
|
andrew@0
|
55
|
andrew@0
|
56 }
|
andrew@0
|
57 #pragma mark -loadingPrerecordedTracks
|
andrew@0
|
58 void RecordedMultipleAudio::setDifferentMultitracks(const int& setToLoad){
|
andrew@0
|
59 const char *kickfilename ;//= "../../../data/sound/LiveDues/kick_liveDues.wav";
|
andrew@0
|
60 const char *roomfilename ;//"../../../data/sound/LiveDues/bass_upsideLive.wav";
|
andrew@0
|
61 const char *snarefilename ;
|
andrew@0
|
62 std::string sonicVizBeatsFilename ;
|
andrew@0
|
63
|
andrew@0
|
64 switch (setToLoad) {
|
andrew@0
|
65 case 0:
|
andrew@0
|
66 kickfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/kickFuture.wav";
|
andrew@0
|
67 roomfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/roomFuture.wav";
|
andrew@0
|
68 snarefilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/snareFuture.wav";
|
andrew@0
|
69 sonicVizBeatsFilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/futureHides/FutureHidesBeats.txt";
|
andrew@0
|
70 break;
|
andrew@0
|
71
|
andrew@0
|
72
|
andrew@0
|
73 case 1:
|
andrew@0
|
74 roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/Mixdown/PennyArcade_StudioMixdown.wav";
|
andrew@0
|
75 kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/kick.wav";
|
andrew@0
|
76 snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/snare.wav";
|
andrew@0
|
77 sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcadeStudio14aMultitrack/Mixdown/PennyArcade_StudioMixdown_beats.txt";
|
andrew@0
|
78 break;
|
andrew@0
|
79
|
andrew@0
|
80 case 2:
|
andrew@0
|
81 roomfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/colesL_bip.wav";
|
andrew@0
|
82 kickfilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/Kick_bip.wav";
|
andrew@0
|
83 snarefilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/Snare_bip.wav";
|
andrew@0
|
84 sonicVizBeatsFilename = "/Users/andrew/Documents/work/programming/MadMax/AudioFiles/MattIngramGreenSection/IngramGreenSectionBeats.txt";
|
andrew@0
|
85 break;
|
andrew@0
|
86
|
andrew@0
|
87 case 3:
|
andrew@0
|
88 roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/coles_bip.wav";
|
andrew@0
|
89 kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/kick d112_bip.wav";
|
andrew@0
|
90 snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/snare bottom_bip.wav";
|
andrew@0
|
91 sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDiamondWhite/tractorsDiamondWhite/Bounces/diamondWhiteMultiTakeOne/TakeOneBeats.txt";
|
andrew@0
|
92 break;
|
andrew@0
|
93
|
andrew@0
|
94 case 4:
|
andrew@0
|
95 roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/neuamnn_bip.wav";
|
andrew@0
|
96 kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/kick_bip.wav";
|
andrew@0
|
97 snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/snare_bip.wav";
|
andrew@0
|
98 sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeOne_4/PennyArcade_take4_beats.txt";
|
andrew@0
|
99 break;
|
andrew@0
|
100
|
andrew@2
|
101 /*
|
andrew@0
|
102 case 5:
|
andrew@0
|
103 roomfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/neuamnn_bip.wav";
|
andrew@0
|
104 kickfilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/kick_bip.wav";
|
andrew@0
|
105 snarefilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/snare_bip.wav";
|
andrew@0
|
106 sonicVizBeatsFilename = "/Volumes/Supersaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/PennyArcade_take5_beats.txt";
|
andrew@0
|
107 break;
|
andrew@2
|
108 */
|
andrew@2
|
109 case 5:
|
andrew@2
|
110 roomfilename = "/Volumes/MiniSaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/neuamnn_bip.wav";
|
andrew@2
|
111 kickfilename = "/Volumes/MiniSaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/kick_bip.wav";
|
andrew@2
|
112 snarefilename = "/Volumes/MiniSaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/snare_bip.wav";
|
andrew@2
|
113 sonicVizBeatsFilename = "/Volumes/MiniSaurus/TractorsAlbum/tractorsDemo/Bounces/PennyArcade_Multitracks/TakeTwo_5/PennyArcade_take5_beats.txt";
|
andrew@2
|
114 break;
|
andrew@0
|
115
|
andrew@0
|
116
|
andrew@2
|
117 case 6:
|
andrew@2
|
118 roomfilename = "/Users/andrew/Documents/work/Alignment/FunkyDrummerAnalysis/FunkyDrummerBreak.wav";
|
andrew@2
|
119 kickfilename = "/Users/andrew/Documents/work/Alignment/FunkyDrummerAnalysis/FunkyDrummerBreak.wav";
|
andrew@2
|
120 snarefilename = "/Users/andrew/Documents/work/Alignment/FunkyDrummerAnalysis/FunkyDrummerBreak.wav";
|
andrew@2
|
121 sonicVizBeatsFilename = "/Users/andrew/Documents/work/Alignment/FunkyDrummerAnalysis/FunkyDrummerBeats.txt";
|
andrew@2
|
122 break;
|
andrew@2
|
123
|
andrew@2
|
124 case 7:
|
andrew@2
|
125 roomfilename = "/Users/andrew/Music/Logic/Tractors_April13/tractorIsaak2/Bounces/Take23_mono.wav";
|
andrew@2
|
126 kickfilename = "/Users/andrew/Music/Logic/Tractors_April13/tractorIsaak2/Bounces/Take23_mono.wav";
|
andrew@2
|
127 snarefilename = "/Users/andrew/Music/Logic/Tractors_April13/tractorIsaak2/Bounces/Take23_mono.wav";
|
andrew@2
|
128 sonicVizBeatsFilename = "/Users/andrew/Music/Logic/Tractors_April13/tractorIsaak2/Bounces/IsaakTake23MonoBeats.txt";
|
andrew@2
|
129 break;
|
andrew@2
|
130
|
andrew@0
|
131
|
andrew@0
|
132 }
|
andrew@0
|
133 if (kickfilename != NULL){
|
andrew@2
|
134 printf("Loading Kick file: %s\n", kickfilename);
|
andrew@0
|
135 loadAudioTrack(kickfilename, 0);
|
andrew@0
|
136 }
|
andrew@0
|
137
|
andrew@0
|
138 if (roomfilename != NULL){
|
andrew@2
|
139 printf("loading room: %s\n", roomfilename);
|
andrew@0
|
140 loadAudioTrack(roomfilename, 1);
|
andrew@0
|
141 }
|
andrew@0
|
142
|
andrew@2
|
143 if (snarefilename != NULL){
|
andrew@2
|
144 printf("Loading Snare file: %s\n", kickfilename);
|
andrew@0
|
145 loadAudioTrack(snarefilename, 2);
|
andrew@2
|
146 }
|
andrew@0
|
147
|
andrew@0
|
148 if (sonicVizBeatsFilename.c_str() != NULL){
|
andrew@2
|
149 printf("Reading sonic viz beats: %s\n", sonicVizBeatsFilename.c_str());
|
andrew@0
|
150 readInBeatsFile(sonicVizBeatsFilename);
|
andrew@0
|
151 printBeatTimes();
|
andrew@0
|
152 checkFileErrors(0);
|
andrew@0
|
153 checkFileErrors(2);
|
andrew@0
|
154 findBeatOnsets();
|
andrew@2
|
155
|
andrew@2
|
156 exportErrorInformation();
|
andrew@2
|
157 exportExactOnsetTimes();
|
andrew@2
|
158
|
andrew@2
|
159 exportKickRelativeTempoTimes();
|
andrew@2
|
160 exportKickRelativeErrorTimes();
|
andrew@2
|
161 exportKickRelativeClickTimes();
|
andrew@2
|
162
|
andrew@2
|
163 exportDrumTimerTempoTimes();
|
andrew@2
|
164 exportDrumTimerErrorTimes();
|
andrew@2
|
165 exportDrumTimerClickTimes();
|
andrew@0
|
166 }
|
andrew@0
|
167 }
|
andrew@0
|
168
|
andrew@0
|
169 void RecordedMultipleAudio::loadAudioTrack(std::string name, const int& channel){
|
andrew@0
|
170 //kick - track type 0
|
andrew@0
|
171 //bass - type 1
|
andrew@0
|
172 //snare type 2
|
andrew@0
|
173 //guitar type 3
|
andrew@0
|
174 if (channel >= 0 && channel <= numberOfAudioTracks){
|
andrew@0
|
175 loadedAudioPtr = new LoadedAudioHolder;
|
andrew@0
|
176 //set tracktype before we do analysis
|
andrew@0
|
177 //so we dont do unnecessary chroma and pitch calculations
|
andrew@0
|
178 if (channel == 0 || channel == 2){
|
andrew@0
|
179 loadedAudioPtr->setTrackType(channel);
|
andrew@0
|
180 }
|
andrew@0
|
181 else{
|
andrew@0
|
182 loadedAudioPtr->setTrackType(0);
|
andrew@0
|
183 }
|
andrew@0
|
184 loadedAudioPtr->loadAudioFile(name);
|
andrew@0
|
185
|
andrew@0
|
186 loadedAudioFiles[channel] = *loadedAudioPtr;
|
andrew@0
|
187 loadedAudioFiles[channel].fileLoader.onsetDetect.window.setToRelativeSize(0, trackScreenHeight*channel, 1, trackScreenHeight);
|
andrew@0
|
188 //loadedAudioFiles[channel].setTrackType(channel);
|
andrew@0
|
189 }
|
andrew@0
|
190 }
|
andrew@0
|
191
|
andrew@0
|
192
|
andrew@0
|
193
|
andrew@0
|
194 void RecordedMultipleAudio::readInBeatsFile(std::string& pathName){
|
andrew@0
|
195
|
andrew@0
|
196 // "/Users/andrew/Documents/work/MuseScore/RWC/ANNOTATION/RM-C002_annotation+WavPos.csv"
|
andrew@0
|
197 beatTimes.clear();
|
andrew@0
|
198
|
andrew@0
|
199 printf("- - - - \n\nREAD FILE %s\n", pathName.c_str());
|
andrew@0
|
200 ifstream file ( pathName.c_str());
|
andrew@0
|
201 string value, tmpLine;
|
andrew@0
|
202 stringstream iss;
|
andrew@0
|
203 int count = 0;
|
andrew@0
|
204
|
andrew@0
|
205 while ( file.good() )
|
andrew@0
|
206 {
|
andrew@0
|
207 getline(file, tmpLine);
|
andrew@0
|
208 iss << tmpLine;
|
andrew@0
|
209 int lineCount = 0;
|
andrew@0
|
210 // printf("tmp line %s\n", tmpLine.c_str());
|
andrew@0
|
211 while(getline ( iss, value, '\t' )){ // read a string until next comma: http://www.cplusplus.com/reference/string/getline/
|
andrew@0
|
212 // cout << string( value, 1, value.length()-2 ); // display value removing the first and the last character from it
|
andrew@0
|
213 // printf("line:%s\n", value.c_str());
|
andrew@0
|
214 string::size_type start = value.find_first_not_of(" ,\t\v\n");
|
andrew@0
|
215
|
andrew@0
|
216 string part = value.substr(start, string::npos);
|
andrew@0
|
217
|
andrew@0
|
218 //printf("%s\n", firstpart.c_str());
|
andrew@0
|
219 if (lineCount == 0){
|
andrew@0
|
220 //printf("First part of line found '%s'\n", part.c_str());
|
andrew@0
|
221 double newBeatTime = atof(part.c_str());
|
andrew@0
|
222 beatTimes.push_back(newBeatTime);
|
andrew@0
|
223 }
|
andrew@0
|
224 lineCount++;
|
andrew@0
|
225
|
andrew@0
|
226 }//end while reading line
|
andrew@0
|
227 iss.clear();
|
andrew@0
|
228
|
andrew@0
|
229
|
andrew@0
|
230 }//end while
|
andrew@0
|
231
|
andrew@2
|
232
|
andrew@0
|
233 printf("There are %i BEAT annotations\n", (int)beatTimes.size());
|
andrew@0
|
234
|
andrew@0
|
235 }
|
andrew@0
|
236
|
andrew@0
|
237 void RecordedMultipleAudio::printBeatTimes(){
|
andrew@0
|
238 for (int i = 0;i < beatTimes.size();i++){
|
andrew@0
|
239 printf("Beat[%i] = %f\n", i, beatTimes[i]);
|
andrew@0
|
240 }
|
andrew@0
|
241 }
|
andrew@0
|
242
|
andrew@0
|
243
|
andrew@0
|
244 void RecordedMultipleAudio::checkFileErrors(int channel){
|
andrew@0
|
245 int beatIndex = 0;
|
andrew@2
|
246 int cutoff = 60;//ms width to check
|
andrew@0
|
247 for (int i = 0;i < loadedAudioFiles[channel].onsetTimesMillis.size();i++){
|
andrew@2
|
248
|
andrew@2
|
249 printf("onset time %i ms %i frames ", (int)loadedAudioFiles[channel].onsetTimesMillis[i], (int)loadedAudioFiles[channel].onsetTimesFrames[i]);
|
andrew@2
|
250
|
andrew@0
|
251 while (beatIndex < beatTimes.size() && 1000.0*beatTimes[beatIndex] < loadedAudioFiles[channel].onsetTimesMillis[i] - cutoff) {
|
andrew@0
|
252 beatIndex++;
|
andrew@0
|
253 }
|
andrew@0
|
254 double error = (1000.0*beatTimes[beatIndex] - loadedAudioFiles[channel].onsetTimesMillis[i]);
|
andrew@0
|
255 if (fabs(error) < cutoff){
|
andrew@0
|
256 if (channel == 0)
|
andrew@0
|
257 printf("Pos: %i Beat Time %f Kick Time %f Error %f\n", beatIndex%4, 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
|
andrew@0
|
258 else
|
andrew@0
|
259 printf("Pos: %i Beat Time %f Snare Time %f Error %f\n", beatIndex%4, 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
|
andrew@0
|
260
|
andrew@0
|
261 }else{
|
andrew@0
|
262 if (channel == 0)
|
andrew@2
|
263 printf("Ch.0 Out of Beat: Kick Time %f beat error %f\n", 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
|
andrew@0
|
264 else
|
andrew@2
|
265 printf("Ch.2 Out of Beat: %f Snare Time %f best error %f\n", 1000.0*beatTimes[beatIndex], loadedAudioFiles[0].onsetTimesMillis[i], error);
|
andrew@0
|
266
|
andrew@0
|
267 }
|
andrew@0
|
268 }
|
andrew@0
|
269 }
|
andrew@0
|
270
|
andrew@0
|
271
|
andrew@0
|
272 #pragma mark -labelExactOnsets
|
andrew@0
|
273
|
andrew@0
|
274 void RecordedMultipleAudio::findBeatOnsets(){
|
andrew@0
|
275 //tries to find kicks on 1, 3; snares on 2,4
|
andrew@0
|
276 int beatIndex = 0;
|
andrew@0
|
277 int kickIndex = 0;
|
andrew@0
|
278 int snareIndex = 0;
|
andrew@0
|
279 double kickTime, snareTime;
|
andrew@0
|
280 int cutoff = 50;//ms width to check
|
andrew@0
|
281
|
andrew@0
|
282 onsetInfo.clear();
|
andrew@0
|
283
|
andrew@0
|
284 // kickErrors.clear();
|
andrew@0
|
285 // snareErrors.clear();
|
andrew@0
|
286
|
andrew@0
|
287 bool beatFound;
|
andrew@0
|
288 for (int k = 0;k < beatTimes.size();k++){
|
andrew@0
|
289 beatFound = false;
|
andrew@0
|
290 double newBeatTime = beatTimes[k]*1000.0;
|
andrew@0
|
291 int beatPosition = k % 4;
|
andrew@0
|
292 OnsetInformation information;
|
andrew@2
|
293 double bestBeatDifference = 1000;//i.e. high
|
andrew@0
|
294 switch (beatPosition) {
|
andrew@0
|
295 case 0: case 2://check for kick when it is on the `one' or 'three' (0 or 2 in our metrical position)
|
andrew@0
|
296 // printf("check %i kindex %i\n", beatPosition, kickIndex);
|
andrew@0
|
297 while (kickIndex < loadedAudioFiles[0].onsetTimesMillis.size() && loadedAudioFiles[0].onsetTimesMillis[kickIndex] < newBeatTime - cutoff){
|
andrew@0
|
298 kickIndex++;
|
andrew@0
|
299 kickTime = loadedAudioFiles[0].onsetTimesMillis[kickIndex];
|
andrew@0
|
300 // printf("checking beat[%i] %f kick %f error %f\n", k, beatTimes[k]*1000.0, kickTime, (kickTime - beatTimes[k]*1000.0));
|
andrew@2
|
301 if (fabs(kickTime - beatTimes[k]*1000.0) < cutoff && fabs(kickTime - beatTimes[k]*1000.0) < bestBeatDifference){
|
andrew@0
|
302 beatFound = true;
|
andrew@2
|
303 printf("beat[%i] pos %i time %f kick %f error %f\n", k, beatPosition, beatTimes[k]*1000.0, kickTime, (kickTime - beatTimes[k]*1000.0));
|
andrew@0
|
304
|
andrew@0
|
305 information.error = (kickTime - beatTimes[k]*1000.0);//FOR NOW ONLY
|
andrew@0
|
306 information.metricalPosition = beatPosition;
|
andrew@0
|
307 information.type = 0;
|
andrew@0
|
308 information.exactOnsetTime = kickTime;
|
andrew@2
|
309 bestBeatDifference = fabs(kickTime - beatTimes[k]*1000.0);
|
andrew@0
|
310 // exactBeatPositions.push_back(kickTime);
|
andrew@0
|
311 }
|
andrew@0
|
312 }
|
andrew@0
|
313
|
andrew@0
|
314 break;
|
andrew@0
|
315 case 1: case 3://snare
|
andrew@0
|
316 while (snareIndex < loadedAudioFiles[1].onsetTimesMillis.size() && loadedAudioFiles[1].onsetTimesMillis[snareIndex] < newBeatTime - cutoff ){
|
andrew@0
|
317 snareIndex++;
|
andrew@0
|
318 snareTime = loadedAudioFiles[1].onsetTimesMillis[snareIndex];
|
andrew@2
|
319 if (fabs(snareTime - beatTimes[k]*1000.0) < cutoff && fabs(snareTime - beatTimes[k]*1000.0) < bestBeatDifference){
|
andrew@0
|
320 beatFound = true;
|
andrew@0
|
321 // snareErrors.push_back((beatTimes[k]*1000.0 - snareTime));
|
andrew@0
|
322 information.error = (snareTime - beatTimes[k]*1000.0);
|
andrew@0
|
323 information.metricalPosition = beatPosition;//.push_back(beatPosition);
|
andrew@0
|
324 information.type = 1;
|
andrew@0
|
325 information.exactOnsetTime = snareTime;
|
andrew@2
|
326 bestBeatDifference = fabs(snareTime - beatTimes[k]*1000.0);
|
andrew@2
|
327 printf("beat[%i] pos %i tim e%f snare %f error %f\n", k, beatPosition, beatTimes[k]*1000.0, snareTime, (snareTime - beatTimes[k]*1000.0));
|
andrew@0
|
328 // exactBeatPositions.push_back(snareTime);
|
andrew@0
|
329 }
|
andrew@0
|
330 }
|
andrew@0
|
331
|
andrew@0
|
332 break;
|
andrew@0
|
333 }
|
andrew@0
|
334 if (!beatFound){
|
andrew@0
|
335 information.type = -1;//not a kick or snare
|
andrew@0
|
336 information.exactOnsetTime = beatTimes[k]*1000.0;
|
andrew@0
|
337 information.metricalPosition = beatPosition;//.push_
|
andrew@0
|
338 // exactBeatPositions.push_back(beatTimes[k]*1000.0);//have to go with the annotated beat instead (no matching kick or snare)
|
andrew@0
|
339
|
andrew@0
|
340 printf("beat[%i] %f NOT FOUND, kicktime %f snaretime %f\n", k, beatTimes[k]*1000.0, kickTime, snareTime );
|
andrew@0
|
341 }
|
andrew@0
|
342
|
andrew@0
|
343 onsetInfo.push_back(information);
|
andrew@0
|
344
|
andrew@0
|
345 }//end for all beat annotations
|
andrew@0
|
346
|
andrew@0
|
347 correctExactBeatTiming();//get rid of the first onset time
|
andrew@0
|
348
|
andrew@0
|
349 calculateTimingAnalysis();
|
andrew@0
|
350
|
andrew@2
|
351
|
andrew@2
|
352 //so our exact beat times are then information.exactOnsetTime
|
andrew@2
|
353
|
andrew@0
|
354 }
|
andrew@0
|
355
|
andrew@0
|
356 #pragma mark -doTimingAnalysis
|
andrew@0
|
357 void RecordedMultipleAudio::correctExactBeatTiming(){
|
andrew@0
|
358 //get rid of firtst onset time
|
andrew@0
|
359 if (onsetInfo.size() > 0){
|
andrew@0
|
360 timingOffset = onsetInfo[0].exactOnsetTime;//s[0];
|
andrew@0
|
361 double tmpPosn;
|
andrew@0
|
362 for (int i = 0;i < onsetInfo.size();i++){
|
andrew@0
|
363 onsetInfo[i].beatTimeToProcess = onsetInfo[i].exactOnsetTime - timingOffset;
|
andrew@0
|
364 printf("exact [%i] type %i %f, corrected %f\n", i, onsetInfo[i].type, onsetInfo[i].exactOnsetTime, onsetInfo[i].beatTimeToProcess );
|
andrew@0
|
365 }
|
andrew@0
|
366 }
|
andrew@0
|
367 }
|
andrew@0
|
368
|
andrew@0
|
369 void RecordedMultipleAudio::calculateTimingAnalysis(){
|
andrew@0
|
370
|
andrew@0
|
371 for (int i = 0;i < onsetInfo.size();i++){
|
andrew@0
|
372 drumTimingAnalyser.updateCostToPoint(onsetInfo[i].beatTimeToProcess, i);
|
andrew@0
|
373 //updatecounter is the beat position for this note event - can be used to do other kinds of durations than just simple beats
|
andrew@0
|
374
|
andrew@0
|
375 drumTimingAnalyser.beatPosition.push_back(onsetInfo[i].exactOnsetTime);
|
andrew@0
|
376 }
|
andrew@0
|
377 drumTimingAnalyser.processPathHistory();
|
andrew@0
|
378 drumTimingAnalyser.calculateTempoLimits();
|
andrew@0
|
379
|
andrew@0
|
380 getErrorTimesFromAnalysis();
|
andrew@0
|
381
|
andrew@0
|
382 alternativeKickRelativeAnalysis();
|
andrew@0
|
383
|
andrew@0
|
384 displayKickRelativeMedianErrors();//NB messes ther order of these
|
andrew@0
|
385
|
andrew@0
|
386 //this was how we did it in multimatch program
|
andrew@0
|
387 // timer.processPathHistory();
|
andrew@0
|
388 // timer.calculateTempoLimits();
|
andrew@0
|
389 // timer.exportTimingData();
|
andrew@0
|
390 // timer.exportProcessedBeatTimes(firstNoteTime);
|
andrew@0
|
391 }
|
andrew@0
|
392
|
andrew@0
|
393 void RecordedMultipleAudio::getErrorTimesFromAnalysis(){
|
andrew@0
|
394 printf("\nDrumTimingLoader: get error times from analysis!!!\n");
|
andrew@0
|
395 setUpErrorsByMetricalPosition();
|
andrew@0
|
396 //gets the errors from the drum timing analyser (i.e. ISMIR paper code) and attributes these to the onsets
|
andrew@0
|
397 for (int i = 0;i < drumTimingAnalyser.timingData.size();i++){
|
andrew@0
|
398 onsetInfo[i].error = drumTimingAnalyser.timingData[i][5];
|
andrew@0
|
399 onsetInfo[i].clickTime = drumTimingAnalyser.timingData[i][1] + timingOffset;//this is where teh timing analyser placed the click times
|
andrew@0
|
400 errorsByMetricalPosition[onsetInfo[i].metricalPosition].push_back(onsetInfo[i].error);
|
andrew@2
|
401 printf("beat %i metrical posn %i exact onset time %f click time %i error %i\n", i, onsetInfo[i].metricalPosition, onsetInfo[i].exactOnsetTime,
|
andrew@2
|
402 onsetInfo[i].clickTime, (int)onsetInfo[i].error);
|
andrew@0
|
403 }
|
andrew@0
|
404 displayMedianErrors();
|
andrew@0
|
405 }
|
andrew@0
|
406
|
andrew@0
|
407
|
andrew@0
|
408 void RecordedMultipleAudio::alternativeKickRelativeAnalysis(){
|
andrew@0
|
409 printf("\n\nAnalysis Relative to the Kick Drum on the ONE\n");
|
andrew@0
|
410 //this sees kicks as ON the beat, looks at relative error of the three other beats if we chop the bar evenly
|
andrew@0
|
411
|
andrew@0
|
412 double recentBeatTime, currentTempo;
|
andrew@0
|
413 kickRelativeErrors.clear();
|
andrew@0
|
414 kickRelativeClickTimes.clear();
|
andrew@2
|
415 kickRelativeTempo.clear();
|
andrew@0
|
416
|
andrew@0
|
417 for (int i = 0;i < drumTimingAnalyser.timingData.size();i++){
|
andrew@0
|
418 int beatPosition = i%4;
|
andrew@2
|
419 if (beatPosition == 0){//then divide kicks and get even division
|
andrew@0
|
420 recentBeatTime = onsetInfo[i].exactOnsetTime;
|
andrew@0
|
421 if (i+4 < onsetInfo.size())
|
andrew@0
|
422 currentTempo = (onsetInfo[i+4].exactOnsetTime - onsetInfo[i].exactOnsetTime)/4.0;
|
andrew@0
|
423 printf("new beat time %f tempo %f\n", recentBeatTime, currentTempo);
|
andrew@0
|
424 }
|
andrew@0
|
425 double error = onsetInfo[i].exactOnsetTime - (recentBeatTime + beatPosition*currentTempo);
|
andrew@2
|
426 printf("Beat %i KR Predicted Beat: %f Onset: %f KRerror %f DTerror %i\n", beatPosition, (recentBeatTime + beatPosition*currentTempo), onsetInfo[i].exactOnsetTime, error, (int)onsetInfo[i].error);
|
andrew@0
|
427 kickRelativeErrors.push_back(error);
|
andrew@0
|
428 kickRelativeClickTimes.push_back(recentBeatTime + beatPosition*currentTempo);
|
andrew@2
|
429 kickRelativeTempo.push_back(currentTempo);
|
andrew@0
|
430 }
|
andrew@0
|
431 }
|
andrew@0
|
432
|
andrew@2
|
433 #pragma mark -exportInfo
|
andrew@0
|
434 void RecordedMultipleAudio::exportErrorInformation(){
|
andrew@0
|
435 printf("Export final timing information\n");
|
andrew@0
|
436
|
andrew@0
|
437 ofstream ofs(infoFilepath.c_str());
|
andrew@0
|
438 for (int i = 0;i < onsetInfo.size() && kickRelativeErrors.size();i++){// drumTimingAnalyser.timingData.size()
|
andrew@0
|
439 ofs << i << "," << (i%4) << "," << onsetInfo[i].exactOnsetTime << ",";
|
andrew@0
|
440 ofs << onsetInfo[i].clickTime << "," << onsetInfo[i].error << ",";//the error for the ISMIR timing analyser
|
andrew@0
|
441 ofs << kickRelativeClickTimes[i] << "," << kickRelativeErrors[i];//the click and error for beats evenly between kicks
|
andrew@0
|
442 ofs << endl;
|
andrew@0
|
443 }
|
andrew@0
|
444
|
andrew@0
|
445 }
|
andrew@0
|
446
|
andrew@2
|
447
|
andrew@2
|
448 void RecordedMultipleAudio::exportExactOnsetTimes(){
|
andrew@2
|
449 printf("Export exact beat times\n");
|
andrew@2
|
450
|
andrew@2
|
451 ofstream ofs(exactOnsetFilePath.c_str());
|
andrew@2
|
452 for (int i = 0;i < onsetInfo.size();i++){// drumTimingAnalyser.timingData.size()
|
andrew@2
|
453 ofs << onsetInfo[i].exactOnsetTime/1000.0 << endl;
|
andrew@2
|
454 if (printExportInfo)
|
andrew@2
|
455 printf("exporting exact beat times %f\n", onsetInfo[i].exactOnsetTime);
|
andrew@2
|
456 }
|
andrew@2
|
457 }
|
andrew@2
|
458
|
andrew@2
|
459 void RecordedMultipleAudio::exportKickRelativeTempoTimes(){
|
andrew@2
|
460 printf("Export kick relative Tempo times\n");
|
andrew@2
|
461
|
andrew@2
|
462 ofstream ofs(kickRelativeTempoFilePath.c_str());
|
andrew@2
|
463 for (int i = 0;i < kickRelativeTempo.size() && i < onsetInfo.size();i++){
|
andrew@2
|
464 ofs << onsetInfo[i].exactOnsetTime << "\t" << kickRelativeTempo[i] << endl;
|
andrew@2
|
465 if (printExportInfo)
|
andrew@2
|
466 printf("exporting tempo[%i]: onset %f %f\n", i, onsetInfo[i].exactOnsetTime, kickRelativeTempo[i]);
|
andrew@2
|
467 }
|
andrew@2
|
468 }
|
andrew@2
|
469
|
andrew@2
|
470 void RecordedMultipleAudio::exportKickRelativeErrorTimes(){
|
andrew@2
|
471 printf("Export kick relative Error times\n");
|
andrew@2
|
472
|
andrew@2
|
473 ofstream ofs(kickRelativeErrorsFilePath.c_str());
|
andrew@2
|
474 for (int i = 0;i < kickRelativeErrors.size() && i < onsetInfo.size();i++){
|
andrew@2
|
475 ofs << onsetInfo[i].exactOnsetTime << "\t" << kickRelativeErrors[i] << endl;
|
andrew@2
|
476 if (printExportInfo)
|
andrew@2
|
477 printf("exporting tempo[%i]: onset %f %f\n", i, onsetInfo[i].exactOnsetTime, kickRelativeErrors[i]);
|
andrew@2
|
478 }
|
andrew@2
|
479 }
|
andrew@2
|
480
|
andrew@2
|
481 void RecordedMultipleAudio::exportKickRelativeClickTimes(){
|
andrew@2
|
482 printf("Export kick relative click times\n");
|
andrew@2
|
483
|
andrew@2
|
484 ofstream ofs(kickRelativeClickFilePath.c_str());
|
andrew@2
|
485 for (int i = 0;i < kickRelativeClickTimes.size();i++){
|
andrew@2
|
486 ofs << kickRelativeClickTimes[i]/1000.0 << endl;
|
andrew@2
|
487 if (printExportInfo)
|
andrew@2
|
488 printf("exporting KD click [%i]:%f\n", i,kickRelativeClickTimes[i]);
|
andrew@2
|
489 }
|
andrew@2
|
490 }
|
andrew@2
|
491
|
andrew@2
|
492
|
andrew@2
|
493 void RecordedMultipleAudio::exportDrumTimerTempoTimes(){
|
andrew@2
|
494 printf("Export drum timing tempo times\n");
|
andrew@2
|
495
|
andrew@2
|
496 ofstream ofs(drumTimerTempoFilePath.c_str());
|
andrew@2
|
497 for (int i = 0;i < drumTimingAnalyser.timingData.size() && i < onsetInfo.size();i++){
|
andrew@2
|
498 ofs << onsetInfo[i].exactOnsetTime << "\t" << drumTimingAnalyser.timingData[i][0] << endl;
|
andrew@2
|
499 if (printExportInfo)
|
andrew@2
|
500 printf("exporting tempo[%i]: onset %f tempo %i\n", i, onsetInfo[i].exactOnsetTime, drumTimingAnalyser.timingData[i][0]);
|
andrew@2
|
501 }
|
andrew@2
|
502 }
|
andrew@2
|
503
|
andrew@2
|
504
|
andrew@2
|
505 void RecordedMultipleAudio::exportDrumTimerErrorTimes(){
|
andrew@2
|
506 printf("Export drum timing Error times\n");
|
andrew@2
|
507
|
andrew@2
|
508 ofstream ofs(drumTimerErrorsFilePath.c_str());
|
andrew@2
|
509 for (int i = 0;i < drumTimingAnalyser.timingData.size() && i < onsetInfo.size();i++){
|
andrew@2
|
510 ofs << onsetInfo[i].exactOnsetTime << "\t" << drumTimingAnalyser.timingData[i][5] << endl;
|
andrew@2
|
511 if (printExportInfo)
|
andrew@2
|
512 printf("exporting [%i]: onset %f error %i\n", i, onsetInfo[i].exactOnsetTime, drumTimingAnalyser.timingData[i][5]);
|
andrew@2
|
513 }
|
andrew@2
|
514 }
|
andrew@2
|
515
|
andrew@2
|
516
|
andrew@2
|
517 void RecordedMultipleAudio::exportDrumTimerClickTimes(){
|
andrew@2
|
518 printf("Export drum timing Click times\n");
|
andrew@2
|
519
|
andrew@2
|
520 ofstream ofs(drumTimerClickFilePath.c_str());
|
andrew@2
|
521 for (int i = 0;i < onsetInfo.size();i++){
|
andrew@2
|
522 ofs << onsetInfo[i].clickTime/1000.0 << endl;
|
andrew@2
|
523 if (printExportInfo)
|
andrew@2
|
524 printf("exporting click[%i] %i\n", i, onsetInfo[i].clickTime);
|
andrew@2
|
525 }
|
andrew@2
|
526 }
|
andrew@2
|
527
|
andrew@2
|
528
|
andrew@2
|
529
|
andrew@2
|
530
|
andrew@2
|
531
|
andrew@2
|
532
|
andrew@2
|
533
|
andrew@0
|
534 void RecordedMultipleAudio::printKickRelativeErrors(){
|
andrew@0
|
535 for (int i = 0;i < kickRelativeErrors.size();i++){
|
andrew@0
|
536 printf("KR error [%i] : %.1f\n", i, kickRelativeErrors[i]);
|
andrew@0
|
537 }
|
andrew@0
|
538 }
|
andrew@0
|
539
|
andrew@0
|
540 void RecordedMultipleAudio::setUpErrorsByMetricalPosition(){
|
andrew@0
|
541 //clear this matrix
|
andrew@0
|
542 errorsByMetricalPosition.clear();
|
andrew@0
|
543 for (int i = 0;i < 4;i++){
|
andrew@0
|
544 DoubleVector v;
|
andrew@0
|
545 errorsByMetricalPosition.push_back(v);
|
andrew@0
|
546 }
|
andrew@0
|
547
|
andrew@0
|
548 }
|
andrew@0
|
549
|
andrew@0
|
550 void RecordedMultipleAudio::displayMedianErrors(){
|
andrew@0
|
551 printf("Medians of the Decoded Tempo variations\n");
|
andrew@0
|
552 for (int i = 0;i < 4;i++){
|
andrew@0
|
553 //printErrorsForMetricalPosition(i);
|
andrew@0
|
554 std::sort(errorsByMetricalPosition[i].begin(), errorsByMetricalPosition[i].end());//sort vector
|
andrew@0
|
555 double median = errorsByMetricalPosition[i][(int)(errorsByMetricalPosition[i].size()/2)];
|
andrew@0
|
556 printf("median for metrical position %i is %f\n", i, median);
|
andrew@0
|
557 }
|
andrew@0
|
558 }
|
andrew@0
|
559
|
andrew@0
|
560
|
andrew@0
|
561 void RecordedMultipleAudio::displayKickRelativeMedianErrors(){
|
andrew@0
|
562 printf("Medians of the KR variations\n");
|
andrew@0
|
563
|
andrew@0
|
564 DoubleVector tmpKRErrors;
|
andrew@0
|
565
|
andrew@0
|
566
|
andrew@0
|
567 for (int i = 0;i < 4;i++){
|
andrew@0
|
568
|
andrew@0
|
569 tmpKRErrors.clear();
|
andrew@0
|
570 int index = 0;
|
andrew@0
|
571
|
andrew@0
|
572 while (index+i < kickRelativeErrors.size()) {
|
andrew@0
|
573 tmpKRErrors.push_back(kickRelativeErrors[index + i]);
|
andrew@0
|
574 index += 4;
|
andrew@0
|
575 }
|
andrew@0
|
576
|
andrew@0
|
577 // for (int k = 0;k < tmpKRErrors.size();k++)
|
andrew@0
|
578 // printf("kr %i [%i] = %f\n", i, k, tmpKRErrors[k]);
|
andrew@0
|
579
|
andrew@0
|
580 //printErrorsForMetricalPosition(i);
|
andrew@0
|
581 std::sort(tmpKRErrors.begin(), tmpKRErrors.end());//sort vector
|
andrew@0
|
582
|
andrew@0
|
583 // for (int k = 0;k < tmpKRErrors.size();k++)
|
andrew@0
|
584 // printf("sorted kr %i [%i] = %f\n", i, k, tmpKRErrors[k]);
|
andrew@0
|
585
|
andrew@0
|
586
|
andrew@0
|
587 double median = tmpKRErrors[(int)(tmpKRErrors.size()/2)];
|
andrew@0
|
588 printf("median for metrical position %i is %f\n", i, median);
|
andrew@0
|
589 }
|
andrew@0
|
590 }
|
andrew@0
|
591
|
andrew@0
|
592 void RecordedMultipleAudio::printErrorsForMetricalPosition(const int& i){
|
andrew@0
|
593 for (int k = 0;k < errorsByMetricalPosition[i].size();k++){
|
andrew@0
|
594 printf("metrical posn %i, [%i] = %f\n", i, k, errorsByMetricalPosition[i][k]);
|
andrew@0
|
595 }
|
andrew@0
|
596 }
|
andrew@0
|
597
|
andrew@0
|
598 #pragma mark -drawTracks
|
andrew@0
|
599
|
andrew@0
|
600 void RecordedMultipleAudio::drawTracks(){
|
andrew@0
|
601 if (drawWindow == 0){
|
andrew@0
|
602 for (int i = 0;i < numberOfAudioTracks;i++){
|
andrew@0
|
603 loadedAudioFiles[i].draw();
|
andrew@0
|
604 }
|
andrew@0
|
605 } else {
|
andrew@0
|
606 drumTimingAnalyser.drawTempoCurve();
|
andrew@0
|
607 }
|
andrew@2
|
608
|
andrew@2
|
609 // ofDrawBitmapString(ofToString(playPositionSeconds),20, 40);
|
andrew@0
|
610 }
|
andrew@0
|
611
|
andrew@0
|
612 #pragma mark -update
|
andrew@0
|
613 void RecordedMultipleAudio::updatePosition(){
|
andrew@0
|
614 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
615 loadedAudioFiles[i].updateToPlayPosition();
|
andrew@2
|
616
|
andrew@2
|
617 playPositionSeconds = samplesToSeconds(loadedAudioFiles[0].fileLoader.audioHolder.playPosition);
|
andrew@2
|
618 drumTimingAnalyser.updatePlayIndex(playPositionSeconds);
|
andrew@2
|
619
|
andrew@2
|
620
|
andrew@2
|
621 }
|
andrew@2
|
622
|
andrew@2
|
623 double RecordedMultipleAudio::samplesToSeconds(const double& samples){
|
andrew@2
|
624 return samples/44100.0;
|
andrew@0
|
625 }
|
andrew@0
|
626
|
andrew@0
|
627 void RecordedMultipleAudio::updatePositionToMillis(const double& millis){
|
andrew@0
|
628 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
629 loadedAudioFiles[i].updateToMillisPosition(millis);
|
andrew@0
|
630 }
|
andrew@0
|
631
|
andrew@0
|
632 void RecordedMultipleAudio::updatePlaybackPositionToMillis(const double& millis){
|
andrew@0
|
633 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
634 loadedAudioFiles[i].updatePlaybackPositionToMillis(millis);
|
andrew@0
|
635 }
|
andrew@0
|
636
|
andrew@0
|
637 void RecordedMultipleAudio::togglePlay(){
|
andrew@0
|
638 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
639 loadedAudioFiles[i].togglePlay();
|
andrew@0
|
640 }
|
andrew@0
|
641
|
andrew@0
|
642 void RecordedMultipleAudio::stop(){
|
andrew@0
|
643 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
644 loadedAudioFiles[i].stop();
|
andrew@0
|
645 }
|
andrew@0
|
646
|
andrew@0
|
647
|
andrew@0
|
648 void RecordedMultipleAudio::printInfo(){
|
andrew@0
|
649 loadedAudioFiles[0].fileLoader.onsetDetect.printChromaInfo();
|
andrew@0
|
650 loadedAudioFiles[0].printEvents();
|
andrew@0
|
651 }
|
andrew@0
|
652
|
andrew@2
|
653 #pragma mark -ScreenFunctions
|
andrew@0
|
654 void RecordedMultipleAudio::windowResized(const int& w, const int& h){
|
andrew@0
|
655 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
656 loadedAudioFiles[i].windowResized(w, h);
|
andrew@0
|
657 }
|
andrew@0
|
658
|
andrew@0
|
659 void RecordedMultipleAudio::zoomIn(){
|
andrew@0
|
660 if (drawWindow == 0){
|
andrew@0
|
661 printf("zoom in\n");
|
andrew@0
|
662 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
663 loadedAudioFiles[i].fileLoader.zoomIn();
|
andrew@0
|
664 }
|
andrew@0
|
665
|
andrew@0
|
666 if (drawWindow == 1)
|
andrew@0
|
667 drumTimingAnalyser.zoomIn();//numberOfPointsPerPage /= 2;
|
andrew@0
|
668 }
|
andrew@0
|
669
|
andrew@0
|
670 void RecordedMultipleAudio::zoomOut(){
|
andrew@0
|
671 printf("zoom out\n");
|
andrew@0
|
672 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@0
|
673 loadedAudioFiles[i].fileLoader.zoomOut();
|
andrew@0
|
674
|
andrew@0
|
675 if (drawWindow == 1)
|
andrew@0
|
676 drumTimingAnalyser.zoomOut();//numberOfPointsPerPage *= 2;
|
andrew@0
|
677
|
andrew@0
|
678 }
|
andrew@0
|
679
|
andrew@2
|
680 void RecordedMultipleAudio::switchScreens(){
|
andrew@2
|
681 for (int i = 0;i < numberOfAudioTracks;i++)
|
andrew@2
|
682 loadedAudioFiles[i].switchScreens();
|
andrew@2
|
683 }
|
andrew@0
|
684
|