Custom Pre/Post Processing¶
If your model requires custom pre- or post-processing, you can inherit from the anira::PrePostProcessor
class and override the anira::PrePostProcessor::pre_process()
and anira::PrePostProcessor::post_process()
methods to match your model’s specific requirements.
The anira::PrePostProcessor::pre_process()
method receives input data from the application through a vector of anira::RingBuffer
instances and transforms them into output buffers (a vector of anira::BufferF
). These output buffers are then fed directly to the inference engine.
The anira::PrePostProcessor::post_process()
method receives inference results through a vector of anira::BufferF
instances and writes them to output ring buffers (a vector of anira::RingBuffer
). The anira::InferenceHandler
then retrieves samples from these ring buffers and returns them to the audio application.
Non-streamable tensors, such as control parameters or static values, can be handled using the anira::PrePostProcessor::get_input()
and anira::PrePostProcessor::set_input()
methods for input data, and anira::PrePostProcessor::get_output()
and anira::PrePostProcessor::set_output()
methods for output data. These methods allow you to store and retrieve non-streamable tensor values in a thread-safe manner.
Understanding Streamable vs Non-Streamable Tensors¶
Anira supports two types of tensors that require different handling in custom preprocessors:
Streamable Tensors:
- Data that flows continuously (time-varying signals)
- Have preprocess_input_size > 0
and postprocess_output_size > 0
- Data comes from anira::RingBuffer
instances via the input
parameter
- Use helper methods like pop_samples_from_buffer()
to extract data
Non-Streamable Tensors:
- Control parameters, static values, or metadata (non-time-varying)
- Have preprocess_input_size == 0
and postprocess_output_size == 0
- Data comes from the preprocessor’s internal storage via get_input()
and set_input()
methods
- Must be manually written to and read from anira::BufferF
tensors
- Note: Non-streamable tensors have no channel count (always use channel 0)
Basic Custom PrePostProcessor Implementation¶
#include <anira/anira.h>
class CustomPrePostProcessor : public anira::PrePostProcessor {
public:
// Inherit constructor from base class
using anira::PrePostProcessor::PrePostProcessor;
virtual void pre_process(std::vector<anira::RingBuffer>& input,
std::vector<anira::BufferF>& output,
[[maybe_unused]] anira::InferenceBackend current_inference_backend) override {
for (size_t i = 0; i < m_inference_config.get_tensor_input_shape().size(); ++i) {
if (m_inference_config.get_preprocess_input_size()[i] > 0) {
// Streamable tensor: extract audio data from ring buffer
pop_samples_from_buffer(input[i], output[i],
m_inference_config.get_preprocess_input_size()[i]);
} else {
// Non-streamable tensor: get data from internal storage
// Note: Non-streamable tensors always use channel 0
for (size_t sample = 0; sample < m_inference_config.get_tensor_input_size()[i]; ++sample) {
output[i].set_sample(0, sample, get_input(i, sample));
}
}
}
}
virtual void post_process(std::vector<anira::BufferF>& input,
std::vector<anira::RingBuffer>& output,
[[maybe_unused]] anira::InferenceBackend current_inference_backend) override {
for (size_t i = 0; i < m_inference_config.get_tensor_output_shape().size(); ++i) {
if (m_inference_config.get_postprocess_output_size()[i] > 0) {
// Streamable tensor: write audio data to ring buffer
push_samples_to_buffer(input[i], output[i],
m_inference_config.get_postprocess_output_size()[i]);
} else {
// Non-streamable tensor: store data in internal storage
// Note: Non-streamable tensors always use channel 0
for (size_t sample = 0; sample < m_inference_config.get_tensor_output_size()[i]; ++sample) {
set_output(input[i].get_sample(0, sample), i, sample);
}
}
}
}
};
Available Helper Methods¶
The anira::PrePostProcessor
provides several helper methods to facilitate data handling between audio buffers and neural network tensors. Here are the key methods you can use:
Method |
Description |
---|---|
Extracts samples from input ring buffer and writes them to output buffer. Multiple overloads support different windowing modes. |
|
Writes samples from input buffer to output ring buffer. |
|
Retrieves non-streamable input values from internal storage (thread-safe). |
|
Sets non-streamable input values to internal storage (thread-safe). |
|
Retrieves non-streamable output values from internal storage (thread-safe). |
|
Sets non-streamable output values to internal storage (thread-safe). |
Integration with InferenceHandler¶
Once you’ve implemented your custom preprocessor, integrate it with the inference system:
// First create your inference configuration
anira::InferenceConfig inference_config(/* your config parameters */);
// Create your custom preprocessor instance
// Note: The preprocessor requires an InferenceConfig reference
CustomPrePostProcessor pp_processor(inference_config);
// Create InferenceHandler with custom preprocessor
anira::InferenceHandler inference_handler(pp_processor, inference_config);
Note
The preprocess and postprocess methods are called from the audio thread and must be real-time safe. Avoid operations that could cause blocking, memory allocation, or other non-deterministic behavior that could introduce audio dropouts or latency issues.