The octaver effect generates an octaved signal which is derived from the original guitar input signal by halving (octave-down) or doubling (octave-up) the frequency. In order to do it, a small portion of the signal is stored in a buffer and then played at half or double rate.
The octaver.ino pedal can do an octave-down and octave-up. The potentiometer 0 controls the mode, the first 1/3 of the turn is octave-down, the middle point is normal sound and the last 1/3 of the turn is octave-up.
To implement the effect two buffers are used in parallel to store the signal and playing it again at double rate. The buffers are written at the same time but they are read with different indexes. When buffer A is starting, buffer B is in the middle point, and therefore when buffer A is in the middle point, buffer B is finishing. Each buffer writes in a different DAC (DAC0 and DAC1) which are hardware summed out of Arduino.
I did this buffer mistmatch in order to avoid the popping effect which is very common in digital pitch shifted effects.
- Potentiometer0: The rotation selects octave-down, normal or octave-up mode.
- Potentiometer1: Not used.
- Potentiometer2: Volume control.
- Mix Switch: when selected it mixes the original and the octaved signal
octaver.ino// Licensed under a Creative Commons Attribution 3.0 Unported License.
// Based on rcarduino.blogspot.com previous work.
// www.electrosmash.com/pedalshield
/*octaver.ino creates an octave-up or octave-down signal from the original one.*/
int in_ADC0, in_ADC1; //variables for 2 ADCs values (ADC0, ADC1)
int POT0, POT1, POT2, out_DAC0, out_DAC1; //variables for 3 pots (ADC8, ADC9, ADC10)
int LED = 3;
int FOOTSWITCH = 7;
int TOGGLE = 2;
#define MAX_DELAY 10000
uint16_t sDelayBuffer0[MAX_DELAY-1];
uint16_t sDelayBuffer1[MAX_DELAY-1];
unsigned int write_pt=0;
unsigned int read_pt_A=0, read_pt_B= MAX_DELAY/2;
unsigned int Delay_Depth, increment, divider=0, buffer0, buffer1;
void setup()
{
//turn on the timer clock in the power management controller
pmc_set_writeprotect(false);
pmc_enable_periph_clk(ID_TC4);
//we want wavesel 01 with RC
TC_Configure(TC1, 1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
TC_SetRC(TC1, 1, 238); // sets <> 44.1 Khz interrupt rate 109
TC_Start(TC1, 1);
// enable timer interrupts on the timer
TC1->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;
TC1->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;
//Enable the interrupt in the nested vector interrupt controller
//TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number
//(=(1*3)+1) for timer1 channel1
NVIC_EnableIRQ(TC4_IRQn);
//ADC Configuration
ADC->ADC_MR |= 0x80; // DAC in free running mode.
ADC->ADC_CR=2; // Starts ADC conversion.
ADC->ADC_CHER=0x1CC0; // Enable ADC channels 0,1,8,9 and 10
//DAC Configuration
analogWrite(DAC0,0); // Enables DAC0
analogWrite(DAC1,0); // Enables DAC0
}
void loop()
{
//Read the ADCs
while((ADC->ADC_ISR & 0x1CC0)!=0x1CC0);// wait for ADC 0,1,8,9,10 conversion complete.
in_ADC0=ADC->ADC_CDR[7]; // read data from ADC0
in_ADC1=ADC->ADC_CDR[6]; // read data from ADC1
POT0=ADC->ADC_CDR[10]; // read data from ADC8
POT1=ADC->ADC_CDR[11]; // read data from ADC9
POT2=ADC->ADC_CDR[12]; // read data from ADC10
}
void TC4_Handler() //Interrupt at 44.1KHz rate (every 22.6us)
{
TC_GetStatus(TC1, 1); //Clear status interrupt to be fired again.
//Store current readings
sDelayBuffer0[write_pt] = in_ADC0;
sDelayBuffer1[write_pt] = in_ADC1;
//Adjust Delay Depth based in pot2 position.
Delay_Depth = MAX_DELAY-1;
//Increse/reset delay counter.
write_pt++;
if(write_pt >= Delay_Depth) write_pt = 0;
out_DAC0 = ((sDelayBuffer0[read_pt_A]));
out_DAC1 = ((sDelayBuffer1[read_pt_B]));
if (POT0>2700)
{
read_pt_A = read_pt_A + 2;
read_pt_B = read_pt_B + 2;
}
else if (POT0>1350)
{
read_pt_A = read_pt_A + 1;
read_pt_B = read_pt_B + 1;
}
else
{
divider++;
if (divider>=2)
{
read_pt_A = read_pt_A + 1;
read_pt_B = read_pt_B + 1;
divider=0;
}
}
if(read_pt_A >= Delay_Depth) read_pt_A = 0;
if(read_pt_B >= Delay_Depth) read_pt_B = 0;
//Add volume control with POT2
out_DAC0=map(out_DAC0,0,4095,1,POT2);
out_DAC1=map(out_DAC1,0,4095,1,POT2);
//Write the DACs
dacc_set_channel_selection(DACC_INTERFACE, 0); //select DAC channel 0
dacc_write_conversion_data(DACC_INTERFACE, out_DAC0);//write on DAC
dacc_set_channel_selection(DACC_INTERFACE, 1); //select DAC channel 1
dacc_write_conversion_data(DACC_INTERFACE, out_DAC1);//write on DAC
}
You can listen this
Octaver effect in SoundCloud.
There are a lot of ways to implement octavers or pitch-shifting effects. In this code I have implemented a
Delay Based Pitch Shifting Algorithm described by D. Goswami, H. Narula and C. Rogers.
To improve the performance and remove the audible popping a fade out technique could be used:
Two 30ms long buffers hold the channel buffers A and B. Both channels begin with a 30ms delay but the gain of channel A starts at one while the gain of channel B is zero. The delay of channel A is steadily decreased and as the delay starts to approach zero, the gain begins to steadily decrease while channel B’s gain begins to increase and its delay decrease. Here the gain of channel A and channel B are both decreasing and increasing respectively at the same rate to maintain their sum at one, keeping a constant amplitude out. This crossover area between the two gains is referred to as crossfading because one channel fades out and the other fades in. The crossfading helps the transition from a close to 0ms delay to 30ms delay seem continuous and smooth.