diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index d0dc5c5fc4..dcad532f7a 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -11910,6 +11910,23 @@
swcodec: "Soft Knee"
+
+ id: LANG_COMPRESSOR_ATTACK
+ desc: in sound settings
+ user: core
+
+
+ *: none
+ swcodec: "Attack Time"
+
+
+ *: none
+ swcodec: "Attack Time"
+
+
id: LANG_COMPRESSOR_RELEASE
desc: in sound settings
diff --git a/apps/menus/sound_menu.c b/apps/menus/sound_menu.c
index fd192cb661..28cc257193 100644
--- a/apps/menus/sound_menu.c
+++ b/apps/menus/sound_menu.c
@@ -140,12 +140,15 @@ static int timestretch_callback(int action,const struct menu_item_ex *this_item)
MENUITEM_SETTING(compressor_knee,
&global_settings.compressor_settings.knee,
lowlatency_callback);
+ MENUITEM_SETTING(compressor_attack,
+ &global_settings.compressor_settings.attack_time,
+ lowlatency_callback);
MENUITEM_SETTING(compressor_release,
&global_settings.compressor_settings.release_time,
lowlatency_callback);
MAKE_MENU(compressor_menu,ID2P(LANG_COMPRESSOR), NULL, Icon_NOICON,
&compressor_threshold, &compressor_gain, &compressor_ratio,
- &compressor_knee, &compressor_release);
+ &compressor_knee, &compressor_attack, &compressor_release);
#endif
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 6ffb2b551b..bd2bfce36f 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1653,7 +1653,11 @@ const struct settings_list settings[] = {
CHOICE_SETTING(F_SOUNDSETTING|F_NO_WRAP, compressor_settings.knee,
LANG_COMPRESSOR_KNEE, 1, "compressor knee",
"hard knee,soft knee", compressor_set, 2,
- ID2P(LANG_COMPRESSOR_HARD_KNEE), ID2P(LANG_COMPRESSOR_SOFT_KNEE)),
+ ID2P(LANG_COMPRESSOR_HARD_KNEE), ID2P(LANG_COMPRESSOR_SOFT_KNEE)),
+ INT_SETTING_NOWRAP(F_SOUNDSETTING, compressor_settings.attack_time,
+ LANG_COMPRESSOR_ATTACK, 5,
+ "compressor attack time", UNIT_MS, 0, 30,
+ 5, NULL, NULL, compressor_set),
INT_SETTING_NOWRAP(F_SOUNDSETTING, compressor_settings.release_time,
LANG_COMPRESSOR_RELEASE, 500,
"compressor release time", UNIT_MS, 100, 1000,
diff --git a/docs/CREDITS b/docs/CREDITS
index b78c44904b..94e8df2706 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -632,6 +632,7 @@ Vanja Cvelbar
Richard Quirk
Kirill Stryaponoff
Roman Poltoradnev
+Ryan Billing
The libmad team
The wavpack team
diff --git a/lib/rbcodec/dsp/compressor.c b/lib/rbcodec/dsp/compressor.c
index a222caed7f..685851ec29 100644
--- a/lib/rbcodec/dsp/compressor.c
+++ b/lib/rbcodec/dsp/compressor.c
@@ -23,44 +23,155 @@
#include "fracmul.h"
#include
-/* Define LOGF_ENABLE to enable logf output in this file */
-/*#define LOGF_ENABLE*/
+/* Define LOGF_ENABLE to enable logf output in this file
+ * #define LOGF_ENABLE
+ */
#include "logf.h"
#include "dsp_proc_entry.h"
#include "compressor.h"
#include "dsp_misc.h"
+#define UNITY (1L << 24) /* unity gain in S7.24 format */
+#define MAX_DLY 960 /* Max number of samples to delay
+ output (960 = 5ms @ 192 kHz)
+ */
+#define MAX_CH 4 /* Is there a good malloc() or equal
+ for rockbox?
+ */
+#define DLY_TIME 3 /* milliseconds */
+
static struct compressor_settings curr_set; /* Cached settings */
-static int32_t comp_rel_slope IBSS_ATTR; /* S7.24 format */
-static int32_t comp_makeup_gain IBSS_ATTR; /* S7.24 format */
-static int32_t comp_curve[66] IBSS_ATTR; /* S7.24 format */
-static int32_t release_gain IBSS_ATTR; /* S7.24 format */
+static int32_t comp_makeup_gain IBSS_ATTR; /* S7.24 format */
+static int32_t comp_curve[66] IBSS_ATTR; /* S7.24 format */
+static int32_t release_gain IBSS_ATTR; /* S7.24 format */
+static int32_t release_holdoff IBSS_ATTR; /* S7.24 format */
-#define UNITY (1L << 24) /* unity gain in S7.24 format */
+/* 1-pole filter coefficients for exponential attack/release times */
+static int32_t rlsca IBSS_ATTR; /* Release 'alpha' */
+static int32_t rlscb IBSS_ATTR; /* Release 'beta' */
+
+static int32_t attca IBSS_ATTR; /* Attack 'alpha' */
+static int32_t attcb IBSS_ATTR; /* Attack 'beta' */
+
+static int32_t limitca IBSS_ATTR; /* Limiter Attack 'alpha' */
+
+/* 1-pole filter coefficients for sidechain pre-emphasis filters */
+static int32_t hp1ca IBSS_ATTR; /* hpf1 'alpha' */
+static int32_t hp2ca IBSS_ATTR; /* hpf2 'beta' */
+
+/* 1-pole hp filter state variables for pre-emphasis filters */
+static int32_t hpfx1 IBSS_ATTR; /* hpf1 and hpf2 x[n-1] */
+static int32_t hp1y1 IBSS_ATTR; /* hpf2 y[n-1] */
+static int32_t hp2y1 IBSS_ATTR; /* hpf2 y[n-1] */
+
+/* Delay Line for look-ahead compression */
+static int32_t labuf[MAX_CH][MAX_DLY]; /* look-ahead buffer */
+static int32_t delay_time;
+static int32_t delay_write;
+static int32_t delay_read;
+
+/** 1-Pole LP Filter first coefficient computation
+ * Returns S7.24 format integer used for "a" coefficient
+ * rc: "RC Time Constant", or time to decay to 1/e
+ * fs: Sampling Rate
+ * Interpret attack and release time as an RC time constant
+ * (time to decay to 1/e)
+ * 1-pole filters use approximation
+ * a0 = 1/(fs*rc + 1)
+ * b1 = 1.0 - a0
+ * fs = Sampling Rate
+ * rc = Time to decay to 1/e
+ * y[n] = a0*x[n] + b1*y[n-1]
+ *
+ * According to simulation on Intel hardware
+ * this algorithm produces < 2% error for rc < ~100ms
+ * For rc 100ms - 1000ms, error approaches 0%
+ * For compressor attack/release times, this is more than adequate.
+ *
+ * Error was measured against the more rigorous computation:
+ * a0 = 1.0 - e^(-1.0/(fs*rc))
+ */
+
+int32_t get_lpf_coeff(int32_t rc, int32_t fs, int32_t rc_units)
+{
+ int32_t c = fs*rc;
+ c /= rc_units;
+ c += 1;
+ c = UNITY/c;
+ return c;
+}
+
+/** Coefficients to get 10dB change per time period "rc"
+ * from 1-pole LP filter topology
+ * This function is better used to match behavior of
+ * linear release which was implemented prior to implementation
+ * of exponential attack/release function
+ */
+
+int32_t get_att_rls_coeff(int32_t rc, int32_t fs)
+{
+ int32_t c = UNITY/fs;
+ c *= 1152; /* 1000 * 10/( 20*log10( 1/e ) ) */
+ c /= rc;
+ return c;
+}
/** COMPRESSOR UPDATE
- * Called via the menu system to configure the compressor process */
+ * Called via the menu system to configure the compressor process
+ */
static bool compressor_update(struct dsp_config *dsp,
const struct compressor_settings *settings)
{
/* make settings values useful */
- int threshold = settings->threshold;
- bool auto_gain = settings->makeup_gain == 1;
+ int threshold = settings->threshold;
+ bool auto_gain = settings->makeup_gain == 1;
static const int comp_ratios[] = { 2, 4, 6, 10, 0 };
- int ratio = comp_ratios[settings->ratio];
- bool soft_knee = settings->knee == 1;
- int release = settings->release_time *
- dsp_get_output_frequency(dsp) / 1000;
+ int ratio = comp_ratios[settings->ratio];
+ bool soft_knee = settings->knee == 1;
+ int32_t release = settings->release_time;
+ int32_t attack = settings->attack_time;
- bool changed = settings == &curr_set; /* If frequency change */
+ /* Compute Attack and Release Coefficients */
+ int32_t fs = dsp_get_output_frequency(dsp);
+
+ /* Release */
+ rlsca = get_att_rls_coeff(release, fs);
+ rlscb = UNITY - rlsca ;
+
+ /* Attack */
+ if(attack > 0)
+ {
+ attca = get_att_rls_coeff(attack, fs);
+ attcb = UNITY - attca ;
+ }
+ else {
+ attca = UNITY;
+ attcb = 0;
+ }
+
+
+ /* Sidechain pre-emphasis filter coefficients */
+ hp1ca = fs + 0x003C1; /** The "magic" constant is 1/RC. This filter
+ * cut-off is approximately 237 Hz
+ */
+ hp1ca = UNITY/hp1ca;
+ hp1ca *= fs;
+
+ hp2ca = fs + 0x02065; /* The "magic" constant is 1/RC. This filter
+ * cut-off is approximately 2.18 kHz
+ */
+ hp2ca = UNITY/hp2ca;
+ hp2ca *= fs;
+
+ bool changed = settings == &curr_set; /* If frequency changes */
bool active = threshold < 0;
if (memcmp(settings, &curr_set, sizeof (curr_set)))
{
/* Compressor settings have changed since last call */
changed = true;
-
+
#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE)
if (settings->threshold != curr_set.threshold)
{
@@ -91,6 +202,10 @@ static bool compressor_update(struct dsp_config *dsp,
{
logf(" Compressor Release: %d", release);
}
+ if (settings->attack_time != cur_set.attack_time)
+ {
+ logf(" Compressor Attack: %d", attack);
+ }
#endif
curr_set = *settings;
@@ -125,18 +240,18 @@ static bool compressor_update(struct dsp_config *dsp,
int32_t offset; /* S15.16 format */
} db_curve[5];
- /** Set up the shape of the compression curve first as decibel
- values */
- /* db_curve[0] = bottom of knee
- [1] = threshold
- [2] = top of knee
- [3] = 0 db input
- [4] = ~+12db input (2 bits clipping overhead) */
+ /** Set up the shape of the compression curve first as decibel values
+ * db_curve[0] = bottom of knee
+ * [1] = threshold
+ * [2] = top of knee
+ * [3] = 0 db input
+ * [4] = ~+12db input (2 bits clipping overhead)
+ */
db_curve[1].db = threshold << 16;
if (soft_knee)
{
- /* bottom of knee is 3dB below the threshold for soft knee*/
+ /* bottom of knee is 3dB below the threshold for soft knee */
db_curve[0].db = db_curve[1].db - (3 << 16);
/* top of knee is 3dB above the threshold for soft knee */
db_curve[2].db = db_curve[1].db + (3 << 16);
@@ -175,24 +290,28 @@ static bool compressor_update(struct dsp_config *dsp,
}
/** Now set up the comp_curve table with compression offsets in the
- form of gain factors in S7.24 format */
- /* comp_curve[0] is 0 (-infinity db) input */
+ * form of gain factors in S7.24 format
+ * comp_curve[0] is 0 (-infinity db) input
+ */
comp_curve[0] = UNITY;
- /* comp_curve[1 to 63] are intermediate compression values
- corresponding to the 6 MSB of the input values of a non-clipped
- signal */
+ /** comp_curve[1 to 63] are intermediate compression values
+ * corresponding to the 6 MSB of the input values of a non-clipped
+ * signal
+ */
for (int i = 1; i < 64; i++)
{
- /* db constants are stored as positive numbers;
- make them negative here */
+ /** db constants are stored as positive numbers;
+ * make them negative here
+ */
int32_t this_db = -db[i];
/* no compression below the knee */
if (this_db <= db_curve[0].db)
comp_curve[i] = UNITY;
- /* if soft knee and below top of knee,
- interpolate along soft knee slope */
+ /** if soft knee and below top of knee,
+ * interpolate along soft knee slope
+ */
else if (soft_knee && (this_db <= db_curve[2].db))
comp_curve[i] = fp_factor(fp_mul(
((this_db - db_curve[0].db) / 6),
@@ -204,14 +323,22 @@ static bool compressor_update(struct dsp_config *dsp,
fp_div((db_curve[1].db - this_db), db_curve[1].db, 16),
db_curve[3].offset, 16), 16) << 8;
}
- /* comp_curve[64] is the compression level of a maximum level,
- non-clipped signal */
+ /** comp_curve[64] is the compression level of a maximum level,
+ * non-clipped signal
+ */
comp_curve[64] = fp_factor(db_curve[3].offset, 16) << 8;
- /* comp_curve[65] is the compression level of a maximum level,
- clipped signal */
+ /** comp_curve[65] is the compression level of a maximum level,
+ * clipped signal
+ */
comp_curve[65] = fp_factor(db_curve[4].offset, 16) << 8;
+ /** if using auto peak, then makeup gain is max offset -
+ * 3dB headroom
+ */
+ comp_makeup_gain = auto_gain ?
+ fp_factor(-(db_curve[3].offset) - 0x4AC4, 16) << 8 : UNITY;
+
#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE)
logf("\n *** Compression Offsets ***");
/* some settings for display only, not used in calculations */
@@ -233,20 +360,10 @@ static bool compressor_update(struct dsp_config *dsp,
if (i % 4 == 0) DEBUGF("\n");
}
DEBUGF("\n");
+
+ logf("Makeup gain:\t%.6f", (float)comp_makeup_gain / UNITY);
#endif
- /* if using auto peak, then makeup gain is max offset -
- .1dB headroom */
- comp_makeup_gain = auto_gain ?
- fp_factor(-(db_curve[3].offset) - 0x199A, 16) << 8 : UNITY;
- logf("Makeup gain:\t%.6f", (float)comp_makeup_gain / UNITY);
-
- /* calculate per-sample gain change a rate of 10db over release time
- */
- comp_rel_slope = 0xAF0BB2 / release;
- logf("Release slope:\t%.6f", (float)comp_rel_slope / UNITY);
-
- release_gain = UNITY;
return active;
}
@@ -258,39 +375,41 @@ static inline int32_t get_compression_gain(struct sample_format *format,
int32_t sample)
{
const int frac_bits_offset = format->frac_bits - 15;
-
+
/* sample must be positive */
if (sample < 0)
sample = -(sample + 1);
-
+
/* shift sample into 15 frac bit range */
if (frac_bits_offset > 0)
sample >>= frac_bits_offset;
if (frac_bits_offset < 0)
sample <<= -frac_bits_offset;
-
+
/* normal case: sample isn't clipped */
if (sample < (1 << 15))
{
/* index is 6 MSB, rem is 9 LSB */
int index = sample >> 9;
int32_t rem = (sample & 0x1FF) << 22;
-
- /* interpolate from the compression curve:
- higher gain - ((rem / (1 << 31)) * (higher gain - lower gain)) */
+
+ /** interpolate from the compression curve:
+ * higher gain - ((rem / (1 << 31)) * (higher gain - lower gain))
+ */
return comp_curve[index] - (FRACMUL(rem,
(comp_curve[index] - comp_curve[index + 1])));
}
/* sample is somewhat clipped, up to 2 bits of overhead */
if (sample < (1 << 17))
{
- /* straight interpolation:
- higher gain - ((clipped portion of sample * 4/3
- / (1 << 31)) * (higher gain - lower gain)) */
+ /** straight interpolation:
+ * higher gain - ((clipped portion of sample * 4/3
+ * / (1 << 31)) * (higher gain - lower gain))
+ */
return comp_curve[64] - (FRACMUL(((sample - (1 << 15)) / 3) << 16,
(comp_curve[64] - comp_curve[65])));
}
-
+
/* sample is too clipped, return invalid value */
return -1;
}
@@ -322,55 +441,115 @@ static void compressor_process(struct dsp_proc_entry *this,
while (count-- > 0)
{
- /* use lowest (most compressed) gain factor of the output buffer
- sample pair for both samples (mono is also handled correctly here)
- */
+
+ /* Use the average of the channels */
+
int32_t sample_gain = UNITY;
+ int32_t x = 0;
+ int32_t tmpx = 0;
+ int32_t in_buf_max_level = 0;
for (int ch = 0; ch < num_chan; ch++)
{
- int32_t this_gain = get_compression_gain(&buf->format, *in_buf[ch]);
- if (this_gain < sample_gain)
- sample_gain = this_gain;
+ tmpx = *in_buf[ch];
+ x += tmpx;
+ labuf[ch][delay_write] = tmpx;
+ /* Limiter detection */
+ if(tmpx < 0) tmpx = -(tmpx + 1);
+ if(tmpx > in_buf_max_level) in_buf_max_level = tmpx;
}
-
- /* perform release slope; skip if no compression and no release slope
+
+ /** Divide it by the number of channels, roughly
+ * It will be exact if the number of channels a power of 2
+ * it will be imperfect otherwise. Real division costs too
+ * much here, and most of the time it will be 2 channels (stereo)
*/
- if ((sample_gain != UNITY) || (release_gain != UNITY))
- {
- /* if larger offset than previous slope, start new release slope
- */
- if ((sample_gain <= release_gain) && (sample_gain > 0))
- {
+ x >>= (num_chan >> 1);
+
+ /** 1p HP Filters: y[n] = a*(y[n-1] + x - x[n-1])
+ * Zero and Pole in the same place to reduce computation
+ * Run the first pre-emphasis filter
+ */
+ int32_t tmp1 = x - hpfx1 + hp1y1;
+ hp1y1 = FRACMUL_SHL(hp1ca, tmp1, 7);
+
+ /* Run the second pre-emphasis filter */
+ tmp1 = x - hpfx1 + hp2y1;
+ hp2y1 = FRACMUL_SHL(hp2ca, tmp1, 7);
+ hpfx1 = x;
+
+ /* Apply weighted sum to the pre-emphasis network */
+ sample_gain = (x>>1) + hp1y1 + (hp2y1<<1); /* x/2 + hp1 + 2*hp2 */
+ sample_gain >>= 1;
+ sample_gain += sample_gain >> 1;
+ sample_gain = get_compression_gain(&buf->format, sample_gain);
+
+ /* Exponential Attack and Release */
+
+ if ((sample_gain <= release_gain) && (sample_gain > 0))
+ {
+ /* Attack */
+ if(attca != UNITY)
+ {
+ int32_t this_gain = FRACMUL_SHL(release_gain, attcb, 7);
+ this_gain += FRACMUL_SHL(sample_gain, attca, 7);
+ release_gain = this_gain;
+ }
+ else
+ {
release_gain = sample_gain;
+ }
+ /** reset it to delay time so it cannot release before the
+ * delayed signal releases
+ */
+ release_holdoff = delay_time;
+ }
+ else
+ /* Reverse exponential decay to current gain value */
+ {
+ /* Don't start release while output is still above thresh */
+ if(release_holdoff > 0)
+ {
+ release_holdoff--;
}
else
- /* keep sloping towards unity gain (and ignore invalid value) */
{
- release_gain += comp_rel_slope;
- if (release_gain > UNITY)
- {
- release_gain = UNITY;
- }
+ /* Release */
+ int32_t this_gain = FRACMUL_SHL(release_gain, rlscb, 7);
+ this_gain += FRACMUL_SHL(sample_gain,rlsca,7);
+ release_gain = this_gain;
}
+
+ }
+
+ /** total gain factor is the product of release gain and makeup gain,
+ * but avoid computation if possible
+ */
+
+ int32_t total_gain = FRACMUL_SHL(release_gain, comp_makeup_gain, 7);
+
+ /* Look-ahead limiter */
+ int32_t test_gain = FRACMUL_SHL(total_gain, in_buf_max_level, 3);
+ if( test_gain > UNITY)
+ {
+ release_gain -= limitca;
}
-
- /* total gain factor is the product of release gain and makeup gain,
- but avoid computation if possible */
- int32_t total_gain = ((release_gain == UNITY) ? comp_makeup_gain :
- (comp_makeup_gain == UNITY) ? release_gain :
- FRACMUL_SHL(release_gain, comp_makeup_gain, 7));
-
- /* Implement the compressor: apply total gain factor (if any) to the
- output buffer sample pair/mono sample */
+
+ /** Implement the compressor: apply total gain factor (if any) to the
+ * output buffer sample pair/mono sample
+ */
if (total_gain != UNITY)
{
for (int ch = 0; ch < num_chan; ch++)
{
- *in_buf[ch] = FRACMUL_SHL(total_gain, *in_buf[ch], 7);
+ *in_buf[ch] = FRACMUL_SHL(total_gain, labuf[ch][delay_read], 7);
}
}
in_buf[0]++;
in_buf[1]++;
+ delay_write++;
+ delay_read++;
+ if(delay_write >= MAX_DLY) delay_write = 0;
+ if(delay_read >= MAX_DLY) delay_read = 0;
}
(void)this;
@@ -382,6 +561,8 @@ static intptr_t compressor_configure(struct dsp_proc_entry *this,
unsigned int setting,
intptr_t value)
{
+ int i,j;
+
switch (setting)
{
case DSP_PROC_INIT:
@@ -394,7 +575,29 @@ static intptr_t compressor_configure(struct dsp_proc_entry *this,
/* Fall-through */
case DSP_RESET:
case DSP_FLUSH:
+
release_gain = UNITY;
+ for(i=0; i= MAX_DLY) {
+ delay_write = MAX_DLY - 1; /* Limit to the max allocated buffer */
+ }
+
+ delay_time = delay_write;
+ release_holdoff = delay_write;
+ limitca = get_att_rls_coeff(DLY_TIME, fs); /** Attack time for
+ * look-ahead limiter
+ */
break;
case DSP_SET_OUT_FREQUENCY:
diff --git a/lib/rbcodec/dsp/compressor.h b/lib/rbcodec/dsp/compressor.h
index e41950926e..35aa0eeb65 100644
--- a/lib/rbcodec/dsp/compressor.h
+++ b/lib/rbcodec/dsp/compressor.h
@@ -28,6 +28,7 @@ struct compressor_settings
int ratio;
int knee;
int release_time;
+ int attack_time;
};
void dsp_set_compressor(const struct compressor_settings *settings);
diff --git a/manual/configure_rockbox/sound_settings.tex b/manual/configure_rockbox/sound_settings.tex
index f06bfaf6e5..d2da07b983 100644
--- a/manual/configure_rockbox/sound_settings.tex
+++ b/manual/configure_rockbox/sound_settings.tex
@@ -603,6 +603,9 @@ non-compressed signal to a compressed signal. Hard Knee means that the
transition occurs precisely at the threshold. The Soft Knee setting smoothes
the transition from plus or minus three decibels around the threshold.
+The \setting{Attack Time} setting sets the delay in milliseconds between the
+input signal exceeding the activation threshold and acting upon it.
+
The \setting{Release Time} setting sets the recovery time after the signal is
compressed. Once the compressor determines that compression is necessary,
the input signal is reduced appropriately, but the gain isn't allowed to