/* Audio Library for Teensy 3.X
Copyright ( c ) 2014 , Pete ( El Supremo )
Copyright ( c ) 2019 , Holger Wirtz
Permission is hereby granted , free of charge , to any person obtaining a copy
of this software and associated documentation files ( the " Software " ) , to deal
in the Software without restriction , including without limitation the rights
to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
copies of the Software , and to permit persons to whom the Software is
furnished to do so , subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE .
*/
# include <Arduino.h>
# include <Audio.h>
# include "arm_math.h"
# include "effect_modulated_delay.h"
# include "config.h"
extern config_t configuration ;
/******************************************************************/
// Based on; A u d i o E f f e c t D e l a y
// Written by Pete (El Supremo) Jan 2014
// 140529 - change to handle mono stream - change modify() to voices()
// 140219 - correct storage class (not static)
// 190527 - added modulation input (by Holger Wirtz)
boolean AudioEffectModulatedDelay : : begin ( short * delayline , int d_length )
{
#if 0
Serial . print ( F ( " AudioEffectModulatedDelay.begin(Chorus delay line length = " ) ) ;
Serial . print ( d_length ) ;
Serial . println ( F ( " ) " ) ) ;
# endif
_delayline = NULL ;
_delay_length = 0 ;
_delay_offset = 0.0 ;
_cb_index = 0 ;
if ( delayline = = NULL ) {
return ( false ) ;
}
if ( d_length < 10 ) {
return ( false ) ;
}
_delayline = delayline ;
_delay_length = d_length ;
set_modulator_filter_coeffs ( 1.0 , configuration . chorus_frequency / 10 , 1.0 ) ; // gain, center frerquency
modulator_filter_data = { 1 , & modulator_filter_state , modulator_filter_coeffs } ;
return ( true ) ;
}
void AudioEffectModulatedDelay : : set_modulator_filter_coeffs ( float gain , float fc , float width )
{
// modulator filter
// coefficients calculated with "IOWA Hills IIR Filter Designer 6.5", http://www.iowahills.com/8DownloadPage.html
// Example: https://web.fhnw.ch/technik/projekte/eit/Fruehling2016/MuelZum/html/parametric_equalizer_example_8c-example.html
/* float32_t A = sqrt(powf(10, gain / 20.0f));
float32_t w0 = 2.0f * PI * fc / ( ( float32_t ) AUDIO_SAMPLE_RATE_EXACT ) ;
float32_t cosw0 = cosf ( w0 ) ;
float32_t sinw0 = sinf ( w0 ) ;
float32_t alpha = sinw0 / ( 2.0f * width ) ;
float32_t a0 = 1.0f + alpha / A ;
modulator_filter_coeffs [ 0 ] = ( 1.0f + alpha * A ) / a0 ; // b0
modulator_filter_coeffs [ 1 ] = ( - 2.0f * cosw0 ) / a0 ; // b1
modulator_filter_coeffs [ 2 ] = ( 1.0f - alpha * A ) / a0 ; // b2
modulator_filter_coeffs [ 3 ] = - ( 2.0f * cosw0 ) / - a0 ; // -a1
modulator_filter_coeffs [ 4 ] = ( 1.0f - alpha / A ) / - a0 ; // -a2 */
// OmegaC = 0.1, SR = 44117.64706, Fc = 2.21 kHz, N=2
modulator_filter_coeffs [ 0 ] = 0.020727217357494492 ; // b0
modulator_filter_coeffs [ 1 ] = 0.020727217357494492 ; // b1
modulator_filter_coeffs [ 2 ] = 0.020727217357494492 ; // b2
modulator_filter_coeffs [ 3 ] = 1.563046149664217620 ; // -a1
modulator_filter_coeffs [ 4 ] = - 0.642749223719756180 ; // -a2
}
void AudioEffectModulatedDelay : : update ( void )
{
audio_block_t * block ;
audio_block_t * modulation ;
if ( _delayline = = NULL )
return ;
block = receiveWritable ( 0 ) ;
modulation = receiveReadOnly ( 1 ) ;
if ( block & & modulation )
{
int16_t * bp ;
int16_t cb_mod_index_neighbor ;
float * mp ;
float mod_index ;
float mod_number ;
float mod_fraction ;
float modulation_f32 [ AUDIO_BLOCK_SAMPLES ] ;
bp = block - > data ;
arm_q15_to_float ( modulation - > data , modulation_f32 , AUDIO_BLOCK_SAMPLES ) ;
//arm_biquad_cascade_df1_f32(&modulator_filter_data, modulation_f32, modulation_f32, AUDIO_BLOCK_SAMPLES);
mp = modulation_f32 ;
for ( uint16_t i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i + + )
{
// write data into circular buffer (delayline)
if ( _cb_index > = _delay_length )
_cb_index = 0 ;
_delayline [ _cb_index ] = * bp ;
// Calculate the modulation-index as a floating point number for interpolation
mod_index = * mp * ( 1 - MODULATION_MAX_FACTOR ) * _delay_length ; // "(1 - MODULATION_MAX_FACTOR) * _delay_length" means: maximum bytes of modulation allowed by given delay length
mod_fraction = modff ( mod_index , & mod_number ) ; // split float of mod_index into integer (= mod_number) and fraction part
// calculate modulation index into circular buffer
cb_mod_index = ( _cb_index - ( _delay_offset + int ( mod_index ) ) ) ;
if ( cb_mod_index > = _delay_length )
cb_mod_index - = _delay_length ;
if ( cb_mod_index < 0 ) // check for negative offsets and correct them
cb_mod_index + = _delay_length ;
if ( * mp < 0.0 )
{
if ( cb_mod_index = = 0 )
cb_mod_index_neighbor = _delay_length ;
else
cb_mod_index_neighbor = cb_mod_index - 1 ;
}
else
{
if ( cb_mod_index = = _delay_length )
cb_mod_index_neighbor = 0 ;
else
cb_mod_index_neighbor = cb_mod_index + 1 ;
}
if ( * mp < 0.0 )
* bp = round ( float ( _delayline [ cb_mod_index ] ) * mod_fraction + float ( _delayline [ cb_mod_index_neighbor ] ) * ( 1.0 - mod_fraction ) ) ;
else
* bp = round ( float ( _delayline [ cb_mod_index_neighbor ] ) * mod_fraction + float ( _delayline [ cb_mod_index ] ) * ( 1.0 - mod_fraction ) ) ;
// push the pointers forward
bp + + ; // next audio data
mp + + ; // next modulation data
_cb_index + + ; // next circular buffer index
}
}
if ( modulation )
release ( modulation ) ;
if ( block )
{
transmit ( block , 0 ) ;
release ( block ) ;
}
}
float AudioEffectModulatedDelay : : offset ( float offset_value ) // in ms
{
uint16_t offset_frames = ( offset_value / 1000 ) * AUDIO_SAMPLE_RATE ;
if ( offset_frames > _delay_length * MODULATION_MAX_FACTOR )
_delay_offset = _delay_length * MODULATION_MAX_FACTOR ;
else if ( offset_frames < = _delay_length * ( 1 - MODULATION_MAX_FACTOR ) )
_delay_offset = _delay_length * ( 1 - MODULATION_MAX_FACTOR ) ;
else
_delay_offset = offset_frames ;
return ( offset_frames / AUDIO_SAMPLE_RATE * 1000 ) ;
}