API for using custom decimator filters#259
Conversation
c3b331f to
ad6944c
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds support for custom decimation filters in the microphone array library by introducing a new API (mic_array_init_custom_filter) alongside the existing default API. The changes refactor the decimator and PDM RX components to accept runtime configuration instead of relying solely on compile-time template parameters.
Key Changes:
- New custom filter API with configuration structs for flexible filter specification
- Refactored
TwoStageDecimatorandStandardPdmRxServiceto use runtime configuration - Removed the deprecated prefab API (
BasicMicArray) - Added new example (
app_custom_filter) and updated existing tests - Enhanced Python scripts for generating filter header files
Reviewed changes
Copilot reviewed 73 out of 79 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| lib_mic_array/api/mic_array/mic_array_conf_struct.h | New configuration structs for custom filter API |
| lib_mic_array/api/mic_array/mic_array_task.h | Added mic_array_init_custom_filter function declaration |
| lib_mic_array/api/mic_array/cpp/Decimator.hpp | Refactored to accept runtime config via Init() |
| lib_mic_array/api/mic_array/cpp/PdmRx.hpp | Removed base class, changed to accept runtime buffers |
| lib_mic_array/api/mic_array/cpp/Prefab.hpp | Removed deprecated prefab API |
| lib_mic_array/src/mic_array_task.cpp | Simplified to use single mic array type with runtime config |
| lib_mic_array/src/mic_array_task_internal.hpp | Refactored internal helpers for new API |
| python/*.py | Enhanced filter generation scripts with header file output |
| examples/app_custom_filter/* | New example demonstrating custom filter usage |
| examples/app_prefab/* | Removed deprecated prefab example |
| tests/**/*.cpp | Updated tests to use new API patterns |
| tests/**/*.py | Updated test scripts for custom filter support |
| doc/**/*.rst | Updated documentation for custom filter API |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| s2_filter = filters.Stage2Filter(s2_coef, s2_df) | ||
|
|
||
| if self.debug_print_filters: | ||
| self.print_two_stage_filter(s1_filter, s2_filter) |
There was a problem hiding this comment.
Call to method MicArraySharedBase.print_two_stage_filter with too many arguments; should be no more than 1.
| s2_filter = filters.Stage2Filter(s2_coef, s2_df) | ||
|
|
||
| if self.debug_print_filters: | ||
| def print_two_stage_filter(s1_filter, s2_filter): |
There was a problem hiding this comment.
Normal methods should have 'self', rather than 's1_filter', as their first parameter.
| import stage1 | ||
| import stage2 | ||
| from pathlib import Path | ||
| from header_utils import * |
There was a problem hiding this comment.
Import pollutes the enclosing namespace, as the imported module header_utils does not define 'all'.
| from pathlib import Path | ||
|
|
||
| import mic_array.filters as filters | ||
| from header_utils import * |
There was a problem hiding this comment.
Import pollutes the enclosing namespace, as the imported module header_utils does not define 'all'.
| from pathlib import Path | ||
|
|
||
| import mic_array.filters as filters | ||
| from header_utils import * |
There was a problem hiding this comment.
Import pollutes the enclosing namespace, as the imported module header_utils does not define 'all'.
| # Copyright 2025 XMOS LIMITED. | ||
| # This Software is subject to the terms of the XMOS Public Licence: Version 1. | ||
|
|
||
| import argparse |
There was a problem hiding this comment.
Import of 'argparse' is not used.
ad6944c to
842eb16
Compare
…onf_t and pdm_rx_conf_t) - Removed S2_DEC_FACTOR and S2_TAP_COUNT template parameters from TwoStageDecimator - Modified TwoStageDecimator::Init() API to accept mic_array_decimator_conf_t &decimator_conf - Removed SUBBLOCKS template parameter from StandardPdmRxService - Modified StandardPdmRxService::Init() API to accept pdm_rx_conf_t &pdm_rx_config - Removed PdmRxService class and the associated CRTP stuff - Modified default API implementation to use the simplified decimator and pdmrx classes - Get tests and examples building - Update MIPS and memory numbers - Remove prefab tests and examples - lib checks + fix doc build errors
- Added support in stage1.py, stage2.py and combined.py to output the filters in a .h file - Added support in BasicMicArray test app CMakeLists.txt to autogenerate the custom filter header file from the pkl file - Remove num_mics from decimator and pdmrx conf (not needed for the custom filter feature
- Modified mic_array_decimator_conf_t to have a mic_array_filter_conf_t pointer and num_filter_stages, to avoid having a MAX_FILTER_STAGES type limitation. - Renamed some fields in the decimator and pdmrx conf to be more descriptive - Renamed app_mic_array_custom_filter to app_custom_filter - Renamed state_size to state_words_per_channel - Renamed Init_new() back to Init()
842eb16 to
40e844d
Compare
8cca269 to
6120953
Compare
- Removed READMEs from tests/ - Removed references to crtp from documentation - Added documentation to stage1.py, stage2.py and combined.py - Changelog update - Address copilot review comments
6120953 to
658192d
Compare
- modified stage1.py and stage2.py to work for 3 stage filters - Extended BasicMicArray test to test a <custom_filter>.pkl file that's specified in test_params.json - Added support for specifying custom filter .pkl file in app_mips CMakeLists. User required to run the _customfs config manually to check the MIPS impact - Refactor mic_array_task.cpp to remove duplicated code - Renamed Decimator_3_stage.hpp to ThreeStageDecimator.hpp - Added tests/signal/BasicMicArray/good_3_stage_filter_int.pkl
664e80e to
1f893a1
Compare
…r_int.pkl to good_2_stage_filter_int.pkl - Update mips and memory profile numbers (~1.2 mips/channel increase in mips and extra ~1536 bytes of memory) This has increased MIPS and memory overhead but better THDN performance
1f893a1 to
d829e44
Compare
uvvpavel
left a comment
There was a problem hiding this comment.
Great work! Some nitpicks, but all good really.
Side note:
Not related to your PR, but the library API has an inconsistent namespases. Some APIs start with mic_array_ while others with ma_. I think this would need to be resolved in the future.
(I'd prefer ma_, if anyone would ask me ;))
| * Reads stage-1 and stage-2 filter parameters from @p decimator_conf and prepares | ||
| * internal state: | ||
| * The caller must ensure all pointers inside @p decimator_conf.filter_conf[0] | ||
| * and @p decimator_conf.filter_conf[0] are valid and remain alive for the |
There was a problem hiding this comment.
Don't really like alive in docs, not sure how to change it tho...
| * subsequent application stages. | ||
| * @endparblock | ||
| * | ||
| * @tparam MIC_COUNT Number of microphone output channels from the the mic array component |
| * | ||
| * `SubType::GetPdmBlock()` responsible for receiving a block of PDM data from | ||
| * `SubType::SendBlock()` as a pointer, deinterleaving the buffer contents, | ||
| * `GetPdmBlock()` responsible for receiving a block of PDM data from |
| this->phase = CHANNELS_IN * this->pdm_out_words_per_channel; | ||
| this->num_phases = CHANNELS_IN * this->pdm_out_words_per_channel; |
There was a problem hiding this comment.
could do something like:
this->num_phases = CHANNEL_IN * this->pdm_out_words_per_channel;
// starting at the last phase
this->phase = this->num_phase;
| The simplest way to use these coefficients in an application is to run something like: | ||
|
|
||
| .. code:: | ||
|
|
||
| lib_mic_array\python>python combined.py filter_design\good_2_stage_filter_int.pkl --file-prefix custom_filter | ||
|
|
||
| and include the generated ``custom_filter.h`` file in the application. |
There was a problem hiding this comment.
I'd say something like "run this from the repo root"
Then include cd lib_mic_array/python
And the finishing part should be its own sentence, like:
Then include the generated ..... in the application
| print(f"#ifndef {args.file_prefix.upper()}_H", file=out) | ||
| print(f"#define {args.file_prefix.upper()}_H", file=out) |
There was a problem hiding this comment.
not C/C++ standard :)
| print(f"\n/* Autogenerated by running 'python combined.py {args.coef_pkl_file} -fp {args.file_prefix}'. Do not edit */", file=out) | ||
| print(f"\n#include <stdint.h>", file=out) |
There was a problem hiding this comment.
Could add a copyright as well, example here https://github.com/xmos/lib_pdm/blob/develop/tests/write_signals.py#L12
There was a problem hiding this comment.
didnt think about it. thanks!
| /** | ||
| * @brief PDM RX output block (input to the decimator) for all microphones. | ||
| * @details | ||
| * Packed PDM samples as 32-bit words. The layout is a contiguous buffer | ||
| * sized `output_mic_count * pdm_out_words_per_channel` 32-bit words. | ||
| * Typically contains enough PDM words to produce one PCM sample per microphone | ||
| * after decimation. | ||
| * | ||
| * This buffer must be aligned to a 32-bit word boundary. | ||
| */ | ||
| uint32_t *pdm_out_block; |
There was a problem hiding this comment.
Do we want to include details on how to put the PDM data in? LSB or MSB first?
There was a problem hiding this comment.
good point. Its mentioned elsewhere in the documentation but I'll added it here as well.
| .. note:: | ||
|
|
||
| The default API requires approximately 6 KiB more memory than the custom configuration. | ||
| The default API requires approximately 4 KiB more memory than the custom configuration. |
There was a problem hiding this comment.
In the getting started guide this number is 3. Is this correct?
There was a problem hiding this comment.
Ah. I think I'll remove quoting an exact number. It keeps changing as the code changes
Main changes in this PR
mic_array_init_custom_filter()) that allows initialising the mic array with custom decimation filtersprefabfrom code and documentation