Skip to content

Commit 224b204

Browse files
committed
Handling clipping
1 parent 28832de commit 224b204

4 files changed

Lines changed: 91 additions & 29 deletions

File tree

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ Or:
7373
for i in range(nb_frames):
7474
paked_input = faded.readframes(1)
7575
input_g, input_d = struct.unpack('<hh', paked_input)
76-
output_g = downsample_l.transfert(input_g/256) + 128
77-
output_d = downsample_r.transfert(input_d/256) + 128
76+
output_g = downsample_l.transfer(input_g/256) + 128
77+
output_d = downsample_r.transfer(input_d/256) + 128
7878
print(input_g, ':', output_g, '|', input_d, ':', output_d)
7979
packed_output_g = struct.pack('B', output_g)
8080
packed_output_d = struct.pack('B', output_d)
@@ -86,8 +86,8 @@ OR:
8686
for i in range(nb_frames):
8787
paked_input = faded.readframes(1)
8888
input_g, input_d = struct.unpack('<hh', paked_input)
89-
output_g = downsample_l.transfert(input_g/256) + 128
90-
output_d = downsample_r.transfert(input_d/256) + 128
89+
output_g = downsample_l.transfer(input_g/256) + 128
90+
output_d = downsample_r.transfer(input_d/256) + 128
9191
print(input_g, ':', output_g, '|', input_d, ':', output_d)
9292
packed_output = struct.pack('<BB', output_g, output_d)
9393
downsample.writeframesraw(packed_output)
@@ -98,8 +98,8 @@ OR:
9898
for i in range(nb_frames):
9999
paked_input = faded.readframes(1)
100100
input_g, input_d = struct.unpack('<hh', paked_input)
101-
output_g = downsample_l.transfert(input_g/256) + 128
102-
output_d = downsample_r.transfert(input_d/256) + 128
101+
output_g = downsample_l.transfer(input_g/256) + 128
102+
output_d = downsample_r.transfer(input_d/256) + 128
103103
print(input_g, ':', output_g, '|', input_d, ':', output_d)
104104
packed_output = struct.pack('<BB', output_g, output_d)
105105
downsample.writeframes(packed_output)

sigprocessutils/lib/integration.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import division
2+
import logging
23

4+
LOG = logging.getLogger(__name__)
35

46
class Integrator(object):
57

@@ -38,8 +40,10 @@ class DownSamplingLSBIntegration(object):
3840
rounded_offset = 0
3941
coef = 1
4042
output = None
43+
min = None
44+
max = None
4145

42-
def __init__(self, integrator=None, offset=0, coef=1):
46+
def __init__(self, integrator=None, offset=0, coef=1, min_value=None, max_value=None):
4347
self.offset = offset
4448
self.rounded_offset = int(round(self.offset))
4549
self.coef = coef
@@ -48,11 +52,20 @@ def __init__(self, integrator=None, offset=0, coef=1):
4852
self.integrator = integrator
4953
else:
5054
self.integrator = Integrator(-self.offset, self.offset)
55+
self.set_min_max(min_value, max_value)
5156

52-
def transfert(self, value):
53-
v1 = (value * self.coef) - self.output
54-
v2 = self.integrator.integrate(v1)
55-
self.output = int(round(v2))
57+
def transfer(self, value):
58+
self.output = int(round(
59+
self.integrator.integrate(
60+
(value * self.coef) - self.output
61+
)
62+
))
63+
if self.min is not None and self.min > self.output:
64+
LOG.warning("Low Clipping during integration : %s", self.output)
65+
self.output = self.min
66+
elif self.max is not None and self.max < self.output:
67+
LOG.warning("High Clipping during integration : %s", self.output)
68+
self.output = self.max
5669
return self.output
5770

5871
def reset(self, offset=None, coef=None):
@@ -63,3 +76,7 @@ def reset(self, offset=None, coef=None):
6376
self.coef = coef
6477
self.output = self.rounded_offset
6578
self.integrator.reset(-self.offset, self.offset)
79+
80+
def set_min_max(self, min_value=None, max_value=None):
81+
self.min = min_value
82+
self.max = max_value

sigprocessutils/lib/wavelib.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
from functools import cached_property
2+
import logging
13
import wave
24
from struct import pack as struct_pack
35
from struct import unpack as struct_unpack
46

7+
8+
LOG = logging.getLogger(__name__)
9+
10+
511
# Bit size struct mapping:
612
# https://docs.python.org/3.6/library/struct.html#format-characters
713
# https://stackoverflow.com/questions/3783677/how-to-read-integers-from-a-file-that-are-24bit-and-little-endian-using-python
@@ -34,6 +40,13 @@
3440
4: 32,
3541
}
3642

43+
WIDTH_MIN_MAX = {
44+
1: (0, 255),
45+
2: (-32768, 32767),
46+
3: (-8388608, 8388607),
47+
4: (-2147483648, 2147483647),
48+
}
49+
3750

3851
class WaveMixin(object):
3952
_filename = None
@@ -48,42 +61,50 @@ def close(self):
4861
def tell(self):
4962
return self._wfp.tell()
5063

51-
@property
64+
@cached_property
5265
def struct_fmt(self):
5366
return '<%d%s' % (self.nchannels, SAMPLE_WIDTHS[self.sampwidth])
5467

