andrew@2: /* andrew@2: * PreciseOnsetDetectorOffline.cpp andrew@2: * ofxPreciseOnsetDetectionOffline andrew@2: * andrew@2: * Created by Andrew Robertson on 25/12/2013. andrew@2: * Copyright 2013 QMUL. All rights reserved. andrew@2: * andrew@2: */ andrew@2: andrew@2: andrew@2: //add a retrigger threshold andrew@2: andrew@2: #include "PreciseOnsetDetectorOffline.h" andrew@2: Venetian@7: const bool printingOn = false;//true Venetian@7: Venetian@7: //can work just by calling initialise, then recursive call to processAudioFrame(float* frame, in numSamples) Venetian@7: //set the dfType - initialises to energy - good for bass and drums, not so good for harmonic instruments andrew@6: andrew@2: PreciseOnsetDetectorOffline::PreciseOnsetDetectorOffline(){ andrew@2: frameSize = 1024; andrew@2: hopSize = 512; andrew@2: preciseLocator.setup(frameSize); andrew@2: writeOutput = true;//write output to txt when loading file - stored at sample location as file Venetian@7: Venetian@7: dfType = 5;//see onset detection class Venetian@7: //0 energy Venetian@7: //5 is csd Venetian@7: Venetian@7: ringbuffer = new AudioRingBuffer(frameSize, hopSize); Venetian@7: sampleCount = 0; Venetian@7: frameCount = 0; Venetian@7: Venetian@7: initialised = false; Venetian@7: Venetian@7: //some params for peak processing Venetian@7: peakProcess.cutoffForRepeatOnsetsFrames = 8; Venetian@7: peakProcess.detectionTriggerRatio = 0.5f;//was 0.5 Venetian@7: peakProcess.bestSlopeMedian = 3; Venetian@7: peakProcess.thresholdRelativeToMedian = 1.81; Venetian@7: Venetian@7: isBass = false; andrew@2: } andrew@2: andrew@2: PreciseOnsetDetectorOffline::~PreciseOnsetDetectorOffline(){ Venetian@7: Venetian@7: if (initialised) Venetian@7: delete detectionFunction; Venetian@7: Venetian@7: delete ringbuffer; Venetian@7: andrew@2: } andrew@2: Venetian@7: void PreciseOnsetDetectorOffline::initialise(){ Venetian@7: clearAll(); Venetian@7: sampleCount = 0; Venetian@7: frameCount = 0; Venetian@7: // initialises with hopsize = 512, framesize = 1024, complex spectral difference DF and hanning window Venetian@7: detectionFunction = new OnsetDetectionFunction(hopSize, frameSize, dfType, 1); Venetian@7: Venetian@7: setDfType(5);//for csd Venetian@7: initialised = true; Venetian@7: Venetian@7: peakProcess.reset(); Venetian@7: } Venetian@7: Venetian@7: void PreciseOnsetDetectorOffline::endProcessing(){ Venetian@7: delete detectionFunction; Venetian@7: detectionFunction = NULL; Venetian@7: initialised = false; Venetian@7: Venetian@7: printf("frames in seconds %f\n", secondsToFrameIndex(samples/44100.)); Venetian@7: printf("frames %i\n", (int) dfValues.size()); Venetian@7: Venetian@7: } Venetian@7: Venetian@7: #pragma mark AudioProcessing Venetian@7: void PreciseOnsetDetectorOffline::processAudioFrame(float* frame, int n){ Venetian@7: //call initialise before starting here Venetian@7: assert(initialised); Venetian@7: Venetian@7: sampleCount += n; Venetian@7: Venetian@7: if (ringbuffer->addToBuffer(frame, n)){ Venetian@7: //for (int i = 0; i < ringbuffer->buffersize; i++) Venetian@7: // printf("ring[%i] %.6f\n", i, ringbuffer->audiobuffer[i]); Venetian@7: Venetian@7: Venetian@7: //needed to make sure df fn is same framesize as our ringbuffer Venetian@7: double dfval = detectionFunction->getDFsample(ringbuffer->audiobuffer); // compute detection function sample Venetian@7: Venetian@7: dfValues.push_back(dfval); Venetian@7: Venetian@7: if (printingOn) Venetian@7: printf("det val %i: %f\n", frameCount, dfval); Venetian@7: Venetian@7: if (peakProcess.peakProcessing(dfval)){ Venetian@7: Venetian@7: //onsetPositionFrames.push_back(frameCount); Venetian@7: Venetian@7: int precisesample = preciseLocator.findExactOnset(ringbuffer->audiobuffer); Venetian@7: Venetian@7: //printf("precise sample is %i\n", precisesample); Venetian@7: //so exact sample is Venetian@7: //int exactsample = (frameCount-1)*hopSize;//chunks in from beginning of frame Venetian@7: //as we have just added hopsize samples in, the beginning of the frame is -1 from counter Venetian@7: //exactsample += precisesample; Venetian@7: Venetian@7: //above was old way, but not as neat Venetian@7: Venetian@7: //number of samples in, but to beginning of frame, then precisesample in from there Venetian@7: int alternativePreciseSample = sampleCount - frameSize + precisesample; Venetian@7: Venetian@7: //printf("PreciseSample %i, %i == %i\n", precisesample, exactsample, alternativePrecise); Venetian@7: Venetian@7: OnsetInfo newOnsetInfo; Venetian@7: newOnsetInfo.onsetLocation = alternativePreciseSample/44100.; Venetian@7: newOnsetInfo.positionSamples = alternativePreciseSample; Venetian@7: newOnsetInfo.positionFrames = frameCount; Venetian@7: newOnsetInfo.pitch = 0; Venetian@7: newOnsetInfo.matchIndicator = 0; Venetian@7: newOnsetInfo.onBeat = false; Venetian@7: newOnsetInfo.expressiveTiming = 0; Venetian@7: // onsetLocations.push_back(alternativePreciseSample/44100.); Venetian@7: // onsetPositionSamples.push_back(alternativePreciseSample); Venetian@7: onsetList.push_back(newOnsetInfo); Venetian@7: Venetian@7: Venetian@7: if (printingOn) Venetian@7: printf("BANG\n"); Venetian@7: } Venetian@7: Venetian@7: frameCount++; Venetian@7: Venetian@7: } Venetian@7: Venetian@7: } Venetian@7: Venetian@7: Venetian@7: void PreciseOnsetDetectorOffline::setDfType(int t){ Venetian@7: if (t >= 0 && t <= 9) Venetian@7: dfType = t; Venetian@7: Venetian@7: /* Venetian@7: switch (df_type){ Venetian@7: case 0: Venetian@7: df_sample = energy_envelope(); // calculate energy envelope detection function sample Venetian@7: break; Venetian@7: case 1: Venetian@7: df_sample = energy_difference(); // calculate half-wave rectified energy difference detection function sample Venetian@7: break; Venetian@7: case 2: Venetian@7: df_sample = spectral_difference(); // calculate spectral difference detection function sample Venetian@7: break; Venetian@7: case 3: Venetian@7: df_sample = spectral_difference_hwr(); // calculate spectral difference detection function sample (half wave rectified) Venetian@7: break; Venetian@7: case 4: Venetian@7: df_sample = phase_deviation(); // calculate phase deviation detection function sample (half wave rectified) Venetian@7: break; Venetian@7: case 5: Venetian@7: df_sample = complex_spectral_difference(); // calcualte complex spectral difference detection function sample Venetian@7: break; Venetian@7: case 6: Venetian@7: df_sample = complex_spectral_difference_hwr(); // calcualte complex spectral difference detection function sample (half-wave rectified) Venetian@7: break; Venetian@7: case 7: Venetian@7: df_sample = high_frequency_content(); // calculate high frequency content detection function sample Venetian@7: break; Venetian@7: case 8: Venetian@7: df_sample = high_frequency_spectral_difference(); // calculate high frequency spectral difference detection function sample Venetian@7: break; Venetian@7: case 9: Venetian@7: df_sample = high_frequency_spectral_difference_hwr(); // calculate high frequency spectral difference detection function (half-wave rectified) Venetian@7: break; Venetian@7: */ Venetian@7: } andrew@2: andrew@4: int PreciseOnsetDetectorOffline::load(std::string filename){ andrew@2: andrew@2: //name for output file is same dir and filename as aif/wav but with _preciseOnsets.txt added andrew@2: //eg 'song.wav' is 'song.wav_preciseOnsets.txt' andrew@2: Venetian@7: //printf("PODO: load '%s'\n", filename.c_str()); Venetian@7: return processAudioFileForBeatTimes(filename); andrew@2: } andrew@2: andrew@2: Venetian@7: void PreciseOnsetDetectorOffline::clearAll(){ Venetian@7: Venetian@7: dfValues.clear(); Venetian@7: onsetList.clear(); Venetian@7: // onsetLocations.clear(); Venetian@7: // onsetPositionFrames.clear(); Venetian@7: // onsetPositionSamples.clear(); Venetian@7: } andrew@2: Venetian@7: Venetian@7: int PreciseOnsetDetectorOffline::processAudioFileForBeatTimes(std::string audiofile){ andrew@2: //originally from BeatAnnotationViewer project Venetian@7: printf("PODO: ProcessFunction %s\n", audiofile.c_str()); Venetian@7: Venetian@7: initialise();// - needed but elsewhere Venetian@7: andrew@3: andrew@2: andrew@2: // static double frame[FRAMESIZE]; // to hold a single frame andrew@2: double buffer[frameSize]; andrew@2: double dfval; andrew@2: andrew@2: for (int i = 0;i < frameSize;i++) andrew@2: { andrew@2: buffer[i] = 0; andrew@2: } andrew@2: andrew@2: //detfun = new df(1,(FRAMESIZE*2),0); andrew@2: Venetian@7: andrew@2: andrew@2: SNDFILE *infile, *outfile ; // define input and output sound files andrew@2: andrew@2: SF_INFO sfinfo ; // struct to hold info about sound file andrew@2: int readcount ; // counts number of samples read from sound file andrew@2: const char *infilename = audiofile.c_str(); andrew@2: //"/Users/andrew/Music/Station To Station 2/3-03 Panic In Detroit (Live Nassau Coliseum '76).wav";//"ledzep.wav" ; // input file name andrew@2: // const char *outfilename = "output.wav" ; // output file name andrew@2: andrew@2: andrew@2: // Open Input File andrew@2: if (! (infile = sf_open (infilename, SFM_READ, &sfinfo))) andrew@2: { // Open failed andrew@2: printf ("Not able to open input file %s.\n", infilename) ; andrew@2: // Print the error message from libsndfile. andrew@2: puts (sf_strerror (NULL)) ; andrew@2: return 1; andrew@2: } ; andrew@2: andrew@2: printf("opened '%s'\n", audiofile.c_str()); andrew@2: loadedFilename = audiofile; andrew@2: //STEREO OKAY andrew@2: andrew@2: //HERE IS THE CLASSIC LOADING FILE CODE andrew@2: //DEALS WITH MORE THAN MONO andrew@2: int channels = sfinfo.channels; andrew@2: samples = sfinfo.frames; andrew@2: printf("Number of channels %i, samples %i\n", channels, samples); andrew@2: andrew@2: andrew@2: int blocksize = hopSize;//FRAMESIZE; andrew@2: float buf [channels * blocksize] ; andrew@2: float frame[blocksize]; andrew@2: andrew@2: int k, m; andrew@2: readcount = 1; Venetian@7: andrew@2: andrew@2: //DoubleVector d; andrew@2: while ((readcount = sf_readf_float (infile, buf, blocksize)) > 0){ andrew@2: for (k = 0 ; k < readcount ; k++){ andrew@2: //d.clear(); andrew@2: frame[k] = 0; andrew@2: andrew@2: for (m = 0 ; m < channels ; m++){ andrew@2: frame[k] += buf[k*channels + 0];//sum the channels together andrew@2: //d.push_back(buf [k * channels + m]); andrew@2: } andrew@2: andrew@2: frame[k] /= channels;//average of the channels andrew@2: } andrew@2: Venetian@7: processAudioFrame(frame, blocksize); Venetian@7: andrew@2: //add to our buffer for pocessing andrew@2: for (int i = 0; i< frameSize-hopSize;i++) andrew@2: { andrew@2: buffer[i] = buffer[i+hopSize]; andrew@2: buffer[i+hopSize] = frame[i]; andrew@2: } Venetian@7: /* Venetian@7: for (int i = 0; i < frameSize; i++) Venetian@7: printf("buffer[%i] %.6f\n", i, buffer[i]); andrew@2: andrew@2: dfval = detectionFunction->getDFsample(buffer); // compute detection function sample andrew@2: andrew@2: dfValues.push_back(dfval); andrew@2: Venetian@7: // if (printingOn) Venetian@7: // printf("det val %i: %f\n", counter, dfval); andrew@2: andrew@2: if (peakProcess.peakProcessing(dfval)){ andrew@6: andrew@2: onsetPositionFrames.push_back(counter); andrew@2: int precisesample = preciseLocator.findExactOnset(&buffer[0]); Venetian@7: printf("precise sample is %i\n", precisesample); andrew@2: //so exact sample is Venetian@7: int exactsample = (counter-1)*hopSize;//chunks in from beginning of frame andrew@2: //as we have just added hopsize samples in, the beginning of the frame is -1 from counter andrew@2: exactsample += precisesample; andrew@2: //printf("PreciseSample %i, %i\n", precisesample, exactsample); andrew@3: andrew@2: andrew@2: onsetLocations.push_back(exactsample/44100.); Venetian@7: onsetPositionSamples.push_back(exactsample); Venetian@7: Venetian@7: andrew@6: andrew@6: if (printingOn) andrew@6: printf("BANG\n"); andrew@2: } andrew@2: andrew@2: counter++; Venetian@7: */ andrew@4: //printf("read %i samples\n", readcount); andrew@2: //was sf_write_double(outfile, frame, readcount) ; andrew@2: andrew@2: }//end readcount andrew@2: //END STEREO OKAY andrew@2: andrew@2: // Close input file andrew@2: sf_close (infile); andrew@2: Venetian@7: endProcessing(); andrew@6: andrew@2: return 0; andrew@2: andrew@2: } andrew@2: andrew@4: double PreciseOnsetDetectorOffline::frameIndexToSeconds(const int& frameIndex){ andrew@4: return ((double)(frameIndex*hopSize) /44100.);//- (detectionFunction.framesize/2)?; andrew@4: } andrew@4: andrew@4: double PreciseOnsetDetectorOffline::secondsToFrameIndex(const double& seconds){ andrew@4: return (seconds*44100./(double)hopSize );//- (detectionFunction.framesize/2)?; andrew@4: } andrew@4: andrew@4: andrew@3: void PreciseOnsetDetectorOffline::exportOnsetTimes(){ andrew@3: exportOnsetTimes(0.0, samples/44100.0);//i.e. the whole file andrew@3: } andrew@2: andrew@2: andrew@3: void PreciseOnsetDetectorOffline::exportOnsetTimes(double startTime, double endTime){ andrew@3: //for writing output andrew@3: BeatWriter writer; andrew@3: std::string outputFilename = loadedFilename+"_preciseOnsets.txt"; andrew@3: andrew@3: if (writeOutput) andrew@3: writer.openFile(outputFilename); andrew@3: andrew@3: andrew@3: int index = 0; Venetian@7: while (index < onsetList.size() && onsetList[index].onsetLocation < startTime) andrew@3: index++; andrew@3: Venetian@7: while (index < onsetList.size() && onsetList[index].onsetLocation <= endTime){ Venetian@7: writer.writeBeatTime(onsetList[index].onsetLocation); andrew@3: index++; andrew@3: } andrew@3: andrew@3: if (writeOutput) andrew@3: writer.closeFile(); andrew@3: } andrew@2: andrew@2: void PreciseOnsetDetectorOffline::update(){ andrew@2: andrew@2: } andrew@2: andrew@2: andrew@2: void PreciseOnsetDetectorOffline::draw(){ andrew@2: //do vizualisation in a specialised class andrew@2: } andrew@2: andrew@2: void PreciseOnsetDetectorOffline::printOnsetLocations(){ Venetian@7: for (int i = 0; i < (int)onsetList.size(); i++) Venetian@7: printf("Onset[%i]: %.3f, samples %i\n", i, onsetList[i].onsetLocation, onsetList[i].positionSamples); andrew@2: } andrew@3: andrew@3: double PreciseOnsetDetectorOffline::closestOnset(double& targetVal){ andrew@3: int bestIndex = 0; andrew@3: double bestDiff = 99999; andrew@3: double bestVal = -1; andrew@3: Venetian@7: for (int testIndex = 0; testIndex < (int)onsetList.size(); testIndex++){ Venetian@7: double testDiff = (onsetList[testIndex].onsetLocation - targetVal); andrew@3: if (fabs(testDiff) < bestDiff){ andrew@3: bestDiff = fabs(testDiff); Venetian@7: bestVal = onsetList[testIndex].onsetLocation; andrew@3: bestIndex = testIndex; andrew@3: } andrew@3: } andrew@3: return bestVal; andrew@3: } andrew@3: andrew@4: void PreciseOnsetDetectorOffline::loadOnsetLocations(DoubleVector& beats){ andrew@4: //replaces onset locations with new vector Venetian@7: onsetList.clear(); Venetian@7: Venetian@7: //onsetLocations.clear(); andrew@4: for (int i = 0; i < beats.size(); i++){ Venetian@7: //onsetLocations.push_back(beats[i]); Venetian@7: OnsetInfo newInfo; Venetian@7: newInfo.onsetLocation = beats[i]; Venetian@7: onsetList.push_back(newInfo); andrew@4: } Venetian@7: } Venetian@7: Venetian@7: double PreciseOnsetDetectorOffline::onsetAtIndex(int index){ Venetian@7: if (index < onsetList.size()) Venetian@7: return onsetList[index].onsetLocation; Venetian@7: else Venetian@7: return 0; Venetian@7: /* Venetian@7: if (index < onsetLocations.size()) Venetian@7: return onsetLocations[index]; Venetian@7: else Venetian@7: return 0; Venetian@7: */ Venetian@7: } Venetian@7: Venetian@7: void PreciseOnsetDetectorOffline::cropStartTo(double startTime){ Venetian@7: int index = 0; Venetian@7: while (index < onsetList.size() && onsetList[index].onsetLocation < startTime){ Venetian@7: onsetList.erase(onsetList.begin()); Venetian@7: // onsetLocations.erase(onsetLocations.begin()); Venetian@7: // onsetPositionSamples.erase(onsetPositionSamples.begin()); Venetian@7: // onsetPositionFrames.erase(onsetPositionFrames.begin()); Venetian@7: } Venetian@7: /* Venetian@7: int index = 0; Venetian@7: while (index < onsetLocations.size() && onsetLocations[index] < startTime){ Venetian@7: onsetLocations.erase(onsetLocations.begin()); Venetian@7: onsetPositionSamples.erase(onsetPositionSamples.begin()); Venetian@7: onsetPositionFrames.erase(onsetPositionFrames.begin()); Venetian@7: } Venetian@7: */ Venetian@7: } Venetian@7: Venetian@7: void PreciseOnsetDetectorOffline::setMinimumThreshold(float fVal){ Venetian@7: peakProcess.minimumThreshold = fVal; Venetian@7: } Venetian@7: Venetian@7: Venetian@7: Venetian@7: double PreciseOnsetDetectorOffline::beatAtIndex(std::vector beatTimes, int beatIndex){ Venetian@7: if (beatIndex >= 0 && beatIndex < beatTimes.size()){ Venetian@7: return beatTimes[beatIndex]; Venetian@7: } else { Venetian@7: printf("OUT OF RANGE\n"); Venetian@7: return 0; Venetian@7: } Venetian@7: } Venetian@7: Venetian@7: Venetian@7: Venetian@7: #pragma mark Quantisation Venetian@7: void PreciseOnsetDetectorOffline::categoriseOnsets(std::vector beatTimes){ Venetian@7: double onsetTime; Venetian@7: double cutoff = 0.06;//seconds - is it within this range of given beat times Venetian@7: Venetian@7: for (int i = 0; i < onsetList.size(); i++){ Venetian@7: Venetian@7: onsetTime = onsetList[i].onsetLocation; Venetian@7: Venetian@7: int beatIndex = 0; Venetian@7: while(beatIndex < beatTimes.size() && beatTimes[beatIndex] < onsetTime){ Venetian@7: beatIndex++; Venetian@7: } Venetian@7: while(beatIndex > 0 && beatTimes[beatIndex] > onsetTime){ Venetian@7: beatIndex--; Venetian@7: } Venetian@7: //beatIndex now either side of onset, or onset before first beat Venetian@7: Venetian@7: printf("beat %.2f, beat+1 %.2f, onset %.2f\n", beatAtIndex(beatTimes, beatIndex), Venetian@7: beatAtIndex(beatTimes, beatIndex+1), onsetTime); Venetian@7: Venetian@7: double beatTime = beatAtIndex(beatTimes, beatIndex); Venetian@7: double nextBeatTime = beatAtIndex(beatTimes, beatIndex+1); Venetian@7: Venetian@7: //new cutoff part Venetian@7: onsetList[i].onBeat = false; Venetian@7: Venetian@7: Venetian@7: Venetian@7: if (fabs(onsetTime-beatTime) < cutoff || fabs(onsetTime-nextBeatTime) < cutoff){ Venetian@7: onsetList[i].onBeat = true; Venetian@7: } Venetian@7: //end cutoff part Venetian@7: Venetian@7: double diff = onsetTime - beatTime; Venetian@7: Venetian@7: double period; Venetian@7: if (nextBeatTime){ Venetian@7: period = nextBeatTime - beatTime; Venetian@7: } else { Venetian@7: period = beatAtIndex(beatTimes, beatIndex) - beatAtIndex(beatTimes, beatIndex-1); Venetian@7: } Venetian@7: Venetian@7: while (onsetTime < beatTime){ Venetian@7: //bug if onset is before th first beat Venetian@7: beatTime -= period; Venetian@7: nextBeatTime -= period; Venetian@7: beatIndex--; Venetian@7: printf("FIXING: beat index %i time %f onsest %f\n", beatIndex, beatTime, onsetTime); Venetian@7: } Venetian@7: Venetian@7: if (period > 0){ Venetian@7: while (diff < 0){ Venetian@7: diff += period; Venetian@7: //i.e. add the beat period to bring it in range Venetian@7: } Venetian@7: //now can look which point it is nearest Venetian@7: double ratio = diff/period; Venetian@7: ratio *= 12; Venetian@7: int beattype = round(ratio); Venetian@7: if (beattype == 12){ Venetian@7: beattype = 0; Venetian@7: Venetian@7: beatIndex++;//added need to test Venetian@7: } Venetian@7: Venetian@7: doCorrection(beattype, beatIndex); Venetian@7: Venetian@7: Venetian@7: Venetian@7: // Onset newOnset; Venetian@7: // newOnset.time = onsetTime; Venetian@7: // newOnset.onsetType = beattype; Venetian@7: //int position = beatIndex%4; Venetian@7: Venetian@7: // if (onsetTime > beatTime + (period/2.)) Venetian@7: // pos++; Venetian@7: // newOnset.position = pos%4; Venetian@7: Venetian@7: onsetList[i].beatPosition = beatIndex; Venetian@7: onsetList[i].onsetType = beattype; Venetian@7: Venetian@7: printf("Position %i,%i\n", onsetList[i].beatPosition, beattype); Venetian@7: } Venetian@7: Venetian@7: } Venetian@7: Venetian@7: } Venetian@7: Venetian@7: Venetian@7: void PreciseOnsetDetectorOffline::doCorrection(int& beattype, int& beatPosition){ Venetian@7: switch (beattype) { Venetian@7: case 1: Venetian@7: beattype = 0;//on beat Venetian@7: break; Venetian@7: case 2: Venetian@7: beattype = 3;//16th Venetian@7: break; Venetian@7: case 5: case 7: Venetian@7: beattype = 6;//8th note Venetian@7: break; Venetian@7: case 10: Venetian@7: beattype = 9; Venetian@7: break; Venetian@7: case 11: Venetian@7: beattype = 0;//on the beat Venetian@7: beatPosition++; Venetian@7: break; Venetian@7: default: Venetian@7: break; Venetian@7: } Venetian@7: Venetian@7: } Venetian@7: Venetian@7: Venetian@7: Venetian@7: Venetian@7: int PreciseOnsetDetectorOffline::onsetAtBeat(int testPosition){ Venetian@7: //find onset bear at particular beat Venetian@7: int index = 0; Venetian@7: int foundIndex = 0; Venetian@7: bool found = false; Venetian@7: double tmpDiff = 10000; Venetian@7: while (index < onsetList.size() && onsetList[index].beatPosition <= testPosition){ Venetian@7: Venetian@7: if (onsetList[index].beatPosition == testPosition && onsetList[index].onsetType == 0){ Venetian@7: found = true; Venetian@7: foundIndex = index; Venetian@7: //tmpDiff = fabs(onsetList[index].onsetLocation - Venetian@7: //break; - could break here - would find the first onset Venetian@7: //commented out finds the last one - really we'd just want the closest one to beat time Venetian@7: } Venetian@7: index++; Venetian@7: } Venetian@7: if (found) Venetian@7: return foundIndex; Venetian@7: else Venetian@7: return -1; Venetian@7: } Venetian@7: