Skip to content

Commit

Permalink
echo: feedback filter is now 1-pole IIR.
Browse files Browse the repository at this point in the history
  • Loading branch information
dpwe committed Sep 6, 2024
1 parent 4364b80 commit 6917e33
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 18 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 FIR coefficient. |
| `M` | `echo` | float[,int,int,float,float] | Echo parameters -- level, delay_ms, max_delay_ms, feedback, 1st order IIR zero location. |
| `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
17 changes: 7 additions & 10 deletions src/amy.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ typedef struct echo_config {
uint32_t delay_samples; // Current delay, quantized to samples.
uint32_t max_delay_samples; // Maximum delay, i.e. size of allocated delay line.
SAMPLE feedback; // Gain applied when feeding back output to input.
SAMPLE filter_coef0; // Echo is filtered by a two-point normalize FIR, these are the coefficients.
SAMPLE filter_coef1;
SAMPLE filter_coef; // Echo is filtered by a two-point normalize IIR. This is the real pole location.
} echo_config_t;

uint32_t enclosing_power_of_2(uint32_t n) {
Expand All @@ -166,7 +165,7 @@ echo_config_t echo = {F2S(ECHO_DEFAULT_LEVEL),
(uint32_t)(ECHO_DEFAULT_DELAY_MS * 1000.f / AMY_SAMPLE_RATE),
65536, // enclosing_power_of_2((uint32_t)(ECHO_DEFAULT_MAX_DELAY_MS * 1000.f / AMY_SAMPLE_RATE)),
F2S(ECHO_DEFAULT_FEEDBACK),
F2S(1.f), F2S(0)}; // Don't attempt to inline the mapping from FILTER_COEF to the two params.
F2S(ECHO_DEFAULT_FILTER_COEF)};

SAMPLE *echo_delays[AMY_NCHANS];

Expand All @@ -191,11 +190,9 @@ void config_echo(float level, float delay_ms, float max_delay_ms, float feedback
echo.level = F2S(level);
echo.delay_samples = delay_samples;
echo.feedback = F2S(feedback);
// FIR filter is [1, filter_coef] but scale it to have peak gain of 1.0.
float filter_norm = 1.0f / (1.0f + fabs(filter_coef));
echo.filter_coef0 = F2S(filter_norm);
echo.filter_coef1 = F2S(filter_norm * filter_coef);
//fprintf(stderr, "config_echo: delay_samples=%d level=%.3f feedback=%.3f filter_coef=%.3f fc0=%.3f fc1=%.3f\n", delay_samples, level, feedback, filter_coef, S2F(echo.filter_coef0), S2F(echo.filter_coef1));
// FIR filter is [1], [1, filter_coef] but scaled to have peak gain of 1.0.
echo.filter_coef = F2S(filter_coef);
//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));
}

void dealloc_echo_delay_lines(void) {
Expand Down Expand Up @@ -1477,7 +1474,7 @@ int16_t * amy_fill_buffer() {
// Apply echo.
if (echo.level > 0 && echo_delay_lines[0] != NULL ) {
for (int16_t c=0; c < AMY_NCHANS; ++c) {
apply_fixed_delay(fbl[0] + c * AMY_BLOCK_SIZE, echo_delay_lines[c], echo.delay_samples, echo.level, echo.feedback, echo.filter_coef0, echo.filter_coef1);
apply_fixed_delay(fbl[0] + c * AMY_BLOCK_SIZE, echo_delay_lines[c], echo.delay_samples, echo.level, echo.feedback, echo.filter_coef);
}
}
}
Expand Down Expand Up @@ -1792,7 +1789,7 @@ struct event amy_parse_message(char * message) {
if (AMY_IS_UNSET(echo_params[1])) echo_params[1] = (float)echo.delay_samples * 1000.f / AMY_SAMPLE_RATE;
if (AMY_IS_UNSET(echo_params[2])) echo_params[2] = (float)echo.max_delay_samples * 1000.f / AMY_SAMPLE_RATE;
if (AMY_IS_UNSET(echo_params[3])) echo_params[3] = S2F(echo.feedback);
if (AMY_IS_UNSET(echo_params[4])) echo_params[4] = S2F(echo.filter_coef1) / S2F(echo.filter_coef0);
if (AMY_IS_UNSET(echo_params[4])) echo_params[4] = S2F(echo.filter_coef);
config_echo(echo_params[0], echo_params[1], echo_params[2], echo_params[3], echo_params[4]);
}
case 'N': e.latency_ms = atoi(message + start); break;
Expand Down
16 changes: 10 additions & 6 deletions src/delay.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,19 @@ static inline SAMPLE LPF(SAMPLE samp, SAMPLE state, SAMPLE lpcoef, SAMPLE lpgain
return SMULR6(SHIFTR(gain, 1), state + SMULR6(lpgain, samp - state));
}

void delay_line_in_out_fixed_delay(SAMPLE *in, SAMPLE *out, int n_samples, int delay_samples, delay_line_t *delay_line, SAMPLE mix_level, SAMPLE feedback_level, SAMPLE filter_coef0, SAMPLE filter_coef1) {
void delay_line_in_out_fixed_delay(SAMPLE *in, SAMPLE *out, int n_samples, int delay_samples, delay_line_t *delay_line, SAMPLE mix_level, SAMPLE feedback_level, SAMPLE filter_coef) {
// 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 sample_1 = DEL_OUT(delay_line, 1);
SAMPLE sample_0 = DEL_OUT(delay_line, 0);
SAMPLE sample = SMULR6(filter_coef0, sample_0) + SMULR6(filter_coef1, sample_1);
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.
}
Expand All @@ -123,8 +127,8 @@ void apply_variable_delay(SAMPLE *block, delay_line_t *delay_line, SAMPLE *delay
delay_line_in_out(block, block, AMY_BLOCK_SIZE, delay_mod, delay_scale, delay_line, mix_level, feedback_level);
}

void apply_fixed_delay(SAMPLE *block, delay_line_t *delay_line, uint32_t delay_samples, SAMPLE mix_level, SAMPLE feedback, SAMPLE filter_coef0, SAMPLE filter_coef1) {
delay_line_in_out_fixed_delay(block, block, AMY_BLOCK_SIZE, delay_samples, delay_line, mix_level, feedback, filter_coef0, filter_coef1);
void apply_fixed_delay(SAMPLE *block, delay_line_t *delay_line, uint32_t delay_samples, SAMPLE mix_level, SAMPLE feedback, SAMPLE filter_coef) {
delay_line_in_out_fixed_delay(block, block, AMY_BLOCK_SIZE, delay_samples, delay_line, mix_level, feedback, filter_coef);
}

SAMPLE f1state = 0, f2state = 0, f3state = 0, f4state = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/delay.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ delay_line_t *new_delay_line(int len, int fixed_delay, int ram_type /* e.g. MALL
void free_delay_line(delay_line_t *d);

void apply_variable_delay(SAMPLE *block, delay_line_t *delay_line, SAMPLE *delay_samples, SAMPLE mod_scale, SAMPLE mix_level, SAMPLE feedback_level);
void apply_fixed_delay(SAMPLE *block, delay_line_t *delay_line, uint32_t delay_samples, SAMPLE mix_level, SAMPLE feedback, SAMPLE filter_coef0, SAMPLE filter_coef1);
void apply_fixed_delay(SAMPLE *block, delay_line_t *delay_line, uint32_t delay_samples, SAMPLE mix_level, SAMPLE feedback, SAMPLE filter_coef);

void config_stereo_reverb(float a_liveness, float crossover_hz, float damping);
void init_stereo_reverb(void);
Expand Down

0 comments on commit 6917e33

Please sign in to comment.