first commit

master
Holger Wirtz 4 years ago
commit cadc2d9b90
  1. 1
      README.md
  2. 383
      accurate_vibrato2.cpp
  3. 61
      accurate_vibrato2.h

@ -0,0 +1 @@
From: https://forum.pjrc.com/threads/62556-Simple-Vibrato-effect

@ -0,0 +1,383 @@
/* Accurate Vibrato effect 2
* Copyright (c) 2020, Mark Tillotson, markt@chaos.org.uk
*
* 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, development funding 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 <math.h>
#include <stdint.h>
#include <Arduino.h>
#include "accurate_vibrato2.h"
#define BUF_MASK 0x1FF // 4 blocks
#if AUDIO_BLOCK_SAMPLES != 128
# error "Only works for AUDIO_BLOCK_SAMPLES == 128 currently"
#endif
#define WORD_MAX 4294967296.0
#define MAX_DEVIATION (190 - VIBRATO_SINC_SAMPLES)
float AudioEffectHighQualityVibrato::cos_table [VIBRATO_SINC_SAMPLES+1] ;
AudioEffectHighQualityVibrato::AudioEffectHighQualityVibrato(void) : AudioStream(1, inputQueueArray)
{
mod_phase_acc = 0 ;
modulation (5.0, 2.0) ; // 5Hz, 2% vibrato (6% is a semitone)
for (int i = 0 ; i < 4 * AUDIO_BLOCK_SAMPLES ; i++)
buffer[i] = 0.0 ;
// interpolation from buffer with max offset +/- 1.5 * 128, so 3 buffers needed + 1 for buffering
bptr = 3 * AUDIO_BLOCK_SAMPLES / 2 ;
cptr = 0 ;
while (cptr < 3 * AUDIO_BLOCK_SAMPLES) // insert new data starting at last block, with first 3 doing the tap
buffer [cptr++] = 0 ;
// initialize cosine taper table.
if (cos_table [0] == 0.0)
{
for (int m = 0 ; m <= VIBRATO_SINC_SAMPLES+1 ; m++)
if (m < VIBRATO_SINC_SAMPLES_FULL)
cos_table [m] = 1.0 ;
else
cos_table [m] = 0.5 * (1.0 + cos ((m - VIBRATO_SINC_SAMPLES_FULL) * M_PI / \
(VIBRATO_SINC_SAMPLES - VIBRATO_SINC_SAMPLES_FULL))) ;
}
}
void AudioEffectHighQualityVibrato::modulation (float hertz, float percent)
{
if (hertz < 0.5) hertz = 0.5 ;
if (hertz > 20.0) hertz = 20.0 ;
if (percent > 10.0) percent = 10.0 ;
mod_phase_inc = (uint32_t) int (round (hertz * WORD_MAX / AUDIO_SAMPLE_RATE)) ;
float mod_d = (percent / 100.0) * AUDIO_SAMPLE_RATE / (2 * M_PI * hertz) ;
if (mod_d > MAX_DEVIATION) mod_d = MAX_DEVIATION ;
if (mod_d < 0.0) mod_d = 0.0 ;
mod_depth = mod_d ;
//Serial.printf ("setup vibrato, hertz %f, percent %f, inx %i, mod_d %f\n", hertz, percent, mod_phase_inc, mod_d) ;
}
void AudioEffectHighQualityVibrato::update(void)
{
audio_block_t * vibrato = allocate () ;
if (vibrato == NULL)
return ;
int16_t * data ;
audio_block_t * new_block = receiveReadOnly (0) ;
// copy new_block to circular buffer or zero
if (new_block != NULL)
{
data = new_block->data ;
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
buffer [cptr++] = data [i] ;
release (new_block) ;
}
else
{
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
buffer [cptr++] = 0 ;
}
cptr &= BUF_MASK ;
data = vibrato->data ;
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
{
float mod = mod_depth * sin (mod_phase_acc / WORD_MAX * 2 * M_PI) ; // scale by mod depth
int intpart = int (floor (mod)) ;
float fract = mod - intpart ; // fractional part of sample offset
if (fract == 0.0) // perfectly aligned case, would involve div by zero if we didn't handle specially.
{
data [i] = buffer [(bptr + intpart) & BUF_MASK] ;
}
else
{
float sum = 0.0;
/*
float sine = sin (- fract * M_PI) ; // only need one sine call for every point as spaced pi apart.
// inner loop, this is where to optimize!
for (int m = -VIBRATO_SINC_SAMPLES ; m <= VIBRATO_SINC_SAMPLES ; m++)
{
float sinc = sine / (m - fract) ;
sinc *= cos_table [m < 0 ? -m : m] ;
sum += sinc * buffer [(bptr + intpart + m) & BUF_MASK] ; // add sinc wave samples from buffer
sine = -sine ; // alternates sign for sinc
}
*/
// optimized the loop a bit, remove conditionals and value sign alternation
float y = sin (- fract * M_PI) ; // numerator for sinc, pre-scaled by value
int32_t p = bptr + intpart ;
int32_t m = -VIBRATO_SINC_SAMPLES ; // iterate m from -SINC_SAMPLES to +SINC_SAMPLES
float zz = m - fract ; // denominator for sinc
while (true) // loop through -ve m
{
float sinc = y / zz * cos_table [-m] ; // even index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
sinc = -y / zz * cos_table [-m] ; // odd index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
if (m >= -VIBRATO_SINC_SAMPLES_FULL)
break ;
}
while (true) // loop through -ve m
{
float sinc = y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
sinc = -y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
if (m >= 0)
break ; // m no longer negative
}
// m is zero now
zz = -fract ; // need the denominator properly accurate for when m == 0
while (true) // loop through +ve/zero m
{
float sinc = y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
sinc = -y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
if (m >= VIBRATO_SINC_SAMPLES_FULL)
break ;
}
while (true) // loop through +ve/zero m
{
float sinc = y / zz * cos_table [m] ; // even index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
if (m >= VIBRATO_SINC_SAMPLES)
break ;
m ++ ; zz ++ ;
sinc = -y / zz * cos_table [m] ; // odd index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
}
data [i] = int (round (sum / M_PI)) ;
}
mod_phase_acc += mod_phase_inc ; // advance modulation phase
bptr ++ ;
}
transmit (vibrato, 0) ;
release (vibrato) ;
}/* Accurate Vibrato effect 2
* Copyright (c) 2020, Mark Tillotson, markt@chaos.org.uk
*
* 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, development funding 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 <math.h>
#include <stdint.h>
#include <Arduino.h>
#include "accurate_vibrato2.h"
#define BUF_MASK 0x1FF // 4 blocks
#if AUDIO_BLOCK_SAMPLES != 128
# error "Only works for AUDIO_BLOCK_SAMPLES == 128 currently"
#endif
#define WORD_MAX 4294967296.0
#define MAX_DEVIATION (190 - VIBRATO_SINC_SAMPLES)
float AudioEffectHighQualityVibrato::cos_table [VIBRATO_SINC_SAMPLES+1] ;
AudioEffectHighQualityVibrato::AudioEffectHighQualityVibrato(void) : AudioStream(1, inputQueueArray)
{
mod_phase_acc = 0 ;
modulation (5.0, 2.0) ; // 5Hz, 2% vibrato (6% is a semitone)
for (int i = 0 ; i < 4 * AUDIO_BLOCK_SAMPLES ; i++)
buffer[i] = 0.0 ;
// interpolation from buffer with max offset +/- 1.5 * 128, so 3 buffers needed + 1 for buffering
bptr = 3 * AUDIO_BLOCK_SAMPLES / 2 ;
cptr = 0 ;
while (cptr < 3 * AUDIO_BLOCK_SAMPLES) // insert new data starting at last block, with first 3 doing the tap
buffer [cptr++] = 0 ;
// initialize cosine taper table.
if (cos_table [0] == 0.0)
{
for (int m = 0 ; m <= VIBRATO_SINC_SAMPLES+1 ; m++)
if (m < VIBRATO_SINC_SAMPLES_FULL)
cos_table [m] = 1.0 ;
else
cos_table [m] = 0.5 * (1.0 + cos ((m - VIBRATO_SINC_SAMPLES_FULL) * M_PI / \
(VIBRATO_SINC_SAMPLES - VIBRATO_SINC_SAMPLES_FULL))) ;
}
}
void AudioEffectHighQualityVibrato::modulation (float hertz, float percent)
{
if (hertz < 0.5) hertz = 0.5 ;
if (hertz > 20.0) hertz = 20.0 ;
if (percent > 10.0) percent = 10.0 ;
mod_phase_inc = (uint32_t) int (round (hertz * WORD_MAX / AUDIO_SAMPLE_RATE)) ;
float mod_d = (percent / 100.0) * AUDIO_SAMPLE_RATE / (2 * M_PI * hertz) ;
if (mod_d > MAX_DEVIATION) mod_d = MAX_DEVIATION ;
if (mod_d < 0.0) mod_d = 0.0 ;
mod_depth = mod_d ;
//Serial.printf ("setup vibrato, hertz %f, percent %f, inx %i, mod_d %f\n", hertz, percent, mod_phase_inc, mod_d) ;
}
void AudioEffectHighQualityVibrato::update(void)
{
audio_block_t * vibrato = allocate () ;
if (vibrato == NULL)
return ;
int16_t * data ;
audio_block_t * new_block = receiveReadOnly (0) ;
// copy new_block to circular buffer or zero
if (new_block != NULL)
{
data = new_block->data ;
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
buffer [cptr++] = data [i] ;
release (new_block) ;
}
else
{
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
buffer [cptr++] = 0 ;
}
cptr &= BUF_MASK ;
data = vibrato->data ;
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
{
float mod = mod_depth * sin (mod_phase_acc / WORD_MAX * 2 * M_PI) ; // scale by mod depth
int intpart = int (floor (mod)) ;
float fract = mod - intpart ; // fractional part of sample offset
if (fract == 0.0) // perfectly aligned case, would involve div by zero if we didn't handle specially.
{
data [i] = buffer [(bptr + intpart) & BUF_MASK] ;
}
else
{
float sum = 0.0;
/*
float sine = sin (- fract * M_PI) ; // only need one sine call for every point as spaced pi apart.
// inner loop, this is where to optimize!
for (int m = -VIBRATO_SINC_SAMPLES ; m <= VIBRATO_SINC_SAMPLES ; m++)
{
float sinc = sine / (m - fract) ;
sinc *= cos_table [m < 0 ? -m : m] ;
sum += sinc * buffer [(bptr + intpart + m) & BUF_MASK] ; // add sinc wave samples from buffer
sine = -sine ; // alternates sign for sinc
}
*/
// optimized the loop a bit, remove conditionals and value sign alternation
float y = sin (- fract * M_PI) ; // numerator for sinc, pre-scaled by value
int32_t p = bptr + intpart ;
int32_t m = -VIBRATO_SINC_SAMPLES ; // iterate m from -SINC_SAMPLES to +SINC_SAMPLES
float zz = m - fract ; // denominator for sinc
while (true) // loop through -ve m
{
float sinc = y / zz * cos_table [-m] ; // even index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
sinc = -y / zz * cos_table [-m] ; // odd index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
if (m >= -VIBRATO_SINC_SAMPLES_FULL)
break ;
}
while (true) // loop through -ve m
{
float sinc = y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
sinc = -y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
if (m >= 0)
break ; // m no longer negative
}
// m is zero now
zz = -fract ; // need the denominator properly accurate for when m == 0
while (true) // loop through +ve/zero m
{
float sinc = y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
sinc = -y / zz ;
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
if (m >= VIBRATO_SINC_SAMPLES_FULL)
break ;
}
while (true) // loop through +ve/zero m
{
float sinc = y / zz * cos_table [m] ; // even index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
if (m >= VIBRATO_SINC_SAMPLES)
break ;
m ++ ; zz ++ ;
sinc = -y / zz * cos_table [m] ; // odd index
sum += sinc * buffer [(p+m) & BUF_MASK] ;
m ++ ; zz ++ ;
}
data [i] = int (round (sum / M_PI)) ;
}
mod_phase_acc += mod_phase_inc ; // advance modulation phase
bptr ++ ;
}
transmit (vibrato, 0) ;
release (vibrato) ;
}

@ -0,0 +1,61 @@
/* Accurate Vibrato object 2
* Copyright (c) 2020, Mark Tillotson, markt@chaos.org.uk
*
* 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, development funding 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.
*/
#ifndef __ACCURATE_VIBRATO2_h__
#define __ACCURATE_VIBRATO2_h__
#include <Arduino.h>
#include <Audio.h>
#include <AudioStream.h>
#if AUDIO_BLOCK_SAMPLES != 128
# error "Only works for AUDIO_BLOCK_SAMPLES == 128 currently"
#endif
#define VIBRATO_SINC_SAMPLES 16 // assumed in code to be even
#define VIBRATO_SINC_SAMPLES_FULL 10 // the range of sinc samples with no tapering applied
// so the sinc index varies -16 -- +16, with cosine tapering applied outside the range -10 -- +10
// Reasonable compromise between speed and effect accuracy. For better accuracy use 24 & 16
// about 10% CPU on 600MHz T4, 40% CPU on 150MHz T4
class AudioEffectHighQualityVibrato : public AudioStream
{
public:
AudioEffectHighQualityVibrato(void) ;// : AudioStream(1, inputQueueArray) {}
void modulation (float hertz, float percent) ;
virtual void update(void);
private:
audio_block_t * inputQueueArray[1];
uint32_t mod_phase_acc ;
uint32_t mod_phase_inc ;
float buffer [4 * AUDIO_BLOCK_SAMPLES] ;
int bptr, cptr ; // circular buffer indices
float mod_depth ; // fixpoint 8.24 modulation depth in samples, must stay in range -126.0 -> +126.0
static float cos_table [VIBRATO_SINC_SAMPLES+1] ;
};
#endif
Loading…
Cancel
Save