diff --git a/CMakeLists.txt b/CMakeLists.txt index 65747ffe1a8f..4a65d394519c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.13) +if(CMAKE_C_COMPILER_ID MATCHES "Clang") + add_compile_options(-mllvm --text-section-literals=false) +endif() if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") message(FATAL_ERROR " In-source builds are not supported.\n" @@ -58,7 +61,10 @@ set(CMAKE_ASM_FLAGS -DASSEMBLY) # that may be exported / installed add_library(sof_public_headers INTERFACE) -target_include_directories(sof_public_headers INTERFACE ${PROJECT_SOURCE_DIR}/src/include) +target_include_directories(sof_public_headers INTERFACE + ${PROJECT_SOURCE_DIR}/src/include + ${PROJECT_SOURCE_DIR}/../tflite-micro +) # interface library that is used only as container for sof binary options # other targets can use it to build with the same options diff --git a/src/audio/mfcc/Kconfig b/src/audio/mfcc/Kconfig index f56cadb40de2..62c998e2c52a 100644 --- a/src/audio/mfcc/Kconfig +++ b/src/audio/mfcc/Kconfig @@ -11,6 +11,7 @@ config COMP_MFCC select MATH_DECIBELS select MATH_FFT select MATH_MATRIX + select MATH_PCAN select MATH_WINDOW select NATURAL_LOGARITHM_FIXED select NUMBERS_NORM diff --git a/src/audio/mfcc/README.md b/src/audio/mfcc/README.md index 31f9c331e545..e2f348231f24 100644 --- a/src/audio/mfcc/README.md +++ b/src/audio/mfcc/README.md @@ -1,25 +1,84 @@ -# MFCC Feature Extraction Architecture +# MFCC & PCAN Feature Extraction Architecture -This directory contains the Mel-Frequency Cepstral Coefficients (MFCC) feature extractor. +This directory contains the **Mel-Frequency Cepstral Coefficients (MFCC)** feature extractor and integrated **PCAN (Per-Channel AGC Normalization)** audio pre-processing component for Sound Open Firmware (SOF). -## Overview +--- -MFCC extracts audio features commonly used as inputs for machine learning models, such as wake-word detection or speech recognition. +## 1. Overview -## Architecture Diagram +The MFCC module converts raw streaming time-domain PCM audio into compact acoustic feature representations (e.g. 13-bin cepstral coefficients, 40-bin or 80-bin Mel spectrograms) suitable for on-device machine learning models such as wake-word classifiers (TFLM, microWakeWord) and speech recognition engines (Whisper). + +When enabled, **PCAN** applies per-channel adaptive dynamic gain control and piecewise root dynamic range compression across time to suppress stationary background noise and normalize channel energy before feature quantization. + +--- + +## 2. Architecture & Data Flow ```mermaid graph LR - In[Audio Frame] --> Win[Windowing] - Win --> FFT[FFT] - FFT --> Mel[Mel Filterbank] - Mel --> DCT[DCT] - DCT --> Out[MFCC Output Features] + In[Audio PCM Frame] --> Win[Windowing: Hamming/Hann] + Win --> FFT[FFT: 512-pt] + FFT --> Mel[Mel Filterbank: 40/80 Bins] + Mel --> PCAN[PCAN Gain Normalization] + PCAN --> Log[Log Scaling / dB] + Log --> VAD[VAD / DTX Silence Gating] + Log --> DCT[DCT: Cepstral Matrix] + DCT --> Lifter[Cepstral Lifter] + Lifter --> Out[MFCC Output Features] +``` + +--- + +## 3. Dependencies + +| Dependency | Purpose | +|---|---| +| **West Manifest (`west.yml`)** | Manages workspace dependencies including `tflite-micro` | +| **`tflite-micro`** | Provides Google's upstream Apache-2.0 `microfrontend` PCAN library (`pcan_gain_control.c`, `noise_reduction.c`) | +| **GNU Octave ($\ge 5.0$)** | Required for topology configuration generation (`tune/setup_mfcc.m`) and reference vector export | +| **CMake ($\ge 3.13$)** | SOF build system | +| **Zephyr SDK / Xtensa XCC** | DSP cross-compilation toolchains for Cadence HiFi3 / HiFi4 SIMD kernels | +| **CMocka** | Host unit testing framework | + +--- + +## 4. Build & Tuning Instructions + +### 4.1 Synchronize Dependencies via West +```bash +west update ``` -## Configuration and Scripts +### 4.2 Build Host Unit Tests +```bash +# Configure unit tests +cmake -S sof -B build_ut -DBUILD_UNIT_TESTS=ON -DBUILD_UNIT_TESTS_HOST=ON -DINIT_CONFIG=unit_test_defconfig + +# Build all math and MFCC tests +cmake --build build_ut --target pcan +ctest --test-dir build_ut -R "pcan|auditory|dct|matrix|window|fft" --output-on-failure +``` + +### 4.3 Build Target DSP Firmware +```bash +west build -b intel_adsp_ace15_mtpm app +``` + +### 4.4 Generate Topology Configurations (Octave) +The tuning script generates binary configuration blobs and ALSA topology configurations: +```bash +cd src/audio/mfcc/tune +octave-cli --eval "setup_mfcc" +``` +Available presets generated: +- `ceps13_compress_dtx.conf`: 13-bin cepstral features with VAD and DTX silence suppression. +- `mel80_compress.conf`: 80-bin linear Mel spectrogram features. +- `mel80_compress_dtx.conf`: 80-bin Mel spectrogram with DTX gating. +- `mel80_pcan_compress.conf`: 80-bin Mel spectrogram with Google PCAN dynamic AGC normalization. + +--- + +## 5. Configuration Options -- **Kconfig**: Enables the MFCC component (`COMP_MFCC`) which selects necessary math libraries (`MATH_FFT`, `MATH_DCT`, `MATH_16BIT_MEL_FILTERBANK`, etc.). Depends on `COMP_MODULE_ADAPTER`. -- **CMakeLists.txt**: Compiles generic, common, and HIFI implementations (`mfcc_hifi3.c`, `mfcc_hifi4.c`). Provides support for Zephyr loadable extensions (`llext`). -- **mfcc.toml**: Specifies the topology configuration for the MFCC module (UUID, affinity, memory parameters, and pin formats). -- **Topology (.conf)**: Derived from `tools/topology/topology2/include/components/mfcc.conf`, configuring a `mfcc` widget object of type `effect` with UUID `73:a7:10:db:a4:1a:ea:4c:a2:1f:2d:57:a5:c9:82:eb`. +- **`CONFIG_COMP_MFCC`**: Enables the MFCC component and automatically selects required math libraries (`CONFIG_MATH_FFT`, `CONFIG_MATH_AUDITORY`, `CONFIG_MATH_DCT`, `CONFIG_MATH_PCAN`, `CONFIG_MATH_WINDOW`). +- **`CONFIG_COMP_MODULE_ADAPTER`**: Module adapter infrastructure for SOF processing components. diff --git a/src/audio/mfcc/mfcc_common.c b/src/audio/mfcc/mfcc_common.c index 3890671165fd..d087d43a3a53 100644 --- a/src/audio/mfcc/mfcc_common.c +++ b/src/audio/mfcc/mfcc_common.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -355,6 +356,11 @@ int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd) psy_apply_mel_filterbank_32(&state->melfb, fft->fft_out, state->power_spectra, state->mel_log_32, mel_scale_shift); + if (state->pcan.enable_pcan) { + pcan_update_noise_estimate(&state->pcan, (const uint32_t *)state->mel_log_32); + pcan_apply(&state->pcan, (uint32_t *)state->mel_log_32); + } + if (state->mel_only) { /* In Mel-only mode output Mel log spectra directly */ cc_count += state->dct.num_in; diff --git a/src/audio/mfcc/mfcc_setup.c b/src/audio/mfcc/mfcc_setup.c index 1d062b2a5206..2406160ef3e7 100644 --- a/src/audio/mfcc/mfcc_setup.c +++ b/src/audio/mfcc/mfcc_setup.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -459,9 +460,63 @@ int mfcc_setup(struct processing_module *mod, int max_frames, int sample_rate, i } } + if (config->enable_pcan) { + struct pcan_config pcfg; + uint32_t *pcan_noise; + int16_t *pcan_lut; + + pcfg.strength = (config->pcan_strength > 0) ? + ((float)config->pcan_strength / 32768.0f) : 0.95f; + pcfg.offset = (config->pcan_offset > 0) ? + ((float)config->pcan_offset / 128.0f) : 80.0f; + pcfg.gain_bits = (config->pcan_gain_bits > 0) ? + config->pcan_gain_bits : 21; + pcfg.smoothing_coef = (config->pcan_smoothing_coef > 0) ? + (uint16_t)config->pcan_smoothing_coef : 819; + pcfg.smoothing_bits = 10; + pcfg.input_correction_bits = 0; + pcfg.enable_pcan = true; + + pcan_noise = mod_zalloc(mod, config->num_mel_bins * sizeof(uint32_t)); + if (!pcan_noise) { + comp_err(dev, "Failed PCAN noise estimate alloc"); + ret = -ENOMEM; + goto free_vad; + } + + pcan_lut = mod_zalloc(mod, PCAN_LUT_SIZE * sizeof(int16_t)); + if (!pcan_lut) { + comp_err(dev, "Failed PCAN gain LUT alloc"); + mod_free(mod, pcan_noise); + ret = -ENOMEM; + goto free_vad; + } + + ret = pcan_populate_state(&pcfg, &state->pcan, pcan_noise, pcan_lut, + config->num_mel_bins, 10, 0); + if (ret < 0) { + comp_err(dev, "Failed PCAN state init"); + mod_free(mod, pcan_noise); + mod_free(mod, pcan_lut); + goto free_vad; + } + } else { + state->pcan.enable_pcan = false; + state->pcan.noise_estimate = NULL; + state->pcan.gain_lut = NULL; + } + comp_dbg(dev, "done"); return 0; +free_vad: + if (config->enable_vad) { + mod_free(mod, cd->vad.noise_floor); + mod_free(mod, cd->vad.weights); + cd->vad.noise_floor = NULL; + cd->vad.weights = NULL; + } + free_out_stage: mod_free(mod, state->out_stage); @@ -528,6 +583,8 @@ void mfcc_free_buffers(struct processing_module *mod) mfcc_free_and_null(mod, (void **)&cd->state.dct.matrix); mfcc_free_and_null(mod, (void **)&cd->state.lifter.matrix); mfcc_free_and_null(mod, (void **)&cd->state.out_stage); + mfcc_free_and_null(mod, (void **)&cd->state.pcan.noise_estimate); + mfcc_free_and_null(mod, (void **)&cd->state.pcan.gain_lut); mfcc_free_and_null(mod, (void **)&cd->vad.noise_floor); mfcc_free_and_null(mod, (void **)&cd->vad.weights); } diff --git a/src/audio/mfcc/tune/setup_mfcc.m b/src/audio/mfcc/tune/setup_mfcc.m index dbf69587a74f..ecc918f8631c 100644 --- a/src/audio/mfcc/tune/setup_mfcc.m +++ b/src/audio/mfcc/tune/setup_mfcc.m @@ -51,6 +51,13 @@ function setup_mfcc() setup.tplg_fn = 'ceps13_compress_dtx.conf'; export_mfcc_setup(gen_cfg, setup); + % Mel spectrogram with PCAN normalization and compress PCM output + setup = get_mel_spectrogram_config(); + setup.enable_pcan = true; + setup.compress_output = true; + setup.tplg_fn = 'mel80_pcan_compress.conf'; + export_mfcc_setup(gen_cfg, setup); + end function cfg = get_mfcc_default_config() @@ -94,6 +101,11 @@ function setup_mfcc() cfg.dtx_silence_hops_interval = 0; cfg.update_controls = false; cfg.compress_output = false; + cfg.enable_pcan = false; + cfg.pcan_strength = 0.95; + cfg.pcan_offset = 80.0; + cfg.pcan_gain_bits = 21; + cfg.pcan_smoothing_coef = 819; end function cfg = get_mel_spectrogram_config() @@ -113,7 +125,7 @@ function setup_mfcc() cfg.num_mel_bins = 80; cfg.preemphasis_coefficient = 0; cfg.raw_energy = false; - cfg.remove_dc_offset = false; + cfg.remove_dc_offset = true; cfg.round_to_power_of_two = true; cfg.sample_frequency = 16000; cfg.snip_edges = true; @@ -123,20 +135,25 @@ function setup_mfcc() cfg.vtln_low = 0; cfg.vtln_warp = 1.0; cfg.window_type = 'hann'; - cfg.mel_log = 'log10'; + cfg.mel_log = 'log'; % Set to 'db' for librosa, set to 'log10' for matlab cfg.pmin = 1e-10; - cfg.top_db = 8; % applied for log10, would be 80 dB clamp for decibels as 10*log10() - cfg.mel_offset = 4.0; % For whisper like Mel scale and normalize - cfg.mel_scale = 0.25; % For whisper like Mel scale and normalize - cfg.mmax_init = 0; % Initial value max Mel value, data clamp is mmax - top_db - cfg.mmax_coef = 0; % Dynamic max Mel value decay coefficient (zero lock to found max) - cfg.dynamic_mmax = true; - cfg.enable_vad = true; + cfg.top_db = 80.0; + cfg.mel_offset = 4.0; % Whisper: (mel + 4.0) * 0.25 + cfg.mel_scale = 0.25; + cfg.mmax_init = 0; + cfg.mmax_coef = 0.005; % Whisper mmax tracking: slow decay + cfg.dynamic_mmax = false; + cfg.enable_vad = false; cfg.enable_dtx = false; cfg.dtx_trailing_silence_hops = 0; cfg.dtx_silence_hops_interval = 0; - cfg.update_controls = true; + cfg.update_controls = false; cfg.compress_output = false; + cfg.enable_pcan = false; + cfg.pcan_strength = 0.95; + cfg.pcan_offset = 80.0; + cfg.pcan_gain_bits = 21; + cfg.pcan_smoothing_coef = 819; end function export_mfcc_setup(gen_cfg, cfg) @@ -173,8 +190,9 @@ function export_mfcc_setup(gen_cfg, cfg) v = cfg.dtx_trailing_silence_hops; [b8, j] = add_w16b(v, b8, j); % DTX trailing silence hops v = cfg.dtx_silence_hops_interval; [b8, j] = add_w16b(v, b8, j); % DTX silence frame interval +v = cfg.pcan_smoothing_coef; [b8, j] = add_w32b(v, b8, j); % PCAN smoothing coef in Q14 % Reserved -for i = 1:5 +for i = 1:4 [b8, j] = add_w32b(0, b8, j); end @@ -200,10 +218,9 @@ function export_mfcc_setup(gen_cfg, cfg) v = 0; [b8, j] = add_w16b(v, b8, j); % vtln_high Qx.y TBD v = 0; [b8, j] = add_w16b(v, b8, j); % vtln_low Qx.y TBD v = 0; [b8, j] = add_w16b(v, b8, j); % vtln_warp Qx.y TBD -% reserved16[3] -for i = 1:3 - [b8, j] = add_w16b(0, b8, j); -end +v = q_convert(cfg.pcan_strength, 15); [b8, j] = add_w16b(v, b8, j); % PCAN strength in Q1.15 +v = q_convert(cfg.pcan_offset, 7); [b8, j] = add_w16b(v, b8, j); % PCAN offset in Q8.7 +v = cfg.pcan_gain_bits; [b8, j] = add_w16b(v, b8, j); % PCAN gain bits v = cfg.htk_compat; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.raw_energy; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.remove_dc_offset; [b8, j] = add_w8b(v, b8, j); % bool @@ -216,8 +233,9 @@ function export_mfcc_setup(gen_cfg, cfg) v = cfg.enable_dtx; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.update_controls; [b8, j] = add_w8b(v, b8, j); % bool v = cfg.compress_output; [b8, j] = add_w8b(v, b8, j); % bool -% reserved_bool[4] -for i = 1:4 +v = cfg.enable_pcan; [b8, j] = add_w8b(v, b8, j); % bool +% reserved_bool[3] +for i = 1:3 [b8, j] = add_w8b(0, b8, j); end diff --git a/src/include/sof/audio/mfcc/mfcc_comp.h b/src/include/sof/audio/mfcc/mfcc_comp.h index 885339004fc0..3afd4ddf5136 100644 --- a/src/include/sof/audio/mfcc/mfcc_comp.h +++ b/src/include/sof/audio/mfcc/mfcc_comp.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -99,6 +100,7 @@ struct mfcc_state { struct mfcc_fft fft; /**< FFT related */ struct dct_plan_16 dct; /**< DCT related */ struct psy_mel_filterbank melfb; /**< Mel filter bank */ + struct pcan_state pcan; /**< PCAN state */ struct mfcc_cepstral_lifter lifter; /**< Cepstral lifter coefficients */ struct mat_matrix_16b *mel_spectra; /**< Pointer to scratch */ struct mat_matrix_16b *cepstral_coef; /**< Pointer to scratch */ diff --git a/src/include/sof/math/pcan.h b/src/include/sof/math/pcan.h new file mode 100644 index 000000000000..0617f64d479d --- /dev/null +++ b/src/include/sof/math/pcan.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. All rights reserved. + * + * Author: Antigravity AI & SOF Team + */ + +/** + * \file include/sof/math/pcan.h + * \brief Per-Channel AGC Normalization (PCAN) library interface + * + * Wraps and integrates Google's upstream TFLite Micro microfrontend + * library (under Apache-2.0 with patent grant protection). + */ + +#ifndef __SOF_MATH_PCAN_H__ +#define __SOF_MATH_PCAN_H__ + +#include +#include +#include + +#include "tensorflow/lite/experimental/microfrontend/lib/bits.h" +#include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h" +#include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h" +#include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h" +#include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h" + +#define PCAN_SNR_BITS kPcanSnrBits /* 12 */ +#define PCAN_OUTPUT_BITS kPcanOutputBits /* 6 */ +#define PCAN_SMOOTHING_COEF_BITS kNoiseReductionBits /* 14 */ +#define PCAN_WIDE_DYNAMIC_BITS kWideDynamicFunctionBits /* 32 */ +#define PCAN_LUT_SIZE kWideDynamicFunctionLUTSize /* 125 */ + +/* Aliases for Google Microfrontend Types */ +typedef struct PcanGainControlConfig pcan_config_t; +typedef struct PcanGainControlState pcan_state_t; +typedef struct NoiseReductionConfig noise_reduction_config_t; +typedef struct NoiseReductionState noise_reduction_state_t; + +/** + * \brief SOF PCAN configuration structure. + */ +struct pcan_config { + float strength; /**< Exponent alpha (e.g. 0.95) */ + float offset; /**< Additive offset delta (e.g. 80.0) */ + int32_t gain_bits; /**< Gain bit shift (e.g. 21) */ + uint16_t smoothing_coef; /**< IIR smoothing coefficient in Q14 (e.g. 819 for 0.05) */ + uint16_t smoothing_bits; /**< Bit shift for noise estimate (e.g. 10) */ + int32_t input_correction_bits; /**< Input correction bits shift (e.g. 0) */ + bool enable_pcan; /**< Enable flag */ +}; + +/** + * \brief SOF PCAN runtime state wrapper. + */ +struct pcan_state { + struct PcanGainControlState g_pcan; /**< Google upstream PCAN state */ + struct NoiseReductionState g_noise; /**< Google upstream Noise Reduction state */ + uint32_t *noise_estimate; /**< Pointer to noise estimate buffer */ + int16_t *gain_lut; /**< Pointer to gain LUT */ + int num_channels; /**< Number of channels */ + int32_t snr_shift; /**< SNR bit shift */ + uint16_t smoothing_coef; /**< IIR smoothing coef in Q14 */ + uint16_t one_minus_smoothing_coef; /**< (1 << 14) - smoothing_coef */ + uint16_t smoothing_bits; /**< Smoothing bits */ + bool enable_pcan; /**< PCAN enabled */ + bool allocated; /**< Internally allocated buffers */ +}; + +/** + * \brief Evaluate Google's WideDynamicFunction. + */ +static inline int16_t pcan_wide_dynamic_function(uint32_t x, const int16_t *lut) +{ + return WideDynamicFunction(x, lut); +} + +/** + * \brief Evaluate Google's PcanShrink. + */ +static inline uint32_t pcan_shrink(uint32_t x) +{ + return PcanShrink(x); +} + +/** + * \brief Compute single point in continuous PCAN gain curve. + */ +int16_t pcan_gain_lookup_function(float strength, float offset, int32_t gain_bits, + int32_t input_bits, uint32_t x); + +/** + * \brief Fill 125-entry gain lookup table for WideDynamicFunction. + */ +int pcan_compute_lut(float strength, float offset, int32_t gain_bits, + int32_t input_bits, int16_t *gain_lut); + +/** + * \brief Initialize PCAN state with supplied buffers or allocate them. + */ +int pcan_populate_state(const struct pcan_config *config, struct pcan_state *state, + uint32_t *noise_estimate_buffer, int16_t *gain_lut_buffer, + int num_channels, uint16_t smoothing_bits, + int32_t input_correction_bits); + +/** + * \brief Free allocated resources in PCAN state. + */ +void pcan_free_state(struct pcan_state *state); + +/** + * \brief Reset PCAN temporal state. + */ +void pcan_reset(struct pcan_state *state); + +/** + * \brief Update per-channel temporal noise estimate. + */ +void pcan_update_noise_estimate(struct pcan_state *state, const uint32_t *signal); + +/** + * \brief Apply PCAN gain control and compression using Google's PcanGainControlApply. + */ +static inline void pcan_apply(struct pcan_state *state, uint32_t *signal) +{ + if (state && state->enable_pcan) + PcanGainControlApply(&state->g_pcan, signal); +} + +#endif /* __SOF_MATH_PCAN_H__ */ diff --git a/src/include/user/mfcc.h b/src/include/user/mfcc.h index 286ee4f5e985..e8793811509b 100644 --- a/src/include/user/mfcc.h +++ b/src/include/user/mfcc.h @@ -56,7 +56,8 @@ struct sof_mfcc_config { int16_t mmax_coef; /**< Q1.15 decay coefficient for dynamic mmax, a small value for slow */ uint16_t dtx_trailing_silence_hops; /**< DTX: number of silence hops to send after speech, 0 = send first only */ uint16_t dtx_silence_hops_interval; /**< DTX: send silence frame every Nth hop during VAD=0, 0 = disable */ - uint32_t reserved[5]; + uint32_t pcan_smoothing_coef; /**< Q14 smoothing coef for PCAN, e.g. 819 for 0.05 */ + uint32_t reserved[4]; int32_t sample_frequency; /**< Hz. e.g. 16000 */ int32_t pmin; /**< Q1.31 linear power, limit minimum Mel energy, e.g. 1e-9 */ enum sof_mfcc_mel_log_type mel_log; /**< Use MEL_LOG_IS_LOG, LOG10 or DB*/ @@ -79,7 +80,9 @@ struct sof_mfcc_config { int16_t vtln_high; /**< Reserved, no support */ int16_t vtln_low; /**< Reserved, no support */ int16_t vtln_warp; /**< Reserved, no support */ - int16_t reserved16[3]; /**< Reserved for future 16-bit fields, set to 0 */ + int16_t pcan_strength; /**< Q1.15 strength alpha, e.g. 31130 for 0.95 */ + int16_t pcan_offset; /**< Q8.7 offset delta, e.g. 10240 for 80.0 */ + int16_t pcan_gain_bits; /**< Gain bits scale, e.g. 21 */ bool htk_compat; /**< Must be false */ bool raw_energy; /**< Reserved, no support */ bool remove_dc_offset; /**< Reserved, no support */ @@ -92,7 +95,8 @@ struct sof_mfcc_config { bool enable_dtx; /**< Discontinuous transmission: suppress silence after trailing frames */ bool update_controls; /**< Update controls with VAD decision */ bool compress_output; /**< Use compress PCM output: variable size, no zero padding */ - bool reserved_bool[4]; /* Reserved for future boolean flags, set to false (0) */ + bool enable_pcan; /**< Enable PCAN normalization on Mel filterbank energies */ + bool reserved_bool[3]; /* Reserved for future boolean flags, set to false (0) */ } __attribute__((packed)); #endif /* __USER_MFCC_H__ */ diff --git a/src/math/CMakeLists.txt b/src/math/CMakeLists.txt index a52263006295..4f950725e3d5 100644 --- a/src/math/CMakeLists.txt +++ b/src/math/CMakeLists.txt @@ -83,6 +83,10 @@ if(CONFIG_MATH_AUDITORY) add_subdirectory(auditory) endif() +if(CONFIG_MATH_PCAN) + add_subdirectory(pcan) +endif() + if(CONFIG_MATH_DCT) list(APPEND base_files dct.c) endif() diff --git a/src/math/Kconfig b/src/math/Kconfig index 1feaab8f03d1..3b962696cb32 100644 --- a/src/math/Kconfig +++ b/src/math/Kconfig @@ -287,6 +287,14 @@ config MATH_32BIT_MEL_FILTERBANK endmenu +config MATH_PCAN + bool "PCAN (Per-Channel AGC Normalization) library" + default n + help + Select this to build PCAN (Per-Channel Automatic Gain Control Normalization) + library. PCAN performs dynamic per-channel gain control and piecewise polynomial + root compression across time to suppress stationary noise and enhance acoustic transients. + config MATH_DCT bool "DCT transform library" default n diff --git a/src/math/pcan/CMakeLists.txt b/src/math/pcan/CMakeLists.txt new file mode 100644 index 000000000000..e9bf3b3b0c85 --- /dev/null +++ b/src/math/pcan/CMakeLists.txt @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT DEFINED TFLM_PATH) + if(EXISTS "${sof_top_dir}/../tflite-micro") + set(TFLM_PATH "${sof_top_dir}/../tflite-micro") + elseif(EXISTS "${PROJECT_SOURCE_DIR}/../tflite-micro") + set(TFLM_PATH "${PROJECT_SOURCE_DIR}/../tflite-micro") + elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../../../tflite-micro") + set(TFLM_PATH "${CMAKE_CURRENT_LIST_DIR}/../../../../tflite-micro") + endif() +endif() + +set(MICROFRONTEND_DIR ${TFLM_PATH}/tensorflow/lite/experimental/microfrontend/lib) + +set(base_files + pcan.c + ${MICROFRONTEND_DIR}/pcan_gain_control.c + ${MICROFRONTEND_DIR}/pcan_gain_control_util.c + ${MICROFRONTEND_DIR}/noise_reduction.c + ${MICROFRONTEND_DIR}/noise_reduction_util.c +) + +is_zephyr(zephyr) +if(zephyr) ### Zephyr ### + + zephyr_include_directories(${TFLM_PATH}) + zephyr_library_sources( + ${base_files} + ) + +else() ### library, e.g. testbench or plugin ### + + include_directories(${TFLM_PATH}) + add_local_sources(sof ${base_files}) + +endif() diff --git a/src/math/pcan/README.md b/src/math/pcan/README.md new file mode 100644 index 000000000000..854425aca295 --- /dev/null +++ b/src/math/pcan/README.md @@ -0,0 +1,106 @@ +# Per-Channel AGC Normalization (PCAN) Math Library + +This directory contains the **PCAN (Per-Channel Automatic Gain Control Normalization)** library for Sound Open Firmware (SOF), integrating Google's upstream `microfrontend` library from [TFLite Micro](https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/experimental/microfrontend/lib) under the **Apache 2.0 license** with express patent grant protection. + +--- + +## 1. Overview & Mathematical Formulation + +PCAN applies per-channel adaptive dynamic gain control and root dynamic range compression across time to linear filterbank energies (e.g. Mel spectrogram bins), suppressing stationary background noise while enhancing transient acoustic events (speech onsets, keywords). + +``` + +-------------------------------------------------------------+ + | Input Filterbank Energy Matrix | + | E_in[channel, frame] | + +-------------------------------------------------------------+ + | + v ++---------------------------------------------------------------------------------------------+ +| PCAN Processing Stages | +| | +| 1. Temporal Noise Estimation (IIR Smoothing): | +| E_est[k] = ((E_in[k] << smoothing_bits)*alpha_s + E_est[k]*(1 - alpha_s)) >> 14 | +| | +| 2. Piecewise Quadratic Octave Gain Lookup: | +| gain[k] = WideDynamicFunction(E_est[k], gain_lut) | +| - Fast single-cycle CLZ/MSB (AE_NSAU on Tensilica HiFi) | +| - 10-bit fractional index quadratic interpolation | +| | +| 3. Dynamic Gain & Root Polynomial Compression: | +| SNR[k] = ((uint64_t)E_in[k] * gain[k]) >> snr_shift | +| E_out[k] = PcanShrink(SNR[k]) | +| - If SNR[k] < 8192 (2 << 12): SNR[k]^2 >> 20 | +| - If SNR[k] >= 8192: (SNR[k] >> 6) - 64 | ++---------------------------------------------------------------------------------------------+ + | + v + +-------------------------------------------------------------+ + | PCAN-Normalized Feature Tensor | + | E_out[channel, frame] | + +-------------------------------------------------------------+ +``` + +--- + +## 2. Dependencies + +The PCAN math library requires the following tools and repositories: + +| Dependency | Required Version | Description | +|---|---|---| +| **West Manifest (`west.yml`)** | $\ge 0.13$ | Pulls `tflite-micro` workspace dependency via `west update` | +| **TFLite-Micro** | Git revision `e86d97b6` | Upstream Google microfrontend C source files (`pcan_gain_control.c`, `noise_reduction.c`, `bits.h`) | +| **CMake** | $\ge 3.13$ | SOF build and configuration system | +| **Host Toolchain** | GCC $\ge 9.0$ / Clang $\ge 11.0$ | For building host testbench and CMocka unit tests | +| **DSP Toolchains** | Zephyr SDK / Xtensa XCC / Clang | For building target firmware with HiFi3 / HiFi4 / HiFi5 intrinsics | +| **CMocka** | Built with SOF | Unit testing framework for bit-exact validation | +| **GNU Octave** | $\ge 5.0$ | Reference modeling and golden test vector generation | + +--- + +## 3. Build & Test Instructions + +### 3.1 Synchronize Workspace via West +Ensure `tflite-micro` is pulled and synchronized in your workspace: +```bash +cd /path/to/sof-workspace +west update +``` + +### 3.2 Build and Run CMocka Unit Tests (Host) +Configure and run the 5-phase PCAN unit test suite on the host: +```bash +# Configure unit test build +cmake -S sof -B build_ut -DBUILD_UNIT_TESTS=ON -DBUILD_UNIT_TESTS_HOST=ON -DINIT_CONFIG=unit_test_defconfig + +# Build PCAN unit test binary +cmake --build build_ut --target pcan + +# Run PCAN test executable directly +./build_ut/test/cmocka/src/math/pcan/pcan + +# Or run via CTest with full math regression +ctest --test-dir build_ut -R "pcan|auditory|dct|matrix|window|fft" --output-on-failure +``` + +### 3.3 Regenerate Octave Golden Reference Vectors +To re-generate or verify golden test headers from the Octave reference suite: +```bash +cd test/cmocka/src/math/pcan +octave-cli --eval "ref_pcan" +``` +This updates `ref_pcan_lut.h`, `ref_pcan_func.h`, `ref_pcan_stream.h`, and `ref_pcan_corners.h`. + +### 3.4 Build Target Firmware (Zephyr / SOF) +When building SOF for a target DSP platform (e.g. Intel ACE15 / Meteor Lake / Arrow Lake): +```bash +west build -b intel_adsp_ace15_mtpm app +``` + +--- + +## 4. Kconfig & CMake Options + +- `CONFIG_MATH_PCAN`: Enables building the PCAN math library and links Google's microfrontend C source files. +- `CONFIG_PCAN_HIFI3` / `CONFIG_PCAN_HIFI4`: Enables Tensilica HiFi DSP assembly/SIMD optimizations. +- `CONFIG_COMP_MFCC`: Automatically selects `CONFIG_MATH_PCAN` when MFCC feature extraction is enabled. diff --git a/src/math/pcan/pcan.c b/src/math/pcan/pcan.c new file mode 100644 index 000000000000..61e8428df66e --- /dev/null +++ b/src/math/pcan/pcan.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Antigravity AI & SOF Team + +#include +#include +#include +#include +#include + +int16_t pcan_gain_lookup_function(float strength, float offset, int32_t gain_bits, + int32_t input_bits, uint32_t x) +{ + struct PcanGainControlConfig config; + config.strength = strength; + config.offset = offset; + config.gain_bits = gain_bits; + return PcanGainLookupFunction(&config, input_bits, x); +} + +int pcan_compute_lut(float strength, float offset, int32_t gain_bits, + int32_t input_bits, int16_t *gain_lut) +{ + struct PcanGainControlConfig config; + struct PcanGainControlState state; + int ret; + + if (!gain_lut) + return -EINVAL; + + config.enable_pcan = 1; + config.strength = strength; + config.offset = offset; + config.gain_bits = gain_bits; + + ret = PcanGainControlPopulateState(&config, &state, NULL, 1, + (uint16_t)input_bits, 0); + if (!ret) + return -EINVAL; + + memcpy(gain_lut, state.gain_lut, PCAN_LUT_SIZE * sizeof(int16_t)); + PcanGainControlFreeStateContents(&state); + return 0; +} + +int pcan_populate_state(const struct pcan_config *config, struct pcan_state *state, + uint32_t *noise_estimate_buffer, int16_t *gain_lut_buffer, + int num_channels, uint16_t smoothing_bits, + int32_t input_correction_bits) +{ + struct PcanGainControlConfig g_config; + int ret; + + if (!config || !state || num_channels <= 0) + return -EINVAL; + + memset(state, 0, sizeof(*state)); + state->enable_pcan = config->enable_pcan; + if (!state->enable_pcan) + return 0; + + state->num_channels = num_channels; + state->smoothing_bits = smoothing_bits; + state->smoothing_coef = (config->smoothing_coef > 0) ? config->smoothing_coef : 819; + state->one_minus_smoothing_coef = (1 << PCAN_SMOOTHING_COEF_BITS) - state->smoothing_coef; + state->snr_shift = config->gain_bits - input_correction_bits - PCAN_SNR_BITS; + if (state->snr_shift < 0) + return -EINVAL; + + if (noise_estimate_buffer) { + state->noise_estimate = noise_estimate_buffer; + } else { + state->noise_estimate = malloc(num_channels * sizeof(uint32_t)); + if (!state->noise_estimate) + return -ENOMEM; + state->allocated = true; + } + memset(state->noise_estimate, 0, num_channels * sizeof(uint32_t)); + + g_config.enable_pcan = 1; + g_config.strength = config->strength; + g_config.offset = config->offset; + g_config.gain_bits = config->gain_bits; + + if (gain_lut_buffer) { + /* Use caller-supplied buffer */ + state->gain_lut = gain_lut_buffer; + ret = pcan_compute_lut(config->strength, config->offset, config->gain_bits, + (int32_t)smoothing_bits - input_correction_bits, + gain_lut_buffer); + if (ret < 0) { + pcan_free_state(state); + return ret; + } + state->g_pcan.enable_pcan = 1; + state->g_pcan.noise_estimate = state->noise_estimate; + state->g_pcan.num_channels = num_channels; + state->g_pcan.gain_lut = gain_lut_buffer; + state->g_pcan.snr_shift = state->snr_shift; + } else { + /* Use Google's internal allocator */ + ret = PcanGainControlPopulateState(&g_config, &state->g_pcan, + state->noise_estimate, + num_channels, smoothing_bits, + input_correction_bits); + if (!ret) { + pcan_free_state(state); + return -EINVAL; + } + state->gain_lut = state->g_pcan.gain_lut; + } + + return 0; +} + +void pcan_free_state(struct pcan_state *state) +{ + if (!state) + return; + + if (!state->gain_lut && state->g_pcan.gain_lut) + PcanGainControlFreeStateContents(&state->g_pcan); + else if (state->g_pcan.gain_lut && state->gain_lut == state->g_pcan.gain_lut && state->allocated) + PcanGainControlFreeStateContents(&state->g_pcan); + + if (state->allocated && state->noise_estimate) { + free(state->noise_estimate); + state->noise_estimate = NULL; + } + + memset(state, 0, sizeof(*state)); +} + +void pcan_reset(struct pcan_state *state) +{ + if (!state || !state->noise_estimate) + return; + + memset(state->noise_estimate, 0, state->num_channels * sizeof(uint32_t)); +} + +void pcan_update_noise_estimate(struct pcan_state *state, const uint32_t *signal) +{ + int i; + const uint32_t smoothing = state->smoothing_coef; + const uint32_t one_minus_smoothing = state->one_minus_smoothing_coef; + const int smoothing_bits = state->smoothing_bits; + + for (i = 0; i < state->num_channels; ++i) { + const uint32_t signal_scaled_up = signal[i] << smoothing_bits; + const uint32_t estimate = + (uint32_t)((((uint64_t)signal_scaled_up * smoothing) + + ((uint64_t)state->noise_estimate[i] * one_minus_smoothing)) >> + PCAN_SMOOTHING_COEF_BITS); + state->noise_estimate[i] = estimate; + } +} diff --git a/src/math/pcan/pcan_hifi3.c b/src/math/pcan/pcan_hifi3.c new file mode 100644 index 000000000000..52bddea1e794 --- /dev/null +++ b/src/math/pcan/pcan_hifi3.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Antigravity AI & SOF Team + +#include +#include +#include + +#if defined(PCAN_HIFI3) + +#include + +int16_t pcan_wide_dynamic_function(uint32_t x, const int16_t *lut) +{ + if (x <= 2) + return lut[x]; + + /* HiFi fast leading zero count (MSB = 32 - clz) */ + ae_int32 x_reg = (ae_int32)x; + int clz = AE_NSAU(x_reg); + int interval = 32 - clz; + const int16_t *base_lut = lut + (4 * interval - 6); + + int16_t frac; + if (interval < 11) + frac = (int16_t)((x << (11 - interval)) & 0x3FF); + else + frac = (int16_t)((x >> (interval - 11)) & 0x3FF); + + int32_t result = ((int32_t)base_lut[2] * frac) >> 5; + result += (int32_t)((uint32_t)base_lut[1] << 5); + result *= frac; + result = (result + (1 << 14)) >> 15; + result += base_lut[0]; + return (int16_t)result; +} + +uint32_t pcan_shrink(uint32_t x) +{ + if (x < (2 << PCAN_SNR_BITS)) { + return (uint32_t)(((uint64_t)x * x) >> (2 + 2 * PCAN_SNR_BITS - PCAN_OUTPUT_BITS)); + } else { + return (x >> (PCAN_SNR_BITS - PCAN_OUTPUT_BITS)) - (1 << PCAN_OUTPUT_BITS); + } +} + +void pcan_update_noise_estimate(struct pcan_state *state, const uint32_t *signal) +{ + int i; + const uint32_t smoothing = state->smoothing_coef; + const uint32_t one_minus_smoothing = state->one_minus_smoothing_coef; + const int smoothing_bits = state->smoothing_bits; + + for (i = 0; i < state->num_channels; ++i) { + const uint32_t signal_scaled_up = signal[i] << smoothing_bits; + const uint32_t estimate = + (uint32_t)((((uint64_t)signal_scaled_up * smoothing) + + ((uint64_t)state->noise_estimate[i] * one_minus_smoothing)) >> + PCAN_SMOOTHING_COEF_BITS); + state->noise_estimate[i] = estimate; + } +} + +void pcan_apply(struct pcan_state *state, uint32_t *signal) +{ + int i; + for (i = 0; i < state->num_channels; ++i) { + const uint32_t gain = (uint32_t)(uint16_t)pcan_wide_dynamic_function(state->noise_estimate[i], state->gain_lut); + const uint32_t snr = (uint32_t)(((uint64_t)signal[i] * gain) >> state->snr_shift); + signal[i] = pcan_shrink(snr); + } +} + +#endif /* PCAN_HIFI3 */ diff --git a/src/math/pcan/pcan_hifi4.c b/src/math/pcan/pcan_hifi4.c new file mode 100644 index 000000000000..390d4433ec7a --- /dev/null +++ b/src/math/pcan/pcan_hifi4.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Antigravity AI & SOF Team + +#include +#include +#include + +#if defined(PCAN_HIFI4) || defined(PCAN_HIFI5) + +#if XCHAL_HAVE_HIFI5 +#include +#else +#include +#endif + +int16_t pcan_wide_dynamic_function(uint32_t x, const int16_t *lut) +{ + if (x <= 2) + return lut[x]; + + /* HiFi4 fast single-cycle CLZ/MSB */ + ae_int32 x_reg = (ae_int32)x; + int clz = AE_NSAU(x_reg); + int interval = 32 - clz; + const int16_t *base_lut = lut + (4 * interval - 6); + + int16_t frac; + if (interval < 11) + frac = (int16_t)((x << (11 - interval)) & 0x3FF); + else + frac = (int16_t)((x >> (interval - 11)) & 0x3FF); + + int32_t result = ((int32_t)base_lut[2] * frac) >> 5; + result += (int32_t)((uint32_t)base_lut[1] << 5); + result *= frac; + result = (result + (1 << 14)) >> 15; + result += base_lut[0]; + return (int16_t)result; +} + +uint32_t pcan_shrink(uint32_t x) +{ + if (x < (2 << PCAN_SNR_BITS)) { + return (uint32_t)(((uint64_t)x * x) >> (2 + 2 * PCAN_SNR_BITS - PCAN_OUTPUT_BITS)); + } else { + return (x >> (PCAN_SNR_BITS - PCAN_OUTPUT_BITS)) - (1 << PCAN_OUTPUT_BITS); + } +} + +void pcan_update_noise_estimate(struct pcan_state *state, const uint32_t *signal) +{ + int i; + const uint32_t smoothing = state->smoothing_coef; + const uint32_t one_minus_smoothing = state->one_minus_smoothing_coef; + const int smoothing_bits = state->smoothing_bits; + + for (i = 0; i < state->num_channels; ++i) { + const uint32_t signal_scaled_up = signal[i] << smoothing_bits; + const uint32_t estimate = + (uint32_t)((((uint64_t)signal_scaled_up * smoothing) + + ((uint64_t)state->noise_estimate[i] * one_minus_smoothing)) >> + PCAN_SMOOTHING_COEF_BITS); + state->noise_estimate[i] = estimate; + } +} + +void pcan_apply(struct pcan_state *state, uint32_t *signal) +{ + int i; + for (i = 0; i < state->num_channels; ++i) { + const uint32_t gain = (uint32_t)(uint16_t)pcan_wide_dynamic_function(state->noise_estimate[i], state->gain_lut); + const uint32_t snr = (uint32_t)(((uint64_t)signal[i] * gain) >> state->snr_shift); + signal[i] = pcan_shrink(snr); + } +} + +#endif /* PCAN_HIFI4 || PCAN_HIFI5 */ diff --git a/test/cmocka/src/math/CMakeLists.txt b/test/cmocka/src/math/CMakeLists.txt index 83180953eb55..4fb5c75fb62c 100644 --- a/test/cmocka/src/math/CMakeLists.txt +++ b/test/cmocka/src/math/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(window) add_subdirectory(matrix) add_subdirectory(auditory) add_subdirectory(dct) +add_subdirectory(pcan) diff --git a/test/cmocka/src/math/pcan/CMakeLists.txt b/test/cmocka/src/math/pcan/CMakeLists.txt new file mode 100644 index 000000000000..f6367669f44c --- /dev/null +++ b/test/cmocka/src/math/pcan/CMakeLists.txt @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT DEFINED TFLM_PATH) + if(EXISTS "${sof_top_dir}/../tflite-micro") + set(TFLM_PATH "${sof_top_dir}/../tflite-micro") + elseif(EXISTS "${PROJECT_SOURCE_DIR}/../tflite-micro") + set(TFLM_PATH "${PROJECT_SOURCE_DIR}/../tflite-micro") + elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../../../../../tflite-micro") + set(TFLM_PATH "${CMAKE_CURRENT_LIST_DIR}/../../../../../../tflite-micro") + endif() +endif() + +set(MICROFRONTEND_DIR ${TFLM_PATH}/tensorflow/lite/experimental/microfrontend/lib) + +include_directories(${TFLM_PATH}) + +cmocka_test(pcan + pcan_test.c + ${PROJECT_SOURCE_DIR}/src/math/pcan/pcan.c + ${MICROFRONTEND_DIR}/pcan_gain_control.c + ${MICROFRONTEND_DIR}/pcan_gain_control_util.c + ${MICROFRONTEND_DIR}/noise_reduction.c + ${MICROFRONTEND_DIR}/noise_reduction_util.c +) diff --git a/test/cmocka/src/math/pcan/pcan_gain_lookup.m b/test/cmocka/src/math/pcan/pcan_gain_lookup.m new file mode 100644 index 000000000000..68fc83f246b6 --- /dev/null +++ b/test/cmocka/src/math/pcan/pcan_gain_lookup.m @@ -0,0 +1,77 @@ +% pcan_gain_lookup - Compute PCAN continuous gain and generate LUT +% +% SPDX-License-Identifier: BSD-3-Clause +% Copyright(c) 2026 Intel Corporation. All rights reserved. + +function [gain_lut, y_lut] = pcan_gain_lookup(config, input_bits) +% Inputs: +% config.strength - Exponent alpha (e.g., 0.95) +% config.offset - Additive constant delta (e.g., 80.0) +% config.gain_bits - Scale factor exponent (e.g., 21) +% input_bits - smoothing_bits - input_correction_bits +% +% Outputs: +% gain_lut - 125-entry int16 lookup table for WideDynamicFunction +% y_lut - Raw function values at octave evaluation points + + if nargin < 2 + input_bits = 10; + end + + strength = config.strength; + offset = config.offset; + gain_bits = config.gain_bits; + + kWideDynamicFunctionBits = 32; + kWideDynamicFunctionLUTSize = 4 * kWideDynamicFunctionBits - 3; % 125 + + gain_lut = zeros(kWideDynamicFunctionLUTSize, 1, 'int16'); + + % Evaluate point x in gain function + function y = eval_gain(x_val) + x_float = double(x_val) / double(bitshift(uint64(1), input_bits)); + g_float = double(bitshift(uint64(1), gain_bits)) * ((x_float + offset) ^ (-strength)); + if g_float > 32767 + y = int16(32767); + else + y = int16(round(g_float)); + end + end + + gain_lut(1) = eval_gain(0); + gain_lut(2) = eval_gain(1); + + % Intervals 2 through 32 + % In C: lut is offset by -6 so that interval 2 writes to lut[4*2]=lut[8] -> offset 2 in array + for interval = 2:kWideDynamicFunctionBits + x0 = bitshift(uint64(1), interval - 1); + x1 = x0 + bitshift(x0, -1); + if interval == kWideDynamicFunctionBits + x2 = x0 + (x0 - 1); + else + x2 = 2 * x0; + end + + y0 = int32(eval_gain(x0)); + y1 = int32(eval_gain(x1)); + y2 = int32(eval_gain(x2)); + + diff1 = y1 - y0; + diff2 = y2 - y0; + a1 = 4 * diff1 - diff2; + a2 = diff2 - a1; + + % Map to 1-based index in gain_lut: + % In C: index is 4 * interval - 6 (0-based) -> +1 for 1-based + idx = 4 * interval - 5; + + gain_lut(idx) = int16(y0); + gain_lut(idx + 1) = int16(a1); + gain_lut(idx + 2) = int16(a2); + if interval < kWideDynamicFunctionBits + gain_lut(idx + 3) = int16(0); + end + end + + y_lut = gain_lut; +end diff --git a/test/cmocka/src/math/pcan/pcan_noise_estimate.m b/test/cmocka/src/math/pcan/pcan_noise_estimate.m new file mode 100644 index 000000000000..b9f340de52df --- /dev/null +++ b/test/cmocka/src/math/pcan/pcan_noise_estimate.m @@ -0,0 +1,37 @@ +% pcan_noise_estimate - Octave fixed-point model of PCAN temporal IIR noise smoothing +% +% SPDX-License-Identifier: BSD-3-Clause +% Copyright(c) 2026 Intel Corporation. All rights reserved. + +function [estimate_out] = pcan_noise_estimate(estimate_in, signal_in, smoothing_coef, smoothing_bits, coef_bits) +% Inputs: +% estimate_in - Previous noise estimate vector (uint32) +% signal_in - Current frame input energy vector (uint32) +% smoothing_coef - IIR smoothing factor in Q(coef_bits) (default 819 for 0.05 in Q14) +% smoothing_bits - Scale shift for input energy (default 10) +% coef_bits - Number of fractional bits in smoothing_coef (default 14) +% +% Output: +% estimate_out - Updated noise estimate vector (uint32) + + if nargin < 3 + smoothing_coef = 819; % ~0.05 in Q14 (16384 * 0.05 = 819.2) + end + if nargin < 4 + smoothing_bits = 10; + end + if nargin < 5 + coef_bits = 14; + end + + one_minus_coef = bitshift(1, coef_bits) - smoothing_coef; + num_channels = length(signal_in); + estimate_out = zeros(num_channels, 1, 'uint32'); + + for i = 1:num_channels + sig_scaled = bitshift(uint64(signal_in(i)), smoothing_bits); + est_prev = uint64(estimate_in(i)); + est_new = bitshift((sig_scaled * uint64(smoothing_coef)) + (est_prev * uint64(one_minus_coef)), -coef_bits); + estimate_out(i) = uint32(est_new); + end +end diff --git a/test/cmocka/src/math/pcan/pcan_process.m b/test/cmocka/src/math/pcan/pcan_process.m new file mode 100644 index 000000000000..d853c117da36 --- /dev/null +++ b/test/cmocka/src/math/pcan/pcan_process.m @@ -0,0 +1,55 @@ +% pcan_process - Octave model of full PCAN frame processing +% +% SPDX-License-Identifier: BSD-3-Clause +% Copyright(c) 2026 Intel Corporation. All rights reserved. + +function [output_frames, final_noise_estimate] = pcan_process(input_frames, config, smoothing_bits, input_correction_bits) +% Inputs: +% input_frames - [num_channels x num_frames] matrix of uint32 filterbank energies +% config.strength - Exponent alpha (e.g. 0.95) +% config.offset - Additive constant delta (e.g. 80.0) +% config.gain_bits - Gain bit shift (e.g. 21) +% config.smoothing_coef - Smoothing factor in Q14 (e.g. 819) +% smoothing_bits - Smoothing bit shift (e.g. 10) +% input_correction_bits - Input correction shift (e.g. 0) +% +% Outputs: +% output_frames - [num_channels x num_frames] matrix of uint32 PCAN normalized outputs +% final_noise_estimate - [num_channels x 1] final noise estimate state + + if nargin < 3 + smoothing_bits = 10; + end + if nargin < 4 + input_correction_bits = 0; + end + + [num_channels, num_frames] = size(input_frames); + input_bits = smoothing_bits - input_correction_bits; + kPcanSnrBits = 12; + snr_shift = config.gain_bits - input_correction_bits - kPcanSnrBits; + + [gain_lut, ~] = pcan_gain_lookup(config, input_bits); + + noise_estimate = zeros(num_channels, 1, 'uint32'); + output_frames = zeros(num_channels, num_frames, 'uint32'); + + for f = 1:num_frames + sig_in = input_frames(:, f); + + % 1. Update temporal noise estimate + noise_estimate = pcan_noise_estimate(noise_estimate, sig_in, config.smoothing_coef, smoothing_bits, 14); + + % 2. Apply PCAN gain control per channel + frame_out = zeros(num_channels, 1, 'uint32'); + for c = 1:num_channels + gain = pcan_wide_dynamic_func(noise_estimate(c), gain_lut); + snr = bitshift(uint64(sig_in(c)) * uint64(uint16(gain)), -snr_shift); + frame_out(c) = pcan_shrink(uint32(snr), 12, 6); + end + + output_frames(:, f) = frame_out; + end + + final_noise_estimate = noise_estimate; +end diff --git a/test/cmocka/src/math/pcan/pcan_shrink.m b/test/cmocka/src/math/pcan/pcan_shrink.m new file mode 100644 index 000000000000..2ebd98736cc9 --- /dev/null +++ b/test/cmocka/src/math/pcan/pcan_shrink.m @@ -0,0 +1,41 @@ +% pcan_shrink - Octave fixed-point model of PcanShrink piecewise compression +% +% SPDX-License-Identifier: BSD-3-Clause +% Copyright(c) 2026 Intel Corporation. All rights reserved. + +function y = pcan_shrink(x, snr_bits, output_bits) +% Inputs: +% x - 32-bit unsigned SNR input value (scalar or vector) +% snr_bits - Number of fractional bits in SNR (default 12) +% output_bits - Number of fractional bits in output (default 6) +% +% Output: +% y - 32-bit unsigned compressed output value + + if nargin < 2 + snr_bits = 12; + end + if nargin < 3 + output_bits = 6; + end + + threshold = bitshift(uint64(2), snr_bits); % 2 << 12 = 8192 + quadratic_shift = -(2 + 2 * snr_bits - output_bits); % -20 + linear_shift = -(snr_bits - output_bits); % -6 + linear_offset = uint64(bitshift(1, output_bits)); % 64 + + y = zeros(size(x), 'uint32'); + + for k = 1:numel(x) + val = uint64(x(k)); + if val < threshold + % Quadratic compression: x^2 / 4 + prod_val = val * val; + res = bitshift(prod_val, quadratic_shift); + else + % Linear compression: x - 1 + res = bitshift(val, linear_shift) - linear_offset; + end + y(k) = uint32(res); + end +end diff --git a/test/cmocka/src/math/pcan/pcan_test.c b/test/cmocka/src/math/pcan/pcan_test.c new file mode 100644 index 000000000000..583833ad7308 --- /dev/null +++ b/test/cmocka/src/math/pcan/pcan_test.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. All rights reserved. +// +// Author: Antigravity AI & SOF Team + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ref_pcan_lut.h" +#include "ref_pcan_func.h" +#include "ref_pcan_stream.h" +#include "ref_pcan_corners.h" + +/** + * \brief Phase 1: Test LUT Generation against Octave reference + */ +static void test_pcan_lut_generation(void **state) +{ + (void)state; + int16_t lut[PCAN_LUT_SIZE]; + int ret; + int i; + + /* Test Configuration 1 (Default TFLM settings) */ + ret = pcan_compute_lut(0.95f, 80.0f, 21, 10, lut); + assert_int_equal(ret, 0); + + for (i = 0; i < PCAN_TEST_LUT1_SIZE; i++) { + assert_int_equal(lut[i], ref_pcan_lut1[i]); + } + + /* Test Configuration 2 (Custom settings) */ + ret = pcan_compute_lut(0.8f, 50.0f, 18, 12, lut); + assert_int_equal(ret, 0); + + for (i = 0; i < PCAN_TEST_LUT2_SIZE; i++) { + assert_int_equal(lut[i], ref_pcan_lut2[i]); + } +} + +/** + * \brief Phase 2: Test WideDynamicFunction quadratic interpolation against Octave reference + */ +static void test_pcan_wide_dynamic_function(void **state) +{ + (void)state; + int16_t lut[PCAN_LUT_SIZE]; + int16_t out; + int ret; + int i; + + ret = pcan_compute_lut(0.95f, 80.0f, 21, 10, lut); + assert_int_equal(ret, 0); + + for (i = 0; i < PCAN_TEST_WDF_NUM_POINTS; i++) { + out = pcan_wide_dynamic_function(ref_pcan_wdf_inputs[i], lut); + assert_int_equal(out, ref_pcan_wdf_outputs[i]); + } +} + +/** + * \brief Phase 3: Test PcanShrink piecewise compression against Octave reference + */ +static void test_pcan_shrink(void **state) +{ + (void)state; + uint32_t out; + int i; + + for (i = 0; i < PCAN_TEST_SHRINK_NUM_POINTS; i++) { + out = pcan_shrink(ref_pcan_shrink_inputs[i]); + assert_int_equal(out, ref_pcan_shrink_outputs[i]); + } +} + +/** + * \brief Phase 4: Test Full Streaming Multi-Channel Processing & IIR Noise Estimation + */ +static void test_pcan_streaming(void **state) +{ + (void)state; + struct pcan_config cfg; + struct pcan_state pstate; + uint32_t channel_data[PCAN_STREAM_NUM_CHANNELS]; + int ret; + int f; + int c; + + cfg.strength = 0.95f; + cfg.offset = 80.0f; + cfg.gain_bits = 21; + cfg.smoothing_coef = PCAN_STREAM_SMOOTHING_COEF; + cfg.smoothing_bits = PCAN_STREAM_SMOOTHING_BITS; + cfg.input_correction_bits = PCAN_STREAM_INPUT_CORRECTION_BITS; + cfg.enable_pcan = true; + + ret = pcan_populate_state(&cfg, &pstate, NULL, NULL, + PCAN_STREAM_NUM_CHANNELS, + PCAN_STREAM_SMOOTHING_BITS, + PCAN_STREAM_INPUT_CORRECTION_BITS); + assert_int_equal(ret, 0); + + /* Stream frame-by-frame and check outputs */ + for (f = 0; f < PCAN_STREAM_NUM_FRAMES; f++) { + for (c = 0; c < PCAN_STREAM_NUM_CHANNELS; c++) { + channel_data[c] = ref_pcan_stream_inputs[f * PCAN_STREAM_NUM_CHANNELS + c]; + } + + pcan_update_noise_estimate(&pstate, channel_data); + pcan_apply(&pstate, channel_data); + + for (c = 0; c < PCAN_STREAM_NUM_CHANNELS; c++) { + uint32_t expected = ref_pcan_stream_outputs[f * PCAN_STREAM_NUM_CHANNELS + c]; + assert_int_equal(channel_data[c], expected); + } + } + + /* Verify final noise estimate vector */ + for (c = 0; c < PCAN_STREAM_NUM_CHANNELS; c++) { + assert_int_equal(pstate.noise_estimate[c], ref_pcan_stream_final_noise[c]); + } + + /* Test reset functionality */ + pcan_reset(&pstate); + for (c = 0; c < PCAN_STREAM_NUM_CHANNELS; c++) { + assert_int_equal(pstate.noise_estimate[c], 0); + } + + pcan_free_state(&pstate); +} + +/** + * \brief Phase 5: Test Corner and Boundary Cases + */ +static void test_pcan_corner_cases(void **state) +{ + (void)state; + int16_t lut[PCAN_LUT_SIZE]; + struct pcan_config bad_cfg; + struct pcan_state bad_state; + int16_t wdf_out; + uint32_t shrink_out; + int ret; + int i; + + ret = pcan_compute_lut(0.95f, 80.0f, 21, 10, lut); + assert_int_equal(ret, 0); + + /* Test numerical corner cases */ + for (i = 0; i < PCAN_CORNERS_NUM_POINTS; i++) { + wdf_out = pcan_wide_dynamic_function(ref_pcan_corner_inputs[i], lut); + assert_int_equal(wdf_out, ref_pcan_corner_wdf_outputs[i]); + + shrink_out = pcan_shrink(ref_pcan_corner_inputs[i]); + assert_int_equal(shrink_out, ref_pcan_corner_shrink_outputs[i]); + } + + /* Test invalid configuration parameters */ + bad_cfg.strength = 0.95f; + bad_cfg.offset = 80.0f; + bad_cfg.gain_bits = 5; /* Too small -> negative snr_shift */ + bad_cfg.smoothing_coef = 819; + bad_cfg.smoothing_bits = 10; + bad_cfg.input_correction_bits = 0; + bad_cfg.enable_pcan = true; + + ret = pcan_populate_state(&bad_cfg, &bad_state, NULL, NULL, 16, 10, 0); + assert_true(ret < 0); + + ret = pcan_populate_state(NULL, &bad_state, NULL, NULL, 16, 10, 0); + assert_true(ret < 0); + + ret = pcan_populate_state(&bad_cfg, &bad_state, NULL, NULL, 0, 10, 0); + assert_true(ret < 0); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_pcan_lut_generation), + cmocka_unit_test(test_pcan_wide_dynamic_function), + cmocka_unit_test(test_pcan_shrink), + cmocka_unit_test(test_pcan_streaming), + cmocka_unit_test(test_pcan_corner_cases), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/cmocka/src/math/pcan/pcan_wide_dynamic_func.m b/test/cmocka/src/math/pcan/pcan_wide_dynamic_func.m new file mode 100644 index 000000000000..1ecf32041efb --- /dev/null +++ b/test/cmocka/src/math/pcan/pcan_wide_dynamic_func.m @@ -0,0 +1,56 @@ +% pcan_wide_dynamic_func - Octave fixed-point model of WideDynamicFunction +% +% SPDX-License-Identifier: BSD-3-Clause +% Copyright(c) 2026 Intel Corporation. All rights reserved. + +function y = pcan_wide_dynamic_func(x, gain_lut) +% Inputs: +% x - 32-bit unsigned input value (scalar or vector) +% gain_lut - 125-entry int16 lookup table from pcan_gain_lookup +% +% Output: +% y - 16-bit signed interpolated gain factor (same shape as x) + + y = zeros(size(x), 'int16'); + + for k = 1:numel(x) + val = uint32(x(k)); + if val <= 2 + % Directly return lut[0], lut[1], or lut[2] + y(k) = gain_lut(val + 1); + else + % Compute MSB (1 to 32) + clz_val = 0; + tmp = val; + for b = 31:-1:0 + if bitand(tmp, bitshift(uint32(1), b)) ~= 0 + break; + end + clz_val = clz_val + 1; + end + interval = 32 - clz_val; + + % In C: lut pointer is offset by (4 * interval - 6) + % 1-based index for lut[0]: + idx = 4 * interval - 5; + l0 = int32(gain_lut(idx)); + l1 = int32(gain_lut(idx + 1)); + l2 = int32(gain_lut(idx + 2)); + + if interval < 11 + frac = bitand(bitshift(val, 11 - interval), uint32(1023)); % 0x3FF + else + frac = bitand(bitshift(val, -(interval - 11)), uint32(1023)); + end + frac_i32 = int32(frac); + + res = bitshift(l2 * frac_i32, -5); + res = res + bitshift(l1, 5); + res = res * frac_i32; + res = bitshift(res + 16384, -15); % (1 << 14) = 16384 + res = res + l0; + + y(k) = int16(res); + end + end +end diff --git a/test/cmocka/src/math/pcan/ref_pcan.m b/test/cmocka/src/math/pcan/ref_pcan.m new file mode 100644 index 000000000000..865d52757840 --- /dev/null +++ b/test/cmocka/src/math/pcan/ref_pcan.m @@ -0,0 +1,212 @@ +% ref_pcan - Generate C header files for PCAN library unit tests +% +% SPDX-License-Identifier: BSD-3-Clause +% Copyright(c) 2026 Intel Corporation. All rights reserved. + +function ref_pcan() + % Add path to cmocka helper export functions + path(path(), '../../../m'); + + try + opt.describe = export_get_git_describe(); + catch + opt.describe = 'SOF PCAN Reference Generator'; + end + + %% Phase 1: Export Golden LUTs for default and custom configurations + export_ref_lut(opt); + + %% Phase 2 & 3: Export Golden values for WideDynamicFunction and PcanShrink + export_ref_functions(opt); + + %% Phase 4: Export Streaming Multi-Channel Multi-Frame Test Vectors + export_ref_streaming(opt); + + %% Phase 5: Export Corner & Edge Cases + export_ref_corner_cases(opt); + + fprintf(1, 'All PCAN test headers exported successfully.\n'); +end + +function export_ref_lut(opt) + header_fn = 'ref_pcan_lut.h'; + fh = export_headerfile_open(header_fn); + export_comment(fh, sprintf('Generated by ref_pcan.m (%s)', opt.describe)); + + % Config 1: Default TFLM configuration + cfg1.strength = 0.95; + cfg1.offset = 80.0; + cfg1.gain_bits = 21; + input_bits1 = 10; + [lut1, ~] = pcan_gain_lookup(cfg1, input_bits1); + + export_ndefine(fh, 'PCAN_TEST_LUT1_SIZE', length(lut1)); + export_ndefine(fh, 'PCAN_TEST_LUT1_GAIN_BITS', cfg1.gain_bits); + export_ndefine(fh, 'PCAN_TEST_LUT1_INPUT_BITS', input_bits1); + export_vector(fh, 16, 'ref_pcan_lut1', lut1); + + % Config 2: Custom configuration (strength=0.8, offset=50.0, gain_bits=18) + cfg2.strength = 0.8; + cfg2.offset = 50.0; + cfg2.gain_bits = 18; + input_bits2 = 12; + [lut2, ~] = pcan_gain_lookup(cfg2, input_bits2); + + export_ndefine(fh, 'PCAN_TEST_LUT2_SIZE', length(lut2)); + export_ndefine(fh, 'PCAN_TEST_LUT2_GAIN_BITS', cfg2.gain_bits); + export_ndefine(fh, 'PCAN_TEST_LUT2_INPUT_BITS', input_bits2); + export_vector(fh, 16, 'ref_pcan_lut2', lut2); + + fclose(fh); + fprintf(1, 'Exported %s.\n', header_fn); +end + +function export_ref_functions(opt) + header_fn = 'ref_pcan_func.h'; + fh = export_headerfile_open(header_fn); + export_comment(fh, sprintf('Generated by ref_pcan.m (%s)', opt.describe)); + + cfg.strength = 0.95; + cfg.offset = 80.0; + cfg.gain_bits = 21; + input_bits = 10; + [lut, ~] = pcan_gain_lookup(cfg, input_bits); + + % 1. WideDynamicFunction Test Vector: Test various points across all 32 intervals + wdf_inputs = uint32([ ... + 0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128, ... + 255, 256, 511, 512, 1023, 1024, 2047, 2048, 4095, 4096, 8191, 8192, ... + 16383, 16384, 32767, 32768, 65535, 65536, 131071, 131072, ... + 262143, 262144, 524287, 524288, 1048575, 1048576, 2097151, 2097152, ... + 4194303, 4194304, 8388607, 8388608, 16777215, 16777216, ... + 33554431, 33554432, 67108863, 67108864, 134217727, 134217728, ... + 268435455, 268435456, 536870911, 536870912, 1073741823, 1073741824, ... + 2147483647, 2147483648, 4294967295 ... + ]); + wdf_outputs = pcan_wide_dynamic_func(wdf_inputs, lut); + + export_ndefine(fh, 'PCAN_TEST_WDF_NUM_POINTS', length(wdf_inputs)); + export_uint32_vector(fh, 'ref_pcan_wdf_inputs', wdf_inputs); + export_vector(fh, 16, 'ref_pcan_wdf_outputs', wdf_outputs); + + % 2. PcanShrink Test Vector: Sweep below, around (8192), and above threshold + shrink_inputs = uint32([ ... + 0, 1, 10, 100, 500, 1000, 2000, 4000, 8000, 8190, 8191, 8192, 8193, 8194, ... + 10000, 16384, 32768, 65536, 100000, 500000, 1000000, 10000000 ... + ]); + shrink_outputs = pcan_shrink(shrink_inputs, 12, 6); + + export_ndefine(fh, 'PCAN_TEST_SHRINK_NUM_POINTS', length(shrink_inputs)); + export_uint32_vector(fh, 'ref_pcan_shrink_inputs', shrink_inputs); + export_uint32_vector(fh, 'ref_pcan_shrink_outputs', shrink_outputs); + + fclose(fh); + fprintf(1, 'Exported %s.\n', header_fn); +end + +function export_ref_streaming(opt) + header_fn = 'ref_pcan_stream.h'; + fh = export_headerfile_open(header_fn); + export_comment(fh, sprintf('Generated by ref_pcan.m (%s)', opt.describe)); + + cfg.strength = 0.95; + cfg.offset = 80.0; + cfg.gain_bits = 21; + cfg.smoothing_coef = 819; % 0.05 in Q14 + smoothing_bits = 10; + input_correction_bits = 0; + + num_channels = 16; + num_frames = 20; + + % Create deterministic synthetic test signal: + % Baseline background noise + mid-stream bursts + rng(42); + input_matrix = zeros(num_channels, num_frames, 'uint32'); + for f = 1:num_frames + for c = 1:num_channels + noise = 50 + mod(c * 17 + f * 23, 100); + if f >= 5 && f <= 12 && c >= 4 && c <= 10 + speech_burst = 2000 + (c * 150); + else + speech_burst = 0; + end + input_matrix(c, f) = uint32(noise + speech_burst); + end + end + + [output_matrix, final_noise] = pcan_process(input_matrix, cfg, smoothing_bits, input_correction_bits); + + export_ndefine(fh, 'PCAN_STREAM_NUM_CHANNELS', num_channels); + export_ndefine(fh, 'PCAN_STREAM_NUM_FRAMES', num_frames); + export_ndefine(fh, 'PCAN_STREAM_SMOOTHING_COEF', cfg.smoothing_coef); + export_ndefine(fh, 'PCAN_STREAM_SMOOTHING_BITS', smoothing_bits); + export_ndefine(fh, 'PCAN_STREAM_INPUT_CORRECTION_BITS', input_correction_bits); + + input_linear = reshape(input_matrix, [], 1); + output_linear = reshape(output_matrix, [], 1); + + export_uint32_vector(fh, 'ref_pcan_stream_inputs', input_linear); + export_uint32_vector(fh, 'ref_pcan_stream_outputs', output_linear); + export_uint32_vector(fh, 'ref_pcan_stream_final_noise', final_noise); + + fclose(fh); + fprintf(1, 'Exported %s.\n', header_fn); +end + +function export_ref_corner_cases(opt) + header_fn = 'ref_pcan_corners.h'; + fh = export_headerfile_open(header_fn); + export_comment(fh, sprintf('Generated by ref_pcan.m (%s)', opt.describe)); + + cfg.strength = 0.95; + cfg.offset = 80.0; + cfg.gain_bits = 21; + input_bits = 10; + [lut, ~] = pcan_gain_lookup(cfg, input_bits); + + % Corner case points: zeros, boundaries, transitions, overflows + corner_inputs = uint32([ ... + 0, ... + 1, ... + 2, ... + 3, ... + 4, ... + 2047, 2048, 2049, ... + 8191, 8192, 8193, ... + 65535, 65536, 65537, ... + 1073741823, 1073741824, 1073741825, ... + 2147483647, 2147483648, 2147483649, ... + 4294967294, 4294967295 ... + ]); + + wdf_corner_outs = pcan_wide_dynamic_func(corner_inputs, lut); + shrink_corner_outs = pcan_shrink(corner_inputs, 12, 6); + + export_ndefine(fh, 'PCAN_CORNERS_NUM_POINTS', length(corner_inputs)); + export_uint32_vector(fh, 'ref_pcan_corner_inputs', corner_inputs); + export_vector(fh, 16, 'ref_pcan_corner_wdf_outputs', wdf_corner_outs); + export_uint32_vector(fh, 'ref_pcan_corner_shrink_outputs', shrink_corner_outs); + + fclose(fh); + fprintf(1, 'Exported %s.\n', header_fn); +end + +function export_uint32_vector(fh, vname, data) + columns = 6; + rows = ceil(length(data) / columns); + fprintf(fh, '\nstatic const uint32_t %s[%d] = {\n', vname, length(data)); + i = 1; + for j = 1:rows + fprintf(fh, '\t%11uU,', data(i)); + i = i + 1; + for k = 2:columns + if i <= length(data) + fprintf(fh, ' %11uU,', data(i)); + i = i + 1; + end + end + fprintf(fh, '\n'); + end + fprintf(fh, '};\n'); +end diff --git a/test/cmocka/src/math/pcan/ref_pcan_corners.h b/test/cmocka/src/math/pcan/ref_pcan_corners.h new file mode 100644 index 000000000000..8fa1852b4222 --- /dev/null +++ b/test/cmocka/src/math/pcan/ref_pcan_corners.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + */ + +/* Generated by ref_pcan.m (v1.9-rc1-7901-g9500fa321) */ + +#define PCAN_CORNERS_NUM_POINTS 22 + +static const uint32_t ref_pcan_corner_inputs[22] = { + 0U, 1U, 2U, 3U, 4U, 2047U, + 2048U, 2049U, 8191U, 8192U, 8193U, 65535U, + 65536U, 65537U, 1073741823U, 1073741824U, 1073741825U, 2147483647U, + 2147483648U, 2147483649U, 4294967294U, 4294967295U, +}; + +static const int16_t ref_pcan_corner_wdf_outputs[22] = { + 32636, 32635, 32635, 32635, 32634, 31879, 31879, 31879, 29812, 29811, + 29811, 18676, 18672, 18672, 4, 4, 4, 2, 2, 2, + 1, 1, +}; + +static const uint32_t ref_pcan_corner_shrink_outputs[22] = { + 0U, 0U, 0U, 0U, 0U, 3U, + 4U, 4U, 63U, 64U, 64U, 959U, + 960U, 960U, 16777151U, 16777152U, 16777152U, 33554367U, + 33554368U, 33554368U, 67108799U, 67108799U, +}; diff --git a/test/cmocka/src/math/pcan/ref_pcan_func.h b/test/cmocka/src/math/pcan/ref_pcan_func.h new file mode 100644 index 000000000000..f86dfb415d6f --- /dev/null +++ b/test/cmocka/src/math/pcan/ref_pcan_func.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + */ + +/* Generated by ref_pcan.m (v1.9-rc1-7901-g9500fa321) */ + +#define PCAN_TEST_WDF_NUM_POINTS 65 + +static const uint32_t ref_pcan_wdf_inputs[65] = { + 0U, 1U, 2U, 3U, 4U, 5U, + 7U, 8U, 15U, 16U, 31U, 32U, + 63U, 64U, 127U, 128U, 255U, 256U, + 511U, 512U, 1023U, 1024U, 2047U, 2048U, + 4095U, 4096U, 8191U, 8192U, 16383U, 16384U, + 32767U, 32768U, 65535U, 65536U, 131071U, 131072U, + 262143U, 262144U, 524287U, 524288U, 1048575U, 1048576U, + 2097151U, 2097152U, 4194303U, 4194304U, 8388607U, 8388608U, + 16777215U, 16777216U, 33554431U, 33554432U, 67108863U, 67108864U, + 134217727U, 134217728U, 268435455U, 268435456U, 536870911U, 536870912U, + 1073741823U, 1073741824U, 2147483647U, 2147483648U, 4294967295U, +}; + +static const int16_t ref_pcan_wdf_outputs[65] = { + 32636, 32635, 32635, 32635, 32634, 32634, 32634, 32633, 32630, 32630, + 32624, 32624, 32612, 32612, 32587, 32587, 32539, 32539, 32443, 32443, + 32253, 32253, 31879, 31879, 31159, 31158, 29812, 29811, 27448, 27446, + 23710, 23707, 18676, 18672, 13169, 13166, 8351, 8348, 4876, 4874, + 2698, 2697, 1446, 1446, 762, 762, 398, 398, 207, 207, + 107, 107, 56, 56, 29, 29, 15, 15, 8, 8, + 4, 4, 2, 2, 1, +}; +#define PCAN_TEST_SHRINK_NUM_POINTS 22 + +static const uint32_t ref_pcan_shrink_inputs[22] = { + 0U, 1U, 10U, 100U, 500U, 1000U, + 2000U, 4000U, 8000U, 8190U, 8191U, 8192U, + 8193U, 8194U, 10000U, 16384U, 32768U, 65536U, + 100000U, 500000U, 1000000U, 10000000U, +}; + +static const uint32_t ref_pcan_shrink_outputs[22] = { + 0U, 0U, 0U, 0U, 0U, 0U, + 3U, 15U, 61U, 63U, 63U, 64U, + 64U, 64U, 92U, 192U, 448U, 960U, + 1498U, 7748U, 15561U, 156186U, +}; diff --git a/test/cmocka/src/math/pcan/ref_pcan_lut.h b/test/cmocka/src/math/pcan/ref_pcan_lut.h new file mode 100644 index 000000000000..6736041146cc --- /dev/null +++ b/test/cmocka/src/math/pcan/ref_pcan_lut.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + */ + +/* Generated by ref_pcan.m (v1.9-rc1-7901-g9500fa321) */ + +#define PCAN_TEST_LUT1_SIZE 125 +#define PCAN_TEST_LUT1_GAIN_BITS 21 +#define PCAN_TEST_LUT1_INPUT_BITS 10 + +static const int16_t ref_pcan_lut1[125] = { + 32636, 32635, 32635, 1, -2, 0, 32634, 1, -2, 0, + 32633, -5, 2, 0, 32630, -6, 0, 0, 32624, -12, + 0, 0, 32612, -23, -2, 0, 32587, -48, 0, 0, + 32539, -96, 0, 0, 32443, -190, 0, 0, 32253, -378, + 4, 0, 31879, -739, 18, 0, 31158, -1409, 62, 0, + 29811, -2567, 202, 0, 27446, -4301, 562, 0, 23707, -6265, + 1230, 0, 18672, -7458, 1952, 0, 13166, -7030, 2212, 0, + 8348, -5342, 1868, 0, 4874, -3459, 1282, 0, 2697, -2025, + 774, 0, 1446, -1120, 436, 0, 762, -596, 232, 0, + 398, -313, 122, 0, 207, -164, 64, 0, 107, -85, + 34, 0, 56, -45, 18, 0, 29, -22, 8, 0, + 15, -13, 6, 0, 8, -8, 4, 0, 4, -2, + 0, 0, 2, -3, 2, +}; +#define PCAN_TEST_LUT2_SIZE 125 +#define PCAN_TEST_LUT2_GAIN_BITS 18 +#define PCAN_TEST_LUT2_INPUT_BITS 12 + +static const int16_t ref_pcan_lut2[125] = { + 11465, 11465, 11465, 0, 0, 0, 11465, -3, 2, 0, + 11464, 0, 0, 0, 11464, 1, -2, 0, 11463, 1, + -2, 0, 11462, -5, 2, 0, 11459, -6, 0, 0, + 11453, -9, -2, 0, 11442, -25, 2, 0, 11419, -47, + 2, 0, 11374, -91, 2, 0, 11285, -178, 4, 0, + 11111, -341, 10, 0, 10780, -637, 38, 0, 10181, -1116, + 116, 0, 9181, -1749, 286, 0, 7718, -2315, 526, 0, + 5929, -2478, 700, 0, 4151, -2156, 696, 0, 2691, -1588, + 552, 0, 1655, -1047, 378, 0, 986, -647, 238, 0, + 577, -386, 144, 0, 335, -226, 84, 0, 193, -130, + 48, 0, 111, -77, 30, 0, 64, -45, 18, 0, + 37, -24, 8, 0, 21, -15, 6, 0, 12, -7, + 2, 0, 7, -5, 2, +}; diff --git a/test/cmocka/src/math/pcan/ref_pcan_stream.h b/test/cmocka/src/math/pcan/ref_pcan_stream.h new file mode 100644 index 000000000000..1960909baa6f --- /dev/null +++ b/test/cmocka/src/math/pcan/ref_pcan_stream.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Intel Corporation. + */ + +/* Generated by ref_pcan.m (v1.9-rc1-7901-g9500fa321) */ + +#define PCAN_STREAM_NUM_CHANNELS 16 +#define PCAN_STREAM_NUM_FRAMES 20 +#define PCAN_STREAM_SMOOTHING_COEF 819 +#define PCAN_STREAM_SMOOTHING_BITS 10 +#define PCAN_STREAM_INPUT_CORRECTION_BITS 0 + +static const uint32_t ref_pcan_stream_inputs[320] = { + 90U, 107U, 124U, 141U, 58U, 75U, + 92U, 109U, 126U, 143U, 60U, 77U, + 94U, 111U, 128U, 145U, 113U, 130U, + 147U, 64U, 81U, 98U, 115U, 132U, + 149U, 66U, 83U, 100U, 117U, 134U, + 51U, 68U, 136U, 53U, 70U, 87U, + 104U, 121U, 138U, 55U, 72U, 89U, + 106U, 123U, 140U, 57U, 74U, 91U, + 59U, 76U, 93U, 110U, 127U, 144U, + 61U, 78U, 95U, 112U, 129U, 146U, + 63U, 80U, 97U, 114U, 82U, 99U, + 116U, 2733U, 2800U, 2967U, 3134U, 3301U, + 3468U, 3635U, 52U, 69U, 86U, 103U, + 120U, 137U, 105U, 122U, 139U, 2656U, + 2823U, 2990U, 3157U, 3324U, 3491U, 3558U, + 75U, 92U, 109U, 126U, 143U, 60U, + 128U, 145U, 62U, 2679U, 2846U, 3013U, + 3180U, 3347U, 3414U, 3581U, 98U, 115U, + 132U, 149U, 66U, 83U, 51U, 68U, + 85U, 2702U, 2869U, 3036U, 3103U, 3270U, + 3437U, 3604U, 121U, 138U, 55U, 72U, + 89U, 106U, 74U, 91U, 108U, 2725U, + 2892U, 2959U, 3126U, 3293U, 3460U, 3627U, + 144U, 61U, 78U, 95U, 112U, 129U, + 97U, 114U, 131U, 2748U, 2815U, 2982U, + 3149U, 3316U, 3483U, 3550U, 67U, 84U, + 101U, 118U, 135U, 52U, 120U, 137U, + 54U, 2671U, 2838U, 3005U, 3172U, 3339U, + 3406U, 3573U, 90U, 107U, 124U, 141U, + 58U, 75U, 143U, 60U, 77U, 2694U, + 2861U, 3028U, 3195U, 3262U, 3429U, 3596U, + 113U, 130U, 147U, 64U, 81U, 98U, + 66U, 83U, 100U, 117U, 134U, 51U, + 68U, 85U, 102U, 119U, 136U, 53U, + 70U, 87U, 104U, 121U, 89U, 106U, + 123U, 140U, 57U, 74U, 91U, 108U, + 125U, 142U, 59U, 76U, 93U, 110U, + 127U, 144U, 112U, 129U, 146U, 63U, + 80U, 97U, 114U, 131U, 148U, 65U, + 82U, 99U, 116U, 133U, 50U, 67U, + 135U, 52U, 69U, 86U, 103U, 120U, + 137U, 54U, 71U, 88U, 105U, 122U, + 139U, 56U, 73U, 90U, 58U, 75U, + 92U, 109U, 126U, 143U, 60U, 77U, + 94U, 111U, 128U, 145U, 62U, 79U, + 96U, 113U, 81U, 98U, 115U, 132U, + 149U, 66U, 83U, 100U, 117U, 134U, + 51U, 68U, 85U, 102U, 119U, 136U, + 104U, 121U, 138U, 55U, 72U, 89U, + 106U, 123U, 140U, 57U, 74U, 91U, + 108U, 125U, 142U, 59U, 127U, 144U, + 61U, 78U, 95U, 112U, 129U, 146U, + 63U, 80U, 97U, 114U, 131U, 148U, + 65U, 82U, +}; + +static const uint32_t ref_pcan_stream_outputs[320] = { + 28U, 39U, 51U, 65U, 12U, 19U, + 29U, 40U, 53U, 67U, 13U, 21U, + 30U, 42U, 54U, 68U, 39U, 50U, + 62U, 12U, 21U, 30U, 40U, 52U, + 64U, 13U, 22U, 31U, 42U, 53U, + 8U, 14U, 50U, 8U, 13U, 21U, + 32U, 41U, 51U, 8U, 14U, 22U, + 33U, 42U, 52U, 9U, 16U, 23U, + 9U, 15U, 21U, 31U, 42U, 52U, + 9U, 16U, 22U, 32U, 44U, 53U, + 10U, 17U, 25U, 33U, 16U, 24U, + 31U, 922U, 936U, 949U, 979U, 1006U, + 1013U, 1036U, 6U, 11U, 17U, 26U, + 35U, 43U, 25U, 33U, 40U, 574U, + 597U, 604U, 617U, 628U, 631U, 628U, + 13U, 18U, 26U, 35U, 45U, 8U, + 34U, 43U, 7U, 421U, 432U, 435U, + 442U, 450U, 444U, 456U, 21U, 27U, + 35U, 44U, 9U, 14U, 5U, 9U, + 14U, 336U, 346U, 350U, 345U, 351U, + 355U, 361U, 30U, 36U, 6U, 10U, + 16U, 22U, 10U, 15U, 21U, 283U, + 288U, 280U, 285U, 287U, 289U, 292U, + 39U, 6U, 11U, 16U, 23U, 30U, + 17U, 23U, 29U, 240U, 234U, 236U, + 240U, 242U, 244U, 241U, 8U, 12U, + 18U, 24U, 32U, 4U, 25U, 31U, + 4U, 199U, 203U, 205U, 209U, 213U, + 210U, 215U, 14U, 19U, 26U, 32U, + 5U, 9U, 33U, 5U, 9U, 176U, + 181U, 185U, 189U, 185U, 189U, 192U, + 21U, 26U, 34U, 6U, 11U, 16U, + 6U, 10U, 15U, 0U, 0U, 0U, + 0U, 0U, 0U, 0U, 28U, 4U, + 7U, 11U, 17U, 23U, 12U, 17U, + 22U, 0U, 0U, 0U, 0U, 0U, + 0U, 0U, 5U, 8U, 13U, 17U, + 24U, 30U, 18U, 23U, 29U, 0U, + 0U, 0U, 0U, 0U, 0U, 0U, + 10U, 14U, 19U, 24U, 3U, 6U, + 25U, 3U, 6U, 0U, 0U, 0U, + 0U, 0U, 0U, 0U, 16U, 21U, + 26U, 4U, 7U, 11U, 4U, 7U, + 11U, 0U, 0U, 0U, 0U, 0U, + 0U, 0U, 22U, 28U, 5U, 8U, + 13U, 17U, 9U, 13U, 17U, 0U, + 1U, 0U, 0U, 0U, 0U, 0U, + 3U, 6U, 9U, 13U, 19U, 24U, + 14U, 19U, 23U, 0U, 0U, 0U, + 0U, 0U, 0U, 0U, 7U, 10U, + 15U, 19U, 26U, 4U, 20U, 25U, + 4U, 0U, 0U, 0U, 0U, 0U, + 0U, 0U, 12U, 16U, 21U, 26U, + 5U, 8U, +}; + +static const uint32_t ref_pcan_stream_final_noise[16] = { + 65089U, 66549U, 66741U, 658990U, 693317U, 727673U, + 763723U, 799477U, 833963U, 863004U, 61280U, 65959U, + 67716U, 69176U, 63373U, 64115U, +}; diff --git a/west.yml b/west.yml index a684deb8871a..144e94f6d789 100644 --- a/west.yml +++ b/west.yml @@ -11,6 +11,8 @@ manifest: url-base: https://github.com/thesofproject - name: zephyrproject url-base: https://github.com/zephyrproject-rtos + - name: tensorflow + url-base: https://github.com/tensorflow # When upgrading projects here please run git log --oneline in the # project and if not too long then include the output in your commit @@ -41,6 +43,12 @@ manifest: path: sof/tools/rimage/tomlc99 revision: e3a03f5ec7d8d33be705c5ce8a632d998ce9b4d1 + - name: tflite-micro + repo-path: tflite-micro + path: tflite-micro + revision: e86d97b6237f88ab5925c0b41e3e3589a1560d86 + remote: tensorflow + - name: zephyr repo-path: zephyr revision: fbd71c30f7b9083de9826577edb57d360efc01ed