Remove PitchTracker# (not good enough)
This commit is contained in:
1 parent
c96fac02c2
commit
167202e8ff
7 files changed
-1960
No files matched your search
@@ -1,71 +0,0 @@
|
|||||||
#region license
|
|
||||||
// This file is part of Vocaluxe.
|
|
||||||
//
|
|
||||||
// Vocaluxe is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Vocaluxe is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pitchtracker (Pt) in C# that uses IIR filters for smoothing and an intelligent detection algorithm to reduce calculations
|
|
||||||
/// DO NOT USE in 32 Bit! Due to floating point errors the filter will raise all values to NAN (TODO: FIX it!)
|
|
||||||
/// </summary>
|
|
||||||
class CPtSharp : CPitchTracker
|
|
||||||
{
|
|
||||||
private Pitch.PitchTracker _Instance;
|
|
||||||
|
|
||||||
public CPtSharp()
|
|
||||||
{
|
|
||||||
_Instance = new Pitch.PitchTracker {SampleRate = 44100, DetectLevelThreshold = 0.01f, PitchRecordHistorySize = 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Input(byte[] data)
|
|
||||||
{
|
|
||||||
short[] samplesShort = new short[data.Length / 2];
|
|
||||||
Buffer.BlockCopy(data, 0, samplesShort, 0, data.Length);
|
|
||||||
float[] samplesFloat = new float[samplesShort.Length];
|
|
||||||
for (int i = 0; i < samplesShort.Length; i++)
|
|
||||||
samplesFloat[i] = (float)samplesShort[i] / short.MaxValue;
|
|
||||||
_Instance.ProcessBuffer(samplesFloat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetNote(out float maxVolume, float[] weights)
|
|
||||||
{
|
|
||||||
int note = _Instance.CurrentPitchRecord.MidiNote - 15 - 21;
|
|
||||||
maxVolume = _Instance.LastMaxVol;
|
|
||||||
_SetWeights(note, weights);
|
|
||||||
return note;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetNumHalfTones()
|
|
||||||
{
|
|
||||||
return NumHalfTonesDef;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override float VolumeTreshold
|
|
||||||
{
|
|
||||||
get { return _Instance.DetectLevelThreshold; }
|
|
||||||
set { _Instance.DetectLevelThreshold = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void _Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (_Instance == null)
|
|
||||||
throw new ObjectDisposedException(GetType().Name);
|
|
||||||
_Instance = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
|
||||||
{
|
|
||||||
public class CircularBuffer<T> : IDisposable
|
|
||||||
{
|
|
||||||
int m_bufSize;
|
|
||||||
int m_begBufOffset;
|
|
||||||
int m_availBuf;
|
|
||||||
long m_startPos; // total circular buffer position
|
|
||||||
T[] m_buffer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public CircularBuffer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bufCount"></param>
|
|
||||||
public CircularBuffer(int bufCount)
|
|
||||||
{
|
|
||||||
SetSize(bufCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
SetSize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reset to the beginning of the buffer
|
|
||||||
/// </summary>
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
m_begBufOffset = 0;
|
|
||||||
m_availBuf = 0;
|
|
||||||
m_startPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the buffer to the specified size
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newSize"></param>
|
|
||||||
public void SetSize(int newSize)
|
|
||||||
{
|
|
||||||
Reset();
|
|
||||||
|
|
||||||
if (m_bufSize == newSize)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_buffer != null)
|
|
||||||
m_buffer = null;
|
|
||||||
|
|
||||||
m_bufSize = newSize;
|
|
||||||
|
|
||||||
if (m_bufSize > 0)
|
|
||||||
m_buffer = new T[m_bufSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear the buffer
|
|
||||||
/// </summary>
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Array.Clear(m_buffer, 0, m_buffer.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or set the start position
|
|
||||||
/// </summary>
|
|
||||||
public long StartPosition
|
|
||||||
{
|
|
||||||
get { return m_startPos; }
|
|
||||||
set { m_startPos = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the end position
|
|
||||||
/// </summary>
|
|
||||||
public long EndPosition
|
|
||||||
{
|
|
||||||
get { return m_startPos + m_availBuf; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or set the amount of avaliable space
|
|
||||||
/// </summary>
|
|
||||||
public int Available
|
|
||||||
{
|
|
||||||
get { return m_availBuf; }
|
|
||||||
set { m_availBuf = Math.Min(value, m_bufSize); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write data into the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="m_pInBuffer"></param>
|
|
||||||
/// <param name="count"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public int WriteBuffer(T[] m_pInBuffer, int count)
|
|
||||||
{
|
|
||||||
count = Math.Min(count, m_bufSize);
|
|
||||||
|
|
||||||
var startPos = m_availBuf != m_bufSize ? m_availBuf : m_begBufOffset;
|
|
||||||
var pass1Count = Math.Min(count, m_bufSize - startPos);
|
|
||||||
var pass2Count = count - pass1Count;
|
|
||||||
|
|
||||||
PitchDsp.CopyBuffer(m_pInBuffer, 0, m_buffer, startPos, pass1Count);
|
|
||||||
|
|
||||||
if (pass2Count > 0)
|
|
||||||
PitchDsp.CopyBuffer(m_pInBuffer, pass1Count, m_buffer, 0, pass2Count);
|
|
||||||
|
|
||||||
if (pass2Count == 0)
|
|
||||||
{
|
|
||||||
// did not wrap around
|
|
||||||
if (m_availBuf != m_bufSize)
|
|
||||||
m_availBuf += count; // have never wrapped around
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_begBufOffset += count;
|
|
||||||
m_startPos += count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// wrapped around
|
|
||||||
if (m_availBuf != m_bufSize)
|
|
||||||
m_startPos += pass2Count; // first time wrap-around
|
|
||||||
else
|
|
||||||
m_startPos += count;
|
|
||||||
|
|
||||||
m_begBufOffset = pass2Count;
|
|
||||||
m_availBuf = m_bufSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read from the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outBuffer"></param>
|
|
||||||
/// <param name="startRead"></param>
|
|
||||||
/// <param name="readCount"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool ReadBuffer(T[] outBuffer, long startRead, int readCount)
|
|
||||||
{
|
|
||||||
var endRead = (int)(startRead + readCount);
|
|
||||||
var endAvail = (int)(m_startPos + m_availBuf);
|
|
||||||
|
|
||||||
if (startRead < m_startPos || endRead > endAvail)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var startReadPos = (int)(((startRead - m_startPos) + m_begBufOffset) % m_bufSize);
|
|
||||||
var block1Samples = Math.Min(readCount, m_bufSize - startReadPos);
|
|
||||||
var block2Samples = readCount - block1Samples;
|
|
||||||
|
|
||||||
PitchDsp.CopyBuffer(m_buffer, startReadPos, outBuffer, 0, block1Samples);
|
|
||||||
|
|
||||||
if (block2Samples > 0)
|
|
||||||
PitchDsp.CopyBuffer(m_buffer, 0, outBuffer, block1Samples, block2Samples);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
|
||||||
{
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Clear the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
public static void Clear(this float[] buffer)
|
|
||||||
{
|
|
||||||
Array.Clear(buffer, 0, buffer.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
public static void Clear(this double[] buffer)
|
|
||||||
{
|
|
||||||
Array.Clear(buffer, 0, buffer.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copy the values from one buffer to a different or the same buffer.
|
|
||||||
/// It is safe to copy to the same buffer, even if the areas overlap
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
/// <param name="shiftBy"></param>
|
|
||||||
/// <param name="startIdx"></param>
|
|
||||||
/// <param name="length"></param>
|
|
||||||
public static void Copy(this float[] fromBuffer, float[] toBuffer, int fromStart, int toStart, int length)
|
|
||||||
{
|
|
||||||
if (toBuffer == null || fromBuffer.Length == 0 || toBuffer.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int fromBegIdx = fromStart;
|
|
||||||
int fromEndIdx = fromStart + length;
|
|
||||||
int toBegIdx = toStart;
|
|
||||||
int toEndIdx = toStart + length;
|
|
||||||
|
|
||||||
if (fromBegIdx < 0)
|
|
||||||
{
|
|
||||||
toBegIdx -= fromBegIdx;
|
|
||||||
fromBegIdx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toBegIdx < 0)
|
|
||||||
{
|
|
||||||
fromBegIdx -= toBegIdx;
|
|
||||||
toBegIdx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromEndIdx >= fromBuffer.Length)
|
|
||||||
{
|
|
||||||
toEndIdx -= fromEndIdx - fromBuffer.Length + 1;
|
|
||||||
fromEndIdx = fromBuffer.Length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toEndIdx >= toBuffer.Length)
|
|
||||||
{
|
|
||||||
fromEndIdx -= toEndIdx - toBuffer.Length + 1;
|
|
||||||
toEndIdx = fromBuffer.Length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromBegIdx < toBegIdx)
|
|
||||||
{
|
|
||||||
// Shift right, so start at the right
|
|
||||||
for (int fromIdx = fromEndIdx, toIdx = toEndIdx; fromIdx >= fromBegIdx; fromIdx--, toIdx--)
|
|
||||||
toBuffer[toIdx] = fromBuffer[fromIdx];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Shift left, so start at the left
|
|
||||||
for (int fromIdx = fromBegIdx, toIdx = toBegIdx; fromIdx <= fromEndIdx; fromIdx++, toIdx++)
|
|
||||||
toBuffer[toIdx] = fromBuffer[fromIdx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
public static void Clear(this float[] buffer, int startIdx, int endIdx)
|
|
||||||
{
|
|
||||||
Array.Clear(buffer, startIdx, endIdx - startIdx + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fill the buffer with the specified value
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
/// <param name="?"></param>
|
|
||||||
public static void Fill(this double[] buffer, double value)
|
|
||||||
{
|
|
||||||
for (int idx = 0; idx < buffer.Length; idx++)
|
|
||||||
buffer[idx] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,613 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Infinite impulse response filter (old style analog filters)
|
|
||||||
/// </summary>
|
|
||||||
class IIRFilter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The type of filter
|
|
||||||
/// </summary>
|
|
||||||
public enum FilterType
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
LP,
|
|
||||||
HP,
|
|
||||||
BP
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The filter prototype
|
|
||||||
/// </summary>
|
|
||||||
public enum ProtoType
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Butterworth,
|
|
||||||
Chebyshev,
|
|
||||||
}
|
|
||||||
|
|
||||||
const int kHistMask = 31;
|
|
||||||
const int kHistSize = 32;
|
|
||||||
|
|
||||||
private int m_order;
|
|
||||||
private ProtoType m_protoType;
|
|
||||||
private FilterType m_filterType;
|
|
||||||
|
|
||||||
private float m_fp1;
|
|
||||||
private float m_fp2;
|
|
||||||
private float m_fN;
|
|
||||||
private float m_ripple;
|
|
||||||
private float m_sampleRate;
|
|
||||||
private double[] m_real;
|
|
||||||
private double[] m_imag;
|
|
||||||
private double[] m_z;
|
|
||||||
private double[] m_aCoeff;
|
|
||||||
private double[] m_bCoeff;
|
|
||||||
private double[] m_inHistory;
|
|
||||||
private double[] m_outHistory;
|
|
||||||
private int m_histIdx;
|
|
||||||
private bool m_invertDenormal;
|
|
||||||
|
|
||||||
public IIRFilter()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if all the filter parameters are valid
|
|
||||||
/// </summary>
|
|
||||||
public bool FilterValid
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_order < 1 || m_order > 16 ||
|
|
||||||
m_protoType == ProtoType.None ||
|
|
||||||
m_filterType == FilterType.None ||
|
|
||||||
m_sampleRate <= 0.0f ||
|
|
||||||
m_fN <= 0.0f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
switch (m_filterType)
|
|
||||||
{
|
|
||||||
case FilterType.LP:
|
|
||||||
if (m_fp2 <= 0.0f)
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FilterType.BP:
|
|
||||||
if (m_fp1 <= 0.0f || m_fp2 <= 0.0f || m_fp1 >= m_fp2)
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FilterType.HP:
|
|
||||||
if (m_fp1 <= 0.0f)
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For bandpass, the order must be even
|
|
||||||
if (m_filterType == FilterType.BP && (m_order & 1) != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the filter prototype
|
|
||||||
/// </summary>
|
|
||||||
public ProtoType Proto
|
|
||||||
{
|
|
||||||
get { return m_protoType; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_protoType = value;
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the filter type
|
|
||||||
/// </summary>
|
|
||||||
public FilterType Type
|
|
||||||
{
|
|
||||||
get { return m_filterType; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_filterType = value;
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Order
|
|
||||||
{
|
|
||||||
get { return m_order; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_order = Math.Min(16, Math.Max(1, Math.Abs(value)));
|
|
||||||
|
|
||||||
if (m_filterType == FilterType.BP && Odd(m_order))
|
|
||||||
m_order++;
|
|
||||||
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float SampleRate
|
|
||||||
{
|
|
||||||
get { return m_sampleRate; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_sampleRate = value;
|
|
||||||
m_fN = 0.5f * m_sampleRate;
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float FreqLow
|
|
||||||
{
|
|
||||||
get { return m_fp1; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_fp1 = value;
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float FreqHigh
|
|
||||||
{
|
|
||||||
get { return m_fp2; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_fp2 = value;
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float Ripple
|
|
||||||
{
|
|
||||||
get { return m_ripple; }
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_ripple = value;
|
|
||||||
Design();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if n is odd
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="n"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private bool Odd(int n)
|
|
||||||
{
|
|
||||||
return (n & 1) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Square
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="f"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private float Sqr(float value)
|
|
||||||
{
|
|
||||||
return value * value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Square
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="f"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private double Sqr(double value)
|
|
||||||
{
|
|
||||||
return value * value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines poles and zeros of IIR filter
|
|
||||||
/// based on bilinear transform method
|
|
||||||
/// </summary>
|
|
||||||
private void LocatePolesAndZeros()
|
|
||||||
{
|
|
||||||
m_real = new double[m_order + 1];
|
|
||||||
m_imag = new double[m_order + 1];
|
|
||||||
m_z = new double[m_order + 1];
|
|
||||||
double ln10 = Math.Log(10.0);
|
|
||||||
|
|
||||||
// Butterworth, Chebyshev parameters
|
|
||||||
int n = m_order;
|
|
||||||
|
|
||||||
if (m_filterType == FilterType.BP)
|
|
||||||
n = n / 2;
|
|
||||||
|
|
||||||
int ir = n % 2;
|
|
||||||
int n1 = n + ir;
|
|
||||||
int n2 = (3 * n + ir) / 2 - 1;
|
|
||||||
double f1;
|
|
||||||
|
|
||||||
switch (m_filterType)
|
|
||||||
{
|
|
||||||
case FilterType.LP:
|
|
||||||
f1 = m_fp2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FilterType.HP:
|
|
||||||
f1 = m_fN - m_fp1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FilterType.BP:
|
|
||||||
f1 = m_fp2 - m_fp1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
f1 = 0.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
double tanw1 = Math.Tan(0.5 * Math.PI * f1 / m_fN);
|
|
||||||
double tansqw1 = Sqr(tanw1);
|
|
||||||
|
|
||||||
// Real and Imaginary parts of low-pass poles
|
|
||||||
double t, a = 1.0, r = 1.0, i = 1.0;
|
|
||||||
|
|
||||||
for (int k = n1; k <= n2; k++)
|
|
||||||
{
|
|
||||||
t = 0.5 * (2 * k + 1 - ir) * Math.PI / (double)n;
|
|
||||||
|
|
||||||
switch (m_protoType)
|
|
||||||
{
|
|
||||||
case ProtoType.Butterworth:
|
|
||||||
double b3 = 1.0 - 2.0 * tanw1 * Math.Cos(t) + tansqw1;
|
|
||||||
r = (1.0 - tansqw1) / b3;
|
|
||||||
i = 2.0 * tanw1 * Math.Sin(t) / b3;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ProtoType.Chebyshev:
|
|
||||||
double d = 1.0 - Math.Exp(-0.05 * m_ripple * ln10);
|
|
||||||
double e = 1.0 / Math.Sqrt(1.0 / Sqr(1.0 - d) - 1.0);
|
|
||||||
double x = Math.Pow(Math.Sqrt(e * e + 1.0) + e, 1.0 / (double)n);
|
|
||||||
a = 0.5 * (x - 1.0 / x);
|
|
||||||
double b = 0.5 * (x + 1.0 / x);
|
|
||||||
double c3 = a * tanw1 * Math.Cos(t);
|
|
||||||
double c4 = b * tanw1 * Math.Sin(t);
|
|
||||||
double c5 = Sqr(1.0 - c3) + Sqr(c4);
|
|
||||||
r = 2.0 * (1.0 - c3) / c5 - 1.0;
|
|
||||||
i = 2.0 * c4 / c5;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int m = 2 * (n2 - k) + 1;
|
|
||||||
m_real[m + ir] = r;
|
|
||||||
m_imag[m + ir] = Math.Abs(i);
|
|
||||||
m_real[m + ir + 1] = r;
|
|
||||||
m_imag[m + ir + 1] = -Math.Abs(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Odd(n))
|
|
||||||
{
|
|
||||||
if (m_protoType == ProtoType.Butterworth)
|
|
||||||
r = (1.0 - tansqw1) / (1.0 + 2.0 * tanw1 + tansqw1);
|
|
||||||
|
|
||||||
if (m_protoType == ProtoType.Chebyshev)
|
|
||||||
r = 2.0 / (1.0 + a * tanw1) - 1.0;
|
|
||||||
|
|
||||||
m_real[1] = r;
|
|
||||||
m_imag[1] = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (m_filterType)
|
|
||||||
{
|
|
||||||
case FilterType.LP:
|
|
||||||
for (int m = 1; m <= n; m++)
|
|
||||||
m_z[m] = -1.0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FilterType.HP:
|
|
||||||
// Low-pass to high-pass transformation
|
|
||||||
for (int m = 1; m <= n; m++)
|
|
||||||
{
|
|
||||||
m_real[m] = -m_real[m];
|
|
||||||
m_z[m] = 1.0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FilterType.BP:
|
|
||||||
// Low-pass to bandpass transformation
|
|
||||||
for (int m = 1; m <= n; m++)
|
|
||||||
{
|
|
||||||
m_z[m] = 1.0;
|
|
||||||
m_z[m + n] = -1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
double f4 = 0.5 * Math.PI * m_fp1 / m_fN;
|
|
||||||
double f5 = 0.5 * Math.PI * m_fp2 / m_fN;
|
|
||||||
double aa = Math.Cos(f4 + f5) / Math.Cos(f5 - f4);
|
|
||||||
double aR, aI, h1, h2, p1R, p2R, p1I, p2I;
|
|
||||||
|
|
||||||
for (int m1 = 0; m1 <= (m_order - 1) / 2; m1++)
|
|
||||||
{
|
|
||||||
int m = 1 + 2 * m1;
|
|
||||||
aR = m_real[m];
|
|
||||||
aI = m_imag[m];
|
|
||||||
|
|
||||||
if (Math.Abs(aI) < 0.0001)
|
|
||||||
{
|
|
||||||
h1 = 0.5 * aa * (1.0 + aR);
|
|
||||||
h2 = Sqr(h1) - aR;
|
|
||||||
if (h2 > 0.0)
|
|
||||||
{
|
|
||||||
p1R = h1 + Math.Sqrt(h2);
|
|
||||||
p2R = h1 - Math.Sqrt(h2);
|
|
||||||
p1I = 0.0;
|
|
||||||
p2I = 0.0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
p1R = h1;
|
|
||||||
p2R = h1;
|
|
||||||
p1I = Math.Sqrt(Math.Abs(h2));
|
|
||||||
p2I = -p1I;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double fR = aa * 0.5 * (1.0 + aR);
|
|
||||||
double fI = aa * 0.5 * aI;
|
|
||||||
double gR = Sqr(fR) - Sqr(fI) - aR;
|
|
||||||
double gI = 2 * fR * fI - aI;
|
|
||||||
double sR = Math.Sqrt(0.5 * Math.Abs(gR + Math.Sqrt(Sqr(gR) + Sqr(gI))));
|
|
||||||
double sI = gI / (2.0 * sR);
|
|
||||||
p1R = fR + sR;
|
|
||||||
p1I = fI + sI;
|
|
||||||
p2R = fR - sR;
|
|
||||||
p2I = fI - sI;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_real[m] = p1R;
|
|
||||||
m_real[m + 1] = p2R;
|
|
||||||
m_imag[m] = p1I;
|
|
||||||
m_imag[m + 1] = p2I;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Odd(n))
|
|
||||||
{
|
|
||||||
m_real[2] = m_real[n + 1];
|
|
||||||
m_imag[2] = m_imag[n + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int k = n; k >= 1; k--)
|
|
||||||
{
|
|
||||||
int m = 2 * k - 1;
|
|
||||||
m_real[m] = m_real[k];
|
|
||||||
m_real[m + 1] = m_real[k];
|
|
||||||
m_imag[m] = Math.Abs(m_imag[k]);
|
|
||||||
m_imag[m + 1] = -Math.Abs(m_imag[k]);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculate all the values
|
|
||||||
/// </summary>
|
|
||||||
public void Design()
|
|
||||||
{
|
|
||||||
if (!this.FilterValid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_aCoeff = new double[m_order + 1];
|
|
||||||
m_bCoeff = new double[m_order + 1];
|
|
||||||
m_inHistory = new double[kHistSize];
|
|
||||||
m_outHistory = new double[kHistSize];
|
|
||||||
|
|
||||||
double[] newA = new double[m_order + 1];
|
|
||||||
double[] newB = new double[m_order + 1];
|
|
||||||
|
|
||||||
// Find filter poles and zeros
|
|
||||||
LocatePolesAndZeros();
|
|
||||||
|
|
||||||
// Compute filter coefficients from pole/zero values
|
|
||||||
m_aCoeff[0] = 1.0;
|
|
||||||
m_bCoeff[0] = 1.0;
|
|
||||||
|
|
||||||
for (int i = 1; i <= m_order; i++)
|
|
||||||
{
|
|
||||||
m_aCoeff[i] = 0.0;
|
|
||||||
m_bCoeff[i] = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int k = 0;
|
|
||||||
int n = m_order;
|
|
||||||
int pairs = n / 2;
|
|
||||||
|
|
||||||
if (Odd(m_order))
|
|
||||||
{
|
|
||||||
// First subfilter is first order
|
|
||||||
m_aCoeff[1] = -m_z[1];
|
|
||||||
m_bCoeff[1] = -m_real[1];
|
|
||||||
k = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int p = 1; p <= pairs; p++)
|
|
||||||
{
|
|
||||||
int m = 2 * p - 1 + k;
|
|
||||||
double alpha1 = -(m_z[m] + m_z[m + 1]);
|
|
||||||
double alpha2 = m_z[m] * m_z[m + 1];
|
|
||||||
double beta1 = -2.0 * m_real[m];
|
|
||||||
double beta2 = Sqr(m_real[m]) + Sqr(m_imag[m]);
|
|
||||||
|
|
||||||
newA[1] = m_aCoeff[1] + alpha1 * m_aCoeff[0];
|
|
||||||
newB[1] = m_bCoeff[1] + beta1 * m_bCoeff[0];
|
|
||||||
|
|
||||||
for (int i = 2; i <= n; i++)
|
|
||||||
{
|
|
||||||
newA[i] = m_aCoeff[i] + alpha1 * m_aCoeff[i - 1] + alpha2 * m_aCoeff[i - 2];
|
|
||||||
newB[i] = m_bCoeff[i] + beta1 * m_bCoeff[i - 1] + beta2 * m_bCoeff[i - 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i <= n; i++)
|
|
||||||
{
|
|
||||||
m_aCoeff[i] = newA[i];
|
|
||||||
m_bCoeff[i] = newB[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the filter is normalized
|
|
||||||
FilterGain(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reset the history buffers
|
|
||||||
/// </summary>
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
if (m_inHistory != null)
|
|
||||||
m_inHistory.Clear();
|
|
||||||
|
|
||||||
if (m_outHistory != null)
|
|
||||||
m_outHistory.Clear();
|
|
||||||
|
|
||||||
m_histIdx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reset the filter, and fill the appropriate history buffers with the specified value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="historyValue"></param>
|
|
||||||
public void Reset(double startValue)
|
|
||||||
{
|
|
||||||
m_histIdx = 0;
|
|
||||||
|
|
||||||
if (m_inHistory == null || m_outHistory == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_inHistory.Fill(startValue);
|
|
||||||
|
|
||||||
if (m_inHistory != null)
|
|
||||||
{
|
|
||||||
switch (m_filterType)
|
|
||||||
{
|
|
||||||
case FilterType.LP:
|
|
||||||
m_outHistory.Fill(startValue);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
m_outHistory.Clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Apply the filter to the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bufIn"></param>
|
|
||||||
public void FilterBuffer(float[] srcBuf, long srcPos, float[] dstBuf, long dstPos, long nLen)
|
|
||||||
{
|
|
||||||
const double kDenormal = 0.000000000000001;
|
|
||||||
double denormal = m_invertDenormal ? -kDenormal : kDenormal;
|
|
||||||
m_invertDenormal = !m_invertDenormal;
|
|
||||||
|
|
||||||
for (int sampleIdx = 0; sampleIdx < nLen; sampleIdx++)
|
|
||||||
{
|
|
||||||
double sum = 0.0f;
|
|
||||||
|
|
||||||
m_inHistory[m_histIdx] = srcBuf[srcPos + sampleIdx] + denormal;
|
|
||||||
|
|
||||||
for (int idx = 0; idx < m_aCoeff.Length; idx++)
|
|
||||||
sum += m_aCoeff[idx] * m_inHistory[(m_histIdx - idx) & kHistMask];
|
|
||||||
|
|
||||||
for (int idx = 1; idx < m_bCoeff.Length; idx++)
|
|
||||||
sum -= m_bCoeff[idx] * m_outHistory[(m_histIdx - idx) & kHistMask];
|
|
||||||
|
|
||||||
m_outHistory[m_histIdx] = sum;
|
|
||||||
m_histIdx = (m_histIdx + 1) & kHistMask;
|
|
||||||
dstBuf[dstPos + sampleIdx] = (float)sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float FilterSample(float inVal)
|
|
||||||
{
|
|
||||||
double sum = 0.0f;
|
|
||||||
|
|
||||||
m_inHistory[m_histIdx] = inVal;
|
|
||||||
|
|
||||||
for (int idx = 0; idx < m_aCoeff.Length; idx++)
|
|
||||||
sum += m_aCoeff[idx] * m_inHistory[(m_histIdx - idx) & kHistMask];
|
|
||||||
|
|
||||||
for (int idx = 1; idx < m_bCoeff.Length; idx++)
|
|
||||||
sum -= m_bCoeff[idx] * m_outHistory[(m_histIdx - idx) & kHistMask];
|
|
||||||
|
|
||||||
m_outHistory[m_histIdx] = sum;
|
|
||||||
m_histIdx = (m_histIdx + 1) & kHistMask;
|
|
||||||
|
|
||||||
return (float)sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the gain at the specified number of frequency points
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="freqPoints"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public float[] FilterGain(int freqPoints)
|
|
||||||
{
|
|
||||||
// Filter gain at uniform frequency intervals
|
|
||||||
float[] g = new float[freqPoints];
|
|
||||||
double theta, s, c, sac, sas, sbc, sbs;
|
|
||||||
float gMax = -100.0f;
|
|
||||||
float sc = 10.0f / (float)Math.Log(10.0f);
|
|
||||||
double t = Math.PI / (freqPoints - 1);
|
|
||||||
|
|
||||||
for (int i = 0; i < freqPoints; i++)
|
|
||||||
{
|
|
||||||
theta = i * t;
|
|
||||||
|
|
||||||
if (i == 0)
|
|
||||||
theta = Math.PI * 0.0001;
|
|
||||||
|
|
||||||
if (i == freqPoints - 1)
|
|
||||||
theta = Math.PI * 0.9999;
|
|
||||||
|
|
||||||
sac = 0.0f;
|
|
||||||
sas = 0.0f;
|
|
||||||
sbc = 0.0f;
|
|
||||||
sbs = 0.0f;
|
|
||||||
|
|
||||||
for (int k = 0; k <= m_order; k++)
|
|
||||||
{
|
|
||||||
c = Math.Cos(k * theta);
|
|
||||||
s = Math.Sin(k * theta);
|
|
||||||
sac += c * m_aCoeff[k];
|
|
||||||
sas += s * m_aCoeff[k];
|
|
||||||
sbc += c * m_bCoeff[k];
|
|
||||||
sbs += s * m_bCoeff[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
g[i] = sc * (float)Math.Log((Sqr(sac) + Sqr(sas)) / (Sqr(sbc) + Sqr(sbs)));
|
|
||||||
gMax = Math.Max(gMax, g[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize to 0 dB maximum gain
|
|
||||||
for (int i = 0; i < freqPoints; i++)
|
|
||||||
g[i] -= gMax;
|
|
||||||
|
|
||||||
// Normalize numerator (a) coefficients
|
|
||||||
float normFactor = (float)Math.Pow(10.0, -0.05 * gMax);
|
|
||||||
|
|
||||||
for (int i = 0; i <= m_order; i++)
|
|
||||||
m_aCoeff[i] *= normFactor;
|
|
||||||
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,561 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pitch related DSP
|
|
||||||
/// </summary>
|
|
||||||
public class PitchDsp
|
|
||||||
{
|
|
||||||
public static readonly double InverseLog2 = 1.0 / Math.Log10(2.0);
|
|
||||||
|
|
||||||
private const int kCourseOctaveSteps = 96;
|
|
||||||
private const int kScanHiSize = 31;
|
|
||||||
private const float kScanHiFreqStep = 1.005f;
|
|
||||||
private const int kMinMidiNote = 21; // A0
|
|
||||||
private const int kMaxMidiNote = 108; // C8
|
|
||||||
|
|
||||||
private float m_minPitch;
|
|
||||||
private float m_maxPitch;
|
|
||||||
private int m_minNote;
|
|
||||||
private int m_maxNote;
|
|
||||||
private int m_blockLen14; // 1/4 block len
|
|
||||||
private int m_blockLen24; // 2/4 block len
|
|
||||||
private int m_blockLen34; // 3/4 block len
|
|
||||||
private int m_blockLen44; // 4/4 block len
|
|
||||||
private double m_sampleRate;
|
|
||||||
|
|
||||||
private int m_numCourseSteps;
|
|
||||||
private float[] m_pCourseFreqOffset;
|
|
||||||
private float[] m_pCourseFreq;
|
|
||||||
private float[] m_scanHiOffset = new float[kScanHiSize];
|
|
||||||
private float[] m_peakBuf = new float[kScanHiSize];
|
|
||||||
private int m_prevPitchIdx;
|
|
||||||
private float[] m_detectCurve;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fSampleRate"></param>
|
|
||||||
/// <param name="minFreq"></param>
|
|
||||||
/// <param name="maxFreq"></param>
|
|
||||||
/// <param name="fFreqStep"></param>
|
|
||||||
public PitchDsp(double sampleRate, float minPitch, float maxPitch)
|
|
||||||
{
|
|
||||||
m_sampleRate = sampleRate;
|
|
||||||
m_minPitch = minPitch;
|
|
||||||
m_maxPitch = maxPitch;
|
|
||||||
|
|
||||||
m_minNote = (int)(PitchToMidiNote(m_minPitch) + 0.5f) + 2;
|
|
||||||
m_maxNote = (int)(PitchToMidiNote(m_maxPitch) + 0.5f) - 2;
|
|
||||||
|
|
||||||
m_blockLen44 = (int)(m_sampleRate / m_minPitch + 0.5f);
|
|
||||||
m_blockLen34 = (m_blockLen44 * 3) / 4;
|
|
||||||
m_blockLen24 = m_blockLen44 / 2;
|
|
||||||
m_blockLen14 = m_blockLen44 / 4;
|
|
||||||
|
|
||||||
m_numCourseSteps = (int)((Math.Log((double)m_maxPitch / (double)m_minPitch) / Math.Log(2.0)) * kCourseOctaveSteps + 0.5) + 3;
|
|
||||||
|
|
||||||
m_pCourseFreqOffset = new float[m_numCourseSteps + 10000];
|
|
||||||
m_pCourseFreq = new float[m_numCourseSteps + 10000];
|
|
||||||
|
|
||||||
m_detectCurve = new float[m_numCourseSteps];
|
|
||||||
|
|
||||||
double freqStep = 1.0 / Math.Pow(2.0, 1.0 / kCourseOctaveSteps);
|
|
||||||
double curFreq = m_maxPitch / freqStep;
|
|
||||||
|
|
||||||
// frequency is stored from high to low
|
|
||||||
for (int idx = 0; idx < m_numCourseSteps; idx++)
|
|
||||||
{
|
|
||||||
m_pCourseFreq[idx] = (float)curFreq;
|
|
||||||
m_pCourseFreqOffset[idx] = (float)(m_sampleRate / curFreq);
|
|
||||||
curFreq *= freqStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int idx = 0; idx < kScanHiSize; idx++)
|
|
||||||
m_scanHiOffset[idx] = (float)Math.Pow(kScanHiFreqStep, (kScanHiSize / 2) - idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the max detected pitch
|
|
||||||
/// </summary>
|
|
||||||
public float MaxPitch
|
|
||||||
{
|
|
||||||
get { return m_maxPitch; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the min detected pitch
|
|
||||||
/// </summary>
|
|
||||||
public float MinPitch
|
|
||||||
{
|
|
||||||
get { return m_minPitch; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the max note
|
|
||||||
/// </summary>
|
|
||||||
public int MaxNote
|
|
||||||
{
|
|
||||||
get { return m_maxNote; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the min note
|
|
||||||
/// </summary>
|
|
||||||
public int MinNote
|
|
||||||
{
|
|
||||||
get { return m_minNote; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Detect the pitch
|
|
||||||
/// </summary>
|
|
||||||
public float DetectPitch(float[] samplesLo, float[] samplesHi)
|
|
||||||
{
|
|
||||||
return DetectPitchLo(samplesLo, samplesHi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Low resolution pitch detection
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataIdx"></param>
|
|
||||||
/// <param name="begFreqIdx"></param>
|
|
||||||
/// <param name="endFreqIdx"></param>
|
|
||||||
/// <param name="blockLen"></param>
|
|
||||||
/// <param name="stepSize"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private float DetectPitchLo(float[] samplesLo, float[] samplesHi)
|
|
||||||
{
|
|
||||||
m_detectCurve.Clear();
|
|
||||||
|
|
||||||
const int skipSize = 8;
|
|
||||||
const int peakScanSize = 23;
|
|
||||||
const int peakScanSizeHalf = peakScanSize / 2;
|
|
||||||
|
|
||||||
float peakThresh1 = 200.0f;
|
|
||||||
float peakThresh2 = 600.0f;
|
|
||||||
bool bufferSwitched = false;
|
|
||||||
|
|
||||||
for (int idx = 0; idx < m_numCourseSteps; idx += skipSize)
|
|
||||||
{
|
|
||||||
int blockLen = Math.Min(m_blockLen44, (int)m_pCourseFreqOffset[idx] * 2);
|
|
||||||
float[] curSamples = null;
|
|
||||||
|
|
||||||
// 258 is at 250 Hz, which is the switchover frequency for the two filters
|
|
||||||
bool loBuffer = idx >= 258;
|
|
||||||
|
|
||||||
if (loBuffer)
|
|
||||||
{
|
|
||||||
if (!bufferSwitched)
|
|
||||||
{
|
|
||||||
m_detectCurve.Clear(258 - peakScanSizeHalf, 258 + peakScanSizeHalf);
|
|
||||||
bufferSwitched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
curSamples = samplesLo;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
curSamples = samplesHi;
|
|
||||||
|
|
||||||
int stepSizeLoRes = blockLen / 10;
|
|
||||||
int stepSizeHiRes = Math.Max(1, Math.Min(5, idx * 5 / m_numCourseSteps));
|
|
||||||
|
|
||||||
float fValue = RatioAbsDiffLinear(curSamples, idx, blockLen, stepSizeLoRes, false);
|
|
||||||
|
|
||||||
if (fValue > peakThresh1)
|
|
||||||
{
|
|
||||||
// Do a closer search for the peak
|
|
||||||
int peakIdx = -1;
|
|
||||||
float peakVal = 0.0f;
|
|
||||||
float prevVal = 0.0f;
|
|
||||||
int dir = 4; // start going forward
|
|
||||||
int curPos = idx; // start at center of the scan range
|
|
||||||
int begSearch = Math.Max(idx - peakScanSizeHalf, 0);
|
|
||||||
int endSearch = Math.Min(idx + peakScanSizeHalf, m_numCourseSteps - 1);
|
|
||||||
|
|
||||||
while (curPos >= begSearch && curPos < endSearch)
|
|
||||||
{
|
|
||||||
float curVal = RatioAbsDiffLinear(curSamples, curPos, blockLen, stepSizeHiRes, true);
|
|
||||||
|
|
||||||
if (peakVal < curVal)
|
|
||||||
{
|
|
||||||
peakVal = curVal;
|
|
||||||
peakIdx = curPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevVal > curVal)
|
|
||||||
{
|
|
||||||
dir = -dir >> 1;
|
|
||||||
|
|
||||||
if (dir == 0)
|
|
||||||
{
|
|
||||||
if (peakVal > peakThresh2 && peakIdx >= 6 && peakIdx <= m_numCourseSteps - 7)
|
|
||||||
{
|
|
||||||
float fValL = RatioAbsDiffLinear(curSamples, peakIdx - 5, blockLen, stepSizeHiRes, true);
|
|
||||||
float fValR = RatioAbsDiffLinear(curSamples, peakIdx + 5, blockLen, stepSizeHiRes, true);
|
|
||||||
float fPointy = peakVal / (fValL + fValR) * 2.0f;
|
|
||||||
|
|
||||||
float minPointy = (m_prevPitchIdx > 0 && Math.Abs(m_prevPitchIdx - peakIdx) < 10) ? 1.2f : 1.5f;
|
|
||||||
|
|
||||||
if (fPointy > minPointy)
|
|
||||||
{
|
|
||||||
float pitchHi = DetectPitchHi(curSamples, peakIdx);
|
|
||||||
|
|
||||||
if (pitchHi > 1.0f)
|
|
||||||
{
|
|
||||||
m_prevPitchIdx = peakIdx;
|
|
||||||
return pitchHi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevVal = curVal;
|
|
||||||
curPos += dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_prevPitchIdx = 0;
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// High resolution pitch detection
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataIdx"></param>
|
|
||||||
/// <param name="lowFreqIdx"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private float DetectPitchHi(float[] samples, int lowFreqIdx)
|
|
||||||
{
|
|
||||||
int peakIdx = -1;
|
|
||||||
float prevVal = 0.0f;
|
|
||||||
int dir = 4; // start going forward
|
|
||||||
int curPos = kScanHiSize >> 1; // start at center of the scan range
|
|
||||||
|
|
||||||
m_peakBuf.Clear();
|
|
||||||
|
|
||||||
float offset = m_pCourseFreqOffset[lowFreqIdx];
|
|
||||||
|
|
||||||
while (curPos >= 0 && curPos < kScanHiSize)
|
|
||||||
{
|
|
||||||
if (m_peakBuf[curPos] == 0.0f)
|
|
||||||
m_peakBuf[curPos] = SumAbsDiffHermite(samples, offset * m_scanHiOffset[curPos], m_blockLen44, 1);
|
|
||||||
|
|
||||||
if (peakIdx < 0 || m_peakBuf[peakIdx] < m_peakBuf[curPos])
|
|
||||||
peakIdx = curPos;
|
|
||||||
|
|
||||||
if (prevVal > m_peakBuf[curPos])
|
|
||||||
{
|
|
||||||
dir = -dir >> 1;
|
|
||||||
|
|
||||||
if (dir == 0)
|
|
||||||
{
|
|
||||||
// found the peak
|
|
||||||
float minVal = Math.Min(m_peakBuf[peakIdx - 1], m_peakBuf[peakIdx + 1]);
|
|
||||||
|
|
||||||
minVal -= minVal * (1.0f / 32.0f);
|
|
||||||
|
|
||||||
var y1 = (float)Math.Log10(m_peakBuf[peakIdx - 1] - minVal);
|
|
||||||
var y2 = (float)Math.Log10(m_peakBuf[peakIdx] - minVal);
|
|
||||||
var y3 = (float)Math.Log10(m_peakBuf[peakIdx + 1] - minVal);
|
|
||||||
|
|
||||||
float fIdx = (float)peakIdx + (y3 - y1) / (2.0f * (2.0f * y2 - y1 - y3));
|
|
||||||
|
|
||||||
return (float)Math.Pow(kScanHiFreqStep, fIdx - (kScanHiSize / 2)) * m_pCourseFreq[lowFreqIdx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevVal = m_peakBuf[curPos];
|
|
||||||
curPos += dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a sine wave with the specified frequency, amplitude and starting angle.
|
|
||||||
/// Returns the updated angle.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
/// <param name="numSamples"></param>
|
|
||||||
/// <param name="freq"></param>
|
|
||||||
/// <param name="amplitude"></param>
|
|
||||||
/// <param name="startAngle"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static double CreateSineWave(float[] buffer, int numSamples, float sampleRate,
|
|
||||||
float freq, float amplitude, double startAngle)
|
|
||||||
{
|
|
||||||
double angleStep = freq / sampleRate * Math.PI * 2.0;
|
|
||||||
double curAngle = startAngle;
|
|
||||||
|
|
||||||
for (int idx = 0; idx < numSamples; idx++)
|
|
||||||
{
|
|
||||||
buffer[idx] = (float)Math.Sin(curAngle) * amplitude;
|
|
||||||
|
|
||||||
curAngle += angleStep;
|
|
||||||
|
|
||||||
while (curAngle > Math.PI)
|
|
||||||
curAngle -= Math.PI * 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return curAngle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the level is above the specified value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buffer"></param>
|
|
||||||
/// <param name="startIdx"></param>
|
|
||||||
/// <param name="len"></param>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool LevelIsAbove(float[] buffer, int len, float level)
|
|
||||||
{
|
|
||||||
if (buffer == null || buffer.Length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int endIdx = Math.Min(buffer.Length, len);
|
|
||||||
|
|
||||||
for (int idx = 0; idx < endIdx; idx++)
|
|
||||||
{
|
|
||||||
if (Math.Abs(buffer[idx]) >= level)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copy the data from the source to the destination buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source"></param>
|
|
||||||
/// <param name="destination"></param>
|
|
||||||
/// <param name="startPos"></param>
|
|
||||||
/// <param name="length"></param>
|
|
||||||
public static void CopyBuffer<T>(T[] source, int srcStart, T[] destination, int dstStart, int length)
|
|
||||||
{
|
|
||||||
if (length < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("length");
|
|
||||||
|
|
||||||
if (source == null || source.Length < srcStart + length)
|
|
||||||
throw new Exception("Source buffer is null or not large enough");
|
|
||||||
|
|
||||||
if (destination == null || destination.Length < dstStart + length)
|
|
||||||
throw new Exception("Destination buffer is null or not large enough");
|
|
||||||
|
|
||||||
int srcIdx = srcStart;
|
|
||||||
int dstIdx = dstStart;
|
|
||||||
|
|
||||||
for (int idx = 0; idx < length; idx++)
|
|
||||||
destination[dstIdx++] = source[srcIdx++];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// // 4-point, 3rd-order Hermite (x-form)
|
|
||||||
/// </summary>
|
|
||||||
private float InterpolateHermite(float fY0, float fY1, float fY2, float fY3, float frac)
|
|
||||||
{
|
|
||||||
float c1 = 0.5f * (fY2 - fY0);
|
|
||||||
float c3 = 1.5f * (fY1 - fY2) + 0.5f * (fY3 - fY0);
|
|
||||||
float c2 = fY0 - fY1 + c1 - c3;
|
|
||||||
|
|
||||||
return ((c3 * frac + c2) * frac + c1) * frac + fY1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Linear interpolation
|
|
||||||
/// nFrac is based on 1.0 = 256
|
|
||||||
/// </summary>
|
|
||||||
private float InterpolateLinear(float y0, float y1, float frac)
|
|
||||||
{
|
|
||||||
return y0 * (1.0f - frac) + y1 * frac;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Medium Low res SumAbsDiff
|
|
||||||
/// </summary>
|
|
||||||
private float RatioAbsDiffLinear(float[] samples, int freqIdx, int blockLen, int stepSize, bool hiRes)
|
|
||||||
{
|
|
||||||
if (hiRes && m_detectCurve[freqIdx] > 0.0f)
|
|
||||||
return m_detectCurve[freqIdx];
|
|
||||||
|
|
||||||
var offsetInt = (int)m_pCourseFreqOffset[freqIdx];
|
|
||||||
float offsetFrac = m_pCourseFreqOffset[freqIdx] - offsetInt;
|
|
||||||
float rect = 0.0f;
|
|
||||||
float absDiff = 0.01f; // prevent divide by zero
|
|
||||||
int count = 0;
|
|
||||||
float interp;
|
|
||||||
float sample;
|
|
||||||
|
|
||||||
// Do a scan using linear interpolation and the specified step size
|
|
||||||
for (int idx = 0; idx < blockLen; idx += stepSize, count++)
|
|
||||||
{
|
|
||||||
sample = samples[idx];
|
|
||||||
interp = InterpolateLinear(samples[offsetInt + idx], samples[offsetInt + idx + 1], offsetFrac);
|
|
||||||
absDiff += Math.Abs(sample - interp);
|
|
||||||
rect += Math.Abs(sample) + Math.Abs(interp);
|
|
||||||
}
|
|
||||||
|
|
||||||
float finalVal = rect / absDiff * 100.0f;
|
|
||||||
|
|
||||||
if (hiRes)
|
|
||||||
m_detectCurve[freqIdx] = finalVal;
|
|
||||||
|
|
||||||
return finalVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Medium High res SumAbsDiff
|
|
||||||
/// </summary>
|
|
||||||
private float SumAbsDiffHermite(float[] samples, float fOffset, int blockLen, int stepSize)
|
|
||||||
{
|
|
||||||
var offsetInt = (int)fOffset;
|
|
||||||
float offsetFrac = fOffset - offsetInt;
|
|
||||||
float value = 0.001f; // prevent divide by zero
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
// do a scan using linear interpolation and the specified step size
|
|
||||||
for (int idx = 0; idx < blockLen; idx += stepSize, count++)
|
|
||||||
{
|
|
||||||
int offsetIdx = offsetInt + idx;
|
|
||||||
|
|
||||||
value += Math.Abs(samples[idx] - InterpolateHermite(samples[offsetIdx - 1],
|
|
||||||
samples[offsetIdx],
|
|
||||||
samples[offsetIdx + 1],
|
|
||||||
samples[offsetIdx + 2],
|
|
||||||
offsetFrac));
|
|
||||||
}
|
|
||||||
|
|
||||||
return count / value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the MIDI note and cents of the pitch
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pitch"></param>
|
|
||||||
/// <param name="note"></param>
|
|
||||||
/// <param name="cents"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static bool PitchToMidiNote(float pitch, out int note, out int cents)
|
|
||||||
{
|
|
||||||
if (pitch < 20.0f)
|
|
||||||
{
|
|
||||||
note = 0;
|
|
||||||
cents = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float fNote = (float)((12.0 * Math.Log10(pitch / 55.0) * InverseLog2)) + 33.0f;
|
|
||||||
note = (int)(fNote + 0.5f);
|
|
||||||
cents = (int)((note - fNote) * 100);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the pitch from the MIDI note
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pitch"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static float PitchToMidiNote(float pitch)
|
|
||||||
{
|
|
||||||
if (pitch < 20.0f)
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
return (float)(12.0 * Math.Log10(pitch / 55.0) * InverseLog2) + 33.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the pitch from the MIDI note
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="note"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public float MidiNoteToPitch(float note)
|
|
||||||
{
|
|
||||||
if (note < 33.0f)
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
float pitch = (float)Math.Pow(10.0, (note - 33.0f) / InverseLog2 / 12.0f) * 55.0f;
|
|
||||||
return pitch <= m_maxPitch ? pitch : 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Format a midi note to text
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="note"></param>
|
|
||||||
/// <param name="sharps"></param>
|
|
||||||
/// <param name="showOctave"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string GetNoteName(int note, bool sharps, bool showOctave)
|
|
||||||
{
|
|
||||||
if (note < kMinMidiNote || note > kMaxMidiNote)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
note -= kMinMidiNote;
|
|
||||||
|
|
||||||
int octave = (note + 9) / 12;
|
|
||||||
note = note % 12;
|
|
||||||
string noteText = null;
|
|
||||||
|
|
||||||
switch (note)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
noteText = "A";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
noteText = sharps ? "A#" : "Bb";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
noteText = "B";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
noteText = "C";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
noteText = sharps ? "C#" : "Db";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
noteText = "D";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
noteText = sharps ? "D#" : "Eb";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7:
|
|
||||||
noteText = "E";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
noteText = "F";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 9:
|
|
||||||
noteText = sharps ? "F#" : "Gb";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 10:
|
|
||||||
noteText = "G";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 11:
|
|
||||||
noteText = sharps ? "G#" : "Ab";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showOctave)
|
|
||||||
noteText += " " + octave.ToString();
|
|
||||||
|
|
||||||
return noteText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,431 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks pitch
|
|
||||||
/// </summary>
|
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
public class PitchTracker
|
|
||||||
{
|
|
||||||
private const int kOctaveSteps = 96;
|
|
||||||
private const int kStepOverlap = 4;
|
|
||||||
private const float kMinFreq = 50.0f; // A1, Midi note 33, 55.0Hz
|
|
||||||
private const float kMaxFreq = 1800.0f; // A#6. Midi note 92
|
|
||||||
private const int kStartCircular = 40; // how far into the sample buffer do we start checking (allow for filter settling)
|
|
||||||
private const float kDetectOverlapSec = 0.005f;
|
|
||||||
private const float kMaxOctaveSecRate = 10.0f;
|
|
||||||
|
|
||||||
private const float kAvgOffset = 0.005f; // time offset between pitch averaging values
|
|
||||||
private const int kAvgCount = 1; // number of average pitch samples to take
|
|
||||||
private const float kCircularBufSaveTime = 1.0f; // Amount of samples to store in the history buffer
|
|
||||||
|
|
||||||
private PitchDsp m_dsp;
|
|
||||||
private CircularBuffer<float> m_circularBufferLo;
|
|
||||||
private CircularBuffer<float> m_circularBufferHi;
|
|
||||||
private double m_sampleRate;
|
|
||||||
private float m_detectLevelThreshold = 0.01f; // -40dB
|
|
||||||
private int m_pitchRecordsPerSecond = 50; // default is 50, or one record every 20ms
|
|
||||||
|
|
||||||
private float[] m_pitchBufLo;
|
|
||||||
private float[] m_pitchBufHi;
|
|
||||||
private int m_pitchBufSize;
|
|
||||||
private int m_samplesPerPitchBlock;
|
|
||||||
private int m_curPitchIndex;
|
|
||||||
private long m_curPitchSamplePos;
|
|
||||||
|
|
||||||
private int m_detectOverlapSamples;
|
|
||||||
private float m_maxOverlapDiff;
|
|
||||||
|
|
||||||
private bool m_recordPitchRecords;
|
|
||||||
private int m_pitchRecordHistorySize;
|
|
||||||
private List<PitchRecord> m_pitchRecords = new List<PitchRecord>();
|
|
||||||
private PitchRecord m_curPitchRecord = new PitchRecord();
|
|
||||||
|
|
||||||
private IIRFilter m_iirFilterLoLo;
|
|
||||||
private IIRFilter m_iirFilterLoHi;
|
|
||||||
private IIRFilter m_iirFilterHiLo;
|
|
||||||
private IIRFilter m_iirFilterHiHi;
|
|
||||||
|
|
||||||
public float LastMaxVol = 0;
|
|
||||||
|
|
||||||
public delegate void PitchDetectedHandler(PitchTracker sender, PitchRecord pitchRecord);
|
|
||||||
|
|
||||||
public event PitchDetectedHandler PitchDetected;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public PitchTracker() {}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the sample rate
|
|
||||||
/// </summary>
|
|
||||||
public double SampleRate
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (m_sampleRate == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_sampleRate = value;
|
|
||||||
Setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the detect level threshold, The value must be between 0.0001f and 1.0f (-80 dB to 0 dB)
|
|
||||||
/// </summary>
|
|
||||||
public float DetectLevelThreshold
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
float newValue = Math.Max(0.0001f, Math.Min(1.0f, value));
|
|
||||||
|
|
||||||
if (m_detectLevelThreshold == newValue)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_detectLevelThreshold = newValue;
|
|
||||||
//Setup();
|
|
||||||
}
|
|
||||||
get { return m_detectLevelThreshold; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the samples per pitch block
|
|
||||||
/// </summary>
|
|
||||||
public int SamplesPerPitchBlock
|
|
||||||
{
|
|
||||||
get { return m_samplesPerPitchBlock; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or set the number of pitch records per second (default is 50, or one record every 20ms)
|
|
||||||
/// </summary>
|
|
||||||
public int PitchRecordsPerSecond
|
|
||||||
{
|
|
||||||
get { return m_pitchRecordsPerSecond; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_pitchRecordsPerSecond = Math.Max(1, Math.Min(100, value));
|
|
||||||
Setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or set whether pitch records should be recorded into a history buffer
|
|
||||||
/// </summary>
|
|
||||||
public bool RecordPitchRecords
|
|
||||||
{
|
|
||||||
get { return m_recordPitchRecords; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (m_recordPitchRecords == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_recordPitchRecords = value;
|
|
||||||
|
|
||||||
if (!m_recordPitchRecords)
|
|
||||||
m_pitchRecords = new List<PitchRecord>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or set the max number of pitch records to keep. A value of 0 means no limit.
|
|
||||||
/// Don't leave this at 0 when RecordPitchRecords is true and this is used in a realtime
|
|
||||||
/// application since the buffer will grow indefinately!
|
|
||||||
/// </summary>
|
|
||||||
public int PitchRecordHistorySize
|
|
||||||
{
|
|
||||||
get { return m_pitchRecordHistorySize; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
m_pitchRecordHistorySize = value;
|
|
||||||
|
|
||||||
m_pitchRecords.Capacity = m_pitchRecordHistorySize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the current pitch records
|
|
||||||
/// </summary>
|
|
||||||
public IList PitchRecords
|
|
||||||
{
|
|
||||||
get { return m_pitchRecords.AsReadOnly(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the latest pitch record
|
|
||||||
/// </summary>
|
|
||||||
public PitchRecord CurrentPitchRecord
|
|
||||||
{
|
|
||||||
get { return m_curPitchRecord; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the current pitch position
|
|
||||||
/// </summary>
|
|
||||||
public long CurrentPitchSamplePosition
|
|
||||||
{
|
|
||||||
get { return m_curPitchSamplePos; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the minimum frequency that can be detected
|
|
||||||
/// </summary>
|
|
||||||
public static float MinDetectFrequency
|
|
||||||
{
|
|
||||||
get { return kMinFreq; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the maximum frequency that can be detected
|
|
||||||
/// </summary>
|
|
||||||
public static float MaxDetectFrequency
|
|
||||||
{
|
|
||||||
get { return kMaxFreq; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the frequency step
|
|
||||||
/// </summary>
|
|
||||||
public static double FrequencyStep
|
|
||||||
{
|
|
||||||
get { return Math.Pow(2.0, 1.0 / kOctaveSteps); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the number of samples that the detected pitch is offset from the input buffer.
|
|
||||||
/// This is just an estimate to sync up the samples and detected pitch
|
|
||||||
/// </summary>
|
|
||||||
public int DetectSampleOffset
|
|
||||||
{
|
|
||||||
get { return (m_pitchBufSize + m_detectOverlapSamples) / 2; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reset the pitch tracker. Call this when the sample position is
|
|
||||||
/// not consecutive from the previous position
|
|
||||||
/// </summary>
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
m_curPitchIndex = 0;
|
|
||||||
m_curPitchSamplePos = 0;
|
|
||||||
m_pitchRecords.Clear();
|
|
||||||
m_iirFilterLoLo.Reset();
|
|
||||||
m_iirFilterLoHi.Reset();
|
|
||||||
m_iirFilterHiLo.Reset();
|
|
||||||
m_iirFilterHiHi.Reset();
|
|
||||||
m_circularBufferLo.Reset();
|
|
||||||
m_circularBufferLo.Clear();
|
|
||||||
m_circularBufferHi.Reset();
|
|
||||||
m_circularBufferHi.Clear();
|
|
||||||
m_pitchBufLo.Clear();
|
|
||||||
m_pitchBufHi.Clear();
|
|
||||||
|
|
||||||
m_circularBufferLo.StartPosition = -m_detectOverlapSamples;
|
|
||||||
m_circularBufferLo.Available = m_detectOverlapSamples;
|
|
||||||
m_circularBufferHi.StartPosition = -m_detectOverlapSamples;
|
|
||||||
m_circularBufferHi.Available = m_detectOverlapSamples;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process the passed in buffer of data. During this call, the PitchDetected event will
|
|
||||||
/// be fired zero or more times, depending how many pitch records will fit in the new
|
|
||||||
/// and previously cached buffer.
|
|
||||||
///
|
|
||||||
/// This means that there is no size restriction on the buffer that is passed into ProcessBuffer.
|
|
||||||
/// For instance, ProcessBuffer can be called with one very large buffer that contains all of the
|
|
||||||
/// audio to be processed (many PitchDetected events will be fired), or just a small buffer at
|
|
||||||
/// a time which is more typical for realtime applications. In the latter case, the PitchDetected
|
|
||||||
/// event might not be fired at all since additional calls must first be made to accumulate enough
|
|
||||||
/// data do another pitch detect operation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inBuffer">Input buffer. Samples must be in the range -1.0 to 1.0</param>
|
|
||||||
/// <param name="maxVolume"></param>
|
|
||||||
/// <param name="sampleCount">Number of samples to process. Zero means all samples in the buffer</param>
|
|
||||||
public void ProcessBuffer(float[] inBuffer, int sampleCount = 0)
|
|
||||||
{
|
|
||||||
if (inBuffer == null)
|
|
||||||
throw new ArgumentNullException("inBuffer", "Input buffer cannot be null");
|
|
||||||
|
|
||||||
int samplesProcessed = 0;
|
|
||||||
int srcLength = sampleCount == 0 ? inBuffer.Length : Math.Min(sampleCount, inBuffer.Length);
|
|
||||||
|
|
||||||
float maxVolume = LastMaxVol * 0.85f;
|
|
||||||
|
|
||||||
while (samplesProcessed < srcLength)
|
|
||||||
{
|
|
||||||
int frameCount = Math.Min(srcLength - samplesProcessed, m_pitchBufSize + m_detectOverlapSamples);
|
|
||||||
|
|
||||||
m_iirFilterLoLo.FilterBuffer(inBuffer, samplesProcessed, m_pitchBufLo, 0, frameCount);
|
|
||||||
m_iirFilterLoHi.FilterBuffer(m_pitchBufLo, 0, m_pitchBufLo, 0, frameCount);
|
|
||||||
|
|
||||||
m_iirFilterHiLo.FilterBuffer(inBuffer, samplesProcessed, m_pitchBufHi, 0, frameCount);
|
|
||||||
m_iirFilterHiHi.FilterBuffer(m_pitchBufHi, 0, m_pitchBufHi, 0, frameCount);
|
|
||||||
|
|
||||||
m_circularBufferLo.WriteBuffer(m_pitchBufLo, frameCount);
|
|
||||||
m_circularBufferHi.WriteBuffer(m_pitchBufHi, frameCount);
|
|
||||||
|
|
||||||
// Loop while there is enough samples in the circular buffer
|
|
||||||
while (m_circularBufferLo.ReadBuffer(m_pitchBufLo, m_curPitchSamplePos, m_pitchBufSize + m_detectOverlapSamples))
|
|
||||||
{
|
|
||||||
float pitch1;
|
|
||||||
float pitch2;
|
|
||||||
float detectedPitch = 0.0f;
|
|
||||||
|
|
||||||
m_circularBufferHi.ReadBuffer(m_pitchBufHi, m_curPitchSamplePos, m_pitchBufSize + m_detectOverlapSamples);
|
|
||||||
|
|
||||||
maxVolume = 0f;
|
|
||||||
for (int i = 0; i < (m_pitchBufSize + m_detectOverlapSamples) / 2; i++)
|
|
||||||
{
|
|
||||||
if (maxVolume < m_pitchBufHi[i])
|
|
||||||
maxVolume = m_pitchBufHi[i];
|
|
||||||
if (maxVolume < m_pitchBufLo[i])
|
|
||||||
maxVolume = m_pitchBufLo[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
pitch1 = (maxVolume >= m_detectLevelThreshold) ? m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi) : 0f;
|
|
||||||
|
|
||||||
if (pitch1 > 0.0f)
|
|
||||||
{
|
|
||||||
// Shift the buffers left by the overlapping amount
|
|
||||||
m_pitchBufLo.Copy(m_pitchBufLo, m_detectOverlapSamples - 1, 0, m_pitchBufSize);
|
|
||||||
m_pitchBufHi.Copy(m_pitchBufHi, m_detectOverlapSamples - 1, 0, m_pitchBufSize);
|
|
||||||
|
|
||||||
pitch2 = m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi);
|
|
||||||
|
|
||||||
if (pitch2 > 0.0f)
|
|
||||||
{
|
|
||||||
float fDiff = Math.Max(pitch1, pitch2) / Math.Min(pitch1, pitch2) - 1.0f;
|
|
||||||
|
|
||||||
if (fDiff < m_maxOverlapDiff)
|
|
||||||
detectedPitch = (pitch1 + pitch2) * 0.5f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the pitch record
|
|
||||||
AddPitchRecord(detectedPitch);
|
|
||||||
|
|
||||||
m_curPitchSamplePos += m_samplesPerPitchBlock;
|
|
||||||
m_curPitchIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
samplesProcessed += frameCount;
|
|
||||||
}
|
|
||||||
LastMaxVol = maxVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setup
|
|
||||||
/// </summary>
|
|
||||||
private void Setup()
|
|
||||||
{
|
|
||||||
if (m_sampleRate < 1.0f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_dsp = new PitchDsp(m_sampleRate, kMinFreq, kMaxFreq);
|
|
||||||
|
|
||||||
m_iirFilterLoLo = new IIRFilter();
|
|
||||||
m_iirFilterLoLo.Proto = IIRFilter.ProtoType.Butterworth;
|
|
||||||
m_iirFilterLoLo.Type = IIRFilter.FilterType.HP;
|
|
||||||
m_iirFilterLoLo.Order = 5;
|
|
||||||
m_iirFilterLoLo.FreqLow = 45.0f;
|
|
||||||
m_iirFilterLoLo.SampleRate = (float)m_sampleRate;
|
|
||||||
|
|
||||||
m_iirFilterLoHi = new IIRFilter();
|
|
||||||
m_iirFilterLoHi.Proto = IIRFilter.ProtoType.Butterworth;
|
|
||||||
m_iirFilterLoHi.Type = IIRFilter.FilterType.LP;
|
|
||||||
m_iirFilterLoHi.Order = 5;
|
|
||||||
m_iirFilterLoHi.FreqHigh = 280.0f;
|
|
||||||
m_iirFilterLoHi.SampleRate = (float)m_sampleRate;
|
|
||||||
|
|
||||||
m_iirFilterHiLo = new IIRFilter();
|
|
||||||
m_iirFilterHiLo.Proto = IIRFilter.ProtoType.Butterworth;
|
|
||||||
m_iirFilterHiLo.Type = IIRFilter.FilterType.HP;
|
|
||||||
m_iirFilterHiLo.Order = 5;
|
|
||||||
m_iirFilterHiLo.FreqLow = 45.0f;
|
|
||||||
m_iirFilterHiLo.SampleRate = (float)m_sampleRate;
|
|
||||||
|
|
||||||
m_iirFilterHiHi = new IIRFilter();
|
|
||||||
m_iirFilterHiHi.Proto = IIRFilter.ProtoType.Butterworth;
|
|
||||||
m_iirFilterHiHi.Type = IIRFilter.FilterType.LP;
|
|
||||||
m_iirFilterHiHi.Order = 5;
|
|
||||||
m_iirFilterHiHi.FreqHigh = 2000.0f;
|
|
||||||
m_iirFilterHiHi.SampleRate = (float)m_sampleRate;
|
|
||||||
|
|
||||||
m_detectOverlapSamples = (int)(kDetectOverlapSec * m_sampleRate);
|
|
||||||
m_maxOverlapDiff = kMaxOctaveSecRate * kDetectOverlapSec;
|
|
||||||
|
|
||||||
m_pitchBufSize = (int)(((1.0f / (float)kMinFreq) * 2.0f + ((kAvgCount - 1) * kAvgOffset)) * m_sampleRate) + 16;
|
|
||||||
m_pitchBufLo = new float[m_pitchBufSize + m_detectOverlapSamples];
|
|
||||||
m_pitchBufHi = new float[m_pitchBufSize + m_detectOverlapSamples];
|
|
||||||
m_samplesPerPitchBlock = (int)Math.Round(m_sampleRate / m_pitchRecordsPerSecond);
|
|
||||||
|
|
||||||
m_circularBufferLo = new CircularBuffer<float>((int)(kCircularBufSaveTime * m_sampleRate + 0.5f) + 10000);
|
|
||||||
m_circularBufferHi = new CircularBuffer<float>((int)(kCircularBufSaveTime * m_sampleRate + 0.5f) + 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The pitch was detected - add the record
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pitch"></param>
|
|
||||||
private void AddPitchRecord(float pitch)
|
|
||||||
{
|
|
||||||
int midiNote = 0;
|
|
||||||
int midiCents = 0;
|
|
||||||
|
|
||||||
PitchDsp.PitchToMidiNote(pitch, out midiNote, out midiCents);
|
|
||||||
|
|
||||||
var record = new PitchRecord();
|
|
||||||
|
|
||||||
record.RecordIndex = m_curPitchIndex;
|
|
||||||
record.Pitch = pitch;
|
|
||||||
record.MidiNote = midiNote;
|
|
||||||
record.MidiCents = midiCents;
|
|
||||||
|
|
||||||
m_curPitchRecord = record;
|
|
||||||
|
|
||||||
if (m_recordPitchRecords)
|
|
||||||
{
|
|
||||||
if (m_pitchRecordHistorySize > 0 && m_pitchRecords.Count >= m_pitchRecordHistorySize)
|
|
||||||
m_pitchRecords.RemoveAt(0);
|
|
||||||
|
|
||||||
m_pitchRecords.Add(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.PitchDetected != null)
|
|
||||||
this.PitchDetected(this, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores one record
|
|
||||||
/// </summary>
|
|
||||||
public struct PitchRecord
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The index of the pitch record since the last Reset call
|
|
||||||
/// </summary>
|
|
||||||
public int RecordIndex { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The detected pitch
|
|
||||||
/// </summary>
|
|
||||||
public float Pitch { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The detected MIDI note, or 0 for no pitch
|
|
||||||
/// </summary>
|
|
||||||
public int MidiNote { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The offset from the detected MIDI note in cents, from -50 to +50.
|
|
||||||
/// </summary>
|
|
||||||
public int MidiCents { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper restore InconsistentNaming
|
|
||||||
}
|
|
||||||
@@ -292,13 +292,7 @@
|
|||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPitchTrackerTest.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPitchTrackerTest.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtAKF.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPtAKF.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtDyWa.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPtDyWa.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtSharp.cs" />
|
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\IPitchTracker.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\IPitchTracker.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\CircularBuffer.cs" />
|
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\Extensions.cs" />
|
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\IIRFilter.cs" />
|
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\PitchDsp.cs" />
|
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\PitchTracker.cs" />
|
|
||||||
<Compile Include="Lib\Video\Acinerella\CDecoder.cs" />
|
<Compile Include="Lib\Video\Acinerella\CDecoder.cs" />
|
||||||
<Compile Include="Lib\Video\Acinerella\CDecoderThread.cs" />
|
<Compile Include="Lib\Video\Acinerella\CDecoderThread.cs" />
|
||||||
<Compile Include="Lib\Video\Acinerella\CFramebuffer.cs" />
|
<Compile Include="Lib\Video\Acinerella\CFramebuffer.cs" />
|
||||||
|
|||||||
Reference in new issue
Block a user