view src/PitchFilterbank.cpp @ 10:fa87ce20fe8c

Wire up a working plugin. No resampler/filter latency compensation yet, and the code is messy.
author Chris Cannam
date Fri, 14 Aug 2015 17:23:56 +0100
parents f440b0e2192b
children 7518c113bd03
line wrap: on
line source

#include "PitchFilterbank.h"

#include "Resampler.h"
#include "Filter.h"

#include "delays.h"
#include "filter-a.h"
#include "filter-b.h"

#include <stdexcept>

using namespace std;

static const int HIGHEST_FILTER_INDEX_AT_882 = 38;   // MIDI pitch 59
static const int HIGHEST_FILTER_INDEX_AT_4410 = 74;  // MIDI pitch 95
static const int HIGHEST_FILTER_INDEX_AT_22050 = 87; // MIDI pitch 108
static const int HIGHEST_FILTER_INDEX = HIGHEST_FILTER_INDEX_AT_22050;

class PitchFilterbank::D
{
public:
    D(int sampleRate) :
	m_sampleRate(sampleRate),
	m_to22050(sampleRate, 22050),
	m_to4410(sampleRate, 4410),
	m_to882(sampleRate, 882)
    {
	for (int i = 0; i <= HIGHEST_FILTER_INDEX; ++i) {
	    int ix = i + 20;
	    int coeffs = sizeof(filter_a[0]) / sizeof(filter_a[0][0]);
	    vector<double> a(filter_a[ix], filter_a[ix] + coeffs);
	    vector<double> b(filter_b[ix], filter_b[ix] + coeffs);
	    m_filters.push_back(new Filter({ a, b }));
	}
    }
	
    ~D() {
	for (int i = 0; i <= HIGHEST_FILTER_INDEX; ++i) {
	    delete m_filters[i];
	}
    }

    int getSampleRate() const { return m_sampleRate; }
    
    /// A series of real-valued samples ordered in time.
    typedef vector<double> RealSequence;

    /// A series of real-valued samples ordered by bin (frequency or similar).
    typedef vector<double> RealColumn;

    /// A matrix of real-valued samples, indexed by time then bin number.
    typedef vector<RealColumn> RealBlock;

    RealBlock process(const RealSequence &in) {

	m_at22050 = m_to22050.process(in.data(), in.size());
	m_at4410  =  m_to4410.process(in.data(), in.size());
	m_at882   =   m_to882.process(in.data(), in.size());

	for (int i = 0; i <= HIGHEST_FILTER_INDEX; ++i) {

	    if (i <= HIGHEST_FILTER_INDEX_AT_882) {
		pushFiltered(i, m_at882);
	    } else if (i <= HIGHEST_FILTER_INDEX_AT_4410) {
		pushFiltered(i, m_at4410);
	    } else {
		pushFiltered(i, m_at22050);
	    }
	}

	//!!! todo make this known through api. these values are at 22050Hz
	int windowSize = 4410;

	RealBlock energies(HIGHEST_FILTER_INDEX + 1);
	int cols = 0;
	
	while (m_filtered[HIGHEST_FILTER_INDEX].size() >= unsigned(windowSize)) {
	    //!!! Quite inefficient -- we're counting everything
	    //!!! twice. Since there is no actual window shape, isn't
	    //!!! the overlap just averaging?
	    for (int i = 0; i <= HIGHEST_FILTER_INDEX; ++i) {
		int n = windowSize;
		double factor = 1.0;
		if (i <= HIGHEST_FILTER_INDEX_AT_882) {
		    factor = 22050.0 / 882.0;
		} else if (i <= HIGHEST_FILTER_INDEX_AT_4410) {
		    factor = 22050.0 / 4410.0;
		}
		//!!! Problem -- this is not an integer, for
		//!!! fs=882 (it's 176.4)
		n = n / factor;
		double energy = calculateEnergy(m_filtered[i], n, factor);
		energies[i].push_back(energy);
		m_filtered[i] =
		    RealSequence(m_filtered[i].begin() + n/2, m_filtered[i].end());
	    }
	    ++cols;
	}

	RealBlock out(cols);
	for (int j = 0; j < cols; ++j) {
	    for (int i = 0; i <= HIGHEST_FILTER_INDEX; ++i) {
		out[j].push_back(energies[i][j]);
	    }
	}

	return out;
    }

    RealBlock getRemainingOutput() {
	//!!! for now! but we do have some buffered
	return RealBlock();
    }
    
    void pushFiltered(int i, const RealSequence &resampled) {
	int n = resampled.size();
	RealSequence filtered(n, 0.0);
	m_filters[i]->process(resampled.data(), filtered.data(), n);
	m_filtered[i].insert(m_filtered[i].end(), filtered.begin(), filtered.end());
    }	

    double calculateEnergy(const RealSequence &seq, int n, double factor) {
	double energy = 0.0;
	for (int i = 0; i < n; ++i) {
	    energy += seq[i] * seq[i] * factor;
	}
	return energy;
    }
    
private:
    int m_sampleRate;
    
    Resampler m_to22050;
    Resampler m_to4410;
    Resampler m_to882;

    RealSequence m_at22050;
    RealSequence m_at4410;
    RealSequence m_at882;

    RealSequence m_filtered[HIGHEST_FILTER_INDEX + 1];
    
    Resampler &resamplerFor(int filterIndex) {
	if (filterIndex < 0) {
	    throw std::logic_error("Filter index is negative");
	} else if (filterIndex <= HIGHEST_FILTER_INDEX_AT_882) {
	    return m_to882;
	} else if (filterIndex <= HIGHEST_FILTER_INDEX_AT_4410) {
	    return m_to4410;
	} else if (filterIndex <= HIGHEST_FILTER_INDEX_AT_22050) {
	    return m_to22050;
	} else {
	    throw std::logic_error("Filter index out of expected range");
	}
    }

    // This vector is initialised with 88 filter instances.
    // m_filters[n] (for n from 0 to 87) is for MIDI pitch 21+n, so we
    // span the MIDI range from pitch 21 to 108. Then m_filters[n] has
    // coefficients drawn from filter_a[20+n] and filter_b[20+n], and
    // has effective delay filter_delay[20+n].
    vector<Filter *> m_filters;

    int filterIndexForMidiPitch(int pitch) const {
	return pitch - 21;
    }
    int filterDelay(int filterIndex) const {
	return filter_delay[20 + filterIndex];
    }
    int totalDelay(int filterIndex) const {
	return filterDelay(filterIndex) +
	    const_cast<D *>(this)->resamplerFor(filterIndex).getLatency();
    }
};

PitchFilterbank::PitchFilterbank(int sampleRate) :
    m_d(new D(sampleRate))
{
}

PitchFilterbank::~PitchFilterbank()
{
    delete m_d;
}

void
PitchFilterbank::reset()
{
    int rate = m_d->getSampleRate();
    delete m_d;
    m_d = new D(rate);
}

PitchFilterbank::RealBlock
PitchFilterbank::process(const RealSequence &in)
{
    return m_d->process(in);
}

PitchFilterbank::RealBlock
PitchFilterbank::getRemainingOutput()
{
    return m_d->getRemainingOutput();
}