1# 2# Copyright 2022 Google LLC 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import numpy as np 18import scipy.fftpack as fftpack 19 20import lc3 21import tables as T, appendix_c as C 22 23### ------------------------------------------------------------------------ ### 24 25class Sns: 26 27 def __init__(self, dt, sr): 28 29 self.dt = dt 30 self.sr = sr 31 self.I = T.I[dt][sr] 32 33 (self.ind_lf, self.ind_hf, self.shape, self.gain) = \ 34 (None, None, None, None) 35 36 (self.idx_a, self.ls_a, self.idx_b, self.ls_b) = \ 37 (None, None, None, None) 38 39 def get_data(self): 40 41 data = { 'lfcb' : self.ind_lf, 'hfcb' : self.ind_hf, 42 'shape' : self.shape, 'gain' : self.gain, 43 'idx_a' : self.idx_a, 'ls_a' : self.ls_a } 44 45 if self.idx_b is not None: 46 data.update({ 'idx_b' : self.idx_b, 'ls_b' : self.ls_b }) 47 48 return data 49 50 def get_nbits(self): 51 52 return 38 53 54 def spectral_shaping(self, scf, inv, x): 55 56 ## Scale factors interpolation 57 58 scf_i = np.empty(4*len(scf)) 59 scf_i[0 ] = scf[0] 60 scf_i[1 ] = scf[0] 61 scf_i[2:62:4] = scf[:15] + 1/8 * (scf[1:] - scf[:15]) 62 scf_i[3:63:4] = scf[:15] + 3/8 * (scf[1:] - scf[:15]) 63 scf_i[4:64:4] = scf[:15] + 5/8 * (scf[1:] - scf[:15]) 64 scf_i[5:64:4] = scf[:15] + 7/8 * (scf[1:] - scf[:15]) 65 scf_i[62 ] = scf[15 ] + 1/8 * (scf[15] - scf[14 ]) 66 scf_i[63 ] = scf[15 ] + 3/8 * (scf[15] - scf[14 ]) 67 68 nb = len(self.I) - 1 69 70 if nb < 32: 71 n4 = round(abs(1-32/nb)*nb) 72 n2 = nb - n4 73 74 for i in range(n4): 75 scf_i[i] = np.mean(scf_i[4*i:4*i+4]) 76 77 for i in range(n4, n4+n2): 78 scf_i[i] = np.mean(scf_i[2*n4+2*i:2*n4+2*i+2]) 79 80 scf_i = scf_i[:n4+n2] 81 82 elif nb < 64: 83 n2 = 64 - nb 84 85 for i in range(n2): 86 scf_i[i] = np.mean(scf_i[2*i:2*i+2]) 87 scf_i = np.append(scf_i[:n2], scf_i[2*n2:]) 88 89 g_sns = np.power(2, [ -scf_i, scf_i ][inv]) 90 91 ## Spectral shaping 92 93 y = np.empty(len(x)) 94 I = self.I 95 96 for b in range(nb): 97 y[I[b]:I[b+1]] = x[I[b]:I[b+1]] * g_sns[b] 98 99 return y 100 101 102class SnsAnalysis(Sns): 103 104 def __init__(self, dt, sr): 105 106 super().__init__(dt, sr) 107 108 def compute_scale_factors(self, e, att, nbytes): 109 110 dt = self.dt 111 sr = self.sr 112 hr = self.sr >= T.SRATE_48K_HR 113 114 ## Padding 115 116 if len(e) < 32: 117 n4 = round(abs(1-32/len(e))*len(e)) 118 n2 = len(e) - n4 119 120 e = np.append(np.zeros(3*n4+n2), e) 121 for i in range(n4): 122 e[4*i+0] = e[4*i+1] = \ 123 e[4*i+2] = e[4*i+3] = e[3*n4+n2+i] 124 125 for i in range(2*n4, 2*n4+n2): 126 e[2*i+0] = e[2*i+1] = e[2*n4+n2+i] 127 128 elif len(e) < 64: 129 n2 = 64 - len(e) 130 131 e = np.append(np.empty(n2), e) 132 for i in range(n2): 133 e[2*i+0] = e[2*i+1] = e[n2+i] 134 135 ## Smoothing 136 137 e_s = np.zeros(len(e)) 138 e_s[0 ] = 0.75 * e[0 ] + 0.25 * e[1 ] 139 e_s[1:63] = 0.25 * e[0:62] + 0.5 * e[1:63] + 0.25 * e[2:64] 140 e_s[ 63] = 0.25 * e[ 62] + 0.75 * e[ 63] 141 142 ## Pre-emphasis 143 144 g_tilt = [ 14, 18, 22, 26, 30, 30, 34 ][self.sr] 145 e_p = e_s * (10 ** ((np.arange(64) * g_tilt) / 630)) 146 147 ## Noise floor 148 149 noise_floor = max(np.average(e_p) * (10 ** (-40/10)), 2 ** -32) 150 e_p = np.fmax(e_p, noise_floor * np.ones(len(e))) 151 152 ## Logarithm 153 154 e_l = np.log2(10 ** -31 + e_p) / 2 155 156 ## Band energy grouping 157 158 w = [ 1/12, 2/12, 3/12, 3/12, 2/12, 1/12 ] 159 160 e_4 = np.zeros(len(e_l) // 4) 161 e_4[0 ] = w[0] * e_l[0] + np.sum(w[1:] * e_l[:5]) 162 e_4[1:15] = [ np.sum(w * e_l[4*i-1:4*i+5]) for i in range(1, 15) ] 163 e_4[ 15] = np.sum(w[:5] * e_l[59:64]) + w[5] * e_l[63] 164 165 ## Mean removal and scaling, attack handling 166 167 cf = [ 0.85, 0.6 ][hr] 168 if hr and nbytes * 8 > [ 1150, 2300, 0, 4400 ][self.dt]: 169 cf *= [ 0.25, 0.35 ][ self.dt == T.DT_10M ] 170 171 scf = cf * (e_4 - np.average(e_4)) 172 173 scf_a = np.zeros(len(scf)) 174 scf_a[0 ] = np.mean(scf[:3]) 175 scf_a[1 ] = np.mean(scf[:4]) 176 scf_a[2:14] = [ np.mean(scf[i:i+5]) for i in range(12) ] 177 scf_a[ 14] = np.mean(scf[12:]) 178 scf_a[ 15] = np.mean(scf[13:]) 179 180 scf_a = (0.5 if self.dt != T.DT_7M5 else 0.3) * \ 181 (scf_a - np.average(scf_a)) 182 183 return scf_a if att else scf 184 185 def enum_mpvq(self, v): 186 187 sign = None 188 index = 0 189 x = 0 190 191 for (n, vn) in enumerate(v[::-1]): 192 193 if sign is not None and vn != 0: 194 index = 2*index + sign 195 if vn != 0: 196 sign = 1 if vn < 0 else 0 197 198 index += T.SNS_MPVQ_OFFSETS[n][x] 199 x += abs(vn) 200 201 return (index, bool(sign)) 202 203 def quantize(self, scf): 204 205 ## Stage 1 206 207 dmse_lf = [ np.sum((scf[:8] - T.SNS_LFCB[i]) ** 2) for i in range(32) ] 208 dmse_hf = [ np.sum((scf[8:] - T.SNS_HFCB[i]) ** 2) for i in range(32) ] 209 210 self.ind_lf = np.argmin(dmse_lf) 211 self.ind_hf = np.argmin(dmse_hf) 212 213 st1 = np.append(T.SNS_LFCB[self.ind_lf], T.SNS_HFCB[self.ind_hf]) 214 r1 = scf - st1 215 216 ## Stage 2 217 218 t2_rot = fftpack.dct(r1, norm = 'ortho') 219 x = np.abs(t2_rot) 220 221 ## Stage 2 Shape search, step 1 222 223 K = 6 224 225 proj_fac = (K - 1) / sum(np.abs(t2_rot)) 226 y3 = np.floor(x * proj_fac).astype(int) 227 228 ## Stage 2 Shape search, step 2 229 230 corr_xy = np.sum(y3 * x) 231 energy_y = np.sum(y3 * y3) 232 233 k0 = sum(y3) 234 for k in range(k0, K): 235 q_pvq = ((corr_xy + x) ** 2) / (energy_y + 2*y3 + 1) 236 n_best = np.argmax(q_pvq) 237 238 corr_xy += x[n_best] 239 energy_y += 2*y3[n_best] + 1 240 y3[n_best] += 1 241 242 ## Stage 2 Shape search, step 3 243 244 K = 8 245 246 y2 = y3.copy() 247 248 for k in range(sum(y2), K): 249 q_pvq = ((corr_xy + x) ** 2) / (energy_y + 2*y2 + 1) 250 n_best = np.argmax(q_pvq) 251 252 corr_xy += x[n_best] 253 energy_y += 2*y2[n_best] + 1 254 y2[n_best] += 1 255 256 257 ## Stage 2 Shape search, step 4 258 259 y1 = np.append(y2[:10], [0] * 6) 260 261 ## Stage 2 Shape search, step 5 262 263 corr_xy -= sum(y2[10:] * x[10:]) 264 energy_y -= sum(y2[10:] * y2[10:]) 265 266 ## Stage 2 Shape search, step 6 267 268 K = 10 269 270 for k in range(sum(y1), K): 271 q_pvq = ((corr_xy + x[:10]) ** 2) / (energy_y + 2*y1[:10] + 1) 272 n_best = np.argmax(q_pvq) 273 274 corr_xy += x[n_best] 275 energy_y += 2*y1[n_best] + 1 276 y1[n_best] += 1 277 278 ## Stage 2 Shape search, step 7 279 280 y0 = np.append(y1[:10], [ 0 ] * 6) 281 282 q_pvq = ((corr_xy + x[10:]) ** 2) / (energy_y + 2*y0[10:] + 1) 283 n_best = 10 + np.argmax(q_pvq) 284 285 y0[n_best] += 1 286 287 ## Stage 2 Shape search, step 8 288 289 y0 *= np.sign(t2_rot).astype(int) 290 y1 *= np.sign(t2_rot).astype(int) 291 y2 *= np.sign(t2_rot).astype(int) 292 y3 *= np.sign(t2_rot).astype(int) 293 294 ## Stage 2 Shape search, step 9 295 296 xq = [ y / np.sqrt(sum(y ** 2)) for y in (y0, y1, y2, y3) ] 297 298 ## Shape and gain combination determination 299 300 G = [ T.SNS_VQ_REG_ADJ_GAINS, T.SNS_VQ_REG_LF_ADJ_GAINS, 301 T.SNS_VQ_NEAR_ADJ_GAINS, T.SNS_VQ_FAR_ADJ_GAINS ] 302 303 dMSE = [ [ sum((t2_rot - G[j][i] * xq[j]) ** 2) 304 for i in range(len(G[j])) ] for j in range(4) ] 305 306 self.shape = np.argmin([ np.min(dMSE[j]) for j in range(4) ]) 307 self.gain = np.argmin(dMSE[self.shape]) 308 309 gain = G[self.shape][self.gain] 310 311 ## Enumeration of the selected PVQ pulse configurations 312 313 if self.shape == 0: 314 (self.idx_a, self.ls_a) = self.enum_mpvq(y0[:10]) 315 (self.idx_b, self.ls_b) = self.enum_mpvq(y0[10:]) 316 elif self.shape == 1: 317 (self.idx_a, self.ls_a) = self.enum_mpvq(y1[:10]) 318 (self.idx_b, self.ls_b) = (None, None) 319 elif self.shape == 2: 320 (self.idx_a, self.ls_a) = self.enum_mpvq(y2) 321 (self.idx_b, self.ls_b) = (None, None) 322 elif self.shape == 3: 323 (self.idx_a, self.ls_a) = self.enum_mpvq(y3) 324 (self.idx_b, self.ls_b) = (None, None) 325 326 ## Synthesis of the Quantized scale factor 327 328 scf_q = st1 + gain * fftpack.idct(xq[self.shape], norm = 'ortho') 329 330 return scf_q 331 332 def run(self, eb, att, nbytes, x): 333 334 scf = self.compute_scale_factors(eb, att, nbytes) 335 scf_q = self.quantize(scf) 336 y = self.spectral_shaping(scf_q, False, x) 337 338 return y 339 340 def store(self, b): 341 342 shape = self.shape 343 gain_msb_bits = np.array([ 1, 1, 2, 2 ])[shape] 344 gain_lsb_bits = np.array([ 0, 1, 0, 1 ])[shape] 345 346 b.write_uint(self.ind_lf, 5) 347 b.write_uint(self.ind_hf, 5) 348 349 b.write_bit(shape >> 1) 350 351 b.write_uint(self.gain >> gain_lsb_bits, gain_msb_bits) 352 353 b.write_bit(self.ls_a) 354 355 if self.shape == 0: 356 sz_shape_a = 2390004 357 index_joint = self.idx_a + \ 358 (2 * self.idx_b + self.ls_b + 2) * sz_shape_a 359 360 elif self.shape == 1: 361 sz_shape_a = 2390004 362 index_joint = self.idx_a + (self.gain & 1) * sz_shape_a 363 364 elif self.shape == 2: 365 index_joint = self.idx_a 366 367 elif self.shape == 3: 368 sz_shape_a = 15158272 369 index_joint = sz_shape_a + (self.gain & 1) + 2 * self.idx_a 370 371 b.write_uint(index_joint, 14 - gain_msb_bits) 372 b.write_uint(index_joint >> (14 - gain_msb_bits), 12) 373 374 375class SnsSynthesis(Sns): 376 377 def __init__(self, dt, sr): 378 379 super().__init__(dt, sr) 380 381 def deenum_mpvq(self, index, ls, npulses, n): 382 383 y = np.zeros(n, dtype=np.intc) 384 pos = 0 385 386 for i in range(len(y)-1, -1, -1): 387 388 if index > 0: 389 yi = 0 390 while index < T.SNS_MPVQ_OFFSETS[i][npulses - yi]: yi += 1 391 index -= T.SNS_MPVQ_OFFSETS[i][npulses - yi] 392 else: 393 yi = npulses 394 395 y[pos] = [ yi, -yi ][int(ls)] 396 pos += 1 397 398 npulses -= yi 399 if npulses <= 0: 400 break 401 402 if yi > 0: 403 ls = index & 1 404 index >>= 1 405 406 return y 407 408 def unquantize(self): 409 410 ## SNS VQ Decoding 411 412 y = np.empty(16, dtype=np.intc) 413 414 if self.shape == 0: 415 y[:10] = self.deenum_mpvq(self.idx_a, self.ls_a, 10, 10) 416 y[10:] = self.deenum_mpvq(self.idx_b, self.ls_b, 1, 6) 417 elif self.shape == 1: 418 y[:10] = self.deenum_mpvq(self.idx_a, self.ls_a, 10, 10) 419 y[10:] = np.zeros(6, dtype=np.intc) 420 elif self.shape == 2: 421 y = self.deenum_mpvq(self.idx_a, self.ls_a, 8, 16) 422 elif self.shape == 3: 423 y = self.deenum_mpvq(self.idx_a, self.ls_a, 6, 16) 424 425 ## Unit energy normalization 426 427 y = y / np.sqrt(sum(y ** 2)) 428 429 ## Reconstruction of the quantized scale factors 430 431 G = [ T.SNS_VQ_REG_ADJ_GAINS, T.SNS_VQ_REG_LF_ADJ_GAINS, 432 T.SNS_VQ_NEAR_ADJ_GAINS, T.SNS_VQ_FAR_ADJ_GAINS ] 433 434 gain = G[self.shape][self.gain] 435 436 scf = np.append(T.SNS_LFCB[self.ind_lf], T.SNS_HFCB[self.ind_hf]) \ 437 + gain * fftpack.idct(y, norm = 'ortho') 438 439 return scf 440 441 def load(self, b): 442 443 self.ind_lf = b.read_uint(5) 444 self.ind_hf = b.read_uint(5) 445 446 shape_msb = b.read_bit() 447 448 gain_msb_bits = 1 + shape_msb 449 self.gain = b.read_uint(gain_msb_bits) 450 451 self.ls_a = b.read_bit() 452 453 index_joint = b.read_uint(14 - gain_msb_bits) 454 index_joint |= b.read_uint(12) << (14 - gain_msb_bits) 455 456 if shape_msb == 0: 457 sz_shape_a = 2390004 458 459 if index_joint >= sz_shape_a * 14: 460 raise ValueError('Invalide SNS joint index') 461 462 self.idx_a = index_joint % sz_shape_a 463 index_joint = index_joint // sz_shape_a 464 if index_joint >= 2: 465 self.shape = 0 466 self.idx_b = (index_joint - 2) // 2 467 self.ls_b = (index_joint - 2) % 2 468 else: 469 self.shape = 1 470 self.gain = (self.gain << 1) + (index_joint & 1) 471 472 else: 473 sz_shape_a = 15158272 474 if index_joint >= sz_shape_a + 1549824: 475 raise ValueError('Invalide SNS joint index') 476 477 if index_joint < sz_shape_a: 478 self.shape = 2 479 self.idx_a = index_joint 480 else: 481 self.shape = 3 482 index_joint -= sz_shape_a 483 self.gain = (self.gain << 1) + (index_joint % 2) 484 self.idx_a = index_joint // 2 485 486 def run(self, x): 487 488 scf = self.unquantize() 489 y = self.spectral_shaping(scf, True, x) 490 491 return y 492 493### ------------------------------------------------------------------------ ### 494 495def check_analysis(rng, dt, sr): 496 497 ok = True 498 499 analysis = SnsAnalysis(dt, sr) 500 501 for i in range(10): 502 ne = T.I[dt][sr][-1] 503 x = rng.random(ne) * 1e4 504 e = rng.random(len(T.I[dt][sr]) - 1) * 1e10 505 506 if sr >= T.SRATE_48K_HR: 507 for nbits in (1144, 1152, 2296, 2304, 4400, 4408): 508 y = analysis.run(e, False, nbits // 8, x) 509 data = analysis.get_data() 510 511 (y_c, data_c) = lc3.sns_analyze( 512 dt, sr, nbits // 8, e, False, x) 513 514 for k in data.keys(): 515 ok = ok and data_c[k] == data[k] 516 517 ok = ok and lc3.sns_get_nbits() == analysis.get_nbits() 518 ok = ok and np.amax(np.abs(y - y_c)) < 1e-1 519 520 else: 521 for att in (0, 1): 522 y = analysis.run(e, att, 0, x) 523 data = analysis.get_data() 524 525 (y_c, data_c) = lc3.sns_analyze(dt, sr, 0, e, att, x) 526 527 for k in data.keys(): 528 ok = ok and data_c[k] == data[k] 529 530 ok = ok and lc3.sns_get_nbits() == analysis.get_nbits() 531 ok = ok and np.amax(np.abs(y - y_c)) < 1e-1 532 533 return ok 534 535def check_synthesis(rng, dt, sr): 536 537 ok = True 538 539 synthesis = SnsSynthesis(dt, sr) 540 541 for i in range(100): 542 543 synthesis.ind_lf = rng.integers(0, 32) 544 synthesis.ind_hf = rng.integers(0, 32) 545 546 shape = rng.integers(0, 4) 547 sz_shape_a = [ 2390004, 2390004, 15158272, 774912 ][shape] 548 sz_shape_b = [ 6, 1, 0, 0 ][shape] 549 synthesis.shape = shape 550 synthesis.gain = rng.integers(0, [ 2, 4, 4, 8 ][shape]) 551 synthesis.idx_a = rng.integers(0, sz_shape_a, endpoint=True) 552 synthesis.ls_a = bool(rng.integers(0, 1, endpoint=True)) 553 synthesis.idx_b = rng.integers(0, sz_shape_b, endpoint=True) 554 synthesis.ls_b = bool(rng.integers(0, 1, endpoint=True)) 555 556 ne = T.I[dt][sr][-1] 557 x = rng.random(ne) * 1e4 558 559 y = synthesis.run(x) 560 y_c = lc3.sns_synthesize(dt, sr, synthesis.get_data(), x) 561 ok = ok and np.amax(np.abs(1 - y/y_c)) < 1e-5 562 563 return ok 564 565def check_analysis_appendix_c(dt): 566 567 i0 = dt - T.DT_7M5 568 sr = T.SRATE_16K 569 570 ok = True 571 572 for i in range(len(C.E_B[i0])): 573 574 scf = lc3.sns_compute_scale_factors(dt, sr, 0, C.E_B[i0][i], False) 575 ok = ok and np.amax(np.abs(scf - C.SCF[i0][i])) < 1e-4 576 577 (lf, hf) = lc3.sns_resolve_codebooks(scf) 578 ok = ok and lf == C.IND_LF[i0][i] and hf == C.IND_HF[i0][i] 579 580 (y, yn, shape, gain) = lc3.sns_quantize(scf, lf, hf) 581 ok = ok and np.any(y[0][:16] - C.SNS_Y0[i0][i] == 0) 582 ok = ok and np.any(y[1][:10] - C.SNS_Y1[i0][i] == 0) 583 ok = ok and np.any(y[2][:16] - C.SNS_Y2[i0][i] == 0) 584 ok = ok and np.any(y[3][:16] - C.SNS_Y3[i0][i] == 0) 585 ok = ok and shape == 2*C.SUBMODE_MSB[i0][i] + C.SUBMODE_LSB[i0][i] 586 ok = ok and gain == C.G_IND[i0][i] 587 588 scf_q = lc3.sns_unquantize(lf, hf, yn[shape], shape, gain) 589 ok = ok and np.amax(np.abs(scf_q - C.SCF_Q[i0][i])) < 1e-5 590 591 x = lc3.sns_spectral_shaping(dt, sr, C.SCF_Q[i0][i], False, C.X[i0][i]) 592 ok = ok and np.amax(np.abs(1 - x/C.X_S[i0][i])) < 1e-5 593 594 (x, data) = lc3.sns_analyze(dt, sr, 0, C.E_B[i0][i], False, C.X[i0][i]) 595 ok = ok and data['lfcb'] == C.IND_LF[i0][i] 596 ok = ok and data['hfcb'] == C.IND_HF[i0][i] 597 ok = ok and data['shape'] == 2*C.SUBMODE_MSB[i0][i] + \ 598 C.SUBMODE_LSB[i0][i] 599 ok = ok and data['gain'] == C.G_IND[i0][i] 600 ok = ok and data['idx_a'] == C.IDX_A[i0][i] 601 ok = ok and data['ls_a'] == C.LS_IND_A[i0][i] 602 ok = ok and (C.IDX_B[i0][i] is None or 603 data['idx_b'] == C.IDX_B[i0][i]) 604 ok = ok and (C.LS_IND_B[i0][i] is None or 605 data['ls_b'] == C.LS_IND_B[i0][i]) 606 ok = ok and np.amax(np.abs(1 - x/C.X_S[i0][i])) < 1e-5 607 608 return ok 609 610def check_synthesis_appendix_c(dt): 611 612 i0 = dt - T.DT_7M5 613 sr = T.SRATE_16K 614 615 ok = True 616 617 for i in range(len(C.X_HAT_TNS[i0])): 618 619 data = { 620 'lfcb' : C.IND_LF[i0][i], 'hfcb' : C.IND_HF[i0][i], 621 'shape' : 2*C.SUBMODE_MSB[i0][i] + C.SUBMODE_LSB[i0][i], 622 'gain' : C.G_IND[i0][i], 623 'idx_a' : C.IDX_A[i0][i], 624 'ls_a' : C.LS_IND_A[i0][i], 625 'idx_b' : C.IDX_B[i0][i] if C.IDX_B[i0][i] is not None else 0, 626 'ls_b' : C.LS_IND_B[i0][i] if C.LS_IND_B[i0][i] is not None else 0, 627 } 628 629 x = lc3.sns_synthesize(dt, sr, data, C.X_HAT_TNS[i0][i]) 630 ok = ok and np.amax(np.abs(x - C.X_HAT_SNS[i0][i])) < 1e0 631 632 return ok 633 634def check(): 635 636 rng = np.random.default_rng(1234) 637 ok = True 638 639 for dt in range(T.NUM_DT): 640 for sr in range(T.SRATE_8K, T.SRATE_48K + 1): 641 ok = ok and check_analysis(rng, dt, sr) 642 ok = ok and check_synthesis(rng, dt, sr) 643 644 for dt in ( T.DT_2M5, T.DT_5M, T.DT_10M ): 645 for sr in ( T.SRATE_48K_HR, T.SRATE_96K_HR ): 646 ok = ok and check_analysis(rng, dt, sr) 647 ok = ok and check_synthesis(rng, dt, sr) 648 649 for dt in ( T.DT_7M5, T.DT_10M ): 650 check_analysis_appendix_c(dt) 651 check_synthesis_appendix_c(dt) 652 653 return ok 654 655### ------------------------------------------------------------------------ ### 656