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 | } | ||