diff --git a/README.md b/README.md index 793630d..25ccaef 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/src/amy.c b/src/amy.c index 6f6e82b..f2e28d1 100644 --- a/src/amy.c +++ b/src/amy.c @@ -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) { @@ -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]; @@ -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) { @@ -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); } } } @@ -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; diff --git a/src/delay.c b/src/delay.c index 14bc33f..070c765 100644 --- a/src/delay.c +++ b/src/delay.c @@ -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. } @@ -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; diff --git a/src/delay.h b/src/delay.h index 5d6d786..21a7910 100644 --- a/src/delay.h +++ b/src/delay.h @@ -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);