This commit is contained in:
Steven Dan
2025-12-11 09:43:42 +08:00
commit d8b2974133
1822 changed files with 280037 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/xmath.h"
#include "dsp/defines.h"
#include "control/biquad.h"
#include "control/cascaded_biquads.h"
#include "control/drc.h"
#include "control/signal_chain.h"
#include "control/reverb.h"
#include "control/helpers.h"
#include "control/reverb_plate.h"

View File

@@ -0,0 +1,321 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/types.h"
#include "dsp/biquad.h"
/**
* @brief Initialise a slewing biquad filter object.
* This sets the active filter coefficients to the target value
*
* @param target_coeffs Filter coefficients
* @param lsh Filter left shift compensation value
* @param slew_shift Shift value used in the exponential slew
* @return biquad_slew_t Slewing biquad object
*/
biquad_slew_t adsp_biquad_slew_init(
q2_30 target_coeffs[8],
left_shift_t lsh,
left_shift_t slew_shift
);
/**
* @brief Update the target coefficients in a slewing biquad filter object.
* This updates the target coefficients, and manages any change in filter
* coefficient left shift. This may require shifting the active filter
* coefficients and states.
*
* @param slew_state Slewing biquad state object
* @param states Filter state for each biquad channel
* @param channels Number of channels in states
* @param target_coeffs New filter coefficients
* @param lsh New filter left shift compensation value
*/
void adsp_biquad_slew_update_coeffs(
biquad_slew_t* slew_state,
int32_t** states,
int32_t channels,
q2_30 target_coeffs[8],
left_shift_t lsh
);
/**
* @brief Design biquad filter bypass
* This function creeates a bypass biquad filter. Only the b0 coefficient is set.
*
* @param coeffs Bypass filter coefficients
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_bypass(q2_30 coeffs[5]);
/**
* @brief Design mute biquad filter
* This function creates a mute biquad filter. All the coefficients are 0.
*
* @param coeffs Mute filter coefficients
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_mute(q2_30 coeffs[5]);
/**
* @brief Design gain biquad filter
* This function creates a biquad filter with a specified gain
*
* @param coeffs Gain filter coefficients
* @param gain_db Gain in dB
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_gain(q2_30 coeffs[5], const float gain_db);
/**
* @brief Design lowpass biquad filter
* This function creates a biquad filter with a lowpass response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* @param coeffs Lowpass filter coefficients
* @param fc Cutoff frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_lowpass(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q);
/**
* @brief Design highpass biquad filter
* This function creates a biquad filter with a highpass response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* @param coeffs Highpass filter coefficients
* @param fc Cutoff frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_highpass(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q);
/**
* @brief Design bandpass biquad filter
* This function creates a biquad filter with a bandpass response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* @param coeffs Bandpass filter coefficients
* @param fc Central frequency
* @param fs Sampling frequency
* @param bandwidth Bandwidth
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_bandpass(
q2_30 coeffs[5],
const float fc,
const float fs,
const float bandwidth);
/**
* @brief Design bandstop biquad filter
* This function creates a biquad filter with a bandstop response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* @param coeffs Bandstop filter coefficients
* @param fc Central frequency
* @param fs Sampling frequency
* @param bandwidth Bandwidth
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_bandstop(
q2_30 coeffs[5],
const float fc,
const float fs,
const float bandwidth);
/**
* @brief Design notch biquad filter
* This function creates a biquad filter with an notch response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* @param coeffs Notch filter coefficients
* @param fc Central frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_notch(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q);
/**
* @brief Design allpass biquad filter
* This function creates a biquad filter with an allpass response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* @param coeffs Allpass filter coefficients
* @param fc Central frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_allpass(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q);
/**
* @brief Design peaking biquad filter
* This function creates a biquad filter with a peaking response
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* The gain must be less than 18 dB, otherwise the coefficients may overflow.
* If the gain is greater than 18 dB, it is saturated to that value.
*
* @param coeffs Peaking filter coefficients
* @param fc Central frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @param gain_db Gain in dB
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_peaking(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db);
/**
* @brief Design constant Q peaking biquad filter
* This function creates a biquad filter with a constant Q peaking response.
*
* Constant Q means that the bandwidth of the filter remains constant
* as the gain varies. It is commonly used for graphic equalisers.
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* The gain must be less than 18 dB, otherwise the coefficients may overflow.
* If the gain is greater than 18 dB, it is saturated to that value.
*
* @param coeffs Constant Q filter coefficients
* @param fc Central frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @param gain_db Gain in dB
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_const_q(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db);
/**
* @brief Design lowshelf biquad filter
* This function creates a biquad filter with a lowshelf response.
*
* The Q factor is defined in a similar way to standard low pass, i.e.
* Q > 0.707 will yield peakiness (where the shelf response does not
* monotonically change). The level change at f will be boost_db/2.
* ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* The gain must be less than 12 dB, otherwise the coefficients may overflow.
* If the gain is greater than 12 dB, it is saturated to that value.
*
* @param coeffs Lowshelf filter coefficients
* @param fc Cutoff frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @param gain_db Gain in dB
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_lowshelf(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db);
/**
* @brief Design highshelf biquad filter
* This function creates a biquad filter with a highshelf response.
*
* The Q factor is defined in a similar way to standard high pass, i.e.
* Q > 0.707 will yield peakiness. The level change at f will be
* boost_db/2. ``fc`` must be less than ``fs/2``, otherwise it will be saturated to
* ``fs/2``.
*
* The gain must be less than 12 dB, otherwise the coefficients may overflow.
* If the gain is greater than 12 dB, it is saturated to that value.
*
* @param coeffs Highshelf filter coefficients
* @param fc Cutoff frequency
* @param fs Sampling frequency
* @param filter_Q Filter Q
* @param gain_db Gain in dB
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_highshelf(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db);
/**
* @brief Design Linkwitz transform biquad filter
* This function creates a biquad filter with a Linkwitz transform response.
*
* The Linkwitz Transform is commonly used to change the low frequency
* roll off slope of a loudspeaker. When applied to a loudspeaker, it
* will change the cutoff frequency from f0 to fp, and the quality
* factor from q0 to qp. ``f0`` and ``fp`` must be less than ``fs/2``,
* otherwise they will be saturated to ``fs/2``.
*
* @param coeffs Linkwitz filter coefficients
* @param f0 Original cutoff frequency
* @param fs Sampling frequency
* @param q0 Original quality factor at f0
* @param fp Target cutoff frequency
* @param qp Target quality factor of the filter
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_design_biquad_linkwitz(
q2_30 coeffs[5],
const float f0,
const float fs,
const float q0,
const float fp,
const float qp);
/**
* @brief Modify the gain of a set of biquad filter coefficients
*
* @param coeffs Existing filter coefficients
* @param b_sh Existing left shift compensation value
* @param gain_db Gain in dB
* @return left_shift_t Left shift compensation value
*/
left_shift_t adsp_apply_biquad_gain(q2_30 coeffs[5], left_shift_t b_sh, float gain_db);

View File

@@ -0,0 +1,50 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/types.h"
/**
* @brief Design Nth order Butterworth lowpass filter
* Generate N/2 sets of biquad coefficients for a Butterworth low-pass
* filter.
*
* The function implements the algorithm described in Neil Robertson's article:
* `"Designing Cascaded Biquad Filters Using the Pole-Zero Method"
* <https://www.dsprelated.com/showarticle/1137.php>`_.
* It uses the bilinear transform to convert the analog filter poles to the z-plane.
*
* @param coeffs Butterworth lowpass filter coefficients
* @param N Order of the filter (must be even)
* @param fc Central frequency (-3 dB)
* @param fs Sampling frequency
*/
void adsp_design_butterworth_lowpass_8b(
q2_30 coeffs[40],
const unsigned N,
const float fc,
const float fs);
/**
* @brief Design Nth order Butterworth highpass filter
* Generate N/2 sets of biquad coefficients for a Butterworth high-pass
* filter.
*
* The function implements the algorithm described in Neil Robertson's article:
* `"Designing Cascaded Biquad Filters Using the Pole-Zero Method"
* <https://www.dsprelated.com/showarticle/1137.php>`_.
* It uses the bilinear transform to convert the analog filter poles to the z-plane.
*
* @param coeffs Butterworth highpass filter coefficients
* @param N Order of the filter (must be even)
* @param fc Central frequency (-3 dB)
* @param fs Sampling frequency
*/
void adsp_design_butterworth_highpass_8b(
q2_30 coeffs[40],
const unsigned N,
const float fc,
const float fs);

View File

@@ -0,0 +1,127 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/drc.h"
/**
* @brief Initialise an envelope detector object
*
* @param fs Sampling frequency
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @return env_detector_t Initialised envelope detector object
* @note Detect time is optional. If specified, attack and release times will be equal.
*/
env_detector_t adsp_env_detector_init(
float fs,
float attack_t,
float release_t);
/**
* @brief Initialise a (hard) limiter peak object
*
* @param fs Sampling frequency
* @param threshold_db Threshold in dB
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @return limiter_t Initialised limiter object
*/
limiter_t adsp_limiter_peak_init(
float fs,
float threshold_db,
float attack_t,
float release_t);
/**
* @brief Initialise an RMS limiter object
*
* @param fs Sampling frequency
* @param threshold_db Threshold in dB
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @return limiter_t Initialised limiter object
*/
limiter_t adsp_limiter_rms_init(
float fs,
float threshold_db,
float attack_t,
float release_t);
/**
* @brief Initialise a noise gate object
*
* @param fs Sampling frequency
* @param threshold_db Threshold in dB
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @return noise_gate_t Initialised noise gate object
*/
noise_gate_t adsp_noise_gate_init(
float fs,
float threshold_db,
float attack_t,
float release_t);
/**
* @brief Initialise a noise suppressor (expander) object
*
* @param fs Sampling frequency
* @param threshold_db Threshold in dB
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @param ratio Noise suppression ratio
* @return noise_suppressor_expander_t Initialised noise suppressor (expander) object
*/
noise_suppressor_expander_t adsp_noise_suppressor_expander_init(
float fs,
float threshold_db,
float attack_t,
float release_t,
float ratio);
/**
* @brief Set the threshold of a noise suppressor (expander)
*
* @param nse Noise suppressor (Expander) object
* @param new_th New threshold in Q_SIG
*/
void adsp_noise_suppressor_expander_set_th(
noise_suppressor_expander_t * nse,
int32_t new_th);
/**
* @brief Initialise a compressor object
*
* @param fs Sampling frequency
* @param threshold_db Threshold in dB
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @param ratio Compression ratio
* @return compressor_t Initialised compressor object
*/
compressor_t adsp_compressor_rms_init(
float fs,
float threshold_db,
float attack_t,
float release_t,
float ratio);
/**
* @brief Initialise a stereo compressor object
*
* @param fs Sampling frequency
* @param threshold_db Threshold in dB
* @param attack_t Attack time in seconds
* @param release_t Release time in seconds
* @param ratio Compression ratio
* @return compressor_stereo_t Initialised stereo compressor object
*/
compressor_stereo_t adsp_compressor_rms_stereo_init(
float fs,
float threshold_db,
float attack_t,
float release_t,
float ratio);

View File

@@ -0,0 +1,332 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <xcore/assert.h> // for xassert()
#include <math.h>
#include <limits.h>
#include "xmath/xmath.h"
#include <dsp/_helpers/generic_utils.h> // for Q_alpha
#include <dsp/defines.h>
/**
* @brief Convert a float value to a fixed point int32 number in
* q format. If the value of x is outside the fixed point range,
* this will overflow.
*
* @param x A floating point value
* @param q Q format of the output
* @return int32_t x in q fixed point format
*/
static inline int32_t _float2fixed( float x, int32_t q )
{
#ifdef __XS3A__
int32_t sign, exp, mant;
asm("fsexp %0, %1, %2": "=r" (sign), "=r" (exp): "r" (x));
asm("fmant %0, %1": "=r" (mant): "r" (x));
if(sign){mant = -mant;}
// mant to q
right_shift_t shr = -q - exp + 23;
return mant >>= shr;
#else
if ( x < 0.0f ) return (((float)(1u << q)) * x - 0.5f);
if ( x > 0.0f ) return (((float)((1u << q) - 1)) * x + 0.5f);
return 0;
#endif
}
/**
* @brief Convert a float value to a fixed point int32 number in
* q format. If the value of x is outside the positive
* fixed point range,this will overflow.
*
* @param x A floating point value
* @param q Q format of the output
* @return int32_t x in q fixed point format
*/
static inline int32_t _positive_float2fixed(float x, int32_t q)
{
#ifdef __XS3A__
int32_t sign, exp, mant;
asm("fsexp %0, %1, %2": "=r" (sign), "=r" (exp): "r" (x));
asm("fmant %0, %1": "=r" (mant): "r" (x));
// mant to q
right_shift_t shr = -q - exp + 23;
return mant >>= shr;
#else
return ((float)((1u << q) - 1)) * x + 0.5f
#endif
}
/**
* @brief Convert a float value to a fixed point int32 number in
* q format. If the value of x is outside the fixed point range,
* this will raise an assertion.
*
* @param x A floating point value
* @param q Q format of the output
* @return int32_t x in q fixed point format
*/
static inline int32_t _float2fixed_assert( float x, int32_t q )
{
float max_val = (float)(1<<(31-q));
xassert(x <= max_val); // Too much gain, cannot be represented in desired number format
xassert(x > -max_val);
return _float2fixed(x, q);
}
/**
* @brief Convert a float value to a fixed point int32 number in
* q format. If the value of x is outside the fixed point range,
* it is saturated.
*
* @param x A floating point value
* @param q Q format of the output
* @return int32_t x in q fixed point format
*/
static inline int32_t _float2fixed_saturate( float x, int32_t q )
{
if (x < -(1 << (31-q))) return INT32_MIN;
if (x >= (1 << (31-q))) return INT32_MAX;
return _float2fixed(x, q);
}
/**
* @brief Convert a float value to a fixed point int32 number in
* q format. Negative input will result in the output of zero.
* If the value of x is outside the fixed point range,
* it is saturated.
*
* @param x A floating point value
* @param q Q format of the output
* @return int32_t x in q fixed point format
*/
static inline int32_t _positive_float2fixed_saturate(float x, int32_t q)
{
if ( x <= 0.0f ) return 0;
if ( x >= (1 << (31-q))) return INT32_MAX;
return _positive_float2fixed(x, q);
}
/**
* @brief Convert a positive float value to a fixed point int32 number in
* Q_SIG format. By assuming the value is positive (e.g. a gain value
* converted from decibels), negative cases can be ignored. If the
* value of x exceeds the fixed point maximum, it is saturated.
*
* @param x A positive floating point value
* @return int32_t x in Q_SIG fixed point format
*/
static inline int32_t _positive_float2fixed_qsig(float x)
{
return _positive_float2fixed_saturate(x, Q_SIG);
}
/**
* @brief Convert a value in decibels to a fixed point int32 number in
* a given q format. If the level exceeds the fixed point maximum,
* it is saturated.
*
* @param level_db Level in db
* @param q Q format of the output
* @return int32_t level_db as an int32_t
*/
static inline int32_t db_to_qxx(float level_db, int32_t q) {
float A = powf(10.0f, (level_db / 20.0f));
int32_t out = _positive_float2fixed_saturate(A, q);
return out;
}
/**
* @brief Convert a value in decibels to a fixed point int32 number in
* Q_SIG format. If the level exceeds the fixed point maximum,
* it is saturated.
*
* @param level_db Level in db
* @return int32_t level_db as an int32_t
*/
static inline int32_t db_to_q_sig(float level_db) {
return db_to_qxx(level_db, Q_SIG);
}
/**
* @brief Convert a power level in decibels to a fixed point int32 number in
* a given q format. If the level exceeds the fixed point maximum,
* it is saturated.
*
* @param level_db Power level in db
* @param q Q format of the output
* @return int32_t level_db as an int32_t
*/
static inline int32_t db_pow_to_qxx(float level_db, int32_t q) {
float A = powf(10.0f, (level_db / 10.0f));
int32_t out = _positive_float2fixed_saturate(A, q);
return out;
}
/**
* @brief Convert a power level in decibels to a fixed point int32 number in
* Q_SIG format. If the level exceeds the fixed point maximum,
* it is saturated.
*
* @param level_db Power level in db
* @return int32_t level_db in Q_SIG fixed point format
*/
static inline int32_t db_pow_to_q_sig(float level_db) {
return db_pow_to_qxx(level_db, Q_SIG);
}
/**
* @brief Convert a fixed point int32 number in the given Q format to a
* value in decibels.
*
* @param level Level in the fixed point format specified by q_format
* @param q_format Q format of the input
* @return float level in dB for the signal
*/
static inline float qxx_to_db(int32_t level, int q_format) {
float level_db = 20.0f*log10f((float)level / (float)((1 << q_format) - 1));
return level_db;
}
/**
* @brief Convert a fixed point int32 number in the given Q format to a
* value in decibels, when the input level is power.
*
* @param level Power level in the fixed point format specified by q_format
* @param q_format Q format of the input
* @return float level in dB for the signal
*/
static inline float qxx_to_db_pow(int32_t level, int q_format) {
float level_db = 10.0f*log10f((float)level / (float)((1 << q_format) - 1));
return level_db;
}
/**
* @brief Convert an attack or release time in seconds to an EWM alpha
* value as a fixed point int32 number in Q_alpha format. If the
* desired time is too large or small to be represented in the fixed
* point format, it is saturated.
*
* @param fs sampling frequency in Hz
* @param time attack/release time in seconds
* @return int32_t attack/release alpha as an int32_t
*/
static inline int32_t calc_alpha(float fs, float time) {
float alpha = 1.0f;
if (time > 0.0f){
alpha = 2.0f / (fs * time);
alpha = MIN(alpha, 1.0f);
}
int32_t mant;
if(alpha == 1.0f){
mant = INT32_MAX;
}
else{
mant = _positive_float2fixed_saturate(alpha, Q_alpha);
}
return mant;
}
/**
* @brief Convert a peak compressor/limiter/expander threshold in decibels
* to an int32 fixed point gain in Q_SIG Q format.
* If the threshold is higher than representable in the fixed point
* format, it is saturated.
* The minimum threshold returned by this function is 1.
*
* @param level_db the desired threshold in decibels
* @return int32_t the threshold as a fixed point integer.
*/
static inline int32_t calculate_peak_threshold(float level_db){
int32_t out = db_to_q_sig(level_db);
out = MAX(out, 1);
return out;
}
/**
* @brief Convert an RMS² compressor/limiter/expander threshold in decibels
* to an int32 fixed point gain in Q_SIG Q format.
* If the threshold is higher than representable in the fixed point
* format, it is saturated.
* The minimum threshold returned by this function is 1.
*
* @param level_db the desired threshold in decibels
* @return int32_t the threshold as a fixed point integer.
*/
static inline int32_t calculate_rms_threshold(float level_db){
int32_t out = db_pow_to_q_sig(level_db);
out = MAX(out, 1);
return out;
}
/**
* @brief Convert a compressor ratio to the slope, where the slope is
* defined as (1 - 1 / ratio) / 2.0. The division by 2 compensates for
* the RMS envelope detector returning the RMS². The ratio must be
* greater than 1, if it is not the ratio is set to 1.
*
* @param ratio the desired compressor ratio
* @return float slope of the compressor
*/
static inline float rms_compressor_slope_from_ratio(float ratio){
ratio = MAX(ratio, 1.0f);
float slope = (1.0f - 1.0f / ratio) / 2.0f;
return slope;
}
/**
* @brief Convert an expander ratio to the slope, where the slope is
* defined as (1 - ratio). The ratio must be
* greater than 1, if it is not the ratio is set to 1.
*
* @param ratio the desired expander ratio
* @return float slope of the expander
*/
static inline float peak_expander_slope_from_ratio(float ratio){
ratio = MAX(ratio, 1.0f);
float slope = 1.0f - ratio;
return slope;
}
/**
* @brief Convert a graphic equaliser gain in decibels to a fixed point
* int32 number in Q31 format. The input level is shifted by -12 dB.
* This means that all the graphic EQ sliders can be set to +12
* without clipping, at the cost of -12dB level when the slider
* gains are set to 0dB.
*
* @param level_db Level in db
* @return int32_t level_db as an int32_t
*/
static inline int32_t geq_db_to_gain(float level_db) {
return db_to_qxx(level_db - 12, 31);
}
/**
* @brief Generate the filter coefficients for a 10-band graphic equaliser
*
* Returns a pointer to a set of bandpass filters that can use used
* by ``adsp_graphic_eq_10b``. Sample rates between 16kHz and 192 kHz
* are supported.
*
* @param fs Sample rate of the graphic eq
* @return int32_t* Pointer to the filter coefficients
*/
q2_30* adsp_graphic_eq_10b_init(float fs);

View File

@@ -0,0 +1,177 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/adsp.h"
#include "helpers.h"
#include <stdint.h>
#include "control/adsp_control.h"
/**
* @brief Convert a floating point value to the Q_RVR format, saturate out of
* range values. Accepted range is 0 to 1
*
* @param x A floating point number, will be capped to [0, 1]
* @return Q_RVR int32_t value
*/
static inline int32_t adsp_reverb_float2int(float x) {
return _positive_float2fixed_saturate(x, Q_RVR);
}
/**
* @brief Convert a floating point gain in decibels into a linear Q_RVR value
* for use in controlling the reverb gains.
*
* @param db Floating point value in dB, values above 0 will be clipped.
* @return Q_RVR fixed point linear gain.
*/
static inline int32_t adsp_reverb_db2int(float db) {
return db_to_qxx(db, Q_RVR);
}
/**
* @brief Convert a user damping value into a Q_RVR fixed point value suitable
* for passing to a reverb.
*
* @param damping The chose value of damping.
* @return Damping as a Q_RVR fixed point integer, clipped to the accepted range.
*/
static inline int32_t adsp_reverb_calculate_damping(float damping) {
int32_t ret = adsp_reverb_float2int(damping);
return ret < 1 ? 1 : ret;
}
/**
* @brief Calculate a Q_RVR feedback value for a given decay. Use to calculate
* the feedback parameter in reverb_room.
*
* @param decay The desired decay value.
* @return Calculated feedback as a Q_RVR fixed point integer.
*/
static inline int32_t adsp_reverb_calculate_feedback(float decay) {
decay = decay < 0.0f ? 0.0f : decay;
decay = decay > 1.0f ? 1.0f : decay;
float feedback = (0.28f * decay) + 0.7f;
// always [0.7, 0.98] so no need to saturate
return _positive_float2fixed(feedback, Q_RVR);
}
/**
* @brief Calculate the reverb gain in linear scale
*
* Will convert a gain in dB to a linear scale in Q_RVR format.
* To be used for converting wet and dry gains for the room_reverb.
*
* @param gain_db Gain in dB
* @return int32_t Linear gain in a Q_RVR format
*/
static inline int32_t adsp_reverb_room_calc_gain(float gain_db) {
return adsp_reverb_db2int(gain_db);
}
/**
* @brief Calculate the wet and dry gains according to the mix amount.
*
* When the mix is set to 0, only the dry signal will be output.
* The wet gain will be 0 and the dry gain will be max.
* When the mix is set to 1, only they wet signal will be output.
* The wet gain is max, the dry gain will be 0.
* In order to maintain a consistent signal level across all mix values,
* the signals are panned with a -4.5 dB panning law.
*
* @param gains Output gains: [0] - Dry; [1] - Wet
* @param mix Mix applied from 0 to 1
*/
static inline void adsp_reverb_wet_dry_mix(int32_t gains[2], float mix) {
adsp_crossfader_mix(gains, mix);
}
/**
* @brief Initialise a reverb room object
* A room reverb effect based on Freeverb by Jezar at Dreampoint
*
* @param fs Sampling frequency
* @param max_room_size Maximum room size of delay filters
* @param room_size Room size compared to the maximum room size [0, 1]
* @param decay Length of the reverb tail [0, 1]
* @param damping High frequency attenuation
* @param wet_gain Wet gain in dB
* @param dry_gain Dry gain in dB
* @param pregain Linear pre-gain
* @param max_predelay Maximum size of the predelay buffer in ms
* @param predelay Initial predelay in ms
* @param reverb_heap Pointer to heap to allocate reverb memory
* @return reverb_room_t Initialised reverb room object
*/
reverb_room_t adsp_reverb_room_init(
float fs,
float max_room_size,
float room_size,
float decay,
float damping,
float wet_gain,
float dry_gain,
float pregain,
float max_predelay,
float predelay,
void *reverb_heap);
/**
* @brief Calculate the stereo wet gains of the stereo reverb room
*
* @param wet_gains Output linear wet_1 and wet_2 gains in Q_RVR
* @param wet_gain Input wet gain in dB
* @param width Stereo separation of the room [0, 1]
*/
void adsp_reverb_room_st_calc_wet_gains(int32_t wet_gains[2], float wet_gain, float width);
/**
* @brief Calculate the stereo wet and dry gains according to the mix amount
*
* When the mix is set to 0, only the dry signal will be output.
* The wet gain will be 0 and the dry gain will be max.
* When the mix is set to 1, only they wet signal will be output.
* The wet gain is max, the dry gain will be 0.
* In order to maintain a consistent signal level across all mix values,
* the signals are panned with a -4.5 dB panning law.
* The width controls the mixing between the left and right wet channels
*
* @param gains Output gains: [0] - Dry; [1] - Wet_1; [2] - Wet_2
* @param mix Mix applied from 0 to 1
* @param width Stereo separation of the room [0, 1]
*/
void adsp_reverb_st_wet_dry_mix(int32_t gains[3], float mix, float width);
/**
* @brief Initialise a stereo reverb room object
* A room reverb effect based on Freeverb by Jezar at Dreampoint
*
* @param fs Sampling frequency
* @param max_room_size Maximum room size of delay filters
* @param room_size Room size compared to the maximum room size [0, 1]
* @param decay Length of the reverb tail [0, 1]
* @param damping High frequency attenuation
* @param width Stereo separation of the room [0, 1]
* @param wet_gain Wet gain in dB
* @param dry_gain Dry gain in dB
* @param pregain Linear pre-gain
* @param max_predelay Maximum size of the predelay buffer in ms
* @param predelay Initial predelay in ms
* @param reverb_heap Pointer to heap to allocate reverb memory
* @return reverb_room_st_t Initialised stereo reverb room object
*/
reverb_room_st_t adsp_reverb_room_st_init(
float fs,
float max_room_size,
float room_size,
float decay,
float damping,
float width,
float wet_gain,
float dry_gain,
float pregain,
float max_predelay,
float predelay,
void *reverb_heap);

View File

@@ -0,0 +1,86 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/adsp.h"
#include "helpers.h"
#include <stdint.h>
#if Q_RVP != Q_RVR
#error "Some reverb room APIs are used for the reverb plate, so exponents need to be the same"
#endif
/**
* @brief Convert a user late diffusion value into a Q_RVP fixed point value suitable
* for passing to a reverb.
*
* @param late_diffusion The chose value of late diffusion.
* @return Late diffusion as a Q_RVP fixed point integer, clipped to the accepted range.
*/
static inline int32_t adsp_reverb_plate_calc_late_diffusion(float late_diffusion) {
return -_positive_float2fixed_saturate(late_diffusion, Q_RVP);
}
/**
* @brief Convert a user damping value into a Q_RVP fixed point value suitable
* for passing to a reverb.
*
* @param damping The chose value of damping.
* @return Damping as a Q_RVP fixed point integer, clipped to the accepted range.
*/
static inline int32_t adsp_reverb_plate_calc_damping(float damping) {
int32_t damp = INT32_MAX - _positive_float2fixed_saturate(damping, Q_RVP);
return (damp < 1) ? 1 : damp;
}
/**
* @brief Convert a user bandwidth value in Hz into a Q_RVP fixed point value suitable
* for passing to a reverb.
*
* @param bandwidth The chose value of bandwidth.
* @param fs The sampling frequency in Hz
* @return Bandwidth as a Q_RVP fixed point integer, clipped to the accepted range.
*/
static inline int32_t adsp_reverb_plate_calc_bandwidth(float bandwidth, float fs) {
if (bandwidth >= fs / 2.0f){
return INT32_MAX;
}
float f_bandwidth = cosf(2.0f * (float)M_PI * bandwidth / fs);
bandwidth = (f_bandwidth - 1.0f) + sqrtf((f_bandwidth - 1.0f) * (f_bandwidth - 3.0f));
int32_t band = _positive_float2fixed_saturate(bandwidth, Q_RVP);
return (band < 1) ? 1 : band;
}
/**
* @brief Initialise a reverb plate object
*
* @param fs Sampling frequency
* @param decay Length of the reverb tail [0, 1]
* @param damping High frequency attenuation
* @param bandwidth Pre lowpass
* @param early_diffusion Early diffusion
* @param late_diffusion Late diffusion
* @param width Stereo separation of the room [0, 1]
* @param wet_gain Wet gain in dB
* @param dry_gain Dry gain in dB
* @param pregain Linear pre-gain
* @param max_predelay Maximum size of the predelay buffer in ms
* @param predelay Initial predelay in ms
* @param reverb_heap Pointer to heap to allocate reverb memory
* @return reverb_plate_t Initialised reverb plate object
*/
reverb_plate_t adsp_reverb_plate_init(
float fs,
float decay,
float damping,
float bandwidth,
float early_diffusion,
float late_diffusion,
float width,
float wet_gain,
float dry_gain,
float pregain,
float max_predelay,
float predelay,
void * reverb_heap);

View File

