I am still hashing things out but I THINK, this might be a doable option for realtime voice processing that many desire. I hopped over to this project after I burned out a second wii nunchuck trying to hash out some wiring issues.
I will still have to play with things some but the voice changer will allow any number of voice effects. I am still trying to hash out the code to get a static Gou'ld or Tok'ra sounding voice, but the adjustable pot allows for many more uses as well. This will also do a decent darth vader voice as well.
This just takes Adafruit's wave shield, max4466 microphone, adjustable potentiometer, arduino uno. and a bit of soldering.
This is based off of this tutorial:
https://learn.adafruit.com/wave-shield-voice-changer
I am trying to streamline the code a little bit more.
/* ADAVOICE is an Arduino-based voice pitch changer plus WAV playback.
Fun for Halloween costumes, comic convention getups and other shenanigans!
Hardware requirements:
- Arduino Uno, Duemilanove or Diecimila (not Mega or Leonardo compatible)
- Adafruit Wave Shield, Speaker attached to Wave Shield output, Battery for portable use
If using the voice pitch changer, you will also need:
- Adafruit Microphone Breakout, 10K potentiometer for setting pitch (or hardcode in sketch)
Software requirements: WaveHC library for Arduino, Demo WAV files on FAT-formatted SD card
Connections:
- 3.3V to mic amp+, 1 leg of potentiometer and Arduino AREF pin
- GND to mic amp-, opposite leg of potentiometer
- Analog pin 0 to mic amp output
- Analog pin 1 to center tap of potentiometer
- Wave Shield output to speaker or amplifier
- Wave shield is assumed wired as in product tutorial
Potentiometer sets playback pitch. Pitch adjustment does NOT work in
realtime -- audio sampling requires 100% of the ADC. Pitch setting is
read at startup (or reset) and after a WAV finishes playing. POINT SPEAKER AWAY FROM MIC to avoid feedback.
Written by Adafruit industries, with portions adapted from the 'PiSpeakHC' sketch included with WaveHC library.
*/
#include <WaveHC.h>
#include <WaveUtil.h>
WaveHC wave; // This is the only wave (audio) object, -- we only play one at a time
#define ADC_CHANNEL 0 // Microphone on Analog pin 0. Wave shield DAC: digital pins 2, 3, 4, 5
#define DAC_CS_PORT PORTD
#define DAC_CS PORTD2
#define DAC_CLK_PORT PORTD
#define DAC_CLK PORTD3
#define DAC_DI_PORT PORTD
#define DAC_DI PORTD4
#define DAC_LATCH_PORT PORTD
#define DAC_LATCH PORTD5
uint16_t in = 0, out = 0, xf = 0, nSamples; uint8_t adc_save;// Audio sample counters, ADC Mode
// WaveHC didn't declare it's working buffers private or static,
// so we can be sneaky and borrow the same RAM for audio sampling!
extern uint8_t
buffer1[PLAYBUFFLEN], // Audio sample LSB
buffer2[PLAYBUFFLEN]; // Audio sample MSB
#define XFADE 16 // Number of samples for cross-fade
#define MAX_SAMPLES (PLAYBUFFLEN - XFADE) // Remaining available audio samples
#define DEBOUNCE 10 // Number of iterations before button 'takes'
//////////////////////////////////// SETUP
void setup() {
uint8_t i; Serial.begin(9600); pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); digitalWrite(2, HIGH); // Chip select, Serial clock, Serial data, Latch, Set chip select high
TIMSK0 = 0; analogReference(EXTERNAL); adc_save = ADCSRA; startPitchShift();// 3.3V to AREF, Save ADC setting, Start pitch-shift
}
void loop() { if(!wave.isplaying && !(TIMSK2 & _BV(TOIE2))) startPitchShift(); }
//////////////////////////////////// PITCH-SHIFT CODE
void startPitchShift() { // Read analog pitch setting before starting audio sampling:
int pitch = analogRead(1); Serial.print("Pitch: "); Serial.println(pitch); nSamples = 128;
memset(buffer1, 0, nSamples + XFADE); memset(buffer2, 2, nSamples + XFADE); // (set all samples to 512)
TCCR2A = _BV(WGM21) | _BV(WGM20); // Mode 7 (fast PWM), OC2 disconnected
TCCR2B = _BV(WGM22) | _BV(CS21) | _BV(CS20); // 32:1 prescale
OCR2A = map(pitch, 0, 1023,
F_CPU / 32 / (9615 / 2), // Lowest pitch = -1 octave
F_CPU / 32 / (9615 * 2)); // Highest pitch = +1 octave
// Start up ADC in free-run mode for audio sampling:
DIDR0 |= _BV(ADC0D); // Disable digital input buffer on ADC0
ADMUX = ADC_CHANNEL; // Channel sel, right-adj, AREF to 3.3V regulator
ADCSRB = 0; // Free-run mode
ADCSRA = _BV(ADEN) | // Enable ADC
_BV(ADSC) | // Start conversions
_BV(ADATE) | // Auto-trigger enable
_BV(ADIE) | // Interrupt enable
_BV(ADPS2) | // 128:1 prescale...
_BV(ADPS1) | // ...yields 125 KHz ADC clock...
_BV(ADPS0); // ...13 cycles/conversion = ~9615 Hz
TIMSK2 |= _BV(TOIE2); // Enable Timer2 overflow interrupt
sei(); // Enable interrupts
}
ISR(ADC_vect, ISR_BLOCK) { // ADC conversion complete
// Save old sample from 'in' position to xfade buffer:
buffer1[nSamples + xf] = buffer1[in]; buffer2[nSamples + xf] = buffer2[in]; if(++xf >= XFADE) xf = 0;
// Store new value in sample buffers:
buffer1[in] = ADCL; buffer2[in] = ADCH; if(++in >= nSamples) in = 0; } // MUST read ADCL first!
ISR(TIMER2_OVF_vect) { // Playback interrupt
uint16_t s; uint8_t w, inv, hi, lo, bit; int o2, i2, pos;
// Cross fade around circular buffer 'seam'.
if((o2 = (int)out) == (i2 = (int)in)) {
// Sample positions coincide. Use cross-fade buffer data directly.
pos = nSamples + xf; hi = (buffer2[pos] << 2) | (buffer1[pos] >> 6); lo = (buffer1[pos] << 2) | buffer2[pos]; } //Expand 10-bit data to 12 bits
if((o2 < i2) && (o2 > (i2 - XFADE))) {
// Output sample is close to end of input samples. Cross-fade to avoid click. The shift operations here assume that XFADE is 16;
w = in - out; inv = XFADE - w; // Weight of sample (1-n)+ xfade
pos = nSamples + ((inv + xf) % XFADE); s = ((buffer2[out] << 8) | buffer1[out]) * w + ((buffer2[pos] << 8) | buffer1[pos]) * inv;
hi = s >> 10; lo = s >> 2;// Shift 14 bit result down to 12 bits
} else if (o2 > (i2 + nSamples - XFADE)) { // More cross-fade condition
w = in + nSamples - out; inv = XFADE - w; pos = nSamples + ((inv + xf) % XFADE); s = ((buffer2[out] << 8) | buffer1[out]) * w +
((buffer2[pos] << 8) | buffer1[pos]) * inv; hi = s >> 10; lo = s >> 2;// Shift 14 bit result down to 12 bits
} else { // Input and output counters don't coincide -- just use sample directly.
hi = (buffer2[out] << 2) | (buffer1[out] >> 6); lo = (buffer1[out] << 2) | buffer2[out]; } // Expand 10-bit data to 12 bits
// Might be possible to tweak 'hi' and 'lo' at this point to achieve different voice modulations -- robot effect, etc.?
DAC_CS_PORT &= ~_BV(DAC_CS); // Select DAC Clock out 4 bits DAC config (not in loop because it's constant)
DAC_DI_PORT &= ~_BV(DAC_DI); // 0 = Select DAC A, unbuffered
DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK);
DAC_DI_PORT |= _BV(DAC_DI); // 1X gain, enable = 1
DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK);
for(bit=0x08; bit; bit>>=1) { // Clock out first 4 bits of data
if(hi & bit) DAC_DI_PORT |= _BV(DAC_DI);
else DAC_DI_PORT &= ~_BV(DAC_DI); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); }
for(bit=0x80; bit; bit>>=1) { // Clock out last 8 bits of data
if(lo & bit) DAC_DI_PORT |= _BV(DAC_DI);
else DAC_DI_PORT &= ~_BV(DAC_DI); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); }
DAC_CS_PORT |= _BV(DAC_CS); if(++out >= nSamples) out = 0; // Unselect DAC
}
I will still have to play with things some but the voice changer will allow any number of voice effects. I am still trying to hash out the code to get a static Gou'ld or Tok'ra sounding voice, but the adjustable pot allows for many more uses as well. This will also do a decent darth vader voice as well.
This just takes Adafruit's wave shield, max4466 microphone, adjustable potentiometer, arduino uno. and a bit of soldering.
This is based off of this tutorial:
https://learn.adafruit.com/wave-shield-voice-changer
I am trying to streamline the code a little bit more.
/* ADAVOICE is an Arduino-based voice pitch changer plus WAV playback.
Fun for Halloween costumes, comic convention getups and other shenanigans!
Hardware requirements:
- Arduino Uno, Duemilanove or Diecimila (not Mega or Leonardo compatible)
- Adafruit Wave Shield, Speaker attached to Wave Shield output, Battery for portable use
If using the voice pitch changer, you will also need:
- Adafruit Microphone Breakout, 10K potentiometer for setting pitch (or hardcode in sketch)
Software requirements: WaveHC library for Arduino, Demo WAV files on FAT-formatted SD card
Connections:
- 3.3V to mic amp+, 1 leg of potentiometer and Arduino AREF pin
- GND to mic amp-, opposite leg of potentiometer
- Analog pin 0 to mic amp output
- Analog pin 1 to center tap of potentiometer
- Wave Shield output to speaker or amplifier
- Wave shield is assumed wired as in product tutorial
Potentiometer sets playback pitch. Pitch adjustment does NOT work in
realtime -- audio sampling requires 100% of the ADC. Pitch setting is
read at startup (or reset) and after a WAV finishes playing. POINT SPEAKER AWAY FROM MIC to avoid feedback.
Written by Adafruit industries, with portions adapted from the 'PiSpeakHC' sketch included with WaveHC library.
*/
#include <WaveHC.h>
#include <WaveUtil.h>
WaveHC wave; // This is the only wave (audio) object, -- we only play one at a time
#define ADC_CHANNEL 0 // Microphone on Analog pin 0. Wave shield DAC: digital pins 2, 3, 4, 5
#define DAC_CS_PORT PORTD
#define DAC_CS PORTD2
#define DAC_CLK_PORT PORTD
#define DAC_CLK PORTD3
#define DAC_DI_PORT PORTD
#define DAC_DI PORTD4
#define DAC_LATCH_PORT PORTD
#define DAC_LATCH PORTD5
uint16_t in = 0, out = 0, xf = 0, nSamples; uint8_t adc_save;// Audio sample counters, ADC Mode
// WaveHC didn't declare it's working buffers private or static,
// so we can be sneaky and borrow the same RAM for audio sampling!
extern uint8_t
buffer1[PLAYBUFFLEN], // Audio sample LSB
buffer2[PLAYBUFFLEN]; // Audio sample MSB
#define XFADE 16 // Number of samples for cross-fade
#define MAX_SAMPLES (PLAYBUFFLEN - XFADE) // Remaining available audio samples
#define DEBOUNCE 10 // Number of iterations before button 'takes'
//////////////////////////////////// SETUP
void setup() {
uint8_t i; Serial.begin(9600); pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); digitalWrite(2, HIGH); // Chip select, Serial clock, Serial data, Latch, Set chip select high
TIMSK0 = 0; analogReference(EXTERNAL); adc_save = ADCSRA; startPitchShift();// 3.3V to AREF, Save ADC setting, Start pitch-shift
}
void loop() { if(!wave.isplaying && !(TIMSK2 & _BV(TOIE2))) startPitchShift(); }
//////////////////////////////////// PITCH-SHIFT CODE
void startPitchShift() { // Read analog pitch setting before starting audio sampling:
int pitch = analogRead(1); Serial.print("Pitch: "); Serial.println(pitch); nSamples = 128;
memset(buffer1, 0, nSamples + XFADE); memset(buffer2, 2, nSamples + XFADE); // (set all samples to 512)
TCCR2A = _BV(WGM21) | _BV(WGM20); // Mode 7 (fast PWM), OC2 disconnected
TCCR2B = _BV(WGM22) | _BV(CS21) | _BV(CS20); // 32:1 prescale
OCR2A = map(pitch, 0, 1023,
F_CPU / 32 / (9615 / 2), // Lowest pitch = -1 octave
F_CPU / 32 / (9615 * 2)); // Highest pitch = +1 octave
// Start up ADC in free-run mode for audio sampling:
DIDR0 |= _BV(ADC0D); // Disable digital input buffer on ADC0
ADMUX = ADC_CHANNEL; // Channel sel, right-adj, AREF to 3.3V regulator
ADCSRB = 0; // Free-run mode
ADCSRA = _BV(ADEN) | // Enable ADC
_BV(ADSC) | // Start conversions
_BV(ADATE) | // Auto-trigger enable
_BV(ADIE) | // Interrupt enable
_BV(ADPS2) | // 128:1 prescale...
_BV(ADPS1) | // ...yields 125 KHz ADC clock...
_BV(ADPS0); // ...13 cycles/conversion = ~9615 Hz
TIMSK2 |= _BV(TOIE2); // Enable Timer2 overflow interrupt
sei(); // Enable interrupts
}
ISR(ADC_vect, ISR_BLOCK) { // ADC conversion complete
// Save old sample from 'in' position to xfade buffer:
buffer1[nSamples + xf] = buffer1[in]; buffer2[nSamples + xf] = buffer2[in]; if(++xf >= XFADE) xf = 0;
// Store new value in sample buffers:
buffer1[in] = ADCL; buffer2[in] = ADCH; if(++in >= nSamples) in = 0; } // MUST read ADCL first!
ISR(TIMER2_OVF_vect) { // Playback interrupt
uint16_t s; uint8_t w, inv, hi, lo, bit; int o2, i2, pos;
// Cross fade around circular buffer 'seam'.
if((o2 = (int)out) == (i2 = (int)in)) {
// Sample positions coincide. Use cross-fade buffer data directly.
pos = nSamples + xf; hi = (buffer2[pos] << 2) | (buffer1[pos] >> 6); lo = (buffer1[pos] << 2) | buffer2[pos]; } //Expand 10-bit data to 12 bits
if((o2 < i2) && (o2 > (i2 - XFADE))) {
// Output sample is close to end of input samples. Cross-fade to avoid click. The shift operations here assume that XFADE is 16;
w = in - out; inv = XFADE - w; // Weight of sample (1-n)+ xfade
pos = nSamples + ((inv + xf) % XFADE); s = ((buffer2[out] << 8) | buffer1[out]) * w + ((buffer2[pos] << 8) | buffer1[pos]) * inv;
hi = s >> 10; lo = s >> 2;// Shift 14 bit result down to 12 bits
} else if (o2 > (i2 + nSamples - XFADE)) { // More cross-fade condition
w = in + nSamples - out; inv = XFADE - w; pos = nSamples + ((inv + xf) % XFADE); s = ((buffer2[out] << 8) | buffer1[out]) * w +
((buffer2[pos] << 8) | buffer1[pos]) * inv; hi = s >> 10; lo = s >> 2;// Shift 14 bit result down to 12 bits
} else { // Input and output counters don't coincide -- just use sample directly.
hi = (buffer2[out] << 2) | (buffer1[out] >> 6); lo = (buffer1[out] << 2) | buffer2[out]; } // Expand 10-bit data to 12 bits
// Might be possible to tweak 'hi' and 'lo' at this point to achieve different voice modulations -- robot effect, etc.?
DAC_CS_PORT &= ~_BV(DAC_CS); // Select DAC Clock out 4 bits DAC config (not in loop because it's constant)
DAC_DI_PORT &= ~_BV(DAC_DI); // 0 = Select DAC A, unbuffered
DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK);
DAC_DI_PORT |= _BV(DAC_DI); // 1X gain, enable = 1
DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK);
for(bit=0x08; bit; bit>>=1) { // Clock out first 4 bits of data
if(hi & bit) DAC_DI_PORT |= _BV(DAC_DI);
else DAC_DI_PORT &= ~_BV(DAC_DI); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); }
for(bit=0x80; bit; bit>>=1) { // Clock out last 8 bits of data
if(lo & bit) DAC_DI_PORT |= _BV(DAC_DI);
else DAC_DI_PORT &= ~_BV(DAC_DI); DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); }
DAC_CS_PORT |= _BV(DAC_CS); if(++out >= nSamples) out = 0; // Unselect DAC
}
Comment