First release

This commit is contained in:
Antoine SOULIER
2022-04-11 15:41:45 +02:00
commit 064108b972
66 changed files with 24196 additions and 0 deletions
+4083
View File
File diff suppressed because it is too large Load Diff
+185
View File
@@ -0,0 +1,185 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import build.lc3 as lc3
import tables as T, appendix_c as C
### ------------------------------------------------------------------------ ###
class AttackDetector:
def __init__(self, dt, sr):
self.dt = dt
self.sr = sr
self.ms = T.DT_MS[dt]
self.xn1 = 0
self.xn2 = 0
self.en1 = 0
self.an1 = 0
self.p_att = 0
def is_enabled(self, nbytes):
c1 = self.dt == T.DT_10M and \
self.sr == T.SRATE_32K and nbytes > 80
c2 = self.dt == T.DT_10M and \
self.sr >= T.SRATE_48K and nbytes >= 100
c3 = self.dt == T.DT_7M5 and \
self.sr == T.SRATE_32K and nbytes >= 61 and nbytes < 150
c4 = self.dt == T.DT_7M5 and \
self.sr >= T.SRATE_48K and nbytes >= 75 and nbytes < 150
return c1 or c2 or c3 or c4
def run(self, nbytes, x):
### 3.3.6.2 Downsampling and filtering input
mf = int(16 * self.ms)
r = len(x) // mf
x_att = np.array([ np.sum(x[i*r:(i+1)*r]) for i in range(mf) ])
x_hp = np.empty(mf)
x_hp[0 ] = 0.375 * x_att[0 ] - 0.5 * self.xn1 + 0.125 * self.xn2
x_hp[1 ] = 0.375 * x_att[1 ] - 0.5 * x_att[0 ] + 0.125 * self.xn1
x_hp[2:] = 0.375 * x_att[2:] - 0.5 * x_att[1:-1] + 0.125 * x_att[0:-2]
self.xn2 = x_att[-2]
self.xn1 = x_att[-1]
### 3.3.6.3 Energy calculation
nb = int(self.ms / 2.5)
e_att = np.array([ np.sum(np.square(x_hp[40*i:40*(i+1)]))
for i in range(nb) ])
a_att = np.empty(nb)
a_att[0] = np.maximum(0.25 * self.an1, self.en1)
for i in range(1,nb):
a_att[i] = np.maximum(0.25 * a_att[i-1], e_att[i-1])
self.en1 = e_att[-1]
self.an1 = a_att[-1]
### 3.3.6.4 Attack Detection
p_att = -1
flags = [ (e_att[i] > 8.5 * a_att[i]) for i in range(nb) ]
for (i, f) in enumerate(flags):
if f: p_att = i
f_att = p_att >= 0 or self.p_att - 1 >= nb // 2
self.p_att = 1 + p_att
return self.is_enabled(nbytes) and f_att
def initial_state():
return { 'en1': 0.0, 'an1': 0.0, 'p_att': 0 }
### ------------------------------------------------------------------------ ###
def check_enabling(rng, dt):
ok = True
for sr in range(T.SRATE_16K, T.NUM_SRATE):
attdet = AttackDetector(dt, sr)
for nbytes in [ 61, 61-1, 75-1, 75, 80, 80+1, 100-1, 100, 150-1, 150 ]:
f_att = lc3.attdet_run(dt, sr, nbytes,
initial_state(), 2 * rng.random(T.NS[dt][sr]+6) - 1)
ok = ok and f_att == attdet.is_enabled(nbytes)
return ok
def check_unit(rng, dt, sr):
ns = T.NS[dt][sr]
ok = True
attdet = AttackDetector(dt, sr)
state_c = initial_state()
x_c = np.zeros(ns+6)
for run in range(100):
### Generate noise, and an attack at random point
x = (2 * rng.random(ns)) - 1
x[(ns * rng.random()).astype(int)] *= 100
### Check Implementation
f_att = attdet.run(100, x)
x_c = np.append(x_c[-6:], x)
f_att_c = lc3.attdet_run(dt, sr, 100, state_c, x_c)
ok = ok and f_att_c == f_att
ok = ok and np.amax(np.abs(1 - state_c['en1']/attdet.en1)) < 1e-6
ok = ok and np.amax(np.abs(1 - state_c['an1']/attdet.an1)) < 1e-6
ok = ok and state_c['p_att'] == attdet.p_att
return ok
def check_appendix_c(dt):
sr = T.SRATE_48K
state = initial_state()
x = np.append(np.zeros(6), C.X_PCM_ATT[dt][0])
f_att = lc3.attdet_run(dt, sr, C.NBYTES_ATT[dt], state, x)
ok = f_att == C.F_ATT[dt][0]
x = np.append(x[-6:], C.X_PCM_ATT[dt][1])
f_att = lc3.attdet_run(dt, sr, C.NBYTES_ATT[dt], state, x)
ok = f_att == C.F_ATT[dt][1]
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
ok and check_enabling(rng, dt)
for dt in range(T.NUM_DT):
for sr in range(T.SRATE_32K, T.NUM_SRATE):
ok = ok and check_unit(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+62
View File
@@ -0,0 +1,62 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <attdet.c>
#include "ctypes.h"
static PyObject *attdet_run_py(PyObject *m, PyObject *args)
{
unsigned dt, sr, nbytes;
PyObject *attdet_obj, *x_obj;
struct lc3_attdet_analysis attdet = { 0 };
float *x;
if (!PyArg_ParseTuple(args, "IIIOO",
&dt, &sr, &nbytes, &attdet_obj, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
CTYPES_CHECK(NULL, attdet_obj = to_attdet_analysis(attdet_obj, &attdet));
int ns = LC3_NS(dt, sr);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ns+6, &x));
int att = lc3_attdet_run(dt, sr, nbytes, &attdet, x+6);
from_attdet_analysis(attdet_obj, &attdet);
return Py_BuildValue("i", att);
}
static PyMethodDef methods[] = {
{ "attdet_run", attdet_run_py, METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_attdet_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+240
View File
@@ -0,0 +1,240 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import math
class Bitstream:
def __init__(self, data):
self.bytes = data
self.bp_bw = len(data) - 1
self.mask_bw = 1
self.bp = 0
self.low = 0
self.range = 0xffffff
def dump(self):
b = self.bytes
for i in range(0, len(b), 20):
print(''.join('{:02x} '.format(x)
for x in b[i:min(i+20, len(b))] ))
class BitstreamReader(Bitstream):
def __init__(self, data):
super().__init__(data)
self.low = ( (self.bytes[0] << 16) |
(self.bytes[1] << 8) |
(self.bytes[2] ) )
self.bp = 3
def read_bit(self):
bit = bool(self.bytes[self.bp_bw] & self.mask_bw)
self.mask_bw <<= 1
if self.mask_bw == 0x100:
self.mask_bw = 1
self.bp_bw -= 1
return bit
def read_uint(self, nbits):
val = 0
for k in range(nbits):
val |= self.read_bit() << k
return val
def ac_decode(self, cum_freqs, sym_freqs):
r = self.range >> 10
if self.low >= r << 10:
raise ValueError('Invalid ac bitstream')
val = len(cum_freqs) - 1
while self.low < r * cum_freqs[val]:
val -= 1
self.low -= r * cum_freqs[val]
self.range = r * sym_freqs[val]
while self.range < 0x10000:
self.range <<= 8
self.low <<= 8
self.low &= 0xffffff
self.low += self.bytes[self.bp]
self.bp += 1
return val
def get_bits_left(self):
nbits = 8 * len(self.bytes)
nbits_bw = nbits - \
(8*self.bp_bw + 8 - int(math.log2(self.mask_bw)))
nbits_ac = 8 * (self.bp - 3) + \
(25 - int(math.floor(math.log2(self.range))))
return nbits - (nbits_bw + nbits_ac)
class BitstreamWriter(Bitstream):
def __init__(self, nbytes):
super().__init__(bytearray(nbytes))
self.cache = -1
self.carry = 0
self.carry_count = 0
def write_bit(self, bit):
mask = self.mask_bw
bp = self.bp_bw
if bit == 0:
self.bytes[bp] &= ~mask
else:
self.bytes[bp] |= mask
self.mask_bw <<= 1
if self.mask_bw == 0x100:
self.mask_bw = 1
self.bp_bw -= 1
def write_uint(self, val, nbits):
for k in range(nbits):
self.write_bit(val & 1)
val >>= 1
def ac_shift(self):
if self.low < 0xff0000 or self.carry == 1:
if self.cache >= 0:
self.bytes[self.bp] = self.cache + self.carry
self.bp += 1
while self.carry_count > 0:
self.bytes[self.bp] = (self.carry + 0xff) & 0xff
self.bp += 1
self.carry_count -= 1
self.cache = self.low >> 16
self.carry = 0
else:
self.carry_count += 1
self.low <<= 8
self.low &= 0xffffff
def ac_encode(self, cum_freq, sym_freq):
r = self.range >> 10
self.low += r * cum_freq
if (self.low >> 24) != 0:
self.carry = 1
self.low &= 0xffffff
self.range = r * sym_freq
while self.range < 0x10000:
self.range <<= 8;
self.ac_shift()
def get_bits_left(self):
nbits = 8 * len(self.bytes)
nbits_bw = nbits - \
(8*self.bp_bw + 8 - int(math.log2(self.mask_bw)))
nbits_ac = 8 * self.bp + (25 - int(math.floor(math.log2(self.range))))
if self.cache >= 0:
nbits_ac += 8
if self.carry_count > 0:
nbits_ac += 8 * self.carry_count
return nbits - (nbits_bw + nbits_ac)
def terminate(self):
bits = 1
while self.range >> (24 - bits) == 0:
bits += 1
mask = 0xffffff >> bits;
val = self.low + mask;
over1 = val >> 24
val &= 0x00ffffff
high = self.low + self.range
over2 = high >> 24
high &= 0x00ffffff
val = val & ~mask
if over1 == over2:
if val + mask >= high:
bits += 1
mask >>= 1
val = ((self.low + mask) & 0x00ffffff) & ~mask
if val < self.low:
self.carry = 1
self.low = val
while bits > 0:
self.ac_shift()
bits -= 8
bits += 8;
val = self.cache
if self.carry_count > 0:
self.bytes[self.bp] = self.cache
self.bp += 1
while self.carry_count > 1:
self.bytes[self.bp] = 0xff
self.bp += 1
self.carry_count -= 1
val = 0xff >> (8 - bits)
mask = 0x80;
for k in range(bits):
if val & mask == 0:
self.bytes[self.bp] &= ~mask
else:
self.bytes[self.bp] |= mask
mask >>= 1
return self.bytes
+162
View File
@@ -0,0 +1,162 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import build.lc3 as lc3
import tables as T, appendix_c as C
BW_START = [
[ [], [ 51 ], [ 45, 58 ], [ 42, 53, 60 ], [ 40, 51, 57, 61 ] ],
[ [], [ 53 ], [ 47, 59 ], [ 44, 54, 60 ], [ 41, 51, 57, 61 ] ]
]
BW_STOP = [
[ [], [ 63 ], [ 55, 63 ], [ 51, 58, 63 ], [ 48, 55, 60, 63 ] ],
[ [], [ 63 ], [ 56, 63 ], [ 52, 59, 63 ], [ 49, 55, 60, 63 ] ]
]
TQ = [ 20, 10, 10, 10 ]
TC = [ 15, 23, 20, 20 ]
L = [ [ 4, 4, 3, 2 ], [ 4, 4, 3, 1 ] ]
### ------------------------------------------------------------------------ ###
class BandwidthDetector:
def __init__(self, dt, sr):
self.dt = dt
self.sr = sr
def run(self, e):
dt = self.dt
sr = self.sr
### Stage 1, determine bw0 candidate
bw0 = 0
for bw in range(sr):
i0 = BW_START[dt][sr][bw]
i1 = BW_STOP[dt][sr][bw]
if np.mean(e[i0:i1+1]) >= TQ[bw]:
bw0 = bw + 1
### Stage 2, Cut-off random coefficients at each steps
bw = bw0
if bw0 < sr:
l = L[dt][bw0]
i0 = BW_START[dt][sr][bw0] - l
i1 = BW_START[dt][sr][bw0]
c = 10 * np.log10(1e-31 + e[i0-l+1:i1-l+2] / e[i0+1:i1+2])
if np.amax(c) <= TC[bw0]:
bw = sr
self.bw = bw
return self.bw
def get_nbits(self):
return 0 if self.sr == 0 else \
1 + np.log2(self.sr).astype(int)
def store_bw(self, b):
b.write_uint(self.bw, self.get_nbits())
def get_bw(self, b):
return b.read_uint(self.get_nbits())
### ------------------------------------------------------------------------ ###
def check_unit(rng, dt, sr):
ok = True
bwdet = BandwidthDetector(dt, sr)
for bw0 in range(sr+1):
for drop in range(10):
### Generate random 'high' energy and
### scale relevant bands to select 'bw0'
e = 20 + 100 * rng.random(64)
for i in range(sr):
if i+1 != bw0:
i0 = BW_START[dt][sr][i]
i1 = BW_STOP[dt][sr][i]
e[i0:i1+1] /= (np.mean(e[i0:i1+1]) / TQ[i] + 1e-3)
### Stage 2 Condition,
### cut-off random coefficients at each steps
if bw0 < sr:
l = L[dt][bw0]
i0 = BW_START[dt][sr][bw0] - l
i1 = BW_START[dt][sr][bw0]
e[i0-l+1:i1+2] /= np.power(10, np.arange(2*l+1) / (1 + drop))
### Check with implementation
bw_c = lc3.bwdet_run(dt, sr, e)
ok = ok and bw_c == bwdet.run(e)
return ok
def check_appendix_c(dt):
sr = T.SRATE_16K
ok = True
E_B = C.E_B[dt]
P_BW = C.P_BW[dt]
bw = lc3.bwdet_run(dt, sr, E_B[0])
ok = ok and bw == P_BW[0]
bw = lc3.bwdet_run(dt, sr, E_B[1])
ok = ok and bw == P_BW[1]
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_unit(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+55
View File
@@ -0,0 +1,55 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <bwdet.c>
#include "ctypes.h"
static PyObject *bwdet_run_py(PyObject *m, PyObject *args)
{
unsigned dt, sr;
PyObject *e_obj;
float *e;
if (!PyArg_ParseTuple(args, "IIO", &dt, &sr, &e_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
CTYPES_CHECK("e", to_1d_ptr(e_obj, NPY_FLOAT, LC3_NUM_BANDS, &e));
int bw = lc3_bwdet_run(dt, sr, e);
return Py_BuildValue("i", bw);
}
static PyMethodDef methods[] = {
{ "bwdet_run", bwdet_run_py, METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_bwdet_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+869
View File
@@ -0,0 +1,869 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#ifndef __CTYPES_H
#define __CTYPES_H
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <stdbool.h>
#define CTYPES_CHECK(exc, t) \
do { \
if (!(t)) return (exc) ? PyErr_Format(PyExc_TypeError, exc) : NULL; \
} while(0)
/**
* From C types to Numpy Array types
*/
#define to_scalar(obj, t, ptr) \
__to_scalar(obj, t, (void *)(ptr))
#define to_1d_ptr(obj, t, n, ptr) \
__to_1d_ptr(obj, t, n, (void **)(ptr))
#define to_2d_ptr(obj, t, n1, n2, ptr) \
__to_2d_ptr(obj, t, n1, n2, (void **)(ptr))
#define to_1d_copy(obj, t, ptr, n) \
__to_1d_copy(obj, t, ptr, n)
#define to_2d_copy(obj, t, ptr, n1, n2) \
__to_2d_copy(obj, t, ptr, n1, n2)
/**
* From Numpy Array types to C types
*/
#define new_scalar(obj, ptr) \
__new_scalar(obj, ptr)
#define new_1d_ptr(t, n, ptr) \
__new_1d_ptr(t, n, (void **)(ptr))
#define new_2d_ptr(t, n1, n2, ptr) \
__new_2d_ptr(t, n1, n2, (void **)(ptr))
#define new_1d_copy(t, n, src) \
__new_1d_copy(t, n, src)
#define new_2d_copy(t, n1, n2, src) \
__new_2d_copy(t, n1, n2, src)
/* -------------------------------------------------------------------------- */
__attribute__((unused))
static PyObject *__to_scalar(PyObject *obj, int t, void *ptr)
{
obj = obj ? PyArray_FROMANY(obj, t, 0, 0, NPY_ARRAY_FORCECAST) : obj;
if (!obj)
return NULL;
memcpy(ptr, PyArray_DATA((PyArrayObject *)obj),
PyArray_NBYTES((PyArrayObject *)obj));
return obj;
}
__attribute__((unused))
static PyObject *__to_1d_ptr(PyObject *obj, int t, int n, void **ptr)
{
obj = obj ? PyArray_FROMANY(obj,
t, 1, 1, NPY_ARRAY_FORCECAST|NPY_ARRAY_CARRAY) : obj;
if (!obj || (n && PyArray_SIZE((PyArrayObject *)obj) != n))
return NULL;
*ptr = PyArray_DATA((PyArrayObject *)obj);
return obj;
}
__attribute__((unused))
static PyObject *__to_2d_ptr(PyObject *obj, int t, int n1, int n2, void **ptr)
{
obj = obj ? PyArray_FROMANY(obj,
t, 2, 2, NPY_ARRAY_FORCECAST|NPY_ARRAY_CARRAY) : obj;
if (!obj || (n1 && PyArray_DIMS((PyArrayObject *)obj)[0] != n1)
|| (n2 && PyArray_DIMS((PyArrayObject *)obj)[1] != n2))
return NULL;
*ptr = PyArray_DATA((PyArrayObject *)obj);
return obj;
}
__attribute__((unused))
static PyObject *__to_1d_copy(PyObject *obj, int t, void *v, int n)
{
void *src;
if ((obj = to_1d_ptr(obj, t, n, &src)))
memcpy(v, src, PyArray_NBYTES((PyArrayObject *)obj));
return obj;
}
__attribute__((unused))
static PyObject *__to_2d_copy(PyObject *obj, int t, void *v, int n1, int n2)
{
void *src;
if ((obj = to_2d_ptr(obj, t, n1, n2, &src)))
memcpy(v, src, PyArray_NBYTES((PyArrayObject *)obj));
return obj;
}
/* -------------------------------------------------------------------------- */
__attribute__((unused))
static PyObject *__new_scalar(int t, const void *ptr)
{
PyObject *obj = PyArray_SimpleNew(0, NULL, t);
memcpy(PyArray_DATA((PyArrayObject *)obj), ptr,
PyArray_NBYTES((PyArrayObject *)obj));
return obj;
}
__attribute__((unused))
static PyObject *__new_1d_ptr(int t, int n, void **ptr)
{
PyObject *obj = PyArray_SimpleNew(1, (const npy_intp []){ n }, t);
*ptr = PyArray_DATA((PyArrayObject *)obj);
return obj;
}
__attribute__((unused))
static PyObject *__new_2d_ptr(int t, int n1, int n2, void **ptr)
{
PyObject *obj;
obj = PyArray_SimpleNew(2, ((const npy_intp []){ n1, n2 }), t);
*ptr = PyArray_DATA((PyArrayObject *)obj);
return obj;
}
__attribute__((unused))
static PyObject *__new_1d_copy(int t, int n, const void *src)
{
PyObject *obj;
void *dst;
if ((obj = new_1d_ptr(t, n, &dst)))
memcpy(dst, src, PyArray_NBYTES((PyArrayObject *)obj));
return obj;
}
__attribute__((unused))
static PyObject *__new_2d_copy(int t, int n1, int n2, const void *src)
{
PyObject *obj;
void *dst;
if ((obj = new_2d_ptr(t, n1, n2, &dst)))
memcpy(dst, src, PyArray_NBYTES((PyArrayObject *)obj));
return obj;
}
/* -------------------------------------------------------------------------- */
#include <lc3.h>
__attribute__((unused))
static PyObject *to_attdet_analysis(
PyObject *obj, struct lc3_attdet_analysis *attdet)
{
CTYPES_CHECK("attdet", obj && PyDict_Check(obj));
CTYPES_CHECK("attdet.en1", to_scalar(
PyDict_GetItemString(obj, "en1"), NPY_FLOAT, &attdet->en1));
CTYPES_CHECK("attdet.an1", to_scalar(
PyDict_GetItemString(obj, "an1"), NPY_FLOAT, &attdet->an1));
CTYPES_CHECK("attdet.p_att", to_scalar(
PyDict_GetItemString(obj, "p_att"), NPY_INT, &attdet->p_att));
return obj;
}
__attribute__((unused))
static PyObject *from_attdet_analysis(
PyObject *obj, const struct lc3_attdet_analysis *attdet)
{
if (!obj) obj = PyDict_New();
PyDict_SetItemString(obj, "en1",
new_scalar(NPY_FLOAT, &attdet->en1));
PyDict_SetItemString(obj, "an1",
new_scalar(NPY_FLOAT, &attdet->an1));
PyDict_SetItemString(obj, "p_att",
new_scalar(NPY_INT, &attdet->p_att));
return obj;
}
/* -------------------------------------------------------------------------- */
#include <ltpf.h>
__attribute__((unused))
static PyObject *to_ltpf_hp50_state(
PyObject *obj, struct lc3_ltpf_hp50_state *hp50)
{
CTYPES_CHECK("hp50", obj && PyDict_Check(obj));
CTYPES_CHECK("hp50.s1", to_scalar(
PyDict_GetItemString(obj, "s1"), NPY_FLOAT, &hp50->s1));
CTYPES_CHECK("hp50.s2", to_scalar(
PyDict_GetItemString(obj, "s2"), NPY_FLOAT, &hp50->s2));
return obj;
}
__attribute__((unused))
static PyObject *from_ltpf_hp50_state(
PyObject *obj, const struct lc3_ltpf_hp50_state *hp50)
{
PyDict_SetItemString(obj, "s1",
new_scalar(NPY_FLOAT, &hp50->s1));
PyDict_SetItemString(obj, "s2",
new_scalar(NPY_FLOAT, &hp50->s2));
return obj;
}
__attribute__((unused))
static PyObject *to_ltpf_analysis(
PyObject *obj, struct lc3_ltpf_analysis *ltpf)
{
PyObject *nc_obj, *x_12k8_obj, *x_6k4_obj;
const int n_12k8 = sizeof(ltpf->x_12k8) / sizeof(float);
const int n_6k4 = sizeof(ltpf->x_6k4) / sizeof(float);
CTYPES_CHECK("ltpf", obj && PyDict_Check(obj));
CTYPES_CHECK("ltpf.active", to_scalar(
PyDict_GetItemString(obj, "active"), NPY_BOOL, &ltpf->active));
CTYPES_CHECK("ltpf.pitch", to_scalar(
PyDict_GetItemString(obj, "pitch"), NPY_INT, &ltpf->pitch));
CTYPES_CHECK("ltpf.nc", nc_obj = to_1d_copy(
PyDict_GetItemString(obj, "nc"), NPY_FLOAT, ltpf->nc, 2));
PyDict_SetItemString(obj, "nc", nc_obj);
CTYPES_CHECK(NULL, to_ltpf_hp50_state(
PyDict_GetItemString(obj, "hp50"), &ltpf->hp50));
CTYPES_CHECK("ltpf.x_12k8", x_12k8_obj = to_1d_copy(
PyDict_GetItemString(obj, "x_12k8"), NPY_FLOAT, ltpf->x_12k8, n_12k8));
PyDict_SetItemString(obj, "x_12k8", x_12k8_obj);
CTYPES_CHECK("ltpf.x_6k4", x_6k4_obj = to_1d_copy(
PyDict_GetItemString(obj, "x_6k4"), NPY_FLOAT, ltpf->x_6k4, n_6k4));
PyDict_SetItemString(obj, "x_6k4", x_6k4_obj);
CTYPES_CHECK("ltpf.tc", to_scalar(
PyDict_GetItemString(obj, "tc"), NPY_INT, &ltpf->tc));
return obj;
}
__attribute__((unused))
static PyObject *from_ltpf_analysis(
PyObject *obj, const struct lc3_ltpf_analysis *ltpf)
{
const int n_12k8 = sizeof(ltpf->x_12k8) / sizeof(float);
const int n_6k4 = sizeof(ltpf->x_6k4) / sizeof(float);
if (!obj) obj = PyDict_New();
PyDict_SetItemString(obj, "active",
new_scalar(NPY_BOOL, &ltpf->active));
PyDict_SetItemString(obj, "pitch",
new_scalar(NPY_INT, &ltpf->pitch));
PyDict_SetItemString(obj, "nc",
new_1d_copy(NPY_FLOAT, 2, &ltpf->nc));
PyDict_SetItemString(obj, "hp50",
from_ltpf_hp50_state(PyDict_New(), &ltpf->hp50));
PyDict_SetItemString(obj, "x_12k8",
new_1d_copy(NPY_FLOAT, n_12k8, &ltpf->x_12k8));
PyDict_SetItemString(obj, "x_6k4",
new_1d_copy(NPY_FLOAT, n_6k4, &ltpf->x_6k4));
PyDict_SetItemString(obj, "tc",
new_scalar(NPY_INT, &ltpf->tc));
return obj;
}
__attribute__((unused))
static PyObject *to_ltpf_synthesis(
PyObject *obj, struct lc3_ltpf_synthesis *ltpf)
{
PyObject *c_obj, *x_obj;
CTYPES_CHECK("ltpf", obj && PyDict_Check(obj));
CTYPES_CHECK("ltpf.active", to_scalar(
PyDict_GetItemString(obj, "active"), NPY_BOOL, &ltpf->active));
CTYPES_CHECK("ltpf.pitch", to_scalar(
PyDict_GetItemString(obj, "pitch"), NPY_INT, &ltpf->pitch));
CTYPES_CHECK("ltpf.c", c_obj = to_2d_copy(
PyDict_GetItemString(obj, "c"), NPY_FLOAT, ltpf->c, 12, 2));
PyDict_SetItemString(obj, "c", c_obj);
CTYPES_CHECK("ltpf.x", x_obj = to_1d_copy(
PyDict_GetItemString(obj, "x"), NPY_FLOAT, ltpf->x, 12));
PyDict_SetItemString(obj, "x", x_obj);
return obj;
}
__attribute__((unused))
static PyObject *from_ltpf_synthesis(
PyObject *obj, const struct lc3_ltpf_synthesis *ltpf)
{
if (!obj) obj = PyDict_New();
PyDict_SetItemString(obj, "active",
new_scalar(NPY_BOOL, &ltpf->active));
PyDict_SetItemString(obj, "pitch",
new_scalar(NPY_INT, &ltpf->pitch));
PyDict_SetItemString(obj, "c",
new_2d_copy(NPY_FLOAT, 12, 2, &ltpf->c));
PyDict_SetItemString(obj, "x",
new_1d_copy(NPY_FLOAT, 12, &ltpf->x));
return obj;
}
__attribute__((unused))
static PyObject *new_ltpf_data(const struct lc3_ltpf_data *data)
{
PyObject *obj = PyDict_New();
PyDict_SetItemString(obj, "active",
new_scalar(NPY_BOOL, &data->active));
PyDict_SetItemString(obj, "pitch_index",
new_scalar(NPY_INT, &data->pitch_index));
return obj;
}
__attribute__((unused))
static PyObject *to_ltpf_data(
PyObject *obj, const struct lc3_ltpf_data *data)
{
PyObject *item;
CTYPES_CHECK("ltpf", obj && PyDict_Check(obj));
if ((item = PyDict_GetItemString(obj, "active")))
CTYPES_CHECK("ltpf.active",
to_scalar(item, NPY_BOOL, &data->active));
if ((item = PyDict_GetItemString(obj, "pitch_index")))
CTYPES_CHECK("ltpf.pitch_index",
to_scalar(item, NPY_INT, &data->pitch_index));
return obj;
}
/* -------------------------------------------------------------------------- */
#include <sns.h>
__attribute__((unused))
static PyObject *new_sns_data(const struct lc3_sns_data *data)
{
PyObject *obj = PyDict_New();
PyDict_SetItemString(obj, "lfcb",
new_scalar(NPY_INT, &data->lfcb));
PyDict_SetItemString(obj, "hfcb",
new_scalar(NPY_INT, &data->hfcb));
PyDict_SetItemString(obj, "shape",
new_scalar(NPY_INT, &data->shape));
PyDict_SetItemString(obj, "gain",
new_scalar(NPY_INT, &data->gain));
PyDict_SetItemString(obj, "idx_a",
new_scalar(NPY_INT, &data->idx_a));
PyDict_SetItemString(obj, "ls_a",
new_scalar(NPY_BOOL, &data->ls_a));
PyDict_SetItemString(obj, "idx_b",
new_scalar(NPY_INT, &data->idx_b));
PyDict_SetItemString(obj, "ls_b",
new_scalar(NPY_BOOL, &data->ls_b));
return obj;
}
__attribute__((unused))
static PyObject *to_sns_data(PyObject *obj, struct lc3_sns_data *data)
{
PyObject *item;
CTYPES_CHECK("sns", obj && PyDict_Check(obj));
if ((item = PyDict_GetItemString(obj, "lfcb")))
CTYPES_CHECK("sns.lfcb", to_scalar(item, NPY_INT, &data->lfcb));
if ((item = PyDict_GetItemString(obj, "hfcb")))
CTYPES_CHECK("sns.hfcb", to_scalar(item, NPY_INT, &data->hfcb));
if ((item = PyDict_GetItemString(obj, "shape")))
CTYPES_CHECK("sns.shape", to_scalar(item, NPY_INT, &data->shape));
if ((item = PyDict_GetItemString(obj, "gain")))
CTYPES_CHECK("sns.gain", to_scalar(item, NPY_INT, &data->gain));
if ((item = PyDict_GetItemString(obj, "idx_a")))
CTYPES_CHECK("sns.idx_a", to_scalar(item, NPY_INT, &data->idx_a));
if ((item = PyDict_GetItemString(obj, "ls_a")))
CTYPES_CHECK("sns.ls_a", to_scalar(item, NPY_INT, &data->ls_a));
if ((item = PyDict_GetItemString(obj, "idx_b")))
CTYPES_CHECK("sns.idx_b", to_scalar(item, NPY_INT, &data->idx_b));
if ((item = PyDict_GetItemString(obj, "ls_b")))
CTYPES_CHECK("sns.ls_b", to_scalar(item, NPY_INT, &data->ls_b));
return obj;
}
/* -------------------------------------------------------------------------- */
#include <tns.h>
__attribute__((unused))
static PyObject *new_tns_data(const struct lc3_tns_data *side)
{
PyObject *obj = PyDict_New();
PyDict_SetItemString(obj, "nfilters",
new_scalar(NPY_INT, &side->nfilters));
PyDict_SetItemString(obj, "lpc_weighting",
new_scalar(NPY_BOOL, &side->lpc_weighting));
PyDict_SetItemString(obj, "rc_order",
new_1d_copy(NPY_INT, 2, side->rc_order));
PyDict_SetItemString(obj, "rc",
new_2d_copy(NPY_INT, 2, 8, side->rc));
return obj;
}
__attribute__((unused))
static PyObject *to_tns_data(PyObject *obj, struct lc3_tns_data *side)
{
PyObject *item;
CTYPES_CHECK("tns", obj && PyDict_Check(obj));
if ((item = PyDict_GetItemString(obj, "nfilters")))
CTYPES_CHECK("tns.nfilters",
to_scalar(item, NPY_INT, &side->nfilters));
if ((item = PyDict_GetItemString(obj, "lpc_weighting"))) {
CTYPES_CHECK("tns.lpc_weighting",
to_scalar(item, NPY_BOOL, &side->lpc_weighting));
}
if ((item = PyDict_GetItemString(obj, "rc_order"))) {
CTYPES_CHECK("tns.rc_order",
item = to_1d_copy(item, NPY_INT, side->rc_order, 2));
PyDict_SetItemString(obj, "rc_order", item);
}
if ((item = PyDict_GetItemString(obj, "rc"))) {
CTYPES_CHECK("tns.rc",
item = to_2d_copy(item, NPY_INT, side->rc, 2, 8));
PyDict_SetItemString(obj, "rc", item);
}
return obj;
}
/* -------------------------------------------------------------------------- */
#include <spec.h>
__attribute__((unused))
static PyObject *from_spec_analysis(
PyObject *obj, const struct lc3_spec_analysis *spec)
{
if (!obj) obj = PyDict_New();
PyDict_SetItemString(obj, "nbits_off",
new_scalar(NPY_FLOAT, &spec->nbits_off));
PyDict_SetItemString(obj, "nbits_spare",
new_scalar(NPY_INT, &spec->nbits_spare));
return obj;
}
__attribute__((unused))
static PyObject *to_spec_analysis(
PyObject *obj, struct lc3_spec_analysis *spec)
{
CTYPES_CHECK("spec", obj && PyDict_Check(obj));
CTYPES_CHECK("spec.nbits_off",
to_scalar(PyDict_GetItemString(obj, "nbits_off"),
NPY_FLOAT, &spec->nbits_off));
CTYPES_CHECK("spec.nbits_spare",
to_scalar(PyDict_GetItemString(obj, "nbits_spare"),
NPY_INT, &spec->nbits_spare));
return obj;
}
__attribute__((unused))
static PyObject *new_spec_side(const struct lc3_spec_side *side)
{
PyObject *obj = PyDict_New();
PyDict_SetItemString(obj, "g_idx",
new_scalar(NPY_INT, &side->g_idx));
PyDict_SetItemString(obj, "nq",
new_scalar(NPY_INT, &side->nq));
PyDict_SetItemString(obj, "lsb_mode",
new_scalar(NPY_BOOL, &side->lsb_mode));
return obj;
}
__attribute__((unused))
static PyObject *to_spec_data(
PyObject *obj, struct lc3_spec_side *side)
{
PyObject *item;
CTYPES_CHECK("side", obj && PyDict_Check(obj));
if ((item = PyDict_GetItemString(obj, "g_idx")))
CTYPES_CHECK("side.g_idx",
to_scalar(item, NPY_INT, &side->g_idx));
if ((item = PyDict_GetItemString(obj, "nq")))
CTYPES_CHECK("side.nq",
to_scalar(item, NPY_INT, &side->nq));
if ((item = PyDict_GetItemString(obj, "lsb_mode")))
CTYPES_CHECK("side.lsb_mode",
to_scalar(item, NPY_BOOL, &side->lsb_mode));
return obj;
}
/* -------------------------------------------------------------------------- */
#ifdef __CTYPES_LC3_C
__attribute__((unused))
static PyObject *new_side_data(const struct side_data *side)
{
PyObject *obj = PyDict_New();
PyDict_SetItemString(obj, "bw",
new_scalar(NPY_INT, &(int){ side->bw }));
PyDict_SetItemString(obj, "ltpf",
new_ltpf_data(&side->ltpf));
PyDict_SetItemString(obj, "sns",
new_sns_data(&side->sns));
PyDict_SetItemString(obj, "tns",
new_tns_data(&side->tns));
return obj;
}
__attribute__((unused))
static PyObject *to_side_data(PyObject *obj, struct side_data *side)
{
PyObject *item;
CTYPES_CHECK("frame", obj && PyDict_Check(obj));
if ((item = PyDict_GetItemString(obj, "bw"))) {
int bw;
CTYPES_CHECK("frame.bw", to_scalar(item, NPY_INT, &bw));
side->bw = bw;
}
if ((item = PyDict_GetItemString(obj, "ltpf")))
to_ltpf_data(item, &side->ltpf);
if ((item = PyDict_GetItemString(obj, "sns")))
to_sns_data(item, &side->sns);
if ((item = PyDict_GetItemString(obj, "tns")))
to_tns_data(item, &side->tns);
return obj;
}
__attribute__((unused))
static PyObject *new_plc_state(const struct lc3_plc_state *plc)
{
PyObject *obj = PyDict_New();
PyDict_SetItemString(obj, "seed",
new_scalar(NPY_UINT16, &plc->seed));
PyDict_SetItemString(obj, "count",
new_scalar(NPY_INT, &plc->count));
PyDict_SetItemString(obj, "alpha",
new_scalar(NPY_FLOAT, &plc->alpha));
return obj;
}
__attribute__((unused))
static PyObject *to_plc_state(
PyObject *obj, struct lc3_plc_state *plc)
{
CTYPES_CHECK("plc", obj && PyDict_Check(obj));
CTYPES_CHECK("plc.seed", to_scalar(
PyDict_GetItemString(obj, "seed"), NPY_UINT16, &plc->seed));
CTYPES_CHECK("plc.count", to_scalar(
PyDict_GetItemString(obj, "count"), NPY_INT, &plc->count));
CTYPES_CHECK("plc.alpha", to_scalar(
PyDict_GetItemString(obj, "alpha"), NPY_FLOAT, &plc->alpha));
return obj;
}
__attribute__((unused))
static PyObject *from_encoder(PyObject *obj, const struct lc3_encoder *enc)
{
unsigned dt = enc->dt, sr = enc->sr;
unsigned sr_pcm = enc->sr_pcm;
int ns = LC3_NS(dt, sr);
int nd = LC3_ND(dt, sr);
if (!obj) obj = PyDict_New();
PyDict_SetItemString(obj, "dt",
new_scalar(NPY_INT, &dt));
PyDict_SetItemString(obj, "sr",
new_scalar(NPY_INT, &sr));
PyDict_SetItemString(obj, "sr_pcm",
new_scalar(NPY_INT, &sr_pcm));
PyDict_SetItemString(obj, "attdet",
from_attdet_analysis(NULL, &enc->attdet));
PyDict_SetItemString(obj, "ltpf",
from_ltpf_analysis(NULL, &enc->ltpf));
PyDict_SetItemString(obj, "quant",
from_spec_analysis(NULL, &enc->spec));
PyDict_SetItemString(obj, "xs",
new_1d_copy(NPY_FLOAT, ns+nd, enc->xs-nd));
PyDict_SetItemString(obj, "xf",
new_1d_copy(NPY_FLOAT, ns, enc->xf));
return obj;
}
__attribute__((unused))
static PyObject *to_encoder(PyObject *obj, struct lc3_encoder *enc)
{
unsigned dt, sr, sr_pcm;
PyObject *xs_obj, *xf_obj;
CTYPES_CHECK("encoder", obj && PyDict_Check(obj));
CTYPES_CHECK("encoder.dt", to_scalar(
PyDict_GetItemString(obj, "dt"), NPY_INT, &dt));
CTYPES_CHECK("encoder.dt", (unsigned)(enc->dt = dt) < LC3_NUM_DT);
CTYPES_CHECK("encoder.sr", to_scalar(
PyDict_GetItemString(obj, "sr"), NPY_INT, &sr));
CTYPES_CHECK("encoder.sr", (unsigned)(enc->sr = sr) < LC3_NUM_SRATE);
CTYPES_CHECK("encoder.sr_pcm", to_scalar(
PyDict_GetItemString(obj, "sr_pcm"), NPY_INT, &sr_pcm));
CTYPES_CHECK("encoder.s_pcmr",
(unsigned)(enc->sr_pcm = sr_pcm) < LC3_NUM_SRATE);
int ns = LC3_NS(dt, sr);
int nd = LC3_ND(dt, sr);
CTYPES_CHECK(NULL, to_attdet_analysis(
PyDict_GetItemString(obj, "attdet"), &enc->attdet));
CTYPES_CHECK(NULL, to_ltpf_analysis(
PyDict_GetItemString(obj, "ltpf"), &enc->ltpf));
CTYPES_CHECK(NULL, to_spec_analysis(
PyDict_GetItemString(obj, "quant"), &enc->spec));
CTYPES_CHECK("encoder.xs", xs_obj = to_1d_copy(
PyDict_GetItemString(obj, "xs"), NPY_FLOAT, enc->xs-nd, ns+nd));
PyDict_SetItemString(obj, "xs", xs_obj);
CTYPES_CHECK("encoder.xf", xf_obj = to_1d_copy(
PyDict_GetItemString(obj, "xf"), NPY_FLOAT, enc->xf, ns));
PyDict_SetItemString(obj, "xf", xf_obj);
return obj;
}
__attribute__((unused))
static PyObject *from_decoder(PyObject *obj, const struct lc3_decoder *dec)
{
unsigned dt = dec->dt, sr = dec->sr;
unsigned sr_pcm = dec->sr_pcm;
int ns = LC3_NS(dt, sr);
int nd = LC3_ND(dt, sr);
int nh = LC3_NH(sr);
if (!obj) obj = PyDict_New();
PyDict_SetItemString(obj, "dt",
new_scalar(NPY_INT, &dt));
PyDict_SetItemString(obj, "sr",
new_scalar(NPY_INT, &sr));
PyDict_SetItemString(obj, "sr_pcm",
new_scalar(NPY_INT, &sr_pcm));
PyDict_SetItemString(obj, "ltpf",
from_ltpf_synthesis(NULL, &dec->ltpf));
PyDict_SetItemString(obj, "plc",
new_plc_state(&dec->plc));
PyDict_SetItemString(obj, "xs",
new_1d_copy(NPY_FLOAT, nh+ns, dec->xs-nh));
PyDict_SetItemString(obj, "xd",
new_1d_copy(NPY_FLOAT, nd, dec->xd));
PyDict_SetItemString(obj, "xg",
new_1d_copy(NPY_FLOAT, ns, dec->xg));
return obj;
}
__attribute__((unused))
static PyObject *to_decoder(PyObject *obj, struct lc3_decoder *dec)
{
unsigned dt, sr, sr_pcm;
PyObject *xs_obj, *xd_obj, *xg_obj;
CTYPES_CHECK("decoder", obj && PyDict_Check(obj));
CTYPES_CHECK("decoder.dt", to_scalar(
PyDict_GetItemString(obj, "dt"), NPY_INT, &dt));
CTYPES_CHECK("decoder.dt", (unsigned)(dec->dt = dt) < LC3_NUM_DT);
CTYPES_CHECK("decoder.sr", to_scalar(
PyDict_GetItemString(obj, "sr"), NPY_INT, &sr));
CTYPES_CHECK("decoder.sr", (unsigned)(dec->sr = sr) < LC3_NUM_SRATE);
CTYPES_CHECK("decoder.sr_pcm", to_scalar(
PyDict_GetItemString(obj, "sr_pcm"), NPY_INT, &sr_pcm));
CTYPES_CHECK("decoder.sr_pcm",
(unsigned)(dec->sr_pcm = sr_pcm) < LC3_NUM_SRATE);
int ns = LC3_NS(dt, sr);
int nd = LC3_ND(dt, sr);
int nh = LC3_NH(sr);
CTYPES_CHECK(NULL, to_ltpf_synthesis(
PyDict_GetItemString(obj, "ltpf"), &dec->ltpf));
CTYPES_CHECK(NULL, to_plc_state(
PyDict_GetItemString(obj, "plc"), &dec->plc));
CTYPES_CHECK("decoder.xs", xs_obj = to_1d_copy(
PyDict_GetItemString(obj, "xs"), NPY_FLOAT, dec->xs-nh, nh+ns));
PyDict_SetItemString(obj, "xs", xs_obj);
CTYPES_CHECK("decoder.xd", xd_obj = to_1d_copy(
PyDict_GetItemString(obj, "xd"), NPY_FLOAT, dec->xd, nd));
PyDict_SetItemString(obj, "xd", xd_obj);
CTYPES_CHECK("decoder.xg", xg_obj = to_1d_copy(
PyDict_GetItemString(obj, "xg"), NPY_FLOAT, dec->xg, ns));
PyDict_SetItemString(obj, "xg", xg_obj);
return obj;
}
/* -------------------------------------------------------------------------- */
#endif /* __CTYPES_LC3_C */
#endif /* __CTYPES */
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import scipy.signal as signal
import scipy.io.wavfile as wavfile
import struct
import argparse
import build.lc3 as lc3
import tables as T, appendix_c as C
import mdct, energy, bwdet, sns, tns, spec, ltpf
import bitstream
### ------------------------------------------------------------------------ ###
class Decoder:
def __init__(self, dt_ms, sr_hz):
dt = { 7.5: T.DT_7M5, 10: T.DT_10M }[dt_ms]
sr = { 8000: T.SRATE_8K , 16000: T.SRATE_16K, 24000: T.SRATE_24K,
32000: T.SRATE_32K, 48000: T.SRATE_48K }[sr_hz]
self.sr = sr
self.ne = T.NE[dt][sr]
self.ns = T.NS[dt][sr]
self.mdct = mdct.MdctInverse(dt, sr)
self.bwdet = bwdet.BandwidthDetector(dt, sr)
self.spec = spec.SpectrumSynthesis(dt, sr)
self.tns = tns.TnsSynthesis(dt)
self.sns = sns.SnsSynthesis(dt, sr)
self.ltpf = ltpf.LtpfSynthesis(dt, sr)
def decode(self, data):
b = bitstream.BitstreamReader(data)
bw = self.bwdet.get_bw(b)
if bw > self.sr:
raise ValueError('Invalid bandwidth indication')
self.spec.load(b)
self.tns.load(b, bw, len(data))
pitch = b.read_bit()
self.sns.load(b)
if pitch:
self.ltpf.load(b)
else:
self.ltpf.disable()
x = self.spec.decode(b, bw, len(data))
return (x, bw, pitch)
def synthesize(self, x, bw, pitch, nbytes):
x = self.tns.run(x, bw)
x = self.sns.run(x)
x = np.append(x, np.zeros(self.ns - self.ne))
x = self.mdct.run(x)
x = self.ltpf.run(x, len(data))
return x
def run(self, data):
(x, bw, pitch) = self.decode(data)
x = self.synthesize(x, bw, pitch, len(data))
return x
### ------------------------------------------------------------------------ ###
def check_appendix_c(dt):
ok = True
dec_c = lc3.setup_decoder(int(T.DT_MS[dt] * 1000), 16000)
for i in range(len(C.BYTES_AC[dt])):
pcm = lc3.decode(dec_c, bytes(C.BYTES_AC[dt][i]))
ok = ok and np.max(np.abs(pcm - C.X_HAT_CLIP[dt][i])) < 1
return ok
def check():
ok = True
for dt in range(T.NUM_DT):
ok = ok and check_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='LC3 Decoder Test Framework')
parser.add_argument('lc3_file',
help='Input bitstream file', type=argparse.FileType('r'))
parser.add_argument('--pyout',
help='Python output file', type=argparse.FileType('w'))
parser.add_argument('--cout',
help='C output file', type=argparse.FileType('w'))
args = parser.parse_args()
### File Header ###
f_lc3 = open(args.lc3_file.name, 'rb')
header = struct.unpack('=HHHHHHHI', f_lc3.read(18))
if header[0] != 0xcc1c:
raise ValueError('Invalid bitstream file')
if header[4] != 1:
raise ValueError('Unsupported number of channels')
sr_hz = header[2] * 100
bitrate = header[3] * 100
nchannels = header[4]
dt_ms = header[5] / 100
f_lc3.seek(header[1])
### Setup ###
dec = Decoder(dt_ms, sr_hz)
dec_c = lc3.setup_decoder(int(dt_ms * 1000), sr_hz)
pcm_c = np.empty(0).astype(np.int16)
pcm_py = np.empty(0).astype(np.int16)
### Decoding loop ###
nframes = 0
while True:
data = f_lc3.read(2)
if len(data) != 2:
break
if nframes >= 1000:
break
(frame_nbytes,) = struct.unpack('=H', data)
print('Decoding frame %d' % nframes, end='\r')
data = f_lc3.read(frame_nbytes)
x = dec.run(data)
pcm_py = np.append(pcm_py,
np.clip(np.round(x), -32768, 32767).astype(np.int16))
x_c = lc3.decode(dec_c, data)
pcm_c = np.append(pcm_c, x_c)
nframes += 1
print('done ! %16s' % '')
### Terminate ###
if args.pyout:
wavfile.write(args.pyout.name, sr_hz, pcm_py)
if args.cout:
wavfile.write(args.cout.name, sr_hz, pcm_c)
### ------------------------------------------------------------------------ ###
+213
View File
@@ -0,0 +1,213 @@
#!/usr/bin/env python3
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import scipy.signal as signal
import scipy.io.wavfile as wavfile
import struct
import argparse
import build.lc3 as lc3
import tables as T, appendix_c as C
import attdet, ltpf
import mdct, energy, bwdet, sns, tns, spec
import bitstream
### ------------------------------------------------------------------------ ###
class Encoder:
def __init__(self, dt_ms, sr_hz):
dt = { 7.5: T.DT_7M5, 10: T.DT_10M }[dt_ms]
sr = { 8000: T.SRATE_8K , 16000: T.SRATE_16K, 24000: T.SRATE_24K,
32000: T.SRATE_32K, 48000: T.SRATE_48K }[sr_hz]
self.ne = T.NE[dt][sr]
self.attdet = attdet.AttackDetector(dt, sr)
self.ltpf = ltpf.Ltpf(dt, sr)
self.mdct = mdct.Mdct(dt, sr)
self.energy = e_energy.EnergyBand(dt, sr)
self.bwdet = bwdet.BandwidthDetector(dt, sr)
self.sns = sns.SnsAnalysis(dt, sr)
self.tns = tns.TnsAnalysis(dt)
self.spec = spec.SpectrumEncoder(dt, sr)
def analyse(self, x, nbytes):
att = self.attdet.run(nbytes, x)
pitch_present = self.ltpf.run(x)
x = self.mdct.forward(x)[:self.ne]
(e, nn_flag) = self.energy.compute(x)
if nn_flag:
self.ltpf.disable()
bw = self.bwdet.run(e)
x = self.sns.run(e, att, x)
x = self.tns.run(x, bw, nn_flag, nbytes)
(xq, lastnz, x) = self.spec.quantize(bw, nbytes,
self.bwdet.get_nbits(), self.ltpf.get_nbits(),
self.sns.get_nbits(), self.tns.get_nbits(), x)
return pitch_present
def encode(self, pitch_present, nbytes):
b = bitstream.BitstreamWriter(nbytes)
self.bwdet.store(b)
self.spec.store(b)
self.tns.store(b)
b.write_bit(pitch_present)
self.sns.store(b)
if pitch_present:
self.ltpf.store_data(b)
self.spec.encode(b)
return b.terminate()
def run(self, x, nbytes):
pitch_present = self.analyse(x, nbytes)
data = self.encode(pitch_present, nbytes)
return data
### ------------------------------------------------------------------------ ###
def check_appendix_c(dt):
ok = True
enc_c = lc3.setup_encoder(int(T.DT_MS[dt] * 1000), 16000)
for i in range(len(C.X_PCM[dt])):
data = lc3.encode(enc_c, C.X_PCM[dt][i], C.NBYTES[dt])
ok = ok and data == C.BYTES_AC[dt][i]
if not ok:
dump(data)
dump(C.BYTES_AC[dt][i])
return ok
def check():
ok = True
for dt in range(T.NUM_DT):
ok = ok and check_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
def dump(data):
for i in range(0, len(data), 20):
print(''.join('{:02x} '.format(x)
for x in data[i:min(i+20, len(data))] ))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='LC3 Encoder Test Framework')
parser.add_argument('wav_file',
help='Input wave file', type=argparse.FileType('r'))
parser.add_argument('--bitrate',
help='Bitrate in bps', type=int, required=True)
parser.add_argument('--dt',
help='Frame duration in ms', type=float, default=10)
parser.add_argument('--pyout',
help='Python output file', type=argparse.FileType('w'))
parser.add_argument('--cout',
help='C output file', type=argparse.FileType('w'))
args = parser.parse_args()
if args.bitrate < 16000 or args.bitrate > 320000:
raise ValueError('Invalid bitate %d bps' % args.bitrate)
if args.dt not in (7.5, 10):
raise ValueError('Invalid frame duration %.1f ms' % args.dt)
(sr_hz, pcm) = wavfile.read(args.wav_file.name)
if sr_hz not in (8000, 16000, 24000, 320000, 48000):
raise ValueError('Unsupported input samplerate: %d' % sr_hz)
### Setup ###
enc = Encoder(args.dt, sr_hz)
enc_c = lc3.setup_encoder(int(args.dt * 1000), sr_hz)
frame_samples = int((args.dt * sr_hz) / 1000)
frame_nbytes = int((args.bitrate * args.dt) / (1000 * 8))
### File Header ###
f_py = open(args.pyout.name, 'wb') if args.pyout else None
f_c = open(args.cout.name , 'wb') if args.cout else None
header = struct.pack('=HHHHHHHI', 0xcc1c, 18,
sr_hz // 100, args.bitrate // 100, 1, int(args.dt * 100), 0, len(pcm))
for f in (f_py, f_c):
if f: f.write(header)
### Encoding loop ###
if len(pcm) % frame_samples > 0:
pcm = np.append(pcm, np.zeros(frame_samples - (len(pcm) % frame_samples)))
for i in range(0, len(pcm), frame_samples):
print('Encoding frame %d' % (i // frame_samples), end='\r')
frame_pcm = pcm[i:i+frame_samples]
data = enc.run(frame_pcm, frame_nbytes)
data_c = lc3.encode(enc_c, frame_pcm, frame_nbytes)
for f in (f_py, f_c):
if f: f.write(struct.pack('=H', frame_nbytes))
if f_py: f_py.write(data)
if f_c: f_c.write(data_c)
print('done ! %16s' % '')
### Terminate ###
for f in (f_py, f_c):
if f: f.close()
### ------------------------------------------------------------------------ ###
+92
View File
@@ -0,0 +1,92 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import build.lc3 as lc3
import tables as T, appendix_c as C
### ------------------------------------------------------------------------ ###
class EnergyBand:
def __init__(self, dt, sr):
self.dt = dt
self.I = T.I[dt][sr]
def compute(self, x):
e = [ np.mean(np.square(x[self.I[i]:self.I[i+1]]))
for i in range(len(self.I)-1) ]
e_lo = np.sum(e[:len(e) - [4, 2][self.dt]])
e_hi = np.sum(e[len(e) - [4, 2][self.dt]:])
return np.append(e, np.zeros(64-len(e))), (e_hi > 30*e_lo)
### ------------------------------------------------------------------------ ###
def check_unit(rng, dt, sr):
ns = T.NS[dt][sr]
ok = True
nrg = EnergyBand(dt, sr)
x = (2 * rng.random(T.NS[dt][sr])) - 1
(e , nn ) = nrg.compute(x)
(e_c, nn_c) = lc3.energy_compute(dt, sr, x)
ok = ok and np.amax(np.abs(e_c - e)) < 1e-5 and nn_c == nn
x[15*ns//16:] *= 1e2;
(e , nn ) = nrg.compute(x)
(e_c, nn_c) = lc3.energy_compute(dt, sr, x)
ok = ok and np.amax(np.abs(e_c - e)) < 1e-3 and nn_c == nn
return ok
def check_appendix_c(dt):
sr = T.SRATE_16K
ok = True
e = lc3.energy_compute(dt, sr, C.X[dt][0])[0]
ok = ok and np.amax(np.abs(1 - e/C.E_B[dt][0])) < 1e-6
e = lc3.energy_compute(dt, sr, C.X[dt][1])[0]
ok = ok and np.amax(np.abs(1 - e/C.E_B[dt][1])) < 1e-6
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_unit(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+62
View File
@@ -0,0 +1,62 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include "lc3.h"
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <energy.c>
#include "ctypes.h"
static PyObject *energy_compute_py(PyObject *m, PyObject *args)
{
unsigned dt, sr;
PyObject *x_obj, *e_obj;
float *x, *e;
if (!PyArg_ParseTuple(args, "IIO", &dt, &sr, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ns = LC3_NS(dt, sr);
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, ns, &x));
e_obj = new_1d_ptr(NPY_FLOAT, LC3_NUM_BANDS, &e);
int nn_flag = lc3_energy_compute(dt, sr, x, e);
return Py_BuildValue("Ni", e_obj, nn_flag);
}
static PyMethodDef methods[] = {
{ "energy_compute", energy_compute_py, METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_energy_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+142
View File
@@ -0,0 +1,142 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include "lc3.h"
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <lc3.c>
#define __CTYPES_LC3_C
#include "ctypes.h"
static PyObject *setup_encoder_py(PyObject *m, PyObject *args)
{
int dt_us, sr_hz;
if (!PyArg_ParseTuple(args, "ii", &dt_us, &sr_hz))
return NULL;
CTYPES_CHECK("dt_us", LC3_CHECK_DT_US(dt_us));
CTYPES_CHECK("sr_hz", LC3_CHECK_SR_HZ(sr_hz));
lc3_encoder_t encoder = lc3_setup_encoder(dt_us, sr_hz, 0,
malloc(lc3_encoder_size(dt_us, sr_hz)));
PyObject *encoder_obj = from_encoder(NULL, encoder);
free(encoder);
return Py_BuildValue("N", encoder_obj);
}
static PyObject *encode_py(PyObject *m, PyObject *args)
{
PyObject *encoder_obj, *pcm_obj;
int nbytes;
int16_t *pcm;
if (!PyArg_ParseTuple(args, "OOi", &encoder_obj, &pcm_obj, &nbytes))
return NULL;
lc3_encoder_t encoder =
lc3_setup_encoder(10000, 48000, 0, &(lc3_encoder_mem_48k_t){ });
CTYPES_CHECK(NULL, encoder_obj = to_encoder(encoder_obj, encoder));
int ns = LC3_NS(encoder->dt, encoder->sr);
CTYPES_CHECK("x", pcm_obj = to_1d_ptr(pcm_obj, NPY_INT16, ns, &pcm));
CTYPES_CHECK("nbytes", nbytes >= 20 && nbytes <= 400);
uint8_t out[nbytes];
lc3_encode(encoder, LC3_PCM_FORMAT_S16, pcm, 1, nbytes, out);
from_encoder(encoder_obj, encoder);
return Py_BuildValue("N",
PyBytes_FromStringAndSize((const char *)out, nbytes));
}
static PyObject *setup_decoder_py(PyObject *m, PyObject *args)
{
int dt_us, sr_hz;
if (!PyArg_ParseTuple(args, "ii", &dt_us, &sr_hz))
return NULL;
CTYPES_CHECK("dt_us", LC3_CHECK_DT_US(dt_us));
CTYPES_CHECK("sr_hz", LC3_CHECK_SR_HZ(sr_hz));
lc3_decoder_t decoder = lc3_setup_decoder(dt_us, sr_hz, 0,
malloc(lc3_decoder_size(dt_us, sr_hz)));
PyObject *decoder_obj = from_decoder(NULL, decoder);
free(decoder);
return Py_BuildValue("N", decoder_obj);
}
static PyObject *decode_py(PyObject *m, PyObject *args)
{
PyObject *decoder_obj, *pcm_obj, *in_obj;
int16_t *pcm;
if (!PyArg_ParseTuple(args, "OO", &decoder_obj, &in_obj))
return NULL;
CTYPES_CHECK("in", in_obj == Py_None || PyBytes_Check(in_obj));
char *in = in_obj == Py_None ? NULL : PyBytes_AsString(in_obj);
int nbytes = in_obj == Py_None ? 0 : PyBytes_Size(in_obj);
lc3_decoder_t decoder =
lc3_setup_decoder(10000, 48000, 0, &(lc3_decoder_mem_48k_t){ });
CTYPES_CHECK(NULL, decoder_obj = to_decoder(decoder_obj, decoder));
int ns = LC3_NS(decoder->dt, decoder->sr);
pcm_obj = new_1d_ptr(NPY_INT16, ns, &pcm);
lc3_decode(decoder, in, nbytes, LC3_PCM_FORMAT_S16, pcm, 1);
from_decoder(decoder_obj, decoder);
return Py_BuildValue("N", pcm_obj);
}
static PyMethodDef methods[] = {
{ "setup_encoder" , setup_encoder_py , METH_VARARGS },
{ "encode" , encode_py , METH_VARARGS },
{ "setup_decoder" , setup_decoder_py , METH_VARARGS },
{ "decode" , decode_py , METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_interface_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+660
View File
@@ -0,0 +1,660 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import scipy.signal as signal
import build.lc3 as lc3
import tables as T, appendix_c as C
### ------------------------------------------------------------------------ ###
class Resampler_12k8:
def __init__(self, dt, sr, history = 0):
self.sr = sr
self.p = 192 // T.SRATE_KHZ[sr]
self.w = 240 // self.p
self.n = ((T.DT_MS[dt] * 128) / 10).astype(int)
self.d = [ 44, 24 ][dt]
self.x = np.zeros(self.w + T.NS[dt][sr])
self.u = np.zeros(self.n + 2)
self.y = np.zeros(self.n + self.d + history)
def resample(self, x):
p = self.p
w = self.w
d = self.d
n = self.n
### Sliding window
self.x[:w] = self.x[-w:]
self.x[w:] = x
self.u[:2] = self.u[-2:]
if len(self.y) > 2*n + d:
self.y[n+d:-n] = self.y[d+2*n:]
if len(self.y) > n + d:
self.y[-n:] = self.y[:n]
self.y[:d] = self.y[n:d+n]
x = self.x
u = self.u
### 3.3.9.3 Resampling
h = np.zeros(240 + p)
h[-119:] = T.LTPF_H12K8[:119]
h[ :120] = T.LTPF_H12K8[119:]
for i in range(n):
e = (15 * i) // p
f = (15 * i) % p
k = np.arange(-120, 120 + p, p) - f
u[2+i] = p * np.dot( x[e:e+w+1], np.take(h, k) )
if self.sr == T.SRATE_8K:
u = 0.5 * u
### 3.3.9.4 High-pass filtering
b = [ 0.9827947082978771, -1.9655894165957540, 0.9827947082978771 ]
a = [ 1 , -1.9652933726226904, 0.9658854605688177 ]
self.y[d:d+n] = b[0] * u[2:] + b[1] * u[1:-1] + b[2] * u[:-2]
for i in range(n):
self.y[d+i] -= a[1] * self.y[d+i-1] + a[2] * self.y[d+i-2]
return self.y
class Resampler_6k4:
def __init__(self, n, history = 0):
self.x = np.zeros(n + 5)
self.n = n // 2
self.y = np.zeros(self.n + history)
def resample(self, x):
n = self.n
### Sliding window
self.x[:3] = self.x[-5:-2]
self.x[3:] = x[:2*n+2]
x = self.x
if len(self.y) > 2*n:
self.y[n:-n] = self.y[2*n:]
if len(self.y) > n:
self.y[-n:] = self.y[:n]
### 3.3.9.5 Downsampling to 6.4 KHz
h = [ 0.1236796411180537, 0.2353512128364889, 0.2819382920909148,
0.2353512128364889, 0.1236796411180537 ]
self.y[:n] = [ np.dot(x[2*i:2*i+5], h) for i in range(self.n) ]
return self.y
def initial_hp50_state():
return { 's1': 0.0, 's2': 0.0 }
### ------------------------------------------------------------------------ ###
class Ltpf:
def __init__(self, dt, sr):
self.dt = dt
self.sr = sr
(self.pitch_present, self.pitch_index) = (None, None)
def get_data(self):
return { 'active' : self.active,
'pitch_index' : self.pitch_index }
def get_nbits(self):
return 1 + 10 * int(self.pitch_present)
class LtpfAnalysis(Ltpf):
def __init__(self, dt, sr):
super().__init__(dt, sr)
self.resampler_12k8 = Resampler_12k8(
dt, sr, history = 232)
self.resampler_6k4 = Resampler_6k4(
self.resampler_12k8.n, history = 114)
self.active = False
self.tc = 0
self.pitch = 0
self.nc = np.zeros(2)
def correlate(self, x, n, k0, k1):
return [ np.dot(x[:n], np.take(x, np.arange(n) - k)) \
for k in range(k0, 1+k1) ]
def norm_corr(self, x, n, k):
u = x[:n]
v = np.take(x, np.arange(n) - k)
uv = np.dot(u, v)
return uv / np.sqrt(np.dot(u, u) * np.dot(v, v)) if uv > 0 else 0
def run(self, x):
### 3.3.9.3-4 Resampling
x_12k8 = self.resampler_12k8.resample(x)
### 3.3.9.5-6 Pitch detection algorithm
x = self.resampler_6k4.resample(x_12k8)
n = self.resampler_6k4.n
r = self.correlate(x, n, 17, 114)
rw = r * (1 - 0.5 * np.arange(len(r)) / (len(r) - 1))
tc = self.tc
k0 = max(0, tc-4)
k1 = min(len(r)-1, tc+4)
t = [ 17 + np.argmax(rw), 17 + k0 + np.argmax(r[k0:1+k1]) ]
nc = [ self.norm_corr(x, n, t[i]) for i in range(2) ]
ti = int(nc[1] > 0.85 * nc[0])
self.tc = t[ti] - 17
self.pitch_present = bool(nc[ti] > 0.6)
### 3.3.9.7 Pitch-lag parameter
if self.pitch_present:
tc = self.tc + 17
x = x_12k8
n = self.resampler_12k8.n
k0 = max( 32, 2*tc-4)
k1 = min(228, 2*tc+4)
r = self.correlate(x, n, k0-4, k1+4)
e = k0 + np.argmax(r[4:-4])
h = np.zeros(42)
h[-15:] = T.LTPF_H4[:15]
h[ :16] = T.LTPF_H4[15:]
m = np.arange(-4, 5)
s = [ np.dot( np.take(r, e-k0+4 + m), np.take(h, 4*m-d) ) \
for d in range(-3, 4) ]
f = np.argmax(s[3:]) if e <= 32 else \
-3 + np.argmax(s) if e < 127 else \
-2 + 2*np.argmax(s[1:-1:2]) if e < 157 else 0
e -= (f < 0)
f += 4*(f < 0)
self.pitch_index = 4*e + f - 128 if e < 127 else \
2*e + f//2 + 126 if e < 157 else e + 283
else:
e = f = 0
self.pitch_index = 0
### 3.3.9.8 Activation bit
h = np.zeros(24)
h[-7:] = T.LTPF_HI[:7]
h[ :8] = T.LTPF_HI[7:]
k = np.arange(-2, 3)
u = [ np.dot( np.take(x, i-k), np.take(h, 4*k) ) \
for i in range(n) ]
v = [ np.dot( np.take(x, i-k), np.take(h, 4*k-f) ) \
for i in range(-e, n-e) ]
nc = max(0, np.dot(u, v)) / np.sqrt(np.dot(u, u) * np.dot(v, v)) \
if self.pitch_present else 0
pitch = e + f/4
if not self.active:
active = (self.dt == T.DT_10M or self.nc[1] > 0.94) \
and self.nc[0] > 0.94 and nc > 0.94
else:
dp = abs(pitch - self.pitch)
dc = nc - self.nc[0]
active = nc > 0.9 or (dp < 2 and dc > -0.1 and nc > 0.84)
if not self.pitch_present:
active = False
pitch = 0
nc = 0
self.active = active
self.pitch = pitch
self.nc[1] = self.nc[0]
self.nc[0] = nc
return self.pitch_present
def disable(self):
self.active = False
def store(self, b):
b.write_uint(self.active, 1)
b.write_uint(self.pitch_index, 9)
class LtpfSynthesis(Ltpf):
C_N = [ T.LTPF_N_8K , T.LTPF_N_16K,
T.LTPF_N_24K, T.LTPF_N_32K, T.LTPF_N_48K ]
C_D = [ T.LTPF_D_8K , T.LTPF_D_16K,
T.LTPF_D_24K, T.LTPF_D_32K, T.LTPF_D_48K ]
def __init__(self, dt, sr):
super().__init__(dt, sr)
self.C_N = LtpfSynthesis.C_N[sr]
self.C_D = LtpfSynthesis.C_D[sr]
ns = T.NS[dt][sr]
self.active = [ False, False ]
self.pitch_index = 0
max_pitch_12k8 = 228
max_pitch = max_pitch_12k8 * T.SRATE_KHZ[self.sr] / 12.8
max_pitch = np.ceil(max_pitch).astype(np.int)
self.x = np.zeros(ns)
self.y = np.zeros(max_pitch + len(self.C_D[0]))
self.p_e = [ 0, 0 ]
self.p_f = [ 0, 0 ]
self.c_n = [ None, None ]
self.c_d = [ None, None ]
def load(self, b):
self.active[0] = bool(b.read_uint(1))
self.pitch_index = b.read_uint(9)
def disable(self):
self.active[0] = False
self.pitch_index = 0
def run(self, x, nbytes):
sr = self.sr
dt = self.dt
### 3.4.9.4 Filter parameters
pitch_index = self.pitch_index
if pitch_index >= 440:
p_e = pitch_index - 283
p_f = 0
elif pitch_index >= 380:
p_e = pitch_index // 2 - 63
p_f = 2*(pitch_index - 2*(p_e + 63))
else:
p_e = pitch_index // 4 + 32
p_f = pitch_index - 4*(p_e - 32)
p = (p_e + p_f / 4) * T.SRATE_KHZ[self.sr] / 12.8
self.p_e[0] = int(p * 4 + 0.5) // 4
self.p_f[0] = int(p * 4 + 0.5) - 4*self.p_e[0]
nbits = round(nbytes*80 / T.DT_MS[dt])
g_idx = max(nbits // 80, 3+sr) - (3+sr)
g = [ 0.4, 0.35, 0.3, 0.25 ][g_idx] if g_idx < 4 else 0
g_idx = min(g_idx, 3)
self.c_n[0] = 0.85 * g * LtpfSynthesis.C_N[sr][g_idx]
self.c_d[0] = g * LtpfSynthesis.C_D[sr][self.p_f[0]]
### 3.4.9.2 Transition handling
n0 = (T.SRATE_KHZ[sr] * 1000) // 400
ns = T.NS[dt][sr]
x = np.append(x, self.x)
y = np.append(np.zeros(ns), self.y)
yc = y.copy()
c_n = self.c_n
c_d = self.c_d
l_n = len(c_n[0])
l_d = len(c_d[0])
d = [ self.p_e[0] - (l_d - 1) // 2,
self.p_e[1] - (l_d - 1) // 2 ]
for k in range(n0):
if not self.active[0] and not self.active[1]:
y[k] = x[k]
elif self.active[0] and not self.active[1]:
u = np.dot(c_n[0], np.take(x, k - np.arange(l_n))) - \
np.dot(c_d[0], np.take(y, k - d[0] - np.arange(l_d)))
y[k] = x[k] - (k/n0) * u
elif not self.active[0] and self.active[1]:
u = np.dot(c_n[1], np.take(x, k - np.arange(l_n))) - \
np.dot(c_d[1], np.take(y, k - d[1] - np.arange(l_d)))
y[k] = x[k] - (1 - k/n0) * u
elif self.p_e[0] == self.p_e[1] and self.p_f[0] == self.p_f[1]:
u = np.dot(c_n[0], np.take(x, k - np.arange(l_n))) - \
np.dot(c_d[0], np.take(y, k - d[0] - np.arange(l_d)))
y[k] = x[k] - u
else:
u = np.dot(c_n[1], np.take(x, k - np.arange(l_n))) - \
np.dot(c_d[1], np.take(y, k - d[1] - np.arange(l_d)))
yc[k] = x[k] - (1 - k/n0) * u
u = np.dot(c_n[0], np.take(yc, k - np.arange(l_n))) - \
np.dot(c_d[0], np.take(y , k - d[0] - np.arange(l_d)))
y[k] = yc[k] - (k/n0) * u
### 3.4.9.3 Remainder of the frame
for k in range(n0, ns):
if not self.active[0]:
y[k] = x[k]
else:
u = np.dot(c_n[0], np.take(x, k - np.arange(l_n))) - \
np.dot(c_d[0], np.take(y, k - d[0] - np.arange(l_d)))
y[k] = x[k] - u
### Sliding window
self.active[1] = self.active[0]
self.p_e[1] = self.p_e[0]
self.p_f[1] = self.p_f[0]
self.c_n[1] = self.c_n[0]
self.c_d[1] = self.c_d[0]
self.x = x[:ns]
self.y = np.append(self.y[ns:], y[:ns])
return y[:ns]
def initial_state():
return { 'active' : False, 'pitch': 0, 'nc': np.zeros(2),
'hp50' : initial_hp50_state(),
'x_12k8' : np.zeros(384), 'x_6k4' : np.zeros(178), 'tc' : 0 }
def initial_sstate():
return { 'active': False, 'pitch': 0,
'c': np.zeros((12,2)), 'x': np.zeros(12) }
### ------------------------------------------------------------------------ ###
def check_resampler(rng, dt, sr):
ns = T.NS[dt][sr]
nd = T.ND[dt][sr]
ok = True
r = Resampler_12k8(dt, sr)
hp50_c = initial_hp50_state()
x_c = np.zeros(nd)
y_c = np.zeros(384)
for run in range(10):
x = (2 * rng.random(ns)) - 1
y = r.resample(x)
x_c = np.append(x_c[-nd:], x)
y_c[:-r.n] = y_c[r.n:]
y_c = lc3.ltpf_resample(dt, sr, hp50_c, x_c, y_c)
ok = ok and np.amax(np.abs(y_c[-r.d-r.n:] - y[:r.d+r.n])) < 1e-4
return ok
def check_resampler_appendix_c(dt):
sr = T.SRATE_16K
ok = True
nd = T.ND[dt][sr]
n = [ 96, 128 ][dt]
k = [ 44, 24 ][dt] + n
state = initial_hp50_state()
x = np.append(np.zeros(nd), C.X_PCM[dt][0])
y = np.zeros(384)
y = lc3.ltpf_resample(dt, sr, state, x, y)
u = y[-k:len(C.X_TILDE_12K8D[dt][0])-k]
ok = np.amax(np.abs(u - C.X_TILDE_12K8D[dt][0])) < 1e0
x = np.append(x[-nd:], C.X_PCM[dt][1])
y[:-n] = y[n:]
y = lc3.ltpf_resample(dt, sr, state, x, y)
u = y[-k:len(C.X_TILDE_12K8D[dt][1])-k]
ok = ok and np.amax(np.abs(u - C.X_TILDE_12K8D[dt][1])) < 1e0
return ok
def check_analysis(rng, dt, sr):
ns = T.NS[dt][sr]
nd = T.ND[dt][sr]
ok = True
state_c = initial_state()
x_c = np.zeros(ns+nd)
ltpf = LtpfAnalysis(dt, sr)
t = np.arange(100 * ns) / (T.SRATE_KHZ[sr] * 1000)
s = signal.chirp(t, f0=50, f1=3e3, t1=t[-1], method='logarithmic')
for i in range(20):
x = s[i*ns:(i+1)*ns]
pitch_present = ltpf.run(x)
data = ltpf.get_data()
x_c = np.append(x_c[-nd:], x)
(pitch_present_c, data_c) = lc3.ltpf_analyse(dt, sr, state_c, x_c)
ok = ok and state_c['tc'] == ltpf.tc
ok = ok and np.amax(np.abs(state_c['nc'][0] - ltpf.nc[0])) < 1e-4
ok = ok and pitch_present_c == pitch_present
ok = ok and data_c['active'] == data['active']
ok = ok and data_c['pitch_index'] == data['pitch_index']
ok = ok and lc3.ltpf_get_nbits(pitch_present) == ltpf.get_nbits()
return ok
def check_synthesis(rng, dt, sr):
ok = True
ns = T.NS[dt][sr]
nd = 18 * T.SRATE_KHZ[sr]
synthesis = LtpfSynthesis(dt, sr)
state_c = initial_sstate()
x_c = np.zeros(nd+ns)
for i in range(50):
pitch_present = bool(rng.integers(0, 10) >= 1)
if not pitch_present:
synthesis.disable()
else:
synthesis.active[0] = bool(rng.integers(0, 5) >= 1)
synthesis.pitch_index = rng.integers(0, 512)
data_c = None if not pitch_present else \
{ 'active' : synthesis.active[0],
'pitch_index' : synthesis.pitch_index }
x = rng.random(ns) * 1e4
nbytes = rng.integers(10*(2+sr), 10*(6+sr))
x_c[:nd] = x_c[ns:]
x_c[nd:] = x
y = synthesis.run(x, nbytes)
x_c = lc3.ltpf_synthesize(dt, sr, nbytes, state_c, data_c, x_c)
ok = ok and np.amax(np.abs(x_c[nd:] - y)) < 1e-2
return ok
def check_analysis_appendix_c(dt):
sr = T.SRATE_16K
nd = T.ND[dt][sr]
ok = True
state = initial_state()
x = np.append(np.zeros(nd), C.X_PCM[dt][0])
(pitch_present, data) = lc3.ltpf_analyse(dt, sr, state, x)
ok = ok and C.T_CURR[dt][0] - state['tc'] == 17
ok = ok and np.amax(np.abs(state['nc'][0] - C.NC_LTPF[dt][0])) < 1e-5
ok = ok and pitch_present == C.PITCH_PRESENT[dt][0]
ok = ok and data['pitch_index'] == C.PITCH_INDEX[dt][0]
ok = ok and data['active'] == C.LTPF_ACTIVE[dt][0]
x = np.append(x[-nd:], C.X_PCM[dt][1])
(pitch_present, data) = lc3.ltpf_analyse(dt, sr, state, x)
ok = ok and C.T_CURR[dt][1] - state['tc'] == 17
ok = ok and np.amax(np.abs(state['nc'][0] - C.NC_LTPF[dt][1])) < 1e-5
ok = ok and pitch_present == C.PITCH_PRESENT[dt][1]
ok = ok and data['pitch_index'] == C.PITCH_INDEX[dt][1]
ok = ok and data['active'] == C.LTPF_ACTIVE[dt][1]
return ok
def check_synthesis_appendix_c(dt):
sr = T.SRATE_16K
ok = True
if dt != T.DT_10M:
return ok
ns = T.NS[dt][sr]
nd = 18 * T.SRATE_KHZ[sr]
NBYTES = [ C.LTPF_C2_NBITS // 8, C.LTPF_C3_NBITS // 8,
C.LTPF_C4_NBITS // 8, C.LTPF_C5_NBITS // 8 ]
ACTIVE = [ C.LTPF_C2_ACTIVE, C.LTPF_C3_ACTIVE,
C.LTPF_C4_ACTIVE, C.LTPF_C5_ACTIVE ]
PITCH_INDEX = [ C.LTPF_C2_PITCH_INDEX, C.LTPF_C3_PITCH_INDEX,
C.LTPF_C4_PITCH_INDEX, C.LTPF_C5_PITCH_INDEX ]
X = [ C.LTPF_C2_X, C.LTPF_C3_X,
C.LTPF_C4_X, C.LTPF_C5_X ]
PREV = [ C.LTPF_C2_PREV, C.LTPF_C3_PREV,
C.LTPF_C4_PREV, C.LTPF_C5_PREV ]
TRANS = [ C.LTPF_C2_TRANS, C.LTPF_C3_TRANS,
C.LTPF_C4_TRANS, C.LTPF_C5_TRANS ]
for i in range(4):
state = initial_sstate()
nbytes = NBYTES[i]
data = { 'active' : ACTIVE[i][0], 'pitch_index' : PITCH_INDEX[i][0] }
x = np.append(np.zeros(nd), X[i][0])
lc3.ltpf_synthesize(dt, sr, nbytes, state, data, x)
data = { 'active' : ACTIVE[i][1], 'pitch_index' : PITCH_INDEX[i][1] }
x[ :nd-ns] = PREV[i][0][-nd+ns:]
x[nd-ns:nd] = PREV[i][1]
x[nd:nd+ns] = X[i][1]
y = lc3.ltpf_synthesize(dt, sr, nbytes, state, data, x)[nd:]
ok = ok and np.amax(np.abs(y - TRANS[i])) < 1e-3
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_resampler(rng, dt, sr)
ok = ok and check_analysis(rng, dt, sr)
ok = ok and check_synthesis(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_resampler_appendix_c(dt)
ok = ok and check_analysis_appendix_c(dt)
ok = ok and check_synthesis_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+138
View File
@@ -0,0 +1,138 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <ltpf.c>
#include "ctypes.h"
static PyObject *resample_py(PyObject *m, PyObject *args)
{
unsigned dt, sr;
PyObject *hp50_obj, *x_obj, *y_obj;
struct lc3_ltpf_hp50_state hp50;
float *x, *y;
if (!PyArg_ParseTuple(args, "IIOOO", &dt, &sr, &hp50_obj, &x_obj, &y_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
CTYPES_CHECK(NULL, hp50_obj = to_ltpf_hp50_state(hp50_obj, &hp50));
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
int ny = sizeof((struct lc3_ltpf_analysis){ }.x_12k8) / sizeof(float);
int n = dt == LC3_DT_7M5 ? 96 : 128;
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ns+nd, &x));
CTYPES_CHECK("y", y_obj = to_1d_ptr(y_obj, NPY_FLOAT, ny, &y));
resample_12k8[sr](&hp50, x + nd, y + (ny - n), n);
from_ltpf_hp50_state(hp50_obj, &hp50);
return Py_BuildValue("O", y_obj);
}
static PyObject *analyse_py(PyObject *m, PyObject *args)
{
PyObject *ltpf_obj, *x_obj;
unsigned dt, sr;
struct lc3_ltpf_analysis ltpf;
struct lc3_ltpf_data data = { 0 };
float *x;
if (!PyArg_ParseTuple(args, "IIOO", &dt, &sr, &ltpf_obj, &x_obj))
return NULL;
CTYPES_CHECK("dt", dt < LC3_NUM_DT);
CTYPES_CHECK("sr", sr < LC3_NUM_SRATE);
CTYPES_CHECK(NULL, ltpf_obj = to_ltpf_analysis(ltpf_obj, &ltpf));
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ns+nd, &x));
int pitch_present =
lc3_ltpf_analyse(dt, sr, &ltpf, x + nd, &data);
from_ltpf_analysis(ltpf_obj, &ltpf);
return Py_BuildValue("iN", pitch_present, new_ltpf_data(&data));
}
static PyObject *synthesize_py(PyObject *m, PyObject *args)
{
PyObject *ltpf_obj, *data_obj, *x_obj;
struct lc3_ltpf_synthesis ltpf;
struct lc3_ltpf_data data;
bool pitch_present;
unsigned dt, sr;
int nbytes;
float *x;
if (!PyArg_ParseTuple(args, "IIiOOO",
&dt, &sr, &nbytes, &ltpf_obj, &data_obj, &x_obj))
return NULL;
CTYPES_CHECK("dt", dt < LC3_NUM_DT);
CTYPES_CHECK("sr", sr < LC3_NUM_SRATE);
CTYPES_CHECK("nbytes", nbytes >= 20 && nbytes <= 400);
CTYPES_CHECK(NULL, ltpf_obj = to_ltpf_synthesis(ltpf_obj, &ltpf));
if ((pitch_present = (data_obj != Py_None)))
CTYPES_CHECK(NULL, data_obj = to_ltpf_data(data_obj, &data));
int ns = LC3_NS(dt,sr), nd = 18 * LC3_SRATE_KHZ(sr);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, nd+ns, &x));
lc3_ltpf_synthesize(dt, sr, nbytes,
&ltpf, pitch_present ? &data : NULL, x+nd);
from_ltpf_synthesis(ltpf_obj, &ltpf);
return Py_BuildValue("O", x_obj);
}
static PyObject *get_nbits_py(PyObject *m, PyObject *args)
{
int pitch_present;
if (!PyArg_ParseTuple(args, "i", &pitch_present))
return NULL;
int nbits = lc3_ltpf_get_nbits(pitch_present);
return Py_BuildValue("i", nbits);
}
static PyMethodDef methods[] = {
{ "ltpf_resample" , resample_py , METH_VARARGS },
{ "ltpf_analyse" , analyse_py , METH_VARARGS },
{ "ltpf_synthesize", synthesize_py, METH_VARARGS },
{ "ltpf_get_nbits" , get_nbits_py , METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_ltpf_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+27
View File
@@ -0,0 +1,27 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
TEST_DIR := test
.PHONY: test test-clean
test:
$(V)cd $(TEST_DIR) && python3 setup.py && python3 run.py
test-clean:
$(V)cd $(TEST_DIR) && python3 setup.py clean > /tmp/zero
clean-all: test-clean
+196
View File
@@ -0,0 +1,196 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import scipy.fft
import build.lc3 as lc3
import tables as T, appendix_c as C
### ------------------------------------------------------------------------ ###
class Mdct:
W = [ [ T.W_7M5_60, T.W_7M5_120, T.W_7M5_180, T.W_7M5_240, T.W_7M5_360 ],
[ T.W_10M_80, T.W_10M_160, T.W_10M_240, T.W_10M_320, T.W_10M_480 ] ]
def __init__(self, dt, sr):
self.ns = T.NS[dt][sr]
self.nd = T.ND[dt][sr]
self.t = np.zeros(2*self.ns)
self.w = Mdct.W[dt][sr]
class MdctForward(Mdct):
def __init__(self, dt, sr):
super().__init__(dt, sr)
def run(self, x):
ns = self.ns
nd = self.nd
self.t[nd:nd+ns] = x
t = self.t * self.w
self.t[0:nd] = x[ns-nd:]
n = len(t)
n2 = n // 2
z = t * np.exp(-2j * np.pi * np.arange(n) / (2*n))
z = scipy.fft.fft(z)[:n2]
z = z * np.exp(-2j * np.pi *
(n2/2 + 0.5) * (np.arange(n2) + 0.5) / (2 * n2))
return np.real(z) * np.sqrt(2/n2)
class MdctInverse(Mdct):
def __init__(self, dt, sr):
super().__init__(dt, sr)
def run(self, x):
ns = self.ns
nd = self.nd
n = len(x)
x = np.append(x, -x[::-1])
z = x * np.exp(2j * np.pi * (n/2 + 0.5) * np.arange(2*n) / (2*n))
z = scipy.fft.ifft(z) * n
z = z * np.exp(2j * np.pi * (np.arange(2*n) + (n/2 + 0.5)) / (4*n))
t = np.real(z) * np.sqrt(2/n)
t = t * self.w[::-1]
y = np.empty(ns)
y[:nd] = t[ns-nd:ns] + self.t[2*ns-nd:]
y[nd:] = t[ns:2*ns-nd]
self.t = t
return y
### ------------------------------------------------------------------------ ###
def check_forward_unit(rng, dt, sr):
ns = T.NS[dt][sr]
nd = T.ND[dt][sr]
ok = True
x = (2 * rng.random(ns)) - 1
mdct = MdctForward(dt, sr)
y = [ mdct.run(x), mdct.run(x) ]
y_c = [ lc3.mdct_forward(dt, sr, np.append(np.zeros(nd), x)),
lc3.mdct_forward(dt, sr, np.append(x[-nd:], x)) ]
ok = ok and np.amax(np.abs(y[0] - y_c[0])) < 1e-5
ok = ok and np.amax(np.abs(y[1] - y_c[1])) < 1e-5
return ok
def check_forward_appendix_c(dt):
sr = T.SRATE_16K
ns = T.NS[dt][sr]
nd = T.ND[dt][sr]
ok = True
y = lc3.mdct_forward(dt, sr,
np.append(np.zeros(nd), C.X_PCM[dt][0]))
ok = ok and np.amax(np.abs(y - C.X[dt][0])) < 1e-1
y = lc3.mdct_forward(dt, sr,
np.append(C.X_PCM[dt][0][-nd:], C.X_PCM[dt][1]))
ok = ok and np.amax(np.abs(y - C.X[dt][1])) < 1e-1
return ok
def check_inverse_unit(rng, dt, sr):
ns = T.NS[dt][sr]
nd = [ (23 * ns) // 30, (5 * ns) // 8 ][dt]
ok = True
x = (2 * rng.random(ns)) - 1
y = [ None ] * 2
y_c = [ None ] * 2
mdct = MdctInverse(dt, sr)
y[0] = mdct.run(x)
y[1] = mdct.run(x)
(y_c[0], d_c) = lc3.mdct_inverse(dt, sr, x, np.zeros(nd))
y_c[1] = lc3.mdct_inverse(dt, sr, x, d_c)[0]
ok = ok and np.amax(np.abs(y[0] - y_c[0])) < 1e-5
ok = ok and np.amax(np.abs(y[1] - y_c[1])) < 1e-5
return ok
def check_inverse_appendix_c(dt):
sr = T.SRATE_16K
ns = T.NS[dt][sr]
nd = [ (23 * ns) // 30, (5 * ns) // 8 ][dt]
ok = True
(y, d0) = lc3.mdct_inverse(dt, sr, C.X_HAT_SNS[dt][0], np.zeros(nd))
yr = C.T_HAT_MDCT[dt][0][ns-nd:2*ns-nd]
dr = C.T_HAT_MDCT[dt][0][2*ns-nd:]
ok = ok and np.amax(np.abs(yr - y )) < 1e-1
ok = ok and np.amax(np.abs(dr - d0)) < 1e-1
(y, d1) = lc3.mdct_inverse(dt, sr, C.X_HAT_SNS[dt][1], d0)
yr[ :nd] = C.T_HAT_MDCT[dt][1][ns-nd:ns] + d0
yr[nd:ns] = C.T_HAT_MDCT[dt][1][ns:2*ns-nd]
dr = C.T_HAT_MDCT[dt][1][2*ns-nd:]
ok = ok and np.amax(np.abs(yr - y )) < 1e-1
ok = ok and np.amax(np.abs(dr - d1)) < 1e-1
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_forward_unit(rng, dt, sr)
ok = ok and check_inverse_unit(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_forward_appendix_c(dt)
ok = ok and check_inverse_appendix_c(dt)
return ok
+89
View File
@@ -0,0 +1,89 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <mdct.c>
#include "ctypes.h"
static PyObject *mdct_forward_py(PyObject *m, PyObject *args)
{
PyObject *x_obj, *y_obj;
enum lc3_dt dt;
enum lc3_srate sr;
float *x, *y;
if (!PyArg_ParseTuple(args, "iiO", &dt, &sr, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, nd+ns, &x));
y_obj = new_1d_ptr(NPY_FLOAT, ns, &y);
lc3_mdct_forward(dt, sr, sr, x+nd, y);
return Py_BuildValue("N", y_obj);
}
static PyObject *mdct_inverse_py(PyObject *m, PyObject *args)
{
PyObject *x_obj, *xd_obj, *d_obj, *y_obj;
enum lc3_dt dt;
enum lc3_srate sr;
float *x, *xd, *d, *y;
if (!PyArg_ParseTuple(args, "iiOO", &dt, &sr, &x_obj, &xd_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, ns, &x));
CTYPES_CHECK("xd", to_1d_ptr(xd_obj, NPY_FLOAT, nd, &xd));
d_obj = new_1d_ptr(NPY_FLOAT, nd, &d);
y_obj = new_1d_ptr(NPY_FLOAT, ns, &y);
memcpy(d, xd, nd * sizeof(float));
lc3_mdct_inverse(dt, sr, sr, x, d, y);
return Py_BuildValue("NN", y_obj, d_obj);
}
static PyMethodDef methods[] = {
{ "mdct_forward", mdct_forward_py, METH_VARARGS },
{ "mdct_inverse", mdct_inverse_py, METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_mdct_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+53
View File
@@ -0,0 +1,53 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include <Python.h>
static struct PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "LC3",
.m_doc = "LC3 Test Python Module",
.m_size = -1,
};
PyMODINIT_FUNC lc3_mdct_py_init(PyObject *);
PyMODINIT_FUNC lc3_energy_py_init(PyObject *);
PyMODINIT_FUNC lc3_attdet_py_init(PyObject *);
PyMODINIT_FUNC lc3_bwdet_py_init(PyObject *);
PyMODINIT_FUNC lc3_ltpf_py_init(PyObject *);
PyMODINIT_FUNC lc3_sns_py_init(PyObject *);
PyMODINIT_FUNC lc3_tns_py_init(PyObject *);
PyMODINIT_FUNC lc3_spec_py_init(PyObject *);
PyMODINIT_FUNC lc3_interface_py_init(PyObject *);
PyMODINIT_FUNC PyInit_lc3(void)
{
PyObject *m = PyModule_Create(&module_def);
if (m) m = lc3_mdct_py_init(m);
if (m) m = lc3_energy_py_init(m);
if (m) m = lc3_attdet_py_init(m);
if (m) m = lc3_bwdet_py_init(m);
if (m) m = lc3_ltpf_py_init(m);
if (m) m = lc3_sns_py_init(m);
if (m) m = lc3_tns_py_init(m);
if (m) m = lc3_spec_py_init(m);
if (m) m = lc3_interface_py_init(m);
return m;
}
Executable
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import mdct, energy, bwdet, attdet
import ltpf, sns, tns, spec, encoder, decoder
ok = True
for m in [ ( mdct , "MDCT" ),
( energy , "Energy Band" ),
( bwdet , "Bandwidth Detector" ),
( attdet , "Attack Detector" ),
( ltpf , "Long Term Postfilter" ),
( sns , "Spectral Noise Shaping" ),
( tns , "Temporal Noise Shaping" ),
( spec , "Spectral Quantization" ),
( encoder , "Encoder" ),
( decoder , "Decoder" ) ]:
print('[{:^6}] {:}'.format('...', m[1]), end='\r', flush=True)
ret = m[0].check()
print('[{:^6}] {:}'.format('OK' if ret else 'FAILED', m[1]))
ok = ok and ret
exit(0 if ok else 1);
Executable
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from distutils.core import setup, Extension
import os, sys, glob
if len(sys.argv) <= 1:
sys.argv = sys.argv + [
'build', '--build-base', os.getcwd() + os.sep + 'build',
'install', '--install-lib', os.getcwd() + os.sep + 'build' ]
INC_DIR = '..' + os.sep + 'include'
SRC_DIR = '..' + os.sep + 'src'
sources = glob.glob('*_py.c') + \
[ SRC_DIR + os.sep + 'tables.c',
SRC_DIR + os.sep + 'bits.c',
SRC_DIR + os.sep + 'plc.c' ]
depends = [ 'ctypes.h' ] + \
glob.glob(INC_DIR + os.sep + '*.h') + \
glob.glob(SRC_DIR + os.sep + '*.[c,h]')
includes = [ SRC_DIR, INC_DIR ]
ctiming = Extension('lc3',
extra_compile_args = ['-std=c11'],
define_macros = [ ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION') ],
sources = sources,
depends = depends,
include_dirs = includes)
setup(name = 'LC3',
version = '1.0',
description = 'LC3 Test Python Module',
ext_modules = [ctiming])
+594
View File
@@ -0,0 +1,594 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import scipy.fftpack as fftpack
import build.lc3 as lc3
import tables as T, appendix_c as C
### ------------------------------------------------------------------------ ###
class Sns:
def __init__(self, dt, sr):
self.dt = dt
self.sr = sr
(self.ind_lf, self.ind_hf, self.shape, self.gain) = \
(None, None, None, None)
(self.idx_a, self.ls_a, self.idx_b, self.ls_b) = \
(None, None, None, None)
def get_data(self):
data = { 'lfcb' : self.ind_lf, 'hfcb' : self.ind_hf,
'shape' : self.shape, 'gain' : self.gain,
'idx_a' : self.idx_a, 'ls_a' : self.ls_a }
if self.idx_b is not None:
data.update({ 'idx_b' : self.idx_b, 'ls_b' : self.ls_b })
return data
def get_nbits(self):
return 38
def spectral_shaping(self, scf, inv, x):
## 3.3.7.4 Scale factors interpolation
scf_i = np.empty(4*len(scf))
scf_i[0 ] = scf[0]
scf_i[1 ] = scf[0]
scf_i[2:62:4] = scf[:15] + 1/8 * (scf[1:] - scf[:15])
scf_i[3:63:4] = scf[:15] + 3/8 * (scf[1:] - scf[:15])
scf_i[4:64:4] = scf[:15] + 5/8 * (scf[1:] - scf[:15])
scf_i[5:64:4] = scf[:15] + 7/8 * (scf[1:] - scf[:15])
scf_i[62 ] = scf[15 ] + 1/8 * (scf[15] - scf[14 ])
scf_i[63 ] = scf[15 ] + 3/8 * (scf[15] - scf[14 ])
n2 = 64 - min(len(x), 64)
for i in range(n2):
scf_i[i] = 0.5 * (scf_i[2*i] + scf_i[2*i+1])
scf_i = np.append(scf_i[:n2], scf_i[2*n2:])
g_sns = np.power(2, [ -scf_i, scf_i ][inv])
## 3.3.7.4 Spectral shaping
y = np.empty(len(x))
I = T.I[self.dt][self.sr]
for b in range(len(g_sns)):
y[I[b]:I[b+1]] = x[I[b]:I[b+1]] * g_sns[b]
return y
class SnsAnalysis(Sns):
def __init__(self, dt, sr):
super().__init__(dt, sr)
def compute_scale_factors(self, e, att):
dt = self.dt
## 3.3.7.2.1 Padding
n2 = 64 - len(e)
e = np.append(np.empty(n2), e)
for i in range(n2):
e[2*i+0] = e[2*i+1] = e[n2+i]
## 3.3.7.2.2 Smoothing
e_s = np.zeros(len(e))
e_s[0 ] = 0.75 * e[0 ] + 0.25 * e[1 ]
e_s[1:63] = 0.25 * e[0:62] + 0.5 * e[1:63] + 0.25 * e[2:64]
e_s[ 63] = 0.25 * e[ 62] + 0.75 * e[ 63]
## 3.3.7.2.3 Pre-emphasis
g_tilt = [ 14, 18, 22, 26, 30 ][self.sr]
e_p = e_s * (10 ** ((np.arange(64) * g_tilt) / 630))
## 3.3.7.2.4 Noise floor
noise_floor = max(np.average(e_p) * (10 ** (-40/10)), 2 ** -32)
e_p = np.fmax(e_p, noise_floor * np.ones(len(e)))
## 3.3.7.2.5 Logarithm
e_l = np.log2(10 ** -31 + e_p) / 2
## 3.3.7.2.6 Band energy grouping
w = [ 1/12, 2/12, 3/12, 3/12, 2/12, 1/12 ]
e_4 = np.zeros(len(e_l) // 4)
e_4[0 ] = w[0] * e_l[0] + np.sum(w[1:] * e_l[:5])
e_4[1:15] = [ np.sum(w * e_l[4*i-1:4*i+5]) for i in range(1, 15) ]
e_4[ 15] = np.sum(w[:5] * e_l[59:64]) + w[5] * e_l[63]
## 3.3.7.2.7 Mean removal and scaling, attack handling
scf = 0.85 * (e_4 - np.average(e_4))
scf_a = np.zeros(len(scf))
scf_a[0 ] = np.average(scf[:3])
scf_a[1 ] = np.average(scf[:4])
scf_a[2:14] = [ np.average(scf[i:i+5]) for i in range(12) ]
scf_a[ 14] = np.average(scf[12:])
scf_a[ 15] = np.average(scf[13:])
scf_a = (0.5 if self.dt == T.DT_10M else 0.3) * \
(scf_a - np.average(scf_a))
return scf_a if att else scf
def enum_mpvq(self, v):
sign = None
index = 0
x = 0
for (n, vn) in enumerate(v[::-1]):
if sign is not None and vn != 0:
index = 2*index + sign
if vn != 0:
sign = 1 if vn < 0 else 0
index += T.SNS_MPVQ_OFFSETS[n][x]
x += abs(vn)
return (index, bool(sign))
def quantize(self, scf):
## 3.3.7.3.2 Stage 1
dmse_lf = [ np.sum((scf[:8] - T.SNS_LFCB[i]) ** 2) for i in range(32) ]
dmse_hf = [ np.sum((scf[8:] - T.SNS_HFCB[i]) ** 2) for i in range(32) ]
self.ind_lf = np.argmin(dmse_lf)
self.ind_hf = np.argmin(dmse_hf)
st1 = np.append(T.SNS_LFCB[self.ind_lf], T.SNS_HFCB[self.ind_hf])
r1 = scf - st1
## 3.3.7.3.3 Stage 2
t2_rot = fftpack.dct(r1, norm = 'ortho')
x = np.abs(t2_rot)
## 3.3.7.3.3 Stage 2 Shape search, step 1
K = 6
proj_fac = (K - 1) / sum(np.abs(t2_rot))
y3 = np.floor(x * proj_fac).astype(int)
## 3.3.7.3.3 Stage 2 Shape search, step 2
corr_xy = np.sum(y3 * x)
energy_y = np.sum(y3 * y3)
k0 = sum(y3)
for k in range(k0, K):
q_pvq = ((corr_xy + x) ** 2) / (energy_y + 2*y3 + 1)
n_best = np.argmax(q_pvq)
corr_xy += x[n_best]
energy_y += 2*y3[n_best] + 1
y3[n_best] += 1
## 3.3.7.3.3 Stage 2 Shape search, step 3
K = 8
y2 = y3.copy()
for k in range(sum(y2), K):
q_pvq = ((corr_xy + x) ** 2) / (energy_y + 2*y2 + 1)
n_best = np.argmax(q_pvq)
corr_xy += x[n_best]
energy_y += 2*y2[n_best] + 1
y2[n_best] += 1
## 3.3.7.3.3 Stage 2 Shape search, step 4
y1 = np.append(y2[:10], [0] * 6)
## 3.3.7.3.3 Stage 2 Shape search, step 5
corr_xy -= sum(y2[10:] * x[10:])
energy_y -= sum(y2[10:] * y2[10:])
## 3.3.7.3.3 Stage 2 Shape search, step 6
K = 10
for k in range(sum(y1), K):
q_pvq = ((corr_xy + x[:10]) ** 2) / (energy_y + 2*y1[:10] + 1)
n_best = np.argmax(q_pvq)
corr_xy += x[n_best]
energy_y += 2*y1[n_best] + 1
y1[n_best] += 1
## 3.3.7.3.3 Stage 2 Shape search, step 7
y0 = np.append(y1[:10], [ 0 ] * 6)
q_pvq = ((corr_xy + x[10:]) ** 2) / (energy_y + 2*y0[10:] + 1)
n_best = 10 + np.argmax(q_pvq)
y0[n_best] += 1
## 3.3.7.3.3 Stage 2 Shape search, step 8
y0 *= np.sign(t2_rot).astype(int)
y1 *= np.sign(t2_rot).astype(int)
y2 *= np.sign(t2_rot).astype(int)
y3 *= np.sign(t2_rot).astype(int)
## 3.3.7.3.3 Stage 2 Shape search, step 9
xq = [ y / np.sqrt(sum(y ** 2)) for y in (y0, y1, y2, y3) ]
## 3.3.7.3.3 Shape and gain combination determination
G = [ T.SNS_VQ_REG_ADJ_GAINS, T.SNS_VQ_REG_LF_ADJ_GAINS,
T.SNS_VQ_NEAR_ADJ_GAINS, T.SNS_VQ_FAR_ADJ_GAINS ]
dMSE = [ [ sum((t2_rot - G[j][i] * xq[j]) ** 2)
for i in range(len(G[j])) ] for j in range(4) ]
self.shape = np.argmin([ np.min(dMSE[j]) for j in range(4) ])
self.gain = np.argmin(dMSE[self.shape])
gain = G[self.shape][self.gain]
## 3.3.7.3.3 Enumeration of the selected PVQ pulse configurations
if self.shape == 0:
(self.idx_a, self.ls_a) = self.enum_mpvq(y0[:10])
(self.idx_b, self.ls_b) = self.enum_mpvq(y0[10:])
elif self.shape == 1:
(self.idx_a, self.ls_a) = self.enum_mpvq(y1[:10])
(self.idx_b, self.ls_b) = (None, None)
elif self.shape == 2:
(self.idx_a, self.ls_a) = self.enum_mpvq(y2)
(self.idx_b, self.ls_b) = (None, None)
elif self.shape == 3:
(self.idx_a, self.ls_a) = self.enum_mpvq(y3)
(self.idx_b, self.ls_b) = (None, None)
## 3.3.7.3.4 Synthesis of the Quantized scale factor
scf_q = st1 + gain * fftpack.idct(xq[self.shape], norm = 'ortho')
return scf_q
def run(self, eb, att, x):
scf = self.compute_scale_factors(eb, att)
scf_q = self.quantize(scf)
y = self.spectral_shaping(scf_q, False, x)
return y
def store(self, b):
shape = self.shape
gain_msb_bits = np.array([ 1, 1, 2, 2 ])[shape]
gain_lsb_bits = np.array([ 0, 1, 0, 1 ])[shape]
b.write_uint(self.ind_lf, 5)
b.write_uint(self.ind_hf, 5)
b.write_bit(shape >> 1)
b.write_uint(self.gain >> gain_lsb_bits, gain_msb_bits)
b.write_bit(self.ls_a)
if self.shape == 0:
sz_shape_a = 2390004
index_joint = self.idx_a + \
(2 * self.idx_b + self.ls_b + 2) * sz_shape_a
elif self.shape == 1:
sz_shape_a = 2390004
index_joint = self.idx_a + (self.gain & 1) * sz_shape_a
elif self.shape == 2:
index_joint = self.idx_a
elif self.shape == 3:
sz_shape_a = 15158272
index_joint = sz_shape_a + (self.gain & 1) + 2 * self.idx_a
b.write_uint(index_joint, 14 - gain_msb_bits)
b.write_uint(index_joint >> (14 - gain_msb_bits), 12)
class SnsSynthesis(Sns):
def __init__(self, dt, sr):
super().__init__(dt, sr)
def deenum_mpvq(self, index, ls, npulses, n):
y = np.zeros(n, dtype=np.int)
pos = 0
for i in range(len(y)-1, -1, -1):
if index > 0:
yi = 0
while index < T.SNS_MPVQ_OFFSETS[i][npulses - yi]: yi += 1
index -= T.SNS_MPVQ_OFFSETS[i][npulses - yi]
else:
yi = npulses
y[pos] = [ yi, -yi ][int(ls)]
pos += 1
npulses -= yi
if npulses <= 0:
break
if yi > 0:
ls = index & 1
index >>= 1
return y
def unquantize(self):
## 3.7.4.2.1-2 SNS VQ Decoding
y = np.empty(16, dtype=np.int)
if self.shape == 0:
y[:10] = self.deenum_mpvq(self.idx_a, self.ls_a, 10, 10)
y[10:] = self.deenum_mpvq(self.idx_b, self.ls_b, 1, 6)
elif self.shape == 1:
y[:10] = self.deenum_mpvq(self.idx_a, self.ls_a, 10, 10)
y[10:] = np.zeros(6, dtype=np.int)
elif self.shape == 2:
y = self.deenum_mpvq(self.idx_a, self.ls_a, 8, 16)
elif self.shape == 3:
y = self.deenum_mpvq(self.idx_a, self.ls_a, 6, 16)
## 3.7.4.2.3 Unit energy normalization
y = y / np.sqrt(sum(y ** 2))
## 3.7.4.2.4 Reconstruction of the quantized scale factors
G = [ T.SNS_VQ_REG_ADJ_GAINS, T.SNS_VQ_REG_LF_ADJ_GAINS,
T.SNS_VQ_NEAR_ADJ_GAINS, T.SNS_VQ_FAR_ADJ_GAINS ]
gain = G[self.shape][self.gain]
scf = np.append(T.SNS_LFCB[self.ind_lf], T.SNS_HFCB[self.ind_hf]) \
+ gain * fftpack.idct(y, norm = 'ortho')
return scf
def load(self, b):
self.ind_lf = b.read_uint(5)
self.ind_hf = b.read_uint(5)
shape_msb = b.read_bit()
gain_msb_bits = 1 + shape_msb
self.gain = b.read_uint(gain_msb_bits)
self.ls_a = b.read_bit()
index_joint = b.read_uint(14 - gain_msb_bits)
index_joint |= b.read_uint(12) << (14 - gain_msb_bits)
if shape_msb == 0:
sz_shape_a = 2390004
if index_joint >= sz_shape_a * 14:
raise ValueError('Invalide SNS joint index')
self.idx_a = index_joint % sz_shape_a
index_joint = index_joint // sz_shape_a
if index_joint >= 2:
self.shape = 0
self.idx_b = (index_joint - 2) // 2
self.ls_b = (index_joint - 2) % 2
else:
self.shape = 1
self.gain = (self.gain << 1) + (index_joint & 1)
else:
sz_shape_a = 15158272
if index_joint >= sz_shape_a + 1549824:
raise ValueError('Invalide SNS joint index')
if index_joint < sz_shape_a:
self.shape = 2
self.idx_a = index_joint
else:
self.shape = 3
index_joint -= sz_shape_a
self.gain = (self.gain << 1) + (index_joint % 2)
self.idx_a = index_joint // 2
def run(self, x):
scf = self.unquantize()
y = self.spectral_shaping(scf, True, x)
return y
### ------------------------------------------------------------------------ ###
def check_analysis(rng, dt, sr):
ok = True
analysis = SnsAnalysis(dt, sr)
for i in range(10):
x = rng.random(T.NE[dt][sr]) * 1e4
e = rng.random(min(len(x), 64)) * 1e10
for att in (0, 1):
y = analysis.run(e, att, x)
data = analysis.get_data()
(y_c, data_c) = lc3.sns_analyze(dt, sr, e, att, x)
for k in data.keys():
ok = ok and data_c[k] == data[k]
ok = ok and lc3.sns_get_nbits() == analysis.get_nbits()
ok = ok and np.amax(np.abs(y - y_c)) < 1e-2
return ok
def check_synthesis(rng, dt, sr):
ok = True
synthesis = SnsSynthesis(dt, sr)
for i in range(100):
synthesis.ind_lf = rng.integers(0, 32)
synthesis.ind_hf = rng.integers(0, 32)
shape = rng.integers(0, 4)
sz_shape_a = [ 2390004, 2390004, 15158272, 774912 ][shape]
sz_shape_b = [ 6, 1, 0, 0 ][shape]
synthesis.shape = shape
synthesis.gain = rng.integers(0, [ 2, 4, 4, 8 ][shape])
synthesis.idx_a = rng.integers(0, sz_shape_a, endpoint=True)
synthesis.ls_a = bool(rng.integers(0, 1, endpoint=True))
synthesis.idx_b = rng.integers(0, sz_shape_b, endpoint=True)
synthesis.ls_b = bool(rng.integers(0, 1, endpoint=True))
x = rng.random(T.NE[dt][sr]) * 1e4
y = synthesis.run(x)
y_c = lc3.sns_synthesize(dt, sr, synthesis.get_data(), x)
ok = ok and np.amax(np.abs(y - y_c)) < 1e0
return ok
def check_analysis_appendix_c(dt):
sr = T.SRATE_16K
ok = True
for i in range(len(C.E_B[dt])):
scf = lc3.sns_compute_scale_factors(dt, sr, C.E_B[dt][i], False)
ok = ok and np.amax(np.abs(scf - C.SCF[dt][i])) < 1e-5
(lf, hf) = lc3.sns_resolve_codebooks(scf)
ok = ok and lf == C.IND_LF[dt][i] and hf == C.IND_HF[dt][i]
(y, yn, shape, gain) = lc3.sns_quantize(scf, lf, hf)
ok = ok and np.any(y[0][:16] - C.SNS_Y0[dt][i] == 0)
ok = ok and np.any(y[1][:10] - C.SNS_Y1[dt][i] == 0)
ok = ok and np.any(y[2][:16] - C.SNS_Y2[dt][i] == 0)
ok = ok and np.any(y[3][:16] - C.SNS_Y3[dt][i] == 0)
ok = ok and shape == 2*C.SUBMODE_MSB[dt][i] + C.SUBMODE_LSB[dt][i]
ok = ok and gain == C.G_IND[dt][i]
scf_q = lc3.sns_unquantize(lf, hf, yn[shape], shape, gain)
ok = ok and np.amax(np.abs(scf_q - C.SCF_Q[dt][i])) < 1e-5
x = lc3.sns_spectral_shaping(dt, sr, C.SCF_Q[dt][i], False, C.X[dt][i])
ok = ok and np.amax(np.abs(1 - x/C.X_S[dt][i])) < 1e-6
(x, data) = lc3.sns_analyze(dt, sr, C.E_B[dt][i], False, C.X[dt][i])
ok = ok and data['lfcb'] == C.IND_LF[dt][i]
ok = ok and data['hfcb'] == C.IND_HF[dt][i]
ok = ok and data['shape'] == \
2*C.SUBMODE_MSB[dt][i] + C.SUBMODE_LSB[dt][i]
ok = ok and data['gain'] == C.G_IND[dt][i]
ok = ok and data['idx_a'] == C.IDX_A[dt][i]
ok = ok and data['ls_a'] == C.LS_IND_A[dt][i]
ok = ok and (C.IDX_B[dt][i] is None or
data['idx_b'] == C.IDX_B[dt][i])
ok = ok and (C.LS_IND_B[dt][i] is None or
data['ls_b'] == C.LS_IND_B[dt][i])
ok = ok and np.amax(np.abs(1 - x/C.X_S[dt][i])) < 1e-6
return ok
def check_synthesis_appendix_c(dt):
sr = T.SRATE_16K
ok = True
for i in range(len(C.X_HAT_TNS[dt])):
data = {
'lfcb' : C.IND_LF[dt][i], 'hfcb' : C.IND_HF[dt][i],
'shape' : 2*C.SUBMODE_MSB[dt][i] + C.SUBMODE_LSB[dt][i],
'gain' : C.G_IND[dt][i],
'idx_a' : C.IDX_A[dt][i],
'ls_a' : C.LS_IND_A[dt][i],
'idx_b' : C.IDX_B[dt][i] if C.IDX_B[dt][i] is not None else 0,
'ls_b' : C.LS_IND_B[dt][i] if C.LS_IND_B[dt][i] is not None else 0,
}
x = lc3.sns_synthesize(dt, sr, data, C.X_HAT_TNS[dt][i])
ok = ok and np.amax(np.abs(x - C.X_HAT_SNS[dt][i])) < 1e0
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_analysis(rng, dt, sr)
ok = ok and check_synthesis(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_analysis_appendix_c(dt)
ok = ok and check_synthesis_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+215
View File
@@ -0,0 +1,215 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include "lc3.h"
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <sns.c>
#include "ctypes.h"
static PyObject *compute_scale_factors_py(PyObject *m, PyObject *args)
{
unsigned dt, sr;
PyObject *eb_obj, *scf_obj;
float *eb, *scf;
int att;
if (!PyArg_ParseTuple(args, "IIOp", &dt, &sr, &eb_obj, &att))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int nb = LC3_MIN(lc3_band_lim[dt][sr][LC3_NUM_BANDS], LC3_NUM_BANDS);
CTYPES_CHECK("eb", to_1d_ptr(eb_obj, NPY_FLOAT, nb, &eb));
scf_obj = new_1d_ptr(NPY_FLOAT, 16, &scf);
compute_scale_factors(dt, sr, eb, att, scf);
return Py_BuildValue("N", scf_obj);
}
static PyObject *resolve_codebooks_py(PyObject *m, PyObject *args)
{
PyObject *scf_obj;
float *scf;
int lfcb_idx, hfcb_idx;
if (!PyArg_ParseTuple(args, "O", &scf_obj))
return NULL;
CTYPES_CHECK("eb", to_1d_ptr(scf_obj, NPY_FLOAT, 16, &scf));
resolve_codebooks(scf, &lfcb_idx, &hfcb_idx);
return Py_BuildValue("ii", lfcb_idx, hfcb_idx);
}
static PyObject *quantize_py(PyObject *m, PyObject *args)
{
PyObject *scf_obj, *y_obj, *yn_obj;
float *scf;
int lfcb_idx, hfcb_idx;
int shape_idx, gain_idx;
float (*yn)[16];
int (*y)[16];
if (!PyArg_ParseTuple(args, "Oii", &scf_obj, &lfcb_idx, &hfcb_idx))
return NULL;
CTYPES_CHECK("scf", to_1d_ptr(scf_obj, NPY_FLOAT, 16, &scf));
CTYPES_CHECK("lfcb_idx", (unsigned)lfcb_idx < 32);
CTYPES_CHECK("hfcb_idx", (unsigned)hfcb_idx < 32);
y_obj = new_2d_ptr(NPY_INT, 4, 16, &y);
yn_obj = new_2d_ptr(NPY_FLOAT, 4, 16, &yn);
quantize(scf, lfcb_idx, hfcb_idx,
y, yn, &shape_idx, &gain_idx);
return Py_BuildValue("NNii", y_obj, yn_obj, shape_idx, gain_idx);
}
static PyObject *unquantize_py(PyObject *m, PyObject *args)
{
PyObject *y_obj, *scf_obj;
int lfcb_idx, hfcb_idx;
int shape, gain;
float *y, *scf;
if (!PyArg_ParseTuple(args, "iiOii",
&lfcb_idx, &hfcb_idx, &y_obj, &shape, &gain))
return NULL;
CTYPES_CHECK("lfcb_idx", (unsigned)lfcb_idx < 32);
CTYPES_CHECK("hfcb_idx", (unsigned)hfcb_idx < 32);
CTYPES_CHECK("y", to_1d_ptr(y_obj, NPY_FLOAT, 16, &y));
CTYPES_CHECK("shape", (unsigned)shape < 4);
CTYPES_CHECK("gain",
(unsigned)gain < (unsigned)lc3_sns_vq_gains[shape].count);
scf_obj = new_1d_ptr(NPY_FLOAT, 16, &scf);
unquantize(lfcb_idx, hfcb_idx, y, shape, gain, scf);
return Py_BuildValue("N", scf_obj);
}
static PyObject *spectral_shaping_py(PyObject *m, PyObject *args)
{
PyObject *scf_q_obj, *x_obj;
unsigned dt, sr;
float *scf_q, *x;
int inv;
if (!PyArg_ParseTuple(args, "IIOpO", &dt, &sr, &scf_q_obj, &inv, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ne = LC3_NE(dt, sr);
CTYPES_CHECK("scf_q", to_1d_ptr(scf_q_obj, NPY_FLOAT, 16, &scf_q));
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
spectral_shaping(dt, sr, scf_q, inv, x, x);
return Py_BuildValue("O", x_obj);
}
static PyObject *analyze_py(PyObject *m, PyObject *args)
{
PyObject *eb_obj, *x_obj;
struct lc3_sns_data data = { 0 };
unsigned dt, sr;
float *eb, *x;
int att;
if (!PyArg_ParseTuple(args, "IIOpO", &dt, &sr, &eb_obj, &att, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ne = LC3_NE(dt, sr);
int nb = LC3_MIN(ne, LC3_NUM_BANDS);
CTYPES_CHECK("eb", to_1d_ptr(eb_obj, NPY_FLOAT, nb, &eb));
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
lc3_sns_analyze(dt, sr, eb, att, &data, x, x);
return Py_BuildValue("ON", x_obj, new_sns_data(&data));
}
static PyObject *synthesize_py(PyObject *m, PyObject *args)
{
PyObject *data_obj, *x_obj;
struct lc3_sns_data data;
unsigned dt, sr;
float *x;
if (!PyArg_ParseTuple(args, "IIOO", &dt, &sr, &data_obj, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
CTYPES_CHECK(NULL, data_obj = to_sns_data(data_obj, &data));
int ne = LC3_NE(dt, sr);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
lc3_sns_synthesize(dt, sr, &data, x, x);
return Py_BuildValue("O", x_obj);
}
static PyObject *get_nbits_py(PyObject *m, PyObject *args)
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
int nbits = lc3_sns_get_nbits();
return Py_BuildValue("i", nbits);
}
static PyMethodDef methods[] = {
{ "sns_compute_scale_factors", compute_scale_factors_py, METH_VARARGS },
{ "sns_resolve_codebooks" , resolve_codebooks_py , METH_VARARGS },
{ "sns_quantize" , quantize_py , METH_VARARGS },
{ "sns_unquantize" , unquantize_py , METH_VARARGS },
{ "sns_spectral_shaping" , spectral_shaping_py , METH_VARARGS },
{ "sns_analyze" , analyze_py , METH_VARARGS },
{ "sns_synthesize" , synthesize_py , METH_VARARGS },
{ "sns_get_nbits" , get_nbits_py , METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_sns_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+812
View File
@@ -0,0 +1,812 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import build.lc3 as lc3
import tables as T, appendix_c as C
import bwdet as m_bwdet
import ltpf as m_ltpf
import sns as m_sns
import tns as m_tns
### ------------------------------------------------------------------------ ###
class SpectrumQuantization:
def __init__(self, dt, sr):
self.dt = dt
self.sr = sr
def get_gain_offset(self, nbytes):
g_off = (nbytes * 8) // (10 * (1 + self.sr))
g_off = -min(115, g_off) - (105 + 5*(1 + self.sr))
return g_off
def get_noise_indices(self, bw, xq, lastnz):
nf_start = [ 18, 24 ][self.dt]
nf_width = [ 2, 3 ][self.dt]
bw_stop = int([ 80, 160, 240, 320, 400 ][bw] * (T.DT_MS[self.dt] / 10))
xq = np.append(xq[:lastnz], np.zeros(len(xq) - lastnz))
i_nf = [ np.all(xq[k-nf_width:min(bw_stop, k+nf_width+1)] == 0)
for k in range(nf_start, bw_stop) ]
return (i_nf, nf_start, bw_stop)
class SpectrumAnalysis(SpectrumQuantization):
def __init__(self, dt, sr):
super().__init__(dt, sr)
self.reset_off = 0
self.nbits_off = 0
self.nbits_spec = 0
self.nbits_est = 0
self.g_idx = None
def estimate_gain(self, x, nbits_spec, nbits_off, g_off):
nbits = int(nbits_spec + nbits_off + 0.5)
### Energy (dB) by 4 MDCT coefficients
e = [ np.sum(x[4*k:4*(k+1)] ** 2) for k in range(len(x) // 4) ]
e = 10 * np.log10(2**-31 + np.array(e))
### Compute gain index
g_idx = 255
for i in range(8):
factor = 1 << (7 - i)
g_idx -= factor
tmp = 0
iszero = 1
for ei in e[-1::-1]:
if ei * 28/20 < g_idx + g_off:
if iszero == 0:
tmp += 2.7*28/20
else:
if g_idx + g_off < (ei - 43) * 28/20:
tmp += 2*ei*28/20 - 2*(g_idx + g_off) - 36*28/20
else:
tmp += ei*28/20 - (g_idx + g_off) + 7*28/20
iszero = 0
if tmp > nbits * 1.4 * 28/20 and iszero == 0:
g_idx += factor
### Limit gain index
x_max = np.amax(np.abs(x))
if x_max > 0:
g_min = 28 * np.log10(x_max / (32768 - 0.375))
g_min = np.ceil(g_min).astype(int) - g_off
reset_off = g_idx < g_min
else:
g_min = 0
reset_off = True
if reset_off:
g_idx = g_min
return (g_idx + g_off, reset_off)
def quantize(self, g_int, x):
xg = x / 10 ** (g_int / 28)
xq = np.where(xg < 0, np.ceil(xg - 0.375), np.floor(xg + 0.375))
xq = xq.astype(int)
xq = np.fmin(np.fmax(xq, -32768), 32767)
nz_pairs = np.any([ xq[::2] != 0, xq[1::2] != 0 ], axis=0)
lastnz = len(xq) - 2 * np.argmax(nz_pairs[-1::-1])
if not np.any(nz_pairs):
lastnz = 0
return (xg, xq, lastnz)
def compute_nbits(self, nbytes, x, lastnz, nbits_spec):
mode = 1 if nbytes >= 20 * (3 + self.sr) else 0
rate = 512 if nbytes > 20 * (1 + self.sr) else 0
nbits_est = 0
nbits_trunc = 0
nbits_lsb = 0
lastnz_trunc = 2
c = 0
for n in range(0, lastnz, 2):
t = c + rate
if n > len(x) // 2:
t += 256
a = abs(x[n ])
b = abs(x[n+1])
lev = 0
while max(a, b) >= 4:
nbits_est += \
T.AC_SPEC_BITS[T.AC_SPEC_LOOKUP[t + lev*1024]][16];
if lev == 0 and mode == 1:
nbits_lsb += 2
else:
nbits_est += 2 * 2048
a >>= 1
b >>= 1
lev = min(lev + 1, 3)
nbits_est += \
T.AC_SPEC_BITS[T.AC_SPEC_LOOKUP[t + lev*1024]][a + 4*b]
a_lsb = abs(x[n ])
b_lsb = abs(x[n+1])
nbits_est += (min(a_lsb, 1) + min(b_lsb, 1)) * 2048
if lev > 0 and mode == 1:
a_lsb >>= 1;
b_lsb >>= 1;
nbits_lsb += int(a_lsb == 0 and x[n ] != 0)
nbits_lsb += int(b_lsb == 0 and x[n+1] != 0)
if (x[n] != 0 or x[n+1] != 0) and \
(nbits_est <= nbits_spec * 2048):
lastnz_trunc = n + 2;
nbits_trunc = nbits_est
t = 1 + (a + b) * (lev + 1) if lev <= 1 else 12 + lev;
c = (c & 15) * 16 + t;
nbits_est = (nbits_est + 2047) // 2048 + nbits_lsb;
nbits_trunc = (nbits_trunc + 2047) // 2048
self.rate = rate
self.lsb_mode = mode == 1 and nbits_est > nbits_spec
return (nbits_est, nbits_trunc, lastnz_trunc, self.lsb_mode)
def adjust_gain(self, g_idx, nbits, nbits_spec):
T1 = [ 80, 230, 380, 530, 680 ]
T2 = [ 500, 1025, 1550, 2075, 2600 ]
T3 = [ 850, 1700, 2550, 3400, 4250 ]
sr = self.sr
if nbits < T1[sr]:
delta = (nbits + 48) / 16
elif nbits < T2[sr]:
a = T1[sr] / 16 + 3
b = T2[sr] / 48
delta = a + (nbits - T1[sr]) * (b - a) / (T2[sr] - T1[sr])
elif nbits < T3[sr]:
delta = nbits / 48
else:
delta = T3[sr] / 48;
delta = np.fix(delta + 0.5).astype(int)
if (g_idx < 255 and nbits > nbits_spec) or \
(g_idx > 0 and nbits < nbits_spec - (delta + 2)):
if nbits < nbits_spec - (delta + 2):
return - 1
if g_idx == 254 or nbits < nbits_spec + delta:
return 1
else:
return 2
return 0
def estimate_noise(self, bw, xq, lastnz, x):
(i_nf, nf_start, nf_stop) = self.get_noise_indices(bw, xq, lastnz)
nf = 8 - 16 * sum(abs(x[nf_start:nf_stop] * i_nf)) / sum(i_nf) \
if sum(i_nf) > 0 else 0
return min(max(np.rint(nf).astype(int), 0), 7)
def run(self,
bw, nbytes, nbits_bw, nbits_ltpf, nbits_sns, nbits_tns, x):
sr = self.sr
### Bit budget
nbits_gain = 8
nbits_nf = 3
nbits_ari = np.ceil(np.log2(len(x) / 2)).astype(int)
nbits_ari += 3 + min((8*nbytes - 1) // 1280, 2)
nbits_spec = 8*nbytes - \
nbits_bw - nbits_ltpf - nbits_sns - nbits_tns - \
nbits_gain - nbits_nf - nbits_ari
### Global gain estimation
nbits_off = self.nbits_off + self.nbits_spec - self.nbits_est
nbits_off = min(40, max(-40, nbits_off))
nbits_off = 0 if self.reset_off else \
0.8 * self.nbits_off + 0.2 * nbits_off
g_off = self.get_gain_offset(nbytes)
(g_int, self.reset_off) = \
self.estimate_gain(x, nbits_spec, nbits_off, g_off)
self.nbits_off = nbits_off
self.nbits_spec = nbits_spec
### Quantization
(xg, xq, lastnz) = self.quantize(g_int, x)
(nbits_est, nbits_trunc, lastnz_trunc, _) = \
self.compute_nbits(nbytes, xq, lastnz, nbits_spec)
self.nbits_est = nbits_est
### Adjust gain and requantize
g_adj = self.adjust_gain(g_int - g_off, nbits_est, nbits_spec)
(xg, xq, lastnz) = self.quantize(g_adj, xg)
(nbits_est, nbits_trunc, lastnz_trunc, lsb_mode) = \
self.compute_nbits(nbytes, xq, lastnz, nbits_spec)
self.g_idx = g_int + g_adj - g_off
self.xq = xq
self.lastnz = lastnz_trunc
self.nbits_residual_max = nbits_spec - nbits_trunc + 4
self.xg = xg
### Noise factor
self.noise_factor = self.estimate_noise(bw, xq, lastnz, x)
return (self.xq, self.lastnz, self.xg)
def store(self, b):
ne = T.NE[self.dt][self.sr]
nbits_lastnz = np.ceil(np.log2(ne/2)).astype(int)
b.write_uint((self.lastnz >> 1) - 1, nbits_lastnz)
b.write_uint(self.lsb_mode, 1)
def encode(self, bits):
### Noise factor
bits.write_uint(self.noise_factor, 3)
### Quantized data
lsbs = []
x = self.xq
c = 0
for n in range(0, self.lastnz, 2):
t = c + self.rate
if n > len(x) // 2:
t += 256
a = abs(x[n ])
b = abs(x[n+1])
lev = 0
while max(a, b) >= 4:
bits.ac_encode(
T.AC_SPEC_CUMFREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][16],
T.AC_SPEC_FREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][16])
if lev == 0 and self.lsb_mode:
lsb_0 = a & 1
lsb_1 = b & 1
else:
bits.write_bit(a & 1)
bits.write_bit(b & 1)
a >>= 1
b >>= 1
lev = min(lev + 1, 3)
bits.ac_encode(
T.AC_SPEC_CUMFREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][a + 4*b],
T.AC_SPEC_FREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][a + 4*b])
a_lsb = abs(x[n ])
b_lsb = abs(x[n+1])
if lev > 0 and self.lsb_mode:
a_lsb >>= 1
b_lsb >>= 1
lsbs.append(lsb_0)
if a_lsb == 0 and x[n+0] != 0:
lsbs.append(int(x[n+0] < 0))
lsbs.append(lsb_1)
if b_lsb == 0 and x[n+1] != 0:
lsbs.append(int(x[n+1] < 0))
if a_lsb > 0:
bits.write_bit(int(x[n+0] < 0))
if b_lsb > 0:
bits.write_bit(int(x[n+1] < 0))
t = 1 + (a + b) * (lev + 1) if lev <= 1 else 12 + lev;
c = (c & 15) * 16 + t;
### Residual data
if self.lsb_mode == 0:
nbits_residual = min(bits.get_bits_left(), self.nbits_residual_max)
for i in range(len(self.xg)):
if self.xq[i] == 0:
continue
bits.write_bit(self.xg[i] >= self.xq[i])
nbits_residual -= 1
if nbits_residual <= 0:
break
else:
nbits_residual = min(bits.get_bits_left(), len(lsbs))
for lsb in lsbs[:nbits_residual]:
bits.write_bit(lsb)
class SpectrumSynthesis(SpectrumQuantization):
def __init__(self, dt, sr):
super().__init__(dt, sr)
def fill_noise(self, bw, x, lastnz, f_nf, nf_seed):
(i_nf, nf_start, nf_stop) = self.get_noise_indices(bw, x, lastnz)
k_nf = nf_start + np.argwhere(i_nf)
l_nf = (8 - f_nf)/16
for k in k_nf:
nf_seed = (13849 + nf_seed * 31821) & 0xffff
x[k] = [ -l_nf, l_nf ][nf_seed < 0x8000]
return x
def load(self, b):
ne = T.NE[self.dt][self.sr]
nbits_lastnz = np.ceil(np.log2(ne/2)).astype(int)
self.lastnz = (b.read_uint(nbits_lastnz) + 1) << 1
self.lsb_mode = b.read_uint(1)
self.g_idx = b.read_uint(8)
if self.lastnz > ne:
raise ValueError('Invalid count of coded samples')
def decode(self, bits, bw, nbytes):
### Noise factor
f_nf = bits.read_uint(3)
### Quantized data
x = np.zeros(T.NE[self.dt][self.sr])
rate = 512 if nbytes > 20 * (1 + self.sr) else 0
levs = np.zeros(len(x), dtype=np.int)
c = 0
for n in range(0, self.lastnz, 2):
t = c + rate
if n > len(x) // 2:
t += 256
for lev in range(14):
s = t + min(lev, 3) * 1024
sym = bits.ac_decode(
T.AC_SPEC_CUMFREQ[T.AC_SPEC_LOOKUP[s]],
T.AC_SPEC_FREQ[T.AC_SPEC_LOOKUP[s]])
if sym < 16:
break
if self.lsb_mode == 0 or lev > 0:
x[n ] += bits.read_bit() << lev
x[n+1] += bits.read_bit() << lev
if lev >= 14:
raise ValueError('Out of range value')
a = sym % 4
b = sym // 4
levs[n ] = lev
levs[n+1] = lev
x[n ] += a << lev
x[n+1] += b << lev
if x[n] and bits.read_bit():
x[n] = -x[n]
if x[n+1] and bits.read_bit():
x[n+1] = -x[n+1]
lev = min(lev, 3)
t = 1 + (a + b) * (lev + 1) if lev <= 1 else 12 + lev;
c = (c & 15) * 16 + t;
### Residual data
nbits_residual = bits.get_bits_left()
if nbits_residual < 0:
raise ValueError('Out of bitstream')
if self.lsb_mode == 0:
xr = np.zeros(len(x), dtype=np.bool)
for i in range(len(x)):
if nbits_residual <= 0:
xr.resize(i)
break
if x[i] == 0:
continue
xr[i] = bits.read_bit()
nbits_residual -= 1
else:
for i in range(len(levs)):
if nbits_residual <= 0:
break
if levs[i] <= 0:
continue
lsb = bits.read_bit()
nbits_residual -= 1
if not lsb:
continue
sign = int(x[i] < 0)
if x[i] == 0:
if nbits_residual <= 0:
break
sign = bits.read_bit()
nbits_residual -= 1
x[i] += [ 1, -1 ][sign]
### Set residual and noise
nf_seed = sum(abs(x.astype(np.int)) * range(len(x)))
zero_frame = (self.lastnz <= 2 and x[0] == 0 and x[1] == 0
and self.g_idx <= 0 and nf >= 7)
if self.lsb_mode == 0:
for i in range(len(xr)):
if x[i] and xr[i] == 0:
x[i] += [ -0.1875, -0.3125 ][x[i] < 0]
elif x[i]:
x[i] += [ 0.1875, 0.3125 ][x[i] > 0]
if not zero_frame:
x = self.fill_noise(bw, x, self.lastnz, f_nf, nf_seed)
### Rescale coefficients
g_int = self.get_gain_offset(nbytes) + self.g_idx
x *= 10 ** (g_int / 28)
return x
def initial_state():
return { 'nbits_off' : 0.0, 'nbits_spare' : 0 }
### ------------------------------------------------------------------------ ###
def check_estimate_gain(rng, dt, sr):
ne = T.I[dt][sr][-1]
ok = True
analysis = SpectrumAnalysis(dt, sr)
for i in range(10):
x = rng.random(ne) * i * 1e2
nbytes = 20 + int(rng.random() * 100)
nbits_budget = 8 * nbytes - int(rng.random() * 100)
nbits_off = rng.random() * 10
g_off = 10 - int(rng.random() * 20)
(g_int, reset_off) = \
analysis.estimate_gain(x, nbits_budget, nbits_off, g_off)
(g_int_c, reset_off_c) = lc3.spec_estimate_gain(
dt, sr, x, nbits_budget, nbits_off, -g_off)
ok = ok and g_int_c == g_int
ok = ok and reset_off_c == reset_off
return ok
def check_quantization(rng, dt, sr):
ne = T.I[dt][sr][-1]
ok = True
analysis = SpectrumAnalysis(dt, sr)
for g_int in range(-128, 128):
x = rng.random(ne) * 1e2
nbytes = 20 + int(rng.random() * 30)
(xg, xq, nq) = analysis.quantize(g_int, x)
(xg_c, xq_c, nq_c) = lc3.spec_quantize(dt, sr, g_int, x)
ok = ok and np.amax(np.abs(1 - xg_c/xg)) < 1e-6
ok = ok and np.any(abs(xq_c - xq) < 1)
ok = ok and nq_c == nq
return ok
def check_compute_nbits(rng, dt, sr):
ne = T.I[dt][sr][-1]
ok = True
analysis = SpectrumAnalysis(dt, sr)
for nbytes in range(20, 150):
nbits_budget = nbytes * 8 - int(rng.random() * 100)
xq = (rng.random(ne) * 8).astype(int)
nq = ne // 2 + int(rng.random() * ne // 2)
nq = nq - nq % 2
if xq[nq-2] == 0 and xq[nq-1] == 0:
xq[nq-2] = 1
(nbits, nbits_trunc, nq_trunc, lsb_mode) = \
analysis.compute_nbits(nbytes, xq, nq, nbits_budget)
(nbits_c, nq_c, _) = \
lc3.spec_compute_nbits(dt, sr, nbytes, xq, nq, 0)
(nbits_trunc_c, nq_trunc_c, lsb_mode_c) = \
lc3.spec_compute_nbits(dt, sr, nbytes, xq, nq, nbits_budget)
ok = ok and nbits_c == nbits
ok = ok and nbits_trunc_c == nbits_trunc
ok = ok and nq_trunc_c == nq_trunc
ok = ok and lsb_mode_c == lsb_mode
return ok
def check_adjust_gain(rng, dt, sr):
ne = T.I[dt][sr][-1]
ok = True
analysis = SpectrumAnalysis(dt, sr)
for g_idx in (0, 128, 254, 255):
for nbits in range(50, 5000, 5):
nbits_budget = int(nbits * (0.95 + (rng.random() * 0.1)))
g_adj = analysis.adjust_gain(g_idx, nbits, nbits_budget)
g_adj_c = lc3.spec_adjust_gain(sr, g_idx, nbits, nbits_budget)
ok = ok and g_adj_c == g_adj
return ok
def check_unit(rng, dt, sr):
ns = T.NS[dt][sr]
ne = T.I[dt][sr][-1]
ok = True
state_c = initial_state()
bwdet = m_bwdet.BandwidthDetector(dt, sr)
ltpf = m_ltpf.LtpfAnalysis(dt, sr)
tns = m_tns.TnsAnalysis(dt)
sns = m_sns.SnsAnalysis(dt, sr)
analysis = SpectrumAnalysis(dt, sr)
nbytes = 100
for i in range(10):
x = rng.random(ns) * 1e4
e = rng.random(min(len(x), 64)) * 1e10
bwdet.run(e)
pitch_present = ltpf.run(x)
tns.run(x[:ne], sr, False, nbytes)
sns.run(e, False, x)
(xq, nq, _) = analysis.run(sr, nbytes, bwdet.get_nbits(),
ltpf.get_nbits(), sns.get_nbits(), tns.get_nbits(), x[:ne])
(_, xq_c, side_c) = lc3.spec_analyze(
dt, sr, nbytes, pitch_present, tns.get_data(), state_c, x[:ne])
ok = ok and side_c['g_idx'] == analysis.g_idx
ok = ok and side_c['nq'] == nq
ok = ok and np.any(abs(xq_c - xq) < 1)
return ok
def check_noise(rng, dt, bw):
ne = T.NE[dt][bw]
ok = True
analysis = SpectrumAnalysis(dt, bw)
for i in range(10):
xq = ((rng.random(ne) - 0.5) * 10 ** (0.5)).astype(int)
nq = ne - int(rng.random() * 5)
x = rng.random(ne) * i * 1e-1
nf = analysis.estimate_noise(bw, xq, nq, x)
nf_c = lc3.spec_estimate_noise(dt, bw, xq, nq, x)
ok = ok and nf_c == nf
return ok
def check_appendix_c(dt):
sr = T.SRATE_16K
ne = T.NE[dt][sr]
ok = True
state_c = initial_state()
for i in range(len(C.X_F[dt])):
g_int = lc3.spec_estimate_gain(dt, sr, C.X_F[dt][i],
C.NBITS_SPEC[dt][i], C.NBITS_OFFSET[dt][i], -C.GG_OFF[dt][i])[0]
ok = ok and g_int == C.GG_IND[dt][i] + C.GG_OFF[dt][i]
(_, xq, nq) = lc3.spec_quantize(dt, sr,
C.GG_IND[dt][i] + C.GG_OFF[dt][i], C.X_F[dt][i])
ok = ok and np.any((xq - C.X_Q[dt][i]) == 0)
ok = ok and nq == C.LASTNZ[dt][i]
nbits = lc3.spec_compute_nbits(dt, sr,
C.NBYTES[dt], C.X_Q[dt][i], C.LASTNZ[dt][i], 0)[0]
ok = ok and nbits == C.NBITS_EST[dt][i]
g_adj = lc3.spec_adjust_gain(sr,
C.GG_IND[dt][i], C.NBITS_EST[dt][i], C.NBITS_SPEC[dt][i])
ok = ok and g_adj == C.GG_IND_ADJ[dt][i] - C.GG_IND[dt][i]
if C.GG_IND_ADJ[dt][i] != C.GG_IND[dt][i]:
(_, xq, nq) = lc3.spec_quantize(dt, sr,
C.GG_IND_ADJ[dt][i] + C.GG_OFF[dt][i], C.X_F[dt][i])
lastnz = C.LASTNZ_REQ[dt][i]
ok = ok and np.any(((xq - C.X_Q_REQ[dt][i])[:lastnz]) == 0)
tns_data = {
'nfilters' : C.NUM_TNS_FILTERS[dt][i],
'lpc_weighting' : [ True, True ],
'rc_order' : [ C.RC_ORDER[dt][i][0], 0 ],
'rc' : [ C.RC_I_1[dt][i] - 8, np.zeros(8, dtype = np.int) ]
}
(x, xq, side) = lc3.spec_analyze(dt, sr, C.NBYTES[dt],
C.PITCH_PRESENT[dt][i], tns_data, state_c, C.X_F[dt][i])
ok = ok and np.abs(state_c['nbits_off'] - C.NBITS_OFFSET[dt][i]) < 1e-5
if C.GG_IND_ADJ[dt][i] != C.GG_IND[dt][i]:
xq = C.X_Q_REQ[dt][i]
nq = C.LASTNZ_REQ[dt][i]
ok = ok and side['g_idx'] == C.GG_IND_ADJ[dt][i]
ok = ok and side['nq'] == nq
ok = ok and np.any(((xq[:nq] - xq[:nq])) == 0)
else:
xq = C.X_Q[dt][i]
nq = C.LASTNZ[dt][i]
ok = ok and side['g_idx'] == C.GG_IND[dt][i]
ok = ok and side['nq'] == nq
ok = ok and np.any((xq[:nq] - C.X_Q[dt][i][:nq]) == 0)
ok = ok and side['lsb_mode'] == C.LSB_MODE[dt][i]
gg = C.GG[dt][i] if C.GG_IND_ADJ[dt][i] == C.GG_IND[dt][i] \
else C.GG_ADJ[dt][i]
nf = lc3.spec_estimate_noise(dt, C.P_BW[dt][i],
xq, nq, C.X_F[dt][i] / gg)
ok = ok and nf == C.F_NF[dt][i]
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_estimate_gain(rng, dt, sr)
ok = ok and check_quantization(rng, dt, sr)
ok = ok and check_compute_nbits(rng, dt, sr)
ok = ok and check_adjust_gain(rng, dt, sr)
ok = ok and check_unit(rng, dt, sr)
ok = ok and check_noise(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+192
View File
@@ -0,0 +1,192 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include "lc3.h"
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <spec.c>
#include "ctypes.h"
static PyObject *estimate_gain_py(PyObject *m, PyObject *args)
{
PyObject *x_obj;
unsigned dt, sr;
float *x;
int nbits_budget;
float nbits_off;
int g_off;
bool reset_off;
if (!PyArg_ParseTuple(args, "IIOifi", &dt, &sr,
&x_obj, &nbits_budget, &nbits_off, &g_off))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ne = LC3_NE(dt, sr);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
int g_int = estimate_gain(dt, sr,
x, nbits_budget, nbits_off, g_off, &reset_off);
return Py_BuildValue("ii", g_int, reset_off);
}
static PyObject *adjust_gain_py(PyObject *m, PyObject *args)
{
unsigned sr;
int g_idx, nbits, nbits_budget;
if (!PyArg_ParseTuple(args, "Iiii", &sr, &g_idx, &nbits, &nbits_budget))
return NULL;
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
CTYPES_CHECK("g_idx", g_idx >= 0 && g_idx <= 255);
g_idx = adjust_gain(sr, g_idx, nbits, nbits_budget);
return Py_BuildValue("i", g_idx);
}
static PyObject *quantize_py(PyObject *m, PyObject *args)
{
PyObject *x_obj, *xq_obj;
unsigned dt, sr;
float *x;
int16_t *xq;
int g_int, nq;
if (!PyArg_ParseTuple(args, "IIiO", &dt, &sr, &g_int, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
CTYPES_CHECK("g_int", g_int >= -255 && g_int <= 255);
int ne = LC3_NE(dt, sr);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
xq_obj = new_1d_ptr(NPY_INT16, ne, &xq);
quantize(dt, sr, g_int, x, xq, &nq);
return Py_BuildValue("ONi", x_obj, xq_obj, nq);
}
static PyObject *compute_nbits_py(PyObject *m, PyObject *args)
{
PyObject *xq_obj;
unsigned dt, sr, nbytes;
int16_t *xq;
int nq, nbits_budget;
bool lsb_mode;
if (!PyArg_ParseTuple(args, "IIIOii", &dt, &sr,
&nbytes, &xq_obj, &nq, &nbits_budget))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ne = LC3_NE(dt, sr);
CTYPES_CHECK("xq", xq_obj = to_1d_ptr(xq_obj, NPY_INT16, ne, &xq));
int nbits = compute_nbits(
dt, sr, nbytes, xq, &nq, nbits_budget, &lsb_mode);
return Py_BuildValue("iii", nbits, nq, lsb_mode);
}
static PyObject *analyze_py(PyObject *m, PyObject *args)
{
PyObject *tns_obj, *spec_obj, *x_obj, *xq_obj;
struct lc3_tns_data tns = { 0 };
struct lc3_spec_analysis spec = { 0 };
struct lc3_spec_side side = { 0 };
unsigned dt, sr, nbytes;
int pitch;
float *x;
int16_t *xq;
if (!PyArg_ParseTuple(args, "IIIpOOO", &dt, &sr, &nbytes,
&pitch, &tns_obj, &spec_obj, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
int ne = LC3_NE(dt, sr);
CTYPES_CHECK(NULL, tns_obj = to_tns_data(tns_obj, &tns));
CTYPES_CHECK(NULL, spec_obj = to_spec_analysis(spec_obj, &spec));
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
xq_obj = new_1d_ptr(NPY_INT16, ne, &xq);
lc3_spec_analyze(dt, sr, nbytes, pitch, &tns, &spec, x, xq, &side);
from_spec_analysis(spec_obj, &spec);
return Py_BuildValue("ONN", x_obj, xq_obj, new_spec_side(&side));
}
static PyObject *estimate_noise_py(PyObject *m, PyObject *args)
{
PyObject *x_obj, *xq_obj;
unsigned dt, bw;
int16_t *xq;
float *x;
int nq;
if (!PyArg_ParseTuple(args, "IIOIO", &dt, &bw, &xq_obj, &nq, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("bw", (unsigned)bw < LC3_NUM_BANDWIDTH);
int ne = LC3_NE(dt, bw);
CTYPES_CHECK("xq", xq_obj = to_1d_ptr(xq_obj, NPY_INT16, ne, &xq));
CTYPES_CHECK("x" , x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x ));
int noise_factor = estimate_noise(dt, bw, xq, nq, x);
return Py_BuildValue("i", noise_factor);
}
static PyMethodDef methods[] = {
{ "spec_estimate_gain" , estimate_gain_py , METH_VARARGS },
{ "spec_adjust_gain" , adjust_gain_py , METH_VARARGS },
{ "spec_quantize" , quantize_py , METH_VARARGS },
{ "spec_compute_nbits" , compute_nbits_py , METH_VARARGS },
{ "spec_analyze" , analyze_py , METH_VARARGS },
{ "spec_estimate_noise", estimate_noise_py, METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_spec_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}
+2709
View File
File diff suppressed because it is too large Load Diff
+440
View File
@@ -0,0 +1,440 @@
#
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import build.lc3 as lc3
import tables as T, appendix_c as C
### ------------------------------------------------------------------------ ###
class Tns:
SUB_LIM_10M_NB = [ [ 12, 34, 57, 80 ] ]
SUB_LIM_10M_WB = [ [ 12, 61, 110, 160 ] ]
SUB_LIM_10M_SSWB = [ [ 12, 88, 164, 240 ] ]
SUB_LIM_10M_SWB = [ [ 12, 61, 110, 160 ], [ 160, 213, 266, 320 ] ]
SUB_LIM_10M_FB = [ [ 12, 74, 137, 200 ], [ 200, 266, 333, 400 ] ]
SUB_LIM_10M = [ SUB_LIM_10M_NB, SUB_LIM_10M_WB,
SUB_LIM_10M_SSWB, SUB_LIM_10M_SWB, SUB_LIM_10M_FB ]
SUB_LIM_7M5_NB = [ [ 9, 26, 43, 60 ] ]
SUB_LIM_7M5_WB = [ [ 9, 46, 83, 120 ] ]
SUB_LIM_7M5_SSWB = [ [ 9, 66, 123, 180 ] ]
SUB_LIM_7M5_SWB = [ [ 9, 46, 82, 120 ], [ 120, 159, 200, 240 ] ]
SUB_LIM_7M5_FB = [ [ 9, 56, 103, 150 ], [ 150, 200, 250, 300 ] ]
SUB_LIM_7M5 = [ SUB_LIM_7M5_NB, SUB_LIM_7M5_WB,
SUB_LIM_7M5_SSWB, SUB_LIM_7M5_SWB, SUB_LIM_7M5_FB ]
SUB_LIM = [ SUB_LIM_7M5, SUB_LIM_10M ]
FREQ_LIM_10M_NB = [ 12, 80 ]
FREQ_LIM_10M_WB = [ 12, 160 ]
FREQ_LIM_10M_SSWB = [ 12, 240 ]
FREQ_LIM_10M_SWB = [ 12, 160, 320 ]
FREQ_LIM_10M_FB = [ 12, 200, 400 ]
FREQ_LIM_10M = [ FREQ_LIM_10M_NB, FREQ_LIM_10M_WB,
FREQ_LIM_10M_SSWB, FREQ_LIM_10M_SWB, FREQ_LIM_10M_FB ]
FREQ_LIM_7M5_NB = [ 9, 60 ]
FREQ_LIM_7M5_WB = [ 9, 120 ]
FREQ_LIM_7M5_SSWB = [ 9, 180 ]
FREQ_LIM_7M5_SWB = [ 9, 120, 240 ]
FREQ_LIM_7M5_FB = [ 9, 150, 300 ]
FREQ_LIM_7M5 = [ FREQ_LIM_7M5_NB, FREQ_LIM_7M5_WB,
FREQ_LIM_7M5_SSWB, FREQ_LIM_7M5_SWB, FREQ_LIM_7M5_FB ]
FREQ_LIM = [ FREQ_LIM_7M5, FREQ_LIM_10M ]
def __init__(self, dt):
self.dt = dt
(self.nfilters, self.lpc_weighting, self.rc_order, self.rc) = \
(None, None, None, None)
def get_data(self):
return { 'nfilters' : self.nfilters,
'lpc_weighting' : self.lpc_weighting,
'rc_order' : self.rc_order, 'rc' : self.rc - 8 }
def get_nbits(self):
lpc_weighting = self.lpc_weighting
nbits = 0
for f in range(self.nfilters):
rc_order = self.rc_order[f]
rc = self.rc[f]
nbits_order = T.TNS_ORDER_BITS[int(lpc_weighting)][rc_order]
nbits_coef = sum([ T.TNS_COEF_BITS[k][rc[k]]
for k in range(rc_order) ])
nbits += ((2048 + nbits_order + nbits_coef) + 2047) >> 11
return nbits
class TnsAnalysis(Tns):
def __init__(self, dt):
super().__init__(dt)
def compute_lpc_coeffs(self, bw, f, x):
### Normalized autocorrelation function
S = Tns.SUB_LIM[self.dt][bw][f]
r = np.append([ 3 ], np.zeros(8))
e = [ sum(x[S[s]:S[s+1]] ** 2) for s in range(3) ]
for k in range(len(r) if sum(e) > 0 else 0):
c = [ np.dot(x[S[s]:S[s+1]-k], x[S[s]+k:S[s+1]])
for s in range(3) ]
r[k] = np.sum( np.array(c) / np.array(e) )
r *= np.exp(-0.5 * (0.02 * np.pi * np.arange(9)) ** 2)
### Levinson-Durbin recursion
err = r[0]
a = np.ones(len(r))
for k in range(1, len(a)):
rc = -sum(a[:k] * r[k:0:-1]) / err
a[1:k] += rc * a[k-1:0:-1]
a[k] = rc
err *= 1 - rc ** 2
return (r[0] / err, a)
def lpc_weighting(self, pred_gain, a):
gamma = 1 - (1 - 0.85) * (2 - pred_gain) / (2 - 1.5)
return a * np.power(gamma, np.arange(len(a)))
def coeffs_reflexion(self, a):
rc = np.zeros(8)
b = a.copy()
for k in range(8, 0, -1):
rc[k-1] = b[k]
e = 1 - rc[k-1] ** 2
b[1:k] = (b[1:k] - rc[k-1] * b[k-1:0:-1]) / e
return rc
def quantization(self, rc, lpc_weighting):
delta = np.pi / 17
rc_i = np.rint(np.arcsin(rc) / delta).astype(int) + 8
rc_q = np.sin(delta * (rc_i - 8))
rc_order = len(rc_i) - np.argmin(rc_i[::-1] == 8)
return (rc_order, rc_q, rc_i)
def filtering(self, st, x, rc_order, rc):
y = np.empty(len(x))
for i in range(len(x)):
xi = x[i]
s1 = xi
for k in range(rc_order):
s0 = st[k]
st[k] = s1
s1 = rc[k] * xi + s0
xi += rc[k] * s0
y[i] = xi
return y
def run(self, x, bw, nn_flag, nbytes):
fstate = np.zeros(8)
y = x.copy()
self.nfilters = len(Tns.SUB_LIM[self.dt][bw])
self.lpc_weighting = nbytes * 8 < 48 * T.DT_MS[self.dt]
self.rc_order = np.zeros(2, dtype=np.int)
self.rc = np.zeros((2, 8), dtype=np.int)
for f in range(self.nfilters):
(pred_gain, a) = self.compute_lpc_coeffs(bw, f, x)
tns_off = pred_gain <= 1.5 or nn_flag
if tns_off:
continue
if self.lpc_weighting and pred_gain < 2:
a = self.lpc_weighting(pred_gain, a)
rc = self.coeffs_reflexion(a)
(rc_order, rc_q, rc_i) = \
self.quantization(rc, self.lpc_weighting)
self.rc_order[f] = rc_order
self.rc[f] = rc_i
if rc_order > 0:
i0 = Tns.FREQ_LIM[self.dt][bw][f]
i1 = Tns.FREQ_LIM[self.dt][bw][f+1]
y[i0:i1] = self.filtering(
fstate, x[i0:i1], rc_order, rc_q)
return y
def store(self, b):
for f in range(self.nfilters):
lpc_weighting = self.lpc_weighting[f]
rc_order = self.rc_order[f]
rc = self.rc[f]
b.write_bit(min(rc_order, 1))
if rc_order > 0:
b.ac_encode(
T.TNS_ORDER_CUMFREQ[int(lpc_weighting)][rc_order-1],
T.TNS_ORDER_FREQ[int(lpc_weighting)][rc_order-1] )
for k in range(rc_order):
b.ac_encode(T.TNS_COEF_CUMFREQ[k][rc[k]],
T.TNS_COEF_FREQ[k][rc[k]] )
class TnsSynthesis(Tns):
def filtering(self, st, x, rc_order, rc):
y = x.copy()
for i in range(len(x)):
xi = x[i] - rc[rc_order-1] * st[rc_order-1]
for k in range(rc_order-2, -1, -1):
xi -= rc[k] * st[k]
st[k+1] = xi * rc[k] + st[k];
st[0] = xi;
y[i] = xi
return y
def load(self, b, bw, nbytes):
self.nfilters = len(Tns.SUB_LIM[self.dt][bw])
self.lpc_weighting = nbytes * 8 < 48 * T.DT_MS[self.dt]
self.rc_order = np.zeros(2, dtype=np.int)
self.rc = 8 * np.ones((2, 8), dtype=np.int)
for f in range(self.nfilters):
if not b.read_bit():
continue
rc_order = 1 + b.ac_decode(
T.TNS_ORDER_CUMFREQ[int(self.lpc_weighting)],
T.TNS_ORDER_FREQ[int(self.lpc_weighting)])
self.rc_order[f] = rc_order
for k in range(rc_order):
rc = b.ac_decode(T.TNS_COEF_CUMFREQ[k], T.TNS_COEF_FREQ[k])
self.rc[f][k] = rc
def run(self, x, bw):
fstate = np.zeros(8)
y = x.copy()
for f in range(self.nfilters):
rc_order = self.rc_order[f]
rc = np.sin((np.pi / 17) * (self.rc[f] - 8))
if rc_order > 0:
i0 = Tns.FREQ_LIM[self.dt][bw][f]
i1 = Tns.FREQ_LIM[self.dt][bw][f+1]
y[i0:i1] = self.filtering(
fstate, x[i0:i1], rc_order, rc)
return y
### ------------------------------------------------------------------------ ###
def check_analysis(rng, dt, bw):
ok = True
analysis = TnsAnalysis(dt)
nbytes_lim = int((48 * T.DT_MS[dt]) // 8)
for i in range(10):
x = rng.random(T.NE[dt][bw]) * 1e2
x = pow(x, .5 + i/5)
for nn_flag in (True, False):
for nbytes in (nbytes_lim, nbytes_lim + 1):
y = analysis.run(x, bw, nn_flag, nbytes)
(y_c, data_c) = lc3.tns_analyze(dt, bw, nn_flag, nbytes, x)
ok = ok and data_c['nfilters'] == analysis.nfilters
ok = ok and data_c['lpc_weighting'] == analysis.lpc_weighting
for f in range(analysis.nfilters):
rc_order = analysis.rc_order[f]
rc_order_c = data_c['rc_order'][f]
rc_c = 8 + data_c['rc'][f]
ok = ok and rc_order_c == rc_order
ok = ok and not np.any((rc_c - analysis.rc[f])[:rc_order])
ok = ok and lc3.tns_get_nbits(data_c) == analysis.get_nbits()
ok = ok and np.amax(np.abs(y_c - y)) < 1e-2
return ok
def check_synthesis(rng, dt, bw):
ok = True
synthesis = TnsSynthesis(dt)
for i in range(100):
x = rng.random(T.NE[dt][bw]) * 1e2
synthesis.nfilters = 1 + int(bw >= T.SRATE_32K)
synthesis.rc_order = rng.integers(0, 9, 2)
synthesis.rc = rng.integers(0, 17, 16).reshape(2, 8)
y = synthesis.run(x, bw)
y_c = lc3.tns_synthesize(dt, bw, synthesis.get_data(), x)
ok = ok and np.amax(np.abs(y_c - y) < 1e-6)
return ok
def check_analysis_appendix_c(dt):
sr = T.SRATE_16K
ok = True
fs = Tns.FREQ_LIM[dt][sr][0]
fe = Tns.FREQ_LIM[dt][sr][1]
st = np.zeros(8)
for i in range(len(C.X_S[dt])):
(_, a) = lc3.tns_compute_lpc_coeffs(dt, sr, C.X_S[dt][i])
ok = ok and np.amax(np.abs(a[0] - C.TNS_LEV_A[dt][i])) < 1e-5
rc = lc3.tns_lpc_reflection(a[0])
ok = ok and np.amax(np.abs(rc - C.TNS_LEV_RC[dt][i])) < 1e-5
(rc_order, rc_i) = lc3.tns_quantize_rc(C.TNS_LEV_RC[dt][i])
ok = ok and rc_order == C.RC_ORDER[dt][i][0]
ok = ok and np.any((rc_i + 8) - C.RC_I_1[dt][i] == 0)
rc_q = lc3.tns_unquantize_rc(rc_i, rc_order)
ok = ok and np.amax(np.abs(rc_q - C.RC_Q_1[dt][i])) < 1e-6
(x, side) = lc3.tns_analyze(dt, sr, False, C.NBYTES[dt], C.X_S[dt][i])
ok = ok and side['nfilters'] == 1
ok = ok and side['rc_order'][0] == C.RC_ORDER[dt][i][0]
ok = ok and not np.any((side['rc'][0] + 8) - C.RC_I_1[dt][i])
ok = ok and lc3.tns_get_nbits(side) == C.NBITS_TNS[dt][i]
ok = ok and np.amax(np.abs(x - C.X_F[dt][i])) < 1e-3
return ok
def check_synthesis_appendix_c(dt):
sr = T.SRATE_16K
ok = True
for i in range(len(C.X_HAT_Q[dt])):
side = {
'nfilters' : 1,
'lpc_weighting' : C.NBYTES[dt] * 8 < 48 * T.DT_MS[dt],
'rc_order': C.RC_ORDER[dt][i],
'rc': [ C.RC_I_1[dt][i] - 8, C.RC_I_2[dt][i] - 8 ]
}
g_int = C.GG_IND_ADJ[dt][i] + C.GG_OFF[dt][i]
x = C.X_HAT_Q[dt][i] * (10 ** (g_int / 28))
x = lc3.tns_synthesize(dt, sr, side, x)
ok = ok and np.amax(np.abs(x - C.X_HAT_TNS[dt][i])) < 1e-3
if dt != T.DT_10M:
return ok
sr = T.SRATE_48K
side = {
'nfilters' : 2,
'lpc_weighting' : False,
'rc_order': C.RC_ORDER_48K_10M,
'rc': [ C.RC_I_1_48K_10M - 8, C.RC_I_2_48K_10M - 8 ]
}
x = C.X_HAT_F_48K_10M
x = lc3.tns_synthesize(dt, sr, side, x)
ok = ok and np.amax(np.abs(x - C.X_HAT_TNS_48K_10M)) < 1e-3
return ok
def check():
rng = np.random.default_rng(1234)
ok = True
for dt in range(T.NUM_DT):
for sr in range(T.NUM_SRATE):
ok = ok and check_analysis(rng, dt, sr)
ok = ok and check_synthesis(rng, dt, sr)
for dt in range(T.NUM_DT):
ok = ok and check_analysis_appendix_c(dt)
ok = ok and check_synthesis_appendix_c(dt)
return ok
### ------------------------------------------------------------------------ ###
+183
View File
@@ -0,0 +1,183 @@
/******************************************************************************
*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#include "lc3.h"
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <tns.c>
#include "ctypes.h"
static PyObject *compute_lpc_coeffs_py(PyObject *m, PyObject *args)
{
PyObject *x_obj, *a_obj, *g_obj;
unsigned dt, bw;
float *x, *g, (*a)[9];
if (!PyArg_ParseTuple(args, "IIO", &dt, &bw, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("sr", (unsigned)bw < LC3_NUM_BANDWIDTH);
int ne = LC3_NE(dt, bw);
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
g_obj = new_1d_ptr(NPY_FLOAT, 2, &g);
a_obj = new_2d_ptr(NPY_FLOAT, 2, 9, &a);
compute_lpc_coeffs(dt, bw, x, g, a);
return Py_BuildValue("NN", g_obj, a_obj);
}
static PyObject *lpc_reflection_py(PyObject *m, PyObject *args)
{
PyObject *a_obj, *rc_obj;
float *a, *rc;
if (!PyArg_ParseTuple(args, "O", &a_obj))
return NULL;
CTYPES_CHECK("a", to_1d_ptr(a_obj, NPY_FLOAT, 9, &a));
rc_obj = new_1d_ptr(NPY_FLOAT, 8, &rc);
lpc_reflection(a, rc);
return Py_BuildValue("N", rc_obj);
}
static PyObject *quantize_rc_py(PyObject *m, PyObject *args)
{
PyObject *rc_obj, *rc_q_obj;
float *rc;
int rc_order, *rc_q;
if (!PyArg_ParseTuple(args, "O", &rc_obj))
return NULL;
CTYPES_CHECK("rc", to_1d_ptr(rc_obj, NPY_FLOAT, 8, &rc));
rc_q_obj = new_1d_ptr(NPY_INT, 8, &rc_q);
quantize_rc(rc, &rc_order, rc_q);
return Py_BuildValue("iN", rc_order, rc_q_obj);
}
static PyObject *unquantize_rc_py(PyObject *m, PyObject *args)
{
PyObject *rc_q_obj, *rc_obj;
int rc_order, *rc_q;
float *rc;
if (!PyArg_ParseTuple(args, "OI", &rc_q_obj, &rc_order))
return NULL;
CTYPES_CHECK("rc_q", to_1d_ptr(rc_q_obj, NPY_INT, 8, &rc_q));
CTYPES_CHECK("rc_order", (unsigned)rc_order <= 8);
rc_obj = new_1d_ptr(NPY_FLOAT, 8, &rc);
unquantize_rc(rc_q, rc_order, rc);
return Py_BuildValue("N", rc_obj);
}
static PyObject *analyze_py(PyObject *m, PyObject *args)
{
PyObject *x_obj;
struct lc3_tns_data data = { 0 };
unsigned dt, bw;
int nn_flag;
unsigned nbytes;
float *x;
if (!PyArg_ParseTuple(args, "IIpIO", &dt, &bw, &nn_flag, &nbytes, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("bw", (unsigned)bw < LC3_NUM_BANDWIDTH);
int ne = LC3_NE(dt, bw);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
lc3_tns_analyze(dt, bw, nn_flag, nbytes, &data, x);
return Py_BuildValue("ON", x_obj, new_tns_data(&data));
}
static PyObject *synthesize_py(PyObject *m, PyObject *args)
{
PyObject *data_obj, *x_obj;
unsigned dt, bw;
struct lc3_tns_data data;
float *x;
if (!PyArg_ParseTuple(args, "IIOO", &dt, &bw, &data_obj, &x_obj))
return NULL;
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
CTYPES_CHECK("bw", (unsigned)bw < LC3_NUM_BANDWIDTH);
CTYPES_CHECK(NULL, data_obj = to_tns_data(data_obj, &data));
int ne = LC3_NE(dt, bw);
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
lc3_tns_synthesize(dt, bw, &data, x);
return Py_BuildValue("O", x_obj);
}
static PyObject *get_nbits_py(PyObject *m, PyObject *args)
{
PyObject *data_obj;
struct lc3_tns_data data = { 0 };
if (!PyArg_ParseTuple(args, "O", &data_obj))
return NULL;
CTYPES_CHECK("data", to_tns_data(data_obj, &data));
int nbits = lc3_tns_get_nbits(&data);
return Py_BuildValue("i", nbits);
}
static PyMethodDef methods[] = {
{ "tns_compute_lpc_coeffs", compute_lpc_coeffs_py, METH_VARARGS },
{ "tns_lpc_reflection" , lpc_reflection_py , METH_VARARGS },
{ "tns_quantize_rc" , quantize_rc_py , METH_VARARGS },
{ "tns_unquantize_rc" , unquantize_rc_py , METH_VARARGS },
{ "tns_analyze" , analyze_py , METH_VARARGS },
{ "tns_synthesize" , synthesize_py , METH_VARARGS },
{ "tns_get_nbits" , get_nbits_py , METH_VARARGS },
{ NULL },
};
PyMODINIT_FUNC lc3_tns_py_init(PyObject *m)
{
import_array();
PyModule_AddFunctions(m, methods);
return m;
}