I was needing a vibrato effect, and one I found in some magazine article in Circuit Cellar happened to be
broken (used a triangle wave to modulate the sample interpolation, which produces step-changes in
frequency!).
So I wrote a simple one for myself I thought it might be useful to share.
It has a limit of about 126 samples +/- for the interpolation time shift, which is internally enforced, you
just need to provide a modulation frequency and percentage frequency wobble. Latency is 2.9ms I think.
It uses naive linear interpolation (which produces some distortion, but isn't too obvious I think). I've
also been investigating higher quality methods of doing vibrato, its a interesting problem (ie tough).
There maybe a few bugs, I'm not totally happy with how it goes about allocating Audio blocks for its internal
buffer for instance, it might be simpler if it used its own internal buffer.
vibrato.h
vibrato.cpp
broken (used a triangle wave to modulate the sample interpolation, which produces step-changes in
frequency!).
So I wrote a simple one for myself I thought it might be useful to share.
It has a limit of about 126 samples +/- for the interpolation time shift, which is internally enforced, you
just need to provide a modulation frequency and percentage frequency wobble. Latency is 2.9ms I think.
It uses naive linear interpolation (which produces some distortion, but isn't too obvious I think). I've
also been investigating higher quality methods of doing vibrato, its a interesting problem (ie tough).
There maybe a few bugs, I'm not totally happy with how it goes about allocating Audio blocks for its internal
buffer for instance, it might be simpler if it used its own internal buffer.
vibrato.h
Code:
/* Vibrato object
* Copyright (c) 2020, Mark Tillotson
*
* 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 __VIBRATO_h__
#define __VIBRATO_h__
#include <Arduino.h>
#include <Audio.h>
#include <AudioStream.h>
#include "arm_math.h"
class AudioEffect_Vibrato : public AudioStream
{
public:
AudioEffect_Vibrato(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 < 3 ; i++)
blocks[i] = NULL ;
vibrato = NULL ;
}
void modulation (float hertz, float percent) ;
virtual void update(void);
protected:
int16_t sine_lookup (uint32_t phase) ;
private:
audio_block_t * inputQueueArray[1];
audio_block_t * blocks [3] ;
audio_block_t * vibrato ;
uint32_t mod_phase_acc ;
uint32_t mod_phase_inc ;
int32_t mod_d ; // fixpoint 8.8 modulation depth in samples, must stay in range -126.0 -> +126.0
};
#endif
vibrato.cpp
Code:
/* vibrato effect
* Copyright (c) 2020, Mark Tillotson
*
* 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 "vibrato.h"
// data_waveforms.c
extern "C" {
extern const int16_t AudioWaveformSine[257];
}
int16_t AudioEffect_Vibrato::sine_lookup (uint32_t phase)
{
unsigned int index = phase >> 24 ;
int fract = (phase >> 8) & 0xFFFF ;
int32_t v1 = AudioWaveformSine[index] * (0x10000 - fract) ;
int32_t v2 = AudioWaveformSine[index+1] * fract ;
return (int16_t) ((v1 + v2) >> 16) ;
}
#define WORD_MAX 4294967296.0
void AudioEffect_Vibrato::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_depth = (percent / 100.0) * AUDIO_SAMPLE_RATE / (2 * M_PI * hertz) ;
if (mod_depth > 126.0) mod_depth = 126.0 ;
if (mod_depth < 0) mod_depth = 0 ;
mod_d = int (round (mod_depth * (1<<8))) ; // need a conversion factor to fix8.8
Serial.printf ("setup vibrato, hertz %f, percent %f, inx %i, mod_d 0x%x\n", hertz, percent, mod_phase_inc, mod_d) ;
}
void AudioEffect_Vibrato::update(void)
{
audio_block_t * new_block = receiveReadOnly (0) ;
// ensure we are setup
for (int i = 0 ; i < 3 ; i++)
{
if (blocks[i] == NULL)
{
blocks[i] = allocate () ;
if (blocks[i] != NULL)
{
for (int j = 0 ; j < AUDIO_BLOCK_SAMPLES ; j++)
blocks[i]->data[j] = 0 ;
}
}
}
// allocate starved of blocks? Abandon
for (int i = 0 ; i < 3 ; i++)
if (blocks[i] == NULL)
{
if (new_block) release (new_block) ;
return ;
}
if (new_block == NULL)
return ;
// setup new one
vibrato = allocate () ;
if (vibrato == NULL)
{
if (new_block) release (new_block) ;
return ;
}
release (blocks[0]) ; // release oldest block
blocks[0] = blocks[1] ;
blocks[1] = blocks[2] ;
blocks[2] = new_block ; // get new block, have 4 blocks contiguous samples in the buffer.
for (int i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
{
int16_t mod = sine_lookup (mod_phase_acc) ; // modulation waveform, fix 1.15
mod = (int16_t) ((mod_d * mod) >> 16) ; // scale by mod depth, to yield fix8.8 sample count
if (mod > 0x7E00) mod = 0x7E00 ;
if (mod < -0x7E00) mod = -0x7E00 ;
int intpart = mod >> 8 ; // integer part of sample offset
int fract = mod & 0xFF ; // fractional part of sample offset, 8 bits
int j = AUDIO_BLOCK_SAMPLES + i + intpart ; // index into buffer
int32_t val1 = blocks [j>>7]->data[j&0x7F] ; // get two adjacent samples
j += 1 ;
int32_t val2 = blocks [j>>7]->data[j&0x7F] ;
int32_t interpolated = (0x100 - fract) * val1 + fract * val2 ;
vibrato->data [i] = (int16_t) ((interpolated + 0x80) >> 8) ;
mod_phase_acc += mod_phase_inc ; // advance modulation phase
}
transmit (vibrato, 0) ;
release (vibrato) ;
}