5568
@property
5669
def wfp(self):
5770
return self._wfp
5871

59-
@property
72+
@cached_property
6073
def nchannels(self):
6174
return self._wfp.getnchannels()
6275

63-
@property
76+
@cached_property
6477
def sampwidth(self):
6578
return self._wfp.getsampwidth()
6679

67-
@property
80+
@cached_property
6881
def framerate(self):
6982
return self._wfp.getframerate()
7083

71-
@property
84+
@cached_property
7285
def nframes(self):
7386
return self._wfp.getnframes()
7487

75-
@property
88+
@cached_property
7689
def comptype(self):
7790
return self._wfp.getcomptype()
7891

79-
@property
92+
@cached_property
8093
def compname(self):
8194
return self._wfp.getcompname()
8295

83-
@property
96+
@cached_property
8497
def params(self):
8598
return self._wfp.getparams()
8699

100+
@cached_property
101+
def min_value(self):
102+
return WIDTH_MIN_MAX[self.sampwidth][0]
103+
104+
@cached_property
105+
def max_value(self):
106+
return WIDTH_MIN_MAX[self.sampwidth][1]
107+
87108

88109
class WaveRead(WaveMixin):
89110

@@ -153,8 +174,23 @@ def setcomptype(self, comptype, compname):
153174
def setparams(self, params):
154175
self._wfp.setparams(params)
155176

177+
def constrain_value(self, value):
178+
if self.min_value <= value <= self.max_value:
179+
return value
180+
elif value < self.min_value:
181+
LOG.warning("Low Clipping : %s", value)
182+
return self.min_value
183+
elif value > self.max_value:
184+
LOG.warning("High Clipping : %s", value)
185+
return self.max_value
186+
187+
def constrain_values(self, values):
188+
return tuple(
189+
self.constrain_value(value) for value in values
190+
)
191+
156192
def pack_data(self, *args):
157-
return struct_pack(self.struct_fmt, *args)
193+
return struct_pack(self.struct_fmt, *self.constrain_values(args))
158194

159195
def writeframesraw(self, data):
160196
self._wfp.writeframesraw(data)

sigprocessutils/scripts/audio_downsample.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from ..conf.logconf import configure_logging
99
from ..lib.integration import DownSamplingLSBIntegration, Integrator
10-
from ..lib.wavelib import WaveRead, WaveWrite, BITS_WIDTHS, WIDTH_BITS
10+
from ..lib.wavelib import WaveRead, WaveWrite, BITS_WIDTHS, WIDTH_BITS, WIDTH_MIN_MAX
1111

1212

1313
def create_argument_parser(parser=None):
@@ -28,6 +28,10 @@ def create_argument_parser(parser=None):
2828
parser.add_argument(
2929
"-o", "--output", required=True
3030
)
31+
parser.add_argument(
32+
"-c", "--clip", action="store_true",
33+
help="Clip during integration, not only during output"
34+
)
3135
return parser
3236

3337

@@ -43,26 +47,31 @@ def main():
4347
infile = WaveRead(options.input)
4448

4549
input_bits = WIDTH_BITS[infile.sampwidth]
46-
logger.debug('Input sample width = %s bits', input_bits)
47-
logger.debug('Output sample width = %s bits', options.bits)
50+
logger.info('Input sample width = %s bits', input_bits)
51+
logger.info('Output sample width = %s bits', options.bits)
4852
input_offset = 0
4953
if input_bits == 8:
5054
input_offset = 128
51-
logger.debug('Input offset = %s', input_offset)
55+
logger.info('Input offset = %s', input_offset)
5256

5357
outfile = WaveWrite(options.output, infile.params)
5458
outfile.setsampwidth(BITS_WIDTHS[options.bits])
5559

5660
output_offset = 0
5761
if options.bits == 8:
5862
output_offset = 128
59-
logger.debug('Output offset = %s', output_offset)
60-
divider = math.pow(2, input_bits) / math.pow(2, options.bits)
61-
logger.debug('Divider = %s', divider)
63+
logger.info('Output offset = %s', output_offset)
64+
divider = (math.pow(2, input_bits)/2 - 1) / (math.pow(2, options.bits)/2 - 1)
65+
if options.clip:
66+
min_value, max_value = WIDTH_MIN_MAX[outfile.sampwidth]
67+
logger.info('Clipping values = %s, %s', min_value, max_value)
68+
else:
69+
min_value = max_value = None
70+
logger.info('Divider = %s', divider)
6271
try:
6372

6473
downsample_chans = [
65-
DownSamplingLSBIntegration(Integrator())
74+
DownSamplingLSBIntegration(Integrator(), min_value=min_value, max_value=max_value)
6675
for _ in six.moves.xrange(infile.nchannels)
6776
]
6877

@@ -71,7 +80,7 @@ def main():
7180
for frame_nb in six.moves.xrange(total_frames): # noqa pylint: disable=W0612
7281
inputs = infile.read_unpacked_frames(1)
7382
outputs = tuple(
74-
integrator.transfert(
83+
integrator.transfer(
7584
(inp - input_offset) / divider
7685
) + output_offset
7786
for integrator, inp in zip(downsample_chans, inputs)

0 commit comments

Comments
 (0)