-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
RFD - PCM audio support #1085
Comments
If it were possible to add audio sampling (i.e.: microphone) in addition to this, then the ESP could be used for simple 2-way audio, which would be beyond awesome. |
+1, I remember ESP8266 support i2s interface, but could not find any code example. |
What @devyte said! I think it would be a challenge to make it run smoothly given the non-preemptive nature of the SDK (and shortage of RAM), but if it can be done it'd get a 👍 from me. |
Sounds exciting! How about a WeMos D1 mini with a stackable SD card shield that plays music autonomously when connected to speakers 😄 |
Thanks for your inputs, guys. Audio sampling I2S
On the pro side this solution offers best audio quality and hardware streaming support. Other audio solutions Real-time characteristics |
Had a deeper look into sourcing pcm data from files. This worked very well using the new task interface. The current implementation uses double buffering, each with 1024 bytes. Their size can probably be reduced further since margins are quite big as seen in the plots below. Yellow channel traces audio signal.
|
I am also interested in this short of approach for other bit banging drivers. Just an aside comment. But this is looking good 😄 |
Yes, the concept of double buffering and filling them from a file reader task is quite generic. All specific logic is part of the ISR - feeding a DAC, or pushing patterns through GPIOs. I don't have yet a final view on my implementation of the buffering stuff here, things are still moving. But the ingredients are clear: data producing task, data consuming ISR, and in between a fifo/ double buffering scheme. Having a shared, generic solution for the latter one should be feasible. But that's brainfood for a different issue. |
Up to now I worked just on demonstrators to investigate certain feasibility aspects. The Lua API itself still needs to be settled. Your feedback was very helpful to rethink the overall structure. pcm modulePlay sounds through various back-ends. Supported hardware is sigma-delta (, I2S, XYZ). pcm.new()Initializes the audio driver. Syntax
Parameters
ReturnsAudio driver object. Audio driverEach audio driver provides the same control functions for playing sounds. pcm.drv:close()Stops playback and releases the audio hardware. pcm.drv:on()Register callback functions for events. Syntax
Parameters
Returns
pcm.drv:play()Starts playback. Syntax
Parameters
Returns
pcm.drv:pause()Pauses playback. A call to pcm.drv:stop()Stops playback and releases buffered chunks. |
I would vote pro:
|
Interesting input, thanks Vladimir!
Will consider this for sure as it removes a lot of specific handling from the module. Adding a Lua call layer might slow down things, will check the timing impact later.
Why do you propose
My first sketch considered dedicated
Yes, that was a typo. |
Callbacks: I would just report |
My current model requires that the callback needs to feed data in time before the internal buffers are drained. This is why I plan to distinguish between A simple example : function pcm_cb(d, event)
if event == "data" then
return file.read()
elseif event == "drained" then
print("file done")
file.close()
end
end
file.open("output_16k.u8", "r")
drv = pcm.new_sigmadelta(1)
drv:play(pcm.RATE_16K, pcm_cb) |
@devsaurus I'm curious about your callback model, it's different than the other ones I've come across so far. E.g.: connections:
Applying that model to your interface, it would look like this:
Notice that the if-else logic for the event type in your callback is eliminated. |
I just read somewhere that the onboard ADC could maybe do 2.5KHz sample rate. that would give a theoretical mic bandwidth of 1250 Hz, which I think is too narrow for a mic. I guess sampling with the onboard ADC could still be attempted to check whether that's true, but most likely an external ADC over I2C or something would be needed to make it viable. |
@devyte Right, the example you gave for net is also used in mqtt and uart. A similar approach is found in wifi.sta.eventMonReg(), while other modules do callback registration with a single function like enduser_setup.start() and sntp.sync(). It appears that the |
Regarding ADC I did a quick assessment of the obvious options in the meantime.
Up to now I don't see any promising approaches. My conclusion would change once there's an external solution which can be attached via an interrupt-driven or DMA-like interface. |
@devsaurus
Pros:
On the other hand, a different approach could be used with one function per callback, i.e.: drv:onDrained(onDrainedCallback)
drv:onData(onDataCallback) or: drv:onSent = onSentCallback
drv:onData = onDataCallback This requires no string, but it does require one function per callback. I'm not sure what that means for lua under the hood, though, maybe strings are used to identify/lookup the function? The first of the above is safer from a coding PoV, because the error checking is implicit and smaller than in your case. The second is easier from an implementation PoV, because it doesn't require functions to be implemented, i.e.: the callbacks are just table entries. However, it's slightly more error prone (typos and such), with pretty much no diagnostics to detect them. |
@devsaurus about a device on I2C, I've seen ESP projects with external I2C ADCs doing sampling rates of 40KHz. Didn't look at the details tho...to be honest, 20KHz would be pretty for a mic, and I think we could probably get away with as low as 8KHz. |
Also, how about an ADC with SPI interface? They seem to be cheaper vs. I2C, I see a 4-channel one at USD$2.2 with sample rate of up to 200KSps, which is kind of overkill, of course. The I2C ones seem to go 6-12 bucks a pop. |
Thanks for the detailed feedback on callbacks! Regarding the ADCs - do you have any links for future reference? I don't intend to rule out recording, but would leave this topic to a second iteration once audio generation is settled. |
I found some cheaper I2C ones... NCD9830 I2C, 8bit x 8ch, 2.5-70KSps @ just over 3 bucks |
@devsaurus does this make any sense to you? |
List of links to discussions of the ADC that I've come across (for future ref) It seems some people claim it's possible to do fast sampling with the internal adc, it's just that the wifi and task priorities make it unreliable. I also suspect that they're not using an efficient interrupt scheme. Does that make any sense, or am I writing nonesense? |
Time for a sign of life - most of the API sketch is implemented now in |
I have only ever used the online tool to make custom firmware. Is there a way to build devsaurus/pcm to include https support? I am very excited to play with some raw audio and the nodemcu. |
@Phando Joe, this isn't the right place to ask this sort of Q. Our support page give you links which provide forums for this type of Q. |
Thanks and sorry. Keep it up, the build service is great
|
Hi everybody! I am working with an ADC who works with SPI comunication to treat audio signals.(50Khz) (MCP3201). And this works good when i want to send the data through WIFI UDP using a time.alarm(). |
@guillermo22, I think you should ask your question on the forum. |
I'm closing this since the related PR is well in review loop. |
Hi, I know this is a closed issue but recently I was trying out the "play_file.lua" example code. But when I run the code, I get the following error message: PANIC: unprotected error in call to Lua API (bad argument #1 to '?' (file.obj expected, got userdata)) I changed the line drv:on("data", file.read) to drv:on("data", file) after that, I do not see any error but nothing happens on the pin and also drained cb gets called real quick. Any reason as to why this might happen? |
@navin-bhaskar see #1712 for the fix of this error. |
Thanks! that worked. |
The recent additions of sigma-delta modulation with #1000 and a precise µs timer in #1057 open up the potential for audio support. Apart from a Lua interface, just some glue code is required to combine both into a simple mono audio back-end.
What I envision is support for playing wav-like files over any of the GPIOs:
Few external components for filtering will convert from digital to analog domain and attach to either a headphone jack or an active amplifier driving standard 4-8 Ω speakers. A quick 'n' dirty feasibility study is available in my
pcm
branch, complemented by some notes describing the external analog filter & amplifier and sample Lua code.But before developing this into a PR, I'd like to check with the community whether such a module has a use case and is still within this project's scope. I'll follow up with API concept and architectural details once there's a strong indication that this functionality is considered to be useful.
The text was updated successfully, but these errors were encountered: