I was looking for a slim lib that provides the function to play some sounds on button press over I2S.
What happens with your lib when one sound gets started while the other is still playing?
Is it possible to read the data from PROGMEM char instead? (trouble with non-existing PSRAM)
The sounds are mixed dynamically. If you play one and then play the other, both will play at once.
This is somewhat limited. It takes CPU to mix, and to play sound, and you don't have much.
ESP32s don't need PROGMEM. That's an AVR thing. You can use this with AVR probably but it won't be very capable.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Yes the progmem things mainly because I stepped over from XT-DAC and the Wemos Lolin D32 doesn't have PSRAM capabilities. I guess I have to play around with that a bit, platformio seems to flash the wav from your demo code but it can't read it then.
Tried even with littleFS but oh well... that is why the PROGMEM way smiled to me.
E (132) psram: PSRAM ID read error: 0xffffffff
E (1147) esp_littlefs: ./components/esp_littlefs/src/littlefs/lfs.c:1347:error: Corrupted dir pair at {0x0, 0x1}
E (1148) esp_littlefs: mount failed, (-84)
E (1150) esp_littlefs: Failed to initialize LittleFS
[ 1036] E][LittleFS.cpp:95] begin(): Mounting LittleFS failed! Error: -1
For the sounds playing at once I think that will be okay for me, as they are pretty short they will just overlap for some ms.
It should be able to read it with SPIFFS. I don't know about LittleFS. I don't use it because there are no integrated tools for it.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
but what does your code expect ? Sorry, I am not too deep into coding. Not sure how file() reads the data actually into mem, like it is used in your code.
I assume your file_stm() chops the WAV header and does some other preparation?
The generated code produces uint8_t arrays. For some reason you want char arrays. No. Using char arrays to hold binary data is very problematic. For starters chars are typically signed values. Second, it's easy to confuse them with null terminated strings. It's just generally a bad practice.
My SFX code loads from streams. The streams can be over a uint8_t array using sfx::buffer_stream (read/write) or sfx::const_buffer_stream (preferable if you don't need to write to the array)
The wavs are not preprocessed. They are streamed directly from the array. There is no chopping, or otherwise preprocessing.
sfx::file_stream loads from a file, like off of SPIFFS. If you use that it will stream directly from flash.
You pass streams to the various sfx classes to do things with them.
C++
const_buffer_stream stm(my_wav_array, sizeof(my_wav_array));
wav_file_source wav;
sfx_result errcode = wav_file_source::open(stm,&wav);
if(errcode!=sfx_result::success) {
Serial.println("Error opening WAV");
while(true);
}
// set to loop
wav.loop(true);
transport trans;
errcode = transport::create(i2s_output,wav);
if(errcode!=sfx_result::success) {
Serial.println("Error creating transport");
while(true);
}
// play wav until restart
while(trans.update()==sfx_result::success);
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
SPIFFS is less than ideal anyway because it's like an order of magnitude slower to read from flash than it is to read from a header.
I'll level with you about that. My libraries, like this one (htcw_sfx) and htcw_gfx (my graphics library) are cross platform. They're designed to run on any device, even a PC. Because devices have such different capabilities my libraries are designed to be flexible. That doesn't mean every method is *practical* on every device, if that makes sense. Reading from flash is doable in narrow situations, but in general I would avoid it on the ESP32 if you need performance, which you do in this case.
Embedding wavs as headers is the way to go.
You'll have to mix the wavs using the sfx::mixer class (more control, more code) or the sfx::performer template class (less code, easier to use). See the article for using that.
If you run into issues let me know. The performer class isn't perfect. I was still ironing out kinks at the time of this article and I haven't really picked it up since because life got in the way.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Thanks for your time and quick responses, I'll mess around with it a bit with my limited c++ horizon and probably have to get back to you with one or two questions. I'll also let you know, once I successfully implemented it. Lifetime... oh yes, I am on this project for about two years already with long pauses in between. A lot of hardware experiments and obstacles and almost everything of it new to me.
const_buffer_stream stm(my_wav_array, sizeof(my_wav_array));
wav_file_source wav;
sfx_result errcode = wav_file_source::open(stm,&wav);
if(errcode!=sfx_result::success) {
Serial.println("Error opening WAV");
while(true);
}
// set to loop
wav.loop(true);
transport trans;
errcode = transport::create(i2s_output,wav);
if(errcode!=sfx_result::success) {
Serial.println("Error creating transport");
while(true);
}
// play wav until restart
while(trans.update()==sfx_result::success);
Here "my_wav_array" would be the array you got by dropping a wav file into https://honeythecodewitch.com/gfx/converter[^] while using C/C++ as the Output Type (selected on the page), and typing my_wav_array into the Name field.
When i call wav_file_source::open(stm,&wav) at that point my SFX lib checks the wav header and seeks to the beginning of the wav data.
Any stream used by an open() call must not be close()ed until you're done using it with SFX.
This doesn't really apply to arrays but it does apply to files. Note that using arrays is much faster. I wouldn't try to mix two Files. It would be really slow. Mixing two arrays is more realistic.
Anyway, the transport takes your source (in this case, a wav file source) and sends it to another acceptor - usually an output device, but could be a filter, if I remember correctly.
In this code we're just sending it to the i2s_output - whatever that is in this case.
Edit: I think my transport::create call is wrong in the above code. It should take &trans as the last parameter.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Thanks again, yes the speed issue is another thing I was concerned about, because it will happen that the user will initiate the sounds with pretty short pauses in between (like a space invader fire button).
Though it probably isn't that dramatic if the playing sound gets cut when button is pressed again.
B) use the performer class, which manages "voices"/polyphony for you.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Wavs may not always be appropriate here. They are large. You may find you run out of program space pretty quickly.
Also keep in mind that your wav samplerate and channels need to match what you give SFX. I usually use 44.1khz monoaural wavs.
You might want to consider using things like sine waves or triangle waves and such for some of the simpler sounds. It's more efficient if you don't need a wav.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
I know, I crunched them down to 8kHz mono already (stereo not required). But mainly because I used the internal DAC before, which allowed 8kHz only anyway. I2S sounds already so much better due to the used (filtered) amp board.
But I guess I can go up to 22kHz. Overall I'll have like 5 or 6 sounds, all are pretty short and shouldn't consume more than 1MB in sum. The remaining space should be enough for the code, won't be much more than 500 lines + the libs of course.
Keep in mind the default arguments for things like the transport, the wav_source etc are all 44.1khz. You'll have to explicitly specify a different samplerate if you want to use it.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Yes of course, I expect some 22kHz samples will play too fast then if I don't change anything.
I tried to adapt your suggestions to your generic example:
#include <Arduino.h>
#include <i2s_external.hpp>
#include <sfx.hpp>
usingnamespace arduino;
usingnamespace sfx;
using audio_t = i2s_external < -1, 26, 25, 22, i2s_channels::both, true >;
#define E822_IMPLEMENTATION
#include <e822.h>
audio_t sound;
void setup() {
Serial.begin(115200);
if (!sound.initialize()) {
Serial.println("Unable to initialize I2S audio");
while (1);
}
const_buffer_stream stm(e822, sizeof(e822));
wav_file_source wav;
waveform_source<> wform;
sfx_result r = wav_file_source::open(stm, &wav);
if (r != sfx_result::success) {
Serial.println("Error opening WAV");
while (true);
}
wav.loop(true);
mixer_source<2> mixer;
r = mixer_source<2>::create(&mixer);
if (r != sfx_result::success) {
Serial.print("Error creating mixer: ");
Serial.println((int)r);
while (1);
}
mixer.voice(0, &wform);
mixer.level(0, .25);
mixer.voice(1, &wav);
mixer.level(1, .5);
transport trans;
r = transport::create(sound, mixer, &trans);
if (r != sfx_result::success) {
Serial.print("Error initializing transport: ");
Serial.println((int)r);
while (1);
}
size_t written;
float fq = 200;
float fd = 1;
// make frequency go up and down in a loopwhile ( r == sfx_result::success) {
r = trans.update();
wform.frequency(fq);
if (fq + fd > 2600) {
fd = -fd;
}
if (fq + fd < 200) {
fd = -fd;
}
fq += fd;
}
}
void loop() {
}
however it fails compiling at
src/test.cpp: In function 'void setup()':
src/test.cpp:38:9: error: 'class sfx::mixer_source<2>' has no member named 'voice'
mixer.voice(0, &wform);
^~~~~
src/test.cpp:40:9: error: 'class sfx::mixer_source<2>' has no member named 'voice'
mixer.voice(1, &wav);
^~~~~
*** [.pio\build\lolin_d32\src\test.cpp.o] Error 1
Looks like platformio picked the latest sfx version, what did I miss ?
looks like that has been renamed to track() instead of voice()
sorry, that article is old.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
No problem, both names make sort of sense to me.
It works so far, sounded pretty damaged with my 8kHz 8bit sounds, thought I could set these parameters by adding
Anyway, the sound in 44kHz 16 bit works, now I just need to learn a bit from your other example on how to trigger the sounds by button without interrupting my loop too much and I am ready to go with the sound part.
Thanks a lot! Your work really deserves more attention.
You're quite welcome. Yeah, 8Khz 8-bit is pretty nasty.
The trick with playing sounds is either write everything cooperatively threaded, such that your classes have update() methods on them and only perform a bit of work at a time, OR you can create a high priority FreeRTOS task in ESP32, and in the loop there you can drive the sound. Be careful with that though, as there are many pitfalls, and it's fundamentally less efficient than the first method, overall.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
I will try with 44kHz 16bit mono, maybe it fits with the few sounds.
About the multithreading, yes basically there is not much to do in my code though.
It plays a sound, sends a bit through wifi UDP and blinks an LED.
In theory though, the wifi thing is another obstacle that needs to be solved but at least I understand how to do it, there just seems to be a bug in the current espressif pack so ESP32 LR mode can't get connections. But that is another construction yard.
Worst thing that could happen here is that it prevents too quick button presses but it is about half of a second or smth like that. It should be ok.
While this happens, the second core of the ESP is listing on RX for incoming data, it took me quite some attempts to get this working, the ESP is quite picky with that and easily quits in boot loops if something isn't okay for it. Anyway, once it detects incoming data it will play another sound but at this time all button presses are blocked anyway, so it won't get into trouble with other activities. Uhh, maybe I should say "shouldn't".
as soon as audiodata is wrapped with stm, all SFX operations will take that stm.
r is just an error code. it is sfx_result::success if it succeeded, or one of several possible error values on failure.
remember you need to keep your stm around and opened until you'll never play the wav again
and yeah, you'd use stm. And you wouldn't bother creating the wve_file_source, wav, because performer already handles all that.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
you can't capture in your lambda expression. you're trying to pass stm into a lambda. you can't do that. a lambda is its own function.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
Last Visit: 31-Dec-99 18:00 Last Update: 24-Jul-24 2:13