Index: apps/pcmbuf.c =================================================================== --- apps/pcmbuf.c (revision 21477) +++ 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/plugins/mpegplayer/audio_thread.c =================================================================== --- apps/plugins/mpegplayer/audio_thread.c (revision 21477) +++ 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/dsp.c =================================================================== --- apps/dsp.c (revision 21477) +++ apps/dsp.c (working copy) @@ -34,6 +34,10 @@ #include "tdspeed.h" #include "buffer.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. */ @@ -147,8 +151,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[]); - /* ***************************************************************************/ @@ -178,6 +182,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; + }; /* General DSP config */ @@ -243,7 +249,21 @@ #define RESAMPLE_BUF_LEFT_CHANNEL 0 #define RESAMPLE_BUF_RIGHT_CHANNEL (sample_buf_count/2 * RESAMPLE_RATIO) +/* attack buffer */ +static int32_t attack_buffer[2][ATTACK_BUFFER_SIZE]; +static int32_t *start_attack[2], *end_attack[2]; +static bool attack_buffer_full; +static bool attack_buffer_emptying; +static int count_adjust; +static void dsp_clear_attack_buffer(void); +static int set_attack_buffer(int count, int32_t *ext_buf[]); +static int attack_buffer_count(bool empty_count); + + + + + /* Clip sample to signed 16 bit range */ static inline int32_t clip_sample_16(int32_t sample) { @@ -475,13 +495,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 */ @@ -495,12 +514,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 */ @@ -1192,18 +1210,25 @@ /* 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) + { + sample_input_gt_native_ni_stereo(0, src, tmp); + } + 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; @@ -1213,27 +1238,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 */ + + + + dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst); written += chunk; @@ -1280,6 +1317,17 @@ */ if (count > RESAMPLE_BUF_RIGHT_CHANNEL) count = RESAMPLE_BUF_RIGHT_CHANNEL; + + /* If the attack buffer is filling, expect some or all + * samples to 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; } @@ -1289,6 +1337,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) @@ -1383,7 +1438,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; @@ -1406,6 +1462,10 @@ 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; + dsp_clear_attack_buffer(); break; case DSP_FLUSH: @@ -1414,6 +1474,7 @@ resampler_new_delta(dsp); dither_init(dsp); tdspeed_setup(dsp); + dsp_clear_attack_buffer(); break; case DSP_SET_TRACK_GAIN: @@ -1492,3 +1553,149 @@ 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 dsp_clear_attack_buffer(void) +{ + if (AUDIO_DSP.set_attack_buffer == NULL) return; + int i; + logf("dsp_clear_attack_buffer"); + for (i = 0; i < 2; i++) + start_attack[i] = end_attack[i] = attack_buffer[i]; + 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 *ext_buf[]) +{ + int32_t *ext_buf_start[2], temp; + int empty_space, + i, + out_count = 0; + + ext_buf_start[0] = ext_buf[0]; + ext_buf_start[1] = ext_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++) + { + *ext_buf[i]++ = *start_attack[i]++; + if (start_attack[i] == &attack_buffer[i][ATTACK_BUFFER_SIZE]) + start_attack[i] = attack_buffer[i]; + } + } + dsp_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", count); + do + { + for (i = 0; i < 2; i++) + { + *end_attack[i]++ = *ext_buf[i]++; + if (end_attack[i] == &attack_buffer[i][ATTACK_BUFFER_SIZE]) + end_attack[i] = attack_buffer[i]; + } + 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 */ + if (!attack_buffer_full) + logf("Attack buffer cycle error: %d / %d", + attack_buffer_count(false), ATTACK_BUFFER_SIZE); + out_count = count; + do + { + for (i = 0; i < 2; i++) + { + /* copy incoming sample */ + temp = *ext_buf[i]; + /* put attack buffer sample into outbound buffer */ + *ext_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]; + } + } + while (--count > 0); + } + } + + ext_buf[0] = ext_buf_start[0]; + ext_buf[1] = ext_buf_start[1]; + 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; + + char src_buf[2][ATTACK_BUFFER_SIZE * sizeof(int32_t)]; + const char *src[2]; + src[0] = src_buf[0]; + src[1] = src_buf[1]; + + attack_buffer_emptying = true; + + return dsp_process(&AUDIO_DSP, dest, src, 0); +} Index: apps/dsp.h =================================================================== --- apps/dsp.h (revision 21477) +++ apps/dsp.h (working copy) @@ -26,6 +26,11 @@ #include #define NATIVE_FREQUENCY 44100 + +#define ATTACK_BUFFER_SIZE 512 /* ~11.6 ms */ + +#define MAX_LIMITER_GAIN 200 /* 20 dB */ + enum { STEREO_INTERLEAVED = 0, @@ -53,7 +58,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 { @@ -168,5 +177,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