@@ -0,0 +1,155 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
/**
* @brief Enum for different time units
*/
typedef enum{
/** Time in samples */
SAMPLES = 0,
/** Time in milliseconds */
MILLISECONDS = 1,
/** Time in seconds */
SECONDS = 2
} time_units_t;
/**
* @brief Convert dB gain to linear gain.
*
* @param dB_gain Gain in dB
* @return int32_t Linear gain in Q_GAIN format
* @note With the current Q_GAIN format, the maximum gain is +24 dB, dB_gain will be saturated to this value
* @note Passing -INFINITY to this function will give a linear gain of 0.
*/
int32_t adsp_dB_to_gain(float dB_gain);
/**
* @brief Initialise a slewing gain object
*
* The slew shift will determine the speed of the volume change.
* A list of the first 10 slew shifts is shown below:
*
* 1 -> 0.03 ms,
* 2 -> 0.07 ms,
* 3 -> 0.16 ms,
* 4 -> 0.32 ms,
* 5 -> 0.66 ms,
* 6 -> 1.32 ms,
* 7 -> 2.66 ms,
* 8 -> 5.32 ms,
* 9 -> 10.66 ms,
* 10 -> 21.32 ms.
*
* @param init_gain Initial gain
* @param slew_shift Shift value used in the exponential slew
* @return gain_slew_t The slewing gain object.
*/
gain_slew_t adsp_slew_gain_init(int32_t init_gain, int32_t slew_shift);
/**
* @brief Initialise volume control object.
* The slew shift will determine the speed of the volume change.
* A list of the first 10 slew shifts is shown below:
*
* 1 -> 0.03 ms,
* 2 -> 0.07 ms,
* 3 -> 0.16 ms,
* 4 -> 0.32 ms,
* 5 -> 0.66 ms,
* 6 -> 1.32 ms,
* 7 -> 2.66 ms,
* 8 -> 5.32 ms,
* 9 -> 10.66 ms,
* 10 -> 21.32 ms.
*
* @param gain_dB Target gain in dB
* @param slew_shift Shift value used in the exponential slew
* @param mute_state Initial mute state
* @return volume_control_t Volume control state object
*/
volume_control_t adsp_volume_control_init(
float gain_dB,
int32_t slew_shift,
uint8_t mute_state);
/**
* @brief Initialise a delay object
*
* @param fs Sampling frequency
* @param max_delay Maximum delay in specified units
* @param starting_delay Initial delay in specified units
* @param units Time units (SAMPLES, MILLISECONDS, SECONDS). If an invalid
* unit is passed, SAMPLES is used.
* @param delay_heap Pointer to the allocated delay memory
* @return delay_t Delay state object
*/
delay_t adsp_delay_init(
float fs,
float max_delay,
float starting_delay,
time_units_t units,
void * delay_heap);
/**
* @brief Set the delay of a delay object.
* Will set the delay to the new value, saturating to the maximum delay
*
* @param delay Delay object
* @param delay_time New delay time in specified units
* @param units Time units (SAMPLES, MILLISECONDS, SECONDS). If an invalid
* unit is passed, SAMPLES is used.
*/
void adsp_set_delay(
delay_t * delay,
float delay_time,
time_units_t units);
/**
* @brief Convert a time in seconds/milliseconds/samples to samples for a
* given sampling frequency.
*
* @param fs Sampling frequency
* @param time New delay time in specified units
* @param units Time units (SAMPLES, MILLISECONDS, SECONDS) . If an invalid
* unit is passed, SAMPLES is used.
* @return uint32_t Time in samples
*/
uint32_t time_to_samples(float fs, float time, time_units_t units);
/**
* @brief Initialise a slewing switch object
*
* @param fs Sampling frequency, used to calculate the
* step size.
* @param init_position Starting position of the switch.
* @return switch_slew_t The slewing switch object.
*/
switch_slew_t adsp_switch_slew_init(float fs, int32_t init_position);
/**
* @brief Move the position of the switch. This sets the state of the
* switch for slewing on subsequent samples.
*
* @param switch_slew Slewing switch state object.
* @param new_position The desired input channel to switch to.
*/
void adsp_switch_slew_move(switch_slew_t* switch_slew, int32_t new_position);
/**
* @brief Calculate the gains for a crossfader according to the mix amount.
*
* When the mix is set to 0, only the first signal will be output.
* gains[0] will be max and gains[1] will be 0.
* When the mix is set to 1, only they second signal will be output.
* gains[0] will be 0 and gains[1] will be max.
* In order to maintain a consistent signal level across all mix values,
* when the mix is set to 0.5, each channel has a gain of -4.5 dB.
*
* @param gains Output gains
* @param mix Mix applied from 0 to 1
*/
void adsp_crossfader_mix(int32_t gains[2], float mix);

View File

@@ -0,0 +1,32 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "generic_utils.h"
/**
* @brief Exponential moving average with a Q0.31 alpha
* Upadates the exponential moving average of x with the new sample y
*
* @param x Current moving average
* @param y New sample
* @param alpha Exponential decay factor
* @return int32_t Updated moving average
*/
static inline int32_t q31_ema(int32_t x, int32_t y, q1_31 alpha) {
// this assumes that x and y are positive and alpha is q31
// x and y have to have the same exponent
int32_t ah, al;
int32_t mul = y - x;
// preload the acc with x at position of 31
// (essentially giving it exponent of -31 + x.exp)
asm("linsert %0, %1, %2, %3, 32":"=r" (ah), "=r" (al): "r"(x), "r"(Q_alpha), "0"(0), "1" (0));
// x + alpha * (y - x) with exponent -31 + x.exp
asm("maccs %0,%1,%2,%3":"=r"(ah),"=r"(al):"r"(alpha),"r"(mul), "0" (ah), "1" (al));
// saturate and extract from 63rd bit
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (Q_alpha), "0" (ah), "1" (al));
asm("lextract %0,%1,%2,%3,32":"=r"(x):"r"(ah),"r"(al),"r"(Q_alpha));
return x;
}

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#define Q_alpha (31)
/**
* @brief Saturating rounding multiply by a Q0.31 gain.
*
* @param samp Sample to be multipled.
* @param gain Gain to apply; assumes a Q0.31 gain.
* @return int32_t Returns either samp * gain or MAXINT/MININT if over/underflow
*/
static inline int32_t apply_gain_q31(int32_t samp, q1_31 gain) {
// this assumes that alpha is q31
int32_t q = Q_alpha;
int32_t ah = 0, al = 1 << (q - 1);
// standard multiplication with rounding and saturation
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (samp), "r" (gain), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (q));
return ah;
}

View File

@@ -0,0 +1,111 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
/**
* @brief Quantize an int64 to int32, saturating and quantizing to zero
* in the process. This is useful for feedback paths, where limit
* cycles can occur if you don't round to zero.
*
* @param ah High 32 bits of the word
* @param al Low 32 bits of the word
* @param shift Q factor of the operation
* @return int32_t Signle word output, saturated and quantized to zero
*/
static inline int32_t scale_sat_int64_to_int32_floor(int32_t ah,
int32_t al,
int32_t shift)
{
int32_t big_q = ((uint32_t)1 << shift) - 1, one = 1, shift_minus_one = shift - 1;
// If ah:al < 0, add just under 1
if (ah < 0) // ah is sign extended, so this test is sufficient
{
asm volatile("maccs %0, %1, %2, %3"
: "=r"(ah), "=r"(al)
: "r"(one), "r"(big_q), "0"(ah), "1"(al));
}
// Saturate ah:al. Implements the following:
// if (val > (2 ** (31 + shift) - 1))
// val = 2 ** (31 + shift) - 1
// else if (val < -(2 ** (31 + shift)))
// val = -(2 ** (31 + shift))
// Note the use of 31, rather than 32 - hence here we subtract 1 from shift.
asm volatile("lsats %0, %1, %2"
: "=r"(ah), "=r"(al)
: "r"(shift_minus_one), "0"(ah), "1"(al));
// then we return (ah:al >> shift)
asm volatile("lextract %0, %1, %2, %3, 32"
: "=r"(ah)
: "r"(ah), "r"(al), "r"(shift));
return ah;
}
/**
* @brief Add samples with the feedback applied to the second one.
* Will do: samp1 + (samp2 * feedback).
* The result is quantised to zero.
* This is useful for feedback paths, where limit
* cycles can occur if you don't round to zero.
*
* @param samp1 First sample to add
* @param samp2 Second sample to add with feedback
* @param fb Feedback gain
* @param q Q factor of the feedback
* @return int32_t Sum with the feedback applied
*/
static inline int32_t add_with_fb(int32_t samp1, int32_t samp2, int32_t fb, int32_t q) {
int32_t ah, al;
int64_t a = (int64_t)samp1 << q;
ah = (int32_t)(a >> 32);
al = (int32_t)a;
asm volatile("maccs %0, %1, %2, %3"
: "=r"(ah), "=r"(al)
: "r"(samp2), "r"(fb), "0"(ah), "1"(al));
ah = scale_sat_int64_to_int32_floor(ah, al, q);
return ah;
}
/**
* @brief Mix stereo signal into mono with pregain.
* Will do: (sig1 + sig2) * pregain.
*
* @param sig1 First sample to mix
* @param sig2 Second sample to mix
* @param pregain Pregain
* @param q_gain Q factor of the gain
* @return int32_t Mixed signal
*/
static inline int32_t mix_with_pregain(int32_t sig1, int32_t sig2, int32_t pregain, int32_t q_gain) {
int32_t ah = 0, al = 1 << (q_gain - 1);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (sig1), "r" (pregain), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (sig2), "r" (pregain), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q_gain), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (q_gain));
return ah;
}
/**
* @brief Mix dry and wet paths of the reverb with an effect gain.
* Will do: (wet_sig * effect_gain) + dry_sig.
*
* @param wet_sig Wet reverb signal
* @param dry_sig Dry reverb signal
* @param effect_gain Effect gain
* @param q_gain Q factor of the gain
* @return int32_t Mixed signal
*/
static inline int32_t mix_wet_dry(int32_t wet_sig, int32_t dry_sig, int32_t effect_gain, int32_t q_gain) {
// only exists because the effect gain is not a part of the wet gain, so need to handle properly
int32_t ah = 0, al = 1 << (q_gain - 1);
asm("linsert %0, %1, %2, %3, 32": "=r" (ah), "=r" (al): "r" (dry_sig), "r" (q_gain), "0" (ah), "1" (al));
asm("sext %0, %1": "=r" (ah): "r" (q_gain), "0" (ah));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (wet_sig), "r" (effect_gain), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q_gain), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (q_gain));
return ah;
}

View File

@@ -0,0 +1,15 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/xmath.h"
#include "dsp/defines.h"
#include "dsp/signal_chain.h"
#include "dsp/biquad.h"
#include "dsp/cascaded_biquads.h"
#include "dsp/drc.h"
#include "dsp/graphic_eq.h"
#include "dsp/reverb.h"
#include "dsp/reverb_plate.h"

View File

@@ -0,0 +1,60 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/types.h"
/**
* @brief Slewing biquad state structure
*/
typedef struct {
/** Target filter coefficients, the active coefficients are slewed towards these */
q2_30 DWORD_ALIGNED target_coeffs[8];
/** Active filter coefficients, used to filter the audio */
q2_30 DWORD_ALIGNED active_coeffs[8];
/** Left shift compensation for if the filter coefficents are large
* and cannot be represented in Q1.30, must be positive */
left_shift_t lsh;
/** Shift value used by the exponential slew */
int32_t slew_shift;
/** Remaining shifts for cases when the left shift changes during a target_coeff update. */
left_shift_t remaining_shifts;
} biquad_slew_t;
/**
* @brief Biquad filter.
* This function implements a biquad filter. The filter is implemented as a direct form 1.
* The ``coeffs`` parameter should contain b0/a0, b1/a0, b2/a0, -a1/a0, and -a2/a0 in that
* order, all represented by fixed-point values shifted left by ``30-lsh`` bits
*
* @param new_sample New sample to be filtered
* @param coeffs Filter coefficients
* @param state Filter state. Must be double-word aligned
* @param lsh Left shift compensation value, must be positive
* @return int32_t Filtered sample
* @note No saturation applied. If output exceeds INT32_MAX, it will overflow.
*/
int32_t adsp_biquad(
int32_t new_sample,
q2_30 coeffs[5],
int32_t state[8],
left_shift_t lsh);
/**
* @brief Slew the active filter coefficients towards the target filter
* coefficients. This function should be called either once per sample or
* per frame, and before calling ``adsp_biquad`` to do the filtering.
*
* @param slew_state Slewing biquad state object
* @param states Filter state for each biquad channel
* @param channels Number of channels in states
*/
void adsp_biquad_slew_coeffs(
biquad_slew_t* slew_state,
int32_t** states,
int32_t channels
);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/types.h"
/**
* @brief 8-band cascaded biquad filter
* This function implements an 8-band cascaded biquad filter. The filter is implemented as a direct
* form 1 filter.
*
* @param new_sample New sample to be filtered
* @param coeffs Filter coefficients
* @param state Filter state
* @param lsh Left shift compensation value
* @return int32_t Filtered sample
* @note The filter coefficients must be in [8][5]
*/
int32_t adsp_cascaded_biquads_8b(
int32_t new_sample,
q2_30 coeffs[40],
int32_t state[64],
left_shift_t lsh[8]);

View File

@@ -0,0 +1,9 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
/** Default signal exponent */
#define SIG_EXP (-27)
/** Default Q format */
#define Q_SIG (-SIG_EXP)

View File

@@ -0,0 +1,212 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <xmath/types.h>
/**
* @brief Envelope detector state structure
*/
typedef struct{
/** Attack alpha */
q1_31 attack_alpha;
/** Release alpha */
q1_31 release_alpha;
/** Current envelope */
int32_t envelope;
}env_detector_t;
/**
* @brief Limiter state structure
*/
typedef struct{
/** Envelope detector */
env_detector_t env_det;
/** Linear threshold */
int32_t threshold;
/** Linear gain */
int32_t gain;
}limiter_t;
/**
* @brief Clipper state structure.
* Should be initilised with the linear threshold
*/
typedef int32_t clipper_t;
/**
* @brief Noise gate state structure
*/
typedef limiter_t noise_gate_t;
/**
* @brief Compressor state structure
*/
typedef struct{
/** Envelope detector */
env_detector_t env_det;
/** Linear threshold */
int32_t threshold;
/** Linear gain */
int32_t gain;
/** Slope of the compression curve */
float slope;
}compressor_t;
/**
* @brief Stereo compressor state structure
*/
typedef struct{
/** Envelope detector for left channel */
env_detector_t env_det_l;
/** Envelope detector for right channel */
env_detector_t env_det_r;
/** Linear threshold */
int32_t threshold;
/** Linear gain */
int32_t gain;
/** Slope of the compression curve */
float slope;
}compressor_stereo_t;
typedef struct{
/** Envelope detector */
env_detector_t env_det;
/** Linear threshold */
int32_t threshold;
/** Inverse threshold */
int64_t inv_threshold;
/** Linear gain */
int32_t gain;
/** Slope of the noise suppression curve */
float slope;
}noise_suppressor_expander_t;
/**
* @brief Update the envelope detector peak with a new sample
*
* @param env_det Envelope detector object
* @param new_sample New sample
*/
void adsp_env_detector_peak(
env_detector_t * env_det,
int32_t new_sample);
/**
* @brief Update the envelope detector RMS with a new sample
*
* @param env_det Envelope detector object
* @param new_sample New sample
*/
void adsp_env_detector_rms(
env_detector_t * env_det,
int32_t new_sample);
/**
* @brief Process a new sample with a clipper
*
* @param clip Clipper object
* @param new_samp New sample
* @return int32_t Clipped sample
*/
int32_t adsp_clipper(
clipper_t clip,
int32_t new_samp);
/**
* @brief Process a new sample with a peak limiter
*
* @param lim Limiter object
* @param new_samp New sample
* @return int32_t Limited sample
*/
int32_t adsp_limiter_peak(
limiter_t * lim,
int32_t new_samp);
/**
* @brief Process a new sample with a hard limiter peak
*
* @param lim Limiter object
* @param new_samp New sample
* @return int32_t Limited sample
*/
int32_t adsp_hard_limiter_peak(
limiter_t * lim,
int32_t new_samp);
/**
* @brief Process a new sample with an RMS limiter
*
* @param lim Limiter object
* @param new_samp New sample
* @return int32_t Limited sample
*/
int32_t adsp_limiter_rms(
limiter_t * lim,
int32_t new_samp);
/**
* @brief Process a new sample with a noise gate
*
* @param ng Noise gate object
* @param new_samp New sample
* @return int32_t Gated sample
*/
int32_t adsp_noise_gate(
noise_gate_t * ng,
int32_t new_samp);
/**
* @brief Process a new sample with a noise suppressor (expander)
*
* @param nse Noise suppressor (Expander) object
* @param new_samp New sample
* @return int32_t Suppressed sample
*/
int32_t adsp_noise_suppressor_expander(
noise_suppressor_expander_t * nse,
int32_t new_samp);
/**
* @brief Process a new sample with an RMS compressor
*
* @param comp Compressor object
* @param new_samp New sample
* @return int32_t Compressed sample
*/
int32_t adsp_compressor_rms(
compressor_t * comp,
int32_t new_samp);
/**
* @brief Process a new sample with a sidechain RMS compressor
*
* @param comp Compressor object
* @param input_samp Input sample
* @param detect_samp Sidechain sample
* @return int32_t Compressed sample
*/
int32_t adsp_compressor_rms_sidechain(
compressor_t * comp,
int32_t input_samp,
int32_t detect_samp);
/**
* @brief Process a pair of new samples with a stereo sidechain RMS compressor
*
* @param comp Compressor object
* @param outputs_lr Pointer to the outputs 0:left, 1:right
* @param input_samp_l Left input sample
* @param input_samp_r Right input sample
* @param detect_samp_l Left sidechain sample
* @param detect_samp_r Right sidechain sample
*/
void adsp_compressor_rms_sidechain_stereo(
compressor_stereo_t * comp,
int32_t outputs_lr[2],
int32_t input_samp_l,
int32_t input_samp_r,
int32_t detect_samp_l,
int32_t detect_samp_r);

View File

@@ -0,0 +1,106 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/types.h"
/**
* @brief Frequency domain input data struct.
*/
typedef struct fd_fir_data_t {
/** Pointer to an array of bfp_complex_s32_t structs. */
bfp_complex_s32_t * data_blocks;
/** Pointer to array of samples that form the overlap between consecutive blocks. */
int32_t * prev_td_data;
/** Pointer to array of samples that form the overlap between consecutive outputs. */
int32_t * overlapping_frame_data;
/** Index of the head of the FIFO of data blocks. */
uint32_t head_index;
/** The length of the block when in a TD form including zero padding. */
uint32_t td_block_length;
/** Count of blocks of data. */
uint32_t block_count;
/** The frame advance, in time domain samples, between subsequent blocks. */
uint32_t frame_advance;
} fd_fir_data_t;
/**
* @brief Frequency domain filter struct.
*/
typedef struct fd_fir_filter_t {
/** Pointer to an array of bfp_complex_s32_t structs. */
bfp_complex_s32_t * coef_blocks;
/** The length of the block when in a TD form including zero padding. */
uint32_t td_block_length;
/** Count of blocks in the filter. */
uint32_t block_count;
/** Time domain taps per block. */
uint32_t taps_per_block;
} fd_fir_filter_t;
/**
* @brief Initialise a frequency domain block FIR data structure.
*
* This manages the input data, rather than the coefficients, for a frequency domain block convolution.
* The python filter generator should be run first resulting in a header that defines the parameters
* for this function.
*
* For example, running the generator with `--name={NAME}` would generate defines prepended with
* `{NAME}`, i.e. `{NAME}_DATA_BUFFER_ELEMENTS`, `{NAME}_TD_BLOCK_LENGTH`, etc.
* This function should then be called with:
* ```
* fd_fir_data_t {NAME}_fir_data;
* int32_t {NAME}_data[{NAME}_DATA_BUFFER_ELEMENTS];
* fd_block_fir_data_init(&{NAME}_fir_data, {NAME}_data,
* {NAME}_FRAME_ADVANCE,
* {NAME}_TD_BLOCK_LENGTH,
* {NAME}_BLOCK_COUNT);
* ```
*
* @param fir_data Pointer to struct of type fd_fir_data_t.
* @param data An area of memory to be used by the struct in order to hold a history of
* the samples. The define {NAME}_DATA_BUFFER_ELEMENTS specifies exactly the
* number of int32_t elements to allocate for the filter {NAME} to correctly
* function.
* @param frame_advance The number of samples contained in each frame, i.e. the samples count
* between updates. This should be initialised to {NAME}_FRAME_ADVANCE.
* @param block_length The length of the processing block, independent to the frame_advance.
* Must be a power of two. This should be initialised to {NAME}_TD_BLOCK_LENGTH.
* @param block_count The count of blocks required to implement the filter. This should be
* initialised to {NAME}_BLOCK_COUNT.
*/
void fd_block_fir_data_init(
fd_fir_data_t * fir_data,
int32_t *data,
uint32_t frame_advance,
uint32_t block_length,
uint32_t block_count);
/**
* @brief Function to add samples to the FIR data structure.
*
* @param samples_in Array of int32_t samples of length expected to be fir_data->frame_advance.
* @param fir_data Pointer to struct of type fd_fir_data_t to which the samples will be added.
*/
void fd_block_fir_add_data(
int32_t * samples_in,
fd_fir_data_t * fir_data);
/**
* @brief Function to compute the convolution between fir_data and fir_filter.
*
* @param samples_out Array of length fir_data->td_block_length, which will be used to return the
* processed samples. The samples will be returned from element 0 for
* `(fir_data-td_block_length + 1 - fir_filter->taps_per_block)` elements.
* The remaining samples of the array are used as scratch for the processing to be in-place.
* @param fir_data Pointer to struct of type fd_fir_data_t from which the data samples will be obtained.
* @param fir_filter Pointer to struct of type fd_fir_filter_t from which the coefficients will be obtained.
*/
void fd_block_fir_compute(
int32_t * samples_out,
fd_fir_data_t * fir_data,
fd_fir_filter_t * fir_filter);

View File

@@ -0,0 +1,17 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/filter.h"
/** Heap size to allocate for the delay from samples */
#define FIR_DIRECT_DSP_REQUIRED_MEMORY_SAMPLES(SAMPLES) (sizeof(int32_t) * (SAMPLES))
/**
* @brief Delay state structure
*/
typedef struct{
// lib_xcore_math FIR struct
filter_fir_s32_t filter;
} fir_direct_t;

View File

@@ -0,0 +1,30 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "xmath/types.h"
#define Q_GEQ 31
/**
* @brief 10-band graphic equaliser
*
* This function implements an 10-band graphic equalizer filter.
* The equaliser is implemented as a set of parallel 4th order bandpass
* filters, with a gain controlling the level of each parallel branch.
*
* @param new_sample New sample to be filtered
* @param gains The gains of each band in Q_GEQ format
* @param coeffs Filter coefficients
* @param state Filter state, must be DWORD_ALIGNED
* @return int32_t Filtered sample
* @note The filter coefficients can be generated using ``adsp_graphic_eq_10b_init``.
*/
int32_t adsp_graphic_eq_10b(
int32_t new_sample,
int32_t gains[10],
q2_30 coeffs[50],
int32_t state[160]);

View File

@@ -0,0 +1,249 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <xmath/types.h>
/** Minimum wet/dry gain config for the reverb room in dB */
#define ADSP_RVR_MIN_GAIN_DB (-186.0f)
/** Maximum wet/dry gain config for the reverb room in dB */
#define ADSP_RVR_MAX_GAIN_DB (0)
/** Reverb room scale factor for the room size */
#define ADSP_RVR_SCALE(FS, MAX_ROOM_SZ) (((FS) / 44100.0f) * (MAX_ROOM_SZ))
/** Default reverb room buffer length */
#define ADSP_RVR_SUM_DEFAULT_BUF_LENS (12587)
/** Heap size to allocate for the reverb room */
#define ADSP_RVR_HEAP_SZ(FS, ROOM_SZ, PD) ((uint32_t)((sizeof(int32_t) * \
ADSP_RVR_SCALE(FS, ROOM_SZ) * \
ADSP_RVR_SUM_DEFAULT_BUF_LENS) + \
DELAY_DSP_REQUIRED_MEMORY_SAMPLES(PD)))
/** External API for calculating memory to allocate for the reverb room */
#define REVERB_ROOM_DSP_REQUIRED_MEMORY(FS, ROOM_SZ, PD) ADSP_RVR_HEAP_SZ(FS, ROOM_SZ, PD)
/** Default stereo reverb room buffer length */
#define ADSP_RVRST_SUM_DEFAULT_BUF_LENS (25450)
/** Heap size to allocate for the stereo reverb room */
#define ADSP_RVRST_HEAP_SZ(FS, ROOM_SZ, PD) ((uint32_t)((sizeof(int32_t) * \
ADSP_RVR_SCALE(FS, ROOM_SZ) * \
ADSP_RVRST_SUM_DEFAULT_BUF_LENS) + \
DELAY_DSP_REQUIRED_MEMORY_SAMPLES(PD)))
/** External API for calculating memory to allocate for the stereo reverb room */
#define REVERB_ROOM_ST_DSP_REQUIRED_MEMORY(FS, ROOM_SZ, PD) ADSP_RVRST_HEAP_SZ(FS, ROOM_SZ, PD)
/** Number of comb filters used in the reverb room */
#define ADSP_RVR_N_COMBS 8
/** Number of allpass filters used in the reverb room */
#define ADSP_RVR_N_APS 4
/** Reverb room internal Q factor */
#define Q_RVR 31
/**
* @brief A freeverb style all-pass filter structure
*/
typedef struct
{
/** Maximum delay */
uint32_t max_delay;
/** Current delay */
uint32_t delay;
/** Feedback gain */
int32_t feedback;
/** Delay buffer */
int32_t *buffer;
/** Current buffer index */
int32_t buffer_idx;
} allpass_fv_t;
/**
* @brief A freeverb style comb filter structure
*/
typedef struct
{
/** Maximum delay */
uint32_t max_delay;
/** Current delay */
uint32_t delay;
/** Feedback gain */
int32_t feedback;
/** Delay buffer */
int32_t *buffer;
/** Current buffer index */
int32_t buffer_idx;
/** State variables for low-pass filter */
int32_t filterstore;
/** Damping coefficient 1 */
int32_t damp_1;
/** Damping coefficient 2 */
int32_t damp_2;
} comb_fv_t;
/**
* @brief A room reverb filter structure
*/
typedef struct
{
/** Total buffer length */
uint32_t total_buffer_length;
/** Room size */
float room_size;
/** Wet linear gain */
int32_t wet_gain;
/** Dry linear gain */
int32_t dry_gain;
/** Linear pre-gain */
int32_t pre_gain;
/** Comb filters */
comb_fv_t combs[ADSP_RVR_N_COMBS];
/** Allpass filters */
allpass_fv_t allpasses[ADSP_RVR_N_APS];
/** Predelay applied to the wet channel */
delay_t predelay;
} reverb_room_t;
/**
* @brief A stereo room reverb filter structure
*/
typedef struct
{
/** Total buffer length */
uint32_t total_buffer_length;
/** Spread length */
uint32_t spread_length;
/** Room size */
float room_size;
/** Wet 1 linear gain */
int32_t wet_gain1;
/** Wet 2 linear gain */
int32_t wet_gain2;
/** Dry linear gain */
int32_t dry_gain;
/** Linear pre-gain */
int32_t pre_gain;
/** Comb filters, 0:left, 1:right */
comb_fv_t combs[2][ADSP_RVR_N_COMBS];
/** Allpass filters, 0:left, 1:right */
allpass_fv_t allpasses[2][ADSP_RVR_N_APS];
/** Predelay applied to the wet channel*/
delay_t predelay;
} reverb_room_st_t;
/**
* @brief Lower level function to initialise the filters of a reverb room object
*
* Will initialise allpasses, combs, predelay and set total buffer length.
* Can be used before `adsp_room_reverb_set_room_size()` to
* initialise the filters and set the rooms size.
*
* feedback can be calculated from the decay parameter as follows:
* `feedback = Q_RVR((decay * 0.28f) + 0.7f)`
*
* @param rv Reverb room object
* @param fs Sampling frequency
* @param max_room_size Maximum room size of delay filters
* @param max_predelay Maximum size of the predelay buffer in samples
* @param predelay Initial predelay in samples
* @param feedback Feedback gain for the comb filters in Q_RVR format
* @param damping Damping coefficient for the comb filters in Q_RVR format
* @param reverb_heap Pointer to heap to allocate reverb memory
*/
void adsp_reverb_room_init_filters(
reverb_room_t *rv,
float fs,
float max_room_size,
uint32_t max_predelay,
uint32_t predelay,
int32_t feedback,
int32_t damping,
void * reverb_heap);
/**
* @brief Reset the state of a reverb room object
*
* @param rv Reverb room object
*/
void adsp_reverb_room_reset_state(reverb_room_t *rv);
/**
* @brief Get the buffer length of a reverb room object
*
* @param rv Reverb room object
* @return uint32_t Buffer length
*/
uint32_t adsp_reverb_room_get_buffer_lens(reverb_room_t *rv);
/**
* @brief Set the room size of a reverb room object
*
* @param rv Reverb room object
* @param new_room_size New room size [0, 1]
*/
void adsp_reverb_room_set_room_size(
reverb_room_t *rv,
float new_room_size);
/**
* @brief Process a sample through a reverb room object
*
* @param rv Reverb room object
* @param new_samp New sample to process
* @return int32_t Processed sample
*/
int32_t adsp_reverb_room(
reverb_room_t *rv,
int32_t new_samp);
/**
* @brief Lower level function to initialise the filters of a stereo reverb room object
*
* Will initialise allpasses, combs, predelay and set total buffer length.
* Can be used before `adsp_room_reverb_st_set_room_size()` to
* initialise the filters and set the rooms size.
*
* feedback can be calculated from the decay parameter as follows:
* `feedback = Q_RVR((decay * 0.28f) + 0.7f)`
*
* @param rv Stereo reverb room object
* @param fs Sampling frequency
* @param max_room_size Maximum room size of delay filters
* @param max_predelay Maximum size of the predelay buffer in samples
* @param predelay Initial predelay in samples
* @param feedback Feedback gain for the comb filters in Q_RVR format
* @param damping Damping coefficient for the comb filters in Q_RVR format
* @param reverb_heap Pointer to heap to allocate reverb memory
*/
void adsp_reverb_room_st_init_filters(
reverb_room_st_t *rv,
float fs,
float max_room_size,
uint32_t max_predelay,
uint32_t predelay,
int32_t feedback,
int32_t damping,
void * reverb_heap);
/**
* @brief Set the room size of a stereo reverb room object
*
* @param rv Stereo reverb room object
* @param new_room_size New room size [0, 1]
*/
void adsp_reverb_room_st_set_room_size(
reverb_room_st_t *rv,
float new_room_size);
/**
* @brief Process samples through a stereo reverb room object
*
* @param rv Stereo reverb room object
* @param outputs_lr Pointer to the outputs 0:left, 1:right
* @param in_left New left sample to process
* @param in_right New right sample to process
*/
void adsp_reverb_room_st(
reverb_room_st_t *rv,
int32_t outputs_lr[2],
int32_t in_left,
int32_t in_right);

View File

@@ -0,0 +1,128 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <xmath/types.h>
/** Reverb plate scale factor for the sampling fequency */
#define ADSP_RVP_SCALE(FS) ((float)FS / 29761)
/** Default reverb plate buffer length */
#define ADSP_RVP_SUM_DEFAULT_BUF_LENS (22467)
/** Heap size to allocate for the reverb plate */
#define ADSP_RVP_HEAP_SZ(FS, PD) ((uint32_t)((sizeof(int32_t) * \
ADSP_RVP_SCALE(FS) * \
ADSP_RVP_SUM_DEFAULT_BUF_LENS) + \
DELAY_DSP_REQUIRED_MEMORY_SAMPLES(PD)))
/** External API for calculating memory to allocate for the reverb plate */
#define REVERB_PLATE_DSP_REQUIRED_MEMORY(FS, PD) ADSP_RVP_HEAP_SZ(FS, PD)
/** Numper of paths reverb plate is splitted in */
#define ADSP_RVP_N_PATHS 2
/** Number components used to produce the signle path output in reverb plate */
#define ADSP_RVP_N_OUT_TAPS 7
/** Number of lowpass filters used in the reverb plate */
#define ADSP_RVP_N_LPS 3
/** Number of allpass filters used in the reverb plate */
#define ADSP_RVP_N_APS 6
/** Number of delay lines used in reverb plate */
#define ADSP_RVP_N_DELAYS 4
/** Reverb plate internal Q factor */
#define Q_RVP 31
/**
* @brief A generic first order lowpass filter.
*/
typedef struct {
/** State variables for lowpass filter */
int32_t filterstore;
/** Damping coefficient 1 */
int32_t damp_1;
/** Damping coefficient 2 */
int32_t damp_2;
} lowpass_1ord_t;
/**
* @brief A plate reverb structure
*/
typedef struct {
/** Reverb decay */
int32_t decay;
/** Wet 1 linear gain */
int32_t wet_gain1;
/** Wet 2 linear gain */
int32_t wet_gain2;
/** Dry linear gain */
int32_t dry_gain;
/** Linear pre-gain */
int32_t pre_gain;
/** Saved output paths*/
int32_t paths[ADSP_RVP_N_PATHS];
/** Indexes for the left channel calculation */
int32_t taps_l[ADSP_RVP_N_OUT_TAPS];
/** Max lenghts of buffers use for the left channel calculation */
int32_t taps_len_l[ADSP_RVP_N_OUT_TAPS];
/** Indexes for the right channel calculation */
int32_t taps_r[ADSP_RVP_N_OUT_TAPS];
/** Max lenghts of buffers use for the right channel calculation */
int32_t taps_len_r[ADSP_RVP_N_OUT_TAPS];
/** FIrst order lowpass filters */
lowpass_1ord_t lowpasses[ADSP_RVP_N_LPS];
/** Modulated allpass filters */
allpass_fv_t mod_allpasses[ADSP_RVP_N_PATHS];
/** Allpass filters */
allpass_fv_t allpasses[ADSP_RVP_N_APS];
/** Delay lines */
delay_t delays[ADSP_RVP_N_DELAYS];
/** Predelay applied to the wet channel*/
delay_t predelay;
} reverb_plate_t;
/**
* @brief Initialise first order lowpass
*
* @param feedback Feedback coefficient
* @return lowpass_1ord_t Initialised lowpass object
*/
lowpass_1ord_t lowpass_1ord_init(int32_t feedback);
/**
* @brief Lower level function to initialise the filters of a reverb plate object
*
* Will initialise allpasses, modulated allpasses, delays and predelay.
*
* @param rv Reverb plate object
* @param fs Sampling frequency
* @param decay_diffusion_1 Late diffusion
* @param decay_diffusion_2 Diffusion
* @param in_diffusion_1 Early diffusion 1
* @param in_diffusion_2 Early diffusion 2
* @param max_predelay Maximum size of the predelay buffer in samples
* @param predelay Initial predelay in samples
* @param reverb_heap Pointer to heap to allocate reverb memory
*/
void adsp_reverb_plate_init_filters(
reverb_plate_t * rv,
float fs,
int32_t decay_diffusion_1,
int32_t decay_diffusion_2,
int32_t in_diffusion_1,
int32_t in_diffusion_2,
uint32_t max_predelay,
uint32_t predelay,
void * reverb_heap);
/**
* @brief Process samples through a reverb plate object
*
* @param rv Reverb plate object
* @param outputs_lr Pointer to the outputs 0:left, 1:right
* @param in_left New left sample to process
* @param in_right New right sample to process
*/
void adsp_reverb_plate(
reverb_plate_t *rv,
int32_t outputs_lr[2],
int32_t in_left,
int32_t in_right);

