Index: apps/pcmbuf.c =================================================================== --- apps/pcmbuf.c (revision 21906) +++ apps/pcmbuf.c (working copy) @@ -446,7 +446,7 @@ /* Buffer has to be at least 2s long. */ seconds += 2; #endif - logf("pcmbuf len: %ld", seconds); + logf("pcmbuf len: %ld", (long)seconds); return seconds * (NATIVE_FREQUENCY*4); /* 2 channels + 2 bytes/sample */ } @@ -1155,8 +1155,28 @@ return crossfade_enabled; } +/** PLAY LAST REMAINING SAMPLES AT END OF PLAYBACK + * Empty the attack buffer and commit any remaining + * samples in the PCM buffer to playback. */ void pcmbuf_play_remainder(void) { + char *dest; + int out_count = ATTACK_BUFFER_SIZE; + + /* create room at the end of the PCM buffer for the + samples held back in the attack buffer */ + while ((dest = pcmbuf_request_buffer(&out_count)) == NULL) + { + cancel_cpu_boost(); + sleep(1); + } + + /* flush the attack buffer into the PCM buffer */ + out_count = dsp_flush_attack_buffer(dest); + if (out_count > 0) + pcmbuf_write_complete(out_count); + + /* commit remaining samples to playback */ if (audiobuffer_fillpos) pcmbuf_flush_fillpos(); } Index: apps/settings.c =================================================================== --- apps/settings.c (revision 21906) +++ apps/settings.c (working copy) @@ -939,7 +939,10 @@ } dsp_dither_enable(global_settings.dithering_enabled); + dsp_timestretch_enable(global_settings.timestretch_enabled); + + dsp_set_limiter(global_settings.limiter_level); #endif #ifdef HAVE_SPDIF_POWER Index: apps/plugins/mpegplayer/audio_thread.c =================================================================== --- apps/plugins/mpegplayer/audio_thread.c (revision 21906) +++ apps/plugins/mpegplayer/audio_thread.c (working copy) @@ -482,7 +482,7 @@ td.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP, CODEC_IDX_AUDIO); rb->sound_set_pitch(PITCH_SPEED_100); - rb->dsp_configure(td.dsp, DSP_RESET, 0); + rb->dsp_configure(td.dsp, DSP_RESET_NO_ATTACK_BUFFER, 0); rb->dsp_configure(td.dsp, DSP_SET_SAMPLE_DEPTH, MAD_F_FRACBITS); goto message_wait; Index: apps/lang/english.lang =================================================================== --- apps/lang/english.lang (revision 21906) +++ apps/lang/english.lang (working copy) @@ -12655,3 +12655,20 @@ pitchscreen: "Rate" + + id: LANG_LIMITER + desc: in sound settings + user: core + + *: none + swcodec: "Limiter Preamp" + + + *: none + swcodec: "Limiter Preamp" + + + *: none + swcodec: "Limiter Preamp" + + Index: apps/settings.h =================================================================== --- apps/settings.h (revision 21906) +++ apps/settings.h (working copy) @@ -746,6 +746,11 @@ bool pitch_mode_timestretch; #endif #endif + +#if CONFIG_CODEC == SWCODEC + int limiter_level; +#endif + /* If values are just added to the end, no need to bump plugin API version. */ /* new stuff to be added at the end */ Index: apps/menus/sound_menu.c =================================================================== --- apps/menus/sound_menu.c (revision 21906) +++ apps/menus/sound_menu.c (working copy) @@ -105,6 +105,9 @@ &global_settings.timestretch_enabled, timestretch_callback); MENUITEM_SETTING(dithering_enabled, &global_settings.dithering_enabled, lowlatency_callback); + MENUITEM_SETTING(limiter_level, + &global_settings.limiter_level, lowlatency_callback); + #endif #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) @@ -138,6 +141,7 @@ #if CONFIG_CODEC == SWCODEC ,&crossfeed_menu, &equalizer_menu, &dithering_enabled ,×tretch_enabled + ,&limiter_level #endif #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) ,&loudness,&avc,&superbass,&mdb_enable,&mdb_strength Index: apps/dsp.c =================================================================== --- apps/dsp.c (revision 21906) +++ apps/dsp.c (working copy) @@ -36,6 +36,10 @@ #include "fixedpoint.h" #include "fracmul.h" +/* Define LOGF_ENABLE to enable logf output in this file */ +#define LOGF_ENABLE +#include "logf.h" + /* 16-bit samples are scaled based on these constants. The shift should be * no more than 15. */ @@ -149,8 +153,8 @@ /* DSP local channel processing in place */ typedef void (*channels_process_dsp_fn_type)(int count, struct dsp_data *data, int32_t *buf[]); +typedef int (*attack_fn_type)(int count, int32_t *ext_buf[]); - /* ***************************************************************************/ @@ -165,6 +169,7 @@ int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */ bool tdspeed_active; /* Timestretch is in use */ int frac_bits; + long limiter_preamp; /* limiter amp gain in S7.24 format */ #ifdef HAVE_SW_TONE_CONTROLS /* Filter struct for software bass/treble controls */ struct eqfilter tone_filter; @@ -180,6 +185,8 @@ channels_process_fn_type apply_crossfeed; channels_process_fn_type eq_process; channels_process_fn_type channels_process; + attack_fn_type set_attack_buffer; + channels_process_fn_type limiter_process; }; /* General DSP config */ @@ -219,6 +226,49 @@ static long replaygain; static bool crossfeed_enabled; + +/* attack buffer */ +static int count_adjust; +static bool attack_buffer_full; +static bool attack_buffer_emptying; +static int32_t attack_buffer[2][ATTACK_BUFFER_SIZE] IBSS_ATTR; +static int32_t *start_attack[2], *end_attack[2]; +static uint16_t attack_peak[ATTACK_BUFFER_SIZE] IBSS_ATTR; +static uint16_t *start_peak, *end_peak; +static uint16_t buf_peak[ATTACK_BUFFER_SIZE] IBSS_ATTR; + +static void clear_attack_buffer(void); +static int set_attack_buffer(int count, int32_t *buf[]); +static int attack_buffer_count(bool empty_count); + +/* limiter */ +static uint16_t release_peak; + +static void limiter_process(int count, int32_t *buf[]); +static uint16_t get_peak_value(int32_t sample); +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}; +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}; + #define AUDIO_DSP (dsp_conf[CODEC_IDX_AUDIO]) #define VOICE_DSP (dsp_conf[CODEC_IDX_VOICE]) @@ -245,7 +295,6 @@ #define RESAMPLE_BUF_LEFT_CHANNEL 0 #define RESAMPLE_BUF_RIGHT_CHANNEL (sample_buf_count/2 * RESAMPLE_RATIO) - /* Clip sample to signed 16 bit range */ static inline int32_t clip_sample_16(int32_t sample) { @@ -477,13 +526,12 @@ const int scale = data->output_scale; const int dc_bias = 1 << (scale - 1); - do + while (count-- > 0) { int32_t lr = clip_sample_16((*s0++ + dc_bias) >> scale); *dst++ = lr; *dst++ = lr; } - while (--count > 0); } #endif /* DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO */ @@ -497,12 +545,11 @@ const int scale = data->output_scale; const int dc_bias = 1 << (scale - 1); - do + while (count-- > 0) { *dst++ = clip_sample_16((*s0++ + dc_bias) >> scale); *dst++ = clip_sample_16((*s1++ + dc_bias) >> scale); } - while (--count > 0); } #endif /* DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO */ @@ -876,6 +923,7 @@ /* Combine all gains to a global gain. */ static void set_gain(struct dsp_config *dsp) { + /* gains are in S7.24 format */ dsp->data.gain = DEFAULT_GAIN; /* Replay gain not relevant to voice */ @@ -886,17 +934,23 @@ if (dsp->eq_process && eq_precut) { - dsp->data.gain = - (long) (((int64_t) 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 */ + if ((dsp->limiter_preamp) && (dsp->frac_bits <= 29)) + { + dsp->data.gain = fp_mul(dsp->data.gain, dsp->limiter_preamp, 24); + } + if (dsp->data.gain == DEFAULT_GAIN) { dsp->data.gain = 0; } else { - dsp->data.gain >>= 1; + dsp->data.gain >>= 1; /* convert gain to S8.23 format */ } dsp->apply_gain = dsp->data.gain != 0 ? dsp_apply_gain : NULL; @@ -1194,18 +1248,27 @@ /* Testing function pointers for NULL is preferred since the pointer will be preloaded to be used for the call if not. */ - while (count > 0) + while ((count > 0) || (attack_buffer_emptying)) { int samples = MIN(sample_buf_count/2, count); count -= samples; - dsp->input_samples(samples, src, tmp); + if (attack_buffer_emptying) + { + /* point to flush buffer */ + tmp[0] = (int32_t *)src[0]; + tmp[1] = (int32_t *)src[1]; + } + else + { + dsp->input_samples(samples, src, tmp); - if (dsp->tdspeed_active) - samples = tdspeed_doit(tmp, samples); - + if (dsp->tdspeed_active) + samples = tdspeed_doit(tmp, samples); + } + int chunk_offset = 0; - while (samples > 0) + while ((samples > 0) || (attack_buffer_emptying)) { int32_t *t2[2]; t2[0] = tmp[0]+chunk_offset; @@ -1215,27 +1278,39 @@ chunk_offset += chunk; samples -= chunk; - if (dsp->apply_gain) - dsp->apply_gain(chunk, &dsp->data, t2); + if (!attack_buffer_emptying) /* don't process when chunk = 0 */ + { + if (dsp->apply_gain) + dsp->apply_gain(chunk, &dsp->data, t2); - if (dsp->resample && (chunk = resample(dsp, chunk, t2)) <= 0) - break; /* I'm pretty sure we're downsampling here */ + if (dsp->resample && (chunk = resample(dsp, chunk, t2)) <= 0) + break; /* I'm pretty sure we're downsampling here */ - if (dsp->apply_crossfeed) - dsp->apply_crossfeed(chunk, t2); + if (dsp->apply_crossfeed) + dsp->apply_crossfeed(chunk, t2); - if (dsp->eq_process) - dsp->eq_process(chunk, t2); + if (dsp->eq_process) + dsp->eq_process(chunk, t2); #ifdef HAVE_SW_TONE_CONTROLS - if ((bass | treble) != 0) - eq_filter(t2, &dsp->tone_filter, chunk, - dsp->data.num_channels, FILTER_BISHELF_SHIFT); + if ((bass | treble) != 0) + eq_filter(t2, &dsp->tone_filter, chunk, + dsp->data.num_channels, FILTER_BISHELF_SHIFT); #endif - if (dsp->channels_process) - dsp->channels_process(chunk, t2); + if (dsp->channels_process) + dsp->channels_process(chunk, t2); + } /* !attack_buffer_emptying */ + + /* operate the attack buffer */ + if (dsp->set_attack_buffer) + chunk = set_attack_buffer(chunk, t2); + /* put all processes that refer to the attack buffer after this line */ + + if (dsp->limiter_process) + dsp->limiter_process(chunk, t2); + dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst); written += chunk; @@ -1282,6 +1357,16 @@ */ if (count > RESAMPLE_BUF_RIGHT_CHANNEL) count = RESAMPLE_BUF_RIGHT_CHANNEL; + + /* If the attack buffer is filling, some or all samples will + * be captured by it, so expect fewer samples coming out. + */ + if (dsp->set_attack_buffer && !attack_buffer_full) + { + int empty_space = attack_buffer_count(true); + count_adjust = MIN(empty_space, count); + count -= count_adjust; + } return count; } @@ -1291,6 +1376,13 @@ */ int dsp_input_count(struct dsp_config *dsp, int count) { + /* If the attack buffer is filling, the output count was + * adjusted downward. This adjusts it back so that input + * count is not affected. + */ + if (dsp->set_attack_buffer && !attack_buffer_full) + count += count_adjust; + /* count is now the number of resampled input samples. Convert to original input samples. */ if (dsp->resample) @@ -1385,7 +1477,8 @@ tdspeed_setup(dsp); break; - case DSP_RESET: + case DSP_RESET: /* fall through! */ + case DSP_RESET_NO_ATTACK_BUFFER: dsp->stereo_mode = STEREO_NONINTERLEAVED; dsp->data.num_channels = 2; dsp->sample_depth = NATIVE_DEPTH; @@ -1408,6 +1501,11 @@ dsp_update_functions(dsp); resampler_new_delta(dsp); tdspeed_setup(dsp); + dsp->set_attack_buffer = + ((dsp == &AUDIO_DSP) && (setting != DSP_RESET_NO_ATTACK_BUFFER)) + ? set_attack_buffer : NULL; + clear_attack_buffer(); + release_peak = 0; break; case DSP_FLUSH: @@ -1416,6 +1514,8 @@ resampler_new_delta(dsp); dither_init(dsp); tdspeed_setup(dsp); + clear_attack_buffer(); + release_peak = 0; break; case DSP_SET_TRACK_GAIN: @@ -1490,7 +1590,395 @@ } } - /* Store in S8.23 format to simplify calculations. */ + /* Store in S7.24 format to simplify calculations. */ replaygain = gain; set_gain(&AUDIO_DSP); } + + +/** CLEAR THE ATTACK BUFFER + * Forces the attack buffer to the initial state and discard + * any samples held there. */ +static void clear_attack_buffer(void) +{ + if (AUDIO_DSP.set_attack_buffer == NULL) return; + int i; + logf("clear_attack_buffer"); + for (i = 0; i < 2; i++) + { + start_attack[i] = end_attack[i] = attack_buffer[i]; + start_peak = end_peak = attack_peak; + } + attack_buffer_full = false; + attack_buffer_emptying = false; +} + + +/** OPERATE THE ATTACK BUFFER + * Handles all samples entering or exiting the attack buffer. + * This should only be called from dsp_process, and dsp_process should + * only be called after dsp_output_count and dsp_input_count. */ +static int set_attack_buffer(int count, int32_t *buf[]) +{ + int32_t *in_buf[2], *out_buf[2], temp, samp0 = 0; + int empty_space, + i, + out_count = 0; + uint16_t *buf_peak_index = buf_peak; + const long clip_max = AUDIO_DSP.data.clip_max; + + in_buf[0] = buf[0]; + in_buf[1] = buf[1]; + out_buf[0] = buf[0]; + out_buf[1] = buf[1]; + + if (attack_buffer_emptying) + /** EMPTY THE BUFFER + * since the empty flag has been set, assume no inbound samples and + return all samples in the attack buffer to the outbound buffer */ + { + count = attack_buffer_count(false); + out_count = count; + logf("Emptying attack buffer: %d", count); + while (count-- > 0) + { + for (i = 0; i < 2; i++) + { + /* move samples in attack buffer to output buffer */ + *out_buf[i]++ = *start_attack[i]++; + if (start_attack[i] == &attack_buffer[i][ATTACK_BUFFER_SIZE]) + start_attack[i] = attack_buffer[i]; + /* move attack buffer peak values to output peak values */ + if (i == 0) + { + *buf_peak_index++ = *start_peak++; + if (start_peak == &attack_peak[ATTACK_BUFFER_SIZE]) + start_peak = attack_peak; + } + } + } + clear_attack_buffer(); + } + else /* attack buffer NOT emptying */ + { + empty_space = attack_buffer_count(true); + + 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 attack buffer: %d", empty_space); + do + { + for (i = 0; i < 2; i++) + { + /* put inbound samples in the attack buffer */ + temp = *in_buf[i]++; + *end_attack[i]++ = temp; + if (end_attack[i] == &attack_buffer[i][ATTACK_BUFFER_SIZE]) + end_attack[i] = attack_buffer[i]; + if (temp < 0) /* make positive for comparison */ + temp = -temp - 1; + if (temp <= clip_max) + temp = 0; /* disregard if not clipped */ + if (i == 0) + { + samp0 = temp; + } + else + { + /* assign peak value for each inbound sample pair */ + *end_peak++ = ((samp0 > 0) || (temp > 0)) ? + get_peak_value(MAX(samp0, temp)) : 0; + if (end_peak == &attack_peak[ATTACK_BUFFER_SIZE]) + end_peak = attack_peak; + } + } + count--; + } + while (--empty_space > 0); + /* after buffer fills, the remaining inbound samples are cycled */ + } + + attack_buffer_full = (end_attack[0] == start_attack[0]); + + if (count > 0) + /** CYCLE BUFFER + * return buffered samples and backfill attack buffer with new ones */ + { + /* buffer should always be full here */ +#ifdef SIMULATOR + if (!attack_buffer_full) + DEBUGF("Attack buffer cycle error: %d / %d\n", + attack_buffer_count(false), ATTACK_BUFFER_SIZE); +#endif + out_count = count; + do + { + for (i = 0; i < 2; i++) + { + /* copy incoming sample */ + temp = *in_buf[i]++; + /* put attack buffer sample into outbound buffer */ + *out_buf[i]++ = *start_attack[i]++; + /* put incoming sample on the end of the attack buffer */ + *end_attack[i]++ = temp; + /* ring buffer pointer wrap */ + if (start_attack[i] == &attack_buffer[i][ATTACK_BUFFER_SIZE]) + start_attack[i] = attack_buffer[i]; + if (end_attack[i] == &attack_buffer[i][ATTACK_BUFFER_SIZE]) + end_attack[i] = attack_buffer[i]; + if (temp < 0) /* make positive for comparison */ + temp = -temp - 1; + if (temp <= clip_max) + temp = 0; /* disregard if not clipped */ + if (i == 0) + { + samp0 = temp; + /* assign outgoing sample its associated peak value */ + *buf_peak_index++ = *start_peak++; + if (start_peak == &attack_peak[ATTACK_BUFFER_SIZE]) + start_peak = attack_peak; + } + else + { + /* assign peak value for each inbound sample pair */ + *end_peak++ = ((samp0 > 0) || (temp > 0)) ? + get_peak_value(MAX(samp0, temp)) : 0; + if (end_peak == &attack_peak[ATTACK_BUFFER_SIZE]) + end_peak = attack_peak; + } + } + } + while (--count > 0); + } + } + + return out_count; +} + +/** RETURN ATTACK BUFFER COUNT + * If argument is true, returns empty space remaining, + * otherwise, returns number of samples in the buffer */ +static int attack_buffer_count(bool empty_count) +{ + int count; + if (attack_buffer_full) + count = ATTACK_BUFFER_SIZE; + else if (end_attack[0] >= start_attack[0]) + count = (end_attack[0] - start_attack[0]); + else + count = (end_attack[0] - start_attack[0]) + ATTACK_BUFFER_SIZE; + return empty_count ? (ATTACK_BUFFER_SIZE - count) : count; +} + + +/** FLUSH THE ATTACK BUFFER + * Only called by the pcmbuf_play_remainder routine in pcmbuf.c + * Empties the attack buffer into the buffer pointed to by the argument + * and returns the number of samples in that buffer */ +int dsp_flush_attack_buffer(char *dest) +{ + logf("dsp_flush_attack_buffer"); + if ((AUDIO_DSP.set_attack_buffer == NULL) || + (attack_buffer_count(false) <= 0)) + return 0; + + int32_t flush_buf[2][ATTACK_BUFFER_SIZE]; + const char *src[2]; + src[0] = (char *)flush_buf[0]; + src[1] = (char *)flush_buf[1]; + + attack_buffer_emptying = true; + + return dsp_process(&AUDIO_DSP, dest, src, 0); +} + + +/** GET PEAK VALUE + * 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 + * positive value. + */ +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 int frac_bits = AUDIO_DSP.frac_bits; + int mid, + hi = 48, + lo = 0; + + /* shift sample into 28 frac bit range for comparison */ + if (frac_bits > 28) + sample >>= (frac_bits - 28); + if (frac_bits < 28) + sample <<= (28 - frac_bits); + + /* if clipped out of range, return maximum value */ + if (sample >= clip_steps[48]) + return 48 * 90; + + /* find amount of sample clipping on the table */ + do + { + 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) */ + return ((hi-1) * 90) + (((sample - clip_steps[hi-1]) * 90) / + (clip_steps[hi] - clip_steps[hi-1])); + +#if 0 + /* old working code - keeping this for now in case new stuff breaks... */ + for (i = 1; i <= 48; i++) + { + if (sample < clip_steps[i]) + { + /* interpolate linearly between steps (less accurate but faster) */ + return ((i-1) * 90) + (((sample - clip_steps[i-1]) * 90) / + (clip_steps[i] - clip_steps[i-1])); + } + } + return 48*90; +#endif +} + + +void dsp_set_limiter(int limiter_level) +{ + if (limiter_level > 0) + { + /* enable limiter process */ + AUDIO_DSP.limiter_process = limiter_process; + /* limiter preamp is a gain factor in S7.24 format */ + AUDIO_DSP.limiter_preamp = + fp_factor((((long)limiter_level << 24) / 10), 24); + } + else + { + /* disable limiter process*/ + AUDIO_DSP.limiter_process = NULL; + AUDIO_DSP.limiter_preamp = 0; + } + + set_gain(&AUDIO_DSP); + + logf("Limiter enable: %s\tLimiter amp: %.8f", + AUDIO_DSP.limiter_process ? "Yes" : "No", + (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 void limiter_process(int count, int32_t *buf[]) +{ + /* Limiter process passes through if attack buffer isn't active, or the + * sample depth is too large for safe pre-amping, or there is nothing + * to process. */ + if ((AUDIO_DSP.set_attack_buffer == NULL) || + (AUDIO_DSP.frac_bits > 29) || (count <= 0)) return; + + /* 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 int attack_slope = 15; /* 15:1 ratio between attack and release */ + const int attack_count = attack_buffer_count(false); + + int i, ch; + uint16_t attack_peak_i, + max_peak = 0, + gain_peak, + gain_rem; + long gain; + + /* step through attack buffer in reverse order, in order to find the + * appropriate max_peak for modifying the output buffer */ + for (i = attack_count - 1; i >= 0; i--) + { + attack_peak_i = attack_peak[(start_peak - attack_peak + i) % + ATTACK_BUFFER_SIZE]; + /* if no attack slope, nothing to do */ + if ((attack_peak_i == 0) && (max_peak == 0)) continue; + /* if new peak, start attack slope */ + if (attack_peak_i >= max_peak) + { + max_peak = attack_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 ((buf_peak[i] == 0) && (max_peak == 0)) continue; + /* if new peak, start attack slope */ + if (buf_peak[i] >= max_peak) + { + max_peak = buf_peak[i]; + } + /* keep sloping */ + else + { + if (max_peak > attack_slope) + max_peak -= attack_slope; + else + max_peak = 0; + buf_peak[i] = max_peak; + } + } + /* Now step forward through the output buffer, and modify the peak values + * to establish a smooth, slow release slope.*/ + for (i = 0; i < count; i++) + { + /* if no release slope, nothing to do */ + if ((buf_peak[i] == 0) && (release_peak == 0)) continue; + /* if new peak, start release slope */ + if (buf_peak[i] >= release_peak) + { + release_peak = buf_peak[i]; + } + /* keep sloping */ + else + { + release_peak--; + buf_peak[i] = release_peak; + } + } + /* Implement the limiter: adjust gain of the outbound samples by the gain + * amounts in the gain steps array corresponding to the peak values. */ + for (ch = 0; ch < 2; ch++) + { + int32_t *d = buf[ch]; + for (i = 0; i < count; i++) + { + gain_peak = (buf_peak[i] + 1) / 90; + gain_rem = (buf_peak[i] + 1) % 90; + 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); + d[i] = FRACMUL_SHL(d[i], gain, 3); + } + } +} Index: apps/settings_list.c =================================================================== --- apps/settings_list.c (revision 21906) +++ apps/settings_list.c (working copy) @@ -1236,6 +1236,12 @@ /* timestretch */ OFFON_SETTING(F_SOUNDSETTING, timestretch_enabled, LANG_TIMESTRETCH, false, "timestretch enabled", dsp_timestretch_enable), + + /* limiter */ + INT_SETTING_NOWRAP(F_SOUNDSETTING, limiter_level, + LANG_LIMITER, 0, + "limiter level", UNIT_DB, 0, MAX_LIMITER_GAIN, + 5, db_format, get_dec_talkid, dsp_set_limiter), #endif #ifdef HAVE_WM8758 SOUND_SETTING(F_NO_WRAP, bass_cutoff, LANG_BASS_CUTOFF, Index: apps/dsp.h =================================================================== --- apps/dsp.h (revision 21906) +++ apps/dsp.h (working copy) @@ -26,6 +26,8 @@ #include #define NATIVE_FREQUENCY 44100 +#define ATTACK_BUFFER_SIZE 288 /* ~6.5 ms */ +#define MAX_LIMITER_GAIN 80 /* 8 dB */ enum { STEREO_INTERLEAVED = 0, @@ -53,7 +55,11 @@ DSP_SET_ALBUM_GAIN, DSP_SET_TRACK_PEAK, DSP_SET_ALBUM_PEAK, - DSP_CROSSFEED + DSP_CROSSFEED, + DSP_RESET_NO_ATTACK_BUFFER /* Disables the attack buffer. Reset the DSP + with this if calling dsp_process() without + calling both dsp_output_count() and + dsp_input_count() first */ }; enum { @@ -88,5 +94,7 @@ void dsp_set_timestretch(int32_t percent); int32_t dsp_get_timestretch(void); int dsp_callback(int msg, intptr_t param); +int dsp_flush_attack_buffer(char *dest); +void dsp_set_limiter(int limiter_level); #endif