-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add Audio effects #9640
base: main
Are you sure you want to change the base?
Add Audio effects #9640
Conversation
I'm still digging into the code, but my first instinct is to have the Speaking of buffer sizes, it may be nice to explore having a fixed buffer size and altering the rate of playback of that buffer depending on the delay setting. This would be similar to a bucket-brigade style delay pedal and require some more advanced interpolation, etc. I think it be worth exploring an "Analog" delay effect of this nature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the initial PR! This is very exciting.
I'm not too worried at this point as I'm using Same thought on the
I think this would be good for another effect. I have tossed about the idea of a generic delay buffer that other effects could use. |
Update for anyone following along:
Getting a lot closer. |
Word to the wise... 44.1Khz dual channel audio with asyncio without enough sleep in a loop causes weird stuttering that you will try to debug all night long, and turns out just add a small delay to the sleep loop and you are good. Maybe a sign a 0 asyncio.sleep isn't running audio tasks fully? But it did prompt me to add double buffering in. |
This is very weird! I'd expect audio stuff to run in between VM byte codes. I'd appreciate it if you could dig into why it isn't working. |
Latest updates:
Things are close now. I still want to see if I can tell why MP3s are not working (they should be don't think it is a processing limit). |
Took a look, seemed between two channels of 44.1Khz audio and constantly checking a rotary encoder I hit the processing limit as close as I can tell. Or the rotary encoder blocked long enough (and I was checking it often enough) to slow the audio down. So at this point seems to be a non-issue. |
MP3Decoder as a source works now. Was a bug I just hadn't ran into. Also samples of unusual lengths work now (wasn't update the remaining sample buffer correctly, and most samples use values that just happened to work). |
I finally got around to testing out your audio_effects fork and playing around with audiodelays.Echo. I was hoping to recreate some form of chorus effect with this module, but I don't think that's possible with the current implementation. The For instance, if you wanted to create a strong "slapback" style delay, this is what I would expect to work in that scenario (I'm excluding
The expected outcome would be a full volume repeat of the audio after 500ms and then no additional feedback/decay of that audio. Instead, there is no effect on the audio output because the The solution is relatively simple. Instead of applying |
I've created a pull request on your fork for your consideration, @gamblor21. gamblor21#1 |
I will take a look but probably not until tomorrow. What you were saying makes sense. One thing I do plan to do is create more effects then just this echo. This really is a proof-of-concept still. I was looking a chorus briefly last night, and want to consider others (reverb) as well. If you are playing with it another thing you can try is chaining echos (since that is my only effect ha). But basically |
Though the
In this example, it is clear that the value of the LFO is changing over time, but there is no audible change in the output. |
Oh good catch! Thanks, I'll have a fix for that soon I know what is wrong. |
First off, thank you very much for testing things for me! Second I looked at the PR you added and what you are talking about here. I wrote echo to decay on the first playback to make it more of an echo. I think maybe you want more of a straight delay? So at T=0 you hear original sample, at T=500ms you hear the repeat of the original sample at full volume, and then nothing else after? I think for that I either need to add a flag "don't decay first playback" or I'm thinking a delay effect may be a good idea. It is on my list of things to build. I do want more effects as opposed to one audio effect to rule them all. (Chorus is also on that list) |
I will admit that the example I provided was an extreme use-case and not a good representation of the problem at hand. I still think this is an issue that should be addressed regardless of our interpretation of "echo" vs "delay". I will do my best to demonstrate the issue here.
The in proposed outcome, the level of the initial repeat (and essentially the most audible one) is controlled independently of the rate of the decay/feedback. In the third scenario where one might desire an audible delay/echo but have it trail off faster, it is not possible using the current technique (hence "N/A"). In order to achieve a rate of decay of 25%, you are limiting your maximum volume of that effect to 25%. In my experience with musical equipment of many sorts (guitar effects pedals, rackmount effects, DSP plugins, manual tape machine + mixer effects, etc), the expected outcome is typically the proposed solution as it allows greater control of the effect. Yes, it would technically be possible to achieve a single-shot delay with the proposed technique ( On the topic of other delay-based effects (chorus, phaser, etc), it is likely possible to simulate those effects with the current delay/echo effect. I plan on exploring that with the recent changes you've made to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! Just a few API comments.
shared-bindings/audiodelays/Echo.c
Outdated
//| | ||
//| :param int max_delay_ms: The maximum delay the echo can be | ||
//| :param BlockInput delay_ms: The current echo delay | ||
//| :param BlockInput decay: The rate the echo fades. 0.0 = instant; 1.0 = never. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would 1.0 require a buffer of infinite size? How should you handle a case where the buffer is too small? Maybe mention how max_delay_ms
impacts the internal echo buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A decay
of 1.0 will cause an infinite repeat of the audio signal. It shouldn't have any effect on buffer size. I think it's better to allow for this value rather than prevent it. There are some situation where infinite repeat is desired (imagine a momentary "infinite" button). Some analog delay effects actually allow for a feedback/decay value greater than 1.0 to cause self-oscillation. That's something we want to avoid here, but just a bit of context.
locale/circuitpython.pot
Outdated
#: shared-module/audiomixer/MixerVoice.c | ||
msgid "The sample's bits_per_sample does not match the mixer's" | ||
#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c | ||
msgid "The sample's bits_per_sample does not match" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can join all of these messages together by using the QSTR for each attribute.
msgid "The sample's bits_per_sample does not match" | |
msgid "The sample's %q does not match" |
shared-bindings/audiodelays/Echo.c
Outdated
(mp_obj_t)&audiodelays_echo_get_playing_obj); | ||
|
||
//| def play( | ||
//| self, sample: circuitpython_typing.AudioSample, *, voice: int = 0, loop: bool = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//| self, sample: circuitpython_typing.AudioSample, *, voice: int = 0, loop: bool = False | |
//| self, sample: circuitpython_typing.AudioSample, *, loop: bool = False |
shared-bindings/audiodelays/Echo.c
Outdated
//| :param BlockInput delay_ms: The current echo delay | ||
//| :param BlockInput decay: The rate the echo fades. 0.0 = instant; 1.0 = never. | ||
//| :param BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0). | ||
//| :param int buffer_size: The total size in bytes of the buffers to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be clearer here that it only dictates the playback buffer, not the temporary storage of the echo.
@todbot A question came up about the Versus if mix is 0 you pass the sample straight through but still inject into into the echo buffer, but never hear that play, unless mix is increased? Any thoughts? I also realize this functionality could change effect to effect as well. |
@tannewt which board(s) should I enable this for? Right now I only have it for the Pico2. I could for an 2350 board. I haven't tried it against any others yet. |
At least all RP2350. Then folks can enable it for other chips after they test it. |
Here's my demonstration of the issue. I've provided 2 examples, but the most distinct is likely the second where the effect is delayed 4 seconds before the mix value is turned back on. import board, audiobusio, audiomixer, synthio, audiodelays, time
CHANNELS = 1
SAMPLE_RATE = 44100
i2s_bclk, i2s_lclk, i2s_data = board.GP19, board.GP20, board.GP21
audio = audiobusio.I2SOut(bit_clock=i2s_bclk, word_select=i2s_lclk, data=i2s_data)
mixer = audiomixer.Mixer(voice_count=1, channel_count=CHANNELS, sample_rate=SAMPLE_RATE, buffer_size=2048)
audio.play(mixer)
synth = synthio.Synthesizer(channel_count=CHANNELS, sample_rate=SAMPLE_RATE)
synth.envelope = synthio.Envelope(attack_time=0.02, attack_level=0.5, sustain_level=0.5, release_time=0.02)
echo1 = audiodelays.Echo(max_delay_ms=250, delay_ms=250, decay=0.5, sample_rate=SAMPLE_RATE, channel_count=CHANNELS)
echo1.play(synth)
mixer.voice[0].play(echo1)
# Demonstration of signal lost when mix=0.0
print("mix=0")
echo1.mix = 0.0
synth.press(60)
time.sleep(0.25)
synth.release(60)
time.sleep(0.1)
print("mix=1")
echo1.mix = 1.0
time.sleep(2)
# Demonstration of signal reserved when mix=0.0
print("mix=1")
echo1.mix = 1.0
synth.press(60)
time.sleep(0.25)
synth.release(60)
print("mix=0")
echo1.mix = 0.0
time.sleep(4)
print("mix=1")
echo1.mix = 1.0 The fix is very simple where it's just a matter of removing the branching logic for |
Foundation to add audio effects to CircuitPython as discussed in issue #8974.
This PR aims to create initial modules, some base effects and utilities to help create future effects. The ones included will serve as a template for future creations.
To do:
Looking for feedback on the API and anything else that comes up.
When I have time I plan to document how to create your own effects using the ones provided.
Sample code to run: