Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
716 | lvd | 1 | #include "../std.h" |
2 | |||
3 | #include "../emul.h" |
||
4 | #include "../vars.h" |
||
5 | |||
6 | /* |
||
7 | sound resampling core for Unreal Speccy project |
||
8 | created under public domain license by SMT, jan.2006 |
||
9 | */ |
||
10 | |||
11 | #include "sndrender.h" |
||
12 | |||
13 | unsigned SNDRENDER::render(SNDOUT *src, unsigned srclen, unsigned clk_ticks, bufptr_t dst_start) |
||
14 | { |
||
15 | start_frame(dst_start); |
||
16 | for (unsigned index = 0; index < srclen; index++) |
||
17 | { |
||
18 | // if (src[index].timestamp > clk_ticks) continue; // wrong input data leads to crash |
||
19 | update(src[index].timestamp, src[index].newvalue.ch.left, src[index].newvalue.ch.right); |
||
20 | } |
||
21 | return end_frame(clk_ticks); |
||
22 | } |
||
23 | |||
24 | const unsigned TICK_F = (1<<TICK_FF); |
||
25 | |||
26 | // b = 1+ln2(max_sndtick) = 1+ln2((max_sndfq*TICK_F)/min_intfq) = 1+ln2(48000*64/10) ~= 19.2; |
||
27 | // assert(b+MULT_C <= 32) |
||
28 | |||
29 | |||
30 | void SNDRENDER::start_frame(bufptr_t dst_start) |
||
31 | { |
||
32 | SNDRENDER::dst_start = dstpos = dst_start; |
||
33 | base_tick = tick; |
||
34 | firstsmp = 4; //Alone Coder |
||
35 | } |
||
36 | |||
37 | void SNDRENDER::update(unsigned timestamp, unsigned l, unsigned r) |
||
38 | { |
||
39 | if (!((l ^ mix_l) | (r ^ mix_r))) // (l == mix_l) && (r == mix_r) |
||
40 | return; |
||
41 | |||
42 | //[vv] unsigned endtick = (timestamp * mult_const) >> MULT_C; |
||
43 | uint64_t endtick = (timestamp * (uint64_t)sample_rate * TICK_F) / clock_rate; |
||
44 | flush( (unsigned) (base_tick + endtick) ); |
||
45 | mix_l = l; mix_r = r; |
||
46 | } |
||
47 | |||
48 | unsigned SNDRENDER::end_frame(unsigned clk_ticks) |
||
49 | { |
||
50 | // adjusting 'clk_ticks' with whole history will fix accumulation of rounding errors |
||
51 | //uint64_t endtick = ((passed_clk_ticks + clk_ticks) * mult_const) >> MULT_C; |
||
52 | uint64_t endtick = ((passed_clk_ticks + clk_ticks) * (uint64_t)sample_rate * TICK_F) / clock_rate; |
||
53 | flush( (unsigned) (endtick - passed_snd_ticks) ); |
||
54 | |||
55 | unsigned ready_samples = dstpos - dst_start; |
||
56 | #ifdef SND_EXTERNAL_BUFFER |
||
57 | if ((int)ready_samples < 0) ready_samples += SND_EXTERNAL_BUFFER_SIZE; |
||
58 | #endif |
||
59 | |||
60 | oldfrmleft = ((long)useleft + (long)olduseleft)/2; //Alone Coder |
||
61 | oldfrmright = ((long)useright + (long)olduseright)/2; //Alone Coder |
||
62 | |||
63 | tick -= (ready_samples << TICK_FF); |
||
64 | passed_snd_ticks += (ready_samples << TICK_FF); |
||
65 | passed_clk_ticks += clk_ticks; |
||
66 | |||
67 | return ready_samples; |
||
68 | } |
||
69 | |||
70 | /* |
||
71 | unsigned SNDRENDER::end_empty_frame(unsigned clk_ticks) |
||
72 | { |
||
73 | // adjusting 'clk_ticks' with whole history will fix accumulation of rounding errors |
||
74 | //uint64_t endtick = ((passed_clk_ticks + clk_ticks) * mult_const) >> MULT_C; |
||
75 | uint64_t endtick = ((passed_clk_ticks + clk_ticks) * (uint64_t)sample_rate * TICK_F) / clock_rate; |
||
76 | tick = (unsigned)(endtick-passed_snd_ticks); // flush(endtick-passed_snd_ticks); |
||
77 | // todo: change dstpos! |
||
78 | |||
79 | unsigned ready_samples = dstpos - dst_start; |
||
80 | #ifdef SND_EXTERNAL_BUFFER |
||
81 | if ((int)ready_samples < 0) ready_samples += SND_EXTERNAL_BUFFER_SIZE; |
||
82 | #endif |
||
83 | |||
84 | tick -= (ready_samples << TICK_FF); |
||
85 | passed_snd_ticks += (ready_samples << TICK_FF); |
||
86 | passed_clk_ticks += clk_ticks; |
||
87 | |||
88 | return ready_samples; |
||
89 | } |
||
90 | */ |
||
91 | |||
92 | void SNDRENDER::set_timings(unsigned clock_rate, unsigned sample_rate) |
||
93 | { |
||
94 | SNDRENDER::clock_rate = clock_rate; |
||
95 | SNDRENDER::sample_rate = sample_rate; |
||
96 | |||
97 | tick = 0; dstpos = dst_start = 0; |
||
98 | passed_snd_ticks = passed_clk_ticks = 0; |
||
99 | |||
784 | DimkaM | 100 | // mult_const = (unsigned) (((uint64_t)sample_rate << (MULT_C+TICK_FF)) / clock_rate); |
716 | lvd | 101 | } |
102 | |||
103 | |||
784 | DimkaM | 104 | static unsigned filter_diff[TICK_F*2]; // discrete-time step response |
716 | lvd | 105 | const double filter_sum_full = 1.0, filter_sum_half = 0.5; |
106 | const unsigned filter_sum_full_u = (unsigned)(filter_sum_full * 0x10000), |
||
107 | filter_sum_half_u = (unsigned)(filter_sum_half * 0x10000); |
||
108 | |||
109 | void SNDRENDER::flush(unsigned endtick) |
||
110 | { |
||
784 | DimkaM | 111 | unsigned scale; |
112 | if(!((endtick ^ tick) & ~(TICK_F - 1))) // (endtick / TICK_F) == (tick / TICK_F) |
||
113 | { // Оптимизация, децимация не нужна, т.к. после децимации tick совпадет с endtick |
||
114 | // Входной сигнал изменился быстрее чем один период Fs выходного сигнала |
||
115 | // s1 - значение выходного сигнала в левом узле децимации |
||
116 | // s2 - значение выходного сигнала в правом узле децимации |
||
117 | // mix - значение входного сигнала в интервале [tick.. endtick) |
||
716 | lvd | 118 | |
784 | DimkaM | 119 | //same discrete as before |
120 | // Правая точка вычисленная по переходной характеристике |
||
121 | scale = filter_diff[(endtick & (TICK_F - 1)) + TICK_F] - filter_diff[(tick & (TICK_F - 1)) + TICK_F]; |
||
122 | s2_l += mix_l * scale; |
||
123 | s2_r += mix_r * scale; |
||
716 | lvd | 124 | |
784 | DimkaM | 125 | // Левая точка вычисленная по переходной характеристике |
126 | scale = filter_diff[endtick & (TICK_F - 1)] - filter_diff[tick & (TICK_F - 1)]; |
||
127 | s1_l += mix_l * scale; |
||
128 | s1_r += mix_r * scale; |
||
716 | lvd | 129 | |
784 | DimkaM | 130 | tick = endtick; |
131 | return; |
||
132 | } |
||
716 | lvd | 133 | |
784 | DimkaM | 134 | // Отрезок [TICK_F + (tick % TICK_F) ... 2*TICK_F-1] |
135 | scale = filter_sum_full_u - filter_diff[(tick & (TICK_F - 1)) + TICK_F]; // filter_sum_full_u = filter_diff[(TICK_F - 1) + TICK_F] |
||
716 | lvd | 136 | |
784 | DimkaM | 137 | unsigned sample_value; |
138 | if(conf.soundfilter) |
||
139 | { |
||
140 | /*lame noise reduction by Alone Coder*/ |
||
141 | int templeft = int(mix_l * scale + s2_l); |
||
142 | /*olduseleft = useleft; |
||
143 | if (firstsmp)useleft=oldfrmleft,firstsmp--; |
||
144 | else*/ useleft = ((long)templeft + (long)oldleft) / 2; |
||
145 | oldleft = templeft; |
||
146 | int tempright = int(mix_r * scale + s2_r); |
||
147 | /*olduseright = useright; |
||
148 | if (firstsmp)useright=oldfrmright,firstsmp--; |
||
149 | else*/ useright = ((long)tempright + (long)oldright) / 2; |
||
150 | oldright = tempright; |
||
151 | sample_value = unsigned((useleft >> 16) + int(unsigned(useright) & 0xFFFF0000)); |
||
152 | /**/ |
||
153 | } |
||
154 | else |
||
155 | { |
||
156 | sample_value = ((mix_l*scale + s2_l) >> 16) + |
||
157 | ((mix_r*scale + s2_r) & 0xFFFF0000); |
||
158 | } |
||
716 | lvd | 159 | |
784 | DimkaM | 160 | #ifdef SND_EXTERNAL_BUFFER |
161 | SND_EXTERNAL_BUFFER[dstpos].sample += sample_value; |
||
162 | dstpos = (dstpos + 1) & (SND_EXTERNAL_BUFFER_SIZE - 1); |
||
163 | #else |
||
164 | dstpos->sample = sample_value; |
||
165 | dstpos++; |
||
166 | #endif |
||
716 | lvd | 167 | |
784 | DimkaM | 168 | // Отрезок [tick % TICK_F ... TICK_F-1] |
169 | scale = filter_sum_half_u - filter_diff[tick & (TICK_F - 1)];// filter_sum_half_u = filter_diff[TICK_F - 1] |
||
170 | s2_l = s1_l + mix_l * scale; |
||
171 | s2_r = s1_r + mix_r * scale; |
||
172 | |||
173 | // Выравнивание до узла децимации |
||
174 | tick = (tick | (TICK_F - 1)) + 1; |
||
175 | |||
176 | // Цикл децимации в TICK_F раз, tick уже выровнен до сетки с шагом TICK_F |
||
177 | // endtick / TICK_F - endtick после децимации в TICK_F раз |
||
178 | // tick / TICK_F - tick после децимации в TICK_F раз |
||
179 | if((endtick ^ tick) & ~(TICK_F - 1)) // (endtick / TICK_F) != (tick / TICK_F) |
||
180 | { // Оптимизация, проверка на несовпадение tick и endtick после децимации |
||
181 | |||
182 | // assume filter_coeff is symmetric |
||
183 | // Отрезок [0 ... TICK_F-1] |
||
184 | unsigned val_l = mix_l * filter_sum_half_u; |
||
185 | unsigned val_r = mix_r * filter_sum_half_u; |
||
186 | |||
187 | // Цикл децимации, [tick/TICK_F ... endtick/TICK_F) |
||
188 | do |
||
189 | { |
||
716 | lvd | 190 | if(conf.soundfilter) |
191 | { |
||
192 | /*lame noise reduction by Alone Coder*/ |
||
784 | DimkaM | 193 | int templeft = int(s2_l + val_l); |
716 | lvd | 194 | /*olduseleft = useleft; |
195 | if (firstsmp)useleft=oldfrmleft,firstsmp--; |
||
784 | DimkaM | 196 | else*/ useleft = ((long)templeft + (long)oldleft) / 2; |
716 | lvd | 197 | oldleft = templeft; |
784 | DimkaM | 198 | int tempright = int(s2_r + val_r); |
716 | lvd | 199 | /*olduseright = useright; |
200 | if (firstsmp)useright=oldfrmright,firstsmp--; |
||
784 | DimkaM | 201 | else*/ useright = ((long)tempright + (long)oldright) / 2; |
716 | lvd | 202 | oldright = tempright; |
784 | DimkaM | 203 | sample_value = unsigned((useleft >> 16) + int(unsigned(useright) & 0xFFFF0000)); |
716 | lvd | 204 | /**/ |
205 | } |
||
206 | else |
||
207 | { |
||
208 | sample_value = ((s2_l + val_l) >> 16) + |
||
784 | DimkaM | 209 | ((s2_r + val_r) & 0xFFFF0000); // save s2+val |
716 | lvd | 210 | } |
211 | |||
784 | DimkaM | 212 | #ifdef SND_EXTERNAL_BUFFER |
716 | lvd | 213 | SND_EXTERNAL_BUFFER[dstpos].sample += sample_value; |
784 | DimkaM | 214 | dstpos = (dstpos + 1) & (SND_EXTERNAL_BUFFER_SIZE - 1); |
215 | #else |
||
716 | lvd | 216 | dstpos->sample = sample_value; |
217 | dstpos++; |
||
784 | DimkaM | 218 | #endif |
716 | lvd | 219 | |
220 | tick += TICK_F; |
||
784 | DimkaM | 221 | s2_l = val_l; |
222 | s2_r = val_r; // s2=s1, s1=0; |
||
716 | lvd | 223 | |
784 | DimkaM | 224 | } while((endtick ^ tick) & ~(TICK_F - 1)); // (endtick / TICK_F) != (tick / TICK_F) |
225 | } |
||
716 | lvd | 226 | |
784 | DimkaM | 227 | tick = endtick; |
716 | lvd | 228 | |
784 | DimkaM | 229 | scale = filter_diff[(endtick & (TICK_F - 1)) + TICK_F] - filter_sum_half_u; // filter_sum_half_u = filter_diff[TICK_F - 1] |
230 | s2_l += mix_l * scale; |
||
231 | s2_r += mix_r * scale; |
||
716 | lvd | 232 | |
784 | DimkaM | 233 | scale = filter_diff[endtick & (TICK_F - 1)]; |
234 | s1_l = mix_l * scale; |
||
235 | s1_r = mix_r * scale; |
||
716 | lvd | 236 | } |
237 | |||
784 | DimkaM | 238 | // bw = 1*(10^-2)*pi rad/smp (-3dB) |
239 | // bw = 2*(10^-2)*pi rad/smp (-10dB) |
||
240 | // bw = 20 kHz (fs=44100 * 64 = 2822400) |
||
241 | // matlab: fvtool(filter_coeff) |
||
242 | |||
243 | // Параметры синтеза фильтра: |
||
244 | // matlab: (fdatool/filterDesigner) |
||
245 | // FIR: Window (Hamming) |
||
246 | // order: 127 |
||
247 | // Fs: 2822400 |
||
248 | // Fc: 11025 |
||
249 | |||
716 | lvd | 250 | const double filter_coeff[TICK_F*2] = |
251 | { |
||
252 | // filter designed with Matlab's DSP toolbox |
||
253 | 0.000797243121022152, 0.000815206499600866, 0.000844792477531490, 0.000886460636664257, |
||
254 | 0.000940630171246217, 0.001007677515787512, 0.001087934129054332, 0.001181684445143001, |
||
255 | 0.001289164001921830, 0.001410557756409498, 0.001545998595893740, 0.001695566052785407, |
||
256 | 0.001859285230354019, 0.002037125945605404, 0.002229002094643918, 0.002434771244914945, |
||
257 | 0.002654234457752337, 0.002887136343664226, 0.003133165351783907, 0.003391954293894633, |
||
258 | 0.003663081102412781, 0.003946069820687711, 0.004240391822953223, 0.004545467260249598, |
||
259 | 0.004860666727631453, 0.005185313146989532, 0.005518683858848785, 0.005860012915564928, |
||
260 | 0.006208493567431684, 0.006563280932335042, 0.006923494838753613, 0.007288222831108771, |
||
261 | 0.007656523325719262, 0.008027428904915214, 0.008399949736219575, 0.008773077102914008, |
||
262 | 0.009145787031773989, 0.009517044003286715, 0.009885804729257883, 0.010251021982371376, |
||
263 | 0.010611648461991030, 0.010966640680287394, 0.011314962852635887, 0.011655590776166550, |
||
264 | 0.011987515680350414, 0.012309748033583185, 0.012621321289873522, 0.012921295559959939, |
||
265 | 0.013208761191466523, 0.013482842243062109, 0.013742699838008606, 0.013987535382970279, |
||
266 | 0.014216593638504731, 0.014429165628265581, 0.014624591374614174, 0.014802262449059521, |
||
267 | 0.014961624326719471, 0.015102178534818147, 0.015223484586101132, 0.015325161688957322, |
||
268 | 0.015406890226980602, 0.015468413001680802, 0.015509536233058410, 0.015530130313785910, |
||
269 | 0.015530130313785910, 0.015509536233058410, 0.015468413001680802, 0.015406890226980602, |
||
270 | 0.015325161688957322, 0.015223484586101132, 0.015102178534818147, 0.014961624326719471, |
||
271 | 0.014802262449059521, 0.014624591374614174, 0.014429165628265581, 0.014216593638504731, |
||
272 | 0.013987535382970279, 0.013742699838008606, 0.013482842243062109, 0.013208761191466523, |
||
273 | 0.012921295559959939, 0.012621321289873522, 0.012309748033583185, 0.011987515680350414, |
||
274 | 0.011655590776166550, 0.011314962852635887, 0.010966640680287394, 0.010611648461991030, |
||
275 | 0.010251021982371376, 0.009885804729257883, 0.009517044003286715, 0.009145787031773989, |
||
276 | 0.008773077102914008, 0.008399949736219575, 0.008027428904915214, 0.007656523325719262, |
||
277 | 0.007288222831108771, 0.006923494838753613, 0.006563280932335042, 0.006208493567431684, |
||
278 | 0.005860012915564928, 0.005518683858848785, 0.005185313146989532, 0.004860666727631453, |
||
279 | 0.004545467260249598, 0.004240391822953223, 0.003946069820687711, 0.003663081102412781, |
||
280 | 0.003391954293894633, 0.003133165351783907, 0.002887136343664226, 0.002654234457752337, |
||
281 | 0.002434771244914945, 0.002229002094643918, 0.002037125945605404, 0.001859285230354019, |
||
282 | 0.001695566052785407, 0.001545998595893740, 0.001410557756409498, 0.001289164001921830, |
||
283 | 0.001181684445143001, 0.001087934129054332, 0.001007677515787512, 0.000940630171246217, |
||
284 | 0.000886460636664257, 0.000844792477531490, 0.000815206499600866, 0.000797243121022152 |
||
285 | }; |
||
286 | |||
784 | DimkaM | 287 | // h - impulse response |
288 | // s - step response |
||
289 | // h[n] = s[n] - s[n-1] |
||
290 | |||
291 | // y[n] = sum(x[k]*h[n-k]) = sum(h[k]*x[n-k]) |
||
292 | // k k |
||
293 | // Для кусочно постоянного сигнала применима оптимизация в вычислениях (нет необходимости вычислять промежуточные точки) |
||
294 | // y[n] = y[k] + x*(s[n] - s[k]) |
||
295 | // где n и k - концы интервала, на котором сигнал x постоянен |
||
296 | |||
297 | // Общие формулы: |
||
298 | // u - unit step |
||
299 | // s - step response |
||
300 | // inf |
||
301 | // x[n] = x[-inf] + sum((x[k]-x[k-1])u[n-k]) |
||
302 | // -inf |
||
303 | // |
||
304 | // inf |
||
305 | // y[n] = x[-inf]s[inf] + sum(x[k]-x[k-1])s[n-k] |
||
306 | // -inf |
||
307 | // Из формкл видно, что значения имеют только фронты сигнала, там где сигнал постоянный слагаеые обращаются в ноль |
||
308 | // отсюда становится возможна кусочно-постоянная оптимизация |
||
309 | |||
310 | // Для YM (значения берутся из конфига) |
||
311 | // для примера: |
||
312 | // clock_rate = 1750000 / 8 = 218750 |
||
313 | // 1774400 / 8 = 221800 |
||
314 | // sample_rate = 44100 |
||
315 | // ПЧ = sample_rate * 64 = 2822400 |
||
316 | // Перенос входного сигнала на ПЧ делается домножением на (sample_rate*64)/clock_rate = |
||
317 | // 12.9024 (для clock_rate = 1.7500) и 12.724977 (для clock_rate = 1.7744) |
||
318 | // На ПЧ делается фильтрация и децимация в 64 раза до sample_rate |
||
319 | |||
716 | lvd | 320 | SNDRENDER::SNDRENDER() |
321 | { |
||
322 | set_timings(SNDR_DEFAULT_SYSTICK_RATE, SNDR_DEFAULT_SAMPLE_RATE); |
||
323 | |||
324 | static char diff_ready = 0; |
||
325 | if (!diff_ready) { |
||
326 | double sum = 0; |
||
784 | DimkaM | 327 | for (unsigned i = 0; i < TICK_F*2; i++) { // calculate discrete-time step response |
328 | filter_diff[i] = unsigned(sum * 0x10000); |
||
329 | sum += filter_coeff[i]; // Тут ошибка, сумму надо вычислять до построения переходной характеристики, чтобы filter_diff[0] == filter_coeff[0] |
||
330 | // filter_diff[TICK_F - 1] == 0.5, filter_diff[2*TICK_F - 1] == 1.0 |
||
716 | lvd | 331 | } |
332 | diff_ready = 1; |
||
333 | } |
||
334 | } |