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