diff options
| -rw-r--r-- | timestretch.c | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/timestretch.c b/timestretch.c new file mode 100644 index 0000000..63ba059 --- /dev/null +++ b/timestretch.c | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | #include <stdlib.h> | ||
| 2 | #include <stdio.h> | ||
| 3 | #include <stdint.h> | ||
| 4 | #include <string.h> | ||
| 5 | #include <float.h> | ||
| 6 | #include <math.h> | ||
| 7 | #include <fcntl.h> | ||
| 8 | #include <unistd.h> | ||
| 9 | #include <inttypes.h> | ||
| 10 | |||
| 11 | // global values | ||
| 12 | static short * g_overlap_buffer; | ||
| 13 | static size_t g_overlap; | ||
| 14 | |||
| 15 | static size_t g_input_length; // Minimal length | ||
| 16 | static size_t g_output_length; | ||
| 17 | static size_t g_corr_length; | ||
| 18 | |||
| 19 | static size_t g_skip; // Per frame skip | ||
| 20 | static size_t g_offset; // Offset into stream, lower bits | ||
| 21 | static size_t g_firstframe; // If this is set, the caller should provide initial data in g_overlap_buffer | ||
| 22 | |||
| 23 | /* some good default for mixing voice, values in micro seconds */ | ||
| 24 | #define OVERLAP 10 // overlapping length (music default = 12 ms) | ||
| 25 | #define CORR_WINDOW 15 // overlapping correlation window length (music default = 28 ms) | ||
| 26 | #define OUTPUT_LEN 30 // one processing sequence length in milliseconds (music default = 82 ms) | ||
| 27 | |||
| 28 | // Returns the length of one output frame | ||
| 29 | static size_t calc_convert_values( int sample_rate, float tempo ) { | ||
| 30 | g_overlap = ( sample_rate * OVERLAP ) / 1000; | ||
| 31 | |||
| 32 | free( g_overlap_buffer ); | ||
| 33 | g_overlap_buffer = malloc( sizeof(short) * g_overlap ); | ||
| 34 | g_firstframe = 1; | ||
| 35 | |||
| 36 | g_output_length = (sample_rate * OUTPUT_LEN ) / 1000; | ||
| 37 | if ( g_output_length < g_overlap) | ||
| 38 | g_output_length = g_overlap; | ||
| 39 | g_corr_length = ( sample_rate * CORR_WINDOW ) / 1000; | ||
| 40 | |||
| 41 | g_skip = (size_t)( tempo * (float)g_output_length * 65536.0 ); | ||
| 42 | g_input_length = g_corr_length + g_output_length + g_overlap; | ||
| 43 | if( g_skip / 65536 > g_input_length ) | ||
| 44 | g_input_length = g_skip / 65536; | ||
| 45 | |||
| 46 | return g_output_length; | ||
| 47 | } | ||
| 48 | |||
| 49 | /* | ||
| 50 | Example: tempo 1.5 with 30/15/10 => skip == 45, out = 30 | ||
| 51 | we found an offset of 5 msec | ||
| 52 | |||
| 53 | [#####OOOOOOOOOOVVVVVVVVVVVVVVVVVVVVmmmmmmmmmm?????????????????????????] | ||
| 54 | ^--skip | ||
| 55 | [###############OOOOOOOOOOVVVVVVVVVVVVVVVVVVVVmmmmmmmmmm???????????????] | ||
| 56 | [ #####OOOOOOOOOOVVVVVVVVVVVVVVVVVVVVmmmmmmmmmm?????????????????????????] | ||
| 57 | */ | ||
| 58 | |||
| 59 | static unsigned int find_corr_max(const short *input, const short *mixbuf) { | ||
| 60 | unsigned int i, j, offs = 0; | ||
| 61 | double corr_max = FLT_MIN; | ||
| 62 | |||
| 63 | // Scans for the best correlation value by testing each possible position | ||
| 64 | for (i = 0; i < g_corr_length; ++i) { | ||
| 65 | double acc, i_f = (double)i, sl_f = (double)g_corr_length; | ||
| 66 | double heur = 0.75 - ( i_f * i_f ) / ( sl_f * sl_f ) + i_f / sl_f; | ||
| 67 | int64_t acc_i; | ||
| 68 | |||
| 69 | for(j = 0, acc_i = 0; j < g_overlap; ++j ) | ||
| 70 | acc_i += input[i+j] * mixbuf[j]; | ||
| 71 | |||
| 72 | // boost middle of sequence by top of a flat parabola | ||
| 73 | // slope stolen from soundtouch | ||
| 74 | acc = (double)acc_i * heur; | ||
| 75 | if ( corr_max < acc ) { | ||
| 76 | offs = i; | ||
| 77 | corr_max = acc; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | // printf( "%03d %014.0lf\n", best_offs, best_corr ); | ||
| 81 | return offs; | ||
| 82 | } | ||
| 83 | |||
| 84 | // Returns the amount of samples that can be discarded from begin of the input buffer | ||
| 85 | size_t process_frame( short *input, short *output, short *overlap ) { | ||
| 86 | int i, i_ = (int)g_overlap; | ||
| 87 | unsigned int offset = 0; | ||
| 88 | |||
| 89 | // The first frame needs to be copied verbatim, | ||
| 90 | // we do not have anything to mix, yet. | ||
| 91 | if( g_firstframe ) { | ||
| 92 | memcpy( output, input, g_output_length * sizeof(short) ); | ||
| 93 | g_firstframe = 0; | ||
| 94 | } else { | ||
| 95 | offset = find_corr_max( input, overlap ); | ||
| 96 | |||
| 97 | // Mix end of last frame with begin of this frame | ||
| 98 | for (i = 0; i < (int)g_overlap ; ++i, --i_ ) | ||
| 99 | output[i] = ( i_ * overlap[i] + i * input[i+offset] ) / (int)g_overlap; | ||
| 100 | |||
| 101 | // Copy rest of the input verbatim | ||
| 102 | memcpy( output + g_overlap, input + offset + g_overlap, ( g_output_length - g_overlap ) * sizeof(short) ); | ||
| 103 | } | ||
| 104 | |||
| 105 | // Remember end of this frame for next frame | ||
| 106 | memcpy( overlap, input + offset + g_output_length, g_overlap * sizeof(short) ); | ||
| 107 | |||
| 108 | // Remove the processed samples from the input buffer. | ||
| 109 | g_offset &= 0xffff; | ||
| 110 | g_offset += g_skip; | ||
| 111 | return g_offset / 65536; | ||
| 112 | } | ||
| 113 | |||
| 114 | int main( int args, char **argv ) { | ||
| 115 | size_t out_chunk_size = calc_convert_values( 8000, 1.25f ); | ||
| 116 | size_t in_fill = 0; | ||
| 117 | short outbuf[ g_output_length ]; | ||
| 118 | short inbuf [ g_input_length ]; | ||
| 119 | |||
| 120 | 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 ); | ||
| 121 | |||
| 122 | int fd_in = open( "in.raw", O_RDONLY ); | ||
| 123 | int fd_out = open( "out.raw", O_CREAT | O_WRONLY | O_TRUNC ); | ||
| 124 | |||
| 125 | (void)args; (void)argv; (void)out_chunk_size; | ||
| 126 | |||
| 127 | while( 1 ) { | ||
| 128 | size_t processed; | ||
| 129 | size_t missing = g_input_length - in_fill; | ||
| 130 | size_t fromfd = read( fd_in, inbuf + in_fill, missing * sizeof(short) ); | ||
| 131 | |||
| 132 | if( fromfd > 0 ) | ||
| 133 | in_fill += fromfd / sizeof(short); | ||
| 134 | if( fromfd != missing * sizeof(short) ) { | ||
| 135 | write( fd_out, inbuf, in_fill * sizeof(short) ); | ||
| 136 | close( fd_in ); close( fd_out ); | ||
| 137 | exit(0); | ||
| 138 | } | ||
| 139 | |||
| 140 | // Do one cycle of processing and outputting | ||
| 141 | processed = process_frame( inbuf, outbuf, g_overlap_buffer ); | ||
| 142 | write( fd_out, outbuf, g_output_length * sizeof(short) ); | ||
| 143 | |||
| 144 | memmove( inbuf, inbuf + processed, ( in_fill - processed ) * sizeof(short) ); | ||
| 145 | in_fill -= processed; | ||
| 146 | } | ||
| 147 | |||
| 148 | return 0; | ||
| 149 | } | ||
