Skip to content
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

Apply echo decay after first repeat & improve echo buffer handling #1

Open
wants to merge 4 commits into
base: audio_effects
Choose a base branch
from

Conversation

dcooperdalrymple
Copy link

Allow independent control of the mix level of audiodelays.Echo and the decay amount by applying echo decay only when inserting echo value back into echo_buffer.

@dcooperdalrymple
Copy link
Author

After experimenting with BlockInput on delay_ms, I quickly discovered that it causes undesirable glitching in the audio. The only solution I could think of to fix this problem is to change the echo buffer paradigm. What I've done is implemented a buffer position and rate with 8 "sub-bits". When delay_ms is updated, only the rate of iteration over the echo_buffer (with 8 additional bits of precision) is changed. During the write process, all words between the current position and the next position are written to using a basic loop.

NOTE: In the process, I did remove the separate buffer read & write positions because I didn't think they were necessary for this application.

Here is an example script to test this new feature out. You will here pitch shifting as the rate of playback over the echo buffer changes. This is normal and only affects the output when delay_ms is altered.

import board, audiobusio, audiomixer, synthio, audiodelays, time

CHANNELS = 1
SAMPLE_RATE = 44100
MAX_DELAY = 100

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.75, sustain_level=0.5, release_time=0.1)

lfo = synthio.LFO(scale=MAX_DELAY/4, offset=MAX_DELAY*3/4, rate=0.75)
synth.blocks.append(lfo)

echo1 = audiodelays.Echo(max_delay_ms=MAX_DELAY, delay_ms=lfo, decay=0.5, mix=0.5, sample_rate=SAMPLE_RATE, channel_count=CHANNELS)
echo1.play(synth)
mixer.voice[0].play(echo1)

while True:
    for notenum in (60, 64, 67, 71): # C4, E4, G4, B4
        synth.press(notenum)
        time.sleep(0.1)
        synth.release(notenum)
        time.sleep(0.15)

In addition, you can actually achieve delays far beyond the max_delay_ms parameter at the expensive of audio quality. This could be useful for users who have limited ram or are intentionally looking for a "lo-fi" delay sound. In the example below, we're getting 50x the allocated delay (10ms to 500ms). I do hear some pitch shifting during this process, so there is likely room for improvement.

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.75, sustain_level=0.5, release_time=0.1)

echo1 = audiodelays.Echo(max_delay_ms=10, delay_ms=500, decay=0.75, mix=0.75, sample_rate=SAMPLE_RATE, channel_count=CHANNELS)
echo1.play(synth)
mixer.voice[0].play(echo1)

synth.press(60)
time.sleep(0.1)
synth.release(60)

# Example of manual modification of `delay_ms`
time.sleep(2)
echo1.delay_ms = 400

I know this is may be against your initial plan, @gamblor21, but I think these minor changes allow this effect to be much more powerful.

@dcooperdalrymple
Copy link
Author

As promised, here's an example of chorus using this effect. I have to admit, it's very tricky to get just right.

import board, audiobusio, audiomixer, synthio, audiodelays, time

CHANNELS = 1
SAMPLE_RATE = 44100
MAX_DELAY = 80
DEPTH = 0.25
RATE = 0.5
MIX = 0.6

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.1)

lfo = synthio.LFO(scale=MAX_DELAY*DEPTH/2, offset=MAX_DELAY*(1-DEPTH/2), rate=RATE)
synth.blocks.append(lfo)

echo1 = audiodelays.Echo(max_delay_ms=MAX_DELAY, delay_ms=lfo, decay=0.0, mix=MIX, sample_rate=SAMPLE_RATE, channel_count=CHANNELS)
echo1.play(synth)
mixer.voice[0].play(echo1)

for notenum in (60, 64, 67, 71): # C4, E4, G4, B4
    synth.press(notenum)

while True:
    print("on")
    echo1.mix = MIX
    time.sleep(4)
    print("off")
    echo1.mix = 0
    time.sleep(4)

@dcooperdalrymple dcooperdalrymple changed the title Apply echo decay after first repeat. Apply echo decay after first repeat & improve echo buffer handling Oct 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant