From cadc2d9b9009b019756b3d38bc8101958849430a Mon Sep 17 00:00:00 2001 From: Holger Wirtz Date: Wed, 26 Aug 2020 14:51:55 +0200 Subject: [PATCH] first commit --- README.md | 1 + accurate_vibrato2.cpp | 383 ++++++++++++++++++++++++++++++++++++++++++ accurate_vibrato2.h | 61 +++++++ 3 files changed, 445 insertions(+) create mode 100644 README.md create mode 100644 accurate_vibrato2.cpp create mode 100644 accurate_vibrato2.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c380b7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +From: https://forum.pjrc.com/threads/62556-Simple-Vibrato-effect diff --git a/accurate_vibrato2.cpp b/accurate_vibrato2.cpp new file mode 100644 index 0000000..a76b885 --- /dev/null +++ b/accurate_vibrato2.cpp @@ -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 +#include +#include +#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 +#include +#include +#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) ; +} diff --git a/accurate_vibrato2.h b/accurate_vibrato2.h new file mode 100644 index 0000000..9f918bc --- /dev/null +++ b/accurate_vibrato2.h @@ -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 +#include +#include + +#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