From a7fabf0741c91fb0a2c28b2d8357bcc4630300af Mon Sep 17 00:00:00 2001 From: Thom Johansen Date: Mon, 26 Feb 2007 00:41:26 +0000 Subject: Add software based bass/treble controls for targets which have no such functionality in hardware (currently only X5). They can also be used on any other SWCODEC target by adding #define HAVE_SW_TONE_CONTROLS in the relevant config-*.h file. Also remove some now unneeded zero checks when using get_replaygain_int(). Comments on sound quality are welcome as some parameters can still be fine-tuned. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12489 a1c6a512-1295-4272-9138-f99709370657 --- apps/dsp.c | 71 +++++++++++++++++++++++++++++++-------- apps/dsp.h | 7 ++++ apps/eq.c | 60 ++++++++++++++++++++++++++++++--- apps/eq.h | 7 ++-- apps/menus/sound_menu.c | 10 ++---- apps/settings.c | 7 ++-- firmware/export/config-iaudiox5.h | 3 ++ firmware/export/sound.h | 2 ++ firmware/sound.c | 53 ++++++++++++++++++++--------- 9 files changed, 175 insertions(+), 45 deletions(-) diff --git a/apps/dsp.c b/apps/dsp.c index 0ffaaea8d8..f306069a87 100644 --- a/apps/dsp.c +++ b/apps/dsp.c @@ -116,9 +116,10 @@ struct crossfeed_data /* 8ch */ }; -/* Current setup is one lowshelf filters, three peaking filters and one - highshelf filter. Varying the number of shelving filters make no sense, - but adding peaking filters is possible. */ +/* Current setup is one lowshelf filters three peaking filters and one + * highshelf filter. Varying the number of shelving filters make no sense, + * but adding peaking filters is possible. + */ struct eq_state { char enabled[5]; /* 00h - Flags for active filters */ @@ -171,6 +172,13 @@ static long dither_bias IBSS_ATTR; struct crossfeed_data crossfeed_data IBSS_ATTR; /* A */ /* Equalizer */ static struct eq_state eq_data; /* A/V */ +#ifdef HAVE_SW_TONE_CONTROLS +static int prescale; +static int bass; +static int treble; +/* Filter struct for software bass/treble controls */ +static struct eqfilter tone_filter; +#endif /* Settings applicable to audio codec only */ static int pitch_ratio = 1000; @@ -704,11 +712,7 @@ void dsp_set_crossfeed(bool enable) void dsp_set_crossfeed_direct_gain(int gain) { - /* Work around bug in get_replaygain_int which returns 0 for 0 dB */ - if (gain == 0) - crossfeed_data.gain = 0x7fffffff; - else - crossfeed_data.gain = get_replaygain_int(gain * -10) << 7; + crossfeed_data.gain = get_replaygain_int(gain * -10) << 7; } void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff) @@ -716,8 +720,8 @@ void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff) long g1 = get_replaygain_int(lf_gain * -10) << 3; long g2 = get_replaygain_int(hf_gain * -10) << 3; - filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*cutoff, g1, g2, - crossfeed_data.coefs); + filter_shelf_coefs(0xffffffff/NATIVE_FREQUENCY*cutoff, g1, g2, + crossfeed_data.coefs); } /* Applies crossfeed to the stereo signal in src. @@ -985,6 +989,36 @@ static void channels_process_sound_chan_mono(int count, int32_t *buf[]) } #endif /* DSP_HAVE_ASM_SOUND_CHAN_MONO */ +#ifdef HAVE_SW_TONE_CONTROLS +static void set_tone_controls(void) +{ + filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*200, + 0xffffffff/NATIVE_FREQUENCY*3500, + bass, treble, -prescale, tone_filter.coefs); +} + +int dsp_callback(int msg, intptr_t param) +{ + switch (msg) { + case DSP_CALLBACK_SET_PRESCALE: + prescale = param; + set_tone_controls(); + break; + /* prescaler is always set after calling any of these, so we wait with + * calculating coefs until the above case is hit. + */ + case DSP_CALLBACK_SET_BASS: + bass = param; + break; + case DSP_CALLBACK_SET_TREBLE: + treble = param; + default: + break; + } + return 0; +} +#endif + #ifndef DSP_HAVE_ASM_SOUND_CHAN_CUSTOM static void channels_process_sound_chan_custom(int count, int32_t *buf[]) { @@ -1068,12 +1102,12 @@ int dsp_process(char *dst, const char *src[], int count) int written = 0; int samples; - #if defined(CPU_COLDFIRE) && !defined(SIMULATOR) +#if defined(CPU_COLDFIRE) && !defined(SIMULATOR) /* set emac unit for dsp processing, and save old macsr, we're running in codec thread context at this point, so can't clobber it */ unsigned long old_macsr = coldfire_get_macsr(); coldfire_set_macsr(EMAC_FRACTIONAL | EMAC_SATURATE); - #endif +#endif while (count > 0) { @@ -1085,8 +1119,17 @@ int dsp_process(char *dst, const char *src[], int count) break; /* I'm pretty sure we're downsampling here */ if (dsp->apply_crossfeed) dsp->apply_crossfeed(tmp, samples); + /* TODO: EQ and tone controls need separate structs for audio and voice + * DSP processing thanks to filter history. isn't really audible now, but + * might be the day we start handling voice more delicately. + */ if (eq_enabled) eq_process(samples, tmp); +#ifdef HAVE_SW_TONE_CONTROLS + if ((bass | treble) != 0) + eq_filter(tmp, &tone_filter, samples, dsp->data.num_channels, + FILTER_BISHELF_SHIFT); +#endif if (dsp->channels_process) dsp->channels_process(samples, tmp); dsp->output_samples(samples, &dsp->data, tmp, (int16_t *)dst); @@ -1095,10 +1138,10 @@ int dsp_process(char *dst, const char *src[], int count) yield(); } - #if defined(CPU_COLDFIRE) && !defined(SIMULATOR) +#if defined(CPU_COLDFIRE) && !defined(SIMULATOR) /* set old macsr again */ coldfire_set_macsr(old_macsr); - #endif +#endif return written; } diff --git a/apps/dsp.h b/apps/dsp.h index 63dc68cbb4..03118e8c31 100644 --- a/apps/dsp.h +++ b/apps/dsp.h @@ -51,6 +51,12 @@ enum DSP_CROSSFEED }; +enum { + DSP_CALLBACK_SET_PRESCALE = 0, + DSP_CALLBACK_SET_BASS, + DSP_CALLBACK_SET_TREBLE +}; + /* A bunch of fixed point assembler helper macros */ #if defined(CPU_COLDFIRE) && !defined(SIMULATOR) /* These macros use the Coldfire EMAC extension and need the MACSR flags set @@ -209,6 +215,7 @@ void dsp_set_eq_precut(int precut); void dsp_set_eq_coefs(int band); void sound_set_pitch(int r); int sound_get_pitch(void); +int dsp_callback(int msg, intptr_t param); void channels_set(int value); void stereo_width_set(int value); void dsp_dither_enable(bool enable); diff --git a/apps/eq.c b/apps/eq.c index 588c23f89f..1d74db790e 100644 --- a/apps/eq.c +++ b/apps/eq.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2006 Thom Johansen + * Copyright (C) 2006-2007 Thom Johansen * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. @@ -127,7 +127,7 @@ static long fsincos(unsigned long phase, long *cos) { * @param an gain at Nyquist frequency. s3.27 fixed point. * @param c pointer to coefficient storage. The coefs are s0.31 format. */ -void filter_bishelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c) +void filter_shelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c) { const long one = 1 << 27; long a0, a1; @@ -137,7 +137,7 @@ void filter_bishelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c) cs = one + (cs >> 4); /* For max A = 4 (24 dB) */ - b0 = FRACMUL_SHL(an, cs, 4) + FRACMUL_SHL(ad, s, 4); + b0 = FRACMUL_SHL(ad, s, 4) + FRACMUL_SHL(an, cs, 4); b1 = FRACMUL_SHL(ad, s, 4) - FRACMUL_SHL(an, cs, 4); a0 = s + cs; a1 = s - cs; @@ -147,6 +147,58 @@ void filter_bishelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c) c[2] = -DIV64(a1, a0, 31); } +/** + * Calculate second order section filter consisting of one low-shelf and one + * high-shelf section. + * @param cutoff_low low-shelf midpoint frequency. See eq_pk_coefs for format. + * @param cutoff_high high-shelf midpoint frequency. + * @param A_low decibel value multiplied by ten, describing gain/attenuation of + * low-shelf part. Max value is 24 dB. + * @param A_high decibel value multiplied by ten, describing gain/attenuation of + * high-shelf part. Max value is 24 dB. + * @param A decibel value multiplied by ten, describing additional overall gain. + * @param c pointer to coefficient storage. Coefficients are s4.27 format. + */ +void filter_bishelf_coefs(unsigned long cutoff_low, unsigned long cutoff_high, + long A_low, long A_high, long A, int32_t *c) +{ + long sin1, cos2; /* s0.31 */ + long cos1, sin2; /* s3.28 */ + int32_t b0, b1, b2, b3; /* s3.28 */ + int32_t a0, a1, a2, a3; + const long gd = get_replaygain_int(A_low*5) << 4; /* 10^(db/40), s3.28 */ + const long gn = get_replaygain_int(A_high*5) << 4; /* 10^(db/40), s3.28 */ + const long g = get_replaygain_int(A*10) << 7; /* 10^(db/20), s0.31 */ + + sin1 = fsincos(cutoff_low/2, &cos1); + sin2 = fsincos(cutoff_high/2, &cos2) >> 3; + cos1 >>= 3; + + /* lowshelf filter, ranges listed are for all possible cutoffs */ + b0 = FRACMUL(sin1, gd) + cos1; /* 0.25 .. 4.10 */ + b1 = FRACMUL(sin1, gd) - cos1; /* -1 .. 3.98 */ + a0 = DIV64(sin1, gd, 25) + cos1; /* 0.25 .. 4.10 */ + a1 = DIV64(sin1, gd, 25) - cos1; /* -1 .. 3.98 */ + + /* highshelf filter */ + b2 = sin2 + FRACMUL(cos2, gn); /* 0.25 .. 4.10 */ + b3 = sin2 - FRACMUL(cos2, gn); /* -3.98 .. 1 */ + a2 = sin2 + DIV64(cos2, gn, 25); /* 0.25 .. 4.10 */ + a3 = sin2 - DIV64(cos2, gn, 25); /* -3.98 .. 1 */ + + /* now we cascade the two first order filters to one second order filter + * which can be used by eq_filter(). these resulting coefficients have a + * really wide numerical range, so we use a fixed point format which will + * work for the selected cutoff frequencies (in dsp.c) only. + */ + const int32_t rcp_a0 = DIV64(1, FRACMUL(a0, a2), 53); /* s3.28 */ + *c++ = FRACMUL(g, FRACMUL_SHL(FRACMUL(b0, b2), rcp_a0, 5)); + *c++ = FRACMUL(g, FRACMUL_SHL(FRACMUL(b0, b3) + FRACMUL(b1, b2), rcp_a0, 5)); + *c++ = FRACMUL(g, FRACMUL_SHL(FRACMUL(b1, b3), rcp_a0, 5)); + *c++ = -FRACMUL_SHL(FRACMUL(a0, a3) + FRACMUL(a1, a2), rcp_a0, 5); + *c++ = -FRACMUL_SHL(FRACMUL(a1, a3), rcp_a0, 5); +} + /* Coef calculation taken from Audio-EQ-Cookbook.txt by Robert Bristow-Johnson. * Slightly faster calculation can be done by deriving forms which use tan() * instead of cos() and sin(), but the latter are far easier to use when doing @@ -162,7 +214,7 @@ void filter_bishelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c) * @param Q Q factor value multiplied by ten. Lower bound is artificially set * at 0.5. * @param db decibel value multiplied by ten, describing gain/attenuation at - * peak freq. + * peak freq. Max value is 24 dB. * @param c pointer to coefficient storage. Coefficients are s3.28 format. */ void eq_pk_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c) diff --git a/apps/eq.h b/apps/eq.h index 095c8e82f0..83d235959d 100644 --- a/apps/eq.h +++ b/apps/eq.h @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2006 Thom Johansen + * Copyright (C) 2006-2007 Thom Johansen * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. @@ -25,6 +25,7 @@ /* These depend on the fixed point formats used by the different filter types and need to be changed when they change. */ +#define FILTER_BISHELF_SHIFT 5 #define EQ_PEAK_SHIFT 4 #define EQ_SHELF_SHIFT 6 @@ -33,7 +34,9 @@ struct eqfilter { int32_t history[2][4]; }; -void filter_bishelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c); +void filter_shelf_coefs(unsigned long cutoff, long ad, long an, int32_t *c); +void filter_bishelf_coefs(unsigned long cutoff_low, unsigned long cutoff_high, + long A_low, long A_high, long A, int32_t *c); void eq_pk_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c); void eq_ls_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c); void eq_hs_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c); diff --git a/apps/menus/sound_menu.c b/apps/menus/sound_menu.c index 9dc9579b0e..287b3ec904 100644 --- a/apps/menus/sound_menu.c +++ b/apps/menus/sound_menu.c @@ -55,12 +55,8 @@ int soundmenu_callback(int action,const struct menu_item_ex *this_item) #endif MENUITEM_SETTING(volume, &global_settings.volume, soundmenu_callback); - -#ifndef HAVE_TLV320 - MENUITEM_SETTING(bass, &global_settings.bass, soundmenu_callback); - MENUITEM_SETTING(treble, &global_settings.treble, soundmenu_callback); -#endif - +MENUITEM_SETTING(bass, &global_settings.bass, soundmenu_callback); +MENUITEM_SETTING(treble, &global_settings.treble, soundmenu_callback); MENUITEM_SETTING(balance, &global_settings.balance, soundmenu_callback); MENUITEM_SETTING(channel_config, &global_settings.channel_config, soundmenu_callback); MENUITEM_SETTING(stereo_width, &global_settings.stereo_width, soundmenu_callback); @@ -99,9 +95,7 @@ MENUITEM_SETTING(stereo_width, &global_settings.stereo_width, soundmenu_callback MAKE_MENU(sound_settings, ID2P(LANG_SOUND_SETTINGS), NULL, bitmap_icons_6x8[Icon_Audio], &volume, -#ifndef HAVE_TLV320 &bass,&treble, -#endif &balance,&channel_config,&stereo_width #if CONFIG_CODEC == SWCODEC ,&crossfeed_menu, &equalizer_menu, &dithering_enabled diff --git a/apps/settings.c b/apps/settings.c index dd5e7c5ae3..cc5ab12d6f 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -642,6 +642,9 @@ void settings_apply_pm_range(void) void sound_settings_apply(void) { +#ifdef HAVE_SW_TONE_CONTROLS + sound_set_dsp_callback(dsp_callback); +#endif sound_set(SOUND_BASS, global_settings.bass); sound_set(SOUND_TREBLE, global_settings.treble); sound_set(SOUND_BALANCE, global_settings.balance); @@ -967,7 +970,7 @@ bool set_sound(const unsigned char * string, talkunit = UNIT_PERCENT; else if (*unit == 'H') talkunit = UNIT_HERTZ; - if(!numdec) + if (!numdec) #if CONFIG_CODEC == SWCODEC /* We need to hijack this one and send it off to apps/dsp.c instead of firmware/sound.c */ @@ -975,7 +978,7 @@ bool set_sound(const unsigned char * string, return set_int(string, unit, talkunit, variable, &stereo_width_set, steps, min, max, NULL ); else -#endif +#endif return set_int(string, unit, talkunit, variable, sound_callback, steps, min, max, NULL ); else diff --git a/firmware/export/config-iaudiox5.h b/firmware/export/config-iaudiox5.h index 6aeaab0431..ba9e95398f 100644 --- a/firmware/export/config-iaudiox5.h +++ b/firmware/export/config-iaudiox5.h @@ -84,6 +84,9 @@ #define HAVE_TLV320 +/* TLV320 has no tone controls, so we use the software ones */ +#define HAVE_SW_TONE_CONTROLS + #ifndef SIMULATOR /* Define this if your LCD can set contrast */ diff --git a/firmware/export/sound.h b/firmware/export/sound.h index 2079a84f0f..192384031d 100644 --- a/firmware/export/sound.h +++ b/firmware/export/sound.h @@ -19,6 +19,7 @@ #ifndef SOUND_H #define SOUND_H +#include #ifdef HAVE_UDA1380 #include "uda1380.h" #elif defined(HAVE_WM8975) || defined(HAVE_WM8751) @@ -76,6 +77,7 @@ int sound_max(int setting); int sound_default(int setting); sound_set_type* sound_get_fn(int setting); +void sound_set_dsp_callback(int (*func)(int, intptr_t)); void sound_set_volume(int value); void sound_set_balance(int value); void sound_set_bass(int value); diff --git a/firmware/sound.c b/firmware/sound.c index c3679d41f2..a2b4e96e81 100644 --- a/firmware/sound.c +++ b/firmware/sound.c @@ -86,6 +86,11 @@ static const struct sound_settings_info sound_settings_table[] = { [SOUND_VOLUME] = {"dB", 0, 1, -78, 18, -18, sound_set_volume}, [SOUND_BASS] = {"dB", 0, 1, -15, 15, 7, sound_set_bass}, [SOUND_TREBLE] = {"dB", 0, 1, -15, 15, 7, sound_set_treble}, +#endif +/* Override any other potentially existing treble/bass controllers if wanted */ +#ifdef HAVE_SW_TONE_CONTROLS + [SOUND_BASS] = {"dB", 0, 1, -24, 24, 0, sound_set_bass}, + [SOUND_TREBLE] = {"dB", 0, 1, -24, 24, 0, sound_set_treble}, #endif [SOUND_BALANCE] = {"%", 0, 1,-100, 100, 0, sound_set_balance}, [SOUND_CHANNELS] = {"", 0, 1, 0, 5, 0, sound_set_channels}, @@ -166,6 +171,22 @@ sound_set_type* sound_get_fn(int setting) return NULL; } +#ifdef HAVE_SW_TONE_CONTROLS +/* Copied from dsp.h, nasty nasty, but we don't want to include dsp.h */ +enum { + DSP_CALLBACK_SET_PRESCALE = 0, + DSP_CALLBACK_SET_BASS, + DSP_CALLBACK_SET_TREBLE +}; + +static int (*dsp_callback)(int, intptr_t) = NULL; + +void sound_set_dsp_callback(int (*func)(int, intptr_t)) +{ + dsp_callback = func; +} +#endif + #ifndef SIMULATOR #if CONFIG_CODEC == MAS3507D /* volume/balance/treble/bass interdependency */ #define VOLUME_MIN -780 @@ -293,10 +314,9 @@ int current_bass = 0; /* -150..+150 0..+240 */ static void set_prescaled_volume(void) { - int prescale = 0; + int prescale; int l, r; -#ifndef HAVE_TLV320 prescale = MAX(current_bass, current_treble); if (prescale < 0) prescale = 0; /* no need to prescale if we don't boost @@ -307,13 +327,12 @@ static void set_prescaled_volume(void) * instead (might cause clipping). */ if (current_volume + prescale > VOLUME_MAX) prescale = VOLUME_MAX - current_volume; -#endif - -#if CONFIG_CODEC == MAS3507D + +#if defined(HAVE_SW_TONE_CONTROLS) + dsp_callback(DSP_CALLBACK_SET_PRESCALE, prescale); +#elif CONFIG_CODEC == MAS3507D mas_writereg(MAS_REG_KPRESCALE, prescale_table[prescale/10]); -#elif defined(HAVE_UDA1380) - audiohw_set_mixer_vol(tenthdb2mixer(-prescale), tenthdb2mixer(-prescale)); -#elif defined(HAVE_WM8975) || defined(HAVE_WM8758) \ +#elif defined(HAVE_UDA1380) || defined(HAVE_WM8975) || defined(HAVE_WM8758) \ || defined(HAVE_WM8731) || defined(HAVE_WM8721) || defined(HAVE_WM8751) audiohw_set_mixer_vol(tenthdb2mixer(-prescale), tenthdb2mixer(-prescale)); #endif @@ -338,9 +357,7 @@ static void set_prescaled_volume(void) #if CONFIG_CODEC == MAS3507D dac_volume(tenthdb2reg(l), tenthdb2reg(r), false); -#elif defined(HAVE_UDA1380) - audiohw_set_master_vol(tenthdb2master(l), tenthdb2master(r)); -#elif defined(HAVE_WM8975) || defined(HAVE_WM8758) \ +#elif defined(HAVE_UDA1380) || defined(HAVE_WM8975) || defined(HAVE_WM8758) \ || defined(HAVE_WM8731) || defined(HAVE_WM8721) || defined(HAVE_WM8751) audiohw_set_master_vol(tenthdb2master(l), tenthdb2master(r)); #if defined(HAVE_WM8975) || defined(HAVE_WM8758) || defined(HAVE_WM8751) @@ -484,12 +501,15 @@ void sound_set_balance(int value) #endif } -#ifndef HAVE_TLV320 void sound_set_bass(int value) { if(!audio_is_initialized) return; -#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) +#if defined(HAVE_SW_TONE_CONTROLS) + current_bass = value * 10; + dsp_callback(DSP_CALLBACK_SET_BASS, current_bass); + set_prescaled_volume(); +#elif (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) unsigned tmp = ((unsigned)(value * 8) & 0xff) << 8; mas_codec_writereg(0x14, tmp); #elif CONFIG_CODEC == MAS3507D @@ -515,7 +535,11 @@ void sound_set_treble(int value) { if(!audio_is_initialized) return; -#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) +#if defined(HAVE_SW_TONE_CONTROLS) + current_treble = value * 10; + dsp_callback(DSP_CALLBACK_SET_TREBLE, current_treble); + set_prescaled_volume(); +#elif (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) unsigned tmp = ((unsigned)(value * 8) & 0xff) << 8; mas_codec_writereg(0x15, tmp); #elif CONFIG_CODEC == MAS3507D @@ -536,7 +560,6 @@ void sound_set_treble(int value) (void)value; #endif } -#endif /* HAVE_TLV320 */ void sound_set_channels(int value) { -- cgit v1.2.3