Troubles with (user defined) external audio output and ESP32 internal DAC

384 views
Skip to first unread message

dtr....@gmail.com

unread,
Aug 16, 2020, 1:35:09 PM8/16/20
to Mozzi-users

Hi,

This ties into the discussion here about outputting on the ESP32's internal DAC: https://groups.google.com/g/mozzi-users/c/qw2nIkcVaZE/m/NnJpLAi3BQAJ . Results so far are only partly pleasing so I tried the different route suggested by Tom Combriat: external audio output definition.

I have not been able to make it work and get output from audioOutput(). It seems it doesn't get called/executed.

I've read the discussion here: https://github.com/sensorium/Mozzi/pull/87
There's a note about ESP platforms being excluded from the mechanism. Is this still the case in the current release?

Here's my test sketch (several Mozzi header files need mods for it to compile). Serial.println() in AudioOutput() is only there to check whether it gets called, which it doesn't:

#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
#define CONTROL_RATE 64 // Hz, powers of 2 are most reliable

#define AUDIO_BIAS 128 // 8bits : 2^(8-1)=128

#define DAC1 26

void audioOutput(int l, int r) {
  l += AUDIO_BIAS;
  dacWrite(DAC1, l);
  Serial.println("*");
}

void setup(){
  Serial.begin(9600);
  startMozzi(CONTROL_RATE); // :)
  aSin.setFreq(1500); // set the frequency
}


void updateControl(){
  // put changing controls in here
 
}

int updateAudio(){
  int out = aSin.next()>>0;
  return out; 
}

void loop(){
  audioHook(); // required here
}


Thanks for any help, Dieter

dtr....@gmail.com

unread,
Sep 11, 2020, 9:36:57 AM9/11/20
to Mozzi-users
Hi,

I'm again bumping my head against the external audio output method. This time I'm working on an Arduino Duemilanove 328P and MCP4725 DAC. I'm using the lib and example sketch given by Bayonet here: https://groups.google.com/g/mozzi-users/c/Jn9lJ9v9YWg/m/plzEBzF9EwAJ

I'm trying to modify the example to work with EXTERNAL_AUDIO_OUTPUT but I can't get the audioOutput() function in my sketch to output anything. If I move the line that sets the DAC output to updateAudio() it works. Move it into audioOutput(), nothing happens. This is the output line:

dac.setVoltage(l+AUDIO_BIAS, false); 

What is it that I don't get about the external audio output structure? Sketch below, requires lib from thread linked above. Looks like Google Groups doesn't let me attach an .ino file anymore.

Best, Dieter


#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/cos2048_int8.h> // table for Oscils to play
#include <mozzi_fixmath.h>
#include <EventDelay.h>
#include <mozzi_rand.h>
#include <mozzi_midi.h>

#include <twi_nonblock.h>
#include <Adafruit_MCP4725.h>

Adafruit_MCP4725 dac;

#define AUDIO_BIAS 2048

#define CONTROL_RATE 64 // Hz, powers of 2 are most reliable

