API — ICoreBlocks/ICoreMath/SignalProcessing
The public contract of 24 header(s) under src/ICoreBlocks/ICoreMath/SignalProcessing — 24 class/struct definition(s), 18 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreAnalogPrototypes.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreAnalogPrototypes.h
ICoreAnalogPrototypes#
ICoreAnalogPrototypes.h:33 · class · nested Prototype · 2 declaration(s)
The five analog lowpass prototypes every IIR design starts from (toolbox row T2.5): each answers a zero set, a pole set and a gain for a filter whose passband edge is 1 rad/s, which the band transf...
class ICoreAnalogPrototypes {
public:
// What every prototype answers: a zero set (empty for three of the five),
// a pole set, and the gain that makes the passband what the approximation
// promises.
struct Prototype {
std::vector<std::complex<double>> zeros;
std::vector<std::complex<double>> poles;
double gain = 1.0;
};
// MATLAB's buttap(n): n poles evenly spaced on the left half of the unit
// circle, no zeros, gain 1. The odd order's real pole at -1 comes LAST,
// which is MATLAB's order and is reproduced.
static bool butterworth(const size_t& order, Prototype& out, std::string* whyNot = nullptr);
// MATLAB's cheb1ap(n, rp): the Butterworth circle squashed onto an ellipse
// by the passband ripple `rp` in dB. An EVEN order does not reach unit
// gain at DC -- its gain is patched down by sqrt(1 + epsilon^2), which is
// MATLAB's rule and the reason an even Chebyshev I starts a ripple low.
static bool chebyshevI(const size_t& order, const double& passbandRipple,
Prototype& out, std::string* whyNot = nullptr);
// MATLAB's cheb2ap(n, rs): the RECIPROCAL of a Chebyshev I geometry, so
// the ripple lands in the stopband and the filter gains zeros on the
// imaginary axis. `rs` is the stopband attenuation in dB.
static bool chebyshevII(const size_t& order, const double& stopbandAttenuation,
Prototype& out, std::string* whyNot = nullptr);
// MATLAB's ellipap(n, rp, rs), by way of its `ellipap2`: the poles and
// zeros are values of the Jacobi elliptic functions at the odd quarter
// periods, and the modulus that ties the two ripples to the order is the
// solution of the degree equation.
//
// `rp` must be above zero and below `rs` -- an elliptic filter with no
// passband ripple is a Chebyshev II, and one whose passband ripple exceeds
// its stopband attenuation is not a filter at all. Both are refused with
// that reason, as MATLAB refuses them.
static bool elliptic(const size_t& order, const double& passbandRipple,
const double& stopbandAttenuation, Prototype& out,
std::string* whyNot = nullptr);
// MATLAB's besselap(n): the poles are the roots of the reverse Bessel
// polynomial, whose coefficients are exact integers,
//
// theta_n(s) = sum_k [ (2n-k)! / (2^(n-k) k! (n-k)!) ] s^k
//
// scaled by the n-th root of that polynomial's constant term. **That
// scaling is the whole of MATLAB's convention and it is not the -3 dB
// one**: it makes prod(-p) exactly 1, so the gain is 1 and the DC gain is
// 1 with a monic denominator, and the filter's -3 dB point is therefore
// NOT at 1 rad/s (measured: 1.756 rad/s at order 3, 2.114 at order 4).
// Normalising to the -3 dB frequency instead -- the obvious reading of
// "prototype" -- answers poles 2.4 times too large at order 3.
//
// MATLAB refuses an order above 25 (its own table stops there); this
// computes the roots and so has no table, but the refusal is kept because
// the polynomial's coefficients overflow a double soon after.
static bool bessel(const size_t& order, Prototype& out, std::string* whyNot = nullptr);
};
};
ICoreAnalyticSignal.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreAnalyticSignal.h
ICoreAnalyticSignal#
ICoreAnalyticSignal.h:43 · class · nested Envelope · 0 declaration(s)
The analytic signal and the envelopes read off it (toolbox row T2.19): hilbert and envelope.
class ICoreAnalyticSignal {
public:
// MATLAB's hilbert(x[, n]). `length` of 0 asks for the signal's own
// length; any other value is MATLAB's `n`, and the answer has that many
// samples. The orientation of the input is kept.
static bool analytic(const ICoreMatrix& signal, const size_t& length,
ICoreComplexMatrix& out, std::string* whyNot = nullptr);
// Which envelope is asked for. `Analytic` with a window length is
// MATLAB's FIR form; without one it is the transform above.
enum class Method { Analytic, Rms, Peaks };
// The two envelopes, which travel together because a lower envelope on its
// own is a curve nobody can place.
struct Envelope {
ICoreMatrix upper;
ICoreMatrix lower;
};
// MATLAB's [yupper, ylower] = envelope(x[, n, method]). `window` is
// MATLAB's n: the FIR order for `Analytic`, the sliding length for `Rms`
// and the minimum peak separation for `Peaks`; 0 asks for the plain
// analytic form and is refused by the other two, which have no default.
static bool envelope(const ICoreMatrix& signal, const Method& method,
const size_t& window, Envelope& out, std::string* whyNot = nullptr);
};
};
ICoreCorrelation.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreCorrelation.h
ICoreCorrelation#
ICoreCorrelation.h:31 · class · 0 declaration(s)
The Signal Processing Toolbox correlation family that base MATLAB's xcorr leaves out (toolbox row T2.13): the covariance spelling, the 2-D correlation, circular convolution, and the delay estimat...
class ICoreCorrelation {
public:
// MATLAB's xcov: ICoreMatrix::xcorr of the MEAN-REMOVED signals. The
// arguments mean exactly what xcorr's do -- `y` NULL asks for the
// autocovariance, `maxlag` below zero asks for MATLAB's default
// max(numel(x), numel(y)) - 1, `scale` is "none" (the default), "biased",
// "unbiased" or "coeff" -- and the orientation follows `x`.
//
// The mean removal happens FIRST, so "coeff" normalizes by the
// mean-removed energies and xcov(x, x, 'coeff') is 1 at lag 0 for any
// signal with a non-zero variance, which is the property that makes the
// covariance spelling worth having.
static ICoreMatrix xcov(const ICoreMatrix& x, const ICoreMatrix* y,
const int& maxlag, const std::string& scale,
ICoreMatrix* lags, std::string* whyNot = nullptr);
// MATLAB's xcorr2: the 2-D cross-correlation of two matrices,
//
// C(p, q) = sum_{i, j} A(i, j) * B(i - p, j - q)
//
// over every 2-D lag, so the answer is (ma + mb - 1) x (na + nb - 1) with
// lag (0, 0) at its centre entry (mb, nb) counting from one. `b` NULL asks
// for the 2-D AUTOcorrelation, which is MATLAB's one-argument form.
static ICoreMatrix xcorr2(const ICoreMatrix& a, const ICoreMatrix* b,
std::string* whyNot = nullptr);
// MATLAB's cconv(a, b, n): the length-`n` CIRCULAR convolution. Both
// sequences are wrapped modulo `n` first (MATLAB's `datawrap`, so a
// sequence longer than `n` folds onto itself rather than being cut), and
// the result is the circular convolution of the two wrapped sequences.
// `n` = 0 asks for MATLAB's default, numel(a) + numel(b) - 1, at which
// length the circular convolution IS the linear one.
//
// This is the direct O(n^2) sum where MATLAB's is an FFT, an elementwise
// product and an inverse FFT, so the two agree to rounding rather than
// exactly -- and, as with xcorr, it is MATLAB that carries the error.
static ICoreMatrix cconv(const ICoreMatrix& a, const ICoreMatrix& b,
const size_t& n, std::string* whyNot = nullptr);
// MATLAB's finddelay(x, y[, maxlag]): the estimated delay of `y` relative
// to `x`, in samples, as a whole number in [-maxlag, maxlag]. `maxlag`
// below zero asks for MATLAB's default, max(numel(x), numel(y)) - 1.
//
// Positive means `y` is LATER than `x`. The estimate is the lag of the
// largest |xcorr| normalized by sqrt(sum(x.^2) * sum(y.^2)); a tie between
// the best positive and the best negative lag goes to the smaller |lag|,
// and to the POSITIVE one when the two are the same size; and a peak below
// 1e-8 answers 0, because a correlation that small does not name a delay.
static double findDelay(const ICoreMatrix& x, const ICoreMatrix& y,
const int& maxlag, std::string* whyNot = nullptr);
// MATLAB's [xa, ya, d] = alignsignals(x, y[, maxlag[, 'truncate']]):
// `d` is findDelay's answer, and the signal that starts EARLY is delayed
// by |d| leading zeros so the two line up. With `truncate` the padded
// signal keeps its original length (its tail falls off the end) instead of
// growing by |d|; a delay at least as large as the signal then leaves all
// zeros, which is MATLAB's documented behaviour and its warning.
//
// The orientation of each output follows its own input.
static bool alignSignals(const ICoreMatrix& x, const ICoreMatrix& y,
const int& maxlag, const bool& truncate,
ICoreMatrix& xa, ICoreMatrix& ya, double& delay,
std::string* whyNot = nullptr);
};
};
ICoreDiscreteTransforms.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreDiscreteTransforms.h
ICoreDiscreteTransforms#
ICoreDiscreteTransforms.h:31 · class · 0 declaration(s)
The discrete transforms beside the Fourier one (toolbox row T2.20): the cosine family, and the two ways to evaluate a spectrum somewhere other than on the FFT's own grid.
class ICoreDiscreteTransforms {
public:
// MATLAB's dct / idct. `type` is 1..4 (2 is MATLAB's default), `length` is
// the transform length -- 0 asks for the signal's own, and any other value
// truncates or zero-pads first, as MATLAB's `dct(x, n)` does. `inverse`
// asks for `idct`, which is the same matrix transposed.
//
// A MATRIX is transformed COLUMN BY COLUMN (MATLAB's rule); a vector keeps
// its orientation.
static ICoreMatrix cosineTransform(const ICoreMatrix& x, const size_t& length,
const int& type, const bool& inverse,
std::string* whyNot = nullptr);
// MATLAB's czt(x, m, w, a): the chirp z-transform, which evaluates
//
// X(j) = sum_n x(n) * (a * w^-j)^-n
//
// at `m` points along the spiral z_j = a * w^(-j). The defaults are
// MATLAB's -- m = numel(x), w = exp(-2i*pi/m), a = 1 -- at which the
// answer IS the DFT.
//
// This is the direct O(m*n) sum where MATLAB's is Bluestein's algorithm
// (three FFTs), so the two agree to rounding rather than exactly, and it is
// MATLAB carrying the error: its czt of [1 2 3 4] answers a 4.6e-17
// imaginary part where the sum is real.
static ICoreComplexMatrix chirpZ(const ICoreMatrix& x, const size_t& points,
const std::complex<double>& ratio,
const std::complex<double>& start,
std::string* whyNot = nullptr);
// MATLAB's goertzel(x, k): the DFT at the frequency indices `k`, counted
// from ONE as MATLAB counts them, so k = 1 is the DC bin. `indices` empty
// asks for every bin, which is `fft`.
//
// The indices need not be whole numbers: MATLAB evaluates
// X(k) = sum_n x(n) * exp(-2i*pi*(k-1)*(n-1)/N) for any real k, and a
// fractional one lands between bins rather than being refused.
static ICoreComplexMatrix goertzel(const ICoreMatrix& x, const ICoreMatrix* indices,
std::string* whyNot = nullptr);
};
};
ICoreEllipticFunctions.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreEllipticFunctions.h
ICoreEllipticFunctions#
ICoreEllipticFunctions.h:31 · class · 0 declaration(s)
The complete elliptic integral and the three Jacobi elliptic functions an elliptic filter is built out of (toolbox rows T2.5 and T2.3).
class ICoreEllipticFunctions {
public:
// The descending Landen sequence of moduli, MATLAB's `landen(k, tol)`:
// each entry is the next modulus down, stopping once one falls at or below
// `tolerance` (machine epsilon by default). k = 0 or 1 answers itself.
static std::vector<double> landen(const double& modulus,
const double& tolerance = 2.220446049250313e-16);
// The complete elliptic integral K(k) and its complement K'(k) = K(k'),
// MATLAB's `ellipk`. Either may be infinite -- K(1) and K'(0) are -- and
// that is an answer rather than a failure.
static void completeIntegral(const double& modulus, double& integral, double& complement,
const double& tolerance = 2.220446049250313e-16);
// The Jacobi elliptic functions cd() and sn() in MATLAB's normalized
// spelling, where the argument is measured in QUARTER PERIODS: `cde(u, k)`
// is cd(u*K(k), k) and `sne(u, k)` is sn(u*K(k), k). Both take a complex
// argument, which is what an elliptic filter's poles need.
static std::complex<double> cde(const std::complex<double>& u, const double& modulus,
const double& tolerance = 2.220446049250313e-16);
static std::complex<double> sne(const std::complex<double>& u, const double& modulus,
const double& tolerance = 2.220446049250313e-16);
// Their inverses, again in quarter periods: `acde(w, k)` solves
// cde(u, k) = w and `asne(w, k)` solves sne(u, k) = w, both by the
// ASCENDING Landen transformation. The answer is brought into
// -2 < Re(u) <= 2 and |Im(u)| <= K'/K, as MATLAB's is.
static std::complex<double> acde(const std::complex<double>& w, const double& modulus,
const double& tolerance = 2.220446049250313e-16);
static std::complex<double> asne(const std::complex<double>& w, const double& modulus,
const double& tolerance = 2.220446049250313e-16);
// The DEGREE equation, MATLAB's `ellipdeg(N, k1)`: the selectivity modulus
// k of an order-N elliptic filter whose ripple modulus is k1 -- the
// solution of N * K'(k)/K(k) = K'(k1)/K(k1). Below k1 = 1e-6 it is solved
// through the nome instead, because the Landen sequence loses the
// difference between k1 and 0 there.
static double degree(const size_t& order, const double& rippleModulus,
const double& tolerance = 2.220446049250313e-16);
// The degree equation the other way, MATLAB's `ellipdeg2(1/N, k)`: the
// ripple modulus whose order-N filter has selectivity modulus k. `order`
// is the RECIPROCAL exponent, so pass 1/N to answer k1 from k -- which is
// the direction `ellipord` needs.
static double degreeInverse(const double& exponent, const double& modulus,
const double& tolerance = 2.220446049250313e-16);
};
};
ICoreFFT.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFFT.h
ICoreFFT#
ICoreFFT.h:8 · class · 4 declaration(s)
Discrete Fourier transform of a logged/simulated signal, backed by Eigen::FFT (kissfft backend, bundled -- no external dependency).
class ICoreFFT {
public:
// signal: a row or column vector, N samples.
// Returns the complex spectrum as an N x 2 matrix: column 0 = real part,
// column 1 = imaginary part.
static ICoreMatrix forward(const ICoreMatrix& signal);
// spectrum: an N x 2 matrix [real, imag], as returned by forward().
// Returns the reconstructed N x 1 time-domain signal.
static ICoreMatrix inverse(const ICoreMatrix& spectrum);
// Magnitude / phase (radians) of forward(signal), each as an N x 1 vector.
static ICoreMatrix magnitude(const ICoreMatrix& signal);
static ICoreMatrix phase(const ICoreMatrix& signal);
};
};
ICoreFilterAnalysis.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFilterAnalysis.h
ICoreFilterAnalysis#
ICoreFilterAnalysis.h:34 · class · 0 declaration(s)
What a designed filter is LOOKED AT with in the time domain and in phase (toolbox row T2.7): its impulse and step responses, its group delay and its unwrapped phase.
class ICoreFilterAnalysis {
public:
// MATLAB's impzlength(b, a): how long the response has to be for the
// filter to have finished. `tolerance` is MATLAB's 5e-5.
static bool responseLength(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const double& tolerance, size_t& out,
std::string* whyNot = nullptr);
// MATLAB's [h, t] = impz(b, a[, n][, fs]) and [s, t] = stepz(...): the same
// function with a different input. `points` of 0 asks for responseLength();
// a non-null `indices` is MATLAB's vector form, which answers the response
// AT those sample indices and may reach back before zero.
static bool timeResponse(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const ICoreMatrix* indices, const size_t& points,
const double& sampleRate, const bool& stepInput,
ICoreMatrix& values, ICoreMatrix& times,
std::string* whyNot = nullptr);
// MATLAB's [gd, w] = grpdelay(b, a[, n][, 'whole'][, fs]): the negative
// derivative of the phase, in SAMPLES. `frequencies` is the caller's own
// grid (in rad/sample when `sampleRate` is 0, in Hz otherwise); otherwise
// `points` picks the uniform grid, 0 asking for MATLAB's default of 512.
static bool groupDelay(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const ICoreMatrix* frequencies, const size_t& points,
const bool& whole, const double& sampleRate, ICoreMatrix& delay,
ICoreMatrix& grid, std::string* whyNot = nullptr);
// MATLAB's [phi, w] = phasez(b, a[, n][, 'whole'][, fs]): the UNWRAPPED
// phase, which is the one an analysis reads -- `angle(freqz(...))` is the
// same curve folded into (-pi, pi] and is not what this answers.
static bool unwrappedPhase(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const size_t& points, const bool& whole, const double& sampleRate,
ICoreMatrix& phase, ICoreMatrix& grid,
std::string* whyNot = nullptr);
};
};
ICoreFilterConversions.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFilterConversions.h
ICoreFilterConversions#
ICoreFilterConversions.h:43 · class · nested StateSpace, ZeroPoleGain · 0 declaration(s)
The four ways a filter can be written down, and the conversions between them (toolbox row T2.8): coefficients, zeros-poles-gain, state space, and a cascade of second-order sections.
class ICoreFilterConversions {
public:
// A realization. `a` is nx by nx, `b` nx by 1, `c` ny by nx, `d` ny by 1.
struct StateSpace {
ICoreMatrix a;
ICoreMatrix b;
ICoreMatrix c;
ICoreMatrix d;
};
// Zeros, poles and gain. `zeros` is nz by ny (Inf-padded when the outputs
// have different zero counts, which is MATLAB's own shape), `poles` a
// column, `gain` ny by 1.
struct ZeroPoleGain {
ICoreComplexMatrix zeros;
ICoreComplexMatrix poles;
ICoreMatrix gain;
};
// zp2sos's section ORDER: 'up' puts the pole pair furthest from the unit
// circle first, 'down' reverses the cascade. MATLAB's default is Up.
enum class SectionOrder { Up, Down };
// MATLAB's [A, B, C, D] = tf2ss(num, den): the CONTROLLER canonical form.
// `num` may have one row per output; `den` is a row.
static bool tfToStateSpace(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
StateSpace& out, std::string* whyNot = nullptr);
// MATLAB's [z, p, k] = tf2zp(num, den). Leading zero columns of the
// numerator are stripped first -- they are not zeros of the filter, they
// are absent leading terms -- and `k` is the first surviving numerator
// coefficient over the denominator's leading one.
static bool tfToZeroPoleGain(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
ZeroPoleGain& out, std::string* whyNot = nullptr);
// MATLAB's [num, den] = zp2tf(z, p, k). Both polynomials are the REAL part
// of the product over the roots, which is what makes a conjugate-paired
// set answer real coefficients rather than a complex vector with zero
// imaginary parts.
static bool zeroPoleGainToTf(const ICoreComplexMatrix& zeros, const ICoreComplexMatrix& poles,
const ICoreMatrix& gain, ICoreMatrix& numerator,
ICoreMatrix& denominator, std::string* whyNot = nullptr);
// MATLAB's [z, p, k] = ss2zp(A, B, C, D, iu): the response from the iu'th
// input (1-based, as MATLAB counts). Poles are eig(A) for every output;
// zeros and gain are per output row.
static bool stateSpaceToZeroPoleGain(const ICoreMatrix& a, const ICoreMatrix& b,
const ICoreMatrix& c, const ICoreMatrix& d,
const size_t& input, ZeroPoleGain& out,
std::string* whyNot = nullptr);
// MATLAB's [num, den] = ss2tf(A, B, C, D, iu), which is ss2zp followed by
// poly() on each side -- the route matters, because it is what makes
// ss2tf's numerator carry the zeros' rounding rather than the state
// matrices'.
static bool stateSpaceToTf(const ICoreMatrix& a, const ICoreMatrix& b, const ICoreMatrix& c,
const ICoreMatrix& d, const size_t& input, ICoreMatrix& numerator,
ICoreMatrix& denominator, std::string* whyNot = nullptr);
// MATLAB's [A, B, C, D] = zp2ss(z, p, k): the BALANCED cascade of
// second-order sections, not the controller form -- pairs of poles are
// realized together and each section is balanced by the geometric mean of
// its pole magnitudes, which is why this and tf2ss answer different
// matrices for the same filter and both are right.
static bool zeroPoleGainToStateSpace(const ICoreComplexMatrix& zeros,
const ICoreComplexMatrix& poles, const ICoreMatrix& gain,
StateSpace& out, std::string* whyNot = nullptr);
// MATLAB's [sos, g] = zp2sos(z, p, k, order). The gain is separated only
// when the caller asks for it; the single-output form folds it into the
// FIRST section's numerator, which this reports through `gain` and leaves
// to the caller to fold (the evaluator does, so both console forms match
// MATLAB's two).
static bool zeroPoleGainToSections(const ICoreComplexMatrix& zeros,
const ICoreComplexMatrix& poles, const ICoreMatrix& gain,
const SectionOrder& order, ICoreMatrix& sections,
double& sectionGain, std::string* whyNot = nullptr);
// MATLAB's [sos, g] = tf2sos(b, a) and ss2sos(A, B, C, D, iu): the same
// pairing, reached from the other two spellings. tf2sos equalizes the
// coefficient lengths first (MATLAB's `eqtflength`), which is what makes
// tf2sos([1 2], [1 3 2]) a two-pole cascade rather than an error.
static bool tfToSections(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const SectionOrder& order, ICoreMatrix& sections,
double& sectionGain, std::string* whyNot = nullptr);
static bool stateSpaceToSections(const ICoreMatrix& a, const ICoreMatrix& b,
const ICoreMatrix& c, const ICoreMatrix& d,
const size_t& input, const SectionOrder& order,
ICoreMatrix& sections, double& sectionGain,
std::string* whyNot = nullptr);
// MATLAB's [b, a] = sos2tf(sos, g) and [z, p, k] = sos2zp(sos, g): the
// cascade multiplied back out. A section whose third and sixth entries are
// both zero is a FIRST-order section and its trailing zeros are dropped
// before its roots are taken -- otherwise every such section would
// contribute a spurious zero at the origin.
static bool sectionsToTf(const ICoreMatrix& sections, const ICoreMatrix& gain,
ICoreMatrix& numerator, ICoreMatrix& denominator,
std::string* whyNot = nullptr);
static bool sectionsToZeroPoleGain(const ICoreMatrix& sections, const ICoreMatrix& gain,
ZeroPoleGain& out, std::string* whyNot = nullptr);
// MATLAB's cplxpair, exposed because zp2sos and zp2ss both need it and
// because it is a name of its own (core board C10.10): conjugate pairs
// adjacent with the negative imaginary part first, ordered by ascending
// real part, and the real entries sorted at the END. A set that does not
// pair within `tolerance` is refused rather than silently reordered --
// which is the behaviour zp2ss depends on when it retries with a looser
// tolerance.
static bool complexPair(std::vector<std::complex<double>>& values, const double& tolerance,
std::string* whyNot = nullptr);
};
};
ICoreFilterDesign.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFilterDesign.h
ICoreFilterDesign#
ICoreFilterDesign.h:9 · class · 2 declaration(s)
Digital Butterworth filter design via the analog prototype + bilinear (Tustin) transform, with pre-warped band edges.
class ICoreFilterDesign {
public:
// order: filter order (>= 1). cutoffHz: -3 dB cutoff frequency, must be
// in (0, sampleRateHz/2). sampleRateHz: discrete sampling rate.
// Returns a discrete (Ts = 1/sampleRateHz) transfer function normalized
// to unity gain in the passband (DC for lowpass, Nyquist for highpass).
static ICoreTransferFunction butterworthLowpass(const size_t& order, const double& cutoffHz, const double& sampleRateHz);
static ICoreTransferFunction butterworthHighpass(const size_t& order, const double& cutoffHz, const double& sampleRateHz);
// Band-stop (notch) of the given prototype order, built with the
// lowpass-to-bandstop transform s -> B*s / (s^2 + w0^2). It nulls
// centerHz and passes everything either side of it.
//
// bandwidthHz is the -3 dB width of the stop band, and centerHz +/-
// bandwidthHz/2 must both fall inside (0, sampleRateHz/2). Each prototype
// pole becomes two, so the result has 2*order poles and 2*order zeros --
// order 1 is the classic two-pole/two-zero notch, and higher orders square
// up the shoulders without moving the null.
//
// The zeros land exactly on the unit circle at the centre frequency, so the
// attenuation there is total rather than merely deep (measured |H| at the
// centre is ~1e-13 for order 1, ~1e-10 for order 3, the residual being
// coefficient round-off from the repeated roots). Gain is normalized to
// unity at DC, which for a band-stop is in the passband.
//
// Note on the band edges: this filter shape is geometrically symmetric about
// the centre, which is two degrees of freedom -- not enough to place the null
// at centerHz AND both -3 dB points at centerHz +/- bandwidthHz/2. The null
// is kept exact, so the achieved -3 dB edges come out geometrically
// symmetric (sqrt(f_lo*f_hi) = centerHz) rather than arithmetically. Their
// separation still matches bandwidthHz -- within 0.01% for a narrow notch,
// and a few tenths of a percent for one spanning a large fraction of the
// band -- so the width you ask for is the width you get; only its placement
// shifts slightly downward relative to an arithmetic split.
static ICoreTransferFunction butterworthNotch(const size_t& order, const double& centerHz,
const double& bandwidthHz, const double& sampleRateHz);
};
};
ICoreFilterTransforms.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFilterTransforms.h
ICoreFilterTransforms#
ICoreFilterTransforms.h:31 · class · 0 declaration(s)
The frequency and s-to-z transformations a filter design is assembled from (toolbox row T2.9): move an analog prototype to the band you want, then carry it to the discrete plane.
class ICoreFilterTransforms {
public:
// Which band the analog prototype is being moved to.
enum class Band {
Lowpass, // lp2lp: s -> s / Wo
Highpass, // lp2hp: s -> Wo / s
Bandpass, // lp2bp: s -> (s^2 + Wo^2) / (Bw * s)
Bandstop, // lp2bs: s -> (Bw * s) / (s^2 + Wo^2)
};
// MATLAB's lp2lp / lp2hp / lp2bp / lp2bs on a coefficient PAIR: the
// prototype num/den in descending powers of s, and the answer in the same
// form with the denominator made monic -- which is what MATLAB's
// `poly(at)` route produces and is therefore part of the contract, not
// tidying.
//
// `bandwidth` is read only for the two band forms. A transform that leaves
// no leading denominator coefficient to divide by -- lp2hp of a prototype
// with a pole at the origin -- is refused with that reason rather than
// answered as an infinity.
static bool bandTransform(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const Band& band, const double& cutoff, const double& bandwidth,
ICoreMatrix& outNumerator, ICoreMatrix& outDenominator,
std::string* whyNot = nullptr);
// MATLAB's [bd, ad] = bilinear(b, a, fs[, fp]): the analog pair carried to
// the discrete plane by s = fs2*(z-1)/(z+1), with fs2 = 2*fs, and the
// answer made monic.
//
// `prewarp` is MATLAB's optional fp in Hz: it replaces fs2 with
// 2*pi*fp / tan(pi*fp/fs), which is what makes the analog and digital
// responses agree AT fp rather than only near zero. Pass a null pointer
// for the unwarped transform.
//
// The numerator may not be of higher degree than the denominator, which is
// MATLAB's own restriction (an improper analog filter has no bilinear
// image of the same order).
static bool bilinear(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const double& sampleRate, const double* prewarp,
ICoreMatrix& outNumerator, ICoreMatrix& outDenominator,
std::string* whyNot = nullptr);
// MATLAB's [zd, pd, kd] = bilinear(z, p, k, fs[, fp]): the same transform
// on a zero-pole-gain triple, which maps each zero and pole by
// (1 + s/fs2) / (1 - s/fs2) and scales the gain by
// prod(fs2 - z) / prod(fs2 - p).
//
// The two shapes MATLAB's own answer has, and both matter: the zeros come
// back PADDED with as many -1 entries as the filter has excess poles (the
// bilinear map sends infinity to z = -1), and an infinite zero on the way
// in is dropped before the count is taken.
static bool bilinearZeroPoleGain(const ICoreComplexMatrix& zeros,
const ICoreComplexMatrix& poles, const double& gain,
const double& sampleRate, const double* prewarp,
ICoreComplexMatrix& outZeros, ICoreComplexMatrix& outPoles,
double& outGain, std::string* whyNot = nullptr);
// MATLAB's [bz, az] = impinvar(b, a, fs[, tol]): the discrete filter whose
// impulse response is the analog one SAMPLED at fs -- a different
// transformation from the bilinear one and not a variant of it, since it
// aliases rather than warping.
//
// The route is `impinvar.m`'s, and its shape is the reason the answer is
// what it is: the analog filter is expanded into partial fractions, each
// pole p contributes exp(p/fs) to the discrete denominator, the first
// `order` samples of the analog impulse response are formed directly, and
// the numerator is read back out by filtering that response through the
// new denominator. A repeated pole contributes t^(m-1)/(m-1)! alongside
// its exponential, which is where `tol` comes in: it is the RELATIVE
// distance within which two poles are one repeated pole (MATLAB's 1e-3).
static bool impulseInvariant(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const double& sampleRate, const double& tolerance,
ICoreMatrix& outNumerator, ICoreMatrix& outDenominator,
std::string* whyNot = nullptr);
};
};
ICoreFirDesign.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFirDesign.h
ICoreFirDesign#
ICoreFirDesign.h:38 · class · nested OrderEstimate · 0 declaration(s)
The FIR half of filter design (toolbox row T2.4): firls, fir1, fir2, kaiserord and firpmord.
class ICoreFirDesign {
public:
// Which linear-phase family `firls` is asked for. `Standard` is the
// symmetric Type I/II filter every ordinary design wants.
enum class LeastSquaresType { Standard, Hilbert, Differentiator };
// MATLAB's b = firls(n, f, a[, w][, ftype]). `frequencies` runs 0..1 in
// PAIRS (Nyquist is 1), `amplitudes` gives the template's value at each
// edge, and `weights` holds one weight per band (empty for all ones).
//
// The answer is `order + 1` taps, or one more when firchk's rule above
// applies -- which is reported through `orderRaised` rather than warned
// about, since this console cannot warn from inside an expression.
static bool leastSquares(const size_t& order, const ICoreMatrix& frequencies,
const ICoreMatrix& amplitudes, const ICoreMatrix* weights,
const LeastSquaresType& type, ICoreMatrix& taps,
bool* orderRaised = nullptr, std::string* whyNot = nullptr);
// Which band arrangement `fir1` is asked for. `Low` and `Bandpass` are the
// defaults for a scalar and a two-entry Wn; `High` and `Stop` are the
// words, and a longer Wn is a multiband whose first band follows the word.
enum class Band { Low, High, Bandpass, Stop, DcZero, DcOne };
// MATLAB's b = fir1(n, Wn[, ftype][, window][, 'scale' | 'noscale']).
// `Wn` is normalised to NYQUIST (1 is half the sample rate), `window` is
// the n+1 window to multiply by (empty asks for MATLAB's Hamming default),
// and `scale` is MATLAB's default: the filter is divided by its own gain
// at the centre of the first band, so a lowpass has unit gain at DC.
static bool windowed(const size_t& order, const ICoreMatrix& cutoffs, const Band& band,
const ICoreMatrix* window, const bool& scale, ICoreMatrix& taps,
std::string* whyNot = nullptr);
// MATLAB's b = fir2(n, f, m[, npt][, lap][, window]): the frequency
// SAMPLING design -- interpolate the template onto a dense grid, give it
// the linear phase of an n-tap filter, transform back and window.
//
// `points` of 0 asks for MATLAB's default (512 below 1024 taps, the next
// power of two above that); `ramp` of 0 asks for its `fix(npt / 25)`,
// which is how wide a jump in the template is smeared before transforming.
static bool frequencySampled(const size_t& order, const ICoreMatrix& frequencies,
const ICoreMatrix& magnitudes, const size_t& points,
const size_t& ramp, const ICoreMatrix* window,
ICoreMatrix& taps, std::string* whyNot = nullptr);
// What an order estimate answers: the order, the band edges it wants a
// design to use, and (for kaiserord) the window parameter beta.
struct OrderEstimate {
size_t order = 0;
ICoreMatrix cutoffs; // normalised to Nyquist, ready for fir1
double beta = 0.0; // kaiserord only
Band band = Band::Low; // which ftype the edges imply
ICoreMatrix frequencies; // firpmord only: the firpm band vector
ICoreMatrix amplitudes; // firpmord only
ICoreMatrix weights; // firpmord only
};
// MATLAB's [n, Wn, beta, ftype] = kaiserord(f, a, dev[, fs]): Kaiser's own
// closed-form estimate of how long a windowed design must be to hold a
// given ripple across a given transition. `sampleRate` of 0 means the
// edges are already normalised to a rate of 2 (MATLAB's default).
static bool kaiserOrder(const ICoreMatrix& edges, const ICoreMatrix& amplitudes,
const ICoreMatrix& deviations, const double& sampleRate,
OrderEstimate& out, std::string* whyNot = nullptr);
// MATLAB's b = firpm(n, f, a[, w]): the EQUIRIPPLE design -- the filter
// whose largest weighted error is as small as it can be, which is a
// different filter from firls's (least squares) and usually a shorter one
// for the same specification.
//
// It is the only iterative design in this file. The Remez exchange guesses
// `nfcns + 1` extremal frequencies, fits the unique response that
// alternates through them, then moves each one to the nearby grid point
// where the error is actually largest, and repeats. What makes it
// reproducible rather than approximate is that the exchange is DISCRETE:
// the extrema only ever move to points of a fixed frequency grid, so two
// implementations that build the same grid and apply the same moves agree
// to rounding rather than to a tolerance.
//
// Three things about it are transcribed rather than derived, because none
// of them follows from the mathematics:
//
// THE GRID IS PART OF THE ANSWER. `firpmgrid` lays out `1/(16*nfcns)`
// spacing per band, forces the last point of each band onto the band
// edge, and drops or moves the final point when the last band reaches
// Nyquist. A denser grid is a different filter, not a better-converged
// one, and MATLAB quadruples the density only when the grid came out
// shorter than the filter.
//
// THE BARYCENTRIC WEIGHTS SKIP EVERY `jet`-th POINT (`remezdd`), where
// `jet = (nfcns - 1)/15 + 1`. That is a numerical-conditioning trick of
// the original FORTRAN, not a definition, and it changes the last bits.
//
// THE COEFFICIENTS COME OUT THROUGH A CHEBYSHEV CHANGE OF VARIABLE that
// is SKIPPED when the design spans the whole band or the filter is very
// short (`kkk`), and taken when it does not. Both branches are here
// because MATLAB takes both.
//
// `deviation` is the converged ripple -- MATLAB's second output -- and is
// the number that says whether the specification was met at this order.
// The Hilbert and differentiator types are refused, as `leastSquares`
// refuses them, and for the same reason.
static bool equiripple(const size_t& order, const ICoreMatrix& frequencies,
const ICoreMatrix& amplitudes, const ICoreMatrix* weights,
const LeastSquaresType& type, ICoreMatrix& taps,
double* deviation = nullptr, std::string* whyNot = nullptr);
// MATLAB's [B, G] = sgolay(order, frameLength[, weights]): the
// Savitzky-Golay projection and its differentiation matrix.
//
// B is the projector onto the frame's polynomials -- row (frameLength+1)/2
// smooths the middle of a frame and the rows either side of it smooth each
// edge position -- and it is `ICoreSignalFilters`'s matrix rather than a
// second copy, because `sgolayfilt` is nothing without it and two copies
// would eventually disagree.
//
// G is S * inv(S' * S) where S is the frame's Vandermonde matrix. MATLAB
// spells it Q/R' off an economy QR, which is the SAME matrix by a
// different route -- so it does not depend on which QR produced it, and
// this computes it from the normal equations instead. Column k+1 of G is
// the least-squares estimate of the k-th derivative (times k!), which is
// what makes the second output worth having at all.
static bool savitzkyGolay(const size_t& order, const size_t& frameLength,
const ICoreMatrix* weights, ICoreMatrix& projection,
ICoreMatrix* differentiation, std::string* whyNot = nullptr);
// MATLAB's [n, fo, ao, w] = firpmord(f, a, dev[, fs]): the same question
// for an EQUIRIPPLE design, by Herrmann's formula rather than Kaiser's --
// a different estimate of a different filter, and the two do not agree.
static bool remezOrder(const ICoreMatrix& edges, const ICoreMatrix& amplitudes,
const ICoreMatrix& deviations, const double& sampleRate,
OrderEstimate& out, std::string* whyNot = nullptr);
};
};
ICoreFrequencyResponse.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreFrequencyResponse.h
Both are held BY VALUE in Response below, so a forward declaration will not do: a caller of this header needs their size.
ICoreFrequencyResponse#
ICoreFrequencyResponse.h:39 · class · nested Response · 0 declaration(s)
The frequency response of a filter, digital and analog (toolbox row T2.6): what a design is LOOKED AT with, once the prototypes and the transformations have produced one.
class ICoreFrequencyResponse {
public:
// The response and the grid it was taken on, which travel together because
// a response without its frequencies is a picture nobody can place.
struct Response {
ICoreComplexMatrix values;
ICoreMatrix frequencies;
};
// MATLAB's freqz on a coefficient pair. Exactly one of `frequencies` and
// `points` is used: a non-null `frequencies` is the caller's own grid
// (returned unchanged), and otherwise `points` picks one of the four
// spellings above -- 0 asks for MATLAB's default of 512.
//
// `sampleRate` of 0 asks for the normalized grid in radians per sample;
// any positive value answers in Hz. `whole` swaps 0, pi) for the whole
// circle.
static bool digital(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const ICoreMatrix* frequencies, const size_t& points,
const bool& whole, const double& sampleRate,
Response& out, std::string* whyNot = nullptr);
// MATLAB's freqz(sos, ...): the same response for a filter given as an
// L x 6 matrix of second-order sections, whose response is the PRODUCT of
// the sections' -- so a design that was split into sections for numerical
// reasons is looked at without being reassembled into one long pair.
static bool sections(const ICoreMatrix& sos, const ICoreMatrix* frequencies,
const size_t& points, const bool& whole, const double& sampleRate,
Response& out, std::string* whyNot = nullptr);
// MATLAB's freqs(b, a, w): the ANALOG response, b(jw)/a(jw). The frequency
// vector is not optional here and not a count -- an analog filter has no
// Nyquist rate to spread a default grid over.
static bool analog(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const ICoreMatrix& frequencies, ICoreComplexMatrix& out,
std::string* whyNot = nullptr);
};
};
ICoreIirDesign.h#
[src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreIirDesign.h
ICoreIirDesign#
ICoreIirDesign.h:53 · class · nested Design · 0 declaration(s)
The five classical IIR designs and the four order estimates that size them (toolbox rows T2.1, T2.2 and T2.3): butter, cheby1, cheby2, ellip, besself, and buttord, cheb1ord, `cheb2ord...
class ICoreIirDesign {
public:
// Which approximation, which is to say which prototype the pipeline
// starts from. `Bessel` is analog-only, as MATLAB's `besself` is.
enum class Approximation { Butterworth, ChebyshevI, ChebyshevII, Elliptic, Bessel };
// MATLAB's own btype numbering, which the band edges also decide: one
// edge is a lowpass unless `high` says otherwise, two edges are a
// bandpass unless `stop` does.
enum class Band { Lowpass = 1, Bandpass = 2, Highpass = 3, Bandstop = 4 };
// A designed filter in the form every one of MATLAB's five answers it in.
// The zeros are as many as the poles for a discrete design (the bilinear
// map sends infinity to z = -1) and fewer for an analog lowpass.
struct Design {
std::vector<std::complex<double>> zeros;
std::vector<std::complex<double>> poles;
double gain = 1.0;
};
// The five designs. `edges` is MATLAB's Wn / Wp / Ws / Wo: one entry for a
// lowpass or highpass, two for a bandpass or bandstop, normalised to
// Nyquist (strictly inside 0..1) unless `analog`, where it is rad/s.
//
// `passbandRipple` is read by ChebyshevI and Elliptic, `stopbandAttenuation`
// by ChebyshevII and Elliptic; the other approximations ignore both, which
// is what lets the five share one signature.
static bool design(const Approximation& approximation, const size_t& order,
const double& passbandRipple, const double& stopbandAttenuation,
const std::vector<double>& edges, const Band& band, const bool& analog,
Design& out, std::string* whyNot = nullptr);
// MATLAB's [b, a] out of a design: `den = real(poly(p))` and
// `num = [zeros(1, np - nz) k*real(poly(z))]`, both in descending powers
// and both of length np + 1 -- the numerator is LEADING-ZERO PADDED rather
// than shortened, so `b` and `a` are always the same length and a filter
// of order n reads as one.
static void coefficients(const Design& design, std::vector<double>& numerator,
std::vector<double>& denominator);
// The four order estimates: the least order whose filter is inside `rp` dB
// of ripple across the passband and at least `rs` dB down across the
// stopband, and the natural frequency (or pair) to hand the design.
//
// Each approximation reads its own natural frequency off a different
// place, which is MATLAB's own arrangement and not a tidiness this could
// fix: `buttord` answers the -3 dB frequency it computed, `cheb1ord` the
// PASSBAND edges it was given, `cheb2ord` the STOPBAND edges it was given,
// and `ellipord` the passband edges again. `Bessel` has no estimate --
// MATLAB has no `besselord` -- and is refused by name.
static bool orderEstimate(const Approximation& approximation,
const std::vector<double>& passbandEdges,
const std::vector<double>& stopbandEdges,
const double& passbandRipple, const double& stopbandAttenuation,
const bool& analog, size_t& order,
std::vector<double>& naturalEdges,
std::string* whyNot = nullptr);
};
};
ICoreLinearPrediction.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreLinearPrediction.h
ICoreLinearPrediction#
ICoreLinearPrediction.h:41 · class · 0 declaration(s)
Linear prediction: the Signal Processing Toolbox family that turns a signal (or its autocorrelation) into an all-pole model, and converts between the three equivalent spellings of one (toolbox row ...
class ICoreLinearPrediction {
public:
// MATLAB's [a, e, k] = levinson(r, order): the Levinson-Durbin recursion
// over the autocorrelation sequence `r` (r(0) first, whichever way the
// vector is turned).
//
// `a` comes back as a ROW of order+1 coefficients beginning with 1, `error`
// is the FINAL prediction error E_p, and `reflection` is a COLUMN of the
// order reflection coefficients -- MATLAB's three shapes exactly.
//
// `order` may not exceed numel(r) - 1; MATLAB clamps it to that, and so
// does this, because a recursion has nothing left to read past the end of
// its own data.
static bool levinson(const ICoreMatrix& r, const size_t& order,
ICoreMatrix& a, double& error, ICoreMatrix& reflection,
std::string* whyNot = nullptr);
// MATLAB's [k, r0] = poly2rc(a, efinal): the step-DOWN recursion, from the
// prediction-error polynomial to the reflection coefficients. `efinal` is
// the final prediction error and `zeroLagError` comes back as the
// zero-lag error r0 = efinal / prod(1 - k_i^2).
//
// A reflection coefficient of magnitude exactly 1 stops the recursion --
// its step divides by 1 - k^2 -- and is refused with that reason rather
// than answered as an infinity.
static bool polyToReflection(const ICoreMatrix& a, const double& finalError,
ICoreMatrix& reflection, double& zeroLagError,
std::string* whyNot = nullptr);
// MATLAB's [a, efinal] = rc2poly(k, r0): the step-UP recursion, the exact
// inverse of the one above. `r0` is the ZERO-LAG error and `finalError`
// comes back as r0 * prod(1 - k_i^2).
static bool reflectionToPoly(const ICoreMatrix& reflection, const double& zeroLagError,
ICoreMatrix& a, double& finalError,
std::string* whyNot = nullptr);
// MATLAB's r = poly2ac(a, efinal): the autocorrelation sequence the
// polynomial came from, as a COLUMN of numel(a) entries beginning with r0.
// Its inverse is `levinson` at full order, which is MATLAB's `ac2poly`.
static bool polyToAutocorrelation(const ICoreMatrix& a, const double& finalError,
ICoreMatrix& r, std::string* whyNot = nullptr);
// MATLAB's lpc(x, p) and aryule(x, p), which are one estimator: the
// Levinson recursion over the BIASED autocorrelation of `x` (divided by
// numel(x), so the model is windowed and always stable). `a` is a row,
// `error` the final prediction error -- lpc's `g` and aryule's `e` are the
// same number -- and `reflection` a column, which only aryule reports.
static bool yuleWalker(const ICoreMatrix& x, const size_t& order,
ICoreMatrix& a, double& error, ICoreMatrix& reflection,
std::string* whyNot = nullptr);
// MATLAB's [a, e, k] = arburg(x, p): Burg's recursion on the forward and
// backward prediction errors. Same three shapes as levinson's.
static bool burg(const ICoreMatrix& x, const size_t& order,
ICoreMatrix& a, double& error, ICoreMatrix& reflection,
std::string* whyNot = nullptr);
// MATLAB's arcov(x, p) and, with `modified` set, armcov(x, p): the
// least-squares solve over the covariance data matrix -- the forward
// errors alone, or the forward and backward ones together.
//
// `error` is the white-noise variance estimate, which is a MEAN square:
// the data matrix carries MATLAB's own 1/sqrt(N - p) scaling (and a
// further 1/sqrt(2) for the modified method), and dropping it would answer
// a model with the right coefficients and the wrong variance.
static bool covarianceMethod(const ICoreMatrix& x, const size_t& order,
const bool& modified, ICoreMatrix& a, double& error,
std::string* whyNot = nullptr);
};
};
ICoreParametricSpectrum.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreParametricSpectrum.h
ICoreParametricSpectrum#
ICoreParametricSpectrum.h:41 · class · nested Spectrum · 0 declaration(s)
The parametric (autoregressive) spectral estimates: pyulear, pburg, pcov and pmcov (toolbox row T2.18).
class ICoreParametricSpectrum {
public:
// Which all-pole fit the spectrum is taken of. The four are MATLAB's four
// names, and the difference between them is entirely in the fit.
enum class Estimator {
YuleWalker, // pyulear -- aryule
Burg, // pburg -- arburg
Covariance, // pcov -- arcov
ModifiedCovariance, // pmcov -- armcov
};
// Which part of the circle is reported. `OneSided` is MATLAB's default for
// a real signal and folds as described above; `TwoSided` reports the whole
// circle unfolded.
enum class Range { OneSided, TwoSided };
// The density and the grid it was taken on, which travel together for the
// same reason a frequency response and its grid do.
struct Spectrum {
ICoreMatrix values;
ICoreMatrix frequencies;
};
// MATLAB's [pxx, w] = pyulear/pburg/pcov/pmcov(x, order[, nfft][, fs]).
// `points` of 0 asks for MATLAB's default of 256; `sampleRate` of 0 asks
// for the normalised grid in radians per sample, and any positive value
// answers in Hz with the density scaled by the rate instead of by 2*pi.
//
// An order at or above the signal's length is refused, as MATLAB refuses
// it: there is no model left to fit.
static bool estimate(const ICoreMatrix& signal, const size_t& order, const size_t& points,
const Estimator& estimator, const Range& range,
const double& sampleRate, Spectrum& out,
std::string* whyNot = nullptr);
};
};
ICorePeakFinding.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICorePeakFinding.h
ICorePeakFinding#
ICorePeakFinding.h:43 · class · nested Options, Peaks · 0 declaration(s)
MATLAB's findpeaks (toolbox row T2.23): which samples of a signal are local maxima, how far each one stands above the terrain around it, and how wide it is at half that height.
class ICorePeakFinding {
public:
// The reference level a peak's width is measured at.
enum class WidthReference {
HalfProminence, // MATLAB's default 'halfprom': (peak + base) / 2
HalfHeight, // 'halfheight': peak / 2, bounded by the saddles
};
// What order the surviving peaks are reported in. `None` is MATLAB's
// default and reports them in the order they occur.
enum class Sort { None, Ascend, Descend };
// Every thinning rule, with MATLAB's defaults -- so a default-constructed
// Options is `findpeaks(y)`.
struct Options {
double minHeight = -std::numeric_limits<double>::infinity();
double threshold = 0.0;
double minProminence = 0.0;
double minWidth = 0.0;
double maxWidth = std::numeric_limits<double>::infinity();
double minDistance = 0.0; // measured in the LOCATION units, not samples
size_t maxPeaks = 0; // 0 asks for every peak that survives
Sort sort = Sort::None;
WidthReference widthReference = WidthReference::HalfProminence;
};
// The four outputs of `[pks, locs, w, p] = findpeaks(...)`, which travel
// together because three of them are only meaningful beside the first.
// Each is shaped the way `y` was -- a row signal answers rows.
struct Peaks {
ICoreMatrix values;
ICoreMatrix locations;
ICoreMatrix widths;
ICoreMatrix prominences;
};
// MATLAB's findpeaks on a real vector. `locations` is the x axis the peaks
// are reported and the minimum distance is measured on: pass a null
// pointer for MATLAB's default of 1..n (which is what `findpeaks(y)` uses)
// or a strictly increasing vector of the same length (`findpeaks(y, x)`;
// a sample rate is (0:n-1)/fs, formed by the caller).
//
// A signal shorter than three samples is refused, as MATLAB refuses it:
// the first and last sample can never be strictly greater than a neighbour
// on both sides, so there is nothing for the definition to answer.
static bool find(const ICoreMatrix& signal, const ICoreMatrix* locations,
const Options& options, Peaks& out, std::string* whyNot = nullptr);
};
};
ICoreResampling.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreResampling.h
ICoreResampling#
ICoreResampling.h:41 · class · 0 declaration(s)
The rate changers (toolbox row T2.21): upsample, downsample, upfirdn, interp, decimate and resample.
class ICoreResampling {
public:
// MATLAB's y = upsample(x, n[, phase]) and y = downsample(x, n[, phase]):
// exact, and each other's inverse when the phase agrees. The answer keeps
// the input's orientation, as MATLAB's does.
static bool upsample(const ICoreMatrix& x, const size_t& factor, const size_t& phase,
ICoreMatrix& out, std::string* whyNot = nullptr);
static bool downsample(const ICoreMatrix& x, const size_t& factor, const size_t& phase,
ICoreMatrix& out, std::string* whyNot = nullptr);
// MATLAB's y = upfirdn(x, h, p, q): x upsampled by `p`, filtered by `h`,
// then decimated by `q`, in one pass.
//
// **The output LENGTH is the part that is not obvious** and it is the
// trap the board's note names: `ceil(((Lx - 1)*p + Lh) / q)`, the length of
// the full convolution of the upsampled signal, thinned by q -- so a
// filter longer than the signal still lengthens the answer, and the last
// output sample can be one the filter has not finished.
static bool upfirdn(const ICoreMatrix& x, const ICoreMatrix& h, const size_t& p,
const size_t& q, ICoreMatrix& out, std::string* whyNot = nullptr);
// MATLAB's y = interp(x, r[, n[, cutoff]]): the signal interpolated to r
// times as many samples, so that y(1:r:end) IS x -- which the FIR design
// below guarantees and a general lowpass does not.
//
// `n` is the number of ORIGINAL samples on each side that each new sample
// is computed from (MATLAB's default 4, so a 2*4*r+1 tap filter) and
// `cutoff` the normalised bandwidth (0.5). The signal must be longer than
// 2*n samples, which is MATLAB's own condition and is refused with it.
static bool interpolate(const ICoreMatrix& x, const size_t& factor, const size_t& neighbours,
const double& cutoff, ICoreMatrix& out,
std::string* whyNot = nullptr);
// MATLAB's y = decimate(x, r[, n][, "fir"]): every r-th sample of the
// low-passed signal, `ceil(numel(x)/r)` of them.
//
// The two arms are different filters AND different edge treatments, which
// is why they answer different numbers: the Chebyshev I arm is zero-phase
// (`filtfilt`, so no delay to correct) and the FIR arm is a single forward
// pass whose group delay is corrected by starting the read at
// `round(gd(1) + 1.25)`. ⚠ MATLAB reduces the IIR order until the filter
// it designs really is `rip` dB down at 0.8/r -- a high order at a large r
// is numerically dead, and the loop is reproduced rather than the order
// being trusted.
static bool decimate(const ICoreMatrix& x, const size_t& factor, const size_t& order,
const bool& finiteImpulse, ICoreMatrix& out,
std::string* whyNot = nullptr);
// MATLAB's y = resample(x, p, q[, n[, beta]]): `ceil(numel(x)*p/q)`
// samples of x at p/q times its rate, through a Kaiser-windowed
// least-squares FIR.
//
// `p` and `q` are reduced to lowest terms first, as MATLAB reduces them
// (`rat(p/q, 1e-12)`), so resample(x, 4, 6) IS resample(x, 2, 3) on both
// sides. `n` of 0 asks for the degenerate all-ones filter MATLAB uses
// there; `outFilter`, when not null, receives the filter actually used --
// MATLAB's second output.
static bool resample(const ICoreMatrix& x, const size_t& up, const size_t& down,
const size_t& neighbours, const double& beta, ICoreMatrix& out,
ICoreMatrix* outFilter, std::string* whyNot = nullptr);
};
};
ICoreSignalFilters.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreSignalFilters.h
ICoreSignalFilters#
ICoreSignalFilters.h:38 · class · 0 declaration(s)
The five Signal Processing filtering helpers of toolbox row T2.11 -- sosfilt, fftfilt, medfilt1, sgolayfilt and hampel.
class ICoreSignalFilters {
public:
// MATLAB's `sosfilt(sos, x)`: a cascade of biquads, one per ROW of the
// L x 6 `sos` matrix ([b0 b1 b2 a0 a1 a2]). Each row is divided through by
// its own a0 and each stage runs over the previous stage's output.
//
// There is no gain vector here -- that is `filtfilt(sos, g, x)`'s and
// `tf2sos`'s (T2.8) argument, not this one. `x` is filtered down its
// columns, or along itself when it is a vector, and the answer has x's
// shape.
static ICoreMatrix sections(const ICoreMatrix& sos, const ICoreMatrix& x,
std::string* whyNot = nullptr);
// MATLAB's `fftfilt(b, x)` and `fftfilt(b, x, nfft)`: the FIR filter `b`
// applied by overlap-add, answering the FIRST numel(x) samples of the
// convolution -- the same numbers `filter(b, 1, x)` answers, reached by a
// different route and therefore not to the last bit.
//
// `nfft <= 0` means "choose it", and the choice is MATLAB's own: the
// published flop table in fftfilt.m, minimising ceil(nx/L)*flops(nfft)
// over the power-of-two lengths that leave a usable block. A given `nfft`
// is raised to at least numel(b) and then to the next power of two, as
// MATLAB raises it.
static ICoreMatrix blockConvolution(const ICoreMatrix& b, const ICoreMatrix& x,
const long long& nfft, std::string* whyNot = nullptr);
// How `medfilt1` treats the samples a window reaches past the ends.
// MATLAB's default is ZeroPad -- the window stays full and the missing
// samples are zeros, which pulls the answer towards zero at both ends.
// Truncate is `movmedian`'s (and this console's) shrinking window.
enum class Endpoints { ZeroPad, Truncate };
// MATLAB's `medfilt1(x, n)`: the running median of an n-wide window.
// An even `n` reaches one further BACK than forward (floor(n/2) back,
// ceil(n/2)-1 forward), which is `movmedian`'s rule and therefore the
// console's. `n == 0` answers all-NaN, as medfilt1.m does.
//
// `omitNaN` is MATLAB's 'omitnan'; the default there and here is
// 'includenan', under which one NaN anywhere in a window makes that
// window's answer NaN.
static ICoreMatrix runningMedian(const ICoreMatrix& x, const size_t& n,
const Endpoints& ends, const bool& omitNaN,
std::string* whyNot = nullptr);
// `sgolay`'s first output B (T2.4 owns the NAME; this is the matrix, kept
// here because sgolayfilt is nothing without it and two copies of it would
// eventually disagree). B is frameLength x frameLength: row m+1 is the
// smoothing filter for the middle of the frame, and the rows either side
// of it are the filters for each edge position.
//
// `weights` is NULL for unity. It is a pointer rather than an empty matrix
// because a default-constructed ICoreMatrix is 1 x 1 holding 0 and not
// empty -- numel() cannot tell "no weights" from "one weight", and reading
// it as the latter is what made every unweighted sgolayfilt call refuse
// itself. It is the least-squares weighting, and MATLAB folds it in as
// sqrt(w) on both sides of the projector.
static bool savitzkyGolayProjection(const size_t& order, const size_t& frameLength,
const ICoreMatrix* weights, ICoreMatrix& B,
std::string* whyNot = nullptr);
// MATLAB's `sgolayfilt(x, order, frameLength[, weights])`. `frameLength`
// must be ODD and greater than `order`, and x must be at least
// frameLength long -- MATLAB's own three refusals.
//
// The middle of the answer is one FIR pass with row (frameLength+1)/2 of
// B; the first and last (frameLength-1)/2 samples are not that filter
// started early, they are the polynomial fitted to the first (last) whole
// frame, evaluated at each edge position.
static ICoreMatrix savitzkyGolayFilter(const ICoreMatrix& x, const size_t& order,
const size_t& frameLength,
const ICoreMatrix* weights,
std::string* whyNot = nullptr);
// MATLAB's `[y, i, xmedian, xsigma] = hampel(x, k, nsigma)`: the outlier
// filter that replaces a sample by its local median when it is more than
// `nsigma` local standard deviations away from it.
//
// The window is 2k+1 wide with SHRINKING endpoints, `xsigma` is the moving
// MEDIAN absolute deviation scaled by 1/(sqrt(2)*erfcinv(3/2)) = 1.4826...
// (so it estimates sigma for Gaussian data), and `outliers` is MATLAB's
// logical mask as 1s and 0s. The comparison is written NEGATED -- a sample
// is an outlier when NOT(|x - med| <= nsigma*sigma) -- so a NaN sample is
// an outlier, which a straight `>` would answer the other way.
static bool outlierFilter(const ICoreMatrix& x, const size_t& k, const double& nsigma,
ICoreMatrix& filtered, ICoreMatrix& outliers,
ICoreMatrix& median, ICoreMatrix& sigma,
std::string* whyNot = nullptr);
};
};
ICoreSignalQuality.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreSignalQuality.h
ICoreSignalQuality#
ICoreSignalQuality.h:45 · class · nested Measurement · 0 declaration(s)
The four single-tone quality measurements (the toolbox board's T2.25): snr, thd, sinad and sfdr -- how much of a signal's power is the tone you meant, and how much is everything else.
class ICoreSignalQuality {
public:
// What a measurement answers. Not every field is filled by every name --
// each method's comment says which -- and the ratio is always in dB.
struct Measurement {
double ratio = 0.0; // r, in dB
double power = 0.0; // the noise/distortion/spur power, dB
double frequency = 0.0; // sfdr's spur frequency
std::vector<double> harmonicPower; // thd's per-harmonic power, dB
std::vector<double> harmonicFrequency; // and where each one was found
};
// snr(x, fs[, n]) -- the fundamental's power over everything that is not
// the fundamental, its first `n` harmonics (default 6) or DC. Fills
// `ratio` and `power` (the total noise, dB).
//
// `aliased` is MATLAB's 'aliased' word: a harmonic above Nyquist is folded
// back onto the band rather than ignored.
static bool signalToNoise(const ICoreMatrix& signal, const double& sampleRate,
const size_t& harmonics, const bool& aliased,
Measurement& out, std::string* whyNot);
// snr(x, y) -- the two-signal form, which has no spectrum in it at all:
// 10*log10(sum(x.^2) / sum(y.^2)). MATLAB tells this form from the one
// above by y being a VECTOR rather than a scalar sample rate, and so does
// the console.
static bool signalToNoise(const ICoreMatrix& signal, const ICoreMatrix& noise,
Measurement& out, std::string* whyNot);
// thd(x, fs[, n]) -- the summed power of harmonics 2..n over the
// fundamental's. Fills `ratio`, and `harmonicPower`/`harmonicFrequency`
// with the fundamental FIRST (MATLAB's order), each power in dB.
static bool totalHarmonicDistortion(const ICoreMatrix& signal, const double& sampleRate,
const size_t& harmonics, const bool& aliased,
Measurement& out, std::string* whyNot);
// sinad(x, fs) -- the fundamental over everything else, harmonics
// INCLUDED, which is the one line that separates it from snr: it removes
// DC and the fundamental and calls the whole remainder noise. Fills
// `ratio` and `power`.
static bool signalToNoiseAndDistortion(const ICoreMatrix& signal, const double& sampleRate,
Measurement& out, std::string* whyNot);
// sfdr(x, fs[, msd]) -- the fundamental over the largest SPUR, whatever it
// is and wherever it is; `msd` is a minimum spurious distance, a band
// around the fundamental to exclude. Fills `ratio`, `power` (the spur's,
// dB) and `frequency`.
static bool spuriousFreeDynamicRange(const ICoreMatrix& signal, const double& sampleRate,
const double& minimumSpurDistance, Measurement& out,
std::string* whyNot);
};
};
ICoreSpectralEstimation.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreSpectralEstimation.h
ICoreSpectralEstimation#
ICoreSpectralEstimation.h:44 · class · nested Options, Spectrum, ShortTime · 0 declaration(s)
The non-parametric spectral estimates (toolbox rows T2.14–T2.17): periodogram, pwelch, cpsd, mscohere, tfestimate and spectrogram.
class ICoreSpectralEstimation {
public:
// Whether the answer is a DENSITY (power per unit frequency, MATLAB's
// 'psd') or a POWER (per bin, 'power'). The difference is the normaliser.
enum class Scaling { Density, Power };
// Which part of the circle is reported. `OneSided` folds and is MATLAB's
// default for a real signal; `Centered` puts DC in the middle.
enum class Range { OneSided, TwoSided, Centered };
// What the six names ask this machine for. `Auto` is one signal;
// `Cross`, `Coherence` and `Transfer` take two.
enum class Estimate { Auto, Cross, Coherence, Transfer };
struct Options {
size_t points = 0; // nfft; 0 asks for the estimate's default
double sampleRate = 0.0; // 0 answers in radians per sample (Fs = 2*pi)
Range range = Range::OneSided;
Scaling scaling = Scaling::Density;
};
// The estimate and the grid it was taken on, which travel together.
//
// Two of the four estimates are COMPLEX -- a cross spectrum's phase is the
// lag between the two signals and a transfer estimate's is the system's,
// which is most of what either is asked for -- so the answer carries both
// shapes and says which one it filled. A coherence and an auto spectrum
// are real.
struct Spectrum {
ICoreMatrix values; // filled when isComplex is false
ICoreComplexMatrix complexValues; // filled when isComplex is true
bool isComplex = false;
ICoreMatrix frequencies;
};
// MATLAB's [pxx, f] = periodogram(x[, window[, nfft[, fs]]]): one segment,
// one transform. `window` may be null for MATLAB's default rectangular
// window of the signal's own length; `points` of 0 is
// `max(256, 2^nextpow2(n))`.
static bool periodogram(const ICoreMatrix& signal, const ICoreMatrix* window,
const Options& options, Spectrum& out,
std::string* whyNot = nullptr);
// MATLAB's pwelch / cpsd / mscohere / tfestimate: the same estimate over
// overlapping segments, averaged.
//
// `window` is the caller's window vector, or null; `windowLength` is a
// scalar window argument (0 for none), which asks for a Hamming window of
// that length. `overlap` is used only when `overlapGiven` is set.
//
// `second` is the other signal for the three two-signal estimates and is
// null for `Auto`. The ORDER matters and is MATLAB's: a transfer estimate
// is the cross spectrum of (y, x) over the auto spectrum of x, so a
// reversed pair answers its reciprocal rather than an error.
static bool welch(const ICoreMatrix& signal, const ICoreMatrix* second,
const ICoreMatrix* window, const size_t& windowLength,
const bool& overlapGiven, const size_t& overlap,
const Estimate& estimate, const Options& options,
Spectrum& out, std::string* whyNot = nullptr);
// The short-time transform behind `spectrogram`: one column per segment,
// the segments' centre times, and the frequency grid.
//
// `values` is the complex STFT — MATLAB's first output — and `power` is
// its PSD (the fourth output), which is not `abs(values)^2`: it carries
// the same normalisation and fold the estimates above do.
struct ShortTime {
ICoreComplexMatrix values;
ICoreMatrix frequencies;
ICoreMatrix times;
ICoreMatrix power;
};
static bool shortTime(const ICoreMatrix& signal, const ICoreMatrix* window,
const size_t& windowLength, const bool& overlapGiven,
const size_t& overlap, const Options& options,
ShortTime& out, std::string* whyNot = nullptr);
};
};
ICoreSpectralMeasures.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreSpectralMeasures.h
ICoreSpectralMeasures#
ICoreSpectralMeasures.h:37 · class · nested Band · 0 declaration(s)
The measurements taken OF a signal or of its spectrum (toolbox row T2.24): rms, peak2peak, peak2rms, rssq, bandpower, meanfreq, medfreq, obw and powerbw.
class ICoreSpectralMeasures {
public:
// MATLAB's rms, peak2peak, peak2rms and rssq: exact arithmetic over the
// samples, no spectrum involved. `rms` is core MATLAB
// (toolbox/matlab/datafun/) rather than Signal Processing, and lands here
// because the core board is closed and has no row for it.
static bool rootMeanSquare(const ICoreMatrix& signal, double& out,
std::string* whyNot = nullptr);
static bool peakToPeak(const ICoreMatrix& signal, double& out,
std::string* whyNot = nullptr);
static bool peakToRms(const ICoreMatrix& signal, double& out,
std::string* whyNot = nullptr);
static bool rootSumSquare(const ICoreMatrix& signal, double& out,
std::string* whyNot = nullptr);
// MATLAB's bandpower(x) — the mean square, exactly, with no spectrum at
// all — and bandpower(x, fs, [flo fhi]), which integrates a Hamming
// periodogram over the band. `hasRange` picks between them.
static bool bandPower(const ICoreMatrix& signal, const double& sampleRate,
const bool& hasRange, const double& low, const double& high,
double& out, std::string* whyNot = nullptr);
// What the four spectrum measurements answer. Each takes the signal and a
// sample rate (0 for radians per sample).
struct Band {
double frequency = 0.0; // meanfreq / medfreq
double power = 0.0; // the power in the band each one measured
double bandwidth = 0.0; // obw / powerbw
double low = 0.0; // the band's edges, for obw and powerbw
double high = 0.0;
};
// MATLAB's meanfreq and medfreq: the spectrum's centre of mass, and the
// frequency that halves its power.
static bool meanFrequency(const ICoreMatrix& signal, const double& sampleRate,
Band& out, std::string* whyNot = nullptr);
static bool medianFrequency(const ICoreMatrix& signal, const double& sampleRate,
Band& out, std::string* whyNot = nullptr);
// MATLAB's obw(x[, fs[, [], P]]): the band holding P percent of the power
// (99 by default), and powerbw(x[, fs[, [], R]]): the band whose density
// stays within R dB of the peak.
//
// ⚠ **`drop` HAS NO DEFAULT HERE AND THE CALLER MUST PASS MATLAB'S**, which
// is `10*log10(2)` = 3.0102999566398120 dB and NOT 3. `powerbw.m:18` sets
// it as `R = 10*log10(1/2)` -- the EXACT half power -- while the phrase
// everyone repeats, including MATLAB's own documentation title, is "the
// 3-dB bandwidth". The difference between 10^(-3/10) = 0.501187 and 1/2 is
// 0.24% of the reference level and about 0.34% of the answer, which is
// small enough to look like rounding and far too large to be it: this
// console refused the name for an afternoon over exactly that gap, on the
// theory that the shipped function did not match its own published file.
// It matched; the default did not. An explicit R reproduces to the last
// digit at 3, 6 and 10 dB.
static bool occupiedBandwidth(const ICoreMatrix& signal, const double& sampleRate,
const double& percent, Band& out,
std::string* whyNot = nullptr);
static bool powerBandwidth(const ICoreMatrix& signal, const double& sampleRate,
const double& drop, Band& out, std::string* whyNot = nullptr);
};
};
ICoreWaveforms.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreWaveforms.h
ICoreWaveforms#
ICoreWaveforms.h:28 · class · 6 declaration(s)
The Signal Processing Toolbox waveform generators (T2.26): periodic waves, aperiodic pulses and the swept-frequency cosine.
class ICoreWaveforms {
public:
// sin(pi * x) and cos(pi * x), exact at the whole and half-whole numbers.
static double sinpi(const double& x);
static double cospi(const double& x);
// sawtooth(t) / sawtooth(t, width) -- the modified sawtooth: a rising ramp
// from -1 to 1 over the first `width` of each 2*pi period and a falling
// ramp back over the rest. width is in [0, 1] and defaults to 1; the
// triangle wave is width = 0.5. Refuses a width outside [0, 1].
static ICoreMatrix sawtooth(const ICoreMatrix& t, const double& width, std::string* whyNot);
// square(t) / square(t, duty) -- +1 for the first `duty` PERCENT of each
// 2*pi period and -1 for the rest; duty defaults to 50. MATLAB does not
// range-check duty (a duty of 200 is all +1), and neither does this.
static ICoreMatrix square(const ICoreMatrix& t, const double& duty);
// sinc(x) = sin(pi*x) / (pi*x), the NORMALISED sinc with sinc(0) = 1 and
// a zero at every non-zero whole number. Not sin(x)/x.
static ICoreMatrix sinc(const ICoreMatrix& x);
// rectpuls(t) / rectpuls(t, w) -- the unit-height rectangle of width `w`
// (default 1) centred on t = 0. MATLAB's own half-open convention: the
// pulse is 1 on [-w/2, w/2) and its left edge is included to within eps,
// which is why the edges are compared against eps rather than exactly.
static ICoreMatrix rectpuls(const ICoreMatrix& t, const double& width);
// tripuls(t) / tripuls(t, w) / tripuls(t, w, skew) -- the unit-height
// triangle of width `w` (default 1) centred on t = 0, its peak moved to
// skew * w/2. skew is in [-1, 1] and defaults to 0 (symmetric).
static ICoreMatrix tripuls(const ICoreMatrix& t, const double& width, const double& skew,
std::string* whyNot);
// gauspuls(t, fc, bw, bwr) -- the Gaussian-modulated sinusoidal RF pulse,
// `part` selecting which of MATLAB's three outputs is wanted:
// 0 yc, the in-phase pulse (the single-output answer)
// 1 ys, the quadrature pulse
// 2 ye, the envelope
// fc defaults to 1000 Hz, bw to 0.5 (fractional bandwidth), bwr to -6 dB.
static ICoreMatrix gauspuls(const ICoreMatrix& t, const double& centerHz,
const double& fractionalBandwidth, const double& bandwidthRefDb,
const int& part, std::string* whyNot);
// gauspuls('cutoff', fc, bw, bwr, tpr) -- the time at which the pulse
// envelope has fallen to `tpr` dB below its peak (tpr defaults to -60).
static bool gauspulsCutoff(const double& centerHz, const double& fractionalBandwidth,
const double& bandwidthRefDb, const double& trailingPulseDb,
double& cutoffOut, std::string* whyNot);
// chirp(t, f0, t1, f1, method, phi, quadraticType) -- the swept-frequency
// cosine that is f0 Hz at t = 0 and f1 Hz at t = t1. Defaults, MATLAB's:
// f0 = 0, t1 = 1, f1 = 100, phi = 0 degrees, method "linear".
// `method` is "linear", "quadratic" or "logarithmic"; `quadraticType` is
// "", "concave" or "convex" and is read only by the quadratic sweep,
// where it flips the sweep MATLAB would otherwise choose from the
// direction of f0 -> f1.
static ICoreMatrix chirp(const ICoreMatrix& t, const double& f0, const double& t1,
const double& f1, const std::string& method, const double& phaseDeg,
const std::string& quadraticType, std::string* whyNot);
// pulstran's SAMPLED-prototype half: the pulse train made by shifting a
// sampled prototype `p` (taken at `fs` samples per second) to each delay
// in `delays`, scaling by `amplitudes` when they are given, and summing.
// `method` is an interp1 method name. The CONTINUOUS half -- a function
// handle or a function name shifted per delay -- is not here: it must
// call back into the expression evaluator, which is above this layer, so
// the evaluator owns that arm and this class owns the arithmetic.
static ICoreMatrix pulstranSampled(const ICoreMatrix& t, const std::vector<double>& delays,
const std::vector<double>& amplitudes,
const ICoreMatrix& prototype, const double& sampleRate,
const std::string& method, std::string* whyNot);
};
};
ICoreWindows.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreWindows.h
ICoreWindows#
ICoreWindows.h:39 · class · 4 declaration(s)
The Signal Processing Toolbox spectral windows (T2.22): the sixteen names window dispatches to, each answering an N x 1 COLUMN.
class ICoreWindows {
public:
enum class Sampling { Symmetric, Periodic };
// The one entry point, so `window(@hann, n)` and `hann(n)` cannot drift:
// both come through here. `name` is the MATLAB spelling, lowercase.
//
// `parameter` is the second argument the four parameterised windows take
// and the other twelve refuse -- kaiser's beta (default 0.5), gausswin's
// alpha (2.5), tukeywin's ratio (0.5) and chebwin's sidelobe attenuation
// in dB (100). `hasParameter` says whether the caller gave one.
//
// `length` is a double because MATLAB takes one: a non-integer length is
// ROUNDED (MATLAB warns and rounds; this rounds), a length of 0 answers
// the 0 x 1 empty and a length of 1 answers the 1 x 1 one -- both of them
// before any formula runs, which is why no window here divides by N - 1
// and gets infinity.
static bool build(const std::string& name, const double& length, const Sampling& sampling,
const bool& hasParameter, const double& parameter,
ICoreMatrix& out, std::string* whyNot);
// The names build() answers, in `window`'s own order. A name outside this
// list is refused by build() with the list in the sentence.
static const std::vector<std::string>& names();
// Whether `name` takes the sampling flag, and whether it takes a
// parameter -- the evaluator asks so it can refuse `bartlett(n, 5)` with a
// sentence about bartlett rather than about the argument count.
static bool takesSampling(const std::string& name);
static bool takesParameter(const std::string& name);
// The modified Bessel function of the first kind, order zero -- kaiser's
// engine, and the one piece of new numerics this file adds. Summed from
// the power series I0(x) = sum (x/2)^(2k) / (k!)^2, whose terms are all
// POSITIVE, so there is no cancellation and the relative error stays at
// rounding for every x a window uses.
static double besselI0(const double& x);
};
};
ICoreZeroPhaseFilter.h#
src/ICoreBlocks/ICoreMath/SignalProcessing/ICoreZeroPhaseFilter.h
ICoreZeroPhaseFilter#
ICoreZeroPhaseFilter.h:32 · class · 0 declaration(s)
MATLAB's filtfilt (T2.10): the same filter run forwards and then backwards, so its phase response cancels and only the squared magnitude remains.
class ICoreZeroPhaseFilter {
public:
// filtfilt(b, a, x). `x` is filtered down its COLUMNS, or along itself
// when it is a vector -- MATLAB's rule -- and the answer has x's shape.
//
// Refuses, with MATLAB's own reason, a zero leading denominator
// coefficient and a signal shorter than the pad the filter needs.
static ICoreMatrix apply(const ICoreMatrix& numerator, const ICoreMatrix& denominator,
const ICoreMatrix& x, std::string* whyNot);
// filtfilt(sos, g, x) -- second-order sections, one biquad per ROW of the
// L x 6 `sos`, with `g` the L+1 scale factors MATLAB's `tf2sos` answers
// beside them. Each stage is run through `apply`'s procedure in turn, on
// the OUTPUT of the one before it, which is what filtfilt.m's `numStage`
// loop does -- so the pad length is the stage's, not the cascade's.
//
// `g` may be empty, meaning unity: MATLAB accepts `filtfilt(sos, 1, x)`
// and a scalar gain the same way.
static ICoreMatrix applySections(const ICoreMatrix& sections, const ICoreMatrix& gains,
const ICoreMatrix& x, std::string* whyNot);
};
};