View File

@@ -0,0 +1,264 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <stdbool.h>
/** Heap size to allocate for the delay from samples */
#define DELAY_DSP_REQUIRED_MEMORY_SAMPLES(SAMPLES) (sizeof(int32_t) * (SAMPLES))
/** Heap size to allocate for the delay from milliseconds */
#define DELAY_DSP_REQUIRED_MEMORY_MS(FS, MS) (sizeof(int32_t) * ((FS) * (MS) / 1000))
/** Heap size to allocate for the delay from seconds */
#define DELAY_DSP_REQUIRED_MEMORY_SEC(FS, SEC) (sizeof(int32_t) * (FS) * (SEC))
/** Gain format to be used in the gain APIs */
#define Q_GAIN 27
/**
* @brief Slewing gain state structure
*/
typedef struct{
/** Target linear gain */
int32_t target_gain;
/** Current linear gain */
int32_t gain;
/** Slew shift */
int32_t slew_shift;
} gain_slew_t;
/**
* @brief Volume control state structure
*/
typedef struct{
/** Target linear gain */
int32_t target_gain;
/** Current linear gain */
int32_t gain;
/** Slew shift */
int32_t slew_shift;
/** Saved linear gain */
int32_t saved_gain;
/** Mute state: 0: unmuted, 1 muted */
uint8_t mute_state;
}volume_control_t;
/**
* @brief Delay state structure
*/
typedef struct{
/** Sampling frequency */
float fs;
/** Current delay in samples */
uint32_t delay;
/** Maximum delay in samples */
uint32_t max_delay;
/** Current buffer index */
uint32_t buffer_idx;
/** Buffer */
int32_t * buffer;
} delay_t;
/**
* @brief Slewing switch state structure
*/
typedef struct{
/** If slewing, switching is True until slewing is over. */
bool switching;
/** Current switch pole position. */
int32_t position;
/** Last switch pole position. */
int32_t last_position;
/** Counter for timing slew length. */
int32_t counter;
/** Step increment of counter. */
int32_t step;
} switch_slew_t;
/**
* @brief Slewing crossfader state structure
*/
typedef struct{
/** Slewing gain struct for first crossfader input. */
gain_slew_t gain_1;
/** Slewing gain struct for second crossfader input. */
gain_slew_t gain_2;
/** Mix of the inputs. */
float mix;
} crossfader_slew_t;
/**
* @brief Convert from Q0.31 to Q_SIG
*
* @param input Input in Q0.31 format
* @return int32_t Output in Q_SIG format
*/
int32_t adsp_from_q31(int32_t input);
/**
* @brief Convert from Q_SIG to Q0.31
*
* @param input Input in Q_SIG format
* @return int32_t Output in Q0.31 format
*/
int32_t adsp_to_q31(int32_t input);
/**
* @brief Saturating addition of an array of samples
*
* @param input Array of samples
* @param n_ch Number of channels
* @return int32_t Sum of samples
* @note Will work for any q format
*/
int32_t adsp_adder(int32_t * input, unsigned n_ch);
/**
* @brief Saturating subtraction of two samples, this returns `x - y`.
*
* @param x Minuend
* @param y Subtrahend
* @return int32_t Difference
* @note Will work for any q format
*/
int32_t adsp_subtractor(int32_t x, int32_t y);
/**
* @brief Fixed-point gain
*
* @param input Input sample
* @param gain Gain
* @return int32_t Output sample
* @note One of the inputs has to be in Q_GAIN format
*/
int32_t adsp_fixed_gain(int32_t input, int32_t gain);
/**
* @brief Mixer.
* Will add signals with gain applied to each signal before mixing
*
* @param input Array of samples
* @param n_ch Number of channels
* @param gain Gain
* @return int32_t Mixed sample
* @note Inputs or gain have to be in Q_GAIN format
*/
int32_t adsp_mixer(int32_t * input, unsigned n_ch, int32_t gain);
/**
* @brief Saturating 64-bit accumulator.
* Will saturate to 32-bit, so that the output value is in the range of int32_t
*
* @param acc Accumulator
* @return int32_t Saturated value
*/
int32_t adsp_saturate_32b(int64_t acc);
/**
* @brief Process a new sample with a volume control
*
* @param vol_ctl Volume control object
* @param samp New sample
* @return int32_t Processed sample
*/
int32_t adsp_volume_control(
volume_control_t * vol_ctl,
int32_t samp);
/**
* @brief Set the target gain of a volume control
*
* @param vol_ctl Volume control object
* @param new_gain New target linear gain
*/
void adsp_volume_control_set_gain(
volume_control_t * vol_ctl,
int32_t new_gain);
/**
* @brief Mute a volume control.
* Will save the current target gain and set the target gain to 0
*
* @param vol_ctl Volume control object
*/
void adsp_volume_control_mute(
volume_control_t * vol_ctl);
/**
* @brief Unmute a volume control.
* Will restore the saved target gain
*
* @param vol_ctl Volume control object
*/
void adsp_volume_control_unmute(
volume_control_t * vol_ctl);
/**
* @brief Process a new sample through a delay object
*
* @note The minimum delay provided by this block is 1 sample. Setting
* the delay to 0 will still yield a 1 sample delay.
*
* @param delay Delay object
* @param samp New sample
* @return int32_t Oldest sample
*/
int32_t adsp_delay(
delay_t * delay,
int32_t samp);
/**
* @brief Process a sample through a slewing switch. If the switch
* position has recently changed, this will slew between the desired
* input channel and previous channel.
*
* @param switch_slew Slewing switch state object.
* @param samples An array of input samples for each input channel.
* @return int32_t The output of the switch.
*/
int32_t adsp_switch_slew(switch_slew_t* switch_slew, int32_t* samples);
/**
* @brief Exponentially slew the gain towards the target gain.
*
* @param gain_state Slewing gain state object.
* @return int32_t The current gain.
*/
static inline int32_t adsp_slew_gain(gain_slew_t * gain_state) {
// do the exponential slew
gain_state->gain += (gain_state->target_gain - gain_state->gain) >> gain_state->slew_shift;
return gain_state->gain;
}
/**
* @brief Crossfade between two channels using their gains.
* Will do: (in1 * gain1) + (in2 * gain2).
*
* @param in1 First signal
* @param in2 Second signal
* @param gain1 First gain
* @param gain2 Second gain
* @param q_gain Q factor of the gain
* @return int32_t Mixed signal
*/
static inline int32_t adsp_crossfader(int32_t in1, int32_t in2, int32_t gain1, int32_t gain2, int32_t q_gain) {
int32_t ah = 0, al = 1 << (q_gain - 1);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (in1), "r" (gain1), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (in2), "r" (gain2), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q_gain), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (q_gain));
return ah;
}
/**
* @brief Crossfade between two channels with slew applied to the gains.
* Will do: (in1 * crossfader->gain1.gain) + (in2 * crossfader->gain2.gain).
*
* @param crossfader Slewing crossfader state object.
* @param in1 First signal
* @param in2 Second signal
* @return int32_t Mixed signal
*/
int32_t adsp_crossfader_slew(crossfader_slew_t* crossfader, int32_t in1, int32_t in2);

View File

@@ -0,0 +1,92 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <stdint.h>
// This is fixed due to the VPU
#define TD_BLOCK_FIR_LENGTH 8
/**
* @brief Time domain input data struct.
*/
typedef struct td_block_fir_data_t
{
/** The actual data samples. */
int32_t *data;
/** Index of the head of the FIFO data. */
uint32_t index;
/** The number of bytes a pointer has to have subtracted to move around the circular buffer. */
uint32_t data_stride;
} td_block_fir_data_t;
/**
* @brief Time domain filter coefficient struct.
*/
typedef struct td_block_fir_filter_t {
/** The actual coefficients, reversed for the VPU. */
int32_t * coefs;
/** Count of blocks of data. */
uint32_t block_count;
/** The amount to shr the accumulator after all accumulation is complete.*/
uint32_t accu_shr;
/** The amount to shl the accumulator after all accumulation is complete.*/
uint32_t accu_shl; //t
} td_block_fir_filter_t;
/**
* @brief Initialise a time domain block FIR data structure.
*
* This manages the input data, rather than the coefficients, for a time domain block convolution.
* The python filter generator should be run first resulting in a header that defines the parameters
* for this function.
*
* For example, running the generator with `--name={NAME}` would generate defines prepended with
* `{NAME}`, i.e. `{NAME}_DATA_BUFFER_ELEMENTS`, `{NAME}_TD_BLOCK_LENGTH`, etc.
* This function should then be called with:
* ```
* td_block_fir_data_t {NAME}_fir_data;
* int32_t {NAME}_data[{NAME}_DATA_BUFFER_ELEMENTS];
* td_block_fir_data_init(&{NAME}_fir_data, {NAME}_data, {NAME}_DATA_BUFFER_ELEMENTS);
* ```
*
* @param fir_data Pointer to struct of type td_block_fir_data_t
* @param data Pointer to an amount of memory to be used by the struct in order to
* hold a history of the samples. The define `{NAME}_DATA_BUFFER_ELEMENTS`
* specifies exactly the number of int32_t elements to allocate for
* the filter `{NAME}` to correctly function.
* @param data_buffer_elements The number of words contained in the data array, this should be
`{NAME}_DATA_BUFFER_ELEMENTS`.
*/
void td_block_fir_data_init(
td_block_fir_data_t * fir_data,
int32_t *data,
uint32_t data_buffer_elements);
/**
* @brief Function to add samples to the FIR data structure.
*
* @param samples_in Array of int32_t samples of length TD_BLOCK_FIR_LENGTH.
* @param fir_data Pointer to struct of type td_block_fir_data_t to which
* the samples will be added.
*/
void td_block_fir_add_data(
int32_t samples_in[TD_BLOCK_FIR_LENGTH],
td_block_fir_data_t * fir_data);
/**
* @brief Function to compute the convolution between fir_data and fir_filter.
*
* @param samples_out Array of length TD_BLOCK_FIR_LENGTH(8), which will
* be used to return the processed samples.
* @param fir_data Pointer to struct of type td_block_fir_data_t from
* which the data samples will be obtained.
* @param fir_filter Pointer to struct of type td_block_fir_filter_t from
* which the coefficients will be obtained.
*/
void td_block_fir_compute(
int32_t samples_out[TD_BLOCK_FIR_LENGTH],
td_block_fir_data_t * fir_data,
td_block_fir_filter_t * fir_filter);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "bump_allocator.h"
typedef struct
{
int n_inputs;
int n_outputs;
int frame_size;
}adder_state_t;
#define ADDER_STAGE_REQUIRED_MEMORY 0
void adder_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void adder_process(int32_t **input, int32_t **output, void *app_data_state);

View File

@@ -0,0 +1,125 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/// @file
///
/// The control API for the generated DSP.
///
/// These functions can be executed on any thread which is on the same tile as the
/// generated DSP threads.
///
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "adsp_module.h"
#include "adsp_pipeline.h"
#include "swlock.h"
/// The command to execute. Specifies which stage, what command and contains the buffer
/// to read from or write to.
typedef struct
{
/// The ID of the stage to target. Consider setting the label parameter in the pipeline
/// definition to ensure that a usable identifier gets generated for using with control.
uint8_t instance_id;
/// "See the generated cmds.h for the available commands. Make sure to use a command
/// which is supported for the target stage.
uint8_t cmd_id;
/// Length of the command in bytes.
uint16_t payload_len;
/// The buffer. Must be set to a valid array of size payload_len before calling
/// the read or write functions.
void *payload;
}adsp_stage_control_cmd_t;
/// Control status.
typedef enum
{
ADSP_CONTROL_SUCCESS, ///< Command succesfully executed.
ADSP_CONTROL_BUSY, ///< Stage has not yet processed the command, call again.
}adsp_control_status_t;
/// Object used to control a DSP pipeline.
///
/// As there may be multiple threads attempting to interact with the DSP pipeline at
/// the same time, a separate instance of @ref adsp_controller_t must be used by each
/// to ensure that control can proceed safely.
///
/// Initialise each instance of @ref adsp_controller_t with @ref adsp_controller_init.
typedef struct {
/// @privatesection
module_instance_t* modules;
size_t num_modules;
} adsp_controller_t;
/// Create a DSP controller instance for a particular pipeline.
///
/// @param ctrl The controller instance to initialise.
/// @param pipeline The DSP pipeline that will be controlled with this controller.
void adsp_controller_init(adsp_controller_t* ctrl, adsp_pipeline_t* pipeline);
/// Initiate a read command by passing in an intialised @ref adsp_stage_control_cmd_t.
///
/// Must be called repeatedly with the same cmd until ADSP_CONTROL_SUCCESS is returned. If the caller
/// abandons the attempt to read before SUCCESS is returned then this will leave the stage in a state
/// where it can never be read from again.
///
/// @param ctrl An instance of adsp_controller_t which has been initialised to control the DSP pipeline.
/// @param cmd An initialised @ref adsp_stage_control_cmd_t.
/// @return @ref adsp_control_status_t
adsp_control_status_t adsp_read_module_config(
adsp_controller_t* ctrl,
adsp_stage_control_cmd_t *cmd
);
/// Initiate a write command by passing in an initialised @ref adsp_stage_control_cmd_t.
///
/// Must be called repeatedly with the same cmd until ADSP_CONTROL_SUCCESS is returned.
///
/// @param ctrl An instance of adsp_controller_t which has been initialised to control the DSP pipeline.
/// @param cmd An initialised @ref adsp_stage_control_cmd_t.
/// @return @ref adsp_control_status_t
adsp_control_status_t adsp_write_module_config(
adsp_controller_t* ctrl,
adsp_stage_control_cmd_t *cmd
);
/// Default xscope setup function.
///
/// Sets up a single xscope probe with name ADSP, type XSCOPE_CONTINUOUS, and datatype XSCOPE_UINT.
/// Should be called within xscope_user_init().
void adsp_control_xscope_register_probe();
/// Creates an xscope chanend and connects it to the host. Must be called on the same tile as the DSP pipeline.
/// @return chanend_t
chanend_t adsp_control_xscope_init();
/// Process an xscope chanend containing a control command from the host.
///
/// @param c_xscope A chanend which has been connected to the host.
/// @param ctrl An instance of adsp_controller_t which has been initialised to control the DSP pipeline.
/// @return @ref adsp_control_status_t
adsp_control_status_t adsp_control_xscope_process(
chanend_t c_xscope,
adsp_controller_t *ctrl
);
/// Creates an xscope handler thread for ADSP control.
///
/// Handles all xscope traffic and calls to @ref adsp_read_module_config and
/// @ref adsp_write_module_config. If the application already uses xscope, do
/// not call this function; instead, identify host-to-device packets by the ADSP
/// header and pass them to @ref adsp_control_xscope_process manually.
///
/// @param adsp The DSP pipeline that will be controlled with this xscope thread.
#ifndef __DOXYGEN__
DECLARE_JOB(adsp_control_xscope, (adsp_pipeline_t *));
#else
void adsp_control_xscope(adsp_pipeline_t * adsp);
#endif

View File

@@ -0,0 +1,138 @@
// Copyright 2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
// Very simple FIFO designed to provide channel-like API with asychronous comms.
//
// This utility is used in the generated pipeline and should not be considered safe
// for use in application code.
//
// This is single-producer, single-consumer FIFO. The assumption is that the producer
// will have 1 or more things to send to the receiver in a transation. The producer
// can therefore call write multiple times before finishing the transaction. The consumer
// will be blocked until the producer has completed a transaction and will then be able
// to read from the FIFO.
//
// This FIFO also uses a channel to notify the consumer that there is data available.
// This is to allow the consumer to have a select case which is activated by the FIFO.
//
// To do this select on the `rx_end` member of adsp_fifo_t.
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "swlock.h"
#include "xcore/chanend.h"
#include "xcore/channel.h"
typedef enum {
_ADSP_FIFO_READ,
_ADSP_FIFO_WRITE,
_ADSP_FIFO_READ_DONE,
_ADSP_FIFO_WRITE_DONE,
} adsp_fifo_state_t;
typedef struct {
uint8_t *buffer;
int32_t head; // Index of the next byte to read
chanend_t tx_end;
chanend_t rx_end;
volatile adsp_fifo_state_t state; // State of the FIFO
} adsp_fifo_t;
/**
* Initialize a FIFO.
*
* Provide a buffer which will be used to hold the data. The buffer must be big
* enough to hold the sum of all data written in a write transaction.
*
* @warning its on you to make sure the buffer is big enough
*
* @param fifo Pointer to FIFO structure to initialize
*/
static inline void adsp_fifo_init(adsp_fifo_t* fifo, void* buffer) {
fifo->head = 0;
fifo->state = _ADSP_FIFO_READ_DONE;
channel_t c = chan_alloc();
fifo->tx_end = c.end_a;
fifo->rx_end = c.end_b;
fifo->buffer = (uint8_t*)buffer;
}
/**
* Start a write, blocks until fifo state is ready for a write.
*/
static inline void adsp_fifo_write_start(adsp_fifo_t* fifo) {
while (fifo->state != _ADSP_FIFO_READ_DONE) {
// Wait for the FIFO to be ready for writing
}
fifo->state = _ADSP_FIFO_WRITE;
fifo->head = 0;
}
/**
* Write data to the FIFO.
*
* Always call adsp_fifo_write_start() before this function to ensure
* the FIFO is in the correct state for writing.
*
* Always call adsp_fifo_write_done() after this function to notify
* The reading thread that the FIFO is ready for reading again.
*/
static inline void adsp_fifo_write(adsp_fifo_t* fifo, const void* data, size_t size_bytes) {
memcpy(&fifo->buffer[fifo->head], data, size_bytes);
fifo->head += size_bytes;
}
/**
* Update FIFO state to indicate the write is finished.
*
* This also sends a word on the internal channel to notify
* a waiting thread that the FIFO is ready for a read.
*/
static inline void adsp_fifo_write_done(adsp_fifo_t* fifo) {
fifo->state = _ADSP_FIFO_WRITE_DONE;
// send notification
chanend_out_word(fifo->tx_end, 0);
}
/**
* Wait until write is done and update state to show read is in progress.
*
* Reads from the internal channel to clear the notification.
*/
static inline void adsp_fifo_read_start(adsp_fifo_t* fifo) {
chanend_in_word(fifo->rx_end);
while (fifo->state != _ADSP_FIFO_WRITE_DONE) {
// Wait for the FIFO to be ready for reading
}
// clear notification
fifo->state = _ADSP_FIFO_READ;
fifo->head = 0;
}
/**
* Read data from the FIFO.
*
* Always call adsp_fifo_read_start() before this function to ensure
* the FIFO is in the correct state for reading.
*
* Always call adsp_fifo_read_done() after this function to notify
* writing thread that the FIFIO is ready for writing again.
*
* @param fifo Pointer to the FIFO
* @param data Pointer to buffer to store read data
* @param size_bytes Number of bytes to read
*/
static inline void adsp_fifo_read(adsp_fifo_t* fifo, void* data, size_t size_bytes) {
memcpy(data, &fifo->buffer[fifo->head], size_bytes);
fifo->head += size_bytes;
}
/**
* Update FIFO state to indicate the read is finished.
*/
static inline void adsp_fifo_read_done(adsp_fifo_t* fifo) {
fifo->state = _ADSP_FIFO_READ_DONE;
}

View File

@@ -0,0 +1,45 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/// @file
///
/// Defines the generic structs that will hold the state and control configuration
/// for each stage.
#pragma once
#include <stdint.h>
#include <swlock.h>
/// Control states, used to communicate between DSP and control threads
/// to notify when control needs processing.
typedef enum
{
config_read_pending, ///< Control waiting to read the updated config from DSP.
config_write_pending, ///< Config written by control and waiting for DSP to update.
config_read_updated, ///< Stage has succesfully consumed a read command.
config_none_pending ///< All done. Control and DSP not waiting on anything.
} config_rw_state_t;
/// Control related information shared between control thread and DSP.
typedef struct
{
void *config; ///< Pointer to a stage-specific config struct which is used by the control thread.
uint32_t id; ///< Unique module identifier assigned by the host
uint32_t num_control_commands; ///< The number of control commands for this stage.
uint8_t module_type; ///< Identifies the stage type. Each type of stage has a unique identifier.
uint8_t cmd_id; ///< Is set to the current command being processed.
config_rw_state_t config_rw_state;
intptr_t current_controller; ///< id of the current control object that requested a read, do not modify.
swlock_t lock; ///< lock used by controlling threads to manage access
}module_control_t;
/// The entire state of a stage in the pipeline.
typedef struct
{
void *state; ///< Pointer to the module's state memory.
module_control_t control; ///< Module's control state.
void *constants;
}module_instance_t;

View File

@@ -0,0 +1,157 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/// @file
///
/// Generated pipeline interface. Use the source and sink functions defined here
/// to send samples to the generated DSP and receive processed samples back.
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <xcore/chanend.h>
#include <xcore/channel.h>
#include <xcore/parallel.h>
#include <xcore/select.h>
#include "adsp_module.h"
#include "stages/adsp_fifo.h"
/// @cond
///
/// Private stuff
/// Mapping of input index to channel index for the source and sink configuration.
typedef struct
{
/// @privatesection
uint32_t channel_idx;
uint32_t data_idx;
uint32_t frame_size;
} adsp_mux_elem_t;
/// Source and sink configuration.
typedef struct
{
/// @privatesection
adsp_mux_elem_t *chan_cfg;
size_t n_chan;
} adsp_mux_t;
/// Check if a chanend has an event pending.
static inline bool check_chanend(chanend_t c) {
SELECT_RES(CASE_THEN(c, has_data), DEFAULT_THEN(no_data)) {
has_data: return true;
no_data: return false;
}
}
/// @endcond
/// The DSP pipeline.
///
/// The generated pipeline will contain an init function that returns a pointer
/// to one of these. It can be used to send data in and out of the pipeline, and
/// also execute control commands.
typedef struct
{
/// @privatesection
adsp_fifo_t *p_in;
size_t n_in;
channel_t *p_out;
size_t n_out;
channel_t *p_link;
size_t n_link;
/// @publicsection
module_instance_t *modules; ///< Array of DSP stage states, must be used when calling one of the control functions.
size_t n_modules; ///< Number of modules in the @ref adsp_pipeline_t::modules array.
/// @privatesection
adsp_mux_t input_mux;
adsp_mux_t output_mux;
} adsp_pipeline_t;
/// Pass samples into the DSP pipeline.
///
/// These samples are sent by value to the other thread, therefore the data buffer can be reused
/// immediately after this function returns. This function copies the data to a shared buffer where
/// the DSP pipeline will read them. If called again before the DSP pipeline has emptied the buffer
/// then this will block. If called when the buffer is empty then it will not block.
///
/// If this is called on the same thread as `adsp_pipeline_sink` then it will be most efficient to call
/// sink first followed by source. This allows the channel transactions internal to the pipeline to be
/// completed while the app is copying the input data to the buffer.
///
/// @param adsp The initialised pipeline.
/// @param data An array of arrays of samples. The length of the array shall be the number
/// of pipeline input channels. Each array contained within shall be contain a frame
/// of samples large enough to pass to the stage that it is connected to.
static inline void adsp_pipeline_source(adsp_pipeline_t *adsp, int32_t **data)
{
for(int i = 0; i < adsp->n_in; i++)
{
adsp_fifo_write_start(&adsp->p_in[i]);
}
for (size_t chan_id = 0; chan_id < adsp->input_mux.n_chan; chan_id++)
{
adsp_mux_elem_t cfg = adsp->input_mux.chan_cfg[chan_id];
adsp_fifo_write(&adsp->p_in[cfg.channel_idx],
(uint32_t *)data[cfg.data_idx],
cfg.frame_size*4);
}
for(int i = 0; i < adsp->n_in; i++)
{
adsp_fifo_write_done(&adsp->p_in[i]);
}
}
/// Receive samples from the DSP pipeline.
///
/// Sink is implemented via a blocking channel transaction with the DSP thread and therefore will
/// block until the output threads are available to send the processed data. It should also be noted
/// that if `adsp_pipeline_sink` is called late then the DSP pipeline will block waiting for the
/// application to read the processed data. The application author must take care to ensure that data
/// is being read in a timely fashion.
///
/// @param adsp The initialised pipeline.
/// @param data An array of arrays that will be filled with processed samples from the pipeline.
/// The length of the array shall be the number
/// of pipeline input channels. Each array contained within shall be contain a frame
/// of samples large enough to pass to the stage that it is connected to.
static inline void adsp_pipeline_sink(adsp_pipeline_t *adsp, int32_t **data)
{
for (size_t chan_id = 0; chan_id < adsp->output_mux.n_chan; chan_id++)
{
adsp_mux_elem_t cfg = adsp->output_mux.chan_cfg[chan_id];
chan_in_buf_word(adsp->p_out[cfg.channel_idx].end_b,
(uint32_t *)data[cfg.data_idx],
cfg.frame_size);
}
}
/// Non-blocking receive from the pipeline. It is risky to use this API in an isochronous
/// application as the sink thread can lose synchronisation with the source thread which can
/// cause the source thread to block.
///
/// @param adsp The initialised pipeline.
/// @param data See adsp_pipeline_sink for details of same named param.
/// @retval true The data buffer has been filled with new values from the pipeline.
/// @retval false The pipeline has not produced any more data. The data buffer was untouched.
static inline bool adsp_pipeline_sink_nowait(adsp_pipeline_t *adsp,
int32_t **data)
{
bool ready = true;
for (size_t chan = 0; chan < adsp->n_out; chan++)
{
ready &= check_chanend(adsp->p_out[chan].end_b);
}
if (ready)
{
adsp_pipeline_sink(adsp, data);
}
return ready;
}

View File

@@ -0,0 +1,40 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/biquad.h"
#include "biquad_config.h" // Autogenerated
#include "bump_allocator.h"
#define BIQUAD_STATE_LEN (8)
typedef struct
{
biquad_config_t config;
int32_t **filter_states;
int n_inputs;
int n_outputs;
int frame_size;
}biquad_state_t;
#define _BQ_FILTER_MEMORY \
(BIQUAD_STATE_LEN * sizeof(int32_t))
#define _BQ_ALL_FILTER_MEMORY(N_IN) \
(ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(_BQ_FILTER_MEMORY) * (N_IN))
#define _BQ_ARR_MEMORY(N_IN) \
((N_IN) * sizeof(int32_t*))
#define BIQUAD_STAGE_REQUIRED_MEMORY(N_IN) \
_BQ_ALL_FILTER_MEMORY(N_IN) + _BQ_ARR_MEMORY(N_IN)
void biquad_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void biquad_process(int32_t **input, int32_t **output, void *app_data_state);
void biquad_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,41 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/biquad.h"
#include "biquad_slew_config.h" // Autogenerated
#include "bump_allocator.h"
#define BIQUAD_SLEW_STATE_LEN (8)
typedef struct
{
biquad_slew_config_t config;
int32_t **filter_states;
biquad_slew_t slew_state;
int n_inputs;
int n_outputs;
int frame_size;
}biquad_slew_state_t;
#define _BQ_SLEW_FILTER_MEMORY \
(BIQUAD_SLEW_STATE_LEN * sizeof(int32_t))
#define _BQ_SLEW_ALL_FILTER_MEMORY(N_IN) \
(ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(_BQ_SLEW_FILTER_MEMORY) * (N_IN))
#define _BQ_SLEW_ARR_MEMORY(N_IN) \
((N_IN) * sizeof(int32_t*))
#define BIQUAD_SLEW_STAGE_REQUIRED_MEMORY(N_IN) \
_BQ_SLEW_ALL_FILTER_MEMORY(N_IN) + _BQ_SLEW_ARR_MEMORY(N_IN)
void biquad_slew_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void biquad_slew_process(int32_t **input, int32_t **output, void *app_data_state);
void biquad_slew_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,40 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/// Implementation of a bump allocator. Bump allocators do not support freeing the memory.
#pragma once
#include <stdlib.h>
#include <stddef.h>
/// Bump allocator struct. Initialise with ADSP_BUMP_ALLOCATOR_INITIALISER and
/// use bump_allocator_malloc() to claim memory.
typedef struct {
char* buf;
int n_bytes_left;
} adsp_bump_allocator_t;
/// Initialise a bump allocator with this. Expects an array (not a pointer).
/// lifetime of the array must be at least as long as the lifetime of the
/// allocator.
#define ADSP_BUMP_ALLOCATOR_INITIALISER(array) {(sizeof(array) > 0) ? (void*)(array) : NULL, (sizeof(array) / sizeof(*(array)))}
/// Determine buf size required to ensure it can be DWORD_ALIGNED, assumes malloc is always word alligned.
#define ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(N) ( ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(N) + 4 )
/// Allocate a DWORD_ALIGNED buffer
#define ADSP_BUMP_ALLOCATOR_DWORD_ALLIGNED_MALLOC(allocator, N) \
(void*)(((((uint64_t)adsp_bump_allocator_malloc(allocator, ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(N)) + 4)) >> 3) << 3)
// Round N up to a whole number of words.
#define ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(N) ((N) + (4 - ((N) & 0x3)))
// Allocates N bytes from allocator, ensuring that the result is word aligned.
#define ADSP_BUMP_ALLOCATOR_WORD_ALLIGNED_MALLOC(allocator, N) \
adsp_bump_allocator_malloc(allocator, ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(N))
/// Allocate some memory from the allocator, traps if there is not enough memory.
///
/// This will trap if n_bytes is not a multiple of 4, this prevents the allocator
/// from producing unaligned buffers.
void* adsp_bump_allocator_malloc(adsp_bump_allocator_t* allocator, size_t n_bytes);

