From 3ae0f32ac3d6693a9528703c5f1b3586a9d79601 Mon Sep 17 00:00:00 2001 From: Chiwen Chang Date: Fri, 22 Aug 2014 23:30:35 +1000 Subject: [PATCH] three new DSPs perceptual bass enhancement - a bbe-ish group delay corrction with Biophonic EQ boost. - precut auditory fatigue reduction -reduce signal in frequency that may trigger temporary threshold shift haas surround -frequency between f(x1) and f(x2) is always bypassed. -can apply to side only. Change-Id: Icb6355ce9b1c99bf2c58c9385c3c411c0ae209d3 --- apps/lang/english.lang | 119 +++++++ apps/menus/sound_menu.c | 23 ++ apps/settings.c | 7 + apps/settings.h | 12 + apps/settings_list.c | 45 ++- lib/rbcodec/SOURCES | 3 + lib/rbcodec/dsp/afr.c | 147 +++++++++ lib/rbcodec/dsp/afr.h | 27 ++ lib/rbcodec/dsp/dsp_filter.c | 24 +- lib/rbcodec/dsp/dsp_filter.h | 6 +- lib/rbcodec/dsp/dsp_proc_database.h | 3 + lib/rbcodec/dsp/dsp_proc_settings.h | 3 + lib/rbcodec/dsp/pbe.c | 251 +++++++++++++++ lib/rbcodec/dsp/pbe.h | 29 ++ lib/rbcodec/dsp/surround.c | 333 ++++++++++++++++++++ lib/rbcodec/dsp/surround.h | 31 ++ manual/configure_rockbox/sound_settings.tex | 27 ++ 17 files changed, 1084 insertions(+), 6 deletions(-) create mode 100644 lib/rbcodec/dsp/afr.c create mode 100644 lib/rbcodec/dsp/afr.h create mode 100644 lib/rbcodec/dsp/pbe.c create mode 100644 lib/rbcodec/dsp/pbe.h create mode 100644 lib/rbcodec/dsp/surround.c create mode 100644 lib/rbcodec/dsp/surround.h diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 113570522c..ca5e687ea8 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -13254,3 +13254,122 @@ swcodec: "Q" + + id: LANG_PBE + desc: in sound settings + user: core + + *: none + swcodec: "Perceptual Bass Enhancement" + + + *: none + swcodec: "Perceptual Bass Enhancement" + + + *: none + swcodec: "Perceptual Bass Enhancement" + + + + id: LANG_AFR + desc: in sound settings + user: core + + *: none + swcodec: "Auditory Fatigue Reduction" + + + *: none + swcodec: "Auditory Fatigue Reduction" + + + *: none + swcodec: "Auditory Fatigue Reduction" + + + + id: LANG_SURROUND + desc: in the sound settings menu + user: core + + *: none + swcodec: "Haas Surround" + + + *: none + swcodec: "Haas Surround" + + + *: none + swcodec: "Haas Surround" + + + + id: LANG_SURROUND_FX1 + desc: in sound settings + user: core + + *: none + swcodec: "f(x1)" + + + *: none + swcodec: "f(x1)" + + + *: none + swcodec: "f(x1)" + + + + id: LANG_SURROUND_FX2 + desc: in sound settings + user: core + + *: none + swcodec: "f(x2)" + + + *: none + swcodec: "f(x2)" + + + *: none + swcodec: "f(x2)" + + + + id: LANG_SURROUND_METHOD2 + desc: in sound settings + user: core + + *: none + swcodec: "SIDE ONLY" + + + *: none + swcodec: "SIDE ONLY" + + + *: none + swcodec: "SIDE ONLY" + + + + id: LANG_SURROUND_MIX + desc: in sound settings + user: core + + *: none + swcodec: "Dry / Wet Mix" + + + *: none + swcodec: "Dry / Wet Mix" + + + *: none + swcodec: "Dry / Wet Mix" + + diff --git a/apps/menus/sound_menu.c b/apps/menus/sound_menu.c index 8913266a5f..7c04662233 100644 --- a/apps/menus/sound_menu.c +++ b/apps/menus/sound_menu.c @@ -166,6 +166,28 @@ static int timestretch_callback(int action,const struct menu_item_ex *this_item) MENUITEM_SETTING(dithering_enabled, &global_settings.dithering_enabled, lowlatency_callback); + MENUITEM_SETTING(afr_enabled, + &global_settings.afr_enabled, lowlatency_callback); + MENUITEM_SETTING(pbe, + &global_settings.pbe, lowlatency_callback); + MENUITEM_SETTING(pbe_precut, + &global_settings.pbe_precut, lowlatency_callback); + MAKE_MENU(pbe_menu,ID2P(LANG_PBE), NULL, Icon_NOICON, + &pbe,&pbe_precut); + MENUITEM_SETTING(surround_enabled, + &global_settings.surround_enabled, lowlatency_callback); + MENUITEM_SETTING(surround_balance, + &global_settings.surround_balance, lowlatency_callback); + MENUITEM_SETTING(surround_fx1, + &global_settings.surround_fx1, lowlatency_callback); + MENUITEM_SETTING(surround_fx2, + &global_settings.surround_fx2, lowlatency_callback); + MENUITEM_SETTING(surround_method2, + &global_settings.surround_method2, lowlatency_callback); + MENUITEM_SETTING(surround_mix, + &global_settings.surround_mix, lowlatency_callback); + MAKE_MENU(surround_menu,ID2P(LANG_SURROUND), NULL, Icon_NOICON, + &surround_enabled,&surround_balance,&surround_fx1,&surround_fx2,&surround_method2,&surround_mix); /* compressor submenu */ MENUITEM_SETTING(compressor_threshold, @@ -236,6 +258,7 @@ MAKE_MENU(sound_settings, ID2P(LANG_SOUND_SETTINGS), NULL, Icon_Audio, #endif #if CONFIG_CODEC == SWCODEC ,&crossfeed_menu, &equalizer_menu, &dithering_enabled + ,&surround_menu, &pbe_menu, &afr_enabled #ifdef HAVE_PITCHCONTROL ,×tretch_enabled #endif diff --git a/apps/settings.c b/apps/settings.c index 16d572eb0d..f144a4af03 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -1034,6 +1034,13 @@ void settings_apply(bool read_disk) } dsp_dither_enable(global_settings.dithering_enabled); + dsp_surround_set_balance(global_settings.surround_balance); + dsp_surround_set_cutoff(global_settings.surround_fx1, global_settings.surround_fx2); + dsp_surround_mix(global_settings.surround_mix); + dsp_surround_enable(global_settings.surround_enabled); + dsp_afr_enable(global_settings.afr_enabled); + dsp_pbe_precut(global_settings.pbe_precut); + dsp_pbe_enable(global_settings.pbe); #ifdef HAVE_PITCHCONTROL dsp_timestretch_enable(global_settings.timestretch_enabled); #endif diff --git a/apps/settings.h b/apps/settings.h index 3ec5ee6207..3029026ab6 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -838,6 +838,18 @@ struct user_settings int play_frequency; /* core audio output frequency selection */ #endif int volume_limit; /* maximum volume limit */ + + int surround_enabled; + int surround_balance; + int surround_fx1; + int surround_fx2; + bool surround_method2; + int surround_mix; + + int pbe; + int pbe_precut; + + int afr_enabled; }; /** global variables **/ diff --git a/apps/settings_list.c b/apps/settings_list.c index cddc062d40..abdb512a42 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -446,6 +446,12 @@ static void crossfeed_cross_set(int val) global_settings.crossfeed_hf_cutoff); } +static void surround_set_factor(int val) +{ + (void)val; + dsp_surround_set_cutoff(global_settings.surround_fx1, global_settings.surround_fx2); +} + static void compressor_set(int val) { (void)val; @@ -1667,7 +1673,44 @@ const struct settings_list settings[] = { /* dithering */ OFFON_SETTING(F_SOUNDSETTING, dithering_enabled, LANG_DITHERING, false, "dithering enabled", dsp_dither_enable), - + /* surround */ + TABLE_SETTING(F_SOUNDSETTING, surround_enabled, + LANG_SURROUND, 0, "surround enabled", "off", + UNIT_MS, formatter_unit_0_is_off, getlang_unit_0_is_off, + dsp_surround_enable, 6, + 0,5,8,10,15,30), + INT_SETTING_NOWRAP(F_SOUNDSETTING, surround_balance, + LANG_BALANCE, 35, + "surround balance", UNIT_PERCENT, 0, 99, + 1, NULL, NULL, dsp_surround_set_balance), + INT_SETTING_NOWRAP(F_SOUNDSETTING, surround_fx1, + LANG_SURROUND_FX1, 3400, + "surround_fx1", UNIT_HERTZ, 600, 8000, + 200, NULL, NULL, surround_set_factor), + INT_SETTING_NOWRAP(F_SOUNDSETTING, surround_fx2, + LANG_SURROUND_FX2, 320, + "surround_fx2", UNIT_HERTZ, 40, 400, + 40, NULL, NULL, surround_set_factor), + OFFON_SETTING(F_SOUNDSETTING, surround_method2, LANG_SURROUND_METHOD2, false, + "side only", dsp_surround_side_only), + INT_SETTING_NOWRAP(F_SOUNDSETTING, surround_mix, + LANG_SURROUND_MIX, 50, + "surround mix", UNIT_PERCENT, 0, 100, + 5, NULL, NULL, dsp_surround_mix), + /* auditory fatigue reduction */ + CHOICE_SETTING(F_SOUNDSETTING|F_NO_WRAP, afr_enabled, + LANG_AFR, 0,"afr enabled", + "off,weak,moderate,strong", dsp_afr_enable, 4, + ID2P(LANG_OFF), ID2P(LANG_WEAK),ID2P(LANG_MODERATE),ID2P(LANG_STRONG)), + /* PBE */ + INT_SETTING_NOWRAP(F_SOUNDSETTING, pbe, + LANG_PBE, 0, + "pbe", UNIT_PERCENT, 0, 100, + 25, NULL, NULL, dsp_pbe_enable), + INT_SETTING_NOWRAP(F_SOUNDSETTING, pbe_precut, + LANG_EQUALIZER_PRECUT, -25, + "pbe precut", UNIT_DB, -45, 0, + 1, db_format, NULL, dsp_pbe_precut), #ifdef HAVE_PITCHCONTROL /* timestretch */ OFFON_SETTING(F_SOUNDSETTING, timestretch_enabled, LANG_TIMESTRETCH, false, diff --git a/lib/rbcodec/SOURCES b/lib/rbcodec/SOURCES index 401d546ee6..c2a32e9c28 100644 --- a/lib/rbcodec/SOURCES +++ b/lib/rbcodec/SOURCES @@ -7,6 +7,9 @@ dsp/channel_mode.c dsp/compressor.c dsp/crossfeed.c dsp/dsp_core.c +dsp/pbe.c +dsp/afr.c +dsp/surround.c dsp/dsp_filter.c dsp/dsp_misc.c dsp/dsp_sample_input.c diff --git a/lib/rbcodec/dsp/afr.c b/lib/rbcodec/dsp/afr.c new file mode 100644 index 0000000000..b266e4b549 --- /dev/null +++ b/lib/rbcodec/dsp/afr.c @@ -0,0 +1,147 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Chiwen Chang + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "afr.h" +#include "config.h" +#include "fixedpoint.h" +#include "fracmul.h" +#include "settings.h" +#include "dsp_proc_entry.h" +#include "dsp_filter.h" + +/* Auditory fatigue reduction */ + +static int afr_strength = 0; +static struct dsp_filter afr_filters[4]; + +static void dsp_afr_flush(void) +{ + for (int i = 0 ;i < 4;i++) + filter_flush(&afr_filters[i]); +} + +static void strength_update(unsigned int fout) +{ + int hs=0, ls=0, drop3k=0; + + switch (afr_strength) + { + /* not called if afr_strength == 0 (disabled) */ + case 1: + hs=-29; ls=0; drop3k=-13; + break; + case 2: + hs=-58; ls=-5; drop3k=-29; + break; + case 3: + hs=-72; ls=-5; drop3k=-48; + break; + } + + filter_bishelf_coefs(fp_div(8000, fout, 32), + fp_div(20000, fout, 32), + 0, (hs/2), 0, + &afr_filters[0]); + + filter_pk_coefs(fp_div(8000, fout, 32), 28, hs, + &afr_filters[1]); + + filter_pk_coefs(fp_div(3150, fout, 32), 28, drop3k, + &afr_filters[2]); + + filter_bishelf_coefs(fp_div(200, fout, 32), + fp_div(1280, fout, 32), + ls, 0, 0, + &afr_filters[3]); +} + +void dsp_afr_enable(int var) +{ + if (var == afr_strength) + return; /* No setting change */ + + bool was_enabled = afr_strength > 0; + afr_strength = var; + + bool now_enabled = var > 0; + + if (was_enabled == now_enabled && !now_enabled) + return; + + /* If changing status, enable or disable it; if already enabled push + additional DSP_PROC_INIT messages with value = 1 to force-update the + filters */ + struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO); + dsp_proc_enable(dsp, DSP_PROC_AFR, now_enabled); +} + +static void afr_reduce_process(struct dsp_proc_entry *this, + struct dsp_buffer **buf_p) +{ + struct dsp_buffer *buf = *buf_p; + + for (int i = 0; i < 4; i++) + filter_process(&afr_filters[i], buf->p32, buf->remcount, + buf->format.num_channels); + + (void)this; +} + +/* DSP message hook */ +static intptr_t afr_configure(struct dsp_proc_entry *this, + struct dsp_config *dsp, + unsigned int setting, + intptr_t value) +{ + /* This only attaches to the audio (codec) DSP */ + + switch (setting) + { + case DSP_PROC_INIT: + if (value == 0) + { + /* Coming online; was disabled */ + this->process = afr_reduce_process; + dsp_afr_flush(); + dsp_proc_activate(dsp, DSP_PROC_AFR, true); + } + /* else additional forced messages */ + + strength_update(dsp_get_output_frequency(dsp)); + break; + + case DSP_FLUSH: + /* Discontinuity; clear filters */ + dsp_afr_flush(); + break; + + case DSP_SET_OUT_FREQUENCY: + /* New output frequency */ + strength_update(value); + break; + } + + return 1; +} + +/* Database entry */ +DSP_PROC_DB_ENTRY( + AFR, + afr_configure); diff --git a/lib/rbcodec/dsp/afr.h b/lib/rbcodec/dsp/afr.h new file mode 100644 index 0000000000..783d7e1760 --- /dev/null +++ b/lib/rbcodec/dsp/afr.h @@ -0,0 +1,27 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Chiwen Chang + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef AFR_H +#define AFR_H + +#include +void dsp_afr_enable(int var); + +#endif /* AFR_H */ diff --git a/lib/rbcodec/dsp/dsp_filter.c b/lib/rbcodec/dsp/dsp_filter.c index b74d499e51..3656026536 100644 --- a/lib/rbcodec/dsp/dsp_filter.c +++ b/lib/rbcodec/dsp/dsp_filter.c @@ -72,7 +72,7 @@ void filter_shelf_coefs(unsigned long cutoff, long A, bool low, int32_t *c) *c++ = -FRACMUL_SHL(a1, rcp_a0, 1); /* -1 .. 1 */ } -#ifdef HAVE_SW_TONE_CONTROLS + /** * Calculate second order section filter consisting of one low-shelf and one * high-shelf section. @@ -114,7 +114,7 @@ void filter_bishelf_coefs(unsigned long cutoff_low, unsigned long cutoff_high, f->shift = FILTER_BISHELF_SHIFT; } -#endif /* HAVE_SW_TONE_CONTROLS */ + /* Coef calculation taken from Audio-EQ-Cookbook.txt by Robert Bristow-Johnson. * Slightly faster calculation can be done by deriving forms which use tan() @@ -303,3 +303,23 @@ void filter_process(struct dsp_filter *f, int32_t * const buf[], int count, } } #endif /* CPU */ + +/* ring buffer */ +int32_t dequeue(int32_t* buffer, int *head, int boundary) +{ + int32_t var = buffer[*head]; + if (*head +1 >= boundary) + *head = 0; + else + *head += 1; + return var; +} + +void enqueue(int32_t var, int32_t* buffer, int *head, int boundary) +{ + buffer[*head] = var; + if (*head +1 >= boundary) + *head = 0; + else + *head += 1; +} diff --git a/lib/rbcodec/dsp/dsp_filter.h b/lib/rbcodec/dsp/dsp_filter.h index af6e20ce86..ec922389c9 100644 --- a/lib/rbcodec/dsp/dsp_filter.h +++ b/lib/rbcodec/dsp/dsp_filter.h @@ -37,12 +37,10 @@ struct dsp_filter }; void filter_shelf_coefs(unsigned long cutoff, long A, bool low, int32_t *c); -#ifdef HAVE_SW_TONE_CONTROLS void filter_bishelf_coefs(unsigned long cutoff_low, unsigned long cutoff_high, long A_low, long A_high, long A, struct dsp_filter *f); -#endif /* HAVE_SW_TONE_CONTROLS */ void filter_pk_coefs(unsigned long cutoff, unsigned long Q, long db, struct dsp_filter *f); void filter_ls_coefs(unsigned long cutoff, unsigned long Q, long db, @@ -53,5 +51,7 @@ void filter_copy(struct dsp_filter *dst, const struct dsp_filter *src); void filter_flush(struct dsp_filter *f); void filter_process(struct dsp_filter *f, int32_t * const buf[], int count, unsigned int channels); - +/* ring buffer */ +void enqueue(int32_t var, int32_t* buffer, int *head, int boundary); +int32_t dequeue(int32_t* buffer, int *head, int boundary); #endif /* DSP_FILTER_H */ diff --git a/lib/rbcodec/dsp/dsp_proc_database.h b/lib/rbcodec/dsp/dsp_proc_database.h index 534c165eef..f30ef0b5c0 100644 --- a/lib/rbcodec/dsp/dsp_proc_database.h +++ b/lib/rbcodec/dsp/dsp_proc_database.h @@ -46,6 +46,9 @@ DSP_PROC_DB_START #ifdef HAVE_SW_TONE_CONTROLS DSP_PROC_DB_ITEM(TONE_CONTROLS) /* bass and treble */ #endif + DSP_PROC_DB_ITEM(PBE) /* perceptual bass enhancement */ + DSP_PROC_DB_ITEM(AFR) /* auditory fatigue reduction */ + DSP_PROC_DB_ITEM(SURROUND) /* haas surround */ DSP_PROC_DB_ITEM(CHANNEL_MODE) /* channel modes */ DSP_PROC_DB_ITEM(COMPRESSOR) /* dynamic-range compressor */ DSP_PROC_DB_STOP diff --git a/lib/rbcodec/dsp/dsp_proc_settings.h b/lib/rbcodec/dsp/dsp_proc_settings.h index 26f603b8b1..8f6e3efab4 100644 --- a/lib/rbcodec/dsp/dsp_proc_settings.h +++ b/lib/rbcodec/dsp/dsp_proc_settings.h @@ -28,6 +28,9 @@ #include "dsp_misc.h" #include "eq.h" #include "pga.h" +#include "surround.h" +#include "afr.h" +#include "pbe.h" #ifdef HAVE_PITCHCONTROL #include "tdspeed.h" #endif diff --git a/lib/rbcodec/dsp/pbe.c b/lib/rbcodec/dsp/pbe.c new file mode 100644 index 0000000000..49665880c0 --- /dev/null +++ b/lib/rbcodec/dsp/pbe.c @@ -0,0 +1,251 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Chiwen Chang + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "fixedpoint.h" +#include "fracmul.h" +#include "settings.h" +#include "dsp_proc_entry.h" +#include "dsp_misc.h" +#include "dsp_filter.h" +#include "core_alloc.h" + +/* Perceptual bass enhancement */ + +#define B3_DLY 72 /* ~800 uS */ +#define B2_DLY 180 /* ~2000 uS*/ +#define B0_DLY 276 /* ~3050 uS */ + +#define B3_SIZE (B3_DLY+1) +#define B2_SIZE (B2_DLY+1) +#define B0_SIZE (B0_DLY+1) + +static int pbe_strength = 100; +static int pbe_precut = 0; +static int32_t tcoef1, tcoef2, tcoef3; +static int32_t *b0[2], *b2[2], *b3[2]; +static int b0_r[2],b2_r[2],b3_r[2],b0_w[2],b2_w[2],b3_w[2]; +int32_t temp_buffer; +static struct dsp_filter pbe_filter[5]; +static int handle = -1; + +static void pbe_buffer_alloc(void) +{ + if (handle > 0) + return; /* already-allocated */ + + unsigned int total_len = (B0_SIZE + B2_SIZE + B3_SIZE) * 2; + handle = core_alloc("dsp_pbe_buffer",sizeof(int32_t) * total_len); + + if (handle < 0) + { + pbe_strength = 0; + return; + } + memset(core_get_data(handle),0,sizeof(int32_t) * total_len); +} + +static void pbe_buffer_get_data(void) +{ + if (handle < 0) + return; + b0[0] = core_get_data(handle); + b0[1] = b0[0] + B0_SIZE; + b2[0] = b0[1] + B0_SIZE; + b2[1] = b2[0] + B2_SIZE; + b3[0] = b2[1] + B2_SIZE; + b3[1] = b3[0] + B3_SIZE; +} + +static void dsp_pbe_flush(void) +{ + if (pbe_strength == 0) + return; /* Not currently enabled */ + + pbe_buffer_get_data(); + + memset(b0[0], 0, B0_DLY * sizeof(int32_t)); + memset(b0[1], 0, B0_DLY * sizeof(int32_t)); + memset(b2[0], 0, B2_DLY * sizeof(int32_t)); + memset(b2[1], 0, B2_DLY * sizeof(int32_t)); + memset(b3[0], 0, B3_DLY * sizeof(int32_t)); + memset(b3[1], 0, B3_DLY * sizeof(int32_t)); + b0_r[0] = 0; b0_w[0] = B0_DLY; + b0_r[1] = 0; b0_w[1] = B0_DLY; + b2_r[0] = 0; b2_w[0] = B2_DLY; + b2_r[1] = 0; b2_w[1] = B2_DLY; + b3_r[0] = 0; b3_w[0] = B3_DLY; + b3_r[1] = 0; b3_w[1] = B3_DLY; + + for (int i = 0; i < 5; i++) + filter_flush(&pbe_filter[i]); +} + +static void pbe_update_filter(unsigned int fout) +{ + tcoef1 = fp_div(160, fout, 31); + tcoef2 = fp_div(500, fout, 31); + tcoef3 = fp_div(1150, fout, 31); + /* Biophonic EQ */ + filter_bishelf_coefs(fp_div(20, fout, 32), + fp_div(16000, fout, 32), + 0, 53, -5 + pbe_precut, + &pbe_filter[0]); + filter_pk_coefs(fp_div(64, fout, 32), 28, 53, + &pbe_filter[1]); + filter_pk_coefs(fp_div(2000, fout, 32), 28, 58, + &pbe_filter[2]); + filter_pk_coefs(fp_div(7500, fout, 32), 43, -82, + &pbe_filter[3]); + filter_pk_coefs(fp_div(10000, fout, 32), 43, -29, + &pbe_filter[4]); + +} + +void dsp_pbe_precut(int var) +{ + if (var == pbe_precut) + return; /* No change */ + + pbe_precut = var; + + if (pbe_strength == 0) + return; /* Not currently enabled */ + + /* Push more DSP_PROC_INIT messages to force filter updates + (with value = 1) */ + struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO); + dsp_proc_enable(dsp, DSP_PROC_PBE, true); +} + + +void dsp_pbe_enable(int var) +{ + if (var == pbe_strength) + return; /* No change */ + bool was_enabled = pbe_strength > 0; + pbe_strength = var; + + bool now_enabled = var > 0; + + if (now_enabled == was_enabled) + return; /* No change in enabled status */ + + if (now_enabled == false && handle > 0) + { + core_free(handle); + handle = -1; + } + + struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO); + dsp_proc_enable(dsp, DSP_PROC_PBE, now_enabled); +} + +static void pbe_process(struct dsp_proc_entry *this, + struct dsp_buffer **buf_p) +{ + struct dsp_buffer *buf = *buf_p; + int count = buf->remcount; + int num_channels = buf->format.num_channels; + int b2_level = (B2_DLY * pbe_strength) / 100; + int b0_level = (B0_DLY * pbe_strength) / 100; + int32_t x; + + pbe_buffer_get_data(); + + for(int ch = 0; ch < num_channels; ch++) + { + for (int i = 0; i < count; i++) + { + /* 160hz - 500hz no delay */ + temp_buffer = FRACMUL(buf->p32[ch][i], tcoef1) - + FRACMUL(buf->p32[ch][i], tcoef2); + + /* delay below 160hz*/ + x = buf->p32[ch][i] - + FRACMUL(buf->p32[ch][i], tcoef1); + temp_buffer += dequeue(b0[ch], &b0_r[ch], b0_level); + enqueue(x, b0[ch], &b0_w[ch], b0_level ); + + /* delay 500-1150hz */ + x = FRACMUL(buf->p32[ch][i], tcoef2) - + FRACMUL(buf->p32[ch][i], tcoef3); + temp_buffer += dequeue(b2[ch], &b2_r[ch], b2_level); + enqueue(x, b2[ch], &b2_w[ch], b2_level ); + + /* delay anything beyond 1150hz */ + x = FRACMUL(buf->p32[ch][i], tcoef3); + temp_buffer += dequeue(b3[ch], &b3_r[ch], B3_DLY); + enqueue(x, b3[ch], &b3_w[ch], B3_DLY ); + + buf->p32[ch][i] = temp_buffer; + } + } + + /* apply Biophonic EQ */ + for (int i = 0; i < 5; i++) + filter_process(&pbe_filter[i], buf->p32, buf->remcount, + buf->format.num_channels); + + (void)this; +} + +/* DSP message hook */ +static intptr_t pbe_configure(struct dsp_proc_entry *this, + struct dsp_config *dsp, + unsigned int setting, + intptr_t value) +{ + /* This only attaches to the audio (codec) DSP */ + + switch (setting) + { + case DSP_PROC_INIT: + if (value == 0) + { + /* Coming online; was disabled */ + this->process = pbe_process; + pbe_buffer_alloc(); + dsp_pbe_flush(); + dsp_proc_activate(dsp, DSP_PROC_PBE, true); + } + /* else additional forced messages */ + + pbe_update_filter(dsp_get_output_frequency(dsp)); + break; + + case DSP_FLUSH: + /* Discontinuity; clear filters */ + dsp_pbe_flush(); + break; + + case DSP_SET_OUT_FREQUENCY: + /* New output frequency */ + pbe_update_filter(value); + break; + } + + return 1; +} + +/* Database entry */ +DSP_PROC_DB_ENTRY( + PBE, + pbe_configure); diff --git a/lib/rbcodec/dsp/pbe.h b/lib/rbcodec/dsp/pbe.h new file mode 100644 index 0000000000..1bcf533a63 --- /dev/null +++ b/lib/rbcodec/dsp/pbe.h @@ -0,0 +1,29 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Chiwen Chang + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef PBE_H +#define PBE_H + +#include +void dsp_pbe_enable(int var); +void dsp_pbe_precut(int var); + +#endif /* PBE_H */ + diff --git a/lib/rbcodec/dsp/surround.c b/lib/rbcodec/dsp/surround.c new file mode 100644 index 0000000000..f420bf4cf5 --- /dev/null +++ b/lib/rbcodec/dsp/surround.c @@ -0,0 +1,333 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Chiwen Chang + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "surround.h" +#include "config.h" +#include "fixedpoint.h" +#include "fracmul.h" +#include "settings.h" +#include "dsp_proc_entry.h" +#include "dsp_filter.h" +#include "core_alloc.h" + +static bool surround_enabled = false; +static int surround_balance = 0; +static bool surround_side_only = false; +static int surround_mix = 100; +static int surround_strength = 0; +/*1 sample ~ 11ns */ +#define DLY_5MS 454 +#define DLY_8MS 727 +#define DLY_10MS 909 +#define DLY_15MS 1363 +#define DLY_30MS 2727 +#define MAX_DLY DLY_30MS + +#define B0_DLY (MAX_DLY/8 + 1) +#define B2_DLY (MAX_DLY + 1) +#define BB_DLY (MAX_DLY/4 + 1) +#define HH_DLY (MAX_DLY/2 + 1) +#define CL_DLY B2_DLY +/*only need to buffer right channel */ +static int32_t *b0, *b2, *bb, *hh, *cl; +static int32_t temp_buffer[2]; +static int32_t mid, side; + +/*voice from 300hz - 3400hz ?*/ +static int32_t tcoef1,tcoef2,bcoef,hcoef; + +static int dly_size = MAX_DLY; +static int cutoff_l = 320; +static int cutoff_h = 3400; + +static int b0_r=0,b0_w=0, + b2_r=0,b2_w=0, + bb_r=0,bb_w=0, + hh_r=0,hh_w=0, + cl_r=0,cl_w=0; +static int handle = -1; + +static void surround_buffer_alloc(void) +{ + if (handle > 0) + return; /* already-allocated */ + + unsigned int total_len = B0_DLY + B2_DLY + BB_DLY + HH_DLY + CL_DLY; + handle = core_alloc("dsp_surround_buffer",sizeof(int32_t) * total_len); + + if (handle < 0) + { + surround_enabled = false; + return; + } + memset(core_get_data(handle),0,sizeof(int32_t) * total_len); +} + +static void surround_buffer_get_data(void) +{ + if (handle < 0) + return; + b0 = core_get_data(handle); + b2 = b0 + B0_DLY; + bb = b2 + B2_DLY; + hh = bb + BB_DLY; + cl = hh + HH_DLY; +} + +static void dsp_surround_flush(void) +{ + if (!surround_enabled) + return; + + surround_buffer_get_data(); + + memset(b0,0,MAX_DLY/8 * sizeof(int32_t)); + memset(b2,0,MAX_DLY * sizeof(int32_t)); + memset(bb,0,MAX_DLY/4 * sizeof(int32_t)); + memset(hh,0,MAX_DLY/2 * sizeof(int32_t)); + memset(cl,0,MAX_DLY * sizeof(int32_t)); + b0_r = 0;b0_w = dly_size/8 - 1; + b2_r = 0;b2_w = dly_size - 1; + bb_r = 0;bb_w = dly_size/4 - 1; + hh_r = 0;hh_w = dly_size/2 - 1; + cl_r = 0;cl_w = dly_size - 1; +} + +static void surround_update_filter(unsigned int fout) +{ + tcoef1 = fp_div(cutoff_l, fout, 31); + tcoef2 = fp_div(cutoff_h, fout, 31); + bcoef = fp_div(cutoff_l / 2, fout, 31); + hcoef = fp_div(cutoff_h * 2, fout, 31); +} + +void dsp_surround_set_balance(int var) +{ + surround_balance = var; +} + +void dsp_surround_side_only(bool var) +{ + dsp_surround_flush(); + surround_side_only = var; +} + +void dsp_surround_mix(int var) +{ + surround_mix = var; +} + +void dsp_surround_set_cutoff(int frq_l, int frq_h) +{ + cutoff_l = frq_l;/*fx2*/ + cutoff_h = frq_h;/*fx1*/ + + struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO); + unsigned int fout = dsp_get_output_frequency(dsp); + surround_update_filter(fout); +} + +static void surround_set_stepsize(int surround_strength) +{ + dsp_surround_flush(); + switch(surround_strength) + { + case 1: + dly_size = DLY_5MS; + break; + case 2: + dly_size = DLY_8MS; + break; + case 3: + dly_size = DLY_10MS; + break; + case 4: + dly_size = DLY_15MS; + break; + case 5: + dly_size = DLY_30MS; + break; + } +} + +void dsp_surround_enable(int var) +{ + if (var == surround_strength) + return; /* No setting change */ + + bool was_enabled = surround_strength > 0; + surround_strength = var; + surround_set_stepsize(surround_strength); + + bool now_enabled = var > 0; + + if (was_enabled == now_enabled && !now_enabled) + return; /* No change in enabled status */ + + if (now_enabled == false && handle > 0) + { + core_free(handle); + handle = -1; + } + + struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO); + dsp_proc_enable(dsp, DSP_PROC_SURROUND, now_enabled); +} + +static void surround_process(struct dsp_proc_entry *this, + struct dsp_buffer **buf_p) +{ + + struct dsp_buffer *buf = *buf_p; + int count = buf->remcount; + + int dly_shift3 = dly_size/8; + int dly_shift2 = dly_size/4; + int dly_shift1 = dly_size/2; + int dly = dly_size; + int i; + int32_t x; + + surround_buffer_get_data(); + + for (i = 0; i < count; i++) + { + mid = buf->p32[0][i] /2 + buf->p32[1][i] /2; + side = buf->p32[0][i] - buf->p32[1][i]; + + if (!surround_side_only) + { + /*clone the left channal*/ + temp_buffer[0]= buf->p32[0][i]; + /*keep the middle band of right channel*/ + temp_buffer[1]= FRACMUL(buf->p32[1][i], tcoef1) - + FRACMUL(buf->p32[1][i], tcoef2); + } + else /* apply haas to side only*/ + { + temp_buffer[0] = side / 2; + temp_buffer[1] = FRACMUL(-side,tcoef1)/2 - + FRACMUL(-side, tcoef2)/2; + } + + /* inverted crossfeed delay (left channel) to make sound wider*/ + x = temp_buffer[1]/100 * 35; + temp_buffer[0] += dequeue(cl, &cl_r, dly); + enqueue(-x, cl, &cl_w, dly); + + /* apply 1/8 delay to frequency below fx2 */ + x = buf->p32[1][i] - FRACMUL(buf->p32[1][i], tcoef1); + temp_buffer[1] += dequeue(b0, &b0_r, dly_shift3); + enqueue(x, b0, &b0_w, dly_shift3 ); + + /* cut frequency below half fx2*/ + temp_buffer[1] = FRACMUL(temp_buffer[1], bcoef); + + /* apply 1/4 delay to frequency below half fx2 */ + /* use different delay to fake the sound direction*/ + x = buf->p32[1][i] - FRACMUL(buf->p32[1][i], bcoef); + temp_buffer[1] += dequeue(bb, &bb_r, dly_shift2); + enqueue(x, bb, &bb_w, dly_shift2 ); + + /* apply full delay to higher band */ + x = FRACMUL(buf->p32[1][i], tcoef2); + temp_buffer[1] += dequeue(b2, &b2_r, dly); + enqueue(x, b2, &b2_w, dly ); + + /* do the same direction trick again */ + temp_buffer[1] -= FRACMUL(temp_buffer[1], hcoef); + + x = FRACMUL(buf->p32[1][i], hcoef); + temp_buffer[1] += dequeue(hh, &hh_r, dly_shift1); + enqueue(x, hh, &hh_w, dly_shift1 ); + /*balance*/ + if (surround_balance > 0 && !surround_side_only) + { + temp_buffer[0] -= temp_buffer[0]/200 * surround_balance; + temp_buffer[1] += temp_buffer[1]/200 * surround_balance; + } + else if (surround_balance > 0) + { + temp_buffer[0] += temp_buffer[0]/200 * surround_balance; + temp_buffer[1] -= temp_buffer[1]/200 * surround_balance; + } + + if (surround_side_only) + { + temp_buffer[0] += mid; + temp_buffer[1] += mid; + } + + if (surround_mix == 100) + { + buf->p32[0][i] = temp_buffer[0]; + buf->p32[1][i] = temp_buffer[1]; + } + else + { + /*dry wet mix*/ + buf->p32[0][i] = buf->p32[0][i]/100 * (100-surround_mix) + + temp_buffer[0]/100 * surround_mix; + buf->p32[1][i] = buf->p32[1][i]/100 * (100-surround_mix) + + temp_buffer[1]/100 * surround_mix; + } + } + (void)this; +} + +/* DSP message hook */ +static intptr_t surround_configure(struct dsp_proc_entry *this, + struct dsp_config *dsp, + unsigned int setting, + intptr_t value) +{ + unsigned int fout = dsp_get_output_frequency(dsp); + switch (setting) + { + case DSP_PROC_INIT: + if (value == 0) + { + this->process = surround_process; + surround_buffer_alloc(); + dsp_surround_flush(); + dsp_proc_activate(dsp, DSP_PROC_SURROUND, true); + } + else + surround_update_filter(fout); + break; + case DSP_FLUSH: + dsp_surround_flush(); + break; + case DSP_SET_OUT_FREQUENCY: + surround_update_filter(value); + break; + case DSP_PROC_CLOSE: + break; + } + + return 1; + (void)dsp; +} + +/* Database entry */ +DSP_PROC_DB_ENTRY( + SURROUND, + surround_configure); + diff --git a/lib/rbcodec/dsp/surround.h b/lib/rbcodec/dsp/surround.h new file mode 100644 index 0000000000..1683367865 --- /dev/null +++ b/lib/rbcodec/dsp/surround.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Chiwen Chang + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef SURROUND_H +#define SURROUND_H + +#include +void dsp_surround_enable(int var); +void dsp_surround_set_balance(int var); +void dsp_surround_set_cutoff(int frq_l, int frq_h); +void dsp_surround_side_only(bool var); +void dsp_surround_mix(int var); + +#endif /* SURROUND_H */ diff --git a/manual/configure_rockbox/sound_settings.tex b/manual/configure_rockbox/sound_settings.tex index 8feacd96cc..3fe7efbade 100644 --- a/manual/configure_rockbox/sound_settings.tex +++ b/manual/configure_rockbox/sound_settings.tex @@ -590,6 +590,33 @@ details about how to use the feature. } } +\opt{swcodec}{ +\section{Haas Surround} +This setting does Haas effect with adjustable delay time to enhance the stereo stage +of the sound. A full range haas effect may sounds like everything starts from left channel ends at right. +Therefore, four additional functions are provided to move the stage back to the center: +First is a \setting{Balance} setting for change left-right channel output ratio. Second +is a bypass band for frequency mostly contain vocals, using \setting{f(x1)}, \setting{f(x2)} to reserved the +frequncy range. The \setting{SIDE ONLY} setting uses mid-side +processing to determine and apply effect to side only. And finally, a \setting{Dry/Wet Mix} +to adjust the proportion of mix between the original (dry) and 'effected' (wet) signals. +} + +\opt{swcodec}{ +\section{Perceptual Bass Enhancement} +This setting dose a group delay correction and an additional Biophonic EQ to +emphasize the clarity of the sounds. The \setting{Precut} setting provides +negative overall gain to prevent possible audio distortion due to the EQ +gain. The defult precut value is set to -2.5 dB and can be adjust from 0 dB to -4.5 dB. +} + +\opt{swcodec}{ +\section{Auditory Fatigue Reduction} +Human hearing are most senstive to some frequency bands, and this setting applies custom presets +of equalization and bi-shelf filter to reduce signals in those bands to minimize +the chance that temporary threshold shift (Auditory Fatigue) occurs. +} + \opt{swcodec}{ \section{Compressor} The \setting{Compressor} reduces, or compresses, the dynamic range of the audio