mirror of
https://github.com/dbalsom/x86_microcode.git
synced 2026-06-09 13:04:17 +03:00
312 lines
9.3 KiB
C++
312 lines
9.3 KiB
C++
// 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 T, class Rate = int> class SmallConvolutionPipe
|
|
: public Pipe<T, T, SmallConvolutionPipe<T, Rate> >
|
|
{
|
|
public:
|
|
SmallConvolutionPipe(Rate producerRate, Rate consumerRate,
|
|
ConvolutionKernel kernel, int n = defaultSampleCount)
|
|
: Pipe(this, n),
|
|
_kernel(kernel),
|
|
_t(0),
|
|
_r(static_cast<double>(producerRate)/consumerRate),
|
|
_le(kernel.leftExtent()),
|
|
_re(kernel.rightExtent()),
|
|
_maxRead((n + (_re - _le))/_r)
|
|
{ }
|
|
void produce(int n)
|
|
{
|
|
Accessor<T> reader = _sink.reader(_maxRead);
|
|
Accessor<T> 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<int>((_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 T, class Rate = int> class LCMConvolutionPipe
|
|
: public Pipe<T, T, LCMConvolutionPipe<T, Rate> >
|
|
{
|
|
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<double>(i)/c + le);
|
|
}
|
|
void produce(int n)
|
|
{
|
|
Accessor<T> reader = _sink.reader(_maxRead);
|
|
Accessor<T> 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<int>((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<T> _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 T, class Rate = int> class NearestNeighborConvolutionPipe
|
|
: public Pipe<T, T, NearestNeighborConvolutionPipe<T, Rate> >
|
|
{
|
|
// TODO
|
|
};
|
|
|
|
// A convolution pipe that computes a fixed number of kernel coefficients, and
|
|
// uses linear interpolation to find the others.
|
|
template<class T, class Rate = int> class LinearConvolutionPipe
|
|
: public Pipe<T, T, LinearConvolutionPipe<T, Rate> >
|
|
{
|
|
};
|
|
|
|
|
|
// 985428/44100 == 492714/22050 == 246357/11025 == 82119/3675 == 27373/1225
|
|
|
|
#endif // INCLUDED_CONVOLUTION_PIPE_H
|