diff --git a/software/src/Audio/AudioDelayExt.h b/software/src/Audio/AudioDelayExt.h index 924988016..723f749b0 100644 --- a/software/src/Audio/AudioDelayExt.h +++ b/software/src/Audio/AudioDelayExt.h @@ -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)); @@ -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(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); } @@ -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 diff --git a/software/src/audio_applets/DelayApplet.h b/software/src/audio_applets/DelayApplet.h index 42ef88ec7..49049a3e2 100644 --- a/software/src/audio_applets/DelayApplet.h +++ b/software/src/audio_applets/DelayApplet.h @@ -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" @@ -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++) { @@ -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; @@ -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(PitchFromDelaySecs(1.0f)), + static_cast(PitchFromDelaySecs(MIN_DELAY_SECS)) - 1 + ); break; case SECS: delay_time += knob_accel; CONSTRAIN( - delay_time, 1, static_cast(MAX_DELAY_SECS * 1000) - 1 + delay_time, + static_cast(MIN_DELAY_SECS * 1000), + static_cast(MAX_DELAY_SECS * 1000) - 1 ); break; } @@ -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) { @@ -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 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, @@ -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 input_stream; struct { AudioMixer<2> input_mixer; // 9th tap is freeze head - AudioDelayExt delay; + DelayStream delay; AudioMixer<8> taps_mixer; AudioMixer<2> wet_dry_mixer; diff --git a/software/src/dsputils.h b/software/src/dsputils.h index ba11630da..f2be8a6a1 100644 --- a/software/src/dsputils.h +++ b/software/src/dsputils.h @@ -1,7 +1,6 @@ #pragma once #include "extern/fastapprox/fastexp.h" #include "extern/fastapprox/fastlog.h" -#include #define ONE_POLE(out, in, coefficient) out += (coefficient) * ((in) - out); @@ -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; } @@ -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); diff --git a/software/src/dsputils_arm.h b/software/src/dsputils_arm.h new file mode 100644 index 000000000..17a031f57 --- /dev/null +++ b/software/src/dsputils_arm.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +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); +} +