From afc96087f8a6282cf732d142a4db7a3d604d39d8 Mon Sep 17 00:00:00 2001 From: Bertrik Sikken Date: Tue, 1 May 2012 03:58:27 -0400 Subject: [PATCH] New crossfeed algorithm for Rockbox: "Meier" crossfeed Emulates the basic "Meier" crossfeed (2 capacitors, 3 resistors) as discussed in http://www.meier-audio.homepage.t-online.de/passivefilter.htm This crossfeed blends a bit of low-pass filtered L signal into the R signal (and vice versa) while adding about 300 us delay to the crossfed-signal. A difference with the crossfeed already present in rockbox, is that this algorithm keeps the total spectrum flat (the one currently in rockbox accentuates low-frequency signals, making it sound a bit muffled). This implementation is quite lightweight, just 3 multiplies per left-right pair of samples. Has a default C implementation and optimized assembly versions for ARM and Coldfire. The crossfeed effect is quite subtle and is noticeable mostly one albums that have very strong left-right separation (e.g. one instrument only on the left, another only on the right). In the user interface, the new crossfeed option appears as "Meier" and is not configureable. The existing crossfeed is renamed to "Custom" as it allows itself to be customised. There is no entry for the user manual yet. Change-Id: Iaa100616fe0fcd7e16f08cdb9a7f41501973eee1 --- apps/lang/english.lang | 34 ++++++ apps/plugin.c | 2 +- apps/plugin.h | 2 +- apps/plugins/mpegplayer/mpeg_settings.c | 5 +- apps/settings.c | 2 +- apps/settings.h | 2 +- apps/settings_list.c | 6 +- lib/rbcodec/dsp/crossfeed.c | 133 +++++++++++++++++++++--- lib/rbcodec/dsp/crossfeed.h | 9 +- lib/rbcodec/dsp/dsp_arm.S | 44 ++++++++ lib/rbcodec/dsp/dsp_cf.S | 47 ++++++++- 11 files changed, 259 insertions(+), 27 deletions(-) diff --git a/apps/lang/english.lang b/apps/lang/english.lang index fc575a3764..7366a80030 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -13052,3 +13052,37 @@ *: "Western European" + + id: LANG_CROSSFEED_MEIER + desc: in sound settings + user: core + + *: none + swcodec: "Simple (Meier)" + + + *: none + swcodec: "Simple (Meier)" + + + *: none + swcodec: "Simple" + + + + id: LANG_CROSSFEED_CUSTOM + desc: in sound settings + user: core + + *: none + swcodec: "Custom" + + + *: none + swcodec: "Custom" + + + *: none + swcodec: "Custom" + + diff --git a/apps/plugin.c b/apps/plugin.c index afb336ebdc..e0e565e504 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -565,7 +565,7 @@ static const struct plugin_api rockbox_api = { audio_set_output_source, audio_set_input_source, #endif - dsp_crossfeed_enable, + dsp_set_crossfeed_type , dsp_eq_enable, dsp_dither_enable, #ifdef HAVE_PITCHCONTROL diff --git a/apps/plugin.h b/apps/plugin.h index b1d3c16979..bb2778164b 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -682,7 +682,7 @@ struct plugin_api { void (*audio_set_output_source)(int monitor); void (*audio_set_input_source)(int source, unsigned flags); #endif - void (*dsp_crossfeed_enable)(bool enable); + void (*dsp_set_crossfeed_type)(int type); void (*dsp_eq_enable)(bool enable); void (*dsp_dither_enable)(bool enable); #ifdef HAVE_PITCHCONTROL diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c index 7f92fb7c69..bcef4c66bf 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -457,8 +457,9 @@ static void sync_audio_setting(int setting, bool global) break; case MPEG_AUDIO_CROSSFEED: - rb->dsp_crossfeed_enable((global || settings.crossfeed) ? - rb->global_settings->crossfeed : false); + rb->dsp_set_crossfeed_type((global || settings.crossfeed) ? + rb->global_settings->crossfeed : + CROSSFEED_TYPE_NONE); break; case MPEG_AUDIO_EQUALIZER: diff --git a/apps/settings.c b/apps/settings.c index d777eb1565..3bf9c5bf17 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -979,7 +979,7 @@ void settings_apply(bool read_disk) audio_set_crossfade(global_settings.crossfade); #endif replaygain_update(); - dsp_crossfeed_enable(global_settings.crossfeed); + dsp_set_crossfeed_type(global_settings.crossfeed); dsp_set_crossfeed_direct_gain(global_settings.crossfeed_direct_gain); dsp_set_crossfeed_cross_params(global_settings.crossfeed_cross_gain, global_settings.crossfeed_hf_attenuation, diff --git a/apps/settings.h b/apps/settings.h index 55d3344cb2..ef0bae520e 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -325,7 +325,7 @@ struct user_settings struct replaygain_settings replaygain_settings; /* Crossfeed */ - bool crossfeed; /* enable crossfeed */ + int crossfeed; /* crossfeed type */ unsigned int crossfeed_direct_gain; /* dB x 10 */ unsigned int crossfeed_cross_gain; /* dB x 10 */ unsigned int crossfeed_hf_attenuation; /* dB x 10 */ diff --git a/apps/settings_list.c b/apps/settings_list.c index 10d00d5c89..accd51dfe1 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -1402,8 +1402,10 @@ const struct settings_list settings[] = { #endif /* crossfeed */ - OFFON_SETTING(F_SOUNDSETTING, crossfeed, LANG_CROSSFEED, false, - "crossfeed", dsp_crossfeed_enable), + CHOICE_SETTING(F_SOUNDSETTING, crossfeed, LANG_CROSSFEED, 0,"crossfeed", + "off,meier,custom", dsp_set_crossfeed_type, 3, + ID2P(LANG_OFF), ID2P(LANG_CROSSFEED_MEIER), + ID2P(LANG_CROSSFEED_CUSTOM)), INT_SETTING_NOWRAP(F_SOUNDSETTING, crossfeed_direct_gain, LANG_CROSSFEED_DIRECT_GAIN, -15, "crossfeed direct gain", UNIT_DB, -60, 0, 5, diff --git a/lib/rbcodec/dsp/crossfeed.c b/lib/rbcodec/dsp/crossfeed.c index 344addadd7..3fb51a7594 100644 --- a/lib/rbcodec/dsp/crossfeed.c +++ b/lib/rbcodec/dsp/crossfeed.c @@ -8,6 +8,7 @@ * $Id$ * * Copyright (C) 2006 Thom Johansen + * Copyright (C) 2010 Bertrik Sikken * Copyright (C) 2012 Michael Sevakis * * This program is free software; you can redistribute it and/or @@ -31,7 +32,11 @@ #include /* Implemented here or in target assembly code */ -void crossfeed_process(struct dsp_proc_entry *this, struct dsp_buffer **buf_p); +void crossfeed_process(struct dsp_proc_entry *this, + struct dsp_buffer **buf_p); +void crossfeed_meier_process(struct dsp_proc_entry *this, + struct dsp_buffer **buf_p); + /** * Applies crossfeed to the stereo signal. @@ -46,20 +51,44 @@ static struct crossfeed_state { int32_t gain; /* 00h: Direct path gain */ int32_t coefs[3]; /* 04h: Coefficients for the shelving filter */ - int32_t history[4]; /* 10h: Format is x[n - 1], y[n - 1] (L + R) */ - int32_t delay[13*2]; /* 20h: Delay line buffer (L + R interleaved) */ + union + { + struct /* 10h: Data for meier crossfeed */ + { + int32_t vcl; + int32_t vcr; + int32_t vdiff; + int32_t coef1; + int32_t coef2; + }; + struct /* 10h: Data for custom crossfeed */ + { + int32_t history[4]; /* 10h: Format is x[n - 1], y[n - 1] (L + R) */ + int32_t delay[13*2];/* 20h: Delay line buffer (L + R interleaved) */ + }; + }; int32_t *index; /* 88h: Current pointer into the delay line */ struct dsp_config *dsp; /* 8ch: Current DSP */ /* 90h */ } crossfeed_state IBSS_ATTR; +static int crossfeed_type = CROSSFEED_TYPE_NONE; + /* Discard the sample histories */ static void crossfeed_flush(struct dsp_proc_entry *this) { struct crossfeed_state *state = (void *)this->data; - memset(state->history, 0, sizeof (state->history)); - memset(state->delay, 0, sizeof (state->delay)); - state->index = state->delay; + + if (crossfeed_type == CROSSFEED_TYPE_CUSTOM) + { + memset(state->history, 0, + sizeof (state->history) + sizeof (state->delay)); + state->index = state->delay; + } + else + { + state->vcl = state->vcr = state->vdiff = 0; + } } @@ -74,30 +103,48 @@ static void crossfeed_process_new_format(struct dsp_proc_entry *this, DSP_PRINT_FORMAT(DSP_PROC_CROSSFEED, DSP_PROC_CROSSFEED, buf->format); + bool was_active = dsp_proc_active(state->dsp, DSP_PROC_CROSSFEED); bool active = buf->format.num_channels >= 2; dsp_proc_activate(state->dsp, DSP_PROC_CROSSFEED, active); if (!active) { /* Can't do this. Sleep until next change */ - crossfeed_flush(this); DEBUGF(" DSP_PROC_CROSSFEED- deactivated\n"); return; } - /* Switch to the real function and call it once */ - this->process[0] = crossfeed_process; + dsp_proc_fn_type fn = crossfeed_process; + + if (crossfeed_type != CROSSFEED_TYPE_CUSTOM) + { + /* 1 / (F.Rforward.C) */ + state->coef1 = (0x7fffffff / NATIVE_FREQUENCY) * 2128; + /* 1 / (F.Rcross.C) */ + state->coef2 = (0x7fffffff / NATIVE_FREQUENCY) * 1000; + fn = crossfeed_meier_process; + } + + if (!was_active || this->process[0] != fn) + { + crossfeed_flush(this); /* Going online or actual type change */ + this->process[0] = fn; /* Set real function */ + } + + /* Call it once */ dsp_proc_call(this, buf_p, (unsigned)buf->format.changed - 1); } -/* Enable or disable the crossfeed */ -void dsp_crossfeed_enable(bool enable) +/* Set the type of crossfeed to use */ +void dsp_set_crossfeed_type(int type) { - if (enable != !crossfeed_state.dsp) - return; + if (type == crossfeed_type) + return; /* No change */ + + crossfeed_type = type; struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO); - dsp_proc_enable(dsp, DSP_PROC_CROSSFEED, enable); + dsp_proc_enable(dsp, DSP_PROC_CROSSFEED, type != CROSSFEED_TYPE_NONE); } /* Set the gain of the dry mix */ @@ -182,6 +229,50 @@ void crossfeed_process(struct dsp_proc_entry *this, struct dsp_buffer **buf_p) } #endif /* CPU */ +#if !defined(CPU_COLDFIRE) && !defined(CPU_ARM) +/** + * Implementation of the "simple" passive crossfeed circuit by Jan Meier. + * See also: http://www.meier-audio.homepage.t-online.de/passivefilter.htm + */ + +void crossfeed_meier_process(struct dsp_proc_entry *this, + struct dsp_buffer **buf_p) +{ + struct dsp_buffer *buf = *buf_p; + + /* Get filter state */ + struct crossfeed_state *state = (struct crossfeed_state *)this->data; + int32_t vcl = state->vcl; + int32_t vcr = state->vcr; + int32_t vdiff = state->vdiff; + int32_t coef1 = state->coef1; + int32_t coef2 = state->coef2; + + int count = buf->remcount; + + for (int i = 0; i < count; i++) + { + /* Calculate new output */ + int32_t lout = buf->p32[0][i] + vcl; + int32_t rout = buf->p32[1][i] + vcr; + buf->p32[0][i] = lout; + buf->p32[1][i] = rout; + + /* Update filter state */ + int32_t common = FRACMUL(vdiff, coef2); + vcl -= FRACMUL(vcl, coef1) + common; + vcr -= FRACMUL(vcr, coef1) - common; + + vdiff = lout - rout; + } + + /* Store filter state */ + state->vcl = vcl; + state->vcr = vcr; + state->vdiff = vdiff; +} +#endif /* CPU */ + /* DSP message hook */ static intptr_t crossfeed_configure(struct dsp_proc_entry *this, struct dsp_config *dsp, @@ -191,11 +282,19 @@ static intptr_t crossfeed_configure(struct dsp_proc_entry *this, switch (setting) { case DSP_PROC_INIT: - this->data = (intptr_t)&crossfeed_state; + if (value == 0) + { + /* New object */ + this->data = (intptr_t)&crossfeed_state; + this->process[1] = crossfeed_process_new_format; + ((struct crossfeed_state *)this->data)->dsp = dsp; + } + + /* Force format change call each time */ this->process[0] = crossfeed_process_new_format; - this->process[1] = crossfeed_process_new_format; - ((struct crossfeed_state *)this->data)->dsp = dsp; dsp_proc_activate(dsp, DSP_PROC_CROSSFEED, true); + break; + case DSP_FLUSH: crossfeed_flush(this); break; diff --git a/lib/rbcodec/dsp/crossfeed.h b/lib/rbcodec/dsp/crossfeed.h index 63261bde9f..2c4d47dba5 100644 --- a/lib/rbcodec/dsp/crossfeed.h +++ b/lib/rbcodec/dsp/crossfeed.h @@ -21,7 +21,14 @@ #ifndef CROSSFEED_H #define CROSSFEED_H -void dsp_crossfeed_enable(bool enable); +enum crossfeed_type +{ + CROSSFEED_TYPE_NONE, + CROSSFEED_TYPE_MEIER, + CROSSFEED_TYPE_CUSTOM, +}; + +void dsp_set_crossfeed_type(int type); void dsp_set_crossfeed_direct_gain(int gain); void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff); diff --git a/lib/rbcodec/dsp/dsp_arm.S b/lib/rbcodec/dsp/dsp_arm.S index 1674d6617a..4fdaf8d5a8 100644 --- a/lib/rbcodec/dsp/dsp_arm.S +++ b/lib/rbcodec/dsp/dsp_arm.S @@ -8,6 +8,8 @@ * $Id$ * * Copyright (C) 2006-2007 Thom Johansen + * Copyright (C) 2010 Bertrik Sikken + * Copyright (C) 2012 Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -246,6 +248,48 @@ crossfeed_process: ldmpc regs=r4-r11 .size crossfeed_process, .-crossfeed_process +/**************************************************************************** + * void crossfeed_meier_process(struct dsp_proc_entry *this, + * struct dsp_buffer **buf_p) + */ + .section .text + .global crossfeed_meier_process +crossfeed_meier_process: + @ input: r0 = this, r1 = buf_p + ldr r1, [r1] @ r1 = buf = *buf_p; + ldr r0, [r0] @ r0 = this->data = &crossfeed_state + stmfd sp!, { r4-r10, lr } @ stack non-volatile context + ldmia r1, { r1-r3 } @ r1 = buf->remcout, r2=p32[0], r3=p32[1] + add r0, r0, #16 @ r0 = &state->vcl + ldmia r0, { r4-r8 } @ r4 = vcl, r5 = vcr, r6 = vdiff + @ r7 = coef1, r8 = coef2 +.cfm_loop: + ldr r12, [r2] @ r12 = lout + ldr r14, [r3] @ r14 = rout + smull r9, r10, r8, r6 @ r9, r10 = common = coef2*vdiff + add r12, r12, r4 @ lout += vcl + add r14, r14, r5 @ rout += vcr + sub r6, r12, r14 @ r6 = vdiff = lout - rout + str r12, [r2], #4 @ store left channel + str r14, [r3], #4 @ store right channel + rsbs r12, r9, #0 @ r12 = -common (lo) + rsc r14, r10, #0 @ r14 = -common (hi) + smlal r9, r10, r7, r4 @ r9, r10 = res1 = coef1*vcl + common + smlal r12, r14, r7, r5 @ r12, r14 = res2 = coef1*vcr - common + subs r1, r1, #1 @ count-- + mov r9, r9, lsr #31 @ r9 = convert res1 to s0.31 + orr r9, r9, r10, asl #1 @ . + mov r12, r12, lsr #31 @ r12 = convert res2 to s0.31 + orr r12, r12, r14, asl #1 @ . + sub r4, r4, r9 @ r4 = vcl -= res1 + sub r5, r5, r12 @ r5 = vcr -= res2 + bgt .cfm_loop @ more samples? + + stmia r0, { r4-r6 } @ save vcl, vcr, vdiff + ldmpc regs=r4-r10 @ restore non-volatile context, return + .size crossfeed_meier_process, .-crossfeed_meier_process + + /**************************************************************************** * int lin_resample_resample(struct resample_data *data, * struct dsp_buffer *src, diff --git a/lib/rbcodec/dsp/dsp_cf.S b/lib/rbcodec/dsp/dsp_cf.S index c710df5177..7d193e0957 100644 --- a/lib/rbcodec/dsp/dsp_cf.S +++ b/lib/rbcodec/dsp/dsp_cf.S @@ -8,7 +8,8 @@ * $Id$ * * Copyright (C) 2006 Thom Johansen - * Portions Copyright (C) 2007 Michael Sevakis + * Copyright (C) 2007, 2012 Michael Sevakis + * Copyright (C) 2010 Bertrik Sikken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -133,6 +134,50 @@ crossfeed_process: rts | .size crossfeed_process,.-crossfeed_process +/**************************************************************************** + * void crossfeed_meier_process(struct dsp_proc_entry *this, + * struct dsp_buffer **buf_p) + */ + .section .text + .global crossfeed_meier_process +crossfeed_meier_process: + | input: 4(sp) = this, 8(sp) = buf_p + movem.l 4(%sp), %a0-%a1 | %a0 = this, %a1 = buf_p + lea.l -24(%sp), %sp | save non-volatiles + movem.l %d2-%d6/%a2, (%sp) | . + move.l (%a0), %a0 | %a0 = &this->data = &crossfeed_state + move.l (%a1), %a1 | %a1 = buf = *buf_p + movem.l 16(%a0), %d1-%d5 | %d1 = vcl, %d2 = vcr, %d3 = vdiff, + | %d4 = coef1, %d5 = coef2 + movem.l (%a1), %d0/%a1-%a2 | %d0 = count = buf->remcount + | %a1 = p32[0], %a2 = p32[1] + | Register usage in loop: + | %d0 = count, %d1 = vcl, %d2 = vcr, %d3 = vdiff/lout, + | %d4 = coef1, %d5 = coef2, %d6 = rout/scratch + | %a1 = p32[0], %a2 = p32[1] +10: | loop + mac.l %d5, %d3, %acc0 | %acc0 = common = coef2*vdiff + move.l %acc0, %acc1 | copy common + mac.l %d4, %d1, (%a1), %d3, %acc0 | %acc0 += coef1*vcl, %d3 = lout + msac.l %d4, %d2, (%a2), %d6, %acc1 | %acc1 -= coef1*vcr, %d6 = rout + add.l %d1, %d3 | lout += vcl + add.l %d2, %d6 | rout += vcr + move.l %d3, (%a1)+ | store left channel, pos inc + move.l %d6, (%a2)+ | store right channel, pos inc + sub.l %d6, %d3 | vdiff = lout - rout + movclr.l %acc0, %d6 | %d4 = fetch res1 in s0.31 + sub.l %d6, %d1 | vcl -= res1 + movclr.l %acc1, %d6 | %d5 = fetch -res2 in s0.31 + add.l %d6, %d2 | vcr += -res2 + subq.l #1, %d0 | count-- + bgt 10b | loop | more samples? + | + movem.l %d1-%d3, 16(%a0) | save vcl, vcr, vdiff + movem.l (%sp), %d2-%d6/%a2 | restore non-volatiles + lea.l 24(%sp), %sp | . + rts | + .size crossfeed_meier_process, .-crossfeed_meier_process + /**************************************************************************** * int lin_resample_resample(struct resample_data *data, * struct dsp_buffer *src,