Replace limiter with dynamic range compressor

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@22832 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Jeffrey Goode 2009-09-25 15:46:38 +00:00
parent b9a17dd0ce
commit cf19ba5599
21 changed files with 577 additions and 507 deletions

View file

@ -137,6 +137,15 @@ struct eq_state
/* 10ch */ /* 10ch */
}; };
struct compressor_menu
{
int threshold; /* dB - from menu */
int ratio; /* from menu */
int gain; /* dB - from menu */
bool soft_knee; /* 0 = hard knee, 1 = soft knee */
int release; /* samples - from menu */
};
/* Include header with defines which functions are implemented in assembly /* Include header with defines which functions are implemented in assembly
code for the target */ code for the target */
#include <dsp_asm.h> #include <dsp_asm.h>
@ -171,7 +180,6 @@ struct dsp_config
int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */ int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */
bool tdspeed_active; /* Timestretch is in use */ bool tdspeed_active; /* Timestretch is in use */
int frac_bits; int frac_bits;
long limiter_preamp; /* limiter amp gain in S7.24 format */
#ifdef HAVE_SW_TONE_CONTROLS #ifdef HAVE_SW_TONE_CONTROLS
/* Filter struct for software bass/treble controls */ /* Filter struct for software bass/treble controls */
struct eqfilter tone_filter; struct eqfilter tone_filter;
@ -187,7 +195,7 @@ struct dsp_config
channels_process_fn_type apply_crossfeed; channels_process_fn_type apply_crossfeed;
channels_process_fn_type eq_process; channels_process_fn_type eq_process;
channels_process_fn_type channels_process; channels_process_fn_type channels_process;
return_fn_type limiter_process; return_fn_type compressor_process;
}; };
/* General DSP config */ /* General DSP config */
@ -253,58 +261,17 @@ static int32_t *resample_buf;
#define RESAMPLE_BUF_LEFT_CHANNEL 0 #define RESAMPLE_BUF_LEFT_CHANNEL 0
#define RESAMPLE_BUF_RIGHT_CHANNEL (sample_buf_count/2 * RESAMPLE_RATIO) #define RESAMPLE_BUF_RIGHT_CHANNEL (sample_buf_count/2 * RESAMPLE_RATIO)
/* limiter */ /* compressor */
/* MAX_COUNT is largest possible sample count in limiter_process. This is /* MAX_COUNT is largest possible sample count in compressor_process */
needed in case time stretch makes the count in dsp_process larger than #define MAX_COUNT (SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO / 2)
the limiter buffer. */ static struct compressor_menu c_menu;
#define MAX_COUNT MAX(SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO / 2, LIMITER_BUFFER_SIZE) static int32_t comp_rel_slope IBSS_ATTR; /* S7.24 format */
static int count_adjust; static int32_t comp_makeup_gain IBSS_ATTR; /* S7.24 format */
static bool limiter_buffer_active; static int32_t comp_curve[65] IBSS_ATTR; /* S7.24 format */
static bool limiter_buffer_full; static int32_t gain_buffer[MAX_COUNT] IBSS_ATTR;
static bool limiter_buffer_emptying; static int32_t release_gain IBSS_ATTR;
static int32_t limiter_buffer[2][LIMITER_BUFFER_SIZE] IBSS_ATTR;
static int32_t *start_lim_buf[2] IBSS_ATTR,
*end_lim_buf[2] IBSS_ATTR;
static uint16_t lim_buf_peak[LIMITER_BUFFER_SIZE] IBSS_ATTR;
static uint16_t *start_peak IBSS_ATTR,
*end_peak IBSS_ATTR;
static uint16_t out_buf_peak[MAX_COUNT];
static uint16_t *out_buf_peak_index IBSS_ATTR;
static uint16_t release_peak IBSS_ATTR;
static int32_t in_samp IBSS_ATTR,
samp0 IBSS_ATTR;
static void reset_limiter_buffer(struct dsp_config *dsp); static int compressor_process(int count, int32_t *buf[]);
static int limiter_buffer_count(bool buf_count);
static int limiter_process(int count, int32_t *buf[]);
static uint16_t get_peak_value(int32_t sample);
/* The clip_steps array essentially stores the results of fp_factor from
* 0 to 12 dB, in 48 equal steps, in S3.28 format. */
const long clip_steps[49] ICONST_ATTR = { 0x10000000,
0x10779AFA, 0x10F2B409, 0x1171654C, 0x11F3C9A0, 0x1279FCAD,
0x13041AE9, 0x139241A2, 0x14248EF9, 0x14BB21F9, 0x15561A92,
0x15F599A0, 0x1699C0F9, 0x1742B36B, 0x17F094CE, 0x18A38A01,
0x195BB8F9, 0x1A1948C5, 0x1ADC619B, 0x1BA52CDC, 0x1C73D51D,
0x1D488632, 0x1E236D3A, 0x1F04B8A1, 0x1FEC982C, 0x20DB3D0E,
0x21D0D9E2, 0x22CDA2BE, 0x23D1CD41, 0x24DD9099, 0x25F12590,
0x270CC693, 0x2830AFD3, 0x295D1F37, 0x2A925471, 0x2BD0911F,
0x2D1818B3, 0x2E6930AD, 0x2FC42095, 0x312931EC, 0x3298B072,
0x3412EA24, 0x35982F3A, 0x3728D22E, 0x38C52808, 0x3A6D8847,
0x3C224CD9, 0x3DE3D264, 0x3FB2783F};
/* The gain_steps array essentially stores the results of fp_factor from
* 0 to -12 dB, in 48 equal steps, in S3.28 format. */
const long gain_steps[49] ICONST_ATTR = { 0x10000000,
0xF8BC9C0, 0xF1ADF94, 0xEAD2988, 0xE429058, 0xDDAFD68,
0xD765AC1, 0xD149309, 0xCB59186, 0xC594210, 0xBFF9112,
0xBA86B88, 0xB53BEF5, 0xB017965, 0xAB18964, 0xA63DDFE,
0xA1866BA, 0x9CF1397, 0x987D507, 0x9429BEE, 0x8FF599E,
0x8BDFFD3, 0x87E80B0, 0x840CEBE, 0x804DCE8, 0x7CA9E76,
0x792070E, 0x75B0AB0, 0x7259DB2, 0x6F1B4BF, 0x6BF44D5,
0x68E4342, 0x65EA5A0, 0x63061D6, 0x6036E15, 0x5D7C0D3,
0x5AD50CE, 0x5841505, 0x55C04B8, 0x535176A, 0x50F44D9,
0x4EA84FE, 0x4C6D00E, 0x4A41E78, 0x48268DF, 0x461A81C,
0x441D53E, 0x422E985, 0x404DE62};
/* Clip sample to signed 16 bit range */ /* Clip sample to signed 16 bit range */
@ -944,13 +911,6 @@ static void set_gain(struct dsp_config *dsp)
dsp->data.gain = fp_mul(dsp->data.gain, eq_precut, 24); dsp->data.gain = fp_mul(dsp->data.gain, eq_precut, 24);
} }
/* only preamp for the limiter if limiter is active and sample depth
* allows safe pre-amping (12 dB is OK with 29 or less frac bits) */
if ((dsp->limiter_preamp) && (dsp->frac_bits <= 29))
{
dsp->data.gain = fp_mul(dsp->data.gain, dsp->limiter_preamp, 24);
}
#ifdef HAVE_SW_VOLUME_CONTROL #ifdef HAVE_SW_VOLUME_CONTROL
if (global_settings.volume < SW_VOLUME_MAX || if (global_settings.volume < SW_VOLUME_MAX ||
global_settings.volume > SW_VOLUME_MIN) global_settings.volume > SW_VOLUME_MIN)
@ -1308,8 +1268,8 @@ int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count)
if (dsp->channels_process) if (dsp->channels_process)
dsp->channels_process(chunk, t2); dsp->channels_process(chunk, t2);
if (dsp->limiter_process) if (dsp->compressor_process)
chunk = dsp->limiter_process(chunk, t2); chunk = dsp->compressor_process(chunk, t2);
dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst); dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst);
@ -1358,15 +1318,6 @@ int dsp_output_count(struct dsp_config *dsp, int count)
if (count > RESAMPLE_BUF_RIGHT_CHANNEL) if (count > RESAMPLE_BUF_RIGHT_CHANNEL)
count = RESAMPLE_BUF_RIGHT_CHANNEL; count = RESAMPLE_BUF_RIGHT_CHANNEL;
/* If the limiter buffer is filling, some or all samples will
* be captured by it, so expect fewer samples coming out. */
if (limiter_buffer_active && !limiter_buffer_full)
{
int empty_space = limiter_buffer_count(false);
count_adjust = MIN(empty_space, count);
count -= count_adjust;
}
return count; return count;
} }
@ -1375,13 +1326,6 @@ int dsp_output_count(struct dsp_config *dsp, int count)
*/ */
int dsp_input_count(struct dsp_config *dsp, int count) int dsp_input_count(struct dsp_config *dsp, int count)
{ {
/* If the limiter buffer is filling, the output count was
* adjusted downward. This adjusts it back so that input
* count is not affected.
*/
if (limiter_buffer_active && !limiter_buffer_full)
count += count_adjust;
/* count is now the number of resampled input samples. Convert to /* count is now the number of resampled input samples. Convert to
original input samples. */ original input samples. */
if (dsp->resample) if (dsp->resample)
@ -1499,7 +1443,8 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
dsp_update_functions(dsp); dsp_update_functions(dsp);
resampler_new_delta(dsp); resampler_new_delta(dsp);
tdspeed_setup(dsp); tdspeed_setup(dsp);
reset_limiter_buffer(dsp); if (dsp == &AUDIO_DSP)
release_gain = (1 << 24);
break; break;
case DSP_FLUSH: case DSP_FLUSH:
@ -1508,7 +1453,8 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
resampler_new_delta(dsp); resampler_new_delta(dsp);
dither_init(dsp); dither_init(dsp);
tdspeed_setup(dsp); tdspeed_setup(dsp);
reset_limiter_buffer(dsp); if (dsp == &AUDIO_DSP)
release_gain = (1 << 24);
break; break;
case DSP_SET_TRACK_GAIN: case DSP_SET_TRACK_GAIN:
@ -1588,368 +1534,256 @@ void dsp_set_replaygain(void)
set_gain(&AUDIO_DSP); set_gain(&AUDIO_DSP);
} }
/** RESET THE LIMITER BUFFER /** SET COMPRESSOR
* Force the limiter buffer to its initial state and discard * Called by the menu system to configure the compressor process */
* any samples held there. */ void dsp_set_compressor(int c_threshold, int c_ratio, int c_gain,
static void reset_limiter_buffer(struct dsp_config *dsp) int c_knee, int c_release)
{ {
if (dsp == &AUDIO_DSP) bool changed = false;
bool active = (c_threshold < 0);
const int comp_ratio[] = {2, 4, 6, 10, 0};
int new_ratio = comp_ratio[c_ratio];
bool new_knee = (c_knee == 1);
int new_release = c_release * NATIVE_FREQUENCY / 1000;
if (c_menu.threshold != c_threshold)
{ {
int i; changed = true;
logf(" reset_limiter_buffer"); c_menu.threshold = c_threshold;
for (i = 0; i < 2; i++) logf(" Compressor Threshold: %d dB\tEnabled: %s",
start_lim_buf[i] = end_lim_buf[i] = limiter_buffer[i]; c_menu.threshold, active ? "Yes" : "No");
start_peak = end_peak = lim_buf_peak;
limiter_buffer_full = false;
limiter_buffer_emptying = false;
release_peak = 0;
}
} }
/** OPERATE THE LIMITER BUFFER if (c_menu.ratio != new_ratio)
* Handle all samples entering or exiting the limiter buffer. */
static inline int set_limiter_buffer(int count, int32_t *buf[])
{ {
int32_t *in_buf[] = {buf[0], buf[1]}, changed = true;
*out_buf[] = {buf[0], buf[1]}; c_menu.ratio = new_ratio;
int empty_space, i, out_count; if (c_menu.ratio)
const long clip_max = AUDIO_DSP.data.clip_max; logf(" Compressor Ratio: %d:1", c_menu.ratio);
const int ch = AUDIO_DSP.data.num_channels - 1;
out_buf_peak_index = out_buf_peak;
if (limiter_buffer_emptying)
/** EMPTY THE BUFFER
* since the empty flag has been set, assume no inbound samples and
return all samples in the limiter buffer to the outbound buffer */
{
count = limiter_buffer_count(true);
out_count = count;
logf(" Emptying limiter buffer: %d", count);
while (count-- > 0)
{
for (i = 0; i <= ch; i++)
{
/* move samples in limiter buffer to output buffer */
*out_buf[i]++ = *start_lim_buf[i]++;
if (start_lim_buf[i] == &limiter_buffer[i][LIMITER_BUFFER_SIZE])
start_lim_buf[i] = limiter_buffer[i];
/* move limiter buffer peak values to output peak values */
if (i == 0)
{
*out_buf_peak_index++ = *start_peak++;
if (start_peak == &lim_buf_peak[LIMITER_BUFFER_SIZE])
start_peak = lim_buf_peak;
}
}
}
limiter_buffer_full = false;
limiter_buffer_emptying = false;
}
else /* limiter buffer NOT emptying */
{
if (count <= 0) return 0;
empty_space = limiter_buffer_count(false);
if (empty_space > 0)
/** FILL BUFFER
* use as many inbound samples as necessary to fill the buffer */
{
/* don't try to fill with more samples than available */
if (empty_space > count)
empty_space = count;
logf(" Filling limiter buffer: %d", empty_space);
while (empty_space-- > 0)
{
for (i = 0; i <= ch; i++)
{
/* put inbound samples in the limiter buffer */
in_samp = *in_buf[i]++;
*end_lim_buf[i]++ = in_samp;
if (end_lim_buf[i] == &limiter_buffer[i][LIMITER_BUFFER_SIZE])
end_lim_buf[i] = limiter_buffer[i];
if (in_samp < 0) /* make positive for comparison */
in_samp = -in_samp - 1;
if (in_samp <= clip_max)
in_samp = 0; /* disregard if not clipped */
if (i == 0)
samp0 = in_samp;
if (i == ch)
{
/* assign peak value for each inbound sample pair */
*end_peak++ = ((samp0 > 0) || (in_samp > 0)) ?
get_peak_value(MAX(samp0, in_samp)) : 0;
if (end_peak == &lim_buf_peak[LIMITER_BUFFER_SIZE])
end_peak = lim_buf_peak;
}
}
count--;
}
/* after buffer fills, the remaining inbound samples are cycled */
}
limiter_buffer_full = (end_lim_buf[0] == start_lim_buf[0]);
out_count = count;
/** CYCLE BUFFER
* return buffered samples and backfill limiter buffer with new ones.
* The buffer is always full when cycling. */
while (count-- > 0)
{
for (i = 0; i <= ch; i++)
{
/* copy incoming sample */
in_samp = *in_buf[i]++;
/* put limiter buffer sample into outbound buffer */
*out_buf[i]++ = *start_lim_buf[i]++;
/* put incoming sample on the end of the limiter buffer */
*end_lim_buf[i]++ = in_samp;
/* ring buffer pointer wrap */
if (start_lim_buf[i] == &limiter_buffer[i][LIMITER_BUFFER_SIZE])
start_lim_buf[i] = limiter_buffer[i];
if (end_lim_buf[i] == &limiter_buffer[i][LIMITER_BUFFER_SIZE])
end_lim_buf[i] = limiter_buffer[i];
if (in_samp < 0) /* make positive for comparison */
in_samp = -in_samp - 1;
if (in_samp <= clip_max)
in_samp = 0; /* disregard if not clipped */
if (i == 0)
{
samp0 = in_samp;
/* assign outgoing sample its associated peak value */
*out_buf_peak_index++ = *start_peak++;
if (start_peak == &lim_buf_peak[LIMITER_BUFFER_SIZE])
start_peak = lim_buf_peak;
}
if (i == ch)
{
/* assign peak value for each inbound sample pair */
*end_peak++ = ((samp0 > 0) || (in_samp > 0)) ?
get_peak_value(MAX(samp0, in_samp)) : 0;
if (end_peak == &lim_buf_peak[LIMITER_BUFFER_SIZE])
end_peak = lim_buf_peak;
}
}
}
}
return out_count;
}
/** RETURN LIMITER BUFFER COUNT
* If argument is true, returns number of samples in the buffer,
* otherwise, returns empty space remaining */
static int limiter_buffer_count(bool buf_count)
{
int count;
if (limiter_buffer_full)
count = LIMITER_BUFFER_SIZE;
else if (end_lim_buf[0] >= start_lim_buf[0])
count = (end_lim_buf[0] - start_lim_buf[0]);
else else
count = (end_lim_buf[0] - start_lim_buf[0]) + LIMITER_BUFFER_SIZE; logf(" Compressor Ratio: Limit");
return buf_count ? count : (LIMITER_BUFFER_SIZE - count);
} }
/** FLUSH THE LIMITER BUFFER if (c_menu.gain != c_gain)
* Empties the limiter buffer into the buffer pointed to by the argument
* and returns the number of samples in that buffer */
int dsp_flush_limiter_buffer(char *dest)
{ {
if ((!limiter_buffer_active) || (limiter_buffer_count(true) <= 0)) changed = true;
return 0; c_menu.gain = c_gain;
if (c_menu.gain >= 0)
logf(" dsp_flush_limiter_buffer"); logf(" Compressor Makeup Gain: %d dB", c_menu.gain);
int32_t flush_buf[2][LIMITER_BUFFER_SIZE]; else
int32_t *src[2] = {flush_buf[0], flush_buf[1]}; logf(" Compressor Makeup Gain: Auto");
limiter_buffer_emptying = true;
int count = limiter_process(0, src);
AUDIO_DSP.output_samples(count, &AUDIO_DSP.data,
(const int32_t **)src, (int16_t *)dest);
return count;
} }
/** GET PEAK VALUE if (c_menu.soft_knee != new_knee)
* Return a small value representing how much the sample is clipped. This {
* should only be called if a sample is actually clipped. Sample is a changed = true;
* positive value. c_menu.soft_knee = new_knee;
logf(" Compressor Knee: %s", c_menu.soft_knee==1?"Soft":"Hard");
}
if (c_menu.release != new_release)
{
changed = true;
c_menu.release = new_release;
logf(" Compressor Release: %d", c_menu.release);
}
if (changed && active)
{
/* configure variables for compressor operation */
int i;
const int32_t db[] ={0x000000, /* positive db equivalents in S15.16 format */
0x241FA4, 0x1E1A5E, 0x1A94C8, 0x181518, 0x1624EA, 0x148F82, 0x1338BD, 0x120FD2,
0x1109EB, 0x101FA4, 0x0F4BB6, 0x0E8A3C, 0x0DD840, 0x0D3377, 0x0C9A0E, 0x0C0A8C,
0x0B83BE, 0x0B04A5, 0x0A8C6C, 0x0A1A5E, 0x09ADE1, 0x094670, 0x08E398, 0x0884F6,
0x082A30, 0x07D2FA, 0x077F0F, 0x072E31, 0x06E02A, 0x0694C8, 0x064BDF, 0x060546,
0x05C0DA, 0x057E78, 0x053E03, 0x04FF5F, 0x04C273, 0x048726, 0x044D64, 0x041518,
0x03DE30, 0x03A89B, 0x037448, 0x03412A, 0x030F32, 0x02DE52, 0x02AE80, 0x027FB0,
0x0251D6, 0x0224EA, 0x01F8E2, 0x01CDB4, 0x01A359, 0x0179C9, 0x0150FC, 0x0128EB,
0x010190, 0x00DAE4, 0x00B4E1, 0x008F82, 0x006AC1, 0x004699, 0x002305};
struct curve_point
{
int32_t db; /* S15.16 format */
int32_t offset; /* S15.16 format */
} db_curve[4];
/** 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 */
db_curve[1].db = c_menu.threshold << 16;
db_curve[1].offset = 0;
if (c_menu.soft_knee)
{
/* bottom of knee is 3dB below the threshold for soft knee*/
db_curve[0].db = db_curve[1].db - (3 << 16);
db_curve[0].offset = 0;
/* top of knee is 3dB above the threshold for soft knee */
db_curve[2].db = db_curve[1].db + (3 << 16);
if (c_menu.ratio)
/* offset = -3db * (ratio - 1) / ratio */
db_curve[2].offset = (int32_t)((long long)(-3 << 16)
* (c_menu.ratio - 1) / c_menu.ratio);
else
/* offset = -3db for hard limit */
db_curve[2].offset = (-3 << 16);
}
else
{
/* bottom of knee is at the threshold for hard knee */
db_curve[0].db = c_menu.threshold << 16;
db_curve[0].offset = 0;
/* top of knee is at the threshold for hard knee */
db_curve[2].db = c_menu.threshold << 16;
db_curve[2].offset = 0;
}
/* 0db input is also max offset point (most compression) */
db_curve[3].db = 0;
if (c_menu.ratio)
/* offset = threshold * (ratio - 1) / ratio */
db_curve[3].offset = (int32_t)((long long)(c_menu.threshold << 16)
* (c_menu.ratio - 1) / c_menu.ratio);
else
/* offset = threshold for hard limit */
db_curve[3].offset = (c_menu.threshold << 16);
/* Now set up the comp_curve table with compression offsets in the form
of gain factors in S7.24 format */
comp_curve[0] = (1 << 24);
for (i = 1; i < 64; i++)
{
int32_t this_db = -db[i];
/* no compression below the knee */
if (this_db <= db_curve[0].db)
comp_curve[i] = (1 << 24);
/* if soft knee and below top of knee, interpolate along soft knee slope */
else if (c_menu.soft_knee && (this_db <= db_curve[2].db))
comp_curve[i] = fp_factor(fp_mul(((this_db - db_curve[0].db) / 6),
db_curve[2].offset, 16), 16) << 8;
/* interpolate along ratio slope above the knee */
else
comp_curve[i] = fp_factor(fp_mul(fp_div((this_db - db_curve[1].db),
-db_curve[1].db, 16), db_curve[3].offset, 16), 16) << 8;
}
comp_curve[64] = fp_factor(db_curve[3].offset, 16) << 8;
logf("\n *** Compression Offsets ***");
for (i = 0; i <= 3; i++)
{
logf("Curve[%d]: db: % .1f\toffset: % .4f", i, (float)db_curve[i].db / (1 << 16),
(float)db_curve[i].offset / (1 << 16));
}
logf("\nGain factors:");
for (i = 1; i <= 64; i++)
{
debugf("%02d: %.6f ", i, (float)comp_curve[i] / (1 << 24));
if (i % 4 == 0) debugf("\n");
}
/* if using auto peak, then makeup gain is max offset - .1dB headroom */
int32_t db_makeup = (c_menu.gain == -1) ?
-(db_curve[3].offset) - 0x199A : c_menu.gain << 16;
comp_makeup_gain = fp_factor(db_makeup, 16) << 8;
logf("Makeup gain:\t%.6f", (float)comp_makeup_gain / (1 << 24));
/* calculate per-sample gain change a rate of 10db over release time */
comp_rel_slope = 0xAF0BB2 / c_menu.release;
logf("Release slope:\t%.6f", (float)comp_rel_slope / (1 << 24));
release_gain = (1 << 24);
}
/* enable/disable the compressor */
AUDIO_DSP.compressor_process = active ? compressor_process : NULL;
}
/** GET COMPRESSION GAIN
* Returns the required gain factor in S7.24 format in order to compress the
* sample in accordance with the compression curve. Always 1 or less.
*/ */
static uint16_t get_peak_value(int32_t sample) static inline int32_t get_compression_gain(int32_t sample)
{ {
const int frac_bits = AUDIO_DSP.frac_bits; const int frac_bits = AUDIO_DSP.frac_bits;
int mid,
hi = 48,
lo = 0;
/* shift sample into 28 frac bit range for comparison */ /* sample must be positive */
if (frac_bits > 28) if (sample < 0)
sample >>= (frac_bits - 28); sample = -sample - 1;
if (frac_bits < 28)
sample <<= (28 - frac_bits);
/* if clipped out of range, return maximum value */ /* shift sample into 22 frac bit range */
if (sample >= clip_steps[48]) if (frac_bits > 22)
return 48 * 90; sample >>= (frac_bits - 22);
if (frac_bits < 22)
sample <<= (22 - frac_bits);
/* find amount of sample clipping on the table */ /* index is 6 MSB, rem is 16 LSB */
do int index = sample >> 16;
{ int rem = (sample & 0xFFFF) << 8;
mid = (hi + lo) / 2;
if (sample < clip_steps[mid])
hi = mid;
else if (sample > clip_steps[mid])
lo = mid;
else
return mid * 90;
}
while (hi > (lo + 1));
/* interpolate linearly between steps (less accurate but faster) */ /* interpolate from the compression curve */
return ((hi-1) * 90) + (((sample - clip_steps[hi-1]) * 90) / return comp_curve[index] + (int32_t)FRACMUL_SHL((comp_curve[index + 1]
(clip_steps[hi] - clip_steps[hi-1])); - comp_curve[index]), rem, 7);
} }
/** SET LIMITER /** COMPRESSOR PROCESS
* Called by the menu system to configure the limiter process */ * Changes the gain of the samples according to the compressor curve
void dsp_set_limiter(int limiter_level)
{
if (limiter_level > 0)
{
if (!limiter_buffer_active)
{
/* enable limiter process */
AUDIO_DSP.limiter_process = limiter_process;
limiter_buffer_active = true;
}
/* limiter preamp is a gain factor in S7.24 format */
long old_preamp = AUDIO_DSP.limiter_preamp;
long new_preamp = fp_factor((((long)limiter_level << 24) / 10), 24);
if (old_preamp != new_preamp)
{
AUDIO_DSP.limiter_preamp = new_preamp;
set_gain(&AUDIO_DSP);
logf(" Limiter enable: Yes\tLimiter amp: %.8f",
(float)AUDIO_DSP.limiter_preamp / (1 << 24));
}
}
else
{
/* disable limiter process*/
if (limiter_buffer_active)
{
AUDIO_DSP.limiter_preamp = (1 << 24);
set_gain(&AUDIO_DSP);
/* pcmbuf_flush_limiter_buffer(); */
limiter_buffer_active = false;
AUDIO_DSP.limiter_process = NULL;
reset_limiter_buffer(&AUDIO_DSP);
logf(" Limiter enable: No\tLimiter amp: %.8f",
(float)AUDIO_DSP.limiter_preamp / (1 << 24));
}
}
}
/** LIMITER PROCESS
* Checks pre-amplified signal for clipped samples and smoothly reduces gain
* around the clipped samples using a preset attack/release schedule.
*/ */
static int limiter_process(int count, int32_t *buf[]) static int compressor_process(int count, int32_t *buf[])
{ {
/* Limiter process passes through if limiter buffer isn't active, or the const int num_chan = AUDIO_DSP.data.num_channels;
* sample depth is too large for safe pre-amping */ const int32_t fp_one = (1 << 24);
if ((!limiter_buffer_active) || (AUDIO_DSP.frac_bits > 29))
return count;
count = set_limiter_buffer(count, buf);
if (count <= 0)
return 0;
const int attack_slope = 15; /* 15:1 ratio between attack and release */
const int buffer_count = limiter_buffer_count(true);
int32_t sample_gain, /* S7.24 format */
this_gain; /* S7.24 format */
int i, ch; int i, ch;
uint16_t max_peak = 0,
gain_peak,
gain_rem;
long gain;
/* step through limiter buffer in reverse order, in order to find the /* Step forward through the output buffer, and modify the offset values
* appropriate max_peak for modifying the output buffer */
for (i = buffer_count - 1; i >= 0; i--)
{
const uint16_t peak_i = lim_buf_peak[(start_peak - lim_buf_peak + i) %
LIMITER_BUFFER_SIZE];
/* if no attack slope, nothing to do */
if ((peak_i == 0) && (max_peak == 0)) continue;
/* if new peak, start attack slope */
if (peak_i >= max_peak)
{
max_peak = peak_i;
}
/* keep sloping */
else
{
if (max_peak > attack_slope)
max_peak -= attack_slope;
else
max_peak = 0;
}
}
/* step through output buffer the same way, but this time modifying peak
* values to create a smooth attack slope. */
for (i = count - 1; i >= 0; i--)
{
/* if no attack slope, nothing to do */
if ((out_buf_peak[i] == 0) && (max_peak == 0)) continue;
/* if new peak, start attack slope */
if (out_buf_peak[i] >= max_peak)
{
max_peak = out_buf_peak[i];
}
/* keep sloping */
else
{
if (max_peak > attack_slope)
max_peak -= attack_slope;
else
max_peak = 0;
out_buf_peak[i] = max_peak;
}
}
/* Now step forward through the output buffer, and modify the peak values
* to establish a smooth, slow release slope.*/ * to establish a smooth, slow release slope.*/
for (i = 0; i < count; i++) for (i = 0; i < count; i++)
{ {
/* if no release slope, nothing to do */ sample_gain = fp_one;
if ((out_buf_peak[i] == 0) && (release_peak == 0)) continue; for (ch = 0; ch < num_chan; ch++)
/* if new peak, start release slope */
if (out_buf_peak[i] >= release_peak)
{ {
release_peak = out_buf_peak[i]; this_gain = get_compression_gain(buf[ch][i]);
if (this_gain < sample_gain)
sample_gain = this_gain;
} }
/* keep sloping */ /* if no release slope, only apply makeup gain */
if ((sample_gain == fp_one) && (release_gain == fp_one))
gain_buffer[i] = comp_makeup_gain;
else else
{ {
release_peak--; /* if larger offset, start release slope */
out_buf_peak[i] = release_peak; if (sample_gain <= release_gain)
release_gain = sample_gain;
else /* keep sloping */
{
if (release_gain < (fp_one - comp_rel_slope))
release_gain += comp_rel_slope;
else
release_gain = fp_one;
}
/* store offset with release and also apply makeup gain */
if ((release_gain == fp_one) && (comp_makeup_gain == fp_one))
gain_buffer[i] = fp_one;
else
gain_buffer[i] = FRACMUL_SHL(release_gain, comp_makeup_gain, 7);
} }
} }
/* Implement the limiter: adjust gain of the outbound samples by the gain
* amounts in the gain steps array corresponding to the peak values. */ /* Implement the compressor: apply those gain factors to the output
* buffer samples */
for (i = 0; i < count; i++) for (i = 0; i < count; i++)
{ {
if (out_buf_peak[i] > 0) if (gain_buffer[i] != fp_one)
{ {
gain_peak = (out_buf_peak[i] + 1) / 90; for (ch = 0; ch < num_chan; ch++)
gain_rem = (out_buf_peak[i] + 1) % 90; buf[ch][i] = FRACMUL_SHL(buf[ch][i], gain_buffer[i], 7);
gain = gain_steps[gain_peak];
if ((gain_peak < 48) && (gain_rem > 0))
gain -= gain_rem * ((gain_steps[gain_peak] -
gain_steps[gain_peak + 1]) / 90);
for (ch = 0; ch < AUDIO_DSP.data.num_channels; ch++)
buf[ch][i] = FRACMUL_SHL(buf[ch][i], gain, 3);
} }
} }
return count; return count;

View file

@ -26,8 +26,7 @@
#include <stdbool.h> #include <stdbool.h>
#define NATIVE_FREQUENCY 44100 #define NATIVE_FREQUENCY 44100
#define LIMITER_BUFFER_SIZE 288 /* ~6.5 ms */
#define MAX_LIMITER_GAIN 80 /* 8 dB */
enum enum
{ {
STEREO_INTERLEAVED = 0, STEREO_INTERLEAVED = 0,
@ -82,7 +81,7 @@ int32_t sound_get_pitch(void);
void dsp_set_timestretch(int32_t percent); void dsp_set_timestretch(int32_t percent);
int32_t dsp_get_timestretch(void); int32_t dsp_get_timestretch(void);
int dsp_callback(int msg, intptr_t param); int dsp_callback(int msg, intptr_t param);
int dsp_flush_limiter_buffer(char *dest); void dsp_set_compressor(int c_threshold, int c_ratio, int c_gain,
void dsp_set_limiter(int limiter_level); int c_knee, int c_release);
#endif #endif

View file

@ -61,7 +61,7 @@ long fp_sincos(unsigned long phase, long *cos);
#define FP_NEGINF -(0x7fffffff) #define FP_NEGINF -(0x7fffffff)
/* fracbits in range 12 - 22 work well. Higher is better for /* fracbits in range 12 - 22 work well. Higher is better for
* calculating dB, lower is better for calculating ratio. * calculating dB, lower is better for calculating factor.
*/ */
/* long fp_decibels(unsigned long factor, unsigned int fracbits); */ /* long fp_decibels(unsigned long factor, unsigned int fracbits); */
long fp_factor(long decibels, unsigned int fracbits); long fp_factor(long decibels, unsigned int fracbits);

View file

@ -12646,15 +12646,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Begrænser forforstærkning" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Begrænser for-forstærkning" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12641,15 +12641,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Begrenzer-Vorverstärkung" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Begrenzer-Vorverstärkung" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12707,15 +12707,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>
@ -12769,3 +12769,224 @@
radio: "Other" radio: "Other"
</voice> </voice>
</phrase> </phrase>
<phrase>
id: LANG_COMPRESSOR_THRESHOLD
desc: in sound settings
user: core
<source>
*: none
swcodec: "Threshold"
</source>
<dest>
*: none
swcodec: "Threshold"
</dest>
<voice>
*: none
swcodec: "Threshold"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RATIO
desc: in sound settings
user: core
<source>
*: none
swcodec: "Ratio"
</source>
<dest>
*: none
swcodec: "Ratio"
</dest>
<voice>
*: none
swcodec: "Ratio"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RATIO_2
desc: in sound settings
user: core
<source>
*: none
swcodec: "2:1"
</source>
<dest>
*: none
swcodec: "2:1"
</dest>
<voice>
*: none
swcodec: "2 to 1"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RATIO_4
desc: in sound settings
user: core
<source>
*: none
swcodec: "4:1"
</source>
<dest>
*: none
swcodec: "4:1"
</dest>
<voice>
*: none
swcodec: "4 to 1"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RATIO_6
desc: in sound settings
user: core
<source>
*: none
swcodec: "6:1"
</source>
<dest>
*: none
swcodec: "6:1"
</dest>
<voice>
*: none
swcodec: "6 to 1"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RATIO_10
desc: in sound settings
user: core
<source>
*: none
swcodec: "10:1"
</source>
<dest>
*: none
swcodec: "10:1"
</dest>
<voice>
*: none
swcodec: "10 to 1"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RATIO_LIMIT
desc: in sound settings
user: core
<source>
*: none
swcodec: "Limit"
</source>
<dest>
*: none
swcodec: "Limit"
</dest>
<voice>
*: none
swcodec: "Limit"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_GAIN
desc: in sound settings
user: core
<source>
*: none
swcodec: "Makeup Gain"
</source>
<dest>
*: none
swcodec: "Makeup Gain"
</dest>
<voice>
*: none
swcodec: "Makeup Gain"
</voice>
</phrase>
<phrase>
id: LANG_AUTO
desc: in sound settings
user: core
<source>
*: none
swcodec: "Auto"
</source>
<dest>
*: none
swcodec: "Auto"
</dest>
<voice>
*: none
swcodec: "Auto"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_KNEE
desc: in sound settings
user: core
<source>
*: none
swcodec: "Knee"
</source>
<dest>
*: none
swcodec: "Knee"
</dest>
<voice>
*: none
swcodec: "Knee"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_HARD_KNEE
desc: in sound settings
user: core
<source>
*: none
swcodec: "Hard Knee"
</source>
<dest>
*: none
swcodec: "Hard Knee"
</dest>
<voice>
*: none
swcodec: "Hard Knee"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_SOFT_KNEE
desc: in sound settings
user: core
<source>
*: none
swcodec: "Soft Knee"
</source>
<dest>
*: none
swcodec: "Soft Knee"
</dest>
<voice>
*: none
swcodec: "Soft Knee"
</voice>
</phrase>
<phrase>
id: LANG_COMPRESSOR_RELEASE
desc: in sound settings
user: core
<source>
*: none
swcodec: "Release Time"
</source>
<dest>
*: none
swcodec: "Release Time"
</dest>
<voice>
*: none
swcodec: "Release Time"
</voice>
</phrase>

View file

@ -12663,15 +12663,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Limiteur préampli." swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Limiteur préampli" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12636,15 +12636,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Limitatore Preamp" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Limitatore Preamp" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12644,15 +12644,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Wzmacnianie cichych fragmentów" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Wzmacnianie cichych fragmentów" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12637,15 +12637,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Limiter-förförstärkning" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Limiter-förförstärkning" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12445,15 +12445,15 @@
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>

View file

@ -12632,19 +12632,19 @@
</voice> </voice>
</phrase> </phrase>
<phrase> <phrase>
id: LANG_LIMITER id: LANG_COMPRESSOR
desc: in sound settings desc: in sound settings
user: core user: core
<source> <source>
*: none *: none
swcodec: "Limiter Preamp" swcodec: "Compressor"
</source> </source>
<dest> <dest>
*: none *: none
swcodec: "Aschatrece Preamp" swcodec: "Compressor"
</dest> </dest>
<voice> <voice>
*: none *: none
swcodec: "Aschatrece Preamp" swcodec: "Compressor"
</voice> </voice>
</phrase> </phrase>

View file

@ -105,8 +105,21 @@ static int timestretch_callback(int action,const struct menu_item_ex *this_item)
&global_settings.timestretch_enabled, timestretch_callback); &global_settings.timestretch_enabled, timestretch_callback);
MENUITEM_SETTING(dithering_enabled, MENUITEM_SETTING(dithering_enabled,
&global_settings.dithering_enabled, lowlatency_callback); &global_settings.dithering_enabled, lowlatency_callback);
MENUITEM_SETTING(limiter_level,
&global_settings.limiter_level, lowlatency_callback); /* compressor submenu */
MENUITEM_SETTING(compressor_threshold,
&global_settings.compressor_threshold, lowlatency_callback);
MENUITEM_SETTING(compressor_ratio,
&global_settings.compressor_ratio, lowlatency_callback);
MENUITEM_SETTING(compressor_gain,
&global_settings.compressor_makeup_gain, lowlatency_callback);
MENUITEM_SETTING(compressor_knee,
&global_settings.compressor_knee, lowlatency_callback);
MENUITEM_SETTING(compressor_release,
&global_settings.compressor_release_time, lowlatency_callback);
MAKE_MENU(compressor_menu,ID2P(LANG_COMPRESSOR), NULL, Icon_NOICON,
&compressor_threshold, &compressor_ratio, &compressor_gain,
&compressor_knee, &compressor_release);
#endif #endif
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
@ -140,7 +153,7 @@ MAKE_MENU(sound_settings, ID2P(LANG_SOUND_SETTINGS), NULL, Icon_Audio,
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
,&crossfeed_menu, &equalizer_menu, &dithering_enabled ,&crossfeed_menu, &equalizer_menu, &dithering_enabled
,&timestretch_enabled ,&timestretch_enabled
,&limiter_level ,&compressor_menu
#endif #endif
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
,&loudness,&avc,&superbass,&mdb_enable,&mdb_strength ,&loudness,&avc,&superbass,&mdb_enable,&mdb_strength

View file

@ -1170,30 +1170,6 @@ bool pcmbuf_is_crossfade_enabled(void)
* Commit any remaining samples in the PCM buffer for playback. */ * Commit any remaining samples in the PCM buffer for playback. */
void pcmbuf_play_remainder(void) void pcmbuf_play_remainder(void)
{ {
pcmbuf_flush_limiter_buffer();
if (audiobuffer_fillpos) if (audiobuffer_fillpos)
pcmbuf_flush_fillpos(); pcmbuf_flush_fillpos();
} }
/** FLUSH LIMITER BUFFER
* Empty the limiter buffer and commit its contents
* to the PCM buffer for playback. */
void pcmbuf_flush_limiter_buffer(void)
{
char *dest;
int out_count = LIMITER_BUFFER_SIZE;
/* create room at the end of the PCM buffer for any
samples that may be held back in the limiter buffer */
while ((dest = pcmbuf_request_buffer(&out_count)) == NULL)
{
cancel_cpu_boost();
sleep(1);
}
/* flush the limiter buffer into the PCM buffer */
out_count = dsp_flush_limiter_buffer(dest);
if (out_count > 0)
pcmbuf_write_complete(out_count);
}

View file

@ -76,6 +76,5 @@ void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude);
int pcmbuf_used_descs(void); int pcmbuf_used_descs(void);
int pcmbuf_descs(void); int pcmbuf_descs(void);
void pcmbuf_play_remainder(void); void pcmbuf_play_remainder(void);
void pcmbuf_flush_limiter_buffer(void);
#endif #endif

View file

@ -467,7 +467,6 @@ static const struct plugin_api rockbox_api = {
dsp_process, dsp_process,
dsp_input_count, dsp_input_count,
dsp_output_count, dsp_output_count,
dsp_flush_limiter_buffer,
#endif /* CONFIG_CODEC == SWCODEC */ #endif /* CONFIG_CODEC == SWCODEC */
/* playback control */ /* playback control */

View file

@ -133,12 +133,12 @@ void* plugin_get_buffer(size_t *buffer_size);
#define PLUGIN_MAGIC 0x526F634B /* RocK */ #define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */ /* increase this every time the api struct changes */
#define PLUGIN_API_VERSION 171 #define PLUGIN_API_VERSION 172
/* update this to latest version if a change to the api struct breaks /* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */ new function which are "waiting" at the end of the function table) */
#define PLUGIN_MIN_API_VERSION 171 #define PLUGIN_MIN_API_VERSION 172
/* plugin return codes */ /* plugin return codes */
enum plugin_status { enum plugin_status {
@ -596,7 +596,6 @@ struct plugin_api {
const char *src[], int count); const char *src[], int count);
int (*dsp_input_count)(struct dsp_config *dsp, int count); int (*dsp_input_count)(struct dsp_config *dsp, int count);
int (*dsp_output_count)(struct dsp_config *dsp, int count); int (*dsp_output_count)(struct dsp_config *dsp, int count);
int (*dsp_flush_limiter_buffer)(char *dest);
#endif /* CONFIG_CODEC == SWCODC */ #endif /* CONFIG_CODEC == SWCODC */
/* playback control */ /* playback control */

View file

@ -810,34 +810,10 @@ show_menu:
rb->closedir(dir); rb->closedir(dir);
} }
/* process last samples */
if (use_dsp)
rb->dsp_flush_limiter_buffer(dspbuffer);
} else { } else {
/* Just test the file */ /* Just test the file */
res = test_track(parameter); res = test_track(parameter);
/* process last samples */
if (use_dsp)
{
int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
int count = rb->dsp_flush_limiter_buffer(dspbuffer);
if (channels == 1)
{
unsigned char *s = dspbuffer, *d = dspbuffer;
int c = count;
while (c-- > 0)
{
*d++ = *s++;
*d++ = *s++;
s++;
s++;
}
}
if (wavinfo.fd >= 0)
rb->write(wavinfo.fd, dspbuffer, count * 2 * channels);
}
/* Close WAV file (if there was one) */ /* Close WAV file (if there was one) */
if (wavinfo.fd >= 0) { if (wavinfo.fd >= 0) {
close_wav(); close_wav();

View file

@ -956,7 +956,11 @@ void settings_apply(bool read_disk)
dsp_dither_enable(global_settings.dithering_enabled); dsp_dither_enable(global_settings.dithering_enabled);
dsp_timestretch_enable(global_settings.timestretch_enabled); dsp_timestretch_enable(global_settings.timestretch_enabled);
dsp_set_limiter(global_settings.limiter_level); dsp_set_compressor(global_settings.compressor_threshold,
global_settings.compressor_ratio,
global_settings.compressor_makeup_gain,
global_settings.compressor_knee,
global_settings.compressor_release_time);
#endif #endif
#ifdef HAVE_SPDIF_POWER #ifdef HAVE_SPDIF_POWER

View file

@ -777,7 +777,11 @@ struct user_settings
#endif #endif
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
int limiter_level; int compressor_threshold;
int compressor_ratio;
int compressor_makeup_gain;
int compressor_knee;
int compressor_release_time;
#endif #endif
}; };

View file

@ -356,6 +356,34 @@ static void crossfeed_cross_set(int val)
global_settings.crossfeed_hf_cutoff); global_settings.crossfeed_hf_cutoff);
} }
static void compressor_set(int val)
{
(void)val;
dsp_set_compressor(global_settings.compressor_threshold,
global_settings.compressor_ratio,
global_settings.compressor_makeup_gain,
global_settings.compressor_knee,
global_settings.compressor_release_time);
}
static const char* auto_formatter(char *buffer, size_t buffer_size,
int val, const char *unit)
{
if (val == -1)
return str(LANG_AUTO);
else
snprintf(buffer, buffer_size, "%d %s", val, unit);
return buffer;
}
static int32_t auto_getlang(int value, int unit)
{
if (value == -1)
return LANG_AUTO;
else
return TALK_ID(value, unit);
}
static const char* db_format(char* buffer, size_t buffer_size, int value, static const char* db_format(char* buffer, size_t buffer_size, int value,
const char* unit) const char* unit)
{ {
@ -1256,11 +1284,29 @@ const struct settings_list settings[] = {
OFFON_SETTING(F_SOUNDSETTING, timestretch_enabled, LANG_TIMESTRETCH, false, OFFON_SETTING(F_SOUNDSETTING, timestretch_enabled, LANG_TIMESTRETCH, false,
"timestretch enabled", dsp_timestretch_enable), "timestretch enabled", dsp_timestretch_enable),
/* limiter */ /* compressor */
INT_SETTING_NOWRAP(F_SOUNDSETTING, limiter_level, INT_SETTING_NOWRAP(F_SOUNDSETTING, compressor_threshold,
LANG_COMPRESSOR, 0, LANG_COMPRESSOR_THRESHOLD, 0,
"limiter level", UNIT_DB, 0, MAX_LIMITER_GAIN, "compressor threshold", UNIT_DB, 0, -24,
5, db_format, get_dec_talkid, dsp_set_limiter), -3, formatter_unit_0_is_off, getlang_unit_0_is_off, compressor_set),
CHOICE_SETTING(F_SOUNDSETTING|F_NO_WRAP, compressor_ratio,
LANG_COMPRESSOR_RATIO, 1, "compressor ratio",
"2:1,4:1,6:1,10:1,limit", compressor_set, 5,
ID2P(LANG_COMPRESSOR_RATIO_2), ID2P(LANG_COMPRESSOR_RATIO_4),
ID2P(LANG_COMPRESSOR_RATIO_6), ID2P(LANG_COMPRESSOR_RATIO_10),
ID2P(LANG_COMPRESSOR_RATIO_LIMIT)),
INT_SETTING_NOWRAP(F_SOUNDSETTING, compressor_makeup_gain,
LANG_COMPRESSOR_GAIN, -1,
"compressor makeup gain", UNIT_DB, -1, 20,
1, auto_formatter, auto_getlang, compressor_set),
CHOICE_SETTING(F_SOUNDSETTING|F_NO_WRAP, compressor_knee,
LANG_COMPRESSOR_KNEE, 1, "compressor knee",
"hard knee,soft knee", compressor_set, 2,
ID2P(LANG_COMPRESSOR_HARD_KNEE), ID2P(LANG_COMPRESSOR_SOFT_KNEE)),
INT_SETTING_NOWRAP(F_SOUNDSETTING, compressor_release_time,
LANG_COMPRESSOR_RELEASE, 100,
"compressor release time", UNIT_MS, 20, 200,
10, NULL, NULL, compressor_set),
#endif #endif
#ifdef HAVE_WM8758 #ifdef HAVE_WM8758
SOUND_SETTING(F_NO_WRAP, bass_cutoff, LANG_BASS_CUTOFF, SOUND_SETTING(F_NO_WRAP, bass_cutoff, LANG_BASS_CUTOFF,