Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analog mic + pwm out behaves weird? #1482

Open
pschatzmann opened this issue Apr 4, 2024 Discussed in #1480 · 7 comments
Open

Analog mic + pwm out behaves weird? #1482

pschatzmann opened this issue Apr 4, 2024 Discussed in #1480 · 7 comments

Comments

@pschatzmann
Copy link
Owner

Discussed in #1480

Originally posted by thegoodhen April 4, 2024
Hello, I am putting this into Q and A, since I am not sure if it's a bug or if I am just doing something wrong.

Thank you so much for this awesome library.
I started playing with it and I'm trying to build a simple Dalek voice changer.
As a first step, I am simply trying to copy the microphone data to a speaker.

I tried reading the analog input and streaming it as PWM, but that doesn't work as expected.

I made sure I am using the correct pin for the analog input by running the base_adc_average_mono_serial example, which works.

I made sure I am using the correct pin for the analog output by running the streams_generator_pwm example, which works.

In order to have repeatable data, instead of microphone I am using a signal generator with a DC offset. I verified that the adc sees the data correctly with the base_adc_average_mono_serial.

There is low volume noise coming from the speaker. The oscilloscope shows a waveform with a roughly similar shape to the one I am putting in, extremely noisy, low amplitude, chopped up (discontinuities in the waveform, as if you cut pieces of it and stitched them back together) and at a much lower frequency, suggesting a sampling issue.

Am I just doing something really dumb? Or is this combination not supported? Or is it a bug?

I just need a combination of an analog input and "analog-compatible" (i.e. lm386 with an aliasing filter compatible) output.

As far as I remember, the ESP32 does not support ADC DMA with I2S PDM, since ADC DMA uses the I2S0 memory and furthermore PDM is only supported by I2S0 - that's why I tried my luck with the PWM instead.

I tried enabling "auto_center_read", didnt help.
I tried changing the sample frequency between 8kHz and 44100Hz, didn't help.
I tried changing the input resolution. 8bit (which you say is not supported by most the classes) makes the noise much louder.

Thank you very much!! If you need me to do some more tests, just let me know

HW+SW:

Arduino 1.8.19, ESP32 2.0.9, NodeMCU32S

Sketch:

/**
   @file streams-generator-pwm.ino
   @author Phil Schatzmann
   @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-stream/streams-generator-pwm/README.md
   @author Phil Schatzmann
   @copyright GPLv3
*/

#include "AudioTools.h"
#define SAMPLE_RATE 16000


PWMAudioOutput pwm;
AudioInfo info(SAMPLE_RATE, 1, 16);


// On board analog to digital converter
AnalogAudioStream analog_in;
StreamCopy copier(pwm, analog_in);    // copy in to out

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);


  auto adcConfig = analog_in.defaultConfig(RX_MODE);
  adcConfig.copyFrom(info);


  // adcConfig.is_auto_center_read = true;


  analog_in.begin(adcConfig);


  // setup PWM output
  auto config = pwm.defaultConfig();
  config.copyFrom(info);
  config.resolution = 8;  // must be between 8 and 11 -> drives pwm frequency (8 is default)

  Pins pins = {23};
  config.setPins(pins);
  pwm.begin(config);
}

void loop() {
  copier.copy();
  //delay(10);
}
```</div>
@pschatzmann
Copy link
Owner Author

pschatzmann commented Apr 4, 2024

Both the timer and the PWM APIs have changed: I could identify that the timer was not working.
I committed a correction to address the issue with the timer.

Please note that there is quite a difference to the requested frequency: e.g. Sampling Rate 44100 vs eff: 43476
I am not sure how to address this when you need it to match. Have a look at the code. Maybe you have an idea...

Maybe you could do some resampling or use a frequency which does not need any fraction of us (e,g 20000hz or 40000hz)

@thegoodhen
Copy link

Hello, thank you. I will probably need to fix this eventually. That being said, I don't think the timer is working in the new Arduino ESP32 core (v. 3), which makes it a bit harder to debug. I will file a bug report and investigate.

@thegoodhen
Copy link

thegoodhen commented Jun 24, 2024

There is a very similar problem with ESP32S3 - there the ADC continuous sampling frequency is correct, but the I2S TX frequency is off by about 10% even for non-fractional sample rates, such as 20ksps.

I have written a proof-of-concept resampling algorithm that uses fixed-point arithmetic for FOH resampling. It's reasonably efficient, but so far I didn't finish the automatic frequency compensiation for unknown sample rates. I will need to actually automatically measure the sample rates and calculate the correction factor accordingly. This will take around 2 seconds on startup, since I need to collect enough samples to get reasonably accurate results. I think there will still be signal quality degradation since it's not possible to match the rates exactly, so you will likely have a pop every few seconds or introduce phase noise. I should be able to have some code which grabs data from an analog microphone and spits it out using pdm for ESP32S3 by this weekend (I have it already but without the self calibration of the sample rates).
The code does not yet use your library since I needed to limit the number of variables. Then we can discuss how to best implement this into your library. Sounds good?

Edit: Wait, I just saw that the resample stream is already in the library... never mind, it never occured to me to check.

@pschatzmann
Copy link
Owner Author

You can measure the actual speed with a MeasuringStream and dynamically resample with a ResampleStream

@thegoodhen
Copy link

You can measure the actual speed with a MeasuringStream and dynamically resample with a ResampleStream

Good idea. Correct me if I'm wrong, but I don't think the MeasuringStream can be used to measure the I2S output rate though? Just the input, right? We need to know both to be able to correctly resample.

@pschatzmann
Copy link
Owner Author

pschatzmann commented Jun 24, 2024

If you decouple both processes with a queue, you should be able to measure (and process) both.

@thegoodhen
Copy link

If you decouple both processes with a queue, you should be able to measure (and process) both.

I see. I just send the data from adc to Measuring stream 1 and the queue and then send the queue data to Measuring stream 2 and I2S. This gives me the rates I can then use to resample using ResamplingStream.

Sounds pretty straightforward. Thank you very much!! I think that the best way will be to subclass Copier and make it autoresample on-the-fly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants