Mercurial > hg > lowfreq
view LowFreq.cpp @ 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 |
line wrap: on
line source
#include "LowFreq.h" #include "dsp/rateconversion/Resampler.h" #include "dsp/transforms/FFT.h" #include <cmath> #include <cstdio> using std::cerr; using std::endl; using std::vector; 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; static int defaultN = 64; static float minOverlap = 0.0; static float maxOverlap = 87.5; static float overlapStep = 12.5; static float defaultOverlap = 50.0; LowFreq::LowFreq(float inputSampleRate) : Plugin(inputSampleRate), m_fmin(defaultFmin), m_fmax(defaultFmax), m_n(defaultN), m_overlap(defaultOverlap), m_blockSize(0), m_resampler(0), m_fft(0), m_window(0) { } LowFreq::~LowFreq() { delete m_resampler; delete m_fft; delete m_window; } string LowFreq::getIdentifier() const { return "lowfreq"; } string LowFreq::getName() const { return "Low-frequency Spectrogram"; } string LowFreq::getDescription() const { //!!! Return something helpful here! return ""; } string LowFreq::getMaker() const { return "Queen Mary, University of London"; } int LowFreq::getPluginVersion() const { return 1; } string LowFreq::getCopyright() const { return "GPL"; } LowFreq::InputDomain LowFreq::getInputDomain() const { return TimeDomain; } size_t LowFreq::getPreferredBlockSize() const { return 1024; } size_t LowFreq::getPreferredStepSize() const { return 1024; } size_t LowFreq::getMinChannelCount() const { return 1; } size_t LowFreq::getMaxChannelCount() const { return 1; } LowFreq::ParameterList LowFreq::getParameterDescriptors() const { ParameterList list; ParameterDescriptor d; 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 return."; d.unit = ""; d.minValue = minN; d.maxValue = maxN; d.defaultValue = defaultN; d.isQuantized = true; d.quantizeStep = 1; list.push_back(d); d.identifier = "overlap"; d.name = "Frame overlap"; d.description = "Amount of overlap between one Hann-windowed frame and the next. Should be at least 50% to avoid gaps due to windowing."; d.unit = "%"; d.minValue = minOverlap; d.maxValue = maxOverlap; d.defaultValue = defaultOverlap; d.isQuantized = true; d.quantizeStep = overlapStep; list.push_back(d); return list; } float LowFreq::getParameter(string identifier) const { if (identifier == "fmin") { return m_fmin; } else if (identifier == "fmax") { return m_fmax; } else if (identifier == "n") { return m_n; } else if (identifier == "overlap") { return m_overlap; } return 0; } void LowFreq::setParameter(string identifier, float 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") { m_overlap = value; } } LowFreq::ProgramList LowFreq::getPrograms() const { ProgramList list; return list; } string LowFreq::getCurrentProgram() const { return ""; // no programs } void LowFreq::selectProgram(string name) { } LowFreq::OutputList LowFreq::getOutputDescriptors() const { OutputList list; OutputDescriptor d; d.identifier = "spectrogram"; d.name = "Spectrogram"; d.description = ""; d.unit = ""; d.hasFixedBinCount = true; d.binCount = m_n/2 + 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::FixedSampleRate; d.sampleRate = float(getTargetSampleRate()) / float(getTargetStepSize()); char namebuf[50]; for (int i = 0; i <= m_n/2; ++i) { sprintf(namebuf, "%.3gHz", double(getOutputBinFrequency(i))); d.binNames.push_back(namebuf); } cerr << "output descriptor effective sample rate = " << d.sampleRate << endl; d.hasDuration = false; list.push_back(d); return list; } bool LowFreq::initialise(size_t channels, size_t stepSize, size_t blockSize) { if (channels < getMinChannelCount() || channels > getMaxChannelCount()) { cerr << "LowFreq::initialise: ERROR: channels " << channels << " out of acceptable range " << getMinChannelCount() << " -> " << getMaxChannelCount() << endl; return false; } if (stepSize != blockSize) { // We don't actually care what the block size is, but there // must be no overlap cerr << "LowFreq::initialise: ERROR: step size and block size must be " << "equal (" << stepSize << " != " << blockSize << ")" << endl; return false; } if (m_n < minN || m_n > maxN) { cerr << "LowFreq::initialise: ERROR: bin count " << m_n << " out of acceptable range " << minN << " -> " << maxN << endl; return false; } 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; } if (m_overlap < minOverlap || m_overlap > maxOverlap) { cerr << "LowFreq::initialise: ERROR: overlap " << m_overlap << " out of acceptable range " << minOverlap << " -> " << maxOverlap << endl; return false; } if (fabs(m_inputSampleRate - round(m_inputSampleRate)) > 1e-6) { cerr << "LowFreq::initialise: WARNING: input sample rate " << m_inputSampleRate << " is non-integral, output frequencies " << "will be skewed by rounding it to nearest integer" << endl; } m_blockSize = blockSize; reset(); return true; } void LowFreq::reset() { delete m_resampler; delete m_fft; delete m_window; // 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(getFFTSize()); m_window = new Window<double>(HanningWindow, getFFTSize()); m_buffer = std::vector<double>(); 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 " << 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 LowFreq::process(const float *const *inputBuffers, Vamp::RealTime timestamp) { double *data = new double[m_blockSize]; for (int i = 0; i < m_blockSize; ++i) { data[i] = inputBuffers[0][i]; } vector<double> resampled = m_resampler->process(data, m_blockSize); m_buffer.insert(m_buffer.end(), resampled.begin(), resampled.end()); delete[] data; FeatureSet fs; while (int(m_buffer.size()) >= getFFTSize()) { Feature f = processColumn(); fs[0].push_back(f); advance(); } return fs; } LowFreq::FeatureSet LowFreq::getRemainingFeatures() { FeatureSet fs; while (!m_buffer.empty() && int(m_buffer.size()) < m_n) { m_buffer.push_back(0.0); } while (int(m_buffer.size()) >= getFFTSize()) { Feature f = processColumn(); fs[0].push_back(f); advance(); } return fs; } LowFreq::Feature LowFreq::processColumn() { Feature f; int sz = getFFTSize(); double *realOut = new double[sz]; double *imagOut = new double[sz]; 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) { int ix = getFirstOutputBin() + i; f.values.push_back(realOut[ix] * realOut[ix] + imagOut[ix] * imagOut[ix]); } return f; } void LowFreq::advance() { cerr << "step = " << getTargetStepSize() << endl; std::vector<double> advanced(m_buffer.data() + getTargetStepSize(), m_buffer.data() + m_buffer.size()); m_buffer = advanced; }