// Pipes that convolve with a kernel function to act as a finite impulse // response filter, and classes to define such kernel functions. #include "alfe/main.h" #ifndef INCLUDED_CONVOLUTION_PIPE_H #define INCLUDED_CONVOLUTION_PIPE_H #include "alfe/pipes.h" #include "alfe/gcd.h" #include "float.h" class ProductKernel; // A convolution kernel is indexed in such a way that the input samples // correspond to integer input values. class ConvolutionKernel : private Handle { public: class Body : public Handle::Body { public: virtual double operator()(double x) const = 0; virtual double leftExtent() const { return -DBL_MAX; } virtual double rightExtent() const { return DBL_MAX; } }; ConvolutionKernel(Body* body) : Handle(body) { } double operator()(double x) const { return (body()->operator())(x); } double leftExtent() const { return _body->leftExtent(); } double rightExtent() const { return _body->rightExtent(); } ConvolutionKernel& operator*=(const ConvolutionKernel& right) { _body = new ProductKernel(*this, right); } ConvolutionKernel& operator+=(const ConvolutionKernel& right) { _body = new SumKernel(*this, right); } ConvolutionKernel& operator-=(const ConvolutionKernel& right) { _body = new SumKernel(*this, -right); } ConvolutionKernel operator-() const { return ProductKernel(*this, ConstantKernel()); } ConvolutionKernel operator*(const ConvolutionKernel& right) { ConvolutionKernel k = *this; k *= right; return k; } ConvolutionKernel operator+(const ConvolutionKernel& right) { ConvolutionKernel k = *this; k += right; return k; } ConvolutionKernel operator-(const ConvolutionKernel& right) { ConvolutionKernel k = *this; k -= right; return k; } }; // Sinc filter corresponds to perfect band-limited interpolation - it removes // all frequencies higher than the sample rate (if x is measured in samples). class SincFilter : public ConvolutionKernel { public: SincFilter() : ConvolutionKernel(new Body) { } class Body : public ConvolutionKernel::Body { virtual double operator()(double x) const { return sin(M_PI*x)/(M_PI*x); } }; }; class RectangleWindow : public ConvolutionKernel { public: RectangleWindow(double semiWidth) : ConvolutionKernel(new Body(semiWidth)) { } class Body : public ConvolutionKernel::Body { public: Body(double semiWidth) : _semiWidth(semiWidth) { } virtual double operator()(double x) const { return 1; } virtual double leftExtent() const { return -_semiWidth; } virtual double rightExtent() const { return _semiWidth; } private: double _semiWidth; }; }; class ScaledFilter : public ConvolutionKernel { public: ScaledFilter(ConvolutionKernel kernel, double scale) : ConvolutionKernel(new Body(kernel, scale)) { } class Body : public ConvolutionKernel::Body { public: Body(ConvolutionKernel kernel, double scale) : _kernel(kernel), _scale(scale) { } virtual double operator()(double x) const { return _kernel(x / _scale); } virtual double leftExtent() const { return _kernel.leftExtent() * _scale; } virtual double rightExtent() const { return _kernel.rightExtent() * _scale; } private: ConvolutionKernel _kernel; double _scale; }; }; class ProductKernel : public ConvolutionKernel { public: ProductKernel(ConvolutionKernel a, ConvolutionKernel b) : ConvolutionKernel(new Body(a, b)) { } class Body : public ConvolutionKernel::Body { public: Body(ConvolutionKernel a, ConvolutionKernel b) : _a(a), _b(b) { } virtual double operator()(double x) const { return _a(x)*_b(x); } virtual double leftExtent() const { return max(_a.leftExtent(), _b.leftExtent()); } virtual double rightExtent() const { return min(_a.rightExtent(), _b.rightExtent()); } private: ConvolutionKernel _a; ConvolutionKernel _b; }; }; class SumKernel : public ConvolutionKernel { public: SumKernel(ConvolutionKernel a, ConvolutionKernel b) : ConvolutionKernel(new Body(a, b)) { } class Body : public ConvolutionKernel::Body { public: Body(ConvolutionKernel a, ConvolutionKernel b) : _a(a), _b(b) { } virtual double operator()(double x) const { return _a(x)+_b(x); } virtual double leftExtent() const { return min(_a.leftExtent(), _b.leftExtent()); } virtual double rightExtent() const { return max(_a.rightExtent(), _b.rightExtent()); } private: ConvolutionKernel _a; ConvolutionKernel _b; }; }; class ConstantKernel : public ConvolutionKernel { public: ConstantKernel(double c) : ConvolutionKernel(new Body(c)) { } class Body : public ConvolutionKernel::Body { public: Body(double c) : _c(c) { } virtual double operator()(double) const { return c; } private: double _c; }; }; // A convolution pipe which computes kernel coefficients as they are needed, // which is likely to be very slow. The kernel is measured in output samples // which is the appropriate default for downsampling. For upsampling, scale // the template class SmallConvolutionPipe : public Pipe > { public: SmallConvolutionPipe(Rate producerRate, Rate consumerRate, ConvolutionKernel kernel, int n = defaultSampleCount) : Pipe(this, n), _kernel(kernel), _t(0), _r(static_cast(producerRate)/consumerRate), _le(kernel.leftExtent()), _re(kernel.rightExtent()), _maxRead((n + (_re - _le))/_r) { } void produce(int n) { Accessor reader = _sink.reader(_maxRead); Accessor writer = _source.writer(n); int read = 0; for (int i = 0; i < n; ++i) { T sample = 0; int j = read; for (double k = _t + _le; k < _re; k += _r) { sample += reader.item(j)*_kernel(k); ++j; } writer.item() = sample; int r = static_cast((_r + 1.0 - _t)/_r); _t += r*_r; read += r; } _sink.read(read); _source.written(n); } private: ConvolutionKernel _kernel; double _t; double _r; int _maxRead; // Maximum number of samples that are read at once double _le; double _re; }; // A convolution pipe that stores the kernel coefficients it requires. This // may use a lot of memory if the producing and consuming rates have a high // lowest common multiple, and it can't be used if the resampling factor // changes. template class LCMConvolutionPipe : public Pipe > { public: // For every "consumerRate" samples consumed we will produce "producerRate" // samples. LCMConvolutionPipe(Rate producerRate, Rate consumerRate, ConvolutionKernel kernel, int n = defaultSampleCount) : Pipe(this, n), _t(0) { Rate l = lcm(producerRate, consumerRate); // 275625000 int c = l/producerRate; // 6250 _delta = l/consumerRate; // 231 double le = kernel.leftExtent(); double re = kernel.rightExtent(); double extent = re-le; // 10 _maxRead = (n + extent)/_r; _kernelSize = extent*c; // TODO: rearrange kernel coefficients so that they are adjacent in // memory for better cache performance _kernel.allocate(_kernelSize); for (int i = 0; i < _kernelSize; ++i) _kernel[i] = kernel(static_cast(i)/c + le); } void produce(int n) { Accessor reader = _sink.reader(_maxRead); Accessor writer = _source.writer(n); int read = 0; for (int i = 0; i < n; ++i) { T sample = 0; int j = read; for (int k = _t; k < _kernelSize; k += _delta) { sample += reader.item(j)*_kernel[k]; ++j; } writer.item() = sample; int r = static_cast((c + _delta - _t)/_delta); _t += r*_delta; read += r; } _sink.read(read); _source.written(n); } private: // _kernel is indexed in units of 1/c of an output sample and offset by le // output samples Array _kernel; int _kernelSize; int _t; int _delta; int _t; }; // A convolution pipe that computes a fixed number of kernel coefficients, and // uses the closest one to the one required. template class NearestNeighborConvolutionPipe : public Pipe > { // TODO }; // A convolution pipe that computes a fixed number of kernel coefficients, and // uses linear interpolation to find the others. template class LinearConvolutionPipe : public Pipe > { }; // 985428/44100 == 492714/22050 == 246357/11025 == 82119/3675 == 27373/1225 #endif // INCLUDED_CONVOLUTION_PIPE_H