Go Down

Topic: Audio Chorus (Delay with STM32) (Read 2067 times) previous topic - next topic

bigbluecoconut

Hello,
I hope no one kills me bc i use an STM32 and ask on the arduino forum.  :smiley-sweat:
But i need a bit of ram, in a small package for this project, so i had to switch to an STM32F103.
The goal is to make a chorus pedal for Guitar.
A chorus effect is generated by mixing a Signal with varying delay(0-30ms) to the original one.
Old effects used BBD chips, modern are completely digital.
I just wanna make the delay digital, the rest of the pedal will be analog.
So the code wont be that complex:

Analog in (12bit > 40khz smapling freq)

Delay from 0-100ms controlled via analog Input

Analog out (PWM or R2R)

I already tried it with a blue-pill board, programmed via Arduino.
My frankenstein-code works somehow, but im not happy with it.
Im a lousy programmer, who copy pastes a lot.  :-[
I used 8bit timer PWM (~280khz) as DAC.
The thing is kinda noisy. especially with low delay.
Even after filtering.
And it makes strange noises when i rapidly change the delaytime.
How could i improve my code?
Or can someone help me?
Or would it be better to use a R2R ladder as DAC?

This is my actual code:

Code: [Select]

int Input_Audio=PA2; //Audio input
int Input_Delay=PA1; //LFO input
int Output_PWM=PB9;  //PWM output
int dcval;
int PWM_Out;
int memory[4000];    //circular buffer
int delaytime;

void setup()
{   
    pinMode(Input_Audio, INPUT);
    pinMode(Input_Delay, INPUT);
    pinMode(Output_PWM, PWM);   
    Timer2.setMode(TIMER_CH1,TIMER_OUTPUTCOMPARE);
    Timer2.setCompare(TIMER_CH1, 1);
    Timer4.setOverflow(255); 
    Timer2.setPrescaleFactor(1);
    Timer2.setOverflow(255);
    Timer2.attachInterrupt(TIMER_CH1,PWM_int);
    Timer2.refresh();
}

void loop() {
  delaytime=analogRead(Input_Delay); 
  for(int i = 0; i < delaytime; i ++)
  {
    PWM_Out = memory[i];         
    dcval=analogRead(Input_Audio);
    dcval=map(dcval,4096,0,255,0);     
    memory[i] = dcval;
  }
}

void PWM_int(void)
{
  pwmWrite(Output_PWM,PWM_Out);   
}


Greetings
David


MarkT

You seem to have no control over the timing - wouldn't that be crucial for controlling a delay?
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

bigbluecoconut

The PWM is timed by an Interrupt, and the sampling rate is determined by the duration of the main loop.
Should i time it with another interrupt?


Grumpy_Mike

#3
Dec 05, 2019, 10:26 pm Last Edit: Dec 05, 2019, 10:30 pm by Grumpy_Mike
Quote
But i need a bit of ram, in a small package for this project
Why not use this it is an 128K byte memory in an 8 pin through hole package.

Quote
Or would it be better to use a R2R ladder as DAC?
No, you can only make a proper 8 bit one with very close tolerance resistors. Most of the R2R tutorials on line are utter crap. Use an MCP 4921, it is a 12 bit proper DAC.

Code: [Select]
int memory[4000];    //circular buffer
You can get that in a Mega or one of the ARM Arduinos like Due, Zero, Nano BLE or one of the family of MKR Boards. Or even a Teensy of varying amounts of processing power.

Quote
I used 8bit timer PWM (~280khz) as DAC. The thing is kinda noisy. especially with low delay. Even after filtering.
Looks like your filtering is wrong, a simple single order RC won't cut it and what was the cutoff frequency?

So using only 8 bits derived from a 12 bit sample will not be so good. But the way you convert it to 8 bits is also poor. Just use:-

Code: [Select]
memory[i] = analogRead(Input_Audio) >> 4;

You seem to be storing 16 bits in a buffer but only using 8 bits, so the buffer is twice as big as it needs to be and takes twice as long to put an number into it and out of it than it needs to take. With the code above you only need a byte buffer not an int one.
You will get glitches as well because you have not disabled the interrupts.

However, your biggest problem is the way you are doing the delay. Reading in a buffer and then outputting it will be glitchie, because you have a longer than normal gap between starting the buffer again and the samples in the buffer.

What you need to do is to have pointers to the current buffer input. Then when a sample comes read what is in the buffer at that place, output it, then read the A/D and store it in the buffer. Finally increment the buffer pointer and wrap round if necessary.

If you have an input and output pointer then you can change the delay very simply by changing the distance between them. That is setting them off with different initial values.

bigbluecoconut

Thanks for your help,
I use a 2nd order Lowpass at 15KHz.
I think my noise is mainly the quantisation noise + the weird cracks from my bad code timing.
When i use a fixed delay the signal is clean, with more or less the same code.


Code: [Select]

int Input_Audio=PA0; //Audio input
int Input_Delay=PA7; //LFO input
int Output_PWM=PB9;  //PWM output
int PWM_Out;
byte memory[16000];    //circular buffer
int delaytime;

void setup()
{   
    Timer1.pause();
    Timer3.pause();   
    pinMode(Input_Audio, INPUT);
    pinMode(Input_Delay, INPUT);
    pinMode(Output_PWM, PWM);   
    Timer2.setMode(TIMER_CH1,TIMER_OUTPUTCOMPARE);
    Timer2.setCompare(TIMER_CH1, 1);
    Timer4.setOverflow(255); 
    Timer2.setPrescaleFactor(1);
    Timer2.setOverflow(255);
    Timer2.attachInterrupt(TIMER_CH1,PWM_int);
    Timer2.refresh();
}

void loop() {
//  delaytime=analogRead(Input_Delay); 
  for(int i = 0; i < 15000; i ++)         //fixed circular buffer
  {
    PWM_Out = memory[i];         
    memory[i] = analogRead(Input_Audio) >> 4;
  }
}

void PWM_int(void)
{
  pwmWrite(Output_PWM,PWM_Out);   
}


My problem is, that im to stupid to use pointers.
I tried (for hours :( ) to implement it like you said.
I´ve read a lot of tutorials... but i just dont get it.

Grumpy_Mike

#5
Dec 06, 2019, 06:21 pm Last Edit: Dec 06, 2019, 06:27 pm by Grumpy_Mike
Quote
My problem is, that im to stupid to use pointers.
No your not.

Here is a small extract from my book Arduino Music

What you have here is the input buffer and the output buffer tied together. Whenever you put a sample into the buffer, you take one out. The distance between the input and output pointers coupled with the sample rate give you the delay in seconds. Note here the input and output pointers are moving from right to left. The code fragment to implement this is below. Note I have changed it from the book to allow for a buffer in the processor's memory. The buffer is simply an array you define in memory.

Code: [Select]
void basicDelay(){  // loop time 48.2uS - sample rate 20.83 KHz
  static unsigned int bufferIn=bufferSize, bufferOut=bufferSize - bufferOffset, sample;
  while(1){
     sample = analogRead(0);
     buffer[bufferIn] = sample;
     sample = buffer[bufferOut];
     ADwrite(sample); // change this to how you are outputting your samples
     bufferIn++;
     if(bufferIn > bufferSize) bufferIn=0;
     bufferOut++;
     if(bufferOut > bufferSize) bufferOut=0;
  }
}

Remember this is a code fragment and needs other functions to make it run, but you can easily see the idea. The length of the delay is set by the bufferOffset variable and is the number of samples between the input and output pointers. A sample is read from the A/D and saved at the location given by the bufferIn variable. Then a sample is taken from the memory from a location given in the bufferOut variable and sent to the A/D converter. Finally, the two pointers are incremented and, if either exceeds the total buffer size, they are wrapped around back to zero.
The bufferOffset variable is set from the outside and can implement different lengths of delay, up to the maximum size of the memory you have.

You can download the software from that link. Listing 15.8 is a multi effects program but it is tailored to the board I designed for this chapter.

Quote
I use a 2nd order Lowpass at 15KHz.
Nowhere close to being good enough. At 30KHz you are only 6dBs down. So lower the roll off or get a higher order filter, or both.

Quote
When i use a fixed delay the signal is clean, with more or less the same code.
When you change the delay you have a whole lot of samples in the buffer that are not related to what you are doing. For example you make the delay longer and there is junk samples in the difference between the old buffer and the new one. You will here these samples as noise because they don't fit in with what is going on at this point.

Go Up