Skip to content

Audible stepping when using synthio.LFO on audiodelays.Echo.delay_ms #10030

Open
@relic-se

Description

@relic-se

CircuitPython version and board name

Adafruit CircuitPython 9.2.4; Pimoroni Pico Plus2 with rp2350b

Code/REPL

import audiobusio
import audiodelays
import board, time
import synthio

SAMPLE_RATE = 48000
AMPLITUDE = 0.2
MIX = 1.0 # Would need to be 0.5 if #9994 is merged
TIME = 40
DEPTH = 8
RATE = 1.0
OCTAVE = -1

audio = audiobusio.I2SOut(
    bit_clock=board.GP0,
    word_select=board.GP1,
    data=board.GP2,
)

synth = synthio.Synthesizer(
    sample_rate=SAMPLE_RATE,
    channel_count=2,
)

effect = audiodelays.Echo(
    max_delay_ms=TIME,
    delay_ms=synthio.LFO(offset=TIME, scale=DEPTH, rate=RATE),
    decay=0.0,
    sample_rate=SAMPLE_RATE,
    channel_count=2,
    freq_shift=True,
)

effect.play(synth)
audio.play(effect)

# Chord
for i in range(3):
    synth.press(synthio.Note(
        frequency=synthio.midi_to_hz(65 + i * 4 + OCTAVE * 12),
        panning=synthio.LFO(rate=0.15 + i * 0.05),
        amplitude=AMPLITUDE,
    ))

while True:
    if effect.mix:
        effect.mix = 0.0
        print("Off")
    else:
        effect.mix = MIX
        print("On")
    time.sleep(2.0)

Behavior

There is audible stepping in the chorus effect.

Here's a recording of the above example: https://drive.google.com/file/d/13qotNTCFfkoQ_GuhENawpOw9rmh5oPoS/view?usp=drive_link

Description

No response

Additional information

The optimization at Echo.c#L313 prevents calling recalculate_delay at it's maximum rate of SYNTHIO_MAX_DUR and instead limits it to increments of sample_ms. For effects using larger delay lengths, this stepping isn't noticeable, but for effects which require a delay with a shorter delay length (ie: chorus, flanger), more granularity is required.

if (MICROPY_FLOAT_C_FUN(fabs)(self->current_delay_ms - f_delay_ms) >= self->sample_ms) {
recalculate_delay(self, f_delay_ms);
}

I'm thinking that a suitable solution is to check the difference by sample_ms / 256 when freq_shift=True (for the 8-bits of sub-resolution). That, or test the float equality using memcmp such as in the following function.

static int float_equal_or_update(
mp_float_t *cached,
mp_float_t new) {
// uses memcmp to avoid error about equality float comparison
if (memcmp(cached, &new, sizeof(mp_float_t))) {
*cached = new;
return false;
}
return true;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions