/* #define DEBUGTRACE_ENABLED */ #include "lasp_biquadbank.h" #include "debugtrace.hpp" #include "lasp_thread.h" #include #include using std::cerr; using std::endl; using rte = std::runtime_error; using lock = std::scoped_lock; SeriesBiquad::SeriesBiquad(const vd &filter_coefs) { DEBUGTRACE_ENTER; if (filter_coefs.n_cols != 1) { throw rte("Expected filter coefficients for a single SeriesBiquad as a " "single column with length 6 x n_filters"); } if (filter_coefs.n_rows % 6 != 0) { cerr << "Number of rows given: " << filter_coefs.n_rows << endl; throw rte("filter_coefs should be multiple of 6, given: " + std::to_string(filter_coefs.n_rows)); } us nfilters = filter_coefs.n_rows / 6; /// Initialize state to zero state = dmat(2, nfilters, arma::fill::zeros); sos.resize(6, nfilters); for (us i = 0; i < nfilters; i++) { sos.col(i) = filter_coefs.subvec(6 * i, 6 * (i + 1) - 1); } /// Check if third row in this matrix equals unity. if (!arma::approx_equal(sos.row(3), arma::rowvec(nfilters, arma::fill::ones), "absdiff", 1e-9)) { std::cerr << "Read row: " << sos.row(3) << endl; throw rte( "Filter coefficients should have fourth element (a0) equal to 1.0"); } } SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) { if (fs <= 0) { throw rte("Invalid sampling frequency: " + std::to_string(fs) + " [Hz]"); } if (cuton_Hz <= 0) { throw rte("Invalid cuton frequency: " + std::to_string(cuton_Hz) + " [Hz]"); } if (cuton_Hz >= 0.98 * fs / 2) { throw rte( "Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" + std::to_string(cuton_Hz) + " [Hz]"); } const d tau = 1 / (2 * arma::datum::pi * cuton_Hz); const d facnum = 2 * fs * tau / (1 + 2 * fs * tau); const d facden = (1 - 2 * fs * tau) / (1 + 2 * fs * tau); vd coefs(6); // b0 coefs(0) = facnum; // b1 coefs(1) = -facnum; // b2 coefs(2) = 0; // a0 coefs(3) = 1; // a1 coefs(4) = facden; // a2 coefs(5) = 0; return SeriesBiquad(coefs); } std::unique_ptr SeriesBiquad::clone() const { // sos.as_col() concatenates all columns, exactly what we want. return std::make_unique(sos.as_col()); } void SeriesBiquad::reset() { DEBUGTRACE_ENTER; state.zeros(); } void SeriesBiquad::filter(vd &inout) { DEBUGTRACE_ENTER; /// Implementation is based on Proakis & Manolakis - Digital Signal /// Processing, Fourth Edition, p. 550 for (us filterno = 0; filterno < sos.n_cols; filterno++) { d b0 = sos(0, filterno); d b1 = sos(1, filterno); d b2 = sos(2, filterno); d a1 = sos(4, filterno); d a2 = sos(5, filterno); d w1 = state(0, filterno); d w2 = state(1, filterno); for (us sample = 0; sample < inout.size(); sample++) { d w0 = inout(sample) - a1 * w1 - a2 * w2; d yn = b0 * w0 + b1 * w1 + b2 * w2; w2 = w1; w1 = w0; inout(sample) = yn; } state(0, filterno) = w1; state(1, filterno) = w2; } } BiquadBank::BiquadBank(const dmat &filters, const vd *gains) { DEBUGTRACE_ENTER; /** * @brief Make sure the pool is created once, such that all threads are ready * for use. */ lock lck(_mtx); for (us i = 0; i < filters.n_cols; i++) { _filters.emplace_back(filters.col(i)); } if (gains != nullptr) { setGains(*gains); } else { _gains = vd(_filters.size(), arma::fill::ones); } } void BiquadBank::setGains(const vd &gains) { DEBUGTRACE_ENTER; lock lck(_mtx); const us nfilters = _filters.size(); if (gains.size() != nfilters) { throw rte("Invalid number of gain values given."); } _gains = gains; } void BiquadBank::filter(vd &inout) { lock lck(_mtx); std::vector> futs; #if 1 vd inout_cpy = inout; for (us i = 0; i < _filters.size(); i++) { futs.emplace_back(_pool.submit( [&](vd inout, us i) { _filters[i].filter(inout); return inout; }, // Launch a task to filter. inout_cpy, i // Column i as argument to the lambda function above. )); } // Zero-out in-out and sum-up the filtered values inout.zeros(); for (us i = 0; i < _filters.size(); i++) { inout += futs[i].get() * _gains[i]; } #else /// Testing, unthreaded version vd inout_cpy = inout; inout.zeros(); for (us i = 0; i < _filters.size(); i++) { vd cpy_again = inout_cpy; _filters[i].filter(cpy_again); inout += cpy_again * _gains(i); } #endif } void BiquadBank::reset() { DEBUGTRACE_ENTER; lock lck(_mtx); for (auto &f : _filters) { f.reset(); } } std::unique_ptr BiquadBank::clone() const { lock lck(_mtx); return std::make_unique(_filters, _gains); }