View File

@@ -0,0 +1,18 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
typedef struct
{
int n_inputs;
int n_outputs;
int frame_size;
}bypass_state_t;
#define BYPASS_STAGE_REQUIRED_MEMORY 0
void bypass_init(module_instance_t* module_instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void bypass_process(int32_t **input, int32_t **output, void *app_data_state);

View File

@@ -0,0 +1,34 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/cascaded_biquads.h"
#include "cascaded_biquads_config.h" // Autogenerated
#define CASCADED_BIQUADS_STATE_LEN (64)
typedef struct
{
cascaded_biquads_config_t config;
int32_t **filter_states;
int n_inputs;
int n_outputs;
int frame_size;
}cascaded_biquads_state_t;
#define _CBQ_FILTER_MEMORY \
(CASCADED_BIQUADS_STATE_LEN * sizeof(int32_t))
#define _CBQ_ALL_FILTER_MEMORY(N_IN) \
(ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(_CBQ_FILTER_MEMORY) * (N_IN))
#define _CBQ_ARR_MEMORY(N_IN) \
((N_IN) * sizeof(int32_t*))
#define CASCADED_BIQUADS_STAGE_REQUIRED_MEMORY(N_IN) \
_CBQ_ALL_FILTER_MEMORY(N_IN) + _CBQ_ARR_MEMORY(N_IN)
void cascaded_biquads_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void cascaded_biquads_process(int32_t **input, int32_t **output, void *app_data_state);
void cascaded_biquads_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,34 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/cascaded_biquads.h"
#include "cascaded_biquads_16_config.h" // Autogenerated
#define CASCADED_BIQUADS_16_STATE_LEN (128)
typedef struct
{
cascaded_biquads_16_config_t config;
int32_t **filter_states;
int n_inputs;
int n_outputs;
int frame_size;
}cascaded_biquads_16_state_t;
#define _CBQ16_FILTER_MEMORY \
(CASCADED_BIQUADS_16_STATE_LEN * sizeof(int32_t))
#define _CBQ16_ALL_FILTER_MEMORY(N_IN) \
(ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(_CBQ16_FILTER_MEMORY) * (N_IN))
#define _CBQ16_ARR_MEMORY(N_IN) \
((N_IN) * sizeof(int32_t*))
#define CASCADED_BIQUADS_16_STAGE_REQUIRED_MEMORY(N_IN) \
_CBQ16_ALL_FILTER_MEMORY(N_IN) + _CBQ16_ARR_MEMORY(N_IN)
void cascaded_biquads_16_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void cascaded_biquads_16_process(int32_t **input, int32_t **output, void *app_data_state);
void cascaded_biquads_16_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "clipper_config.h" // Autogenerated
typedef struct
{
clipper_t * clip;
int n_inputs;
int n_outputs;
int frame_size;
}clipper_state_t;
#define CLIPPER_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(clipper_t))
void clipper_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void clipper_process(int32_t **input, int32_t **output, void *app_data_state);
void clipper_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "compressor_rms_config.h" // Autogenerated
typedef struct
{
compressor_t *comp;
int n_inputs;
int n_outputs;
int frame_size;
}compressor_rms_state_t;
#define COMPRESSOR_RMS_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(compressor_t))
void compressor_rms_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void compressor_rms_process(int32_t **input, int32_t **output, void *app_data_state);
void compressor_rms_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "compressor_sidechain_config.h" // Autogenerated
typedef struct
{
compressor_t comp;
int n_inputs;
int n_outputs;
int frame_size;
}compressor_sidechain_state_t;
#define COMPRESSOR_SIDECHAIN_STAGE_REQUIRED_MEMORY (0)
void compressor_sidechain_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void compressor_sidechain_process(int32_t **input, int32_t **output, void *app_data_state);
void compressor_sidechain_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "compressor_sidechain_stereo_config.h" // Autogenerated
typedef struct
{
compressor_stereo_t comp;
int n_inputs;
int n_outputs;
int frame_size;
}compressor_sidechain_stereo_state_t;
#define COMPRESSOR_SIDECHAIN_STEREO_STAGE_REQUIRED_MEMORY (0)
void compressor_sidechain_stereo_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void compressor_sidechain_stereo_process(int32_t **input, int32_t **output, void *app_data_state);
void compressor_sidechain_stereo_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "crossfader_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
crossfader_slew_t cfs;
int n_inputs;
int n_outputs;
int frame_size;
}crossfader_state_t;
#define CROSSFADER_STAGE_REQUIRED_MEMORY 0
void crossfader_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void crossfader_process(int32_t **input, int32_t **output, void *app_data_state);
void crossfader_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "crossfader_stereo_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
crossfader_slew_t cfs;
int n_inputs;
int n_outputs;
int frame_size;
}crossfader_stereo_state_t;
#define CROSSFADER_STEREO_STAGE_REQUIRED_MEMORY 0
void crossfader_stereo_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void crossfader_stereo_process(int32_t **input, int32_t **output, void *app_data_state);
void crossfader_stereo_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,28 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "delay_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
delay_t *delay;
int n_inputs;
int n_outputs;
int frame_size;
}delay_state_t;
#define DELAY_STAGE_REQUIRED_MEMORY(N_CH, SAMPLES) (N_CH * (DELAY_DSP_REQUIRED_MEMORY_SAMPLES(SAMPLES) + sizeof(delay_t)))
void delay_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void delay_process(int32_t **input, int32_t **output, void *app_data_state);
void delay_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,17 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "adsp_module.h"
#include "bump_allocator.h"
#include "dsp_thread_config.h" // Autogenerated
typedef struct
{
uint32_t max_cycles;
}dsp_thread_state_t;
#define DSP_THREAD_STAGE_REQUIRED_MEMORY 0
void dsp_thread_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void dsp_thread_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "envelope_detector_peak_config.h" // Autogenerated
typedef struct
{
env_detector_t *env_det;
int n_inputs;
int n_outputs;
int frame_size;
}envelope_detector_peak_state_t;
#define ENVELOPE_DETECTOR_PEAK_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(env_detector_t))
void envelope_detector_peak_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void envelope_detector_peak_process(int32_t **input, int32_t **output, void *app_data_state);
void envelope_detector_peak_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "envelope_detector_rms_config.h" // Autogenerated
typedef struct
{
env_detector_t *env_det;
int n_inputs;
int n_outputs;
int frame_size;
}envelope_detector_rms_state_t;
#define ENVELOPE_DETECTOR_RMS_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(env_detector_t))
void envelope_detector_rms_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void envelope_detector_rms_process(int32_t **input, int32_t **output, void *app_data_state);
void envelope_detector_rms_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,42 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/fir.h"
// Normally we would #include "fir_direct_config.h", but this module has
// no runtime configurable parameters
#include "bump_allocator.h"
typedef struct
{
fir_direct_t *fir_direct;
int n_inputs;
int n_outputs;
int frame_size;
}fir_direct_state_t;
typedef struct
{
int32_t n_taps;
int32_t shift;
int32_t* coeffs;
}fir_direct_constants_t;
#define FIR_DIRECT_STAGE_REQUIRED_MEMORY(N_CH, SAMPLES) \
(((N_CH) * ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(FIR_DIRECT_DSP_REQUIRED_MEMORY_SAMPLES(SAMPLES))) \
+ ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(N_CH * sizeof(fir_direct_t)))
void fir_direct_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void fir_direct_process(int32_t **input, int32_t **output, void *app_data_state);
void fir_direct_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "fixed_gain_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
fixed_gain_config_t config;
int n_inputs;
int n_outputs;
int frame_size;
}fixed_gain_state_t;
#define FIXED_GAIN_STAGE_REQUIRED_MEMORY 0
void fixed_gain_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void fixed_gain_process(int32_t **input, int32_t **output, void *app_data_state);
void fixed_gain_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,21 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
typedef struct
{
int n_inputs;
int n_outputs;
int frame_size;
int n_forks; // n_outputs / n_inputs
}fork_state_t;
#define FORK_STAGE_REQUIRED_MEMORY 0
void fork_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void fork_process(int32_t **input, int32_t **output, void *app_data_state);

View File

@@ -0,0 +1,42 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/graphic_eq.h"
#include "graphic_eq_10b_config.h" // Autogenerated
#define GEQ10_STATE_LEN (160)
typedef struct
{
graphic_eq_10b_config_t config;
int32_t **filter_states;
q2_30 *filter_coeffs;
int n_inputs;
int n_outputs;
int frame_size;
}graphic_eq_10b_state_t;
typedef struct
{
q2_30 *coeffs;
}graphic_eq_10b_constants_t;
#define _GEQ10_FILTER_MEMORY \
(GEQ10_STATE_LEN * sizeof(int32_t))
#define _GEQ10_ALL_FILTER_MEMORY(N_IN) \
(ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(_GEQ10_FILTER_MEMORY) * (N_IN))
#define _GEQ10_ARR_MEMORY(N_IN) \
((N_IN) * sizeof(int32_t*))
#define GRAPHIC_EQ_10B_STAGE_REQUIRED_MEMORY(N_IN) \
(_GEQ10_ALL_FILTER_MEMORY(N_IN) + _GEQ10_ARR_MEMORY(N_IN))
void graphic_eq_10b_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void graphic_eq_10b_process(int32_t **input, int32_t **output, void *app_data_state);
void graphic_eq_10b_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "hard_limiter_peak_config.h" // Autogenerated
typedef struct
{
limiter_t *lim;
int n_inputs;
int n_outputs;
int frame_size;
}hard_limiter_peak_state_t;
#define HARD_LIMITER_PEAK_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(limiter_t))
void hard_limiter_peak_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void hard_limiter_peak_process(int32_t **input, int32_t **output, void *app_data_state);
void hard_limiter_peak_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "limiter_peak_config.h" // Autogenerated
typedef struct
{
limiter_t *lim;
int n_inputs;
int n_outputs;
int frame_size;
}limiter_peak_state_t;
#define LIMITER_PEAK_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(limiter_t))
void limiter_peak_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void limiter_peak_process(int32_t **input, int32_t **output, void *app_data_state);
void limiter_peak_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "limiter_rms_config.h" // Autogenerated
typedef struct
{
limiter_t *lim;
int n_inputs;
int n_outputs;
int frame_size;
}limiter_rms_state_t;
#define LIMITER_RMS_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(limiter_t))
void limiter_rms_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void limiter_rms_process(int32_t **input, int32_t **output, void *app_data_state);
void limiter_rms_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "mixer_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
mixer_config_t config;
int n_inputs;
int n_outputs;
int frame_size;
}mixer_state_t;
#define MIXER_STAGE_REQUIRED_MEMORY 0
void mixer_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void mixer_process(int32_t **input, int32_t **output, void *app_data_state);
void mixer_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,23 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "noise_gate_config.h" // Autogenerated
typedef struct
{
noise_gate_t *ng;
int n_inputs;
int n_outputs;
int frame_size;
}noise_gate_state_t;
#define NOISE_GATE_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(noise_gate_t))
void noise_gate_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void noise_gate_process(int32_t **input, int32_t **output, void *app_data_state);
void noise_gate_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "bump_allocator.h"
#include "dsp/drc.h"
#include "noise_suppressor_expander_config.h" // Autogenerated
typedef struct
{
noise_suppressor_expander_t *nse;
int n_inputs;
int n_outputs;
int frame_size;
}noise_suppressor_expander_state_t;
#define NOISE_SUPPRESSOR_EXPANDER_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * ADSP_BUMP_ALLOCATOR_DWORD_N_BYTES(sizeof(noise_suppressor_expander_t)))
#define NOISE_SUPPRESSOR_EXPANDER_STAGE_REQUIRED_MEMORY_SLIM(N_IN) (N_IN * sizeof(noise_suppressor_expander_t))
void noise_suppressor_expander_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void noise_suppressor_expander_process(int32_t **input, int32_t **output, void *app_data_state);
void noise_suppressor_expander_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,17 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "adsp_module.h"
#include "bump_allocator.h"
#include "pipeline_config.h" // Autogenerated
typedef struct
{
uint8_t checksum[16];
}pipeline_state_t;
#define PIPELINE_STAGE_REQUIRED_MEMORY 0
void pipeline_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void pipeline_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,35 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <stdint.h>
#include "dsp/adsp.h"
#include "reverb_plate_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
reverb_plate_t rv;
int n_inputs;
int n_outputs;
int frame_size;
}reverb_plate_state_t;
typedef struct
{
uint32_t sampling_freq;
float max_predelay;
} reverb_plate_constants_t;
#define REVERB_PLATE_STAGE_REQUIRED_MEMORY(FS, PD) (ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(REVERB_PLATE_DSP_REQUIRED_MEMORY(FS, PD)))
void reverb_plate_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void reverb_plate_process(int32_t **input, int32_t **output, void *app_data_state);
void reverb_plate_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,36 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <stdint.h>
#include "dsp/adsp.h"
#include "reverb_room_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
reverb_room_t rv;
int n_inputs;
int n_outputs;
int frame_size;
}reverb_room_state_t;
typedef struct
{
uint32_t sampling_freq;
float max_room_size;
float max_predelay;
} reverb_room_constants_t;
#define REVERB_ROOM_STAGE_REQUIRED_MEMORY(FS, MAX_ROOM_SIZE, PD) (ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(REVERB_ROOM_DSP_REQUIRED_MEMORY(FS, MAX_ROOM_SIZE, PD)))
void reverb_room_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void reverb_room_process(int32_t **input, int32_t **output, void *app_data_state);
void reverb_room_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,36 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include <stdint.h>
#include "dsp/adsp.h"
#include "reverb_room_st_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
reverb_room_st_t rv;
int n_inputs;
int n_outputs;
int frame_size;
}reverb_room_st_state_t;
typedef struct
{
uint32_t sampling_freq;
float max_room_size;
float max_predelay;
} reverb_room_st_constants_t;
#define REVERB_ROOM_ST_STAGE_REQUIRED_MEMORY(FS, MAX_ROOM_SIZE, PD) (ADSP_BUMP_ALLOCATOR_WORD_N_BYTES(REVERB_ROOM_ST_DSP_REQUIRED_MEMORY(FS, MAX_ROOM_SIZE, PD)))
void reverb_room_st_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size);
void reverb_room_st_process(int32_t **input, int32_t **output, void *app_data_state);
void reverb_room_st_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,22 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "bump_allocator.h"
typedef struct
{
int n_inputs;
int n_outputs;
int frame_size;
}subtractor_state_t;
#define SUBTRACTOR_STAGE_REQUIRED_MEMORY 0
void subtractor_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void subtractor_process(int32_t **input, int32_t **output, void *app_data_state);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "switch_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
switch_config_t config;
int n_inputs;
int n_outputs;
int frame_size;
}switch_state_t;
#define SWITCH_STAGE_REQUIRED_MEMORY 0
void switch_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void switch_process(int32_t **input, int32_t **output, void *app_data_state);
void switch_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,30 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "switch_slew_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
switch_slew_config_t config;
switch_slew_t switch_state;
int n_inputs;
int n_outputs;
int frame_size;
}switch_slew_state_t;
typedef struct
{
uint32_t fs;
} switch_slew_constants_t;
#define SWITCH_SLEW_STAGE_REQUIRED_MEMORY 0
void switch_slew_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void switch_slew_process(int32_t **input, int32_t **output, void *app_data_state);
void switch_slew_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/signal_chain.h"
#include "switch_stereo_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
switch_stereo_config_t config;
int n_inputs;
int n_outputs;
int frame_size;
}switch_stereo_state_t;
#define SWITCH_STEREO_STAGE_REQUIRED_MEMORY 0
void switch_stereo_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void switch_stereo_process(int32_t **input, int32_t **output, void *app_data_state);
void switch_stereo_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#pragma once
#include "dsp/adsp.h"
#include "volume_control_config.h" // Autogenerated
#include "bump_allocator.h"
typedef struct
{
volume_control_t * vol_ctl;
int n_inputs;
int n_outputs;
int frame_size;
}volume_control_state_t;
#define VOLUME_CONTROL_STAGE_REQUIRED_MEMORY(N_IN) (N_IN * sizeof(volume_control_t))
void volume_control_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size);
void volume_control_process(int32_t **input, int32_t **output, void *app_data_state);
void volume_control_control(void *state, module_control_t *control);

View File