// audio oscils
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aCarrier(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aModulator(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aModDepth(COS2048_DATA);

// for scheduling note changes in updateControl()
EventDelay  kNoteChangeDelay;

// synthesis parameters in fixed point formats
Q8n8 ratio; // unsigned int with 8 integer bits and 8 fractional bits
Q24n8 carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits
Q24n8 mod_freq; // unsigned long with 24 integer bits and 8 fractional bits

// for random notes
Q8n0 octave_start_note = 42;

void setup(){
  ratio = float_to_Q8n8(3.0f);   // define modulation ratio in float and convert to fixed-point
  kNoteChangeDelay.set(200); // note duration ms, within resolution of CONTROL_RATE
  aModDepth.setFreq(13.f);     // vary mod depth to highlight am effects
  randSeed(); // reseed the random generator for different results each time the sketch runs
  dac.begin(0x60);
  //Serial.begin(9600);
  startMozzi(CONTROL_RATE);
}

void updateControl(){
  static Q16n16 last_note = octave_start_note;

  if(kNoteChangeDelay.ready()){

    // change octave now and then
    if(rand((byte)5)==0){
      last_note = 36+(rand((byte)6)*12);
    }

    // change step up or down a semitone occasionally
    if(rand((byte)13)==0){
      last_note += 1-rand((byte)3);
    }

    // change modulation ratio now and then
    if(rand((byte)5)==0){
      ratio = ((Q8n8) 1+ rand((byte)5)) <<8;
    }

    // sometimes add a fractionto the ratio
    if(rand((byte)5)==0){
      ratio += rand((byte)255);
    }

    // step up or down 3 semitones (or 0)
    last_note += 3 * (1-rand((byte)3));

    // convert midi to frequency
    Q16n16 midi_note = Q8n0_to_Q16n16(last_note);
    carrier_freq = Q16n16_to_Q24n8(Q16n16_mtof(midi_note));

    // calculate modulation frequency to stay in ratio with carrier
    mod_freq = (carrier_freq * ratio)>>8; // (Q24n8   Q8n8) >> 8 = Q24n8

      // set frequencies of the oscillators
    aCarrier.setFreq_Q24n8(carrier_freq);
    aModulator.setFreq_Q24n8(mod_freq);

    // reset the note scheduler
    kNoteChangeDelay.start();
  }
}

int updateAudio(){
  long mod = (128u+ aModulator.next()) * ((byte)128+ aModDepth.next());
  int out = (mod * aCarrier.next())>>13;
  return out;
//  dac.setVoltage(out+AUDIO_BIAS, false);

}

void audioOutput(int l, int r)
{
  dac.setVoltage(l+AUDIO_BIAS, false);
}


void loop(){
  audioHook();
}

staffa...@oscillator.se

unread,
Sep 11, 2020, 10:52:03 AM9/11/20
to Mozzi-users
Hi! I would like to try this -- do you notice any improvement in sound quality using the DAC?

Regarding your problem: You never call audioOutput()?! Or is that called by something in a library?

/Staffan

dtr....@gmail.com

unread,
Sep 11, 2020, 11:50:40 AM9/11/20
to Mozzi-users
> Hi! I would like to try this -- do you notice any improvement in sound quality using the DAC?

The way Bayonet implemented it I think I'm getting a sub-optimal signal from the DAC. Quite gritty still. I think if it 'd work with the external output method the output samples would be timed better and quality would improve.

> Regarding your problem: You never call audioOutput()?! Or is that called by something in a library?

It's called from elsewhere. External output mode is enabled by defining "EXTERNAL_AUDIO_OUTPUT true" in mozzi_config.h. You can then write your own audioOutput() function which is normally hidden in the library code. Can't get it to function though.

There are examples included with Mozzi that demonstrate the feature (example category 13).

D.

tomco...@live.fr

unread,
Sep 11, 2020, 1:57:05 PM9/11/20
to Mozzi-users
Hi!

I participated in writing this external audio capability so I am also interested in knowing if that does work everywhere (except ESP…).

To begin:
Yes, the call is hidden in MozziGuts. If you do it in the updateAudio you have no guarantee that it will actually be at the correct frequency as this function is running as fast as it can as long as the buffer is not full.
You changed mozzi_config.h so normally it should work but I have to tell that I never tried on Duemilanove, but as this is an AVR that should work.

What to try:
To check if you are correctly using the external audio output, try to make it outputting on an analog pin (analogWrite(…) ) just to check if it does work.
Can you successful output things on the DAC without mozzi but using the TWI lib?

A few remarks:
@Staffan: normally, as you are outputting 12 bits (instead of something like 8/10) you should have a great improvement on audio quality. Also, it is analog, meaning that you do not need a low-pass to remove the PWM carrier frequency, there is still a carrier but it should be way less present,
This DAC is an I2C dac… From my last tries, I could not get this protocol to go fast enough (even with tweakings) for outputting at audio rate… SPI is better for this. On this, I am now experimenting with a DAC from the same serie (MCP4822) outputting 24 bits audio. With the noise this level of accuracy is not reached however… This is done via SPI and I saw no clear performance differences with PWM. The sounds seems way better, but I should plug a scop to check that properly (ongoing).
I was planning to make a small announcement here once I get all the relevant characteristics, but if you are curious in the meantime: https://github.com/tomcombriat/TES_10-knobs-synth Note I am using a STM32…

Not much but hope it helps!

Tom

dtr....@gmail.com

unread,
Sep 11, 2020, 3:28:41 PM9/11/20
to Mozzi-users
Hi Tom,

Thanks for your help!

> What to try:
> To check if you are correctly using the external audio output, try to make it outputting on an analog pin (analogWrite(…) ) just to check if it does work.
> Can you successful output things on the DAC without mozzi but using the TWI lib?

It will be a a week or 2 before I get back to this. I don't have access to the hardware now. However, after I posted about my issue and before you answered I did another test using the Sinewave_R2R_DAC.ino example. I confirmed with a scope that I was getting output on the digi output pins, then I started adding code to output to the MCP4725 in parallel with the R2R outputs. I could add MCP4725 output with Bayonet's lib until updateAudio() and get output on both the R2R outputs and MCP4725. As soon as I move the  dac.setVoltage() call to audioOutput() BOTH outputs stop working. So not only the MCP4725 but also the R2R outputs gives no output anymore. I'm not sure what to make of that.

I experienced something similar trying to get internal DAC output on ESP32 via external audio output with a dacWrite() call. Nothing I put in audioOutput() seemed to execute. "EXTERNAL_AUDIO_OUTPUT true" is set for sure.


> I participated in writing this external audio capability so I am also interested in knowing if that does work everywhere (except ESP…).

Why do you say "except ESP"? Do you mean you know it works on ESP or you know it can't work on ESP?


In the meantime we also ordered an MCP4921 for testing which I hope should work without issues given that Mozzi includes examples for it.


Best, Dieter

tomco...@live.fr

unread,
Sep 11, 2020, 4:42:12 PM9/11/20
to Mozzi-users
Hei again,

From what you said I have an hypothesis :) !
My guess is: the I2C is too slow to be updated at audio_rate and as the adafruit lib is using Wire.h this is a blocking communication. Because of that:
  - if you put the call for the dac in audioUpdate: this function is not an interrupt, so it will execute completely and you get an output but not at the correct frequency. The R2R because it is called by an interruption which will take over at regular interval
  - if you put the call in audioOutput: as this is executed by an interruption and the call is so long, it does not have time to execute completely before the next call. The I2C communication with the Dac is interrupted (pun intended). Also the audiobuffer cannot fill itself because all the time has been taken by the attempt of communication with the Dac so updateAudio is never called: you blocked the synthesis which is why the R2R also stopped working: nothing has been calculated to output actually…

Maybe rewritting the lib for using non-blocking tw would help, or trying to go for high-speed I2C, or switch to SPI.

I can guarantee that you will have less troubles with MCP48XX/MCP49XX: I wrote and tested these examples with AVR and STM. You might need to use a custom version of the DAC lib, but everything should be explained in the examples. And it will work. This is using SPI, hence it is way faster.

About ESP: audioOutput does not work with ESP. I though I posted something here about this feature but actually not (sorry). Maybe I should, or perhaps something on the front page of mozzi (@Tim)? In the meantime some infos can be found in the pullrequests: https://github.com/sensorium/Mozzi/pull/87 and the next one containing the examples.

I am sorry, this is still very recent feature and you are among the first ones to use it: this is why there is probably not enough information… But with your feedback we can try to fix that ;).

