changing speed PWM playback audio sample from RAM

Hi, I have tested this as a loopback with microphone input and output filters.
It is acceptable quality for my project.

Over the last year I was hoping a solution would be found to smoothly varying the playback speed of 1500 values stored in the array dd[1511].

At the moment the sketch uses the pre-scaler on Timer2 to set the output sample frequency.
So only jumps in speed are possible by changing pre-scaler values.
That is unavoidable because of the use of PWM output.

There may be glitches/artifacts if a pot input was used to change the playback sample frequency on the fly, but since I haven't a solution to that yet, I don't know how intrusive that would be.

I am keeping the hardware as simple as possible, not wanting to get into a DAC solution, because this is a modular unit of at least half a dozen.

Has anyone got any possible way to still use PWM and arbitrary sample rate? Say up to 5 times faster or slower?

[code]
/* Arduino Audio Loopback Test
 *
 * Arduino Realtime Audio Processing
 * 2 ADC 8-Bit Mode
 * analog input 1 is used to sample the audio signal
 * analog input 0 is used to control an audio effect
 * PWM DAC with Timer2 as analog output
 
 
 
 * KHM 2008 / Lab3/  Martin Nawrath nawrath@khm.de
 * Kunsthochschule fuer Medien Koeln
 * Academy of Media Arts Cologne
 
 */


#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))


int ledPin = 13;                 // LED connected to digital pin 13
int testPin = 7;


boolean div32;
boolean div16;
// vars altered by interrupt
volatile boolean f_sample;
volatile byte badc0;
volatile byte badc1;
volatile byte ibb;






int ii;
byte dd[511];  // Audio Memory Array 8-Bit

void setup()
{
  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
  pinMode(testPin, OUTPUT);
  Serial.begin(57600);        // connect to the serial port
  Serial.println("Arduino Audio Loopback");


  // set adc prescaler  to 64 for 19kHz sampling frequency
  cbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);




  sbi(ADMUX,ADLAR);  // 8-Bit ADC in ADCH Register
  sbi(ADMUX,REFS0);  // VCC Reference
  cbi(ADMUX,REFS1);
  cbi(ADMUX,MUX0);   // Set Input Multiplexer to Channel 0
  cbi(ADMUX,MUX1);
  cbi(ADMUX,MUX2);
  cbi(ADMUX,MUX3);


  // Timer2 PWM Mode set to fast PWM 
  cbi (TCCR2A, COM2A0);
  sbi (TCCR2A, COM2A1);
  sbi (TCCR2A, WGM20);
  sbi (TCCR2A, WGM21);

  cbi (TCCR2B, WGM22);




  // Timer2 Clock Prescaler to : 1 
  sbi (TCCR2B, CS20);
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Timer2 PWM Port Enable
  sbi(DDRB,3);                    // set digital pin 11 to output

  //cli();                         // disable interrupts to avoid distortion
  cbi (TIMSK0,TOIE0);              // disable Timer0 !!! delay is off now
  sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt
  
  Serial.print("ADC offset=");     // trim to 127
  ii=badc1;  
  Serial.println(ii);


}





void loop()
{
  while (!f_sample) {         // wait for Sample Value from ADC
  }                           // Cycle 15625 KHz = 64uSec 
  PORTD = PORTD  | 128;       //  Test Output on pin 7
  f_sample=false;

  OCR2A=badc1;                // output audio to PWM port (pin 11)

  // variable delay controlled by potentiometer    
  // when distortion then delay / processing time is too long   
 // for (cnta=0; cnta <= badc0; cnta++) { 
  //  ibb = ibb * 5;              
 // }

  PORTD = PORTD  ^ 128;       //  Test Output on pin 7
} // loop

//******************************************************************
// Timer2 Interrupt Service at 62.5 KHz
// here the audio and pot signal is sampled in a rate of:  16Mhz / 256 / 2 / 2 = 15625 Hz
// runtime : xxxx microseconds
ISR(TIMER2_OVF_vect) {

  PORTB = PORTB  | 1 ;

  div32=!div32;                      // divide timer2 frequency / 2 to 31.25kHz
  if (div32){ 
    div16=!div16;  // 
    if (div16) {                     // sample channel 0 and 1 alternately so each channel is sampled with 15.6kHz
      badc0=ADCH;                    // get ADC channel 0
      sbi(ADMUX,MUX0);               // set multiplexer to channel 1
    }
    else
    {
      badc1=ADCH;                    // get ADC channel 1
      cbi(ADMUX,MUX0);               // set multiplexer to channel 0
      f_sample=true;
    }
    ibb++;                          // short delay before start conversion
    ibb--; 
    ibb++; 
    ibb--;    
    sbi(ADCSRA,ADSC);               // start next conversion
  }

}

[/code]

The prescaler determines the raw frequency and the TOP value the fine PWM frequency. The comparison value between 0 and TOP determines the duty cycle.

Do you want to change the playback frequency with or without changing the audio frequency accordingly?

I do not fully understand your code. Can you explain how you sample the audio signal and convert it into PWM output and then into audio again?

I have tried to research some way to bit-bang an output pin, using a for loop and the PWM value held in the array.

The AT328 can do this in hardware much faster than code in a sketch.
But I wondered if it was worth looking at. I can reduce the sampling on recording audio, to perhaps half the 15KHz in the sketch.

In the following forum , I am not clear how each of the lines of assembler code contributes to the time taken for the loop, in this thread.

A for loop which toggles an output pin would be one way to simulate Timer2 PWM output?

https://forum.arduino.cc/index.php?topic=88126.0

thanks DrDiettrich,

The prescaler determines the raw frequency and the TOP value the fine PWM frequency. The comparison value between 0 and TOP determines the duty cycle.

Ok, I need to revise my understanding of timers and PWM and where the TOP value is determined in the sketch.
How much it changes the output sample frequency, and when/if it can be changed within a running sketch loop()

The array char bb[1511] is declared for storing audio values, as the sketch is compiled at the moment this is a place-holder to check the amount of RAM used and any warnings on compile.
The ADC value is just taken straight to the PWM output. Which is then low pass filtered and amplified to a speaker.

Changing the speed at which the samples are played back, will always change both the tempo and pitch.
That is what is wanted.

The TOP value comes from OCRA or ICR or is fixed depending on the timer mode.

As I see it the playback sample rate must match an integral number of PWM cycles. This might occur in the first while(!f_sample) in loop() waiting for f_sample set in the timer2 ISR on completion of one PWM wave.

With only 1 cycle per sample the ISR can immediately put the next audio amplitude into the OCR, else it has to count the number of cycles before outputting the next amplitude (duty cycle) value.

What remains is mapping the audio amplitude to PWM output compare values if other than a fixed TOP is used. For one cycle the counter goes up from BOTTOM (zero) to TOP and eventually down again to BOTTOM, depending on the PWM mode. The output compare value must be between BOTTOM (0%) and TOP (100% duty cycle) so that the output pin can toggle once on match of the TCNT and OCR registers.

Thanks again DrDiettrich,
I'm going to have to ask for a little clarification though on your answers.

Firstly, this is a link to the inevitable excellent Nick Gammon reference on the AT328 timers.
The pre-scalers are given their properties in the setup() part of his example sketches. And pretty much what every sketch with timers does.

What I am wanting is to change two Timer parameters within the running sketch.
One is the frequency of a sample playback 'clock' (to vary the speed), the second is the duty cycle as determined by a value held in an array.

The recording is done once only, the intention is to develop a switch case (and a physical button) in the sketch to implement a 'record sample' mode. Otherwise it is always looping the values held in the array, in 'play' mode.

As I see it the playback sample rate must match an integral number of PWM cycles.