@@ -0,0 +1,110 @@
set(LIB_C_SRCS "")
# The sources in the "stages" subdirectories of src/ and api/ require
# some code generation to take place. The below implements the code
# generation using some Python that is a part of this repo.
#
# As this repo should also be available as a general purpose DSP library
# for which no code generation is requried it is desired that installing the
# Python dependencies should not be required for that use case. Therefore
# the below checks if the dependencies are available. If they are then it
# always adds the code gen to the build and it is up to Make to decide
# if they are needed. If the dependencies are not present, then the auto gen
# will not be added to the build, a message is printed, and any build which
# uses the stages api will fail at compile time.
set(STAGES_INCLUDED OFF)
find_program(PYTHON_EXE python NO_CACHE)
if(PYTHON_EXE)
execute_process(COMMAND ${PYTHON_EXE} -c "import audio_dsp"
OUTPUT_QUIET ERROR_QUIET RESULT_VARIABLE AUDIO_DSP_NOT_INSTALLED)
if(NOT ${AUDIO_DSP_NOT_INSTALLED})
set(ADSP_ADDITIONAL_STAGE_CONFIG "" CACHE STRING "semicolon separated list of stage yaml config files")
set(STAGES_INCLUDED ON)
set(AUTOGEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/src.autogen )
set(LIB_AUDIO_DSP_PATH ${CMAKE_CURRENT_LIST_DIR})
set(CONFIG_YAML_PATH ${LIB_AUDIO_DSP_PATH}/../stage_config)
file(GLOB MODULE_CONFIG_YAML_FILES ${CONFIG_YAML_PATH}/*.yaml )
list(APPEND MODULE_CONFIG_YAML_FILES ${ADSP_ADDITIONAL_STAGE_CONFIG})
file(GLOB TEMPLATE_FILES ${LIB_AUDIO_DSP_PATH}/../python/audio_dsp/design/templates/*.mako)
set(ALL_CONFIG_YAML_DIR ${AUTOGEN_DIR}/yaml)
unset(CMD_MAP_GEN_ARGS)
list(APPEND CMD_MAP_GEN_ARGS --config-dir ${ALL_CONFIG_YAML_DIR} --out-dir ${AUTOGEN_DIR})
set(CMD_MAP_GEN_SCRIPT ${LIB_AUDIO_DSP_PATH}/../python/audio_dsp/design/parse_config.py)
# Get output C file names
set(OUTPUT_C_FILES ${AUTOGEN_DIR}/generator/gen_cmd_map_offset.c)
# output h file names
set(COPIED_YAML_FILES "")
set(OUTPUT_H_FILES ${AUTOGEN_DIR}/common/cmds.h ${AUTOGEN_DIR}/device/cmd_offsets.h ${AUTOGEN_DIR}/host/host_cmd_map.h)
foreach(YAML_FILE ${MODULE_CONFIG_YAML_FILES})
get_filename_component(STAGE_NAME ${YAML_FILE} NAME_WE)
list(APPEND OUTPUT_H_FILES ${AUTOGEN_DIR}/common/${STAGE_NAME}_config.h)
# copy all yaml files to the same directory so
# they can be used by generation script
set(copied_config ${ALL_CONFIG_YAML_DIR}/${STAGE_NAME}.yaml)
add_custom_command(
OUTPUT ${copied_config}
COMMAND ${CMAKE_COMMAND} -E copy ${YAML_FILE} ${copied_config}
DEPENDS ${YAML_FILE}
COMMENT "Copying ${STAGE_NAME}.yaml"
VERBATIM
)
list(APPEND COPIED_YAML_FILES ${copied_config})
endforeach()
add_custom_command(
OUTPUT ${OUTPUT_C_FILES} ${OUTPUT_H_FILES}
COMMAND ${PYTHON_EXE} -m audio_dsp.design.parse_config ${CMD_MAP_GEN_ARGS}
DEPENDS ${COPIED_YAML_FILES} ${CMD_MAP_GEN_SCRIPT} ${TEMPLATE_FILES}
COMMENT "Generating cmd_map files included in the device and host application"
VERBATIM
)
file(RELATIVE_PATH REL_AUTOGEN_DIR ${CMAKE_CURRENT_LIST_DIR} ${AUTOGEN_DIR})
set(PIPELINE_DESIGN_INCLUDE_DIRS ${REL_AUTOGEN_DIR}/common ${REL_AUTOGEN_DIR}/device)
if(NOT TARGET cmd_map_generation)
add_custom_target(cmd_map_generation
DEPENDS ${OUTPUT_C_FILES} ${OUTPUT_H_FILES})
endif()
file(GLOB STAGES_C_SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/src/stages/*.c")
list(APPEND LIB_C_SRCS ${STAGES_C_SOURCES})
else()
message("Excluding lib_audio_dsp stages as audio_dsp Python package not available")
endif()
else()
message("Excluding lib_audio_dsp stages as Python not available")
endif()
set(LIB_NAME lib_audio_dsp)
set(LIB_VERSION 1.4.0)
set(LIB_INCLUDES api ${PIPELINE_DESIGN_INCLUDE_DIRS})
file(GLOB DSP_C_SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/src/dsp/*.c")
file(GLOB BLOCK_FIR_C_SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/src/dsp/block_fir/*.c")
file(GLOB CONTROL_C_SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/src/control/*.c")
list(APPEND LIB_C_SRCS ${DSP_C_SOURCES} ${CONTROL_C_SOURCES} ${BLOCK_FIR_C_SOURCES})
set(LIB_DEPENDENT_MODULES
"lib_xcore_math(2.2.0)"
"lib_logging(3.2.0)"
"lib_locks(2.2.0)"
)
option(LIB_AUDIO_DSP_DISABLE_OPTIMISATION "Set to disable optimisations for better debugging")
set(_LAD_OPT -O3)
if(LIB_AUDIO_DSP_DISABLE_OPTIMISATION)
set(_LAD_OPT -O0)
endif()
set(LIB_COMPILER_FLAGS ${_LAD_OPT} -Wall -Werror -g )
XMOS_REGISTER_MODULE()
if(STAGES_INCLUDED)
foreach(target ${APP_BUILD_TARGETS})
add_dependencies(${target} cmd_map_generation)
endforeach()
endif()

View File

@@ -0,0 +1,608 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "control/adsp_control.h"
#include <math.h>
#include <string.h>
#include <xcore/assert.h>
#include "control/helpers.h"
#define Q_factor 30
static const float pi = (float)M_PI;
static const float log_2 = 0.69314718055f;
static inline float _check_fc(float fc, float fs) {
float fc_sat = fc;
// saturate if > fs/2
if (fc_sat >= fs / 2.0f){
fc_sat = fs / 2.0f;
}
return fc_sat;
}
static inline left_shift_t _get_b_shift(float b0, float b1, float b2) {
// calculate the required headroom for the b coefficients
float max_b = fabsf(b0);
float tmp = fabsf(b1);
if (tmp > max_b){
max_b = tmp;
}
tmp = fabsf(b2);
if (tmp > max_b){
max_b = tmp;
}
if (max_b == 0){
return 0;
}
tmp = floorf(log2f(max_b));
left_shift_t out = (left_shift_t)tmp;
return out > 0 ? out : 0;
}
biquad_slew_t adsp_biquad_slew_init(
q2_30 target_coeffs[8],
left_shift_t lsh,
left_shift_t slew_shift
){
biquad_slew_t slew_state;
memcpy(slew_state.target_coeffs, target_coeffs, 5*sizeof(int32_t));
memcpy(slew_state.active_coeffs, target_coeffs, 5*sizeof(int32_t));
slew_state.remaining_shifts = 0;
slew_state.lsh = lsh;
slew_state.slew_shift = slew_shift < 1 ? 1 : slew_shift;
return slew_state;
}
void adsp_biquad_slew_update_coeffs(
biquad_slew_t* slew_state,
int32_t** states,
int32_t channels,
q2_30 target_coeffs[8],
left_shift_t lsh
){
left_shift_t old_shift = slew_state->lsh;
slew_state->lsh = lsh;
memcpy(slew_state->target_coeffs, target_coeffs, 5*sizeof(int32_t));
left_shift_t b_shift_change = old_shift - slew_state->lsh;
if (b_shift_change == 0){
return;
}
else if(b_shift_change < 0){
// we can shift down safely as we are increasing headroom
b_shift_change = -b_shift_change;
for (int i=0; i < 3; i++){
slew_state->active_coeffs[i] >>= b_shift_change;
}
for (int i=0; i < channels; i++){
states[i][3] >>= b_shift_change;
states[i][4] >>= b_shift_change;
}
return;
}
else {
// we can't shift safely until we know we have headroom
slew_state->remaining_shifts = b_shift_change;
slew_state->lsh += slew_state->remaining_shifts;
}
}
left_shift_t adsp_design_biquad_bypass(q2_30 coeffs[5]) {
coeffs[0] = 1 << Q_factor;
coeffs[1] = 0;
coeffs[2] = 0;
coeffs[3] = 0;
coeffs[4] = 0;
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_mute(q2_30 coeffs[5]) {
coeffs[0] = 0;
coeffs[1] = 0;
coeffs[2] = 0;
coeffs[3] = 0;
coeffs[4] = 0;
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_gain(q2_30 coeffs[5], const float gain_db) {
float A = powf(10.0f, (gain_db * (1.0f / 20.0f)));
left_shift_t b_sh = _get_b_shift(A, 0, 0);
coeffs[0] = _float2fixed_assert( A, Q_factor - b_sh );
coeffs[1] = 0;
coeffs[2] = 0;
coeffs[3] = 0;
coeffs[4] = 0;
return b_sh;
}
left_shift_t adsp_design_biquad_lowpass
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q
) {
float fc_sat = fc;
// saturate if > fs/2
if (fc_sat >= fs / 2.0f){
fc_sat = fs / 2.0f;
}
// Compute common factors
float K = tanf(pi * fc_sat/fs);
float KK = K * K;
float KQ = K / filter_Q;
float norm = 1.0f / (1.0f + KQ + KK);
// Compute coeffs
float b0 = KK * norm;
float b1 = 2.0f * b0;
float b2 = b0;
float a1 = 2.0f * (KK - 1.0f) * norm;
float a2 = (1.0f - KQ + KK) * norm;
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor );
coeffs[1] = _float2fixed_assert( b1, Q_factor );
coeffs[2] = _float2fixed_assert( b2, Q_factor );
coeffs[3] = _float2fixed_assert( -a1, Q_factor );
coeffs[4] = _float2fixed_assert( -a2, Q_factor );
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_highpass
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float K = tanf(pi * fc_sat/fs);
float KK = K * K;
float KQ = K / filter_Q;
float norm = 1.0f / (1.0f + KQ + KK);
// Compute coeffs
float b0 = norm;
float b1 = -2.0f * b0;
float b2 = b0;
float a1 = 2.0f * (KK - 1.0f) * norm;
float a2 = (1.0f - KQ + KK) * norm;
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor );
coeffs[1] = _float2fixed_assert( b1, Q_factor );
coeffs[2] = _float2fixed_assert( b2, Q_factor );
coeffs[3] = _float2fixed_assert( -a1, Q_factor );
coeffs[4] = _float2fixed_assert( -a2, Q_factor );
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_bandpass
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float bandwidth
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float w0 = 2.0f * pi * fc_sat / fs;
float sin_w0 = f32_sin(w0);
float alpha = sin_w0 * sinhf(log_2 / 2.0f * bandwidth * w0 / sin_w0);
// Compute coeffs
float b0 = alpha;
float b1 = 0.0f;
float b2 = -alpha;
float a0 = 1.0f + alpha;
float a1 = -2.0f * f32_cos(w0);
float a2 = 1.0f - alpha;
float inv_a0 = 1.0f/a0;
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0 * inv_a0, Q_factor );
coeffs[1] = _float2fixed_assert( b1 * inv_a0, Q_factor );
coeffs[2] = _float2fixed_assert( b2 * inv_a0, Q_factor );
coeffs[3] = _float2fixed_assert( -a1 * inv_a0, Q_factor );
coeffs[4] = _float2fixed_assert( -a2 * inv_a0, Q_factor );
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_bandstop
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float bandwidth
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float w0 = 2.0f * pi * fc_sat / fs;
float sin_w0 = f32_sin(w0);
float alpha = sin_w0 * sinhf(log_2 / 2.0f * bandwidth * w0 / sin_w0);
// Compute coeffs
float b0 = 1.0f;
float b1 = -2.0f * f32_cos(w0);
float b2 = 1.0f;
float a0 = 1.0f + alpha;
float a1 = b1;
float a2 = 1.0f - alpha;
float inv_a0 = 1.0f/a0;
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0 * inv_a0, Q_factor );
coeffs[1] = _float2fixed_assert( b1 * inv_a0, Q_factor );
coeffs[2] = _float2fixed_assert( b2 * inv_a0, Q_factor );
coeffs[3] = _float2fixed_assert( -a1 * inv_a0, Q_factor );
coeffs[4] = _float2fixed_assert( -a2 * inv_a0, Q_factor );
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_notch
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float K = tanf(pi * fc_sat/fs);
float KK = K * K;
float KQ = K / filter_Q;
float norm = 1.0f / (1.0f + KQ + KK);
// Compute coeffs
float b0 = (1.0f + KK) * norm;
float b1 = 2.0f * (KK - 1.0f) * norm;
float b2 = b0;
float a1 = b1;
float a2 = (1.0f - KQ + KK) * norm;
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor );
coeffs[1] = _float2fixed_assert( b1, Q_factor );
coeffs[2] = _float2fixed_assert( b2, Q_factor );
coeffs[3] = _float2fixed_assert( -a1, Q_factor );
coeffs[4] = _float2fixed_assert( -a2, Q_factor );
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_allpass
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float K = tanf(pi * fc_sat/fs);
float KK = K * K;
float KQ = K / filter_Q;
float norm = 1.0f / (1.0f + KQ + KK);
// Compute coeffs
float b0 = (1.0f - KQ + KK) * norm;
float b1 = 2.0f * (KK - 1.0f) * norm;
float b2 = 1.0f;
float a1 = b1;
float a2 = b0;
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor );
coeffs[1] = _float2fixed_assert( b1, Q_factor );
coeffs[2] = _float2fixed_assert( b2, Q_factor );
coeffs[3] = _float2fixed_assert( -a1, Q_factor );
coeffs[4] = _float2fixed_assert( -a2, Q_factor );
// b_shift is always zero for this type of filter
return 0;
}
left_shift_t adsp_design_biquad_peaking
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float A = powf(10.0f, (gain_db * (1.0f / 40.0f)));
float w0 = 2.0f * pi * (fc_sat / fs);
// intentional double precision, gets extra precision
float alpha = f32_sin(w0) / (2.0 * filter_Q);
// Compute coeffs
float norm = 1.0f /(1.0f + alpha / A);
float b0 = (1.0f + alpha * A)*norm;
float b1 = (-2.0f * f32_cos(w0))*norm;
float b2 = (1.0f - alpha * A)*norm;
float a1 = b1;
float a2 = (1.0f - alpha / A)*norm;
left_shift_t b_sh = _get_b_shift(b0, b1, b2);
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor - b_sh);
coeffs[1] = _float2fixed_assert( b1, Q_factor - b_sh);
coeffs[2] = _float2fixed_assert( b2, Q_factor - b_sh);
coeffs[3] = _float2fixed_assert( -a1, Q_factor );
coeffs[4] = _float2fixed_assert( -a2, Q_factor );
return b_sh;
}
left_shift_t adsp_design_biquad_const_q
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float V = powf(10.0f, (gain_db * (1.0f/ 20.0f)));
// w0 is only needed for calculating K
float K = tanf(pi * fc_sat / fs);
float factor_a = K / filter_Q;
float factor_b = 0;
float K_pow2 = K * K;
if(gain_db > 0) {
factor_b = V * factor_a;
}
else
{
factor_b = factor_a;
factor_a = factor_b / V;
}
// Compute coeffs
float b0 = 1.0f + factor_b + K_pow2;
float b1 = 2.0f * (K_pow2 - 1.0f);
float b2 = 1.0f - factor_b + K_pow2;
float a0 = 1.0f + factor_a + K_pow2;
float a1 = b1;
float a2 = 1.0f - factor_a + K_pow2;
float inv_a0 = 1.0f/a0;
b0 *= inv_a0;
b1 *= inv_a0;
b2 *= inv_a0;
left_shift_t b_sh = _get_b_shift(b0, b1, b2);
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor - b_sh);
coeffs[1] = _float2fixed_assert( b1, Q_factor - b_sh);
coeffs[2] = _float2fixed_assert( b2, Q_factor - b_sh);
coeffs[3] = _float2fixed_assert( -a1 * inv_a0, Q_factor );
coeffs[4] = _float2fixed_assert( -a2 * inv_a0, Q_factor );
return b_sh;
}
left_shift_t adsp_design_biquad_lowshelf
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float A = powf(10.0f, (gain_db * (1.0f / 40.0f)));
float w0 = 2.0f * pi * fc_sat / fs;
float alpha = sinf(w0) / (2.0f * filter_Q);
float cosw0 = cosf(w0);
float alpha_factor = 2.0f * sqrtf(A) * alpha;
float Am1_cosw0 = (A - 1.0f) * cosw0;
float Ap1_cosw0 = (A + 1.0f) * cosw0;
// Compute coeffs
float b0 = A * ((A + 1.0f) - Am1_cosw0 + alpha_factor);
float b1 = 2.0f * A * ((A - 1.0f) - Ap1_cosw0);
float b2 = A * ((A + 1.0f) - Am1_cosw0 - alpha_factor);
float a0 = (A + 1.0f) + Am1_cosw0 + alpha_factor;
float a1 = -2.0f * ((A - 1.0f) + Ap1_cosw0);
float a2 = (A + 1.0f) + Am1_cosw0 - alpha_factor;
float inv_a0 = 1.0f / a0;
b0 *= inv_a0;
b1 *= inv_a0;
b2 *= inv_a0;
left_shift_t b_sh = _get_b_shift(b0, b1, b2);
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor - b_sh);
coeffs[1] = _float2fixed_assert( b1, Q_factor - b_sh);
coeffs[2] = _float2fixed_assert( b2, Q_factor - b_sh);
coeffs[3] = _float2fixed_assert( -a1 * inv_a0, Q_factor );
coeffs[4] = _float2fixed_assert( -a2 * inv_a0, Q_factor );
return b_sh;
}
left_shift_t adsp_design_biquad_highshelf
(
q2_30 coeffs[5],
const float fc,
const float fs,
const float filter_Q,
const float gain_db
) {
float fc_sat = _check_fc(fc, fs);
// Compute common factors
float A = powf(10.0f, (gain_db * (1.0f / 40.0f)));
float w0 = 2.0f * pi * fc_sat / fs;
float alpha = sinf(w0) / (2.0f * filter_Q);
float alpha_factor = 2.0f * sqrtf(A) * alpha;
float cosw0 = cosf(w0);
float Am1_cosw0 = (A - 1.0f) * cosw0;
float Ap1_cosw0 = (A + 1.0f) * cosw0;
// Compute coeffs
float b0 = A * ((A + 1.0f) + Am1_cosw0 + alpha_factor);
float b1 = -2.0f * A * ((A - 1.0f) + Ap1_cosw0);
float b2 = A * ((A + 1.0f) + Am1_cosw0 - alpha_factor);
float a0 = (A + 1.0f) - Am1_cosw0 + alpha_factor;
float a1 = 2.0f * ((A - 1.0f) - Ap1_cosw0);
float a2 = (A + 1.0f) - Am1_cosw0 - alpha_factor;
float inv_a0 = 1.0f/a0;
b0 *= inv_a0;
b1 *= inv_a0;
b2 *= inv_a0;
left_shift_t b_sh = _get_b_shift(b0, b1, b2);
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor - b_sh);
coeffs[1] = _float2fixed_assert( b1, Q_factor - b_sh);
coeffs[2] = _float2fixed_assert( b2, Q_factor - b_sh);
coeffs[3] = _float2fixed_assert( -a1 * inv_a0, Q_factor );
coeffs[4] = _float2fixed_assert( -a2 * inv_a0, Q_factor );
return b_sh;
}
left_shift_t adsp_design_biquad_linkwitz(
q2_30 coeffs[5],
const float f0,
const float fs,
const float q0,
const float fp,
const float qp
) {
float f0_sat = _check_fc(f0, fs);
float fp_sat = _check_fc(fp, fs);
// Compute common factors
float fc = (f0_sat + fp_sat) / 2.0f;
float w_f0 = 2.0f * pi * f0_sat;
float half_w_fc = pi * fc;
float w_fp = 2.0f * pi * fp_sat;
float w_f0_pow2 = w_f0 * w_f0;
float d1i = w_f0 / q0;
float w_fp_pow2 = w_fp * w_fp;
float c1i = w_fp / qp;
float gn = 2.0f * half_w_fc * (1.0f / (tanf(half_w_fc / fs)));
float gn_pow2 = gn * gn;
float factor_b = gn * d1i;
float factor_a = gn * c1i;
// Compute coeffs
float a0 = w_fp_pow2 + factor_a + gn_pow2;
float a1 = 2.0f * (w_fp_pow2 - gn_pow2);
float a2 = w_fp_pow2 - factor_a + gn_pow2;
float b0 = w_f0_pow2 + factor_b + gn_pow2;
float b1 = 2.0f * (w_f0_pow2 - gn_pow2);
float b2 = w_f0_pow2 - factor_b + gn_pow2;
float inv_a0 = 1.0f / a0;
b0 *= inv_a0;
b1 *= inv_a0;
b2 *= inv_a0;
left_shift_t b_sh = _get_b_shift(b0, b1, b2);
// Store as fixed-point values
coeffs[0] = _float2fixed_assert( b0, Q_factor - b_sh);
coeffs[1] = _float2fixed_assert( b1, Q_factor - b_sh);
coeffs[2] = _float2fixed_assert( b2, Q_factor - b_sh);
coeffs[3] = _float2fixed_assert( -a1 * inv_a0, Q_factor );
coeffs[4] = _float2fixed_assert( -a2 * inv_a0, Q_factor );
return b_sh;
}
left_shift_t adsp_apply_biquad_gain(q2_30 coeffs[5], left_shift_t b_sh, float gain_db)
{
if (gain_db == 0){
return b_sh;
}
float A = powf(10.0f, (gain_db * (1.0f / 20.0f)));
int Q = 31 - MAX(0, (int)ceilf(log2f(A)));
int32_t A_int = _float2fixed(A, Q);
for(int i = 0; i < 3; i++){
int32_t ah = 0, al = 1 << (Q - 1);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (coeffs[i]), "r" (A_int), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (Q), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (coeffs[i]): "r" (ah), "r" (al), "r" (Q));
}
return b_sh + (31 - Q);
}

View File

@@ -0,0 +1,122 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "control/adsp_control.h"
#include <xcore/assert.h>
#include <math.h>
#include <control/helpers.h>
#define Q_factor 30
static const float pi = (float)M_PI;
static inline void _get_pa(complex_float_t * pa, unsigned N) {
unsigned n_filts = N / 2;
for (unsigned k = 0; k < n_filts; k++) {
float theta = (2 * (k + 1) - 1) * pi / (2 * N);
pa[n_filts - 1 - k].re = -f32_sin(theta);
pa[n_filts - 1 - k].im = f32_cos(theta);
}
}
// returns (1 + val) / (1 - val)
static inline complex_float_t _get_p(complex_float_t val) {
complex_float_t one_plus = val;
complex_float_t one_minus = val;
one_plus.re = 1 + one_plus.re;
one_minus.re = 1 - one_minus.re;
one_minus.im = - one_minus.im;
float denom = one_minus.re * one_minus.re + one_minus.im * one_minus.im;
float num = one_plus.re * one_minus.re + one_plus.im * one_minus.im;
val.re = num / denom;
num = one_plus.im * one_minus.re - one_plus.re * one_minus.im;
val.im = num / denom;
return val;
}
void adsp_design_butterworth_lowpass_8b(
q2_30 coeffs[40],
const unsigned N,
const float fc,
const float fs
) {
xassert(N % 2 == 0 && "N must be even");
xassert(fc <= fs / 2 && "fc must be less than fs/2");
complex_float_t pa[8] = {{0}};
float factor = pi / fs;
float Fc = fs / pi * tanf(factor * fc);
factor *= Fc;
_get_pa(pa, N);
for (unsigned i = 0; i < N / 2; i ++) {
complex_float_t val = pa[i];
val.re *= factor;
val.im *= factor;
val = _get_p(val);
float a1 = -2 * val.re;
float a2 = val.re * val.re + val.im * val.im;
float b0 = (1 + a1 + a2) / 4;
float b1 = 2 * b0;
float b2 = b0;
coeffs[5 * i] = _float2fixed( b0 , Q_factor );
coeffs[5 * i + 1] = _float2fixed( b1 , Q_factor );
coeffs[5 * i + 2] = _float2fixed( b2 , Q_factor );
coeffs[5 * i + 3] = _float2fixed( -a1 , Q_factor );
coeffs[5 * i + 4] = _float2fixed( -a2 , Q_factor );
}
for (unsigned i = N / 2; i < 8; i ++) {
adsp_design_biquad_bypass(&coeffs[5 * i]);
}
}
void adsp_design_butterworth_highpass_8b(
q2_30 coeffs[40],
const unsigned N,
const float fc,
const float fs
) {
xassert(N % 2 == 0 && "N must be even");
xassert(fc <= fs / 2 && "fc must be less than fs/2");
complex_float_t pa[8] = {{0}};
float factor = pi / fs;
float Fc = fs / pi * tanf(factor * fc);
factor *= Fc;
_get_pa(pa, N);
for (unsigned i = 0; i < N / 2; i ++) {
float den = pa[i].re * pa[i].re + pa[i].im * pa[i].im;
float num = factor * pa[i].re;
pa[i].re = num / den;
num = - factor * pa[i].im;
pa[i].im = num / den;
pa[i] = _get_p(pa[i]);
float a1 = -2 * pa[i].re;
float a2 = pa[i].re * pa[i].re + pa[i].im * pa[i].im;
float b0 = (1 - a1 + a2) / 4;
float b1 = - 2 * b0;
float b2 = b0;
coeffs[5 * i] = _float2fixed( b0 , Q_factor );
coeffs[5 * i + 1] = _float2fixed( b1 , Q_factor );
coeffs[5 * i + 2] = _float2fixed( b2 , Q_factor );
coeffs[5 * i + 3] = _float2fixed( -a1 , Q_factor );
coeffs[5 * i + 4] = _float2fixed( -a2 , Q_factor );
}
for (unsigned i = N / 2; i < 8; i ++) {
adsp_design_biquad_bypass(&coeffs[5 * i]);
}
}

View File

@@ -0,0 +1,111 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "control/adsp_control.h"
env_detector_t adsp_env_detector_init(
float fs,
float attack_t,
float release_t
) {
env_detector_t env_det;
env_det.attack_alpha = calc_alpha(fs, attack_t);
env_det.release_alpha = calc_alpha(fs, release_t);
env_det.envelope = 0;
return env_det;
}
limiter_t adsp_limiter_peak_init(
float fs,
float threshold_db,
float attack_t,
float release_t
) {
limiter_t lim;
lim.env_det = adsp_env_detector_init(fs, attack_t, release_t);
lim.threshold = db_to_q_sig(threshold_db);
lim.gain = INT32_MAX;
return lim;
}
limiter_t adsp_limiter_rms_init(
float fs,
float threshold_db,
float attack_t,
float release_t
) {
limiter_t lim;
lim.env_det = adsp_env_detector_init(fs, attack_t, release_t);
lim.threshold = db_pow_to_q_sig(threshold_db);
lim.gain = INT32_MAX;
return lim;
}
compressor_t adsp_compressor_rms_init(
float fs,
float threshold_db,
float attack_t,
float release_t,
float ratio
) {
compressor_t comp;
comp.env_det = adsp_env_detector_init(fs, attack_t, release_t);
comp.threshold = db_pow_to_q_sig(threshold_db);
comp.gain = INT32_MAX;
comp.slope = (1.0f - 1.0f / ratio) / 2.0f;
return comp;
}
compressor_stereo_t adsp_compressor_rms_stereo_init(
float fs,
float threshold_db,
float attack_t,
float release_t,
float ratio
) {
compressor_stereo_t comp;
comp.env_det_l = adsp_env_detector_init(fs, attack_t, release_t);
comp.env_det_r = adsp_env_detector_init(fs, attack_t, release_t);
comp.threshold = db_pow_to_q_sig(threshold_db);
comp.gain = INT32_MAX;
comp.slope = (1.0f - 1.0f / ratio) / 2.0f;
return comp;
}
noise_gate_t adsp_noise_gate_init(
float fs,
float threshold_db,
float attack_t,
float release_t
) {
noise_gate_t ng = (noise_gate_t)adsp_limiter_peak_init(fs, threshold_db, attack_t, release_t);
ng.env_det.envelope = (1 << (-SIG_EXP)) - 1;
return ng;
}
void adsp_noise_suppressor_expander_set_th(
noise_suppressor_expander_t * nse,
int32_t new_th
) {
// Avoid division by zero
nse->threshold = (!new_th) ? 1 : new_th;
// x * 2 ^ -63 / y * 2 ^ -27 = xy * 2 ^ -36
nse->inv_threshold = INT64_MAX / nse->threshold;
}
noise_suppressor_expander_t adsp_noise_suppressor_expander_init(
float fs,
float threshold_db,
float attack_t,
float release_t,
float ratio
) {
noise_suppressor_expander_t nse;
nse.env_det = adsp_env_detector_init(fs, attack_t, release_t);
int32_t th = db_to_q_sig(threshold_db);
adsp_noise_suppressor_expander_set_th(&nse, th);
nse.gain = INT32_MAX;
nse.slope = 1 - ratio;
nse.env_det.envelope = (1 << (-SIG_EXP)) - 1;
return nse;
}

View File

@@ -0,0 +1,88 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include <xcore/assert.h>
#include "control/biquad.h"
//hand tuned values 16k (8 band)
static const float cfs_16[8] = {31.125, 64, 125, 250, 500, 1000, 2000, 4200};
static const float bw_16[8] = {1.5175, 1.6175, 1.5175, 1.5175, 1.5175, 1.5175, 1.6175, 1.1};
static const float gains_16[8] = {-0.3, -0.225, 0.175, 0, 0.05, 0.15, -0.4, -0.175};
//hand tuned values 32k (9 band)
static const float cfs_32[9] = {31.125, 64, 125, 250, 500, 1000, 2000, 4000, 8500};
static const float bw_32[9] = {1.5175, 1.6175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.1};
static const float gains_32[9] = {-0.3, -0.225, 0.175, 0, 0.01, 0, 0.075, -0.35, -0.1};
// hand tuned values for 44.1k and 48k
static const float cfs_46[10] = {31.125, 64, 125, 250, 500, 1000, 2000, 4000, 8150, 15000};
static const float bw_46[10] = {1.5175, 1.6175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 0.75875};
static const float gains_46[10] = {-0.3, -0.225, 0.1750, 0, 0.01, 0, 0.05, 0.025, -0.41, -0.25};
// hand tuned values for 88.2k and 96k
static const float cfs_92[10] = {31.125, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000};
static const float bw_92[10] = {1.5175, 1.6175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.1};
static const float gains_92[10] = {-0.3, -0.225, 0.1750, 0, 0.01, 0, 0.0, 0.0, -0.35, -0.2};
// hand tuned values for 192k
static const float cfs_192[10] = {31.125, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000};
static const float bw_192[10] = {1.5175, 1.6175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.5175, 1.1};
static const float gains_192[10] = {-0.3, -0.225, 0.1750, 0, 0.01, 0, 0.0, -0.05, -0.3, -0.2};
q2_30* adsp_graphic_eq_10b_init(float fs)
{
static q2_30 coeffs[50] = {0};
float shift = -0.37;
xassert(fs > 12000.0f && "10 band Graphic EQ only supports fs > 12k");
if ( fs <= 16000) {
int j = 0;
for(int i = 0; i < 8; i++)
{
adsp_design_biquad_bandpass(&coeffs[j], cfs_16[i], fs, bw_16[i]);
adsp_apply_biquad_gain(&coeffs[j], 0, gains_16[i] + shift);
j += 5;
}
}
else if (fs <= 32000){
int j = 0;
for(int i = 0; i < 9; i++)
{
adsp_design_biquad_bandpass(&coeffs[j], cfs_32[i], fs, bw_32[i]);
adsp_apply_biquad_gain(&coeffs[j], 0, gains_32[i] + shift);
j += 5;
}
}
else if ( fs < ((48000.0f + 88200.0f) / 2.0f)) {
int j = 0;
for(int i = 0; i < 10; i++)
{
adsp_design_biquad_bandpass(&coeffs[j], cfs_46[i], fs, bw_46[i]);
adsp_apply_biquad_gain(&coeffs[j], 0, gains_46[i] + shift);
j += 5;
}
}
else if (fs < (96000 + 176400) / 2){
int j = 0;
for(int i = 0; i < 10; i++)
{
adsp_design_biquad_bandpass(&coeffs[j], cfs_92[i], fs, bw_92[i]);
adsp_apply_biquad_gain(&coeffs[j], 0, gains_92[i] + shift);
j += 5;
}
}
else {
int j = 0;
for(int i = 0; i < 10; i++)
{
adsp_design_biquad_bandpass(&coeffs[j], cfs_192[i], fs, bw_192[i]);
adsp_apply_biquad_gain(&coeffs[j], 0, gains_192[i] + shift);
j += 5;
}
}
return coeffs;
}

View File

@@ -0,0 +1,115 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xcore/assert.h>
#include <math.h>
#include "control/adsp_control.h"
reverb_room_t adsp_reverb_room_init(
float fs,
float max_room_size,
float room_size,
float decay,
float damping,
float wet_gain,
float dry_gain,
float pregain,
float max_predelay,
float predelay,
void *reverb_heap)
{
// For larger rooms, increase max_room_size. Don't forget to also increase
// the size of reverb_heap
xassert(room_size >= 0 && room_size <= 1);
xassert(max_predelay > 0);
xassert(predelay <= max_predelay);
xassert(reverb_heap != NULL);
reverb_room_t rv;
// Avoids too much or too little feedback
const int32_t feedback_int = adsp_reverb_calculate_feedback(decay);
const int32_t damping_int = adsp_reverb_calculate_damping(damping);
int32_t predelay_samps = predelay * fs / 1000;
int32_t max_predelay_samps = max_predelay * fs / 1000;
adsp_reverb_room_init_filters(&rv, fs, max_room_size, max_predelay_samps, predelay_samps, feedback_int, damping_int, reverb_heap);
adsp_reverb_room_set_room_size(&rv, room_size);
rv.pre_gain = adsp_reverb_float2int(pregain);
rv.dry_gain = adsp_reverb_room_calc_gain(dry_gain);
rv.wet_gain = adsp_reverb_room_calc_gain(wet_gain);
return rv;
}
static inline void _get_wet_gains(int32_t wet_gains[2], float wet_gain, float width){
width = width > 1.0f ? 1.0f : width;
width = width < 0.0f ? 0.0f : width;
float wet1, wet2;
wet1 = wet_gain * (width / 2.0f + 0.5f);
wet2 = wet_gain * ((1.0f - width) / 2.0f);
wet_gains[0] = adsp_reverb_float2int(wet1);
wet_gains[1] = adsp_reverb_float2int(wet2);
}
void adsp_reverb_room_st_calc_wet_gains(int32_t wet_gains[2], float wet_gain, float width) {
wet_gain = powf(10.0f, wet_gain / 20.0f);
_get_wet_gains(wet_gains, wet_gain, width);
}
void adsp_reverb_st_wet_dry_mix(int32_t gains[3], float mix, float width) {
mix = mix > 1.0f ? 1.0f : mix;
mix = mix < 0.0f ? 0.0f : mix;
const float pi_by_2 = 1.5707963f;
// get an angle [0, pi / 2]
float omega = mix * pi_by_2;
// -4.5 dB panning
float dry = sqrtf((1.0f - mix) * cosf(omega));
float wet = sqrtf(mix * sinf(omega));
gains[0] = adsp_reverb_float2int(dry);
_get_wet_gains(&gains[1], wet, width);
}
reverb_room_st_t adsp_reverb_room_st_init(
float fs,
float max_room_size,
float room_size,
float decay,
float damping,
float width,
float wet_gain,
float dry_gain,
float pregain,
float max_predelay,
float predelay,
void *reverb_heap)
{
// For larger rooms, increase max_room_size. Don't forget to also increase
// the size of reverb_heap
xassert(room_size >= 0 && room_size <= 1);
xassert(max_predelay > 0);
xassert(predelay <= max_predelay);
xassert(reverb_heap != NULL);
reverb_room_st_t rv;
// Avoids too much or too little feedback
const int32_t feedback_int = adsp_reverb_calculate_feedback(decay);
const int32_t damping_int = adsp_reverb_calculate_damping(damping);
int32_t predelay_samps = predelay * fs / 1000;
int32_t max_predelay_samps = max_predelay * fs / 1000;
adsp_reverb_room_st_init_filters(&rv, fs, max_room_size, max_predelay_samps, predelay_samps, feedback_int, damping_int, reverb_heap);
adsp_reverb_room_st_set_room_size(&rv, room_size);
rv.pre_gain = adsp_reverb_float2int(pregain);
rv.dry_gain = adsp_reverb_room_calc_gain(dry_gain);
int32_t wet_gains[2];
adsp_reverb_room_st_calc_wet_gains(wet_gains, wet_gain, width);
rv.wet_gain1 = wet_gains[0];
rv.wet_gain2 = wet_gains[1];
return rv;
}

View File

@@ -0,0 +1,52 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xcore/assert.h>
#include "control/adsp_control.h"
#include <math.h>
reverb_plate_t adsp_reverb_plate_init(
float fs,
float decay,
float damping,
float bandwidth,
float early_diffusion,
float late_diffusion,
float width,
float wet_gain,
float dry_gain,
float pregain,
float max_predelay,
float predelay,
void * reverb_heap)
{
xassert(reverb_heap != NULL);
reverb_plate_t rv;
// lowpasses
int32_t bandwidth_int = adsp_reverb_plate_calc_bandwidth(bandwidth, fs);
rv.lowpasses[0] = lowpass_1ord_init(bandwidth_int);
int32_t damping_int = adsp_reverb_plate_calc_damping(damping);
rv.lowpasses[1] = lowpass_1ord_init(damping_int);
rv.lowpasses[2] = lowpass_1ord_init(damping_int);
rv.pre_gain = adsp_reverb_float2int(pregain);
rv.dry_gain = adsp_reverb_room_calc_gain(dry_gain);
int32_t wet_gains[2];
adsp_reverb_room_st_calc_wet_gains(wet_gains, wet_gain, width);
rv.wet_gain1 = wet_gains[0];
rv.wet_gain2 = wet_gains[1];
rv.decay = adsp_reverb_float2int(decay);
decay += 0.15;
decay = (decay < 0.25) ? 0.25 : decay;
decay = (decay > 0.5) ? 0.5 : decay;
int32_t decay_diff_2 = adsp_reverb_float2int(decay);
int32_t decay_diff_1 = adsp_reverb_plate_calc_late_diffusion(late_diffusion);
int32_t in_dif1 = adsp_reverb_float2int(early_diffusion);
int32_t in_dif2 = adsp_reverb_float2int(early_diffusion * 5 / 6);
int32_t predelay_samps = predelay * fs / 1000;
int32_t max_predelay_samps = max_predelay * fs / 1000;
adsp_reverb_plate_init_filters(&rv, fs, decay_diff_1, decay_diff_2, in_dif1, in_dif2, max_predelay_samps, predelay_samps, reverb_heap);
return rv;
}

View File

@@ -0,0 +1,114 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "control/adsp_control.h"
#include <xcore/assert.h>
#include <math.h>
#if Q_GAIN != 27
#error "Need to change the cap value in adsp_dB_to_gain"
#endif
int32_t adsp_dB_to_gain(float dB_gain) {
dB_gain = MIN(dB_gain, 24.0f);
return db_to_qxx(dB_gain, Q_GAIN);
}
gain_slew_t adsp_slew_gain_init(int32_t init_gain, int32_t slew_shift){
gain_slew_t gain;
gain.target_gain = init_gain;
gain.gain = init_gain;
gain.slew_shift = slew_shift;
return gain;
}
volume_control_t adsp_volume_control_init(
float gain_dB,
int32_t slew_shift,
uint8_t mute_state
) {
volume_control_t vol_ctl;
vol_ctl.mute_state = mute_state;
adsp_volume_control_set_gain(&vol_ctl, adsp_dB_to_gain(gain_dB));
vol_ctl.slew_shift = slew_shift;
vol_ctl.saved_gain = 0;
return vol_ctl;
}
uint32_t time_to_samples(float fs, float time, time_units_t units) {
time = MAX(time, 0); // Time has to be positive
switch (units) {
case MILLISECONDS:
return (uint32_t)(time * fs / 1000);
case SECONDS:
return (uint32_t)(time * fs);
case SAMPLES:
return (uint32_t)time;
default:
xassert(0); // Invalid time units
}
}
delay_t adsp_delay_init(
float fs,
float max_delay,
float starting_delay,
time_units_t units,
void * delay_heap
) {
delay_t delay;
delay.fs = fs;
xassert(delay.max_delay > 0 && "Max delay must be greater than 0");
delay.max_delay = time_to_samples(fs, max_delay, units);
delay.delay = time_to_samples(fs, starting_delay, units);
xassert(delay.delay <= delay.max_delay && "Starting delay must be less or equal to the max delay");
xassert(delay_heap != NULL && "Delay heap must be allocated");
delay.buffer_idx = 0;
delay.buffer = (int32_t *)delay_heap;
return delay;
}
void adsp_set_delay(
delay_t * delay,
float delay_time,
time_units_t units
) {
uint32_t new_delay = time_to_samples(delay->fs, delay_time, units);
delay->delay = (new_delay <= delay->max_delay) ? new_delay : delay->max_delay;
}
switch_slew_t adsp_switch_slew_init(float fs, int32_t init_position){
switch_slew_t out = {.switching = false,
.position = init_position,
.last_position=init_position,
.counter = -(1<<30),
.step = INT32_MAX / (int32_t)(fs * 0.03f)};
return out;
}
void adsp_switch_slew_move(switch_slew_t* switch_slew, int32_t new_position){
if (new_position != switch_slew->position){
switch_slew->last_position = switch_slew->position;
switch_slew->position = new_position;
switch_slew->switching = true;
switch_slew->counter = -(1 << 30);
}
}
void adsp_crossfader_mix(int32_t gains[2], float mix) {
mix = mix > 1.0f ? 1.0f : mix;
mix = mix < 0.0f ? 0.0f : mix;
const float pi_by_2 = 1.5707963f;
// get an angle [0, pi / 2]
float omega = mix * pi_by_2;
// -4.5 dB panning
float dry = sqrtf((1.0f - mix) * cosf(omega));
float wet = sqrtf(mix * sinf(omega));
gains[0] = adsp_reverb_float2int(dry);
gains[1] = adsp_reverb_float2int(wet);
}

View File

@@ -0,0 +1,9 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
.weak __xs3a_sqrtf.maxchanends
.set __xs3a_sqrtf.maxchanends, 0
.weak __xs3a_sqrtf.maxtimers
.set __xs3a_sqrtf.maxtimers, 0
.weak __xs3a_sqrtf.maxcores
.set __xs3a_sqrtf.maxcores, 0

View File

@@ -0,0 +1,64 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/*
int32_t adsp_biquad(int32_t new_sample,
int32_t * coeffs,
int32_t * state,
const int32_t lshift)
*/
#define FUNCTION_NAME adsp_biquad
#define NSTACKWORDS (2)
#define sample r0
#define coeffs r1
#define state r2
#define lsh r3
.text
.issue_mode dual
.globl FUNCTION_NAME;
.type FUNCTION_NAME,@function
.align 8
.cc_top FUNCTION_NAME.function,FUNCTION_NAME
FUNCTION_NAME:
dualentsp NSTACKWORDS
std r4, r5, sp[0]
{ vclrdr ; ldc r11, 0 }
{ vsetc r11 ; }
// state has x[n - 1] x[n - 2] x[n - 3] y[n - 1] y[n - 2]
ldd r5, r4, state[0]
std r4, sample, state[0]
{ stw r5, state[2] ; }
// state has x[n] x[n - 1] x[n - 2] y[n - 1] y[n - 2]
{ vldc coeffs[0] ; ldc r11, 12 }
{ vlmaccr state[0] ; mkmsk r1, 4 }
{ ldw r4, state[3] ; }
{ stw r4, state[4] ; add state, state, r11 }
// vR[0] has y[n]
vstrpv state[0], r1
{ ldw sample, state[0] ; }
// state has x[n] x[n - 1] x[n - 2] y[n] y[n - 1]
ldd r4, r5, sp[0]
{ retsp NSTACKWORDS ; shl sample, sample, lsh }
.cc_bottom FUNCTION_NAME.function;
.set FUNCTION_NAME.nstackwords,NSTACKWORDS; .global FUNCTION_NAME.nstackwords;
.set FUNCTION_NAME.maxcores,1; .global FUNCTION_NAME.maxcores;
.set FUNCTION_NAME.maxtimers,0; .global FUNCTION_NAME.maxtimers;
.set FUNCTION_NAME.maxchanends,0; .global FUNCTION_NAME.maxchanends;
.L_size_end:
.size FUNCTION_NAME, .L_size_end - FUNCTION_NAME

View File

@@ -0,0 +1,59 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include "stdio.h"
#include <stdbool.h>
#include <string.h>
void adsp_biquad_slew_coeffs(
biquad_slew_t* slew_state,
int32_t** states,
int32_t channels
){
if (slew_state->remaining_shifts > 0){
// change in b_shift to manage, target_coeffs have less headroom, so add the headroom back
for (int i=0; i < 3; i++){
int32_t tmp_target = slew_state->target_coeffs[i] >> slew_state->remaining_shifts;
slew_state->active_coeffs[i] += (tmp_target - slew_state->active_coeffs[i]) >> slew_state->slew_shift;
}
for (int i=3; i < 5; i++){
slew_state->active_coeffs[i] += (slew_state->target_coeffs[i] - slew_state->active_coeffs[i]) >> slew_state->slew_shift;
}
// see if we have headroom to do the shift
int32_t max_val = (1 << 30);
bool res = true;
for (int i=0; i < 3; i++){
res = res && (slew_state->active_coeffs[i] < max_val);
res = res && (slew_state->active_coeffs[i] > -max_val);
}
for (int i=0; i < channels; i++){
res = res && (states[i][3] < max_val);
res = res && (states[i][3] > -max_val);
res = res && (states[i][4] < max_val);
res = res && (states[i][4] > -max_val);
}
if (res){
// we now have the headroom to shift
for (int i=0; i < 3; i++){
slew_state->active_coeffs[i] <<= 1;
}
for (int i=0; i < channels; i++){
states[i][3] <<= 1;
states[i][4] <<= 1;
}
slew_state->remaining_shifts -= 1;
slew_state->lsh -= 1;
}
}
else{
for (int i=0; i < 5; i++){
slew_state->active_coeffs[i] += (slew_state->target_coeffs[i] - slew_state->active_coeffs[i]) >> slew_state->slew_shift;
}
}
}

View File

@@ -0,0 +1,160 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include "xmath/xmath.h"
#include "dsp/fd_block_fir.h"
/**
* @brief This does a bfp_complex_s32_macc but bin zero is treated as two real values.
*/
__attribute__((noinline)) // bug workaround
static void
bfp_complex_s32_macc2(
bfp_complex_s32_t *acc,
const bfp_complex_s32_t *b,
const bfp_complex_s32_t *c)
{
exponent_t a_exp;
right_shift_t acc_shr, b_shr, c_shr;
vect_complex_s32_macc_prepare(&a_exp, &acc_shr, &b_shr, &c_shr, acc->exp, b->exp, c->exp, acc->hr, b->hr, c->hr);
acc->exp = a_exp;
headroom_t hr = vect_s32_macc((int32_t *)acc->data, (int32_t *)b->data, (int32_t *)c->data, 2, acc_shr, b_shr, c_shr);
acc->hr = vect_complex_s32_macc(acc->data + 1, b->data + 1, c->data + 1, b->length - 1, acc_shr, b_shr, c_shr);
if (hr < acc->hr)
acc->hr = hr;
}
static unsigned get_tail(fd_fir_data_t *fir_data)
{
unsigned tail = fir_data->head_index + 1;
if (tail == fir_data->block_count)
tail = 0;
return tail;
}
static void advance_head(fd_fir_data_t *fir_data)
{
fir_data->head_index = get_tail(fir_data);
}
/*
This adds fir_data->frame_advance samples to a FIFO of blocks within fir_data.
Blocks wll have overlapping data within them. This also performs a mono FFT.
*/
static void add_data(fd_fir_data_t *fir_data, int32_t *samples_in, exponent_t exp)
{
// Calc the block to evict, i.e. the oldest one
unsigned head_tail_idx = get_tail(fir_data);
int32_t *d = (int32_t *)fir_data->data_blocks[head_tail_idx].data;
// Copy in the new samples plus the frame overlap
int32_t prev_len = (fir_data->td_block_length - fir_data->frame_advance);
memcpy(d, fir_data->prev_td_data, prev_len * sizeof(int32_t));
memcpy(d + prev_len, samples_in, fir_data->frame_advance * sizeof(int32_t));
// Update the prev_td_data
memcpy(fir_data->prev_td_data, d + fir_data->frame_advance,
prev_len * sizeof(int32_t));
//[asj] we could optimise the copying to only copy fir_data->frame_advance samples by keeping an index
// into overlapping frame_data and treating it as a circular buffer
bfp_s32_t *new_bfp_block = (bfp_s32_t *)&(fir_data->data_blocks[head_tail_idx]);
bfp_s32_init((bfp_s32_t *)(fir_data->data_blocks + head_tail_idx), d, exp, fir_data->td_block_length, 1);
bfp_fft_forward_mono(new_bfp_block);
}
#define INTERNAL_EXP (0) // this is arbitrary
#define ZERO_EXP (-99999) // this is fine
void fd_block_fir_add_data(
int32_t *samples_in,
fd_fir_data_t *fir_data)
{
exponent_t exp = INTERNAL_EXP;
add_data(fir_data, samples_in, exp);
advance_head(fir_data);
}
__attribute__((noinline)) // bug workaround
void
fd_block_fir_compute(
int32_t *samples_out, // must be int32_t samples_out[BLOCK_LENGTH];
fd_fir_data_t *fir_data,
fd_fir_filter_t *fir_filter)
{
assert(fir_data->td_block_length == fir_filter->td_block_length);
assert(fir_data->block_count >= fir_filter->block_count);
bfp_complex_s32_t result;
// data_in does not need clearing as a massively negative exponent takes care of it
// to make it represent a zero array.
bfp_complex_s32_init(&result, (complex_s32_t *)samples_out, ZERO_EXP, fir_data->td_block_length / 2, 0);
memset(samples_out, 0, sizeof(int32_t) * fir_data->td_block_length);
int idx = fir_data->head_index;
for (int i = 0; i < fir_filter->block_count; i++)
{
bfp_complex_s32_macc2(&result, &(fir_data->data_blocks[idx]), &(fir_filter->coef_blocks[i]));
if (idx == 0)
idx = fir_data->block_count - 1;
else
idx--;
}
bfp_s32_t *time_domain_result = bfp_fft_inverse_mono(&result);
// denormalise
exponent_t exp = INTERNAL_EXP;
bfp_s32_use_exponent(time_domain_result, exp);
// copy out the result
int output_samples = fir_filter->td_block_length + 1 - fir_filter->taps_per_block;
for (int i = 0; i < output_samples; i++)
{
samples_out[i] = time_domain_result->data[fir_filter->taps_per_block - 1 + i];
}
}
void fd_block_fir_data_init(fd_fir_data_t *d, int32_t *data_blob,
uint32_t frame_advance, uint32_t td_block_length, uint32_t block_count)
{
// These are the three properties
d->td_block_length = td_block_length;
d->block_count = block_count;
d->frame_advance = frame_advance;
uint32_t prev_data_length = d->td_block_length - d->frame_advance;
bfp_complex_s32_t *bfp_data_blocks = (bfp_complex_s32_t *)data_blob;
int32_t *data_buffer = (int32_t *)(bfp_data_blocks + d->block_count);
// if data_buffer isn't double word aligned then force it to be
if (((int)data_buffer) & 0x7)
data_buffer += 1;
for (int i = 0; i < d->block_count; i++)
{
bfp_data_blocks[i].data = (complex_s32_t *)data_buffer;
bfp_data_blocks[i].exp = ZERO_EXP;
bfp_data_blocks[i].length = d->td_block_length / 2;
bfp_data_blocks[i].hr = 32;
bfp_data_blocks[i].flags = 0;
data_buffer += d->td_block_length; // will always be 8 byte aligned
}
d->data_blocks = bfp_data_blocks;
d->prev_td_data = data_buffer; // will always be 8 byte aligned
d->overlapping_frame_data = d->prev_td_data + prev_data_length;
d->head_index = 0;
}

View File

@@ -0,0 +1,12 @@
// Copyright 2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/td_block_fir.h"
void td_block_fir_data_init(td_block_fir_data_t *d,
int32_t *data, uint32_t data_buffer_elements)
{
d->data = data;
d->index = 32;
d->data_stride = (data_buffer_elements*sizeof(int32_t)) - 32;
}

View File

@@ -0,0 +1,67 @@
// Copyright 2020-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#if defined(__XS3A__)
.text
.align 16;
.issue_mode dual
/*
void td_block_fir_add_data(
int32_t samples_in[TD_BLOCK_FIR_LENGTH],
td_block_fir_data_t * fir_data
);
typedef struct td_block_FIR_data_t {
int32_t * data; // 0
uint32_t index; // 1
uint32_t data_stride; // 2
} td_block_fir_data_t;
See td_block_fir_add_data_ref for reference code.
*/
#define FUNCTION_NAME td_block_fir_add_data
#define NSTACKWORDS 4
#define input_block_p r0
#define data_struct_p r1
.cc_top FUNCTION_NAME.function,FUNCTION_NAME
FUNCTION_NAME:
dualentsp NSTACKWORDS
std r4, r5, sp[0]
std r6, r7, sp[1]
{vldd input_block_p[0]; ldc r0, 32}
{ldw r3, data_struct_p[1]; nop} //load index
{ldw r7, data_struct_p[0]; add r6, r3, r0} //load data_pointer
{ldw r2, data_struct_p[2]; add r11, r7, r3} //load data_stride
{vstd r11[0]; sub r3, r2, r3}
// if this is the end of the buffer then paste it onto the front too
{bt r3, td_block_fir_add_data_skip; nop}
{vstd r7[0]; mov r6, r0}
td_block_fir_add_data_skip:
stw r6, data_struct_p[1]
ldd r6, r7, sp[1]
ldd r4, r5, sp[0]
{ nop ; retsp NSTACKWORDS }
.L_func_end_unpack:
.cc_bottom FUNCTION_NAME.function
.global FUNCTION_NAME
.type FUNCTION_NAME,@function
.set FUNCTION_NAME.nstackwords,NSTACKWORDS; .global FUNCTION_NAME.nstackwords
.set FUNCTION_NAME.maxcores,1; .global FUNCTION_NAME.maxcores
.set FUNCTION_NAME.maxtimers,0; .global FUNCTION_NAME.maxtimers
.set FUNCTION_NAME.maxchanends,0; .global FUNCTION_NAME.maxchanends
.size FUNCTION_NAME,.L_func_end_unpack - FUNCTION_NAME
#undef NSTACKWORDS
#undef FUNCTION_NAME
#endif //defined(__XS3A__)

View File

@@ -0,0 +1,132 @@
// Copyright 2020-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#if defined(__XS3A__)
.text
.align 16;
.issue_mode dual
/*
void td_block_fir_compute(
int32_t output_block[TD_BLOCK_FIR_LENGTH],
td_block_fir_data_t * fir_data,
td_block_fir_filter_t * fir_filter,
);
typedef struct td_block_fir_data_t {
int32_t * data; // 0
uint32_t index; // 1
uint32_t data_stride; // 2
} td_block_fir_data_t;
typedef struct td_block_fir_filter_t {
int32_t * coefs; // 0
uint32_t block_count; // 1
uint32_t accu_shr; // 2
} td_block_fir_filter_t;
See td_block_fir_compute_ref for reference code.
*/
#define FUNCTION_NAME td_block_fir_compute
#define NSTACKWORDS (6+8) + 32
#define output_block_p r0
#define data_struct_p r1
#define filter_struct_p r2
#define s r4
#define data_p r6
#define data_stride r7
#define thirtytwo r9
#define t r11
.cc_top FUNCTION_NAME.function,FUNCTION_NAME
FUNCTION_NAME:
dualentsp NSTACKWORDS
std r5, r4, sp[0]
std r7, r6, sp[1]
std r9, r8, sp[2]
{ldw data_p, data_struct_p[0] ; ldc thirtytwo, 32}
{ldw data_stride, data_struct_p[2]; add r3, thirtytwo, thirtytwo}
{ldw r8, data_struct_p[1] ; add data_p, data_p, data_stride}
{ldw r5, filter_struct_p[1] ; add data_p, data_p, r8}
{shl s, r5, 5; sub r8, r8, thirtytwo}
{vclrdr; sub data_p, data_p, s}
ashr t, r8, 5
{sub s, r5, t; lss r8, r5, t }
{ldw r5, filter_struct_p[0] ; nop}
{bf r8, prepare; sub r3, r3, 4}
{add t, t, s; ldc s, 0}
//At the point s and t are the first and second loop counters.
prepare:
{bf s, td_block_fir_compute_skip_first_half; nop} //if s is zero then just do t
td_block_fir_compute_first_half:
{vldc r5[0] ; add r5, r5, thirtytwo }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub s, s, 1 }
{bt s, td_block_fir_compute_first_half; add data_p, data_p, r3}
td_block_fir_compute_skip_first_half:
{bf t, td_block_fir_compute_done; sub data_p, data_p, data_stride} //if t is zero then just do s
td_block_fir_compute_second_half:
{vldc r5[0] ; add r5, r5, thirtytwo }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub data_p, data_p, 4 }
{vlmaccr data_p[0] ; sub t, t, 1 }
{bt t, td_block_fir_compute_second_half; add data_p, data_p, r3}
td_block_fir_compute_done:
{ldw s, filter_struct_p[2]; ldaw t, sp[6]}
//In order to use VLSAT we need 8 shift values from memory. This puts them into memory
//as fast as possible.
std s, s, sp[3]
std s, s, sp[4]
std s, s, sp[5]
std s, s, sp[6]
{vlsat t[0] ; nop}
{ldw s, filter_struct_p[3]; nop}
{vstr t[0] ; nop}
vlashr t[0], s
vstr output_block_p[0]
ldd r5, r4, sp[0]
ldd r7, r6, sp[1]
ldd r9, r8, sp[2]
{ nop ; retsp NSTACKWORDS }
.L_func_end_unpack:
.cc_bottom FUNCTION_NAME.function
.global FUNCTION_NAME
.type FUNCTION_NAME,@function
.set FUNCTION_NAME.nstackwords,NSTACKWORDS; .global FUNCTION_NAME.nstackwords
.set FUNCTION_NAME.maxcores,1; .global FUNCTION_NAME.maxcores
.set FUNCTION_NAME.maxtimers,0; .global FUNCTION_NAME.maxtimers
.set FUNCTION_NAME.maxchanends,0; .global FUNCTION_NAME.maxchanends
.size FUNCTION_NAME,.L_func_end_unpack - FUNCTION_NAME
#undef NSTACKWORDS
#undef FUNCTION_NAME
#endif //defined(__XS3A__)