Hope it helps,

Tom

dtr....@gmail.com

unread,
Sep 12, 2020, 8:51:12 AM9/12/20
to Mozzi-users
Hey Tom,

Ok that makes sense. Thanks for clarifying.

The adafruit MCP4725 lib modified by Bayonet does include non-blocking TWI, but likely that's still not fast enough to execute in time.

I had read https://github.com/sensorium/Mozzi/pull/87 multiple times before but had too little understanding of the Mozzi core mechanics to distill from it what I needed. With your clarifications (and many hours dissecting mozzi_guts while trying to get ESP32 internal DAC smoothed out) it 's now making sense.  

> I am sorry, this is still very recent feature and you are among the first ones to use it: this is why there is probably not enough information… But with your feedback we can try to fix that ;).

No prob. I'm happy to contribute to the developments! I'll pick this up again in a week or 2 with the MCP4921. If I find enough time I might have another go at ESP32 internal DAC but no guarantee as it's now of lesser importance to our project. Though it itches my nerd pride that I left that half-solved :D


Best, Dieter

dtr....@gmail.com

unread,
Oct 22, 2020, 8:36:24 AM10/22/20
to Mozzi-users
Hi, update to this thread:

I did get the MCP4921 DAC but then ran into the issue that I can't use external audio output to get it to work with ESP32. So I reverted to testing it on an Arduino Duemilanove. I compared that to a PT8211 connected to a Lolin D32 (ESP32) and found the PT8211 to sound much better. Can anyone confirm this? I'm assuming Duemilanove vs D32 shouldn't make a difference as long as their CPU cycles can both keep up.

In the process I found the implementation given for ESP32+PT8211 in following thread has issues causing much distortion and resolved them: https://groups.google.com/g/mozzi-users/c/qw2nIkcVaZE/m/hjJef464CQAJ

We are now continuing our project with ESP32+PT8211. I am intending to contribute my Mozzi mod for this to the github soon.


Best, Dieter
Reply all
Reply to author
Forward
0 new messages