Not sure what you mean. The 'sample rate' is the rate at which individual array elements are retrieved.
Each PWM cycle is going to be a duty cycle from 0 to 100% (256 8 bit integer values)?

Yes, the ADC register needs to be read for the pot value to determine 'play' read out samples rate. Is this what you mean - but while 'play' is taking place the reading of the pot can be asychronous.

This might occur in the first while(!f_sample) in loop() waiting for f_sample set in the timer2 ISR on completion of one PWM wave.

Many thanks if you can be patient.
The bit-banging PWM is not going to be fast enough.
So somehow it will have to be done with the timers.

The timer parameters can be changed at any time but not in any order.

A change in the duty cycle can occur only after at least one full PWM cycle was output.

The duty cycle is the fraction of the TOP value and the OCR. With 256 counts from BOTTOM to TOP the duty cycle range is 0-256 or 0-255 in 8 bits, with 200 counts it's 0-200.

Ok thanks DrDiettrich.
Timer2 is only 8 bit, and the 'sampling frequency' in the sketch is determined by the OVF vector.

So a completely different way of doing this is needed on playback.
A range of playback speeds will be wanted.

So is using Timer1 going to work?

Nick Gammon's example has modulation of the PWM output, but not a variation in the value for the timer1_OCRA_setting. The frequency would not be 38KHz obviously.
Is there a problem with putting that setting change inside loop()?

[code]
// Example of modulating a 38 kHz frequency duty cycle by reading a potentiometer
// Author: Nick Gammon
// Date: 24 September 2012

const byte POTENTIOMETER = A0;
const byte LED = 10;  // Timer 1 "B" output: OC1B

// Clock frequency divided by 38 kHz frequency desired
const long timer1_OCR1A_Setting = F_CPU / 38000L;

void setup() 
 {
  pinMode (LED, OUTPUT);

  // set up Timer 1 - gives us 38.005 kHz 
  // Fast PWM top at OCR1A
  TCCR1A = bit (WGM10) | bit (WGM11) | bit (COM1B1); // fast PWM, clear OC1B on compare
  TCCR1B = bit (WGM12) | bit (WGM13) | bit (CS10);   // fast PWM, no prescaler
  OCR1A =  timer1_OCR1A_Setting - 1;                 // zero relative  
  }  // end of setup

void loop()
  {
  // alter Timer 1 duty cycle in accordance with pot reading
  OCR1B = (((long) (analogRead (POTENTIOMETER) + 1) * timer1_OCR1A_Setting) / 1024L) - 1;
  
  // do other stuff here
  }

[/code]

Any timer will do in a PWM mode and variable TOP. Timer2 only has limited granularity for frequency and duty cycle.

Thanks for your patience DrDiettrich.
I think I've got the basic understanding and how to start approaching the solution.

I can set up Timer1 with a pre-scale of 1.
The fastest output of a 256 bit PWM would then be 16000000/256, which is 62.5KHz

That would leave only 256 execution cycles to update the PWM register with a new value from RAM, and read analog input etc. Not a lot.

Slowed down output needs each RAM value to be multiplied in proportion to the Timer1 clock OCR setting value, before it can be used to set the PWM register compare value.

The frequency of the Timer1 clock is still 16000000.

The largest Timer1 OCR value is 16 bits, 65,536. So each RAM value is multiplied by 65,536/256 = 256.
That would give 256 levels of sample output values per second.

The useful range will obviously be between these extremes.
I will use a simple Voltage Controlled Lowpass filter, which tracks the sample output rate.
From experimenting, it works better if the samples have a much higher clock because the control voltage is derived from the falling edge of the PWM signal.

Have I understood this in principle?
That the frequency of the PWM, the 'carrier' waveform, with 8 bit 0% to 100% modulation is going to depend entirely on the OCRA register value?
I could repeat the same RAM value to double this rate for example.
But I cannot have it 'oversample' at a high rate.