View File

@@ -0,0 +1,17 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
int32_t adsp_cascaded_biquads_8b(int32_t new_sample,
q2_30 coeffs[40],
int32_t state[64],
left_shift_t lsh[8]
) {
int32_t out = new_sample;
for(unsigned n = 0; n < 8; n++)
{
out = adsp_biquad(out, &coeffs[5 * n], &state[8 * n], lsh[n]);
}
return out;
}

View File

@@ -0,0 +1,113 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include "dsp/_helpers/drc_utils.h"
static inline int32_t calc_rms_comp_gain(int32_t th, int32_t env, float slope) {
// will calculate (th/env)^slope
// assumes that th and env > 0 and slope [0, 1/2],
// so that output fits in q31
int32_t ah = 0, al = 0, r = 0, new_gain = 0;
float ng_fl = 0;
asm("linsert %0, %1, %2, %3, 32": "=r" (ah), "=r" (al): "r" (th), "r" (Q_alpha), "0" (ah), "1" (al));
asm("ldivu %0, %1, %2, %3, %4": "=r" (new_gain), "=r" (r): "r" (ah), "r" (al), "r" (env));
r = -Q_alpha + 23;
asm("fmake %0, %1, %2, %3, %4": "=r" (ng_fl): "r" (0), "r" (r), "r" (0), "r" (new_gain));
ng_fl = powf(ng_fl, slope);
// risk of ng_fl being 1 if thresh/envelope > (1 - 2^-24)
if(ng_fl >= 1.0f){
return INT32_MAX;
}
asm("fsexp %0, %1, %2": "=r" (al), "=r" (r): "r" (ng_fl));
asm("fmant %0, %1": "=r" (new_gain): "r" (ng_fl));
r = -Q_alpha - r + 23;
new_gain >>= r;
return new_gain;
}
int32_t adsp_compressor_rms(
compressor_t * comp,
int32_t new_samp
) {
adsp_env_detector_rms(&comp->env_det, new_samp);
int32_t env = (comp->env_det.envelope == 0) ? 1 : comp->env_det.envelope;
// this assumes that both th and env > 0 and that the slope is [0, 1/2]
// basically (th/env)^slope > 1 is th^slope > env^slope
// so if th and env both positive we can try to drop the slope
// if slope == 0 expression fails, if slope is positive and th > env - passes
int32_t new_gain = INT32_MAX;
if ((comp->slope > 0) && (comp->threshold < env)) {
new_gain = calc_rms_comp_gain(comp->threshold, env, comp->slope);
}
int32_t alpha = comp->env_det.release_alpha;
if( comp->gain > new_gain ) {
alpha = comp->env_det.attack_alpha;
}
comp->gain = q31_ema(comp->gain, new_gain, alpha);
return apply_gain_q31(new_samp, comp->gain);
}
int32_t adsp_compressor_rms_sidechain(
compressor_t * comp,
int32_t input_samp,
int32_t detect_samp
) {
adsp_env_detector_rms(&comp->env_det, detect_samp);
int32_t env = (comp->env_det.envelope == 0) ? 1 : comp->env_det.envelope;
// this assumes that both th and env > 0 and that the slope is [0, 1/2]
// basically (th/env)^slope > 1 is th^slope > env^slope
// so if th and env both positive we can try to drop the slope
// if slope == 0 expression fails, if slope is positive and th > env - passes
int32_t new_gain = INT32_MAX;
if ((comp->slope > 0) && (comp->threshold < env)) {
new_gain = calc_rms_comp_gain(comp->threshold, env, comp->slope);
}
int32_t alpha = comp->env_det.release_alpha;
if( comp->gain > new_gain ) {
alpha = comp->env_det.attack_alpha;
}
comp->gain = q31_ema(comp->gain, new_gain, alpha);
return apply_gain_q31(input_samp, comp->gain);
}
void adsp_compressor_rms_sidechain_stereo(
compressor_stereo_t * comp,
int32_t outputs_lr[2],
int32_t input_samp_l,
int32_t input_samp_r,
int32_t detect_samp_l,
int32_t detect_samp_r
) {
adsp_env_detector_rms(&comp->env_det_l, detect_samp_l);
adsp_env_detector_rms(&comp->env_det_r, detect_samp_r);
int32_t env = MAX(comp->env_det_l.envelope, comp->env_det_r.envelope);
env = MAX(env, 1);
// this assumes that both th and env > 0 and that the slope is [0, 1/2]
// basically (th/env)^slope > 1 is th^slope > env^slope
// so if th and env both positive we can try to drop the slope
// if slope == 0 expression fails, if slope is positive and th > env - passes
int32_t new_gain = INT32_MAX;
if ((comp->slope > 0) && (comp->threshold < env)) {
new_gain = calc_rms_comp_gain(comp->threshold, env, comp->slope);
}
int32_t alpha = comp->env_det_l.release_alpha;
if( comp->gain > new_gain ) {
alpha = comp->env_det_l.attack_alpha;
}
comp->gain = q31_ema(comp->gain, new_gain, alpha);
outputs_lr[0] = apply_gain_q31(input_samp_l, comp->gain);
outputs_lr[1] = apply_gain_q31(input_samp_r, comp->gain);
return;
}

View File

@@ -0,0 +1,116 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/defines.h"
.section .cp.rodata, "ac", @progbits
.align 4
q_sig_rnd_acc:
.word 1 << (Q_SIG - 1)
/*
void adsp_env_detector_peak(
env_detector_t * env_det,
int32_t new_sample
)*/
.text
.issue_mode dual
.align 8
#define ED_ATT_AL 0
#define ED_REL_AL 1
#define ED_ENV 2
#define NSTACKWORDS 2
#define state r0
#define sample r1
#define ah r2
#define al r3
#define alpha r4
#define env r5
.cc_top adsp_env_detector_peak.function,adsp_env_detector_peak
adsp_env_detector_peak:
{ ldc al, 0 ; dualentsp NSTACKWORDS }
std r5, r4, sp[0]
// take |sample|
ashr r2, sample, 32
{ add sample, sample, r2 ; ldw env, state[ED_ENV] }
xor sample, sample, r2
// choose alpha
{ lss r11, env, sample ; ldw alpha, state[ED_ATT_AL] }
{ ldc ah, 0 ; bt r11, .alpha_if_peak_end }
{ ; ldw alpha, state[ED_REL_AL] }
.alpha_if_peak_end:
// run q31 ema
{ sub sample, sample, env ; mkmsk r11, 5 }
linsert ah, al, env, r11, 32
maccs ah, al, alpha, sample
lsats ah, al, r11
lextract env, ah, al, r11, 32
{ ; stw env, state[ED_ENV] }
ldd r5, r4, sp[0]
retsp NSTACKWORDS
.L_func_end_peak:
.cc_bottom adsp_env_detector_peak.function;
#undef al
#define al r11
.cc_top adsp_env_detector_rms.function,adsp_env_detector_rms
adsp_env_detector_rms:
{ ldc ah, 0 ; dualentsp NSTACKWORDS }
std r5, r4, sp[0]
// calculate sample^2
ldw al, cp[q_sig_rnd_acc]
{ ldc r3, Q_SIG ; }
maccs ah, al, sample, sample
lsats ah, al, r3
lextract sample, ah, al, r3, 32
{ ldc ah, 0 ; ldw env, state[ED_ENV] }
// choose alpha
{ lss r3, env, sample ; ldw alpha, state[ED_ATT_AL] }
{ ldc al, 0 ; bt r3, .alpha_if_rms_end }
{ ; ldw alpha, state[ED_REL_AL] }
.alpha_if_rms_end:
{ sub sample, sample, env ; mkmsk r3, 5 }
// run q31 ema
linsert ah, al, env, r3, 32
maccs ah, al, alpha, sample
lsats ah, al, r3
lextract env, ah, al, r3, 32
{ ; stw env, state[ED_ENV] }
ldd r5, r4, sp[0]
retsp NSTACKWORDS
.L_func_end_rms:
.cc_bottom adsp_env_detector_rms.function;
.globl adsp_env_detector_peak;
.type adsp_env_detector_peak,@function
.set adsp_env_detector_peak.nstackwords,NSTACKWORDS; .global adsp_env_detector_peak.nstackwords;
.set adsp_env_detector_peak.maxcores,1; .global adsp_env_detector_peak.maxcores;
.set adsp_env_detector_peak.maxtimers,0; .global adsp_env_detector_peak.maxtimers;
.set adsp_env_detector_peak.maxchanends,0; .global adsp_env_detector_peak.maxchanends;
.size adsp_env_detector_peak, .L_func_end_peak - adsp_env_detector_peak
.globl adsp_env_detector_rms;
.type adsp_env_detector_rms,@function
.set adsp_env_detector_rms.nstackwords,NSTACKWORDS; .global adsp_env_detector_rms.nstackwords;
.set adsp_env_detector_rms.maxcores,1; .global adsp_env_detector_rms.maxcores;
.set adsp_env_detector_rms.maxtimers,0; .global adsp_env_detector_rms.maxtimers;
.set adsp_env_detector_rms.maxchanends,0; .global adsp_env_detector_rms.maxchanends;
.size adsp_env_detector_rms, .L_func_end_rms - adsp_env_detector_rms

View File

@@ -0,0 +1,37 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include <xcore/assert.h>
int32_t adsp_graphic_eq_10b(int32_t new_sample,
int32_t gains[10],
q2_30 coeffs[50],
int32_t state[160])
{
int32_t ah = 0, al = 1 << (Q_GEQ - 1);
int state_idx = 0;
int32_t polarity = -1;
#pragma unroll
for(unsigned n = 0; n < 10; n++)
{
int32_t this_band;
this_band = adsp_biquad(new_sample, &coeffs[5 * n], &state[state_idx], 0);
state_idx += 8;
this_band = adsp_biquad(this_band, &coeffs[5 * n], &state[state_idx], 0);
state_idx += 8;
// alternate summing and subtracting bands
polarity *= -1;
int32_t this_gain = gains[n] * polarity;
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (this_band), "r" (this_gain), "0" (ah), "1" (al));
}
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (Q_GEQ), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (Q_GEQ));
return ah;
}

View File

@@ -0,0 +1,70 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include "dsp/_helpers/drc_utils.h"
int32_t adsp_limiter_peak(
limiter_t * lim,
int32_t new_samp
) {
adsp_env_detector_peak(&lim->env_det, new_samp);
int32_t env = (lim->env_det.envelope == 0) ? 1 : lim->env_det.envelope;
int32_t new_gain = INT32_MAX;
if(lim->threshold < env) {
int32_t ah = 0, al = 0, r = 0;
asm("linsert %0, %1, %2, %3, 32": "=r" (ah), "=r" (al): "r" (lim->threshold), "r" (31), "0" (ah), "1" (al));
asm("ldivu %0, %1, %2, %3, %4": "=r" (new_gain), "=r" (r): "r" (ah), "r" (al), "r" (env));
}
int32_t alpha = lim->env_det.release_alpha;
if( lim->gain > new_gain ) {
alpha = lim->env_det.attack_alpha;
}
lim->gain = q31_ema(lim->gain, new_gain, alpha);
return apply_gain_q31(new_samp, lim->gain);
}
int32_t adsp_hard_limiter_peak(
limiter_t * lim,
int32_t new_samp
) {
int32_t out = adsp_limiter_peak(lim, new_samp);
// hard clip if above threshold
out = (out > lim->threshold) ? lim->threshold : (out < -lim->threshold) ? -lim->threshold : out;
return out;
}
int32_t adsp_limiter_rms(
limiter_t * lim,
int32_t new_samp
) {
adsp_env_detector_rms(&lim->env_det, new_samp);
int32_t env = (lim->env_det.envelope == 0) ? 1 : lim->env_det.envelope;
int32_t new_gain = INT32_MAX;
if(lim->threshold < env) {
int32_t ah = 0, al = 0; int r = 0;
asm("linsert %0, %1, %2, %3, 32": "=r" (ah), "=r" (al): "r" (lim->threshold), "r" (31), "0" (ah), "1" (al));
asm("ldivu %0, %1, %2, %3, %4": "=r" (new_gain), "=r" (r): "r" (ah), "r" (al), "r" (env));
new_gain = s32_sqrt(&r, new_gain, -31, S32_SQRT_MAX_DEPTH);
right_shift_t rsh = -31 - r;
new_gain >>= rsh;
}
float alpha = lim->env_det.release_alpha;
if( lim->gain > new_gain ) {
alpha = lim->env_det.attack_alpha;
}
lim->gain = q31_ema(lim->gain, new_gain, alpha);
return apply_gain_q31(new_samp, lim->gain);
return new_samp;
}
int32_t adsp_clipper(
clipper_t clip,
int32_t new_samp
) {
return (new_samp > clip) ? clip : (new_samp < -clip) ? -clip : new_samp;
}

View File

@@ -0,0 +1,24 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include "dsp/_helpers/drc_utils.h"
int32_t adsp_noise_gate(
noise_gate_t * ng,
int32_t new_samp
) {
adsp_env_detector_peak(&ng->env_det, new_samp);
int32_t env = (ng->env_det.envelope == 0) ? 1 : ng->env_det.envelope;
int32_t new_gain = (ng->threshold > env) ? 0 : INT32_MAX;
// for the noise gate, the attack and release times are swapped
// i.e. attack time is after going under threshold instead of over
int32_t alpha = ng->env_det.attack_alpha;
if( ng->gain > new_gain ) {
alpha = ng->env_det.release_alpha;
}
ng->gain = q31_ema(ng->gain, new_gain, alpha);
return apply_gain_q31(new_samp, ng->gain);
}

View File

@@ -0,0 +1,40 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include "dsp/_helpers/drc_utils.h"
int32_t adsp_noise_suppressor_expander(
noise_suppressor_expander_t * nse,
int32_t new_samp
) {
adsp_env_detector_peak(&nse->env_det, new_samp);
int32_t new_gain = INT32_MAX;
if (-nse->slope > 0 && nse->threshold > nse->env_det.envelope) {
// This looks a bit scary, but as long as envelope < threshold,
// it can't overflow. The inv_th had exp of -36, when multiplied by env
// has exp of -63.
int64_t new_gain_i64 = nse->env_det.envelope * nse->inv_threshold;
new_gain = new_gain_i64 >> 32;
int32_t exp = -Q_alpha - 32 + 23;
float ng_fl;
asm("fmake %0, %1, %2, %3, %4": "=r" (ng_fl): "r" (0), "r" (exp), "r" (new_gain), "r" ((uint32_t)new_gain_i64));
ng_fl = powf(ng_fl, -nse->slope);
// risk of ng_fl being 1 if envelope/thresh > (1 - 2^-24)
if(ng_fl >= 1.0f){
return INT32_MAX;
}
asm("fsexp %0, %1, %2": "=r" (new_gain), "=r" (exp): "r" (ng_fl));
asm("fmant %0, %1": "=r" (new_gain): "r" (ng_fl));
exp = -Q_alpha - exp + 23;
new_gain >>= exp;
}
int32_t alpha = nse->env_det.attack_alpha;
if( nse->gain > new_gain ) {
alpha = nse->env_det.release_alpha;
}
nse->gain = q31_ema(nse->gain, new_gain, alpha);
return apply_gain_q31(new_samp, nse->gain);
}

View File

