From 9ae78b5594264005979cfa56b364e634b2fe168a Mon Sep 17 00:00:00 2001 From: erdgeist <> Date: Thu, 28 Mar 2013 00:27:22 +0000 Subject: time stretching --- timestretch.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 timestretch.c diff --git a/timestretch.c b/timestretch.c new file mode 100644 index 0000000..63ba059 --- /dev/null +++ b/timestretch.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// global values +static short * g_overlap_buffer; +static size_t g_overlap; + +static size_t g_input_length; // Minimal length +static size_t g_output_length; +static size_t g_corr_length; + +static size_t g_skip; // Per frame skip +static size_t g_offset; // Offset into stream, lower bits +static size_t g_firstframe; // If this is set, the caller should provide initial data in g_overlap_buffer + +/* some good default for mixing voice, values in micro seconds */ +#define OVERLAP 10 // overlapping length (music default = 12 ms) +#define CORR_WINDOW 15 // overlapping correlation window length (music default = 28 ms) +#define OUTPUT_LEN 30 // one processing sequence length in milliseconds (music default = 82 ms) + +// Returns the length of one output frame +static size_t calc_convert_values( int sample_rate, float tempo ) { + g_overlap = ( sample_rate * OVERLAP ) / 1000; + + free( g_overlap_buffer ); + g_overlap_buffer = malloc( sizeof(short) * g_overlap ); + g_firstframe = 1; + + g_output_length = (sample_rate * OUTPUT_LEN ) / 1000; + if ( g_output_length < g_overlap) + g_output_length = g_overlap; + g_corr_length = ( sample_rate * CORR_WINDOW ) / 1000; + + g_skip = (size_t)( tempo * (float)g_output_length * 65536.0 ); + g_input_length = g_corr_length + g_output_length + g_overlap; + if( g_skip / 65536 > g_input_length ) + g_input_length = g_skip / 65536; + + return g_output_length; +} + +/* + Example: tempo 1.5 with 30/15/10 => skip == 45, out = 30 +we found an offset of 5 msec + + [#####OOOOOOOOOOVVVVVVVVVVVVVVVVVVVVmmmmmmmmmm?????????????????????????] + ^--skip + [###############OOOOOOOOOOVVVVVVVVVVVVVVVVVVVVmmmmmmmmmm???????????????] + [ #####OOOOOOOOOOVVVVVVVVVVVVVVVVVVVVmmmmmmmmmm?????????????????????????] +*/ + +static unsigned int find_corr_max(const short *input, const short *mixbuf) { + unsigned int i, j, offs = 0; + double corr_max = FLT_MIN; + + // Scans for the best correlation value by testing each possible position + for (i = 0; i < g_corr_length; ++i) { + double acc, i_f = (double)i, sl_f = (double)g_corr_length; + double heur = 0.75 - ( i_f * i_f ) / ( sl_f * sl_f ) + i_f / sl_f; + int64_t acc_i; + + for(j = 0, acc_i = 0; j < g_overlap; ++j ) + acc_i += input[i+j] * mixbuf[j]; + + // boost middle of sequence by top of a flat parabola + // slope stolen from soundtouch + acc = (double)acc_i * heur; + if ( corr_max < acc ) { + offs = i; + corr_max = acc; + } + } +// printf( "%03d %014.0lf\n", best_offs, best_corr ); + return offs; +} + +// Returns the amount of samples that can be discarded from begin of the input buffer +size_t process_frame( short *input, short *output, short *overlap ) { + int i, i_ = (int)g_overlap; + unsigned int offset = 0; + + // The first frame needs to be copied verbatim, + // we do not have anything to mix, yet. + if( g_firstframe ) { + memcpy( output, input, g_output_length * sizeof(short) ); + g_firstframe = 0; + } else { + offset = find_corr_max( input, overlap ); + + // Mix end of last frame with begin of this frame + for (i = 0; i < (int)g_overlap ; ++i, --i_ ) + output[i] = ( i_ * overlap[i] + i * input[i+offset] ) / (int)g_overlap; + + // Copy rest of the input verbatim + memcpy( output + g_overlap, input + offset + g_overlap, ( g_output_length - g_overlap ) * sizeof(short) ); + } + + // Remember end of this frame for next frame + memcpy( overlap, input + offset + g_output_length, g_overlap * sizeof(short) ); + + // Remove the processed samples from the input buffer. + g_offset &= 0xffff; + g_offset += g_skip; + return g_offset / 65536; +} + +int main( int args, char **argv ) { + size_t out_chunk_size = calc_convert_values( 8000, 1.25f ); + size_t in_fill = 0; + short outbuf[ g_output_length ]; + short inbuf [ g_input_length ]; + + printf( "DEBUG: OL: %zd SWL: %zd SL: %zd SK: %zd ICM: %zd\n", g_overlap, g_output_length, g_corr_length, g_skip / 65536, g_input_length ); + + int fd_in = open( "in.raw", O_RDONLY ); + int fd_out = open( "out.raw", O_CREAT | O_WRONLY | O_TRUNC ); + + (void)args; (void)argv; (void)out_chunk_size; + + while( 1 ) { + size_t processed; + size_t missing = g_input_length - in_fill; + size_t fromfd = read( fd_in, inbuf + in_fill, missing * sizeof(short) ); + + if( fromfd > 0 ) + in_fill += fromfd / sizeof(short); + if( fromfd != missing * sizeof(short) ) { + write( fd_out, inbuf, in_fill * sizeof(short) ); + close( fd_in ); close( fd_out ); + exit(0); + } + + // Do one cycle of processing and outputting + processed = process_frame( inbuf, outbuf, g_overlap_buffer ); + write( fd_out, outbuf, g_output_length * sizeof(short) ); + + memmove( inbuf, inbuf + processed, ( in_fill - processed ) * sizeof(short) ); + in_fill -= processed; + } + + return 0; +} -- cgit v1.2.3