Skip to content

Commit

Permalink
Delay: Add better constraints on delay time and ping-pong feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
qiemem committed Dec 30, 2024
1 parent 8e11fca commit 2098325
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 56 deletions.
11 changes: 9 additions & 2 deletions software/src/Audio/AudioDelayExt.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ template <
size_t CrossfadeSamples = 2048>
class AudioDelayExt : public AudioStream {
public:
static constexpr float MAX_DELAY_SECS
= BufferLength / AUDIO_SAMPLE_RATE_EXACT;
static constexpr float MIN_DELAY_SECS = ChunkSize / AUDIO_SAMPLE_RATE_EXACT;

AudioDelayExt() : AudioStream(1, input_queue_array) {
delay_secs.fill(OnePole(Interpolated(0.0f, AUDIO_BLOCK_SAMPLES), 0.0002f));
fb.fill(Interpolated(0.0f, AUDIO_BLOCK_SAMPLES / ChunkSize));
Expand All @@ -27,9 +31,10 @@ class AudioDelayExt : public AudioStream {
// TODO xfade lut should be static
xfade_in_scalars = new q15_t[CrossfadeSamples];
xfade_out_scalars = new q15_t[CrossfadeSamples];
for (int i = 0; i < 2048; i++) {
float n = static_cast<float>(CrossfadeSamples - 1);
for (size_t i = 0; i < CrossfadeSamples; i++) {
float out, in;
EqualPowerFade(out, in, i / 2047.0f);
EqualPowerFade(out, in, i / n);
xfade_in_scalars[i] = float_to_q15(in);
xfade_out_scalars[i] = float_to_q15(out);
}
Expand All @@ -42,11 +47,13 @@ class AudioDelayExt : public AudioStream {
}

void delay(size_t tap, float secs) {
CONSTRAIN(secs, MIN_DELAY_SECS, MAX_DELAY_SECS);
delay_secs[tap] = secs;
if (delay_secs[tap].Read() == 0.0f) delay_secs[tap].Reset();
}

void cf_delay(size_t tap, float secs) {
CONSTRAIN(secs, MIN_DELAY_SECS, MAX_DELAY_SECS);
auto& t = target_delay[tap];
if (t.phase >= CrossfadeSamples && t.target != secs) {
// 0.0005f comes from variation
Expand Down
52 changes: 36 additions & 16 deletions software/src/audio_applets/DelayApplet.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "HSicons.h"
#include "HemisphereAudioApplet.h"
#include "dsputils.h"
#include "dsputils_arm.h"
#include "Audio/AudioDelayExt.h"
#include "Audio/AudioMixer.h"
#include "Audio/AudioPassthrough.h"
Expand Down Expand Up @@ -57,7 +58,9 @@ class DelayApplet : public HemisphereAudioApplet {
break;
default:
case SECS:
d = DelaySecsFromMs(delay_time + delay_cv.Process(delay_time_cv.In()));
d = DelaySecsFromMs(
delay_time + 0.1f * delay_cv.Process(delay_time_cv.In())
);
break;
}
for (int tap = 0; tap < taps; tap++) {
Expand Down Expand Up @@ -96,9 +99,14 @@ class DelayApplet : public HemisphereAudioApplet {

if (Channels == STEREO) {
for (auto& ch : channels) {
// The tap mixer has already used equal power mixing on the taps so no
// need to do it here.
ch.input_mixer.gain(PP_CH, constrain(-total_feedback, 0.0f, 2.0f));
// The tap mixer has already used equal power mixing on the taps. With
// feedback, we want equal amplitude, so just multiply by one more
// factor of equal power and voila (since equal amplitude scalars are
// the square roots of equal power)
ch.input_mixer.gain(
PP_CH,
constrain(-total_feedback * EQUAL_POWER_EQUAL_MIX[taps], 0.0f, 2.0f)
);
}
}
float dry_gain, wet_gain;
Expand Down Expand Up @@ -227,14 +235,18 @@ class DelayApplet : public HemisphereAudioApplet {
delay_time += knob_accel;
delay_time *= 16;

// -8 * 12 * 128 -> ~1 Hz
// 6 * 12 * 128 -> ~17kHz
CONSTRAIN(delay_time, -8 * 12 * 128, 6 * 12 * 128);
CONSTRAIN(
delay_time,
static_cast<int>(PitchFromDelaySecs(1.0f)),
static_cast<int>(PitchFromDelaySecs(MIN_DELAY_SECS)) - 1
);
break;
case SECS:
delay_time += knob_accel;
CONSTRAIN(
delay_time, 1, static_cast<int>(MAX_DELAY_SECS * 1000) - 1
delay_time,
static_cast<int>(MIN_DELAY_SECS * 1000),
static_cast<int>(MAX_DELAY_SECS * 1000) - 1
);
break;
}
Expand Down Expand Up @@ -305,12 +317,18 @@ class DelayApplet : public HemisphereAudioApplet {
return &output_stream;
}

int PitchFromDelaySecs(float secs) {
return -RatioToPitch(C3 * 2 * secs);
}

float DelaySecsFromPitch(int pitch) {
return constrain(PitchToRatio(-pitch) / (C3 * 2), 0.0f, MAX_DELAY_SECS);
return constrain(
PitchToRatio(-pitch) / (C3 * 2), MIN_DELAY_SECS, MAX_DELAY_SECS
);
}

float DelaySecsFromMs(int ms) {
return constrain(0.001f * ms, 0.0f, MAX_DELAY_SECS);
float DelaySecsFromMs(float ms) {
return constrain(0.001f * ms, MIN_DELAY_SECS, MAX_DELAY_SECS);
}

float DelaySecsFromRatio(int ratio) {
Expand All @@ -336,6 +354,12 @@ class DelayApplet : public HemisphereAudioApplet {
}

private:
// Uses 1MB of psram and gives just under 12 secs of delay time.
static const size_t DELAY_LENGTH = 1024 * 512;
typedef AudioDelayExt<DELAY_LENGTH, 9> DelayStream;
static constexpr float MAX_DELAY_SECS = DelayStream::MAX_DELAY_SECS;
static constexpr float MIN_DELAY_SECS = DelayStream::MIN_DELAY_SECS;

enum Cursor {
CLOCK_SOURCE,
TIME,
Expand Down Expand Up @@ -411,15 +435,11 @@ class DelayApplet : public HemisphereAudioApplet {

static const uint8_t PP_CH = 1;

// Uses 1MB of psram and gives just under 12 secs of delay time.
static const size_t DELAY_LENGTH = 1024 * 512;
static constexpr float MAX_DELAY_SECS = DELAY_LENGTH / AUDIO_SAMPLE_RATE;

AudioPassthrough<Channels> input_stream;
struct {
AudioMixer<2> input_mixer;
// 9th tap is freeze head
AudioDelayExt<DELAY_LENGTH, 9> delay;
DelayStream delay;
AudioMixer<8> taps_mixer;
AudioMixer<2> wet_dry_mixer;

Expand Down
38 changes: 0 additions & 38 deletions software/src/dsputils.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once
#include "extern/fastapprox/fastexp.h"
#include "extern/fastapprox/fastlog.h"
#include <arm_math.h>

#define ONE_POLE(out, in, coefficient) out += (coefficient) * ((in) - out);

Expand Down Expand Up @@ -48,12 +47,6 @@ inline int16_t Clip16(float x) {
}
}

inline q15_t float_to_q15(float x) {
x *= 32768.0f;
x += x > 0.0f ? 0.5f : -0.5f;
return (q15_t)(__SSAT((q31_t)(x), 16));
}

constexpr inline float InterpLinear(float x0, float x1, float t) {
return t * (x1 - x0) + x0;
}
Expand All @@ -70,37 +63,6 @@ constexpr inline float InterpHermite(
return ((((a * t) - b_neg) * t + c) * t + x0);
}

void InterHermiteQ15Vec(
q15_t* xm1,
q15_t* x0,
q15_t* x1,
q15_t* x2,
q15_t* t,
q15_t* out,
size_t size
) {
q15_t c[size], v[size], w[size], a[size], b_neg[size];

arm_sub_q15(x1, xm1, c, size);
arm_scale_q15(c, 16384, 0, c, size);
arm_sub_q15(x0, x1, v, size);
arm_add_q15(c, v, w, size);

arm_sub_q15(x2, x0, a, size);
arm_scale_q15(a, 16384, 0, a, size);
arm_add_q15(a, v, a, size);
arm_add_q15(a, w, a, size);

arm_add_q15(w, a, b_neg, size);

arm_mult_q15(a, t, out, size);
arm_sub_q15(out, b_neg, out, size);
arm_mult_q15(out, t, out, size);
arm_add_q15(out, c, out, size);
arm_mult_q15(out, t, out, size);
arm_add_q15(out, x0, out, size);
}

inline float dbToScalar(float db) {
// pow10(x) = exp(log(10) * x)
return fastexp(2.302585092994046f * 0.05f * db);
Expand Down
42 changes: 42 additions & 0 deletions software/src/dsputils_arm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <arm_math.h>

inline q15_t float_to_q15(float x) {
x *= 32768.0f;
x += x > 0.0f ? 0.5f : -0.5f;
return (q15_t)(__SSAT((q31_t)(x), 16));
}


void InterHermiteQ15Vec(
q15_t* xm1,
q15_t* x0,
q15_t* x1,
q15_t* x2,
q15_t* t,
q15_t* out,
size_t size
) {
q15_t c[size], v[size], w[size], a[size], b_neg[size];

arm_sub_q15(x1, xm1, c, size);
arm_scale_q15(c, 16384, 0, c, size);
arm_sub_q15(x0, x1, v, size);
arm_add_q15(c, v, w, size);

arm_sub_q15(x2, x0, a, size);
arm_scale_q15(a, 16384, 0, a, size);
arm_add_q15(a, v, a, size);
arm_add_q15(a, w, a, size);

arm_add_q15(w, a, b_neg, size);

arm_mult_q15(a, t, out, size);
arm_sub_q15(out, b_neg, out, size);
arm_mult_q15(out, t, out, size);
arm_add_q15(out, c, out, size);
arm_mult_q15(out, t, out, size);
arm_add_q15(out, x0, out, size);
}

0 comments on commit 2098325

Please sign in to comment.