DSP Compressor:  Sidechain, Exponential Atk/Rls

This is an improvement to the current compressor which I have added
to my own Sansa Fuze V2 build.  I am submitting here in case others
find it interesting.

Features added to the existing compressor:
Attack, Look-ahead, Sidechain Filtering.
Exponential attack and release characteristic response.

Benefits from adding missing features:
Attack:
Preserve perceived "brightness" of tone by letting onset transients
come through at a higher level than the rest of the compressed program
material.

Look-ahead:
With Attack comes clipping on the leading several cycles of a transient
onset.  With look-ahead function, this can be pre-emptively mitigated with
a slower gain change (less distortion).  Look-ahead limiting is implemented
to prevent clipping while keeping gain change ramp to an interval near 3ms
instead of instant attack.

The existing compressor implementation distorts the leading edge of a
transient by causing instant gain change, resulting in log() distortion.
This sounds "woofy" to me.

Exponential Attack/Release:
eMore natural sounding.  On attack, this is a true straight line of 10dB per
attack interval.  Release is a little different, however, sounds natural as
an analog compressor.

Sidechain Filtering:
Mild high-pass filter reduces response to low frequency onsets.  For example,
a hard kick drum is less likely to make the whole of the program material
appear to fade in and out.  Combined with a moderate attack time, such a
transient will ride through with minimal audible artifact.

Overall these changes make dynamic music sound more "open", more natural.  The
goal of a compressor is to make dyanamic music sound louder without necessarily
sounding as though it has been compressed.  I believe these changes come closer to this goal.

Enjoy.  If not, I am enjoying it

Change-Id: I664eace546c364b815b4dc9ed4a72849231a0eb2
Reviewed-on: http://gerrit.rockbox.org/626
Tested: Purling Nayuki <cyq.yzfl@gmail.com>
Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index d0dc5c5..dcad532 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -11911,6 +11911,23 @@
   </voice>
 </phrase>
 <phrase>
+  id: LANG_COMPRESSOR_ATTACK
+  desc: in sound settings
+  user: core
+  <source>
+    *: none
+    swcodec: "Attack Time"
+  </source>
+  <dest>
+    *: none
+    swcodec: "Attack Time"
+  </dest>
+  <voice>
+    *: none
+    swcodec: "Attack Time"
+  </voice>
+</phrase>
+<phrase>
   id: LANG_COMPRESSOR_RELEASE
   desc: in sound settings
   user: core
diff --git a/apps/menus/sound_menu.c b/apps/menus/sound_menu.c
index fd192cb..28cc257 100644
--- a/apps/menus/sound_menu.c
+++ b/apps/menus/sound_menu.c
@@ -140,12 +140,15 @@
     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 6ffb2b5..bd2bfce 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1653,7 +1653,11 @@
     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 b78c449..94e8df2 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -632,6 +632,7 @@
 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 a222cae..685851e 100644
--- a/lib/rbcodec/dsp/compressor.c
+++ b/lib/rbcodec/dsp/compressor.c
@@ -23,44 +23,155 @@
 #include "fracmul.h"
 #include <string.h>
 
-/* 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 @@
         {
             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 @@
         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 @@
     }
 
     /** 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 @@
                 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 @@
         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 @@
                                            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 @@
 
     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 @@
                                      unsigned int setting,
                                      intptr_t value)
 {
+    int i,j;
+
     switch (setting)
     {
     case DSP_PROC_INIT:
@@ -394,7 +575,29 @@
         /* Fall-through */
     case DSP_RESET:
     case DSP_FLUSH:
+
         release_gain = UNITY;
+        for(i=0; i<MAX_CH; i++)
+        {
+            for(j=0; j<MAX_DLY; j++)
+            {
+                labuf[i][j] = 0;  /* All Silence */
+            }
+        }
+
+        /* Delay Line Read/Write Pointers */
+        int32_t fs =   dsp_get_output_frequency(dsp);
+        delay_read = 0;
+        delay_write = (DLY_TIME*fs/1000);
+        if(delay_write >= 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 e419509..35aa0ee 100644
--- a/lib/rbcodec/dsp/compressor.h
+++ b/lib/rbcodec/dsp/compressor.h
@@ -28,6 +28,7 @@
     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 f06bfaf..d2da07b 100644
--- a/manual/configure_rockbox/sound_settings.tex
+++ b/manual/configure_rockbox/sound_settings.tex
@@ -603,6 +603,9 @@
 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