Skip to content

Commit

Permalink
delay.c: filter_coef is LPF for 0 to 1, HPF for 0 to -1.
Browse files Browse the repository at this point in the history
  • Loading branch information
dpwe committed Sep 7, 2024
1 parent 6917e33 commit 0e0d3cd
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Here's the full list:
| `l` | `vel` | float 0-1+ | Velocity: > 0 to trigger note on, 0 to trigger note off |
| `L` | `mod_source` | 0 to OSCS-1 | Which oscillator is used as an modulation/LFO source for this oscillator. Source oscillator will be silent. |
| `m` | `portamento` | uint | Time constant (in ms) for pitch changes when note is changed without intervening note-off. default 0 (immediate), 100 is good. |
| `M` | `echo` | float[,int,int,float,float] | Echo parameters -- level, delay_ms, max_delay_ms, feedback, 1st order IIR zero location. |
| `M` | `echo` | float[,int,int,float,float] | Echo parameters -- level, delay_ms, max_delay_ms, feedback, filter_coef (-1 is HPF, 0 is flat, +1 is LPF). |
| `N` | `note` | uint 0-127 | Midi note, sets frequency |
| `N` | `latency_ms` | uint | Sets latency in ms. default 0 (see LATENCY) |
| `o` | `algorithm` | uint 1-32 | DX7 FM algorithm to use for ALGO type |
Expand Down
11 changes: 7 additions & 4 deletions src/amy.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,20 @@ void config_echo(float level, float delay_ms, float max_delay_ms, float feedback
echo.max_delay_samples = max_delay_samples;
//fprintf(stderr, "config_echo: max_delay_samples=%d\n", max_delay_samples);
}
// Apply delay.
if (delay_samples > echo.max_delay_samples) delay_samples = echo.max_delay_samples;
// Apply delay. We have to stay 1 sample less than delay line length for FIR EQ delay.
if (delay_samples > echo.max_delay_samples - 1) delay_samples = echo.max_delay_samples - 1;
for (int c = 0; c < AMY_NCHANS; ++c) {
echo_delay_lines[c]->fixed_delay = delay_samples;
}
}
echo.level = F2S(level);
echo.delay_samples = delay_samples;
echo.feedback = F2S(feedback);
// FIR filter is [1], [1, filter_coef] but scaled to have peak gain of 1.0.
// Filter is IIR [1, filter_coef] normalized for filter_coef > 0 (LPF), or FIR [1, filter_coef] normalized for filter_coef < 0 (HPF).
if (filter_coef > 0.99) filter_coef = 0.99; // Avoid unstable filters.
echo.filter_coef = F2S(filter_coef);
// FIR filter potentially has gain > 1 for high frequencies, so discount the loop feedback to stop things exploding.
if (filter_coef < 0) feedback /= 1.f - filter_coef;
echo.feedback = F2S(feedback);
//fprintf(stderr, "config_echo: delay_samples=%d level=%.3f feedback=%.3f filter_coef=%.3f fc0=%.3f\n", delay_samples, level, feedback, filter_coef, S2F(echo.filter_coef));
}

Expand Down
3 changes: 2 additions & 1 deletion src/amy_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ extern const uint16_t pcm_samples;
// echo setup, Levels etc are SAMPLE (fxpoint), delays are in samples.
#define ECHO_DEFAULT_LEVEL 0
#define ECHO_DEFAULT_DELAY_MS 500.f
#define ECHO_DEFAULT_MAX_DELAY_MS 1000.f
// Delay line allocates in 2^n samples at 44k; 743ms is just under 32768 samples.
#define ECHO_DEFAULT_MAX_DELAY_MS 743.f
#define ECHO_DEFAULT_FEEDBACK 0
#define ECHO_DEFAULT_FILTER_COEF 0

Expand Down
45 changes: 32 additions & 13 deletions src/delay.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ void delay_line_in_out(SAMPLE *in, SAMPLE *out, int n_samples, SAMPLE* mod_in, S
delay_line->next_in = index_in;
}

