init
This commit is contained in:
16
lib_audio_dsp/lib_audio_dsp/api/control/adsp_control.h
Normal file
16
lib_audio_dsp/lib_audio_dsp/api/control/adsp_control.h
Normal 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"
|
||||
321
lib_audio_dsp/lib_audio_dsp/api/control/biquad.h
Normal file
321
lib_audio_dsp/lib_audio_dsp/api/control/biquad.h
Normal 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);
|
||||
50
lib_audio_dsp/lib_audio_dsp/api/control/cascaded_biquads.h
Normal file
50
lib_audio_dsp/lib_audio_dsp/api/control/cascaded_biquads.h
Normal 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);
|
||||
127
lib_audio_dsp/lib_audio_dsp/api/control/drc.h
Normal file
127
lib_audio_dsp/lib_audio_dsp/api/control/drc.h
Normal 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);
|
||||
332
lib_audio_dsp/lib_audio_dsp/api/control/helpers.h
Normal file
332
lib_audio_dsp/lib_audio_dsp/api/control/helpers.h
Normal 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);
|
||||
177
lib_audio_dsp/lib_audio_dsp/api/control/reverb.h
Normal file
177
lib_audio_dsp/lib_audio_dsp/api/control/reverb.h
Normal 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);
|
||||
86
lib_audio_dsp/lib_audio_dsp/api/control/reverb_plate.h
Normal file
86
lib_audio_dsp/lib_audio_dsp/api/control/reverb_plate.h
Normal 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);
|
||||
155
lib_audio_dsp/lib_audio_dsp/api/control/signal_chain.h
Normal file
155
lib_audio_dsp/lib_audio_dsp/api/control/signal_chain.h
Normal 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);
|
||||
32
lib_audio_dsp/lib_audio_dsp/api/dsp/_helpers/drc_utils.h
Normal file
32
lib_audio_dsp/lib_audio_dsp/api/dsp/_helpers/drc_utils.h
Normal 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;
|
||||
}
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/dsp/_helpers/generic_utils.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/dsp/_helpers/generic_utils.h
Normal 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;
|
||||
}
|
||||
111
lib_audio_dsp/lib_audio_dsp/api/dsp/_helpers/reverb_utils.h
Normal file
111
lib_audio_dsp/lib_audio_dsp/api/dsp/_helpers/reverb_utils.h
Normal 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;
|
||||
}
|
||||
15
lib_audio_dsp/lib_audio_dsp/api/dsp/adsp.h
Normal file
15
lib_audio_dsp/lib_audio_dsp/api/dsp/adsp.h
Normal 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"
|
||||
60
lib_audio_dsp/lib_audio_dsp/api/dsp/biquad.h
Normal file
60
lib_audio_dsp/lib_audio_dsp/api/dsp/biquad.h
Normal 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
|
||||
);
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/dsp/cascaded_biquads.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/dsp/cascaded_biquads.h
Normal 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]);
|
||||
9
lib_audio_dsp/lib_audio_dsp/api/dsp/defines.h
Normal file
9
lib_audio_dsp/lib_audio_dsp/api/dsp/defines.h
Normal 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)
|
||||
212
lib_audio_dsp/lib_audio_dsp/api/dsp/drc.h
Normal file
212
lib_audio_dsp/lib_audio_dsp/api/dsp/drc.h
Normal 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);
|
||||
106
lib_audio_dsp/lib_audio_dsp/api/dsp/fd_block_fir.h
Normal file
106
lib_audio_dsp/lib_audio_dsp/api/dsp/fd_block_fir.h
Normal 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);
|
||||
17
lib_audio_dsp/lib_audio_dsp/api/dsp/fir.h
Normal file
17
lib_audio_dsp/lib_audio_dsp/api/dsp/fir.h
Normal 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;
|
||||
30
lib_audio_dsp/lib_audio_dsp/api/dsp/graphic_eq.h
Normal file
30
lib_audio_dsp/lib_audio_dsp/api/dsp/graphic_eq.h
Normal 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]);
|
||||
249
lib_audio_dsp/lib_audio_dsp/api/dsp/reverb.h
Normal file
249
lib_audio_dsp/lib_audio_dsp/api/dsp/reverb.h
Normal 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);
|
||||
128
lib_audio_dsp/lib_audio_dsp/api/dsp/reverb_plate.h
Normal file
128
lib_audio_dsp/lib_audio_dsp/api/dsp/reverb_plate.h
Normal 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);
|
||||
264
lib_audio_dsp/lib_audio_dsp/api/dsp/signal_chain.h
Normal file
264
lib_audio_dsp/lib_audio_dsp/api/dsp/signal_chain.h
Normal 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);
|
||||
92
lib_audio_dsp/lib_audio_dsp/api/dsp/td_block_fir.h
Normal file
92
lib_audio_dsp/lib_audio_dsp/api/dsp/td_block_fir.h
Normal 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);
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/adder.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/adder.h
Normal 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);
|
||||
|
||||
|
||||
125
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_control.h
Normal file
125
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_control.h
Normal 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
|
||||
138
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_fifo.h
Normal file
138
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_fifo.h
Normal 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;
|
||||
}
|
||||
45
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_module.h
Normal file
45
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_module.h
Normal 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;
|
||||
|
||||
157
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_pipeline.h
Normal file
157
lib_audio_dsp/lib_audio_dsp/api/stages/adsp_pipeline.h
Normal 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;
|
||||
}
|
||||
40
lib_audio_dsp/lib_audio_dsp/api/stages/biquad.h
Normal file
40
lib_audio_dsp/lib_audio_dsp/api/stages/biquad.h
Normal 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);
|
||||
41
lib_audio_dsp/lib_audio_dsp/api/stages/biquad_slew.h
Normal file
41
lib_audio_dsp/lib_audio_dsp/api/stages/biquad_slew.h
Normal 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);
|
||||
40
lib_audio_dsp/lib_audio_dsp/api/stages/bump_allocator.h
Normal file
40
lib_audio_dsp/lib_audio_dsp/api/stages/bump_allocator.h
Normal 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);
|
||||
18
lib_audio_dsp/lib_audio_dsp/api/stages/bypass.h
Normal file
18
lib_audio_dsp/lib_audio_dsp/api/stages/bypass.h
Normal 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);
|
||||
34
lib_audio_dsp/lib_audio_dsp/api/stages/cascaded_biquads.h
Normal file
34
lib_audio_dsp/lib_audio_dsp/api/stages/cascaded_biquads.h
Normal 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);
|
||||
34
lib_audio_dsp/lib_audio_dsp/api/stages/cascaded_biquads_16.h
Normal file
34
lib_audio_dsp/lib_audio_dsp/api/stages/cascaded_biquads_16.h
Normal 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);
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/clipper.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/clipper.h
Normal 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);
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/compressor_rms.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/compressor_rms.h
Normal 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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/crossfader.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/crossfader.h
Normal 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);
|
||||
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/crossfader_stereo.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/crossfader_stereo.h
Normal 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);
|
||||
|
||||
28
lib_audio_dsp/lib_audio_dsp/api/stages/delay.h
Normal file
28
lib_audio_dsp/lib_audio_dsp/api/stages/delay.h
Normal 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);
|
||||
17
lib_audio_dsp/lib_audio_dsp/api/stages/dsp_thread.h
Normal file
17
lib_audio_dsp/lib_audio_dsp/api/stages/dsp_thread.h
Normal 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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
42
lib_audio_dsp/lib_audio_dsp/api/stages/fir_direct.h
Normal file
42
lib_audio_dsp/lib_audio_dsp/api/stages/fir_direct.h
Normal 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);
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/fixed_gain.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/fixed_gain.h
Normal 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);
|
||||
|
||||
21
lib_audio_dsp/lib_audio_dsp/api/stages/fork.h
Normal file
21
lib_audio_dsp/lib_audio_dsp/api/stages/fork.h
Normal 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);
|
||||
|
||||
42
lib_audio_dsp/lib_audio_dsp/api/stages/graphic_eq_10b.h
Normal file
42
lib_audio_dsp/lib_audio_dsp/api/stages/graphic_eq_10b.h
Normal 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);
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/hard_limiter_peak.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/hard_limiter_peak.h
Normal 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);
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/limiter_peak.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/limiter_peak.h
Normal 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);
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/limiter_rms.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/limiter_rms.h
Normal 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);
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/mixer.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/mixer.h
Normal 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);
|
||||
|
||||
23
lib_audio_dsp/lib_audio_dsp/api/stages/noise_gate.h
Normal file
23
lib_audio_dsp/lib_audio_dsp/api/stages/noise_gate.h
Normal 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);
|
||||
@@ -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);
|
||||
17
lib_audio_dsp/lib_audio_dsp/api/stages/pipeline.h
Normal file
17
lib_audio_dsp/lib_audio_dsp/api/stages/pipeline.h
Normal 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);
|
||||
35
lib_audio_dsp/lib_audio_dsp/api/stages/reverb_plate.h
Normal file
35
lib_audio_dsp/lib_audio_dsp/api/stages/reverb_plate.h
Normal 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);
|
||||
36
lib_audio_dsp/lib_audio_dsp/api/stages/reverb_room.h
Normal file
36
lib_audio_dsp/lib_audio_dsp/api/stages/reverb_room.h
Normal 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);
|
||||
36
lib_audio_dsp/lib_audio_dsp/api/stages/reverb_room_st.h
Normal file
36
lib_audio_dsp/lib_audio_dsp/api/stages/reverb_room_st.h
Normal 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);
|
||||
22
lib_audio_dsp/lib_audio_dsp/api/stages/subtractor.h
Normal file
22
lib_audio_dsp/lib_audio_dsp/api/stages/subtractor.h
Normal 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);
|
||||
|
||||
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/switch.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/switch.h
Normal 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);
|
||||
|
||||
30
lib_audio_dsp/lib_audio_dsp/api/stages/switch_slew.h
Normal file
30
lib_audio_dsp/lib_audio_dsp/api/stages/switch_slew.h
Normal 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);
|
||||
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/switch_stereo.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/switch_stereo.h
Normal 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);
|
||||
|
||||
24
lib_audio_dsp/lib_audio_dsp/api/stages/volume_control.h
Normal file
24
lib_audio_dsp/lib_audio_dsp/api/stages/volume_control.h
Normal 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);
|
||||
|
||||
110
lib_audio_dsp/lib_audio_dsp/lib_build_info.cmake
Normal file
110
lib_audio_dsp/lib_audio_dsp/lib_build_info.cmake
Normal 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()
|
||||
608
lib_audio_dsp/lib_audio_dsp/src/control/biquad.c
Normal file
608
lib_audio_dsp/lib_audio_dsp/src/control/biquad.c
Normal 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);
|
||||
}
|
||||
122
lib_audio_dsp/lib_audio_dsp/src/control/cascaded_biquads.c
Normal file
122
lib_audio_dsp/lib_audio_dsp/src/control/cascaded_biquads.c
Normal 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]);
|
||||
}
|
||||
}
|
||||
111
lib_audio_dsp/lib_audio_dsp/src/control/drc.c
Normal file
111
lib_audio_dsp/lib_audio_dsp/src/control/drc.c
Normal 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;
|
||||
}
|
||||
88
lib_audio_dsp/lib_audio_dsp/src/control/graphic_eq.c
Normal file
88
lib_audio_dsp/lib_audio_dsp/src/control/graphic_eq.c
Normal 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;
|
||||
}
|
||||
115
lib_audio_dsp/lib_audio_dsp/src/control/reverb.c
Normal file
115
lib_audio_dsp/lib_audio_dsp/src/control/reverb.c
Normal 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;
|
||||
}
|
||||
52
lib_audio_dsp/lib_audio_dsp/src/control/reverb_plate.c
Normal file
52
lib_audio_dsp/lib_audio_dsp/src/control/reverb_plate.c
Normal 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;
|
||||
}
|
||||
114
lib_audio_dsp/lib_audio_dsp/src/control/signal_chain.c
Normal file
114
lib_audio_dsp/lib_audio_dsp/src/control/signal_chain.c
Normal 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);
|
||||
}
|
||||
9
lib_audio_dsp/lib_audio_dsp/src/control/sqrtf_patch.S
Normal file
9
lib_audio_dsp/lib_audio_dsp/src/control/sqrtf_patch.S
Normal 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
|
||||
64
lib_audio_dsp/lib_audio_dsp/src/dsp/biquad.S
Normal file
64
lib_audio_dsp/lib_audio_dsp/src/dsp/biquad.S
Normal 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
|
||||
59
lib_audio_dsp/lib_audio_dsp/src/dsp/biquad.c
Normal file
59
lib_audio_dsp/lib_audio_dsp/src/dsp/biquad.c
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
160
lib_audio_dsp/lib_audio_dsp/src/dsp/block_fir/fd_block_fir.c
Normal file
160
lib_audio_dsp/lib_audio_dsp/src/dsp/block_fir/fd_block_fir.c
Normal 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;
|
||||
}
|
||||
12
lib_audio_dsp/lib_audio_dsp/src/dsp/block_fir/td_block_fir.c
Normal file
12
lib_audio_dsp/lib_audio_dsp/src/dsp/block_fir/td_block_fir.c
Normal 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;
|
||||
}
|
||||
|
||||
@@ -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__)
|
||||
132
lib_audio_dsp/lib_audio_dsp/src/dsp/block_fir/td_block_fir_asm.S
Normal file
132
lib_audio_dsp/lib_audio_dsp/src/dsp/block_fir/td_block_fir_asm.S
Normal 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__)
|
||||
17
lib_audio_dsp/lib_audio_dsp/src/dsp/cascaded_biquads.c
Normal file
17
lib_audio_dsp/lib_audio_dsp/src/dsp/cascaded_biquads.c
Normal 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;
|
||||
}
|
||||
113
lib_audio_dsp/lib_audio_dsp/src/dsp/compressor.c
Normal file
113
lib_audio_dsp/lib_audio_dsp/src/dsp/compressor.c
Normal 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;
|
||||
}
|
||||
116
lib_audio_dsp/lib_audio_dsp/src/dsp/env_detector.S
Normal file
116
lib_audio_dsp/lib_audio_dsp/src/dsp/env_detector.S
Normal 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
|
||||
37
lib_audio_dsp/lib_audio_dsp/src/dsp/graphic_eq.c
Normal file
37
lib_audio_dsp/lib_audio_dsp/src/dsp/graphic_eq.c
Normal 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;
|
||||
}
|
||||
70
lib_audio_dsp/lib_audio_dsp/src/dsp/limiter.c
Normal file
70
lib_audio_dsp/lib_audio_dsp/src/dsp/limiter.c
Normal 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;
|
||||
}
|
||||
24
lib_audio_dsp/lib_audio_dsp/src/dsp/noise_gate.c
Normal file
24
lib_audio_dsp/lib_audio_dsp/src/dsp/noise_gate.c
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
467
lib_audio_dsp/lib_audio_dsp/src/dsp/reverb.c
Normal file
467
lib_audio_dsp/lib_audio_dsp/src/dsp/reverb.c
Normal 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);
|
||||
}
|
||||
310
lib_audio_dsp/lib_audio_dsp/src/dsp/reverb_plate.c
Normal file
310
lib_audio_dsp/lib_audio_dsp/src/dsp/reverb_plate.c
Normal 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];
|
||||
}
|
||||
}
|
||||
196
lib_audio_dsp/lib_audio_dsp/src/dsp/signal_chain.c
Normal file
196
lib_audio_dsp/lib_audio_dsp/src/dsp/signal_chain.c
Normal 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);
|
||||
}
|
||||
36
lib_audio_dsp/lib_audio_dsp/src/stages/adder.c
Normal file
36
lib_audio_dsp/lib_audio_dsp/src/stages/adder.c
Normal 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");
|
||||
}
|
||||
|
||||
|
||||
238
lib_audio_dsp/lib_audio_dsp/src/stages/adsp_control.c
Normal file
238
lib_audio_dsp/lib_audio_dsp/src/stages/adsp_control.c
Normal 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;
|
||||
|
||||
}
|
||||
85
lib_audio_dsp/lib_audio_dsp/src/stages/biquad.c
Normal file
85
lib_audio_dsp/lib_audio_dsp/src/stages/biquad.c
Normal 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.
|
||||
}
|
||||
}
|
||||
86
lib_audio_dsp/lib_audio_dsp/src/stages/biquad_slew.c
Normal file
86
lib_audio_dsp/lib_audio_dsp/src/stages/biquad_slew.c
Normal 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.
|
||||
}
|
||||
}
|
||||
28
lib_audio_dsp/lib_audio_dsp/src/stages/bump_allocator.c
Normal file
28
lib_audio_dsp/lib_audio_dsp/src/stages/bump_allocator.c
Normal 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;
|
||||
}
|
||||
35
lib_audio_dsp/lib_audio_dsp/src/stages/bypass.c
Normal file
35
lib_audio_dsp/lib_audio_dsp/src/stages/bypass.c
Normal 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;
|
||||
}
|
||||
84
lib_audio_dsp/lib_audio_dsp/src/stages/cascaded_biquads.c
Normal file
84
lib_audio_dsp/lib_audio_dsp/src/stages/cascaded_biquads.c
Normal 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.
|
||||
}
|
||||
}
|
||||
90
lib_audio_dsp/lib_audio_dsp/src/stages/cascaded_biquads_16.c
Normal file
90
lib_audio_dsp/lib_audio_dsp/src/stages/cascaded_biquads_16.c
Normal 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.
|
||||
}
|
||||
}
|
||||
75
lib_audio_dsp/lib_audio_dsp/src/stages/clipper.c
Normal file
75
lib_audio_dsp/lib_audio_dsp/src/stages/clipper.c
Normal 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
|
||||
}
|
||||
}
|
||||
100
lib_audio_dsp/lib_audio_dsp/src/stages/compressor_rms.c
Normal file
100
lib_audio_dsp/lib_audio_dsp/src/stages/compressor_rms.c
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user