public void CopyValues(CamdPitchCandidate other) { halftone = other.halftone; error = other.error; normalizedError = other.normalizedError; normalizedErrorWithBias = other.normalizedErrorWithBias; }
public CamdAudioSamplesAnalyzer(int sampleRateHz, int maxSampleLength) { this.maxSampleLength = maxSampleLength; float[] halftoneFrequencies = MidiUtils.PrecalculateHalftoneFrequencies(MinNote, NumHalftones); halftoneDelays = MidiUtils.PrecalculateHalftoneDelays(sampleRateHz, halftoneFrequencies); for (int i = 0; i < currentCandidates.Length; i++) { currentCandidates[i] = new CamdPitchCandidate(); } }
private void FindBestCandidate(CamdPitchCandidate[] currentCandidates, CircularBuffer <CamdPitchCandidate> bestCandidateHistory) { bestCurrentCandidate = null; currentCandidates.ForEach((currentCandidate) => { // Bias towards recently detected pitches. // For candidates with similar normalizedError, // this will prefer a candidate that has been detected before, which reduces the "jitter" of the pitch detection. // However, if a new pitch has been clearly identified with significantly less normalizedError, // then an old detected pitch from the history will not be used instead because its normalizedError will still be less - even without bias. currentCandidate.normalizedErrorWithBias = currentCandidate.normalizedError; if (HalftoneContinuationBias >= 0 && HalftoneContinuationBias <= 1) { for (int historyIndex = 0; historyIndex < bestCandidateHistory.Size; historyIndex++) { if (bestCandidateHistory[historyIndex].halftone == currentCandidate.halftone) { // The further away in the history the halftone was before, the less the bias will impact the current candidate. float biasConsideringHistoryDistance = HalftoneContinuationBias - (HalftoneContinuationBias * (historyIndex / bestCandidateHistory.Size)); currentCandidate.normalizedErrorWithBias = currentCandidate.normalizedError * (1 - biasConsideringHistoryDistance); // Do not apply bias multiple times. break; } } } if (currentCandidate.halftone >= 0 && (bestCurrentCandidate == null || bestCurrentCandidate.halftone < 0 || currentCandidate.normalizedErrorWithBias < bestCurrentCandidate.normalizedErrorWithBias)) { bestCurrentCandidate = currentCandidate; } }); CamdPitchCandidate bestHistoryCandidate = null; for (int i = 0; i < bestCandidateHistory.Size; i++) { // Do not consider the biased error when comparing best candidate from history. CamdPitchCandidate historyCandidate = bestCandidateHistory[i]; if (historyCandidate.halftone >= 0 && (bestHistoryCandidate == null || bestHistoryCandidate.halftone < 0 || historyCandidate.normalizedError < bestHistoryCandidate.normalizedError)) { bestHistoryCandidate = historyCandidate; } } // Do not consider the biased error when comparing best candidate from history. bestCandidate = (bestHistoryCandidate == null || bestCurrentCandidate.normalizedErrorWithBias < bestHistoryCandidate.normalizedError) ? bestCurrentCandidate : bestHistoryCandidate; }
public CamdPitchCandidate(CamdPitchCandidate other) { CopyValues(other); }