static inline SAMPLE DEL_OUT(delay_line_t *delay_line, int offset) {
static inline SAMPLE DEL_OUT(delay_line_t *delay_line, int extra_delay) {
int out_index =
(delay_line->next_in - (delay_line->fixed_delay - offset)) & (delay_line->len - 1);
(delay_line->next_in - (delay_line->fixed_delay + extra_delay)) & (delay_line->len - 1);
return delay_line->samples[out_index];
}

Expand All @@ -108,17 +108,36 @@ void delay_line_in_out_fixed_delay(SAMPLE *in, SAMPLE *out, int n_samples, int d
// Read and write the next n_samples from/to the delay line.
// Simplified version of delay_line_in_out() that uses a fixed integer delay
// for the whole block.
while(n_samples-- > 0) {
SAMPLE next_in = *in++;
SAMPLE last_out = delay_line->samples[(delay_line->next_in - 1) & (delay_line->len - 1)];
//SAMPLE sample_1 = DEL_OUT(delay_line, 1);
SAMPLE sample = DEL_OUT(delay_line, 0);
if (filter_coef > 0)
sample += SMULR6(filter_coef, last_out - sample);
else if (filter_coef < 0)
sample += SMULR6(filter_coef, last_out + sample);
DEL_IN(delay_line, next_in + SMULR6(feedback_level, sample));
*out++ += MUL8_SS(mix_level, sample); // mix delayed + original.
if (filter_coef == 0) {
while(n_samples-- > 0) {
SAMPLE delay_out = DEL_OUT(delay_line, 0);
SAMPLE next_in = *in++ + SMULR6(feedback_level, delay_out);
DEL_IN(delay_line, next_in);
*out++ += MUL8_SS(mix_level, delay_out);
}
} else if (filter_coef > 0) {
// Positive filter coef is a pole on the positive real line, to get low-pass effect.
// We apply the filter on the way *in* to the delay line.
while(n_samples-- > 0) {
SAMPLE delay_out = DEL_OUT(delay_line, 0);
SAMPLE next_in = *in++ + SMULR6(feedback_level, delay_out);
// Peek at the last value we wrote to the delay line to get the most recent filter output.
SAMPLE last_filter_result = delay_line->samples[(delay_line->next_in - 1) & (delay_line->len - 1)];
SAMPLE filter_result = next_in + SMULR6(filter_coef, last_filter_result - next_in);
DEL_IN(delay_line, filter_result);
*out++ += MUL8_SS(mix_level, delay_out);
}
} else {
// Negative filter coef is a zero on the positive real axis to get high-pass.
// We apply the FIR zero on the way *out* of the delay line.
while(n_samples-- > 0) {
SAMPLE delay_out = DEL_OUT(delay_line, 0);
SAMPLE prev_delay_out = DEL_OUT(delay_line, 1);
SAMPLE output = delay_out + SMULR6(filter_coef, prev_delay_out);
SAMPLE next_in = *in++ + SMULR6(feedback_level, output);
DEL_IN(delay_line, next_in);
*out++ += MUL8_SS(mix_level, output);
}
}
}

Expand Down
20 changes: 19 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,25 @@ def run(self):
amy.send(time=0, osc=0, bp0="0,1,200,0,0,0")

amy.send(time=100, osc=0, note=48, vel=1)



class TestEchoLPF(AmyTest):

def run(self):
amy.echo(level=0.5, delay_ms=200, feedback=0.7, filter_coef=0.9)
amy.send(time=0, osc=0, wave=amy.SAW_DOWN, bp0="0,1,200,0,0,0")

amy.send(time=100, osc=0, note=48, vel=1)


class TestEchoHPF(AmyTest):

def run(self):
amy.echo(level=0.5, delay_ms=200, feedback=0.7, filter_coef=-0.9)
amy.send(time=0, osc=0, wave=amy.SAW_DOWN, bp0="0,1,200,0,0,0")

amy.send(time=100, osc=0, note=48, vel=1)


def main(argv):
if len(argv) > 1:
Expand Down
Binary file modified tests/ref/TestEcho.wav
Binary file not shown.
Binary file added tests/ref/TestEchoHPF.wav
Binary file not shown.
Binary file added tests/ref/TestEchoLPF.wav
Binary file not shown.

0 comments on commit 0e0d3cd

Please sign in to comment.