VS10XX as DAC
poetaster opened this issue · comments
I've been using the VS1003b and VS1053b for a bunch of different 328 projects since they are really flexible. Midi stuff 'just works'. But simple PCM output, I'm stuck at feeding from the mozzi output buffer. The general approach on the VS1053 is to feed from a buffer in 32 byte chunks. You begin with a header:
unsigned char bt_wav_header[44] = {
0x52, 0x49, 0x46, 0x46, // RIFF
0xFF, 0xFF, 0xFF, 0xFF, // size
0x57, 0x41, 0x56, 0x45, // WAVE
0x66, 0x6d, 0x74, 0x20, // fmt
0x10, 0x00, 0x00, 0x00, // subchunk1size
0x01, 0x00, // audio format - pcm
0x02, 0x00, // numof channels
0x80, 0xbb, 0x00, 0x00, //, //samplerate 44k1: 0x44, 0xac, 0x00, 0x00 48k: 48000: 0x80, 0xbb, 0x00, 0x00,
0x10, 0xb1, 0x02, 0x00, //byterate
0x04, 0x00, // blockalign
0x10, 0x00, // bits per sample - 16
0x64, 0x61, 0x74, 0x61, // subchunk3id -"data"
0xFF, 0xFF, 0xFF, 0xFF // subchunk3size (endless)
};
borrowed from: https://github.com/jeroenlukas/KitchenRadio/blob/master/src/kr_bluetoothsink.cpp
And then simply play chunks from the buffer, something like this:
// Does the VS1053 want any more data (yet)?
if (player.data_request()) // or block with await_data_request(); from sdi_send_buffer
{
int bytesRead = circBuffer.read((char *)mp3buff, 32);
// If we didn't read the full 32 bytes, that's a worry
if (bytesRead != 32)
{
Serial.printf("Only read %d bytes from circular buffer\n", bytesRead);
}
// Actually send the data to the VS1053
player.playChunk(mp3buff, bytesRead);
}
}
I'm stuck at how I get the data uint_8 data from the mozzi buffer and directly stream from the buffer. Once the WAV header has been set, I should be able to just push 32 byte long chunks of PCM data to the chip. I need to see if the nano lags, but first things first. Any tips? Thanks!
Just a note that I have generic audio output and input working with the nano (both 1003 and 1053) just not with mozzi.
Two steps involved:
- First you need to provide an "external audio output" function, obviously. Note sure, how far you've looked into that, so far. See Examples->Mozzi->13.External_Audio_Output for getting started.
- The examples are however based on cases, where you are feeding single samples to a DAC one by one. In your case, however, the DAC itself appears to have a buffer, and wants to control the rate. In the current master (configuration details are currently being reworked in the devel/Mozzi2-branch), you will have to add
#define EXTERNAL_AUDIO_OUTPUT true
#define BYPASS_MOZZI_OUTPUT_BUFFER true
in mozzi_config.h. Now you are fully bypassing the Mozzi buffer, which means you can (and will have to) take care of intermediate buffering, yourself. You will have to define two functions in your sketch:
void audioOutput(const AudioOutput f) {
// This function will be called once per audio sample generated.
}
bool canBufferAudioOutput() {
// This function shall return true, whenever there is room for another audio sample.
}
An implementation (totally untested) might perhaps look something like this:
int16_t my_buf[256]; // each element will hold one sample, which I think is 16 bits in your case
#define CHUNKSIZE 16 // but you want to queue 32 bytes = 16 samples per transmission. Not sure, if that detail is important.
byte buf_write_pos = 0;
byte buf_read_pos = -CHUNKSIZE;
void audioOutput(const AudioOutput f) {
my_buf[buf_write_pos++] = f.l(); // assuming mono, for simplicity
}
bool canBufferAudioOutput() {
if (play.data_request() && ((byte) (buf_write_pos - buf_read_pos) > 16)) {
buf_read_pos += CHUNKSIZE;
player.playChunk(&my_buf[buf_read_pos], CHUNKSIZE*sizeof(mybuf[0]));
}
// fill buffer, until most recent buf_read_pos is hit, again.
return (buf_write_pos != buf_read_pos);
}
If you get things to work, we'll be interested in adding your code as an example!
Ah, ok. I thought I might have to override the native buffer, but wasn't sure. Thanks a bunch. I'll work my way through this tomorrow!
On question, this retains the updatecontrol and loop audiohook structures, correct? Just bypasses the buffering?
Could not have said better than @tfry-git !
Just two small notes:
another possibility would be to not bypass the Mozzi buffer, but read it. It might be possible keep the Mozzi buffer and read and send some chunk of it once the DAC is available for receiving samples. A quick look at the MozziGuts_impl_RENESAS.hpp might be of guidance (in that case, we bypassed the Mozzi buffer, but created another, mainly for it to be of the correct type, that source code is not the most readable, I could try to come up with an example). This would be a pointer to the Mozzi buffer (in mono):int * output_buffer.adress();
This is basically the same idea than @tfry-git , except that you do not create your own buffer.- beware, that Mozzi will not fare very well with a frequency of 44.1kHz, on the Nano, the default is 16.384kHz and on more powerful platform it is twice as big. This is mainly for performances purposes but, as your DAC will take care of the timing, a lot of things will be off if you let it take samples at 44.1.
Edit: Thinking about it, not sure my solution works, as audioOutput()
will take samples away from the buffer, so you probably need to bypass it as per @tfry-git 's solution (but nothing prevents from using your own instance of a CircularBuffer
class from Mozzi as your own buffer).
On question, this retains the updatecontrol and loop audiohook structures, correct? Just bypasses the buffering?
Yes! audioHook()
is basically the core of Mozzi, taking care of calling updateAudio
when needed, and updateControl()
.
@tfry-git I guess my question on the usage of MOZZI_OUTPUT_EXTERNAL_CUSTOM
might get solved quite soon ;)
Ok, I'm getting errors
In file included from /home/mwa/Arduino/libraries/Mozzi/MozziGuts.cpp:35:0:
/home/mwa/Arduino/libraries/Mozzi/MozziGuts_impl_AVR.hpp: In function 'void __vector_13()':
/home/mwa/Arduino/libraries/Mozzi/MozziGuts_impl_AVR.hpp:182:3: error: 'defaultAudioOutput' was not declared in this scope
defaultAudioOutput();
^~~~~~~~~~~~~~~~~~
Source is here: https://github.com/poetaster/VS1003/blob/master/examples/vs1053_wavcpm_sine/vs1053_wavcpm_sine.ino
This version has a cbuf implementation in it, since that's the approach that worked in other cases (non-mozzi). Trying:
player.playChunk(&my_buf[buf_read_pos], CHUNKSIZE*sizeof(my_buf[0]) );
won't work without a cast to char and I thought I'd try what worked in other contexts firxt.
Ok, I'm getting errors
That's a bug in the AVR port. Do you have your Mozzi installed from git (or are you willing to try that)? Then we could talk you through switching to the devel/Mozzi2-branch. This should work there (after I've just pushed some fixes; you're doing some pioneer work, here). Instead of editing mozzi_config.h, you'd put the following at the top of your sketch (above any other Mozzi includes):
#include "MozziConfigValues.h" // for named option values
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_EXTERNAL_CUSTOM
#include <Mozzi.h> // instead of MozziGuts.h
The remainder of the sketch should work unchanged (if you haven't done anything unusual, that is).
This version has a cbuf implementation in it [...] and I thought I'd try what worked in other contexts firxt
Yes, that makes sense.
Some additions though, after actually looking at your sketch:
- canBufferAudioOutput() should probably
return (circBuffer.room() > sizeof(AudioOutputStorage_t))
, instead (and you won't need that check in audioOutput(), then). - It should also try to write data to the DAC stream, but it should make sure not to block, if that is not currently possible.
- It also shouldn't print anything to Serial, or at least not on every iteration.
Background: audioHook() in your case is essentially doing (skipping some detail):
if (canBufferAudioOutput()) {
audioOutput(updateAudio());
}
Ok, I switched to devel/Mozzi2, and added the includes/defines. Without changing anything else I get:
/home/mwa/Arduino/libraries/Mozzi/internal/MozziGuts.hpp:232: undefined reference to `updateAudio()'
I'm not sure I grok the the logic. I was trying,
audioOutput(const AudioOutput f) write to cbuf,
canBuffer, read out to dac
But I just spotted 2 errors ...
The dac writes do block until the DREQ pin comes up. Writing to the dac:
void sdi_send_buffer(uint8_t* data, size_t len)
{
while (!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating IC is available
digitalWrite(VS_XDCS, LOW); //Select control
while ( len )
{
await_data_request();
delayMicroseconds(3);
size_t chunk_length = min(len, 32);
len -= chunk_length;
while ( chunk_length-- )
SPI.transfer(*data++);
}
while (!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating command is complete
digitalWrite(VS_XDCS, HIGH); //Deselect Control
}
void await_data_request(void)
{
while ( !digitalRead(VS_DREQ) );
}
If I understood the logic correctly it reduces to:
void audioOutput(const AudioOutput f) {
circBuffer.write((char *)f.l(), sizeof( f.l() ) );
}
bool canBufferAudioOutput() {
if (circBuffer.available() )
{
//read from cbuf to the char buff, 32 bytes
int bytesRead = circBuffer.read((char *)mp3buff, 32);
if (bytesRead != 32) // If we didn't read the full 32 bytes, that's a worry
{
return 0;
}
// Actually send the data to the VS1053
player.playChunk(mp3buff, bytesRead);
} else {
return 0;
}
return 1;
}
just pulled latest commits on dev/Mozzi2 and the error does go away if I:
void loop() {
//audioHook(); // required here
}
Ah, I had the old MozziGuts include in, maybe that was the problem :0
- beware, that Mozzi will not fare very well with a frequency of 44.1kHz, on the Nano, the default is 16.384kHz and on more powerful platform it is twice as big. This is mainly for performances purposes but, as your DAC will take care of the timing, a lot of things will be off if you let it take samples at 44.1.
Sorry, just spotted that remark. I can set the frequency and that seems to be a non-issue:
VSWriteRegister16(SCI_AUDATA, 16384);
...
Mode=0x4800 b100100000000000
Stat=0x0040 b1000000 (VS1053)
Vol =0x0000
AUDA=0x4000 (16384Hz)
Typing from my mobile. You still need to provide updateAudio(), too (where thhe Samples are produced). Am 28. Januar 2024 14:23:56 MEZ schrieb Mark Washeim @.***>:
Ok. I'm working on getting rid of the cbuf hack to make sure that doesn't get in way.
Ok. I'm working on getting rid of the cbuf hack to make sure that doesn't get in way.
That should not be the problem.
Based on what you posted about the routines for writing to the DAC:
#include "MozziConfigValues.h" // for named option values
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_EXTERNAL_CUSTOM
#include <Mozzi.h> // instead of MozziGuts.h
// other includes
void setup() {...} // as usual
AudioOutput_t updateAudio() {
// generate samples, as in any Mozzi sketch. E.g.:
return MonoOutput::from8bit(aSin.next());
}
void loop() {
// internally, sdi_send_buffer just sends one byte at the time. Duplicate that here, but avoiding the wait.
while(digitalRead(VS_REQ) && circBuffer.available()) {
delayMicroseconds(3); // not sure, if this is really needed, but copied from your code
char c;
circBuffer.read(&c, 1);
SPI.transfer(c);
}
audioHook(); // needed in every Mozzi sketch
}
// like before:
void audioOutput(const AudioOutput f) {
circBuffer.write((char *)f.l(), sizeof( f.l() ) );
}
// very simple, now:
bool canBufferAudioOutput() {
return (circBuffer.room() >= sizeof(AudioOutputStorage_t)); // room for at least one more sample
}
Ah, ok, I think I get what you meant before. I had not thought of just putting it in the loop. Thanks!
I am more a spectator here, but I wonder, for cleanness, if part of that code should actually be in the void loop()
. Another suggestion built on @tfry-git 's, even if I think it is completely equivalent though:
void loop() {
audioHook(); // needed in every Mozzi sketch
}
// like before:
void audioOutput(const AudioOutput f) {
circBuffer.write((char *)f.l(), sizeof( f.l() ) );
}
// very simple, now:
bool canBufferAudioOutput() {
while(digitalRead(VS_REQ) && circBuffer.available()) {
delayMicroseconds(3); // not sure, if this is really needed, but copied from your code
char c;
circBuffer.read(&c, 1);
SPI.transfer(c);
}
return (circBuffer.room() >= sizeof(AudioOutputStorage_t)); // room for at least one more sample
}
This only has the advantage of keeping the void loop()
clean, but as audioHook()
is calling canBufferAudioOutput()
should be equivalent.
Ok, both work for me, but negelect that I'm actually copying from the cbuf to a 32 byte buffer which then get's sent 'whole'. But otherwise, looks like it could go. I have to rework the driver some more since I'm getting into conflicts over who controlls the spi bus :)
Ok, I've have compiling and can't even get a hello world serial output. I've tried removing the cbuf method. But something is broken. Other mozzi (unaltered) sketches 'just work' and the driver (without mozzi) works. Where should I start to debug this.
In such cases, I usually try finding out what parts of the code get reached, e.g. by toggling the LED_BUILTIN pin. Serial is also an option of course, but beware that Serial does not transmit, instantly. You may want to add Serial.flush() to get more reliable info.
If you post an example, we can look at that, too. The smaller the better (and sometimes, the process of minimizing helps understand what's going wrong, in the first place).
Ok, I just took a PT8211 and wired it to the nano (digi 6 not 5) and that seems to work fine with the Mozzi2 branch. It's noisy, though. Not sure what the artifacts are. In any case:
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_EXTERNAL_TIMED
#define MOZZI_AUDIO_CHANNELS MOZZI_STEREO
Works. Is it possible that not defining the number of channels was the issue with my sketch?
I found a bug or two in my ripped cbuf but had taken that out of the way anyway.
Ok, I'm getting closer, but keep simplifying. Just for reference, here is a non-mozzi, very crappy write a sine wave out to dac. I'm reworking the CircleBuffer and just hacked this together:
https://github.com/poetaster/VS1003/blob/master/examples/vs1053_wavcpm_sine_nomozzi/vs1053_wavcpm_sine_nomozzi.ino
After starting the VS (hardcoded write_register(SCI_AUDATA,16384); ) the WAV/RIFF header is sent and that patch pounds a nasty approximation of a sinewave with no control at the dac. Ugly. But it works.
I'll keep trying some primitive variations of this with the mozzi variant WITHOUT circumventing the buffer. So,
#include "MozziConfigValues.h" // for named option values
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_EXTERNAL_TIMED
#define MOZZI_AUDIO_CHANNELS MOZZI_MONO
BTW, the reason this is in a VS1003 context is that for playing PCM, either chip 1003/1053 will behave the same way with the same setup. Which is nice. That old driver is the very minimal needed for audio out with PCM. Hence the choice to start there.
Not sure if any of these are of interest:
In file included from /tmp/arduino_build_85259/sketch/vs1053_wavcpm_sine.ino.cpp:1:0:
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h: In member function 'StereoOutput& StereoOutput::clip()':
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:57:39: warning: integer overflow in expression [-Woverflow]
#define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) MOZZI_AUDIO_BIAS), (AudioOutputStorage_t) MOZZI_AUDIO_BIAS-1)
/home/mwa/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h:95:41: note: in definition of macro 'constrain'
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
^~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:107:31: note: in expansion of macro 'CLIP_AUDIO'
StereoOutput& clip() { _l = CLIP_AUDIO(_l); _r = CLIP_AUDIO(_r); return *this; };
^~~~~~~~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:57:121: warning: integer overflow in expression [-Woverflow]
#define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) MOZZI_AUDIO_BIAS), (AudioOutputStorage_t) MOZZI_AUDIO_BIAS-1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/mwa/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h:95:60: note: in definition of macro 'constrain'
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
^~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:107:31: note: in expansion of macro 'CLIP_AUDIO'
StereoOutput& clip() { _l = CLIP_AUDIO(_l); _r = CLIP_AUDIO(_r); return *this; };
^~~~~~~~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:57:39: warning: integer overflow in expression [-Woverflow]
#define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) MOZZI_AUDIO_BIAS), (AudioOutputStorage_t) MOZZI_AUDIO_BIAS-1)
/home/mwa/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h:95:41: note: in definition of macro 'constrain'
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
^~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:107:52: note: in expansion of macro 'CLIP_AUDIO'
StereoOutput& clip() { _l = CLIP_AUDIO(_l); _r = CLIP_AUDIO(_r); return *this; };
^~~~~~~~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:57:121: warning: integer overflow in expression [-Woverflow]
#define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) MOZZI_AUDIO_BIAS), (AudioOutputStorage_t) MOZZI_AUDIO_BIAS-1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/mwa/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h:95:60: note: in definition of macro 'constrain'
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
^~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:107:52: note: in expansion of macro 'CLIP_AUDIO'
StereoOutput& clip() { _l = CLIP_AUDIO(_l); _r = CLIP_AUDIO(_r); return *this; };
^~~~~~~~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h: In member function 'MonoOutput& MonoOutput::clip()':
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:57:39: warning: integer overflow in expression [-Woverflow]
#define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) MOZZI_AUDIO_BIAS), (AudioOutputStorage_t) MOZZI_AUDIO_BIAS-1)
/home/mwa/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h:95:41: note: in definition of macro 'constrain'
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
^~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:152:29: note: in expansion of macro 'CLIP_AUDIO'
MonoOutput& clip() { _l = CLIP_AUDIO(_l); return *this; };
^~~~~~~~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:57:121: warning: integer overflow in expression [-Woverflow]
#define CLIP_AUDIO(x) constrain((x), (-(AudioOutputStorage_t) MOZZI_AUDIO_BIAS), (AudioOutputStorage_t) MOZZI_AUDIO_BIAS-1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/mwa/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h:95:60: note: in definition of macro 'constrain'
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
^~~~
/home/mwa/Arduino/libraries/Mozzi/internal/../AudioOutput.h:152:29: note: in expansion of macro 'CLIP_AUDIO'
MonoOutput& clip() { _l = CLIP_AUDIO(_l); return *this; };
Global variables use 1915 bytes (93%) of dynamic memory, leaving 133 bytes for local variables. Maximum is 2048 bytes.
Low memory available, stability problems may occur.
Just a quick note. I'm going to try this on a pi pico since I don't believe I'll have the processing power and ram on a nano. However much I love that machine, 3 different circular buffer types and I'm at writing assembler. And I still won't have enough ram. So, I'll get back to this thread when I've tried on a pico.
Sigh. Well, the pi pico experiments aren't much better :) pschatzmanns tools and drivers didn't help me, though, with his driver my crappy direct to dac sketch works: https://github.com/poetaster/VS1003/tree/master/examples/vs1053_wavcpm_sine_nomozzi_pico
But, frankly, I can't get reliably audio on the pico either. I've done some radical reductions like:
void audioOutput(const AudioOutput f) {
mp3buff[buf_write_pos++] = f.l();
}
void loop() {
if ( (buf_write_pos ) > 31) {
player.playChunk(mp3buff, 32);
buf_write_pos = 0;
}
audioHook(); // needed in every Mozzi sketch
}
(pico), but that doesn't work. That DOES work with two types of osc. One is just values from a table and the other is an osc. object (stolen from bela). But my crappy approaches feed to the DAC just fine (with the ancient 1003 driver on arduino 328 or the newer 1053 driver on RP2040).
Not sure what to do, but I thought I'd try going back to mozzi 1.1.2 and test there.
Not sure if I understand you correctly. What you're saying is: You can get some very simple audio output going along these lines, but as soon as your output gets any more complex, it stops working? (How exactly does "doesn't work" show?)
That pattern would actually make a whole lot of sense: The code you show buffers 32 bytes of data, then essentially blocks until it can write them all at once, leaving an empty buffer. If your audio generation is minimal, it may be fast enough to avoid a buffer underrun, but that will easily be exceeded with anything even slightly more complex.
I think at the minimum, your loop() should contain a check for digitalRead(VS_REQ)
, and only if that shows that data can actually be written, you can proceed to call playChunk(). (If I read the datasheet right, VS_REQ actually signifies that 32 bytes of data can be accepted, so that might be ok, too, depending on what exactly goes on inside playChunk().)
Not sure if I understand you correctly. What you're saying is: You can get some very simple audio output going along these lines, but as soon as your output gets any more complex, it stops working? (How exactly does "doesn't work" show?)
No, it's not a matter of complexity, per se. On the nano I've only been with and without mozzi. There, I can generate any kind of waveform (also with an osc. object ) and feed it to the dac. The simple form being read from table (with offset) and only playchunk when the mp3buff is full. That works. But not with mozzi.
On the RP2040 the issues are compounded by there being a bug (working on it) in the 1053 driver which is not the same one I'm using on the nano.
That pattern would actually make a whole lot of sense: The code you show buffers 32 bytes of data, then essentially blocks until it can write them all at once, leaving an empty buffer. If your audio generation is minimal, it may be fast enough to avoid a buffer underrun, but that will easily be exceeded with anything even slightly more complex.
I've removed all blocking in the sketch and on the nano made sure the driver is running at rates that nano can supply. Seems ok (tested 8000 - 16 000).
I think at the minimum, your loop() should contain a check for
digitalRead(VS_REQ)
, and only if that shows that data can actually be written, you can proceed to call playChunk(). (If I read the datasheet right, VS_REQ actually signifies that 32 bytes of data can be accepted, so that might be ok, too, depending on what exactly goes on inside playChunk().)
The driver, handles that buffer logic. #231 (comment)
But, in short, you think I'm getting buffer underruns? But those would only lead to choppy sound, not to NO sound, or? If I do serial output in the simple sketch, the sound get's choppy fast.
Hi,
Sorry, I am following that from a bit far away, but I am very interested: making buffered codecs with Mozzi, although possible in theory, has never been done. I will probably try to put my hand on one of these chips (but no sure I can solder the chip with the capabilities I have now, are you using a breakout?). But might not be tomorrow ;)
No, it's not a matter of complexity, per se. On the nano I've only been with and without mozzi. There, I can generate any kind of waveform (also with an osc. object ) and feed it to the dac. The simple form being read from table (with offset) and only playchunk when the mp3buff is full. That works. But not with mozzi.
So to summarize, the problem seems to be how to get samples from Mozzi into your buffer. As: you manage to correctly transfer the content of the buffer to the DAC right?
The way I would approach this problem would be first to try to make the DAC play from a buffer that is not actuated by Mozzi: fill it with 32 recognizable samples in setup (16 at max value and 16 at min value), and get it transfer while Mozzi is started (maybe in updateControl()
?). That can be done in standard condition at first (no EXTERNAL_AUDIO_OUTPUT
) allowing the PWM default output to also be checked (to see if Mozzi is running).
Then, if that works (DAC and PWM output something reasonnable), I would switch to a EXTERNAL_AUDIO_OUTPUT
but still not actuate the DAC in it: maybe start with a simple as a analogWrite()
(not as efficient as mozzi's default, but that is just for checking).
Next step would be the same, but with bypassing the buffer and using yours for analogWrite()
, with the DAC living its own life, looping on its 32 samples that you keep refreshing. You can the send the sample to analogWrite as a bunch of them at the same time, it will sound horrible but that is just to check that everything work at the same time.
If all of that works, sending the buffer to the DAC should be possible. The idea is to first test that there is no hidden conflict, then that the buffer implementation is working correctly. Not sure if that helps…
Not really related but as you mentioned it: the PT8211 is a cheap DAC but you can manage quite good sound quality with it. The crux is that it is nowhere stabilized, hence you should provide it a quite clean voltage source. This is especially true if you are powering the board via USB, which is extremely noisy. It also benefits with filtering the output. I managed some quite good results with the setup here, but this is powered by battery which also helps!
are you using a breakout?). But might not be tomorrow ;)
I have a number of different types of boards. Purchased in China, they're about 8 euros. I'd be happy to sponsor Mozzi with one or two of these. The 1053 is nice since it also has stereo in and general midi instruments on board :) Let me know, and I'll send a board.
So to summarize, the problem seems to be how to get samples from Mozzi into your buffer. As: you manage to correctly transfer the content of the buffer to the DAC right?
Yes. I simplified so far that on the loop a sample to the buffer and simply send and reset if I have 32. That works fine. Building a proper Circular buffer has proven to be more of a challenge :)
The way I would approach this problem would be first to try to make the DAC play from a buffer that is not actuated by Mozzi: fill it with 32 recognizable samples in setup (16 at max value and 16 at min value), and get it transfer while Mozzi is started (maybe in
updateControl()
?). That can be done in standard condition at first (noEXTERNAL_AUDIO_OUTPUT
) allowing the PWM default output to also be checked (to see if Mozzi is running).
I did try something like this, but will do so again, more methodically. I also thought: ' why not just let mozzi bang at the dreq pin once the codec is up :) In theory it should work :)
Then, if that works (DAC and PWM output something reasonnable), I would switch to a
EXTERNAL_AUDIO_OUTPUT
but still not actuate the DAC in it: maybe start with a simple as aanalogWrite()
(not as efficient as mozzi's default, but that is just for checking).
Check.
Next step would be the same, but with bypassing the buffer and using yours for
analogWrite()
, with the DAC living its own life, looping on its 32 samples that you keep refreshing. You can the send the sample to analogWrite as a bunch of them at the same time, it will sound horrible but that is just to check that everything work at the same time.
Check.
If all of that works, sending the buffer to the DAC should be possible. The idea is to first test that there is no hidden conflict, then that the buffer implementation is working correctly. Not sure if that helps…
Yes, I had thought that there maybe a conflict with the use of SPI. The drivers I'm using do some locking on the SPI side (to avoid contention) and that occured to me could be an issue. But, I'll go through the above outline first.
Not really related but as you mentioned it: the PT8211 is a cheap DAC but you can manage quite good sound quality with it. The crux is that it is nowhere stabilized, hence you should provide it a quite clean voltage source. This is especially true if you are powering the board via USB, which is extremely noisy. It also benefits with filtering the output. I managed some quite good results with the setup here, but this is powered by battery which also helps!
I took a look at the spec sheet for the PT8211 and they do 'require' some more filtering. I think the 5012 based boards, at 2 euros more, are better isolated and less of a problem. But I need to do some more testing. I'll try the PT8211 again when I've gotten farther with this. Thanks!
Ok, so first test with mozzi after driver test:
brief excerpt:
#include "MozziConfigValues.h" // for named option values
#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_EXTERNAL_TIMED
#define MOZZI_AUDIO_CHANNELS MOZZI_MONO
in setup()
player.begin();
player.setVolume(90);
player.playChunk(HelloMP3, sizeof(HelloMP3));
delay(500);
startMozzi(CONTROL_RATE); // :)
aSin.setFreq(440); // set the frequency
player.playChunk(HelloMP3, sizeof(HelloMP3));
send_header();
The first chunk of audio (hello!) plays. Delay, and after startMozzi, the second attempt fails. The send_header() command is executed by the module but it's unclear if it get's to the dac.
So Mozzi is doing something odd. If I start mozzi first, no sound.
Thanks for the test! Is this on the Nano?
Mozzi is fiddling with a lot of things when starting: Timer especially, there might be a conflict there (might try to look more into details tomorrow). I had something a bit similar on the pico: depending if the I2S was started before of after Mozzi it would work, or not… (I might be able to find somehow in this thread, but could you link the library you are currently using for the VS10?)
Thanks for the DAC references btw! I looked some time ago for other type of DAC, but could not converge… Nice to have some advice from a DIYer!
Thanks for the test! Is this on the Nano?
That test was with pico and the VS1053Driver from pschatzmann. But it's the same(with Mozzi2) on the Nano and other drivers.
Mozzi is fiddling with a lot of things when starting: Timer especially, there might be a conflict there (might try to look more into details tomorrow). I had something a bit similar on the pico: depending if the I2S was started before of after Mozzi it would work, or not… (I might be able to find somehow in this thread, but could you link the library you are currently using for the VS10?)
ah, I need to look at the guts :) I'm using https://github.com/pschatzmann/arduino-vs1053 on the pico and this one, https://github.com/maniacbug/VS1053 (smaller footprint, less features) on the nano.
Thanks for the DAC references btw! I looked some time ago for other type of DAC, but could not converge… Nice to have some advice from a DIYer!
I need to test this again, but I have one I2S interface that has really clean audio even with usb power (it's a 5102a with phone and headphone outs). I'll try to put some more time into that since it's my default out DAC.
This is the DAC probonopd/MiniDexed#279 (comment) I mean.