queue and i2s dac it even working?

Diodac

Well-known member
Send audio from ADC to i2s output using queue play and dac UDA1334A.
I don't know if it even is able to work, I tried couple of methods and Im disappointed, the sound is distorted or no sound. Im pretty sure that is my fault somewhere. Maybe someone could help me get it work?
The UDA1334A module is working, i tested it with another example Simple Drum and quality of sound is amazing. Why queue doesn't work?
Here is my simple initial code
Code:
IntervalTimer timer;
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <ADC.h>
#include <ADC_util.h>
ADC *adc = new ADC();

// GUItool: begin automatically generated code
AudioPlayQueue           play;         //xy=1137,316
AudioOutputI2S           i2s2Output;           //xy=1344,317
AudioConnection          patchCord1(play, 0, i2s2Output, 0);
AudioConnection          patchCord2(play, 0, i2s2Output, 1);
// GUItool: end automatically generated code

int16_t output = 0;


void setup() {
  AudioMemory(20);
  pinMode(16, INPUT_DISABLE);

  play.setBehaviour(AudioPlayQueue::NON_STALLING);
  play.setMaxBuffers(16);

  timer.begin(effect, 80);
  timer.priority(128);
  //ADC0
  adc->adc0->setAveraging(16);
  adc->adc0->setResolution(12); //resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED);
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED);
  adc->adc0->setReference(ADC_REFERENCE::REF_3V3);
  //ADC1
  adc->adc0->setAveraging(16);
  adc->adc1->setResolution(10); //resolution
  adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); // change the conversion speed
  adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); // change the sampling speed
  adc->adc1->setReference(ADC_REFERENCE::REF_3V3);
}

void dac() { //i2S output
  int16_t *dacBuffer = play.getBuffer();
  //memcpy(dacBuffer, output, 256);
  //queue1.playBuffer();
  for (int i = 0; i < 128; i++) {
    dacBuffer[i] = output;
    play.playBuffer();
  }
}

void loop() {
 dac();
}

void effect() {
 
  output = adc->adc1->analogRead(A2);
}
 
The ADC input is supported by the latest audio library.

https://www.pjrc.com/teensy/gui/?info=AudioInputAnalog

Reading it this way with the ADC library is pretty much impossible to get the sample rate to closely match the audio library sample rate. When you copy data between buffers with mismatched sample rates, all sorts of terrible things happen to the sound quality.

But it could at least be closer. Looks like you're running the timer with 80 us interval. But the library uses 44100 Hz sample rate. So you could at least get close by changing the timer to interval of 22.676 us.

Your function writes the latest sample to "output". Looks like the dac() function just reads the 1 sample 128 times, but doesn't do anything to wait for it to actually change. So each buffer is probably getting filled with 128 copies of the same sample. You probably need the interrupt to set a flag or counter, so the main program can detect when "output" has new data. Usually that data variable and the flag need to be declared "volatile", otherwise the compiler will optimize your code with assumptions the data stored in memory can't spontaneously change. But your interrupt does change the data, so volatile tells the compiler not to make those assumptions.

If you do all that, you can probably get much better quality. But it probably won't be as good as AudioInputAnalog from the library, which reads the ADC at 4X the sample rate and does FIR filtering before downsampling to 44100 Hz.
 
The ADC input is supported by the latest audio library.

https://www.pjrc.com/teensy/gui/?info=AudioInputAnalog

Reading it this way with the ADC library is pretty much impossible to get the sample rate to closely match the audio library sample rate. When you copy data between buffers with mismatched sample rates, all sorts of terrible things happen to the sound quality.

But it could at least be closer. Looks like you're running the timer with 80 us interval. But the library uses 44100 Hz sample rate. So you could at least get close by changing the timer to interval of 22.676 us.

Your function writes the latest sample to "output". Looks like the dac() function just reads the 1 sample 128 times, but doesn't do anything to wait for it to actually change. So each buffer is probably getting filled with 128 copies of the same sample. You probably need the interrupt to set a flag or counter, so the main program can detect when "output" has new data. Usually that data variable and the flag need to be declared "volatile", otherwise the compiler will optimize your code with assumptions the data stored in memory can't spontaneously change. But your interrupt does change the data, so volatile tells the compiler not to make those assumptions.

If you do all that, you can probably get much better quality. But it probably won't be as good as AudioInputAnalog from the library, which reads the ADC at 4X the sample rate and does FIR filtering before downsampling to 44100 Hz.

Paul thank you for suggestions, I got it to work almost everything working like I except, good quality of sound.
I have still small problem with my code.
1. Latency between input and output is around half second, is any trick or optimisation for resolve latency problem?
2. Clicks in sound, they are not horrible but exist.
How I can better optimise my code?
Here is what I got.
clean(); is just forward input to the output.
ring_mod(); is an pseudo octaver/modulator.
Code:
IntervalTimer timer;
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputAnalog         adc1;           //xy=205.00000381469727,195.00000286102295
AudioMixer4              mixer1;         //xy=366.25,208.25
AudioPlayQueue           queuePlay;      //xy=488.25,435.25
AudioRecordQueue         queueRecord;    //xy=509.25,209.25
AudioMixer4              mixer2;         //xy=644.25,454.25
AudioOutputI2S           i2s2Play;       //xy=828.25,454.25
AudioConnection          patchCord1(adc1, 0, mixer1, 0);
//AudioConnection          patchCord2(adc1, 0, mixer1, 1);
AudioConnection          patchCord3(mixer1, queueRecord);
AudioConnection          patchCord4(mixer1, 0, mixer2, 1);
AudioConnection          patchCord5(queuePlay, 0, mixer2, 0);
AudioConnection          patchCord6(mixer2, 0, i2s2Play, 0);
AudioConnection          patchCord7(mixer2, 0, i2s2Play, 1);
// GUItool: end automatically generated code

