/** * @file tiny_conv.h * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg) * @brief tiny_conv | code | header * @version 1.0 * @date 2025-04-27 * @copyright Copyright (c) 2025 * */#pragma once/* DEPENDENCIES */// tiny_dsp configuration file#include"tiny_dsp_config.h"// ESP32 DSP Library for Acceleration#if MCU_PLATFORM_SELECTED == MCU_PLATFORM_ESP32 // ESP32 DSP library#include"dsps_conv.h"#include"dspi_conv.h"#endif#ifdef __cplusplusextern"C"{#endif/** * @name: tiny_conv_f32 * @brief Convolution function (full mode, zero padding implicit) * * @param Signal The input signal array * @param siglen The length of the input signal array (> 0) * @param Kernel The input kernel array * @param kernlen The length of the input kernel array (> 0) * @param convout The output array for the convolution result. * Caller MUST provide at least (siglen + kernlen - 1) elements. * * @note On ESP32 the underlying ESP-DSP routine additionally requires * siglen >= kernlen; the generic fallback handles both orderings. * * @return tiny_error_t */tiny_error_ttiny_conv_f32(constfloat*Signal,constintsiglen,constfloat*Kernel,constintkernlen,float*convout);typedefenum{TINY_PADDING_ZERO=0,// Zero paddingTINY_PADDING_SYMMETRIC=1,// Symmetric reflectionTINY_PADDING_PERIODIC=2// Periodic extension}tiny_padding_mode_t;typedefenum{TINY_CONV_FULL=0,// Full convolution (len = siglen + kernlen - 1)TINY_CONV_HEAD=1,// Head mode (first lkern points)TINY_CONV_CENTER=2,// Centered mode (output siglen points)TINY_CONV_TAIL=3// Tail mode (last lkern points)}tiny_conv_mode_t;/** * @name: tiny_conv_ex_f32 * @brief Extended convolution function with padding and mode options * * @param Signal The input signal array * @param siglen The length of the input signal array (> 0) * @param Kernel The input kernel array * @param kernlen The length of the input kernel array (> 0) * @param convout The output buffer for the convolution result. * Regardless of conv_mode, the caller MUST provide * at least (siglen + kernlen - 1) elements: the routine * first writes the full convolution into convout and then * shifts the requested slice to the front in place. * After return, the meaningful length is: * - TINY_CONV_FULL : siglen + kernlen - 1 * - TINY_CONV_HEAD : kernlen * - TINY_CONV_CENTER : siglen * - TINY_CONV_TAIL : kernlen * @param padding_mode Padding mode (zero, symmetric, periodic) * @param conv_mode Convolution mode (full, head, center, tail) * * @return tiny_error_t */tiny_error_ttiny_conv_ex_f32(constfloat*Signal,constintsiglen,constfloat*Kernel,constintkernlen,float*convout,tiny_padding_mode_tpadding_mode,tiny_conv_mode_tconv_mode);#ifdef __cplusplus}#endif
/** * @file tiny_conv.c * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg) * @brief tiny_conv | code | source * @version 1.0 * @date 2025-04-27 * @copyright Copyright (c) 2025 * *//* DEPENDENCIES */#include"tiny_conv.h"/** * @name: tiny_conv_f32 * @brief Convolution function * * @param Signal The input signal array * @param siglen The length of the input signal array * @param Kernel The input kernel array * @param kernlen The length of the input kernel array * @param convout The output array for the convolution result * * @return tiny_error_t */tiny_error_ttiny_conv_f32(constfloat*Signal,constintsiglen,constfloat*Kernel,constintkernlen,float*convout){if(NULL==Signal||NULL==Kernel||NULL==convout){returnTINY_ERR_DSP_NULL_POINTER;}if(siglen<=0||kernlen<=0){returnTINY_ERR_DSP_INVALID_PARAM;}#if MCU_PLATFORM_SELECTED == MCU_PLATFORM_ESP32/* The ESP-DSP backend requires siglen >= kernlen. The generic fallback * below handles both orderings via its three-stage form. */if(siglen<kernlen){returnTINY_ERR_DSP_INVALID_PARAM;}dsps_conv_f32(Signal,siglen,Kernel,kernlen,convout);#elseconstfloat*sig=Signal;constfloat*kern=Kernel;intlsig=siglen;intlkern=kernlen;// stage Ifor(intn=0;n<lkern;n++){size_tk;convout[n]=0;for(k=0;k<=n;k++){convout[n]+=sig[k]*kern[n-k];}}// stage IIfor(intn=lkern;n<lsig;n++){size_tkmin,kmax,k;convout[n]=0;kmin=n-lkern+1;kmax=n;for(k=kmin;k<=kmax;k++){convout[n]+=sig[k]*kern[n-k];}}// stage IIIfor(intn=lsig;n<lsig+lkern-1;n++){size_tkmin,kmax,k;convout[n]=0;kmin=n-lkern+1;kmax=lsig-1;for(k=kmin;k<=kmax;k++){convout[n]+=sig[k]*kern[n-k];}}#endifreturnTINY_OK;}/** * @name: tiny_conv_ex_f32 * @brief Extended convolution function with padding and mode options * * @param Signal The input signal array * @param siglen The length of the input signal array * @param Kernel The input kernel array * @param kernlen The length of the input kernel array * @param convout The output array for the convolution result * @param padding_mode Padding mode (zero, symmetric, periodic) * @param conv_mode Convolution mode (full, head, center, tail) * * @return tiny_error_t */tiny_error_ttiny_conv_ex_f32(constfloat*Signal,constintsiglen,constfloat*Kernel,constintkernlen,float*convout,tiny_padding_mode_tpadding_mode,tiny_conv_mode_tconv_mode){if(NULL==Signal||NULL==Kernel||NULL==convout){returnTINY_ERR_DSP_NULL_POINTER;}if(siglen<=0||kernlen<=0){returnTINY_ERR_DSP_INVALID_PARAM;}#if MCU_PLATFORM_SELECTED == MCU_PLATFORM_ESP32/* Fast path through ESP-DSP only when its precondition is met. */if(padding_mode==TINY_PADDING_ZERO&&conv_mode==TINY_CONV_FULL&&siglen>=kernlen){dsps_conv_f32(Signal,siglen,Kernel,kernlen,convout);returnTINY_OK;}#endifintpad_len=kernlen-1;intpadded_len=siglen+2*pad_len;float*padded_signal=(float*)calloc(padded_len,sizeof(float));if(padded_signal==NULL){returnTINY_ERR_DSP_MEMORY_ALLOC;}// Fill padded signalswitch(padding_mode){caseTINY_PADDING_ZERO:// Middle copy only, left and right are zeros (calloc already zeroed)memcpy(padded_signal+pad_len,Signal,sizeof(float)*siglen);break;caseTINY_PADDING_SYMMETRIC:for(inti=0;i<pad_len;i++){padded_signal[pad_len-1-i]=Signal[i];// Mirror leftpadded_signal[pad_len+siglen+i]=Signal[siglen-1-i];// Mirror right}memcpy(padded_signal+pad_len,Signal,sizeof(float)*siglen);// Copy centerbreak;caseTINY_PADDING_PERIODIC:for(inti=0;i<pad_len;i++){padded_signal[pad_len-1-i]=Signal[(siglen-pad_len+i)%siglen];// Wrap leftpadded_signal[pad_len+siglen+i]=Signal[i%siglen];// Wrap right}memcpy(padded_signal+pad_len,Signal,sizeof(float)*siglen);// Copy centerbreak;default:free(padded_signal);returnTINY_ERR_DSP_INVALID_PARAM;}// Full convolutionintconvlen_full=siglen+kernlen-1;for(intn=0;n<convlen_full;n++){floatsum=0.0f;for(intk=0;k<kernlen;k++){sum+=padded_signal[n+k]*Kernel[kernlen-1-k];// Convolution is flip+slide}convout[n]=sum;}free(padded_signal);// Handle output modeif(conv_mode==TINY_CONV_FULL){returnTINY_OK;}else{intstart_idx=0;intout_len=0;switch(conv_mode){caseTINY_CONV_HEAD:start_idx=0;out_len=kernlen;break;caseTINY_CONV_CENTER:start_idx=(kernlen-1)/2;out_len=siglen;break;caseTINY_CONV_TAIL:start_idx=siglen-1;out_len=kernlen;break;default:returnTINY_ERR_DSP_INVALID_MODE;}/* The selected slice may overlap with the head of convout, so use * memmove rather than a forward copy / memcpy. */if(start_idx>0&&out_len>0){memmove(convout,convout+start_idx,sizeof(float)*(size_t)out_len);}}returnTINY_OK;}