Voice changer
racemaniac Fri Jun 10, 2016 6:09 pm
I read you can change the pitch of a voice by sampling sound into a circular buffer, and then reading from that buffer at a different rate, so no need for fourier transformations and difficult mathematics.
So i borrowed the DMA ADC code from the scope project (altered it a bit for this purpose), and connected a MP4725 DAC to my maple mini, and had some fun testing this little trick ![]()
How to wire it (on a maple mini):
I2C port 1 gets a mp4725 DAC
pin 3: analog input for the microphone
pin 4: analog input for a potentiometer that will alter the pitch
How it works:
I modified the dma code of this project to
– sample slower (about 35 khz)
– sample 2 pins simultaneously iso 1 pin interleaved
– run in circular mode
– only put the data of the sound sample pin into the memory (so i’m sampling 16 bits via dma, even though the 2 channels together are writing all 32 bits of the data register). I read the other pin directly from the dataregister when i need it.
The playback
– uses i2c clockstretching for a variable sampling rate
– tries to smooth out clicks (experiment with it, seems to work fairly well)
feel free to experiment with this fun little project ![]()
And let me know if you find better settings for buffer size & click removal
First thing that can be tried is a higher sampling rate, you should be able to push the DAC to 60khz with this code, and the sampling rate for the microphone can naturally also go a lot faster
.
Also better would be using a timer to send the samples
, but i just quickly wrote it with a delay
.
Also fun to try, program some special effects on it:
– maybe some echo?
– i read a dalek vocie is made by multiplying a sound signal by a sine wave (i think 100hz or something like that)
– feel free to add ideas
#include <dma_private.h>
#include <libmaple/adc.h>
#include <wirish.h>
#include <libmaple/i2c.h>
#define ADC_CR1_SIMULTANEOUS 0x60000 // simultaneous mode DUAL MODE bits 19-16
#define BufferSize 2048
uint16_t bufferPos = 0;
const int8_t analogInPin = PB0;
const int8_t analogInPin2 = PA7;
uint16_t dataBuffer[BufferSize];
const uint16_t clickTreshold = 100;
uint16_t lastSample = 2048;
uint16_t* analog2 = (uint16_t*)&(ADC1->regs->DR) + 1;
int modulationDelay = 40;
int wait = 0;
void i2c_str_InitPort(i2c_dev *device, uint8_t divider);
void i2c_str_StartSending(i2c_dev *device, uint8_t address);
void i2c_str_sendBytes(i2c_dev *device, uint8_t data, uint8_t data2);
void i2c_str_ReleasePort(i2c_dev *device);
void i2c_str_StopSending(i2c_dev *device);
#define I2C_STRETCH_SB_TIMEOUT 10000 //how many times we will check the status registers to see if the device is starting
#define I2C_STRETCH_DIV_400KHZ 30
#define I2C_STRETCH_DIV_600KHZ 20
#define I2C_STRETCH_DIV_800KHZ 15
#define I2C_STRETCH_DIV_1200KHZ 10
void setup()
{
Serial.begin();
pinMode(4, INPUT_ANALOG);
setADC();
startSampling ();
i2c_str_InitPort(I2C1, I2C_STRETCH_DIV_600KHZ);
i2c_str_StartSending(I2C1, 0x60);
}
void loop()
{
uint16_t sample = dataBuffer[bufferPos];
if((uint16_t)(sample - lastSample) > clickTreshold && (uint16_t)(lastSample - sample) > clickTreshold)
{
if(sample > lastSample)
sample = lastSample + clickTreshold;
else
sample = lastSample - clickTreshold;
}
i2c_str_sendBytes(I2C1, (uint8_t)(sample>>8), sample & 255);
bufferPos = (++bufferPos)%BufferSize;
lastSample = sample;
delayMicroseconds(20 + (*analog2>>7));
}
void startSampling ()
{
dma_init(DMA1);
adc_dma_enable(ADC1);
dma_setup_transfer(DMA1, DMA_CH1, &ADC1->regs->DR, DMA_SIZE_16BITS,
dataBuffer, DMA_SIZE_16BITS, (DMA_MINC_MODE | DMA_CIRC_MODE));// Receive buffer DMA
dma_set_num_transfers(DMA1, DMA_CH1, BufferSize);
dma_enable(DMA1, DMA_CH1);
}
void setADC ()
{
// const adc_dev *dev = PIN_MAP[analogInPin].adc_device;
int pinMapADCin = PIN_MAP[analogInPin].adc_channel;
int pinMapADCin2 = PIN_MAP[analogInPin2].adc_channel;
adc_set_prescaler(ADC_PRE_PCLK2_DIV_8);
adc_set_sample_rate(ADC1, ADC_SMPR_239_5);
adc_set_sample_rate(ADC2, ADC_SMPR_239_5);
// adc_reg_map *regs = dev->regs;
adc_set_reg_seqlen(ADC1, 1);
ADC1->regs->SQR3 = pinMapADCin;
ADC1->regs->CR2 |= ADC_CR2_CONT; // | ADC_CR2_DMA; // Set continuous mode and DMA
ADC1->regs->CR1 |= ADC_CR1_SIMULTANEOUS;
ADC1->regs->CR2 |= ADC_CR2_SWSTART;
ADC2->regs->CR2 |= ADC_CR2_CONT; // ADC 2 continuos
ADC2->regs->SQR3 = pinMapADCin2;
}
void adc_dma_enable(const adc_dev * dev) {
bb_peri_set_bit(&dev->regs->CR2, ADC_CR2_DMA_BIT, 1);
}
void adc_dma_disable(const adc_dev * dev) {
bb_peri_set_bit(&dev->regs->CR2, ADC_CR2_DMA_BIT, 0);
}
static void (*i2c_str_old_error_handler)(i2c_dev *dev);
void i2c_str_InitPort(i2c_dev *device, uint8_t divider)
{
i2c_peripheral_disable(device);
i2c_bus_reset(device);
i2c_init(device);//initialize it
i2c_config_gpios(device);//configure the gpios
device->regs->CR2 = I2C_CR2_ITERREN | 36; //dma enabled, peripheral frequency is 36Mhz
device->regs->CCR = I2C_CCR_FS | divider; //default 30
device->regs->TRISE = 11;
}
void i2c_str_ReleasePort(i2c_dev *device)
{
i2c_str_StopSending(device);
}
void i2c_str_StartSending(i2c_dev *device, uint8_t address)
{
i2c_peripheral_enable(device);//enable the port
i2c_start_condition(device);//set the start condition
uint32_t sr1 = device->regs->SR1;
uint32_t sr2 = device->regs->SR2;
uint16_t wait = 0;
while(!(sr1&I2C_SR1_SB))
{
if(wait++ > I2C_STRETCH_SB_TIMEOUT)
{
i2c_peripheral_disable(device);
return;
}
sr1 = device->regs->SR1;
sr2 = device->regs->SR2;
}
i2c_write(device, address<<1);//write the address of the device you want to contact (shifted 1 to the left)
}
void i2c_str_StopSending(i2c_dev *device)
{
i2c_stop_condition(device);
i2c_peripheral_disable(device);
}
bool i2c_str_IsSending(i2c_dev *device)
{
return !(device->regs->SR1 & I2C_SR1_TXE);
}
bool i2c_str_IsError(i2c_dev *device)
{
return device->regs->SR1 & (I2C_SR1_BERR | I2C_SR1_ARLO | I2C_SR1_AF | I2C_SR1_PECERR | I2C_SR1_TIMEOUT);
}
bool i2c_str_PortEnabled(i2c_dev *device)
{
return device->regs->CR1 & I2C_CR1_PE;
}
void i2c_str_sendByte(i2c_dev *device, uint8_t data)
{
uint32_t sr1 = device->regs->SR1;
uint32_t sr2 = device->regs->SR2;
if(sr1&(I2C_SR1_BTF | I2C_SR1_TXE | I2C_SR1_ADDR))
{
i2c_write(device, data);
}
}
void i2c_str_sendBytes(i2c_dev *device, uint8_t data, uint8_t data2)
{
uint32_t sr1 = device->regs->SR1;
uint32_t sr2 = device->regs->SR2;
if(sr1&(I2C_SR1_BTF | I2C_SR1_ADDR))
{
i2c_write(device, data);
i2c_write(device, data2);
}
}