#define     MAX_SAMPLES  128
#define     MAX_QUEUE_SIZE 16

short DMAMEM bufferIn[MAX_SAMPLES * MAX_QUEUE_SIZE];
short DMAMEM bufferOut[MAX_SAMPLES * MAX_QUEUE_SIZE];
int32_t record_offset = 0;
int32_t play_offset = 0;
int16_t audioDataInput = 0, audioDataOutput = 0;
unsigned int pointer = 0, speed = 0;
unsigned int time = 0;

void setup() {
  Serial.begin(115200);
  AudioMemory(30);
  timer.begin(effect, 22.676);
  timer.priority(128);
  queuePlay.setBehaviour(AudioPlayQueue::NON_STALLING);
  queuePlay.setMaxBuffers(16);

  mixer1.gain(0, 1.0);
  mixer1.gain(1, 0);
  mixer1.gain(2, 0);
  mixer1.gain(3, 0);

  mixer2.gain(0, 1.0); // Fx signal
  mixer2.gain(1, 0);   // Dry signal
  mixer2.gain(2, 0);
  mixer2.gain(3, 0);

  queueRecord.begin();
}

void ADC_to_buffer() {
  memcpy(bufferIn + record_offset, queueRecord.readBuffer(), MAX_SAMPLES * 2);
  queueRecord.freeBuffer();
  record_offset += MAX_SAMPLES;
  if (record_offset >= (MAX_SAMPLES * MAX_QUEUE_SIZE))  record_offset = 0;
}

void buffer_to_i2s() {
  memcpy(queuePlay.getBuffer(), bufferOut + play_offset , MAX_SAMPLES * 2);
  queuePlay.playBuffer();
  play_offset += MAX_SAMPLES;
  if (play_offset >= (MAX_SAMPLES * MAX_QUEUE_SIZE)) play_offset = 0;
}

void loop() {}

void effect() {
  if (queueRecord.available() >= 2) ADC_to_buffer(), buffer_to_i2s();
  clean();
  //ring_mod();
}

void clean() {
  pointer++;
  audioDataInput = bufferIn[pointer];
  bufferOut[pointer] = audioDataInput;
  if (pointer >= (MAX_SAMPLES * MAX_QUEUE_SIZE)) pointer = 0;
}

void ring_mod() {
  pointer++;
  audioDataInput = bufferIn[pointer];
  bufferOut[pointer] = audioDataOutput;
  if (pointer >= (MAX_SAMPLES * MAX_QUEUE_SIZE)) pointer = 0;
  time = 15;//map(analogRead(A15), 0, 1023, 0, 60);
  speed++;
  if (speed >= time) {
    speed = 0;
    audioDataOutput = audioDataInput;
  }
}
 
1. Latency between input and output is around half second, is any trick or optimisation for resolve latency problem?

Best way is to build your code as audio library classes.

https://www.pjrc.com/teensy/td_libs_AudioNewObjects.html

Using the queues is possible, but they are designed to automatically buffer (queue) audio blocks if your code doesn't run at the correct rate. By creating your own objects in the audio library way, they will get their update() function called for each block of 128 samples without the possibility of building up a queue of buffered data.
 
Best way is to build your code as audio library classes.

https://www.pjrc.com/teensy/td_libs_AudioNewObjects.html

Using the queues is possible, but they are designed to automatically buffer (queue) audio blocks if your code doesn't run at the correct rate. By creating your own objects in the audio library way, they will get their update() function called for each block of 128 samples without the possibility of building up a queue of buffered data.

Yeah it is perfect solution, i tried it but so far without success and I used queue option. Still I didn’t understand exactly some things with Audio library.
I have some other effects pitch shifters, reverbs, modulators, chorus and so on, it will be nice if I get it as an audio objects.
If I get to work my ring_mod as audio object then other will be easy.
 
I tried this code also with Teensy audio shield and get much better sound quality, clicks go away, and latency between input and output is smaller.
Tomorrow I will share it with another effect code.
 
Best way is to build your code as audio library classes.

https://www.pjrc.com/teensy/td_libs_AudioNewObjects.html

Using the queues is possible, but they are designed to automatically buffer (queue) audio blocks if your code doesn't run at the correct rate. By creating your own objects in the audio library way, they will get their update() function called for each block of 128 samples without the possibility of building up a queue of buffered data.

Paul is any better solution for pick up audio from record queue and send audio to play queue than this
Code:
void ADC_to_buffer() {
  memcpy(bufferIn + record_offset, queueRecord.readBuffer(), MAX_SAMPLES * 2);
  queueRecord.freeBuffer();
  record_offset += MAX_SAMPLES;
  if (record_offset >= (MAX_SAMPLES * MAX_QUEUE_SIZE))  record_offset = 0;
}

void buffer_to_i2s() {
  memcpy(queuePlay.getBuffer(), bufferOut + play_offset , MAX_SAMPLES * 2);
  queuePlay.playBuffer();
  play_offset += MAX_SAMPLES;
  if (play_offset >= (MAX_SAMPLES * MAX_QUEUE_SIZE)) play_offset = 0;
}
 
Back
Top