@@ -0,0 +1,467 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <xcore/assert.h>
#include <string.h>
#include "dsp/adsp.h"
#include "dsp/_helpers/generic_utils.h"
#include "dsp/_helpers/reverb_utils.h"
#define DEFAULT_COMB_LENS \
{ \
1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 \
}
#define DEFAULT_AP_LENS \
{ \
556, 441, 341, 225 \
}
#define DEFAULT_SPREAD 23
#define TWO_TO_31 0x80000000
#define DEFAULT_AP_FEEDBACK 0x40000000 // 0.5 in Q0.31
// EFFECT_GAIN is this reverb's "makeup gain". It is applied to the wet signal
// after the wet gain. When set properly, the makeup gain should have the effect
// of bringing the wet signal level up to match the dry signal, assuming the wet
// and dry gains are equal.
//
// This hardcoded value of 10dB was found to be correct for the default config.
// It is set here and not by the user via wet-gain as it is out of range of the
// Q31 wet gain configuration parameter. Possible future enhancement: make configurable.
#define EFFECT_GAIN 424433723 // 10 dB linear in q27
#if Q_GAIN != 27
#error "Need to change the EFFECT_GAIN"
#endif
/**
*
* mem_manager class and methods
*
*/
typedef struct
{
void *heap_start;
uint32_t num_bytes;
uint32_t allocated_bytes;
} mem_manager_t;
static inline mem_manager_t mem_manager_init(
void *heap_address,
uint32_t number_of_bytes)
{
mem_manager_t mem;
mem.heap_start = heap_address;
mem.num_bytes = number_of_bytes;
mem.allocated_bytes = 0;
return mem;
}
static inline void *mem_manager_alloc(mem_manager_t *mem, size_t size)
{
// The xcore is byte aligned! But some instructions require word aligned
// or even double-word aligned data to operate correctly. This allocator
// has no opinions about this - caveat emptor.
void *ret_address = NULL;
if (size <= (mem->num_bytes - mem->allocated_bytes))
{
ret_address = mem->heap_start + mem->allocated_bytes;
mem->allocated_bytes += size;
}
return ret_address;
}
/**
*
* allpass_fv class and methods
*
*/
static inline allpass_fv_t allpass_fv_init(
uint32_t max_delay,
int32_t feedback_gain,
mem_manager_t *mem)
{
allpass_fv_t ap;
ap.max_delay = max_delay;
ap.delay = max_delay;
ap.feedback = feedback_gain;
ap.buffer_idx = 0;
ap.buffer = mem_manager_alloc(mem, max_delay * sizeof(int32_t));
xassert(ap.buffer != NULL);
return ap;
}
static inline void allpass_fv_set_delay(allpass_fv_t *ap, uint32_t delay)
{
// saturate at max_delay
ap->delay = (delay < ap->max_delay) ? delay : ap->max_delay;
}
static inline void allpass_fv_reset_state(allpass_fv_t *ap)
{
memset(ap->buffer, 0, ap->max_delay * sizeof(int32_t));
}
int32_t allpass_fv(allpass_fv_t *ap, int32_t new_sample)
{
int32_t buf_out = ap->buffer[ap->buffer_idx];
// Do (buf_out - new_sample) and saturate
int32_t retval = adsp_subtractor(buf_out, new_sample);
ap->buffer[ap->buffer_idx] = add_with_fb(new_sample, buf_out, ap->feedback, Q_RVR);
ap->buffer_idx += 1;
if (ap->buffer_idx >= ap->delay)
{
ap->buffer_idx = 0;
}
return retval;
}
/**
*
* comb_fv class and methods
*
*/
static inline comb_fv_t comb_fv_init(
uint32_t max_delay,
int32_t feedback_gain,
int32_t damping,
mem_manager_t *mem)
{
comb_fv_t comb;
comb.max_delay = max_delay;
comb.delay = 0;
comb.feedback = feedback_gain;
comb.buffer_idx = 0;
comb.filterstore = 0;
// damping is always at least 1, because we guarantee this earlier
comb.damp_1 = damping;
comb.damp_2 = (uint32_t)TWO_TO_31 - damping;
comb.buffer = mem_manager_alloc(mem, max_delay * sizeof(int32_t));
xassert(comb.buffer != NULL);
return comb;
}
static inline void comb_fv_set_delay(comb_fv_t *comb, uint32_t new_delay)
{
// saturate at max_delay
comb->delay = (new_delay < comb->max_delay) ? new_delay : comb->max_delay;
}
static inline void comb_fv_reset_state(comb_fv_t *comb)
{
memset(comb->buffer, 0, comb->max_delay * sizeof(int32_t));
comb->filterstore = 0;
}
static inline int32_t comb_fv(comb_fv_t *comb, int32_t new_sample)
{
int32_t ah = 0, al = 0, shift = Q_RVR;
int32_t fstore = comb->filterstore, d1 = comb->damp_1, d2 = comb->damp_2;
int32_t output = comb->buffer[comb->buffer_idx];
// Do (output * damp_2) into a 64b word ah:al
asm volatile("maccs %0, %1, %2, %3"
: "=r"(ah), "=r"(al)
: "r"(output), "r"(d2), "0"(ah), "1"(al));
// Then add (filterstore * damp_1) to that
asm volatile("maccs %0, %1, %2, %3"
: "=r"(ah), "=r"(al)
: "r"(fstore), "r"(d1), "0"(ah), "1"(al));
comb->filterstore = scale_sat_int64_to_int32_floor(ah, al, shift);
fstore = comb->filterstore;
comb->buffer[comb->buffer_idx] = add_with_fb(new_sample, fstore, comb->feedback, Q_RVR);
comb->buffer_idx += 1;
if (comb->buffer_idx >= comb->delay)
{
comb->buffer_idx = 0;
}
return output;
}
void adsp_reverb_room_init_filters(
reverb_room_t *rv,
float fs,
float max_room_size,
uint32_t max_predelay,
uint32_t predelay,
int32_t feedback,
int32_t damping,
void * reverb_heap)
{
mem_manager_t memory_manager = mem_manager_init(
reverb_heap,
ADSP_RVR_HEAP_SZ(fs, max_room_size, max_predelay));
const float rv_scale_fac = ADSP_RVR_SCALE(fs, max_room_size);
// shift 2 insted of / sizeof(int32_t), to avoid division
rv->total_buffer_length = ADSP_RVR_HEAP_SZ(fs, max_room_size, max_predelay) >> 2;
uint32_t comb_lengths[ADSP_RVR_N_COMBS] = DEFAULT_COMB_LENS;
uint32_t ap_lengths[ADSP_RVR_N_APS] = DEFAULT_AP_LENS;
for (int i = 0; i < ADSP_RVR_N_COMBS; i++)
{
// Scale maximum lengths by the scale factor (fs/44100 * max_room)
comb_lengths[i] *= rv_scale_fac;
rv->combs[i] = comb_fv_init(
comb_lengths[i],
feedback,
damping,
&memory_manager);
}
for (int i = 0; i < ADSP_RVR_N_APS; i++)
{
// Scale maximum lengths by the scale factor (fs/44100 * max_room)
ap_lengths[i] *= rv_scale_fac;
rv->allpasses[i] = allpass_fv_init(
ap_lengths[i],
DEFAULT_AP_FEEDBACK,
&memory_manager);
}
// init predelay manually with memory_manager
rv->predelay.fs = fs;
rv->predelay.buffer_idx = 0;
rv->predelay.delay = predelay;
rv->predelay.max_delay = max_predelay;
rv->predelay.buffer = mem_manager_alloc(&memory_manager, DELAY_DSP_REQUIRED_MEMORY_SAMPLES(max_predelay));
}
void adsp_reverb_room_reset_state(reverb_room_t *rv)
{
for (int comb = 0; comb < ADSP_RVR_N_COMBS; comb++)
{
comb_fv_reset_state(&rv->combs[comb]);
}
for (int ap = 0; ap < ADSP_RVR_N_APS; ap++)
{
allpass_fv_reset_state(&rv->allpasses[ap]);
}
}
uint32_t adsp_reverb_room_get_buffer_lens(reverb_room_t *rv)
{
return rv->total_buffer_length;
}
void adsp_reverb_room_set_room_size(reverb_room_t *rv,
float new_room_size)
{
// For larger rooms, increase max_room_size
xassert(new_room_size >= 0 && new_room_size <= 1);
rv->room_size = new_room_size;
// could use uq32 for the room size, but it's important
// to represent 1.0 here, so loosing one bit of precision
// and doing extra lextracts :(
int32_t zero = 0, q = 31, exp;
uint32_t ah = 0, al = 0, room_size_int;
asm("fsexp %0, %1, %2": "=r"(zero), "=r"(exp): "r"(new_room_size));
asm("fmant %0, %1": "=r"(room_size_int): "r"(new_room_size));
right_shift_t shr = -q - exp + 23;
room_size_int >>= shr;
for (int comb = 0; comb < ADSP_RVR_N_COMBS; comb++)
{
// Do comb length * new_room_size in UQ0.31
uint32_t l = rv->combs[comb].max_delay;
asm volatile("lmul %0, %1, %2, %3, %4, %5"
: "=r"(ah), "=r"(al)
: "r"(room_size_int), "r"(l), "r"(zero), "r"(zero));
asm volatile("lextract %0, %1, %2, %3, 32"
: "=r"(ah)
: "r"(ah), "r"(al), "r"(q));
comb_fv_set_delay(&rv->combs[comb], ah);
}
for (int ap = 0; ap < ADSP_RVR_N_APS; ap++)
{
// Do ap length * new_room_size in UQ0.31
uint32_t l = rv->allpasses[ap].max_delay;
asm volatile("lmul %0, %1, %2, %3, %4, %5"
: "=r"(ah), "=r"(al)
: "r"(room_size_int), "r"(l), "r"(zero), "r"(zero));
asm volatile("lextract %0, %1, %2, %3, 32"
: "=r"(ah)
: "r"(ah), "r"(al), "r"(q));
allpass_fv_set_delay(&rv->allpasses[ap], ah);
}
}
int32_t adsp_reverb_room(
reverb_room_t *rv,
int32_t new_samp)
{
int32_t delayed_input = adsp_delay(&rv->predelay, new_samp);
int32_t reverb_input = apply_gain_q31(delayed_input, rv->pre_gain);
int32_t output = 0, ah = 0, al = 0, mul = 2, one = 1;
for (int comb = 0; comb < ADSP_RVR_N_COMBS; comb++)
{
// macc combs
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (comb_fv(&(rv->combs[comb]), reverb_input)), "r" (mul), "0" (ah), "1" (al));
}
// saturate and extract
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (one), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (output): "r" (ah), "r" (al), "r" (one));
for (int ap = 0; ap < ADSP_RVR_N_APS; ap++)
{
output = allpass_fv(&(rv->allpasses[ap]), output);
}
output = apply_gain_q31(output, rv->wet_gain);
output = mix_wet_dry(output, apply_gain_q31(new_samp, rv->dry_gain), EFFECT_GAIN, Q_GAIN);
return output;
}
void adsp_reverb_room_st_init_filters(
reverb_room_st_t *rv,
float fs,
float max_room_size,
uint32_t max_predelay,
uint32_t predelay,
int32_t feedback,
int32_t damping,
void * reverb_heap)
{
mem_manager_t memory_manager = mem_manager_init(
reverb_heap,
ADSP_RVRST_HEAP_SZ(fs, max_room_size, max_predelay));
const float rv_scale_fac = ADSP_RVR_SCALE(fs, max_room_size);
// shift 2 insted of / sizeof(int32_t), to avoid division
rv->total_buffer_length = ADSP_RVRST_HEAP_SZ(fs, max_room_size, max_predelay) >> 2;
rv->spread_length = rv_scale_fac * DEFAULT_SPREAD;
uint32_t comb_lengths[ADSP_RVR_N_COMBS] = DEFAULT_COMB_LENS;
uint32_t ap_lengths[ADSP_RVR_N_APS] = DEFAULT_AP_LENS;
for (int i = 0; i < ADSP_RVR_N_COMBS; i++)
{
// Scale maximum lengths by the scale factor (fs/44100 * max_room)
comb_lengths[i] *= rv_scale_fac;
rv->combs[0][i] = comb_fv_init(
comb_lengths[i],
feedback,
damping,
&memory_manager);
rv->combs[1][i] = comb_fv_init(
comb_lengths[i] + rv->spread_length,
feedback,
damping,
&memory_manager);
}
for (int i = 0; i < ADSP_RVR_N_APS; i++)
{
// Scale maximum lengths by the scale factor (fs/44100 * max_room)
ap_lengths[i] *= rv_scale_fac;
rv->allpasses[0][i] = allpass_fv_init(
ap_lengths[i],
DEFAULT_AP_FEEDBACK,
&memory_manager);
rv->allpasses[1][i] = allpass_fv_init(
ap_lengths[i] + rv->spread_length,
DEFAULT_AP_FEEDBACK,
&memory_manager);
}
// init predelay manually with memory_manager
rv->predelay.fs = fs;
rv->predelay.buffer_idx = 0;
rv->predelay.delay = predelay;
rv->predelay.max_delay = max_predelay;
rv->predelay.buffer = mem_manager_alloc(&memory_manager, DELAY_DSP_REQUIRED_MEMORY_SAMPLES(max_predelay));
}
void adsp_reverb_room_st_set_room_size(reverb_room_st_t *rv,
float new_room_size)
{
// For larger rooms, increase max_room_size
xassert(new_room_size >= 0 && new_room_size <= 1);
rv->room_size = new_room_size;
// could use uq32 for the room size, but it's important
// to represent 1.0 here, so loosing one bit of precision
// and doing extra lextracts :(
int32_t zero = 0, q = 31, exp;
uint32_t ah = 0, al = 0, room_size_int, sp_delay;
asm("fsexp %0, %1, %2": "=r"(zero), "=r"(exp): "r"(new_room_size));
asm("fmant %0, %1": "=r"(room_size_int): "r"(new_room_size));
right_shift_t shr = -q - exp + 23;
room_size_int >>= shr;
// Do spread length * new_room_size in UQ0.31
asm("lmul %0, %1, %2, %3, %4, %5"
: "=r" (ah), "=r" (al)
: "r" (room_size_int), "r" (rv->spread_length), "r" (zero), "r" (zero));
asm("lextract %0, %1, %2, %3, 32"
: "=r" (sp_delay)
: "r" (ah), "r" (al), "r" (q));
for (int comb = 0; comb < ADSP_RVR_N_COMBS; comb++)
{
// Do comb length * new_room_size in UQ0.31
uint32_t l = rv->combs[0][comb].max_delay;
asm volatile("lmul %0, %1, %2, %3, %4, %5"
: "=r"(ah), "=r"(al)
: "r"(room_size_int), "r"(l), "r"(zero), "r"(zero));
asm volatile("lextract %0, %1, %2, %3, 32"
: "=r"(ah)
: "r"(ah), "r"(al), "r"(q));
comb_fv_set_delay(&rv->combs[0][comb], ah);
comb_fv_set_delay(&rv->combs[1][comb], ah + sp_delay);
}
for (int ap = 0; ap < ADSP_RVR_N_APS; ap++)
{
// Do ap length * new_room_size in UQ0.31
uint32_t l = rv->allpasses[0][ap].max_delay;
asm volatile("lmul %0, %1, %2, %3, %4, %5"
: "=r"(ah), "=r"(al)
: "r"(room_size_int), "r"(l), "r"(zero), "r"(zero));
asm volatile("lextract %0, %1, %2, %3, 32"
: "=r"(ah)
: "r"(ah), "r"(al), "r"(q));
allpass_fv_set_delay(&rv->allpasses[0][ap], ah);
allpass_fv_set_delay(&rv->allpasses[1][ap], ah + sp_delay);
}
}
void adsp_reverb_room_st(
reverb_room_st_t *rv,
int32_t outputs_lr[2],
int32_t in_left,
int32_t in_right)
{
int32_t reverb_input = mix_with_pregain(in_left, in_right, rv->pre_gain, Q_RVR);
int32_t delayed_input = adsp_delay(&rv->predelay, reverb_input);
int32_t out_r = 0, out_l = 0, ahl = 0, all = 0, ahr = 0, alr = 0, mul = 2, one = 1;
for (int comb = 0; comb < ADSP_RVR_N_COMBS; comb++)
{
// macc combs into left and right accs
asm("maccs %0, %1, %2, %3": "=r" (ahl), "=r" (all): "r" (comb_fv(&(rv->combs[0][comb]), delayed_input)), "r" (mul), "0" (ahl), "1" (all));
asm("maccs %0, %1, %2, %3": "=r" (ahr), "=r" (alr): "r" (comb_fv(&(rv->combs[1][comb]), delayed_input)), "r" (mul), "0" (ahr), "1" (alr));
}
// saturate and extract accs
asm("lsats %0, %1, %2": "=r" (ahl), "=r" (all): "r" (one), "0" (ahl), "1" (all));
asm("lsats %0, %1, %2": "=r" (ahr), "=r" (alr): "r" (one), "0" (ahr), "1" (alr));
asm("lextract %0, %1, %2, %3, 32": "=r" (out_l): "r" (ahl), "r" (all), "r" (one));
asm("lextract %0, %1, %2, %3, 32": "=r" (out_r): "r" (ahr), "r" (alr), "r" (one));
for (int ap = 0; ap < ADSP_RVR_N_APS; ap++)
{
out_l = allpass_fv(&(rv->allpasses[0][ap]), out_l);
out_r = allpass_fv(&(rv->allpasses[1][ap]), out_r);
}
int32_t out = adsp_crossfader(out_l, out_r, rv->wet_gain1, rv->wet_gain2, Q_RVR);
outputs_lr[0] = mix_wet_dry(out, apply_gain_q31(in_left, rv->dry_gain), EFFECT_GAIN, Q_GAIN);
out = adsp_crossfader(out_r, out_l, rv->wet_gain1, rv->wet_gain2, Q_RVR);
outputs_lr[1] = mix_wet_dry(out, apply_gain_q31(in_right, rv->dry_gain), EFFECT_GAIN, Q_GAIN);
}

View File

@@ -0,0 +1,310 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <xcore/assert.h>
#include "dsp/adsp.h"
#include "dsp/_helpers/generic_utils.h"
#include "dsp/_helpers/reverb_utils.h"
#define DEFAULT_AP_LENS {142, 107, 379, 277, 2656, 1800}
#define DEFAULT_DELAY_LENS {4217, 4453, 3136, 3720}
#define DEFAULT_MOD_AP_LENS {908, 672}
#define DEFAULT_TAPS_L {266, 2974, 1913, 1996, 1990, 187, 1066}
#define DEFAULT_TAPS_R {353, 3627, 1228, 2673, 2111, 335, 121}
// EFFECT_GAIN is this reverb's "makeup gain". It is applied to the wet signal
// after the wet gain. When set properly, the makeup gain should have the effect
// of bringing the wet signal level up to match the dry signal, assuming the wet
// and dry gains are equal.
//
// This hardcoded value of -1dB was found to be correct for the default config.
// It is set here and not by the user via wet-gain as it could be out of range of the
// Q31 wet gain configuration parameter. Possible future enhancement: make configurable.
#define EFFECT_GAIN 119621676 // -1 dB linear in q27
#if Q_GAIN != 27
#error "Need to change the EFFECT_GAIN"
#endif
#define TWO_TO_31 0x80000000
typedef struct
{
void *heap_start;
uint32_t num_bytes;
uint32_t allocated_bytes;
} mem_manager_t;
static inline mem_manager_t mem_manager_init(
void *heap_address,
uint32_t number_of_bytes)
{
mem_manager_t mem;
mem.heap_start = heap_address;
mem.num_bytes = number_of_bytes;
mem.allocated_bytes = 0;
return mem;
}
static inline void *mem_manager_alloc(mem_manager_t *mem, size_t size)
{
// The xcore is byte aligned! But some instructions require word aligned
// or even double-word aligned data to operate correctly. This allocator
// has no opinions about this - caveat emptor.
void *ret_address = NULL;
if (size <= (mem->num_bytes - mem->allocated_bytes))
{
ret_address = mem->heap_start + mem->allocated_bytes;
mem->allocated_bytes += size;
}
return ret_address;
}
lowpass_1ord_t lowpass_1ord_init(int32_t feedback) {
lowpass_1ord_t lp;
lp.filterstore = 0;
feedback = (feedback < 1) ? 1 : feedback;
lp.damp_1 = feedback;
lp.damp_2 = (uint32_t)TWO_TO_31 - feedback;
return lp;
}
static inline int32_t lowpass_1ord(lowpass_1ord_t * lp, int32_t new_samp) {
/*
output = (sample * self.coeff_b0_int) + (self._filterstore * self.coeff_a1_int)
self._filterstore = output
return output
*/
int32_t ah = 0, al = 0, shift = Q_RVP;
int32_t fstore = lp->filterstore, d1 = lp->damp_1, d2 = lp->damp_2;
asm volatile("maccs %0, %1, %2, %3"
: "=r"(ah), "=r"(al)
: "r"(new_samp), "r"(d1), "0"(ah), "1"(al));
asm volatile("maccs %0, %1, %2, %3"
: "=r"(ah), "=r"(al)
: "r"(fstore), "r"(d2), "0"(ah), "1"(al));
al = scale_sat_int64_to_int32_floor(ah, al, shift);
lp->filterstore = al;
return al;
}
static inline allpass_fv_t allpass_fv_init(
uint32_t max_delay,
int32_t feedback_gain,
void * mem)
{
xassert(mem != NULL);
allpass_fv_t ap;
ap.max_delay = max_delay;
ap.delay = max_delay;
ap.feedback = feedback_gain;
ap.buffer_idx = 0;
ap.buffer = mem;
return ap;
}
int32_t allpass_2(allpass_fv_t * ap, int32_t new_samp) {
/*
buf_out <- buf
buf_in = new_samp - (buf_out * fb)
buf <- buf_in
inc buf
out = buf_out + new_samp * fb
return out
*/
int32_t buf_out = ap->buffer[ap->buffer_idx];
int32_t buf_in = add_with_fb(new_samp, buf_out, -ap->feedback, Q_RVP);
ap->buffer[ap->buffer_idx] = buf_in;
ap->buffer_idx += 1;
if (ap->buffer_idx >= ap->delay)
{
ap->buffer_idx = 0;
}
int32_t output = add_with_fb(buf_out, buf_in, ap->feedback, Q_RVP);
return output;
}
// 0.6 * 2 ** 29 = 322122547.2
// chosen as one of the closest ot the whole number
// and to give extra headroom for maccs
#define SCALE 322122547
#define SCALE_Q 29
void adsp_reverb_plate(
reverb_plate_t *rv,
int32_t outputs_lr[2],
int32_t in_left,
int32_t in_right)
{
int32_t reverb_input = mix_with_pregain(in_left, in_right, rv->pre_gain, Q_RVP);
reverb_input = adsp_delay(&rv->predelay, reverb_input);
reverb_input = lowpass_1ord(&rv->lowpasses[0], reverb_input);
for (unsigned i = 0; i < 4; i++) {
reverb_input = allpass_2(&rv->allpasses[i], reverb_input);
}
int32_t path_1 = reverb_input, path_2 = reverb_input;
path_1 = add_with_fb(path_1, rv->paths[0], rv->decay, Q_RVP);
path_2 = add_with_fb(path_2, rv->paths[1], rv->decay, Q_RVP);
path_1 = allpass_2(&rv->mod_allpasses[0], path_1);
path_1 = adsp_delay(&rv->delays[0], path_1);
path_1 = lowpass_1ord(&rv->lowpasses[1], path_1);
path_1 = apply_gain_q31(path_1, rv->decay);
path_1 = allpass_2(&rv->allpasses[4], path_1);
rv->paths[1] = adsp_delay(&rv->delays[1], path_1);
path_2 = allpass_2(&rv->mod_allpasses[1], path_2);
path_2 = adsp_delay(&rv->delays[2], path_2);
path_2 = lowpass_1ord(&rv->lowpasses[2], path_2);
path_2 = apply_gain_q31(path_2, rv->decay);
path_2 = allpass_2(&rv->allpasses[5], path_2);
rv->paths[0] = adsp_delay(&rv->delays[3], path_2);
int32_t out_l, out_r, q = SCALE_Q, ah = 0, al = 1 << (q - 1);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[0].buffer[rv->taps_l[0]]), "r" (SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[0].buffer[rv->taps_l[1]]), "r" (SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->allpasses[4].buffer[rv->taps_l[2]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[1].buffer[rv->taps_l[3]]), "r" (SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[2].buffer[rv->taps_l[4]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->allpasses[5].buffer[rv->taps_l[5]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[3].buffer[rv->taps_l[6]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (out_l): "r" (ah), "r" (al), "r" (q));
ah = 0; al = 1 << (q - 1);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[2].buffer[rv->taps_r[0]]), "r" (SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[2].buffer[rv->taps_r[1]]), "r" (SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->allpasses[5].buffer[rv->taps_r[2]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[3].buffer[rv->taps_r[3]]), "r" (SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[0].buffer[rv->taps_r[4]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->allpasses[4].buffer[rv->taps_r[5]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (rv->delays[1].buffer[rv->taps_r[6]]), "r" (-SCALE), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (out_r): "r" (ah), "r" (al), "r" (q));
for (unsigned i = 0; i < ADSP_RVP_N_OUT_TAPS; i++) {
rv->taps_l[i]++;
rv->taps_r[i]++;
if (rv->taps_l[i] >= rv->taps_len_l[i]) {
rv->taps_l[i] = 0;
}
if (rv->taps_r[i] >= rv->taps_len_r[i]) {
rv->taps_r[i] = 0;
}
}
int32_t out = adsp_crossfader(out_l, out_r, rv->wet_gain1, rv->wet_gain2, Q_RVP);
out = mix_wet_dry(out, apply_gain_q31(in_left, rv->dry_gain), EFFECT_GAIN, Q_GAIN);
outputs_lr[0] = out;
out = adsp_crossfader(out_r, out_l, rv->wet_gain1, rv->wet_gain2, Q_RVP);
out = mix_wet_dry(out, apply_gain_q31(in_right, rv->dry_gain), EFFECT_GAIN, Q_GAIN);
outputs_lr[1] = out;
}
void adsp_reverb_plate_init_filters(
reverb_plate_t * rv,
float fs,
int32_t decay_diffusion_1,
int32_t decay_diffusion_2,
int32_t in_diffusion_1,
int32_t in_diffusion_2,
uint32_t max_predelay,
uint32_t predelay,
void * reverb_heap)
{
// init all memory stuff
mem_manager_t mem = mem_manager_init(reverb_heap, ADSP_RVP_HEAP_SZ(fs, max_predelay));
const float rv_scale_fac = ADSP_RVP_SCALE(fs);
uint32_t ap_lens[ADSP_RVP_N_APS] = DEFAULT_AP_LENS;
uint32_t delay_lens[ADSP_RVP_N_DELAYS] = DEFAULT_DELAY_LENS;
uint32_t mod_ap_lens[ADSP_RVP_N_PATHS] = DEFAULT_MOD_AP_LENS;
memset(rv->paths, 0, ADSP_RVP_N_PATHS * sizeof(int32_t));
// predelay
void * delay_mem = mem_manager_alloc(&mem, DELAY_DSP_REQUIRED_MEMORY_SAMPLES(max_predelay));
xassert(delay_mem != NULL);
rv->predelay.fs = fs;
rv->predelay.buffer_idx = 0;
rv->predelay.delay = predelay;
rv->predelay.max_delay = max_predelay;
rv->predelay.buffer = delay_mem;
// allpasses
// yes, it makes me cry as well
for (unsigned i = 0; i < 2; i++) {
ap_lens[i] *= rv_scale_fac;
void * ap_mem = mem_manager_alloc(&mem, ap_lens[i]<<2);
rv->allpasses[i] = allpass_fv_init(ap_lens[i], in_diffusion_1, ap_mem);
}
for (unsigned i = 2; i < 4; i++) {
ap_lens[i] *= rv_scale_fac;
void * ap_mem = mem_manager_alloc(&mem, ap_lens[i]<<2);
rv->allpasses[i] = allpass_fv_init(ap_lens[i], in_diffusion_2, ap_mem);
}
for (unsigned i = 4; i < 6; i++) {
ap_lens[i] *= rv_scale_fac;
void * ap_mem = mem_manager_alloc(&mem, ap_lens[i]<<2);
rv->allpasses[i] = allpass_fv_init(ap_lens[i], decay_diffusion_2, ap_mem);
}
// mod allpasses
for (unsigned i = 0; i < ADSP_RVP_N_PATHS; i++) {
mod_ap_lens[i] *= rv_scale_fac;
void * ap_mem = mem_manager_alloc(&mem, mod_ap_lens[i]<<2);
rv->mod_allpasses[i] = allpass_fv_init(mod_ap_lens[i], decay_diffusion_1, ap_mem);
}
// delays
for (unsigned i = 0; i < ADSP_RVP_N_DELAYS; i++) {
delay_lens[i] *= rv_scale_fac;
void * delay_mem = mem_manager_alloc(&mem, delay_lens[i]<<2);
xassert(delay_mem != NULL);
rv->delays[i].fs = fs;
rv->delays[i].buffer_idx = 0;
rv->delays[i].delay = delay_lens[i];
rv->delays[i].max_delay = delay_lens[i];
rv->delays[i].buffer = delay_mem;
}
// set tap lens
rv->taps_len_l[0] = rv->delays[0].max_delay;
rv->taps_len_l[1] = rv->delays[0].max_delay;
rv->taps_len_l[2] = rv->allpasses[4].max_delay;
rv->taps_len_l[3] = rv->delays[1].max_delay;
rv->taps_len_l[4] = rv->delays[2].max_delay;
rv->taps_len_l[5] = rv->allpasses[5].max_delay;
rv->taps_len_l[6] = rv->delays[3].max_delay;
rv->taps_len_r[0] = rv->delays[2].max_delay;
rv->taps_len_r[1] = rv->delays[2].max_delay;
rv->taps_len_r[2] = rv->allpasses[5].max_delay;
rv->taps_len_r[3] = rv->delays[3].max_delay;
rv->taps_len_r[4] = rv->delays[0].max_delay;
rv->taps_len_r[5] = rv->allpasses[4].max_delay;
rv->taps_len_r[6] = rv->delays[1].max_delay;
memcpy(&rv->taps_l, (int32_t [] )DEFAULT_TAPS_L, sizeof(int32_t) * ADSP_RVP_N_OUT_TAPS);
memcpy(&rv->taps_r, (int32_t [] )DEFAULT_TAPS_R, sizeof(int32_t) * ADSP_RVP_N_OUT_TAPS);
for (unsigned i = 0; i < ADSP_RVP_N_OUT_TAPS; i ++) {
rv->taps_l[i] *= rv_scale_fac;
rv->taps_l[i] = rv->taps_len_l[i] - rv->taps_l[i];
rv->taps_r[i] *= rv_scale_fac;
rv->taps_r[i] = rv->taps_len_r[i] - rv->taps_r[i];
}
}

View File

@@ -0,0 +1,196 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "dsp/adsp.h"
#include <xcore/assert.h>
int32_t adsp_from_q31(int32_t input) {
right_shift_t shr = 31 + SIG_EXP;
asm("ashr %0, %1, %2": "=r" (input): "r" (input), "r" (shr));
return input;
}
int32_t adsp_to_q31(int32_t input) {
// put input into ah so that exp = sig_exp - 32
// and then to get -31 saturate and extract with sig_exp - 1
int32_t ah = input, al = 0, pos = Q_SIG + 1;
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (pos), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32":"=r"(ah):"r"(ah),"r"(al),"r"(pos));
return ah;
}
int32_t adsp_adder(int32_t * input, unsigned n_ch) {
// there is a bug in lsats, so it doesn't work if we give it 0,
// so mul all samples by 2 and then lsats and lextract with 1
int32_t ah = 0, al = 0, mul = 2, one = 1;
for (unsigned i = 0; i < n_ch; i ++) {
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (input[i]), "r" (mul), "0" (ah), "1" (al));
}
// make sure we didn't overflow 32 bits
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (one), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (one));
return ah;
}
int32_t adsp_subtractor(int32_t x, int32_t y) {
// there is a bug in lsats, so it doesn't work if we give it 0,
// so mul all samples by 2 and then lsats and lextract with 1
int32_t ah = 0, al = 0, mul = 2, one = 1;
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (x), "r" (mul), "0" (ah), "1" (al));
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (y), "r" (-mul), "0" (ah), "1" (al));
// make sure we didn't overflow 32 bits
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (one), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (one));
return ah;
}
int32_t adsp_fixed_gain(int32_t input, int32_t gain) {
int32_t q_format = Q_GAIN;
// adding 1 << (q_format - 1) for rounding
int32_t ah = 0, al = 1 << (q_format - 1);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (input), "r" (gain), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (q_format), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (q_format));
return ah;
}
int32_t adsp_mixer(int32_t * input, unsigned n_ch, int32_t gain) {
// there is a bug in lsats, so it doesn't work if we give it 0,
// so mul all samples by 2 and then lsats and lextract with 1
int32_t ah = 0, al = 0, mul = 2, one = 1;
for (unsigned i = 0; i < n_ch; i ++) {
int32_t tmp = adsp_fixed_gain(input[i], gain);
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (tmp), "r" (mul), "0" (ah), "1" (al));
}
// make sure we didn't overflow 32 bits
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (one), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (one));
return ah;
}
int32_t adsp_saturate_32b(int64_t acc) {
// there is a bug in lsats, so it doesn't work if we give it 0,
// so we'll have to use lsats with 1 twice so we can accomodate the whole int64 range
int32_t ah, al, mask = 0xffffffff, one = 1;
al = (int32_t)(acc & mask);
ah = (int32_t)((int64_t)(acc >> 32) & mask);
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (one), "0" (ah), "1" (al));
ah <<= one;
asm("linsert %0, %1, %2, %3, 32": "=r" (ah), "=r" (al): "r" (al), "r" (one), "0" (ah), "1" (al));
asm("lsats %0, %1, %2": "=r" (ah), "=r" (al): "r" (one), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32": "=r" (ah): "r" (ah), "r" (al), "r" (one));
return ah;
}
int32_t adsp_volume_control(
volume_control_t * vol_ctl,
int32_t samp
) {
// do the exponential slew
vol_ctl->gain += (vol_ctl->target_gain - vol_ctl->gain) >> vol_ctl->slew_shift;
// apply gain
return adsp_fixed_gain(samp, vol_ctl->gain);
}
void adsp_volume_control_set_gain(
volume_control_t * vol_ctl,
int32_t new_gain
) {
if(!vol_ctl->mute_state) {
vol_ctl->target_gain = new_gain;
}
else
{
vol_ctl->saved_gain = new_gain;
}
}
void adsp_volume_control_mute(
volume_control_t * vol_ctl
) {
if (!vol_ctl->mute_state) {
vol_ctl->mute_state = 1;
vol_ctl->saved_gain = vol_ctl->target_gain;
vol_ctl->target_gain = 0;
}
}
void adsp_volume_control_unmute(
volume_control_t * vol_ctl
) {
if (vol_ctl->mute_state) {
vol_ctl->mute_state = 0;
vol_ctl->target_gain = vol_ctl->saved_gain;
}
}
int32_t adsp_delay(
delay_t * delay,
int32_t samp
) {
int32_t out = delay->buffer[delay->buffer_idx];
delay->buffer[delay->buffer_idx] = samp;
// Could do this with a modulo operation,
// but it would break for when the delay is 0
// and use the division unit
if (++delay->buffer_idx >= delay->delay) {
delay->buffer_idx = 0;
}
return out;
}
int32_t _cos_approx(int32_t x) {
// A two term cosine fade approximation, based on a Chebyshev
// polynomial fit. x must be between -2**30 and 2**30. y is a gain
// between 1 and 0 in Q31. This is only for use with the slewing switch.
// x**2 >> 30
int32_t ah = 0, al = 0, q = 30, tmp = 0;
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (x), "r" (x), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32":"=r"(tmp):"r"(ah),"r"(al),"r"(q));
// set initial value to -1622688857 << 30 so we can add -1622688857 before shifting
al = (int32_t)((((int64_t)-1622688857) << 30) & 0xFFFFFFFF);
ah = (int32_t)(((uint64_t)(((int64_t)-1622688857) << 30)) >> 32);
// y = ((x^2 * 549248075) >> 30) - 1622688857
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (tmp), "r" (549248075), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32":"=r"(tmp):"r"(ah),"r"(al),"r"(q));
// set initial value to 1 << 60 so we can add 1 << 30 before shifting
al = 0;
ah = 1 << (q - 2);
// y = ((x*y) >> 30) + (1 << 30)
asm("maccs %0, %1, %2, %3": "=r" (ah), "=r" (al): "r" (tmp), "r" (x), "0" (ah), "1" (al));
asm("lextract %0, %1, %2, %3, 32":"=r"(tmp):"r"(ah),"r"(al),"r"(q));
return tmp;
}
int32_t adsp_switch_slew(switch_slew_t* switch_slew, int32_t* samples){
if (switch_slew->switching){
int32_t gain_1 = _cos_approx(switch_slew->counter);
int32_t y = ((int64_t)gain_1 * samples[switch_slew->last_position]) >> 31;
int32_t gain_2 = INT32_MAX - gain_1;
y += ((int64_t)gain_2 * samples[switch_slew->position]) >> 31;
switch_slew->counter += switch_slew->step;
if (switch_slew->counter > 1 <<30){
switch_slew->switching = false;
}
return y;
}
else{
return samples[switch_slew->position];
}
}
int32_t adsp_crossfader_slew(crossfader_slew_t* crossfader, int32_t in1, int32_t in2){
int32_t gain_1 = adsp_slew_gain(&crossfader->gain_1);
int32_t gain_2 = adsp_slew_gain(&crossfader->gain_2);
return adsp_crossfader(in1, in2, gain_1, gain_2, 31);
}

