changeset 5:bf742ae09443

Frequency range, and resample to higher frequency to avoid transition band -- and some comments on what is still wrong with it
author Chris Cannam
date Mon, 10 Mar 2014 16:03:53 +0000
parents 9867e53a2592
children e502cf649389
files LowFreq.cpp LowFreq.h
diffstat 2 files changed, 126 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/LowFreq.cpp	Fri Mar 07 14:17:47 2014 +0000
+++ b/LowFreq.cpp	Mon Mar 10 16:03:53 2014 +0000
@@ -11,9 +11,13 @@
 using std::endl;
 using std::vector;
 
-static float minP = 0.01;
-static float maxP = 10;
-static float defaultP = 0.1;
+static float minFmin = 0;
+static float maxFmin = 500;
+static float defaultFmin = 0.1;
+
+static float minFmax = 0.1;
+static float maxFmax = 500;
+static float defaultFmax = 10;
 
 static int minN = 1;
 static int maxN = 16384;
@@ -26,7 +30,8 @@
 
 LowFreq::LowFreq(float inputSampleRate) :
     Plugin(inputSampleRate),
-    m_p(defaultP),
+    m_fmin(defaultFmin),
+    m_fmax(defaultFmax),
     m_n(defaultN),
     m_overlap(defaultOverlap),
     m_blockSize(0),
@@ -116,19 +121,29 @@
     ParameterList list;
 
     ParameterDescriptor d;
-    d.identifier = "p";
-    d.name = "Shortest period";
-    d.description = "Period in seconds of the highest-frequency component to include in the spectrogram. That is, 1/f where f is the highest frequency (in Hz) spanned by the spectrogram.";
-    d.unit = "s";
-    d.minValue = minP;
-    d.maxValue = maxP;
-    d.defaultValue = defaultP;
+    d.identifier = "fmin";
+    d.name = "Minimum frequency";
+    d.description = "Low bound for frequencies to include in the spectrogram. This will be rounded up to the next highest bin frequency based on the highest frequency and number of bins.";
+    d.unit = "Hz";
+    d.minValue = minFmin;
+    d.maxValue = maxFmin;
+    d.defaultValue = defaultFmin;
+    d.isQuantized = false;
+    list.push_back(d);
+
+    d.identifier = "fmax";
+    d.name = "Maximum frequency";
+    d.description = "Highest frequency component to include in the spectrogram.";
+    d.unit = "Hz";
+    d.minValue = minFmax;
+    d.maxValue = maxFmax;
+    d.defaultValue = defaultFmax;
     d.isQuantized = false;
     list.push_back(d);
 
     d.identifier = "n";
     d.name = "Number of bins";
-    d.description = "Number of spectrogram bins to calculate.";
+    d.description = "Number of spectrogram bins to return.";
     d.unit = "";
     d.minValue = minN;
     d.maxValue = maxN;
@@ -154,8 +169,10 @@
 float
 LowFreq::getParameter(string identifier) const
 {
-    if (identifier == "p") {
-	return m_p;
+    if (identifier == "fmin") {
+	return m_fmin;
+    } else if (identifier == "fmax") {
+	return m_fmax;
     } else if (identifier == "n") {
 	return m_n;
     } else if (identifier == "overlap") {
@@ -167,8 +184,10 @@
 void
 LowFreq::setParameter(string identifier, float value) 
 {
-    if (identifier == "p") {
-	m_p = value;
+    if (identifier == "fmin") {
+	m_fmin = value;
+    } else if (identifier == "fmax") {
+	m_fmax = value;
     } else if (identifier == "n") {
 	m_n = int(value + 0.01);
     } else if (identifier == "overlap") {
@@ -194,18 +213,6 @@
 {
 }
 
-int
-LowFreq::getTargetSampleRate() const
-{
-    return int(ceil(2.0 / m_p));
-}
-
-int
-LowFreq::getTargetStepSize() const
-{
-    return int(round(m_n * (1.0 - (m_overlap / 100.0))));
-}    
-
 LowFreq::OutputList
 LowFreq::getOutputDescriptors() const
 {
@@ -227,7 +234,7 @@
     
     char namebuf[50];
     for (int i = 0; i <= m_n/2; ++i) {
-	sprintf(namebuf, "%.3gHz", double(getTargetSampleRate()) / m_n * i);
+	sprintf(namebuf, "%.3gHz", double(getOutputBinFrequency(i)));
 	d.binNames.push_back(namebuf);
     }
 
@@ -265,10 +272,17 @@
 	return false;
     }
 
-    if (m_p < minP || m_p > maxP) {
-	cerr << "LowFreq::initialise: ERROR: shortest period " << m_p
-	     << " out of acceptable range " << minP
-	     << " -> " << maxP << endl;
+    if (m_fmin < minFmin || m_fmin > maxFmin) {
+	cerr << "LowFreq::initialise: ERROR: lowest frequency " << m_fmin
+	     << " out of acceptable range " << minFmin
+	     << " -> " << maxFmin << endl;
+	return false;
+    }
+
+    if (m_fmax < minFmax || m_fmax > maxFmax) {
+	cerr << "LowFreq::initialise: ERROR: highest frequency " << m_fmax
+	     << " out of acceptable range " << minFmax
+	     << " -> " << maxFmax << endl;
 	return false;
     }
 
@@ -299,24 +313,79 @@
     delete m_fft;
     delete m_window;
 
-    // We have shortest period p and number of bins n.  The bin
-    // frequency for the highest bin of a spectrogram is fs/2 where fs
-    // is the sample rate of the signal. So we must downsample to a
-    // sample rate of (1/p)*2 and then run an n-point FFT.
+    // We want to return a number of bins n between frequencies fmin
+    // and fmax.
+
+    // The bin frequency for bin n of an n-point fft is fs (the sample
+    // rate), with the top half aliasing the bottom half. So in theory
+    // we can downsample to a sample rate of fmax*2 and then run an
+    // m-point FFT, where m is sufficient to give us n bins remaining
+    // between fmin and fmax. For m then we calculate fmax /
+    // ((fmax-fmin)/n).
+
+    // However, our resampler introduces artifacts close to its cutoff
+    // frequency within the filter transition band so we probably
+    // shouldn't aim to resample to exactly fmax*2. Instead we
+    // resample to fmax*4 and carry out an FFT of twice the expected
+    // number of bins.
 
     m_resampler = new Resampler(int(round(m_inputSampleRate)),
 				getTargetSampleRate());
-    m_fft = new FFT(m_n);
-    m_window = new Window<double>(HanningWindow, m_n);
+    m_fft = new FFT(getFFTSize());
+    m_window = new Window<double>(HanningWindow, getFFTSize());
     m_buffer = std::vector<double>();
 
-    cerr << "LowFreq::reset: min period " << m_p << " so max freq " << 1.0/m_p
-	 << endl;
+    cerr << "LowFreq::reset: freq range " << m_fmin << " to " << m_fmax << endl;
     cerr << "LowFreq::reset: block size " << m_blockSize
 	 << ", input sample rate " << m_inputSampleRate << endl;
     cerr << "LowFreq::reset: target rate " << getTargetSampleRate()
+	 << ", target step " << getTargetStepSize()
 	 << ", resampler latency " << m_resampler->getLatency()
-	 << ", fft size " << m_n << endl;
+	 << ", fft size " << getFFTSize() << endl;
+
+    //!!! output has wrong number of bins
+
+    //!!! not handling resampler latency
+
+    //!!! how do we know the fft size is long enough to capture the low frequencies we want?
+}
+
+int
+LowFreq::getTargetSampleRate() const
+{
+    // See notes in reset()
+    return int(ceil(m_fmax)) * 4;
+}
+
+int
+LowFreq::getTargetStepSize() const
+{
+    return int(round(m_n * (1.0 - (m_overlap / 100.0))));
+}    
+
+int
+LowFreq::getFFTSize() const
+{
+    int fftSize = 2 * int(ceil(m_fmax / ((m_fmax - m_fmin) / m_n)));
+    cerr << "LowFreq::getFFTSize: for range " << m_fmin << " -> " << m_fmax
+	 << " and n = " << m_n << ", fft size is " << fftSize << endl;
+    return fftSize;
+}
+
+int
+LowFreq::getFirstOutputBin() const
+{
+    int first = int(floor(m_fmin / getFFTSize()));
+    cerr << "LowFreq::getFirstOutputBin: for range " << m_fmin << " -> " << m_fmax
+	 << " and n = " << m_n << ", bin is " << first << endl;
+    return first;
+}
+
+float
+LowFreq::getOutputBinFrequency(int i) const
+{
+    return (float(getTargetSampleRate()) / getFFTSize())
+	* (i + getFirstOutputBin());
 }
 
 LowFreq::FeatureSet
@@ -334,7 +403,7 @@
 
     FeatureSet fs;
 
-    while (int(m_buffer.size()) >= m_n) {
+    while (int(m_buffer.size()) >= getFFTSize()) {
 	Feature f = processColumn();
 	fs[0].push_back(f);
 	advance();
@@ -352,7 +421,7 @@
 	m_buffer.push_back(0.0);
     }
 
-    while (int(m_buffer.size()) >= m_n) {
+    while (int(m_buffer.size()) >= getFFTSize()) {
 	Feature f = processColumn();
 	fs[0].push_back(f);
 	advance();
@@ -365,17 +434,21 @@
 LowFreq::processColumn()
 {
     Feature f;
+    
+    int sz = getFFTSize();
 
-    double *realOut = new double[m_n];
-    double *imagOut = new double[m_n];
+    double *realOut = new double[sz];
+    double *imagOut = new double[sz];
 
-    double *windowed = new double[m_n];
+    double *windowed = new double[sz];
     m_window->cut(m_buffer.data(), windowed);	
 
     m_fft->process(false, windowed, 0, realOut, imagOut);
 
     for (int i = 0; i <= m_n/2; ++i) {
-	f.values.push_back(realOut[i] * realOut[i] + imagOut[i] * imagOut[i]);
+	int ix = getFirstOutputBin() + i;
+	f.values.push_back(realOut[ix] * realOut[ix] +
+			   imagOut[ix] * imagOut[ix]);
     }
 
     return f;
--- a/LowFreq.h	Fri Mar 07 14:17:47 2014 +0000
+++ b/LowFreq.h	Mon Mar 10 16:03:53 2014 +0000
@@ -53,8 +53,12 @@
 
     int getTargetSampleRate() const;
     int getTargetStepSize() const;
+    int getFFTSize() const;
+    int getFirstOutputBin() const;
+    float getOutputBinFrequency(int i) const;
 
-    float m_p;
+    float m_fmin;
+    float m_fmax;
     int m_n;
     float m_overlap;