Index: apps/pcmbuf.c =================================================================== --- apps/pcmbuf.c (revision 21768) +++ 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 21768) +++ apps/settings.c (working copy) @@ -944,7 +944,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 21768) +++ 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(1000); - 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 21768) +++ apps/lang/english.lang (working copy) @@ -12604,3 +12604,20 @@ remote: "Remote Statusbar" + + 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 21768) +++ apps/settings.h (working copy) @@ -739,6 +739,10 @@ struct touchscreen_parameter ts_calibration_data; #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 21768) +++ 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 21768) +++ 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 @@ int tdspeed_percent; /* Speed % */ 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,35 @@ 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]; +static int32_t *start_attack[2], *end_attack[2]; +static uint8_t attack_peak[ATTACK_BUFFER_SIZE]; +static uint8_t *start_peak, *end_peak; +static uint8_t buf_peak[ATTACK_BUFFER_SIZE]; +#define read_attack_buffer(x, offset) \ + attack_buffer[x][(start_attack[x] - attack_buffer[x] + offset) \ + % ATTACK_BUFFER_SIZE] +#define read_attack_peak(offset) \ + attack_peak[(start_peak - attack_peak + offset) % ATTACK_BUFFER_SIZE] + +static void clear_attack_buffer(void); +static int set_attack_buffer(int count, int32_t *buf[]); +static int attack_buffer_count(bool empty_count); +static uint8_t get_peak_value(int32_t sample); + +/* limiter */ +static int release_count; +static uint8_t release_peak; + +static void limiter_init(void); +static void limiter_process(int count, int32_t *buf[]); + + #define AUDIO_DSP (dsp_conf[CODEC_IDX_AUDIO]) #define VOICE_DSP (dsp_conf[CODEC_IDX_VOICE]) @@ -245,7 +281,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 +512,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 +531,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 +909,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 +920,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 +1234,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 +1264,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 +1343,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 +1362,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 +1463,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 +1487,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; + limiter_init(); + clear_attack_buffer(); break; case DSP_FLUSH: @@ -1416,6 +1500,8 @@ resampler_new_delta(dsp); dither_init(dsp); tdspeed_setup(dsp); + limiter_init(); + clear_attack_buffer(); break; case DSP_SET_TRACK_GAIN: @@ -1490,7 +1576,372 @@ } } - /* 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; + int empty_space, + i, + out_count = 0; + uint8_t temp_peak, + *buf_peak_index = buf_peak; + 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 */ + *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]; + /* assign peak value for each inbound sample pair */ + temp_peak = get_peak_value(temp); + if (i == 0) + { + *end_peak = temp_peak; + } + else + { + *end_peak = MAX(*end_peak, temp_peak); + end_peak++; + 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]; + /* assign outgoing sample its associated peak value */ + *buf_peak_index++ = *start_peak++; + if (start_peak == &attack_peak[ATTACK_BUFFER_SIZE]) + start_peak = attack_peak; + /* assign peak value for each inbound sample pair */ + temp_peak = get_peak_value(temp); + if (i == 0) + { + *end_peak = temp_peak; + } + else + { + *end_peak = MAX(*end_peak, temp_peak); + end_peak++; + 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. + */ +static uint8_t get_peak_value(int32_t sample) +{ + /* The clip_steps array essentially stores the results of fp_factor from + * 0 to 12 dB, in 20 equal steps, in S7.24 format. */ + const long clip_steps[21] = { 0x1000000, + 0x1124F17, 0x125ED65, 0x13AF2E5, 0x1517946, 0x1699C0F, + 0x18378C0, 0x19F2EF2, 0x1BCE084, 0x1DCB1BE, 0x1FEC983, + 0x223517C, 0x24A764D, 0x27467CE, 0x2A1593E, 0x2D1818B, + 0x3051B8E, 0x33C6657, 0x377A576, 0x3B72154, 0x3FB2784}; + const int frac_bits = AUDIO_DSP.frac_bits; + int i; + + /* quick exit if no clipping (usual case) */ + if ((sample < (1 << frac_bits)) && (sample >= -(1 << frac_bits))) + return 0; + + if (frac_bits > 24) + sample >>= (frac_bits - 24); + if (frac_bits < 24) + sample <<= (24 - frac_bits); + for (i = 1; i < 20; i++) + { + if ((sample < clip_steps[i]) && (sample >= -clip_steps[i])) + return i; + } + return 20; +} + + +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)); +} + + +static void limiter_init(void) +{ + logf("limiter_init"); + release_count = 0; + release_peak = 0; +} + + +/** 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 20 equal steps, in S7.24 format. */ + const long gain_steps[21] = { 0x1000000, + 0xEEE9C1, 0xDEF778, 0xD015A9, 0xC23224, 0xB53BEF, + 0xA92335, 0x9DD92C, 0x935008, 0x897AEA, 0x804DCE, + 0x77BD7D, 0x6FBF80, 0x684A13, 0x615418, 0x5AD50D, + 0x54C502, 0x4F1C8F, 0x49D4CA, 0x44E73E, 0x404DE6}; + + const int attack_samples = 16; /* 7ms / 20 */ + const int release_samples = 224; /* 100ms / 20 */ + const int attack_count = attack_buffer_count(false); + + int i, ch; + int step_count; + uint8_t max_peak = 0; + + /* step through attack buffer in reverse order, in order to find the + * appropriate max_peak and step_count for modifying the output buffer */ + for (i = attack_count - 1; i >= 0; i--) + { + /* 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]; + step_count = attack_samples; + } + /* keep sloping */ + else + { + if (step_count-- == 0) + { + max_peak--; + step_count = attack_samples; + } + } + } + /* 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]; + step_count = attack_samples; + } + /* keep sloping */ + else + { + if (step_count-- == 0) + { + max_peak--; + step_count = attack_samples; + } + 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]; + release_count = release_samples; + } + /* keep sloping */ + else + { + if (release_count-- == 0) + { + release_peak--; + release_count = release_samples; + } + 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++) + d[i] = FRACMUL_SHL(d[i], gain_steps[buf_peak[i]], 7); + } +} Index: apps/settings_list.c =================================================================== --- apps/settings_list.c (revision 21768) +++ apps/settings_list.c (working copy) @@ -1237,6 +1237,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 21768) +++ apps/dsp.h (working copy) @@ -26,6 +26,8 @@ #include #define NATIVE_FREQUENCY 44100 +#define ATTACK_BUFFER_SIZE 320 /* ~7.3 ms */ +#define MAX_LIMITER_GAIN 120 /* 12 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(int percent); int 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