View File

@@ -0,0 +1,36 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/adder.h"
void adder_process(int32_t **input, int32_t **output, void *app_data_state)
{
adder_state_t *state = app_data_state;
for(int sample_index = 0; sample_index < state->frame_size; ++sample_index) {
int32_t *out = &output[0][sample_index];
int64_t acc = 0;
for(int input_index = 0; input_index < state->n_inputs; ++input_index) {
acc += input[input_index][sample_index];
}
*out = adsp_saturate_32b(acc);
}
}
void adder_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
adder_state_t *state = instance->state;
memset(state, 0, sizeof(adder_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
xassert(n_outputs == 1 && "Adder should only have one output");
}

View File

@@ -0,0 +1,238 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <xcore/assert.h>
#include <xscope.h>
#include "debug_print.h"
#include "stages/adsp_module.h"
#include "stages/adsp_control.h"
#include "cmd_offsets.h" // Autogenerated
#include "swlock.h"
#define READ_CMD(X) (X & 0x80)
#define WRITE_CMD(X) (~X & 0x80)
#define CLEAR_TOP_BIT(X) (X & 0x7f)
#define HEADER_SIZE 0 // bytes, currently no header
#define INSTANCE_IDX HEADER_SIZE
#define CMD_IDX HEADER_SIZE + 1
#define LEN_IDX HEADER_SIZE + 2
#define PAYLOAD_IDX HEADER_SIZE + 3
#define DEFAULT_DSP_PROBE_ID 0
#define XSCOPE_MAX_PACKET_LEN 256
void adsp_control_xscope_register_probe()
{
xscope_mode_lossless();
xscope_register(1, XSCOPE_CONTINUOUS, "ADSP", XSCOPE_UINT, "Data");
xscope_config_io(XSCOPE_IO_BASIC);
}
chanend_t adsp_control_xscope_init()
{
chanend_t c_dsp_ctrl = chanend_alloc();
xscope_connect_data_from_host(c_dsp_ctrl);
return c_dsp_ctrl;
}
adsp_control_status_t adsp_control_xscope_process(chanend_t c_xscope,
adsp_controller_t *ctrl
)
{
char data[XSCOPE_MAX_PACKET_LEN];
int read;
xscope_data_from_host(c_xscope, (char *)data, &read);
xassert(read <= XSCOPE_MAX_PACKET_LEN);
adsp_control_status_t ret = ADSP_CONTROL_BUSY;
// txfer format is {ADSP, instance_id, cmd_id, payload_len, payload...}
adsp_stage_control_cmd_t cmd = {
.instance_id = data[INSTANCE_IDX],
.cmd_id = CLEAR_TOP_BIT(data[CMD_IDX]),
.payload_len = data[LEN_IDX],
.payload = &data[PAYLOAD_IDX]};
if (READ_CMD(data[CMD_IDX]))
{
while (ret != ADSP_CONTROL_SUCCESS)
{
ret = adsp_read_module_config(ctrl, &cmd);
}
xscope_bytes(DEFAULT_DSP_PROBE_ID, cmd.payload_len, cmd.payload);
}
else
{
while (ret != ADSP_CONTROL_SUCCESS)
{
ret = adsp_write_module_config(ctrl, &cmd);
}
}
return ret;
}
void adsp_control_xscope(adsp_pipeline_t *adsp)
{
chanend_t c_dsp_ctrl = adsp_control_xscope_init();
adsp_controller_t ctrl;
adsp_controller_init(&ctrl, adsp);
SELECT_RES(
CASE_THEN(c_dsp_ctrl, host_transaction))
{
host_transaction:
{
adsp_control_status_t ret = adsp_control_xscope_process(c_dsp_ctrl,
&ctrl);
xassert(ret == ADSP_CONTROL_SUCCESS);
SELECT_CONTINUE_NO_RESET;
}
}
}
static module_instance_t* get_module_instance(module_instance_t *modules, uint32_t res_id, size_t num_modules)
{
for(int i=0; i<num_modules; i++)
{
if(modules[i].control.id == res_id)
{
return &modules[i];
}
}
debug_printf("ERROR: Cannot find a module for the instance-id %lu\n", res_id);
xassert(0);
return NULL;
}
static void get_control_cmd_config_offset(module_instance_t *module, uint8_t cmd_id, uint32_t *offset, uint32_t *size)
{
uint8_t module_type = module->control.module_type;
module_config_offsets_t *config_offsets = ptr_module_offsets[module_type];
for(int i=0; i<module->control.num_control_commands; i++)
{
if(cmd_id == (uint8_t)config_offsets[i].cmd_id)
{
*offset = config_offsets[i].offset;
*size = config_offsets[i].size;
return;
}
}
debug_printf("ERROR: cmd_id %d not found in module_type %d\n", cmd_id, module_type);
xassert(0);
return;
}
void adsp_controller_init(adsp_controller_t* ctrl, adsp_pipeline_t* pipeline) {
xassert(NULL != ctrl);
xassert(NULL != pipeline);
ctrl->modules = pipeline->modules;
ctrl->num_modules = pipeline->n_modules;
}
// Read a module instance's config structure for a given command ID
adsp_control_status_t adsp_read_module_config(
adsp_controller_t* ctrl,
adsp_stage_control_cmd_t *cmd)
{
module_instance_t * modules = ctrl->modules;
size_t num_modules = ctrl->num_modules;
module_instance_t *module = get_module_instance(modules, cmd->instance_id, num_modules);
adsp_control_status_t ret = ADSP_CONTROL_BUSY;
// Multiple threads could be trying to read or write to this stage at the same
// time, a swlock is used to ensure that stage state changes are done atomically.
// In the case where lock contention occurs the function will return busy.
if(SWLOCK_NOT_ACQUIRED != swlock_try_acquire(&module->control.lock)) {
uint32_t offset, size;
// Get offset into the module's config structure for this command
get_control_cmd_config_offset(module, cmd->cmd_id, &offset, &size);
if(size != cmd->payload_len)
{
debug_printf("ERROR: payload_len mismatch. Expected %lu, but received %u\n", size, cmd->payload_len);
xassert(0);
}
config_rw_state_t config_state = module->control.config_rw_state;
if(config_state == config_none_pending) // No command pending or read pending
{
// Inform the module of the read so it can update config with the latest data
module->control.cmd_id = cmd->cmd_id;
module->control.config_rw_state = config_read_pending;
module->control.current_controller = (uintptr_t)ctrl;
// Return RETRY as status
ret = ADSP_CONTROL_BUSY;
}
else if(config_state == config_read_updated && module->control.current_controller == (uintptr_t)ctrl)
{
// Confirm same cmd_id
xassert(module->control.cmd_id == cmd->cmd_id);
// Update payload
memcpy((uint8_t*)&cmd->payload[0], (uint8_t*)module->control.config + offset, size);
module->control.config_rw_state = config_none_pending;
module->control.current_controller = 0;
ret= ADSP_CONTROL_SUCCESS;
}
else
{
// stage is busy handling a request
}
swlock_release(&module->control.lock);
}
return ret;
}
// Write to a module instance's config structure for a given command ID
adsp_control_status_t adsp_write_module_config(
adsp_controller_t* ctrl,
adsp_stage_control_cmd_t *cmd)
{
module_instance_t * modules = ctrl->modules;
size_t num_modules = ctrl->num_modules;
module_instance_t *module = get_module_instance(modules, cmd->instance_id, num_modules);
adsp_control_status_t ret = ADSP_CONTROL_BUSY;
// Multiple threads could be trying to read or write to this stage at the same
// time, a swlock is used to ensure that stage state changes are done atomically.
// In the case where lock contention occurs the function will return busy.
if(SWLOCK_NOT_ACQUIRED != swlock_try_acquire(&module->control.lock)) {
uint32_t offset, size;
// Get offset into the module's config structure for this command
get_control_cmd_config_offset(module, cmd->cmd_id, &offset, &size);
if(size != cmd->payload_len)
{
debug_printf("ERROR: payload_len mismatch. Expected %lu, but received %u\n", size, cmd->payload_len);
xassert(0);
}
config_rw_state_t config_state = module->control.config_rw_state;
if(config_state == config_none_pending)
{
// Receive write payload
memcpy((uint8_t*)module->control.config + offset, cmd->payload, cmd->payload_len);
module->control.cmd_id = cmd->cmd_id;
module->control.config_rw_state = config_write_pending;
ret = ADSP_CONTROL_SUCCESS;
}
else
{
debug_printf("Previous write to the config not applied by the module!! Ignoring write command.\n");
}
swlock_release(&module->control.lock);
}
return ret;
}

View File

@@ -0,0 +1,85 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/biquad.h"
void biquad_process(int32_t **input, int32_t **output, void *app_data_state)
{
biquad_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int i = 0;
do {
int32_t *in = input[i];
int32_t *out = output[i];
int j = 0;
do
{
*out++ = adsp_biquad((*in++),
state->config.filter_coeffs,
state->filter_states[i],
state->config.left_shift);
} while (++j < state->frame_size);
} while (++i < state->n_outputs);
}
void biquad_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size)
{
xassert(n_inputs == n_outputs && "Biquad should have the same number of inputs and outputs");
biquad_state_t *state = instance->state;
biquad_config_t *config = instance->control.config;
memset(state, 0, sizeof(biquad_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->filter_states = adsp_bump_allocator_malloc(allocator, _BQ_ARR_MEMORY(n_inputs)); // Allocate memory for the 1D pointers
for(int i=0; i<n_inputs; i++)
{
state->filter_states[i] = ADSP_BUMP_ALLOCATOR_DWORD_ALLIGNED_MALLOC(allocator, _BQ_FILTER_MEMORY);
memset(state->filter_states[i], 0, _BQ_FILTER_MEMORY);
}
// copy default config
memcpy(&state->config, config, sizeof(biquad_config_t));
}
void biquad_control(void *module_state, module_control_t *control)
{
biquad_state_t *state = module_state;
biquad_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Finish the write by updating the working copy with the new config
memcpy(&state->config, config, sizeof(biquad_config_t));
control->config_rw_state = config_none_pending;
// reset filter states to avoid clicks
for(int i=0; i<state->n_inputs; i++)
{
memset(state->filter_states[i], 0, _BQ_FILTER_MEMORY);
}
}
else if(control->config_rw_state == config_read_pending)
{
memcpy(config, &state->config, sizeof(biquad_config_t));
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do.
}
}

View File

@@ -0,0 +1,86 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/biquad_slew.h"
#include "dsp/biquad.h"
#include "control/biquad.h"
void biquad_slew_process(int32_t **input, int32_t **output, void *app_data_state)
{
biquad_slew_state_t *state = app_data_state;
for (int i=0; i < state->frame_size; i++){
adsp_biquad_slew_coeffs(&(state->slew_state),
state->filter_states,
state->n_outputs);
for (int j=0; j < state->n_outputs; j++){
output[j][i] = adsp_biquad(input[j][i],
state->slew_state.active_coeffs,
state->filter_states[j],
state->slew_state.lsh);
}
}
}
void biquad_slew_init(module_instance_t* instance,
adsp_bump_allocator_t* allocator,
uint8_t id,
int n_inputs,
int n_outputs,
int frame_size)
{
xassert(n_inputs == n_outputs && "Biquad should have the same number of inputs and outputs");
biquad_slew_state_t *state = instance->state;
biquad_slew_config_t *config = instance->control.config;
memset(state, 0, sizeof(biquad_slew_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->filter_states = adsp_bump_allocator_malloc(allocator, _BQ_SLEW_ARR_MEMORY(n_inputs)); // Allocate memory for the 1D pointers
for(int i=0; i<n_inputs; i++)
{
state->filter_states[i] = ADSP_BUMP_ALLOCATOR_DWORD_ALLIGNED_MALLOC(allocator, _BQ_SLEW_FILTER_MEMORY);
memset(state->filter_states[i], 0, _BQ_SLEW_FILTER_MEMORY);
}
// initialise the filter coeffs to the starting values
state->slew_state = adsp_biquad_slew_init(config->filter_coeffs, config->left_shift, config->slew_shift);
// copy default config
memcpy(&state->config, config, sizeof(biquad_slew_config_t));
}
void biquad_slew_control(void *module_state, module_control_t *control)
{
biquad_slew_state_t *state = module_state;
biquad_slew_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Update the working copy with the new config
memcpy(&state->config, config, sizeof(biquad_slew_config_t));
control->config_rw_state = config_none_pending;
adsp_biquad_slew_update_coeffs(&(state->slew_state), state->filter_states,
state->n_inputs, config->filter_coeffs,
config->left_shift);
state->slew_state.slew_shift = config->slew_shift < 1 ? 1 : config->slew_shift;
}
else if(control->config_rw_state == config_read_pending)
{
memcpy(config, &state->config, sizeof(biquad_slew_config_t));
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do.
}
}

View File

@@ -0,0 +1,28 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <stages/bump_allocator.h>
#include <xcore/assert.h>
void* adsp_bump_allocator_malloc(adsp_bump_allocator_t* allocator, size_t n_bytes) {
xassert(NULL != allocator);
if(n_bytes & 0x03) {
// A not word alligned size requested, this will break future
// allocations so ban it.
__builtin_trap();
}
if(n_bytes > allocator->n_bytes_left || NULL == allocator->buf) {
// There is not enough space left in the allocator
__builtin_trap();
}
if(0 == n_bytes) {
return NULL;
}
void* ret = allocator->buf;
allocator->buf += n_bytes;
allocator->n_bytes_left -= n_bytes;
return ret;
}

View File

@@ -0,0 +1,35 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/bypass.h"
void bypass_process(int32_t **input, int32_t **output, void *app_data_state)
{
bypass_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int i = 0;
do {
int32_t *in = input[i];
int32_t *out = output[i];
memcpy(out, in, sizeof(int32_t) * state->frame_size);
} while (++i < state->n_outputs);
}
void bypass_init(module_instance_t* module_instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == n_outputs && "Bypass should have the same number of inputs and outputs");
bypass_state_t *state =module_instance->state;
memset(state, 0, sizeof(bypass_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
}

View File

@@ -0,0 +1,84 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/cascaded_biquads.h"
void cascaded_biquads_process(int32_t **input, int32_t **output, void *app_data_state)
{
xassert(app_data_state != NULL);
cascaded_biquads_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int i = 0;
do {
int32_t *in = input[i];
int32_t *out = output[i];
int j = 0;
do {
*out++ = adsp_cascaded_biquads_8b((*in++),
state->config.filter_coeffs,
state->filter_states[i],
state->config.left_shift);
} while(++j < state->frame_size);
} while(++i < state->n_outputs);
}
void cascaded_biquads_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == n_outputs && "Cascaded biuqads should have the same number of inputs and outputs");
cascaded_biquads_state_t *state = instance->state;
cascaded_biquads_config_t *config = instance->control.config;
memset(state, 0, sizeof(cascaded_biquads_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->filter_states = adsp_bump_allocator_malloc(allocator, _CBQ_ARR_MEMORY(n_inputs)); // Allocate memory for the 1D pointers
for(int i=0; i<n_inputs; i++)
{
state->filter_states[i] = ADSP_BUMP_ALLOCATOR_DWORD_ALLIGNED_MALLOC(allocator, _CBQ_FILTER_MEMORY);
memset(state->filter_states[i], 0, CASCADED_BIQUADS_STATE_LEN * sizeof(int32_t));
}
memcpy(&state->config, config, sizeof(cascaded_biquads_config_t));
}
void cascaded_biquads_control(void *module_state, module_control_t *control)
{
xassert(module_state != NULL);
cascaded_biquads_state_t *state = module_state;
xassert(control != NULL);
cascaded_biquads_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Finish the write by updating the working copy with the new config
memcpy(&state->config, config, sizeof(cascaded_biquads_config_t));
control->config_rw_state = config_none_pending;
for(int i=0; i<state->n_inputs; i++)
{
memset(state->filter_states[i], 0, CASCADED_BIQUADS_STATE_LEN * sizeof(int32_t));
}
}
else if(control->config_rw_state == config_read_pending)
{
memcpy(config, &state->config, sizeof(cascaded_biquads_config_t));
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do.
}
}

View File

@@ -0,0 +1,90 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/cascaded_biquads_16.h"
void cascaded_biquads_16_process(int32_t **input, int32_t **output, void *app_data_state)
{
xassert(app_data_state != NULL);
cascaded_biquads_16_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int i = 0;
do {
int32_t *in = input[i];
int32_t *out = output[i];
int j = 0;
do {
// do 16 filters as 2 chunks of 8
int32_t tmp;
tmp = adsp_cascaded_biquads_8b((*in++),
state->config.filter_coeffs_lower,
state->filter_states[i],
state->config.left_shift);
*out++ = adsp_cascaded_biquads_8b(tmp,
state->config.filter_coeffs_upper,
&(state->filter_states[i][64]),
&(state->config.left_shift[8]));
} while(++j < state->frame_size);
} while(++i < state->n_outputs);
}
void cascaded_biquads_16_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == n_outputs && "Cascaded biuqads should have the same number of inputs and outputs");
cascaded_biquads_16_state_t *state = instance->state;
cascaded_biquads_16_config_t *config = instance->control.config;
memset(state, 0, sizeof(cascaded_biquads_16_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->filter_states = adsp_bump_allocator_malloc(allocator, _CBQ16_ARR_MEMORY(n_inputs)); // Allocate memory for the 1D pointers
for(int i=0; i<n_inputs; i++)
{
state->filter_states[i] = ADSP_BUMP_ALLOCATOR_DWORD_ALLIGNED_MALLOC(allocator, _CBQ16_FILTER_MEMORY);
memset(state->filter_states[i], 0, CASCADED_BIQUADS_16_STATE_LEN * sizeof(int32_t));
}
memcpy(&state->config, config, sizeof(cascaded_biquads_16_config_t));
}
void cascaded_biquads_16_control(void *module_state, module_control_t *control)
{
xassert(module_state != NULL);
cascaded_biquads_16_state_t *state = module_state;
xassert(control != NULL);
cascaded_biquads_16_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Finish the write by updating the working copy with the new config
memcpy(&state->config, config, sizeof(cascaded_biquads_16_config_t));
control->config_rw_state = config_none_pending;
for(int i=0; i<state->n_inputs; i++)
{
memset(state->filter_states[i], 0, CASCADED_BIQUADS_16_STATE_LEN * sizeof(int32_t));
}
}
else if(control->config_rw_state == config_read_pending)
{
memcpy(config, &state->config, sizeof(cascaded_biquads_16_config_t));
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do.
}
}

View File

@@ -0,0 +1,75 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/clipper.h"
void clipper_process(int32_t **input, int32_t **output, void *app_data_state)
{
xassert(app_data_state != NULL);
clipper_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int i = 0;
do {
int32_t *in = input[i];
int32_t *out = output[i];
int j = 0;
do {
*out++ = adsp_clipper(state->clip[i], *in++);
} while(++j < state->frame_size);
} while(++i < state->n_outputs);
}
void clipper_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == n_outputs && "Clipper should have the same number of inputs and outputs");
clipper_state_t *state = instance->state;
clipper_config_t *config = instance->control.config;
memset(state, 0, sizeof(clipper_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->clip = adsp_bump_allocator_malloc(allocator, CLIPPER_STAGE_REQUIRED_MEMORY(state->n_inputs));
memset(state->clip, 0, CLIPPER_STAGE_REQUIRED_MEMORY(state->n_inputs));
for (int i = 0; i < state->n_inputs; i++) {
state->clip[i] = config->threshold;
}
}
void clipper_control(void *module_state, module_control_t *control)
{
xassert(module_state != NULL);
clipper_state_t *state = module_state;
xassert(control != NULL);
clipper_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Finish the write by updating the working copy with the new config
// TODO update only the fields written by the host
for (int i = 0; i < state->n_inputs; i++) {
state->clip[i] = config->threshold;
}
control->config_rw_state = config_none_pending;
}
else if(control->config_rw_state == config_read_pending)
{
config->threshold = state->clip[0];
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do
}
}

View File

@@ -0,0 +1,100 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/compressor_rms.h"
static inline void compressor_copy_config_to_state(compressor_t *comp_state, int n_inputs, const compressor_rms_config_t *comp_config)
{
// Same config for all channels
for(int i=0; i<n_inputs; i++)
{
comp_state[i].env_det.attack_alpha = comp_config->attack_alpha;
comp_state[i].env_det.release_alpha = comp_config->release_alpha;
comp_state[i].threshold = comp_config->threshold;
comp_state[i].slope = comp_config->slope;
}
}
static inline void compressor_copy_state_to_config(compressor_rms_config_t *comp_config, const compressor_t *comp_state)
{
// Copy from channel 0 state to the config
comp_config->attack_alpha = comp_state[0].env_det.attack_alpha;
comp_config->release_alpha = comp_state[0].env_det.release_alpha;
comp_config->envelope = comp_state[0].env_det.envelope;
comp_config->gain = comp_state[0].gain;
comp_config->threshold = comp_state[0].threshold;
comp_config->slope = comp_state[0].slope;
}
void compressor_rms_process(int32_t **input, int32_t **output, void *app_data_state)
{
xassert(app_data_state != NULL);
compressor_rms_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int i = 0;
do {
int32_t *in = input[i];
int32_t *out = output[i];
int j = 0;
do {
*out++ = adsp_compressor_rms(&state->comp[i], *in++);
} while(++j < state->frame_size);
} while(++i < state->n_outputs);
}
void compressor_rms_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == n_outputs && "Compressor should have the same number of inputs and outputs");
compressor_rms_state_t *state = instance->state;
compressor_rms_config_t *config = instance->control.config;
memset(state, 0, sizeof(compressor_rms_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->comp = adsp_bump_allocator_malloc(allocator, COMPRESSOR_RMS_STAGE_REQUIRED_MEMORY(state->n_inputs));
memset(state->comp, 0, COMPRESSOR_RMS_STAGE_REQUIRED_MEMORY(state->n_inputs));
for(int i=0; i<state->n_inputs; i++)
{
state->comp[i].gain = INT32_MAX;
state->comp[i].env_det.envelope = 0;
}
compressor_copy_config_to_state(state->comp, state->n_inputs, config);
}
void compressor_rms_control(void *module_state, module_control_t *control)
{
xassert(module_state != NULL);
compressor_rms_state_t *state = module_state;
xassert(control != NULL);
compressor_rms_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Finish the write by updating the working copy with the new config
// TODO update only the fields written by the host
compressor_copy_config_to_state(state->comp, state->n_inputs, config);
control->config_rw_state = config_none_pending;
}
else if(control->config_rw_state == config_read_pending)
{
compressor_copy_state_to_config(config, state->comp);
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do
}
}

View File

@@ -0,0 +1,91 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/compressor_sidechain.h"
static inline void compressor_copy_config_to_state(compressor_t *comp_state, const compressor_sidechain_config_t *comp_config)
{
comp_state->env_det.attack_alpha = comp_config->attack_alpha;
comp_state->env_det.release_alpha = comp_config->release_alpha;
comp_state->threshold = comp_config->threshold;
comp_state->slope = comp_config->slope;
}
static inline void compressor_copy_state_to_config(compressor_sidechain_config_t *comp_config, const compressor_t *comp_state)
{
// Copy from channel 0 state to the config
comp_config->attack_alpha = comp_state->env_det.attack_alpha;
comp_config->release_alpha = comp_state->env_det.release_alpha;
comp_config->envelope = comp_state->env_det.envelope;
comp_config->gain = comp_state->gain;
comp_config->threshold = comp_state->threshold;
comp_config->slope = comp_state->slope;
}
void compressor_sidechain_process(int32_t **input, int32_t **output, void *app_data_state)
{
xassert(app_data_state != NULL);
compressor_sidechain_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int32_t *in = input[0];
int32_t *detect = input[1];
int32_t *out = output[0];
int j = 0;
do {
*out++ = adsp_compressor_rms_sidechain(&state->comp, *in++, *detect++);
} while(++j < state->frame_size);
}
void compressor_sidechain_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == 2 && "Sidechain compressor should have the 2 inputs");
xassert(n_outputs == 1 && "Sidechain compressor should have 1 output");
compressor_sidechain_state_t *state = instance->state;
compressor_sidechain_config_t *config = instance->control.config;
memset(state, 0, sizeof(compressor_sidechain_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
memset(&state->comp, 0, sizeof(compressor_t));
state->comp.gain = INT32_MAX;
state->comp.env_det.envelope = 0;
compressor_copy_config_to_state(&state->comp, config);
}
void compressor_sidechain_control(void *module_state, module_control_t *control)
{
xassert(module_state != NULL);
compressor_sidechain_state_t *state = module_state;
xassert(control != NULL);
compressor_sidechain_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
// Finish the write by updating the working copy with the new config
// TODO update only the fields written by the host
compressor_copy_config_to_state(&state->comp, config);
control->config_rw_state = config_none_pending;
}
else if(control->config_rw_state == config_read_pending)
{
compressor_copy_state_to_config(config, &state->comp);
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do
}
}

View File

@@ -0,0 +1,98 @@
// Copyright 2024-2025 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <string.h>
#include <stdlib.h>
#include <xcore/assert.h>
#include <debug_print.h>
#include "cmds.h" // Autogenerated
#include "cmd_offsets.h" // Autogenerated
#include "stages/compressor_sidechain_stereo.h"
#include "xmath/xmath.h"
static inline void compressor_stereo_copy_config_to_state(compressor_stereo_t *comp_state, const compressor_sidechain_stereo_config_t *comp_config)
{
comp_state->env_det_l.attack_alpha = comp_config->attack_alpha;
comp_state->env_det_l.release_alpha = comp_config->release_alpha;
comp_state->env_det_r.attack_alpha = comp_config->attack_alpha;
comp_state->env_det_r.release_alpha = comp_config->release_alpha;
comp_state->threshold = comp_config->threshold;
comp_state->slope = comp_config->slope;
}
static inline void compressor_stereo_copy_state_to_config(compressor_sidechain_stereo_config_t *comp_config, const compressor_stereo_t *comp_state)
{
// Copy from channel 0 state to the config
comp_config->attack_alpha = comp_state->env_det_l.attack_alpha;
comp_config->release_alpha = comp_state->env_det_l.release_alpha;
comp_config->envelope = MAX(comp_state->env_det_l.envelope, comp_state->env_det_r.envelope);
comp_config->gain = comp_state->gain;
comp_config->threshold = comp_state->threshold;
comp_config->slope = comp_state->slope;
}
void compressor_sidechain_stereo_process(int32_t **input, int32_t **output, void *app_data_state)
{
xassert(app_data_state != NULL);
compressor_sidechain_stereo_state_t *state = app_data_state;
// do while saves instructions for cases
// where the loop will always execute at
// least once
int32_t *in_l = input[0];
int32_t *in_r = input[1];
int32_t *detect_l = input[2];
int32_t *detect_r = input[3];
int32_t *out_l = output[0];
int32_t *out_r = output[1];
int j = 0;
do {
int32_t outs[2];
adsp_compressor_rms_sidechain_stereo(&state->comp, outs, *in_l++, *in_r++, *detect_l++, *detect_r++);
*out_l++ = outs[0];
*out_r++ = outs[1];
} while(++j < state->frame_size);
}
void compressor_sidechain_stereo_init(module_instance_t* instance, adsp_bump_allocator_t* allocator, uint8_t id, int n_inputs, int n_outputs, int frame_size)
{
xassert(n_inputs == 4 && "Sidechain compressor should have the 4 inputs");
xassert(n_outputs == 2 && "Sidechain compressor should have 2 outputs");
compressor_sidechain_stereo_state_t *state = instance->state;
compressor_sidechain_stereo_config_t *config = instance->control.config;
memset(state, 0, sizeof(compressor_sidechain_stereo_state_t));
state->n_inputs = n_inputs;
state->n_outputs = n_outputs;
state->frame_size = frame_size;
state->comp.gain = INT32_MAX;
state->comp.env_det_l.envelope = 0;
state->comp.env_det_r.envelope = 0;
compressor_stereo_copy_config_to_state(&state->comp, config);
}
void compressor_sidechain_stereo_control(void *module_state, module_control_t *control)
{
xassert(module_state != NULL);
compressor_sidechain_stereo_state_t *state = module_state;
xassert(control != NULL);
compressor_sidechain_stereo_config_t *config = control->config;
if(control->config_rw_state == config_write_pending)
{
compressor_stereo_copy_config_to_state(&state->comp, config);
control->config_rw_state = config_none_pending;
}
else if(control->config_rw_state == config_read_pending)
{
compressor_stereo_copy_state_to_config(config, &state->comp);
control->config_rw_state = config_read_updated;
}
else
{
// nothing to do
}
}

Some files were not shown because too many files have changed in this diff Show More