Voice changer

What are you developing?
racemaniac
Posts: 617
Joined: Sat Nov 07, 2015 9:09 am

Voice changer

Post by racemaniac » Fri Jun 10, 2016 6:09 pm

As i said in the 10$-o-scope project, i borrowed some of its code to do a fun little project

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

Code: Select all

#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);
  }
}

User avatar
ahull
Posts: 1650
Joined: Mon Apr 27, 2015 11:04 pm
Location: Sunny Scotland
Contact:

Re: Voice changer

Post by ahull » Sat Jun 11, 2016 2:08 am

I'll need to have a play. I may adapt for one of the STM32F103 boards I have with built in DAC, or find my existing DAC board (whichever emerges from the clutter first). The STM32 as Vocoder sounds like a fun project. Exterminate.. exterminate... or maybe a touch of the Cylon voice from Battle Star Galactica. Bring on the heartless robots.



You might like to have a crack at digital ring modulation too, I think that is probably how the Daleks are synthesised.
- Andy Hull -

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: Voice changer

Post by RogerClark » Sat Jun 11, 2016 7:51 am

I have several boards with F103R's and F103V's both of which have built in DAC's. So it should be possible to output directly, without using any external hardware

I suspect the big limitation is RAM, as 20k on the F103C (and even the 64k on the F103V etc), is not going to give you a very long time delay buffer to implement things like echo.

The FFT project in another thread, may produce some interesting output audio, if the audio was reconstructed from the FFT. However I think the speed of the FFT is going to be to slow to make it usable for voice changing etc, though I suppose it may create some interesting effects

(Though I'm not precisely sure how you reconstruct a waveform based on a FFT)

racemaniac
Posts: 617
Joined: Sat Nov 07, 2015 9:09 am

Re: Voice changer

Post by racemaniac » Sat Jun 11, 2016 9:52 am

RogerClark wrote:I have several boards with F103R's and F103V's both of which have built in DAC's. So it should be possible to output directly, without using any external hardware

I suspect the big limitation is RAM, as 20k on the F103C (and even the 64k on the F103V etc), is not going to give you a very long time delay buffer to implement things like echo.

The FFT project in another thread, may produce some interesting output audio, if the audio was reconstructed from the FFT. However I think the speed of the FFT is going to be to slow to make it usable for voice changing etc, though I suppose it may create some interesting effects

(Though I'm not precisely sure how you reconstruct a waveform based on a FFT)
If i remember correctly, reversing a FFT is basically the same as doing another FFT, it works both ways. But it's thus equally computationally heavy XD

racemaniac
Posts: 617
Joined: Sat Nov 07, 2015 9:09 am

Re: Voice changer

Post by racemaniac » Sat Jun 11, 2016 8:25 pm

I've now hooked it up to the microphone port of my pc, and started doing some more experiments with it :)
Now settled for a buffer size of 512 samples, which seems to work best :).
And i added a ring modulation to it, and it's also working well :)

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: Voice changer

Post by RogerClark » Sat Jun 11, 2016 10:00 pm

Are you using a pre generated ring modulator input buffer, or generating it on the fly?


Re:FFT

i found this

https://www.dsprelated.com/showarticle/800.php

It looks like the forward FFT can be used to create an inverse FFT, wthout too much additional processing.

However, the speed of the FFT will be too slow to be usable for audio.

I was thinking that perhaps an interesting output from the FFT would be to sine wave frequencies and amplitudes of all the points on the spectrum analysis, and sum them together, (using the amplitudes from the spectum analysis)

But perhaps even this would take too long to process.

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: Voice changer

Post by RogerClark » Sat Jun 11, 2016 10:39 pm

On a related note...

I remember the "Specdrum" for the Sinclair Zx Spectrum computer.

https://www.dsprelated.com/showarticle/800.php

I remember, as a kid, working out that this just used a resistor ladder DAC.
So somehow I got hold of the software, and built my own simple DAC, at virtually no cost.

(I've no idea where I would have gotten the software from, as this was pre-internet and even pre modem; so perhaps someone I knew had the real hardware etc and let me "borrow" the program)

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: Voice changer

Post by RogerClark » Sun Jun 12, 2016 1:06 am

@racemaniac

I've taken a look at your code, but I don't understand 2 things

The I2C code has a comments about DMA
device->regs->CR2 = I2C_CR2_ITERREN | 36; //dma enabled, peripheral frequency is 36Mhz
but as far as I can tell, you are not using DMA to output to I2C, you are calling i2c_write() from loop()?

You seem to be sub-sampling the input buffer into the output buffer using delayMicroseconds

Am I reading the code correctly?

racemaniac
Posts: 617
Joined: Sat Nov 07, 2015 9:09 am

Re: Voice changer

Post by racemaniac » Sun Jun 12, 2016 3:27 am

RogerClark wrote:Are you using a pre generated ring modulator input buffer, or generating it on the fly?


Re:FFT

i found this

https://www.dsprelated.com/showarticle/800.php

It looks like the forward FFT can be used to create an inverse FFT, wthout too much additional processing.

However, the speed of the FFT will be too slow to be usable for audio.

I was thinking that perhaps an interesting output from the FFT would be to sine wave frequencies and amplitudes of all the points on the spectrum analysis, and sum them together, (using the amplitudes from the spectum analysis)

But perhaps even this would take too long to process.
just using a pregenerated buffer with a sine (wel actually half of a sine, just the positive part, it's all i need :) ).
RogerClark wrote:@racemaniac

I've taken a look at your code, but I don't understand 2 things

The I2C code has a comments about DMA
device->regs->CR2 = I2C_CR2_ITERREN | 36; //dma enabled, peripheral frequency is 36Mhz
but as far as I can tell, you are not using DMA to output to I2C, you are calling i2c_write() from loop()?

You seem to be sub-sampling the input buffer into the output buffer using delayMicroseconds

Am I reading the code correctly?
yeah, sorry for the bad comments XD
it's something that started off from my DMA code, but it's indeed just writing to the data register at whatever rate you want :).
You set up the I2C port at a speed that's fast enough for the max speed you want, but write to the data register whenever you actually need to send something. It's indeed not using DMA, but the clockstretching of I2C.
And if i still enable some interrupts, that should probably also be removed. My original code used some changes i made to the maple core where i could inject my own interrupt handlers(but still call the original handlers for ports that weren't using my framework), but that wouldn't work for you guys, so i altered the code (but maybe not all of it XD) to run without interrupts, and just polling of the status registers.

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: Voice changer

Post by RogerClark » Sun Jun 12, 2016 4:22 am

Thanks

I should be able to easily modify the code to use the DAC on the F103V

Post Reply