Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ Or:
for i in range(nb_frames):
paked_input = faded.readframes(1)
input_g, input_d = struct.unpack('<hh', paked_input)
output_g = downsample_l.transfert(input_g/256) + 128
output_d = downsample_r.transfert(input_d/256) + 128
output_g = downsample_l.transfer(input_g/256) + 128
output_d = downsample_r.transfer(input_d/256) + 128
print(input_g, ':', output_g, '|', input_d, ':', output_d)
packed_output_g = struct.pack('B', output_g)
packed_output_d = struct.pack('B', output_d)
Expand All @@ -86,8 +86,8 @@ OR:
for i in range(nb_frames):
paked_input = faded.readframes(1)
input_g, input_d = struct.unpack('<hh', paked_input)
output_g = downsample_l.transfert(input_g/256) + 128
output_d = downsample_r.transfert(input_d/256) + 128
output_g = downsample_l.transfer(input_g/256) + 128
output_d = downsample_r.transfer(input_d/256) + 128
print(input_g, ':', output_g, '|', input_d, ':', output_d)
packed_output = struct.pack('<BB', output_g, output_d)
downsample.writeframesraw(packed_output)
Expand All @@ -98,8 +98,8 @@ OR:
for i in range(nb_frames):
paked_input = faded.readframes(1)
input_g, input_d = struct.unpack('<hh', paked_input)
output_g = downsample_l.transfert(input_g/256) + 128
output_d = downsample_r.transfert(input_d/256) + 128
output_g = downsample_l.transfer(input_g/256) + 128
output_d = downsample_r.transfer(input_d/256) + 128
print(input_g, ':', output_g, '|', input_d, ':', output_d)
packed_output = struct.pack('<BB', output_g, output_d)
downsample.writeframes(packed_output)
Expand Down
27 changes: 22 additions & 5 deletions sigprocessutils/lib/integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import division
import logging

LOG = logging.getLogger(__name__)

class Integrator(object):

Expand Down Expand Up @@ -38,8 +40,10 @@ class DownSamplingLSBIntegration(object):
rounded_offset = 0
coef = 1
output = None
min = None
max = None

def __init__(self, integrator=None, offset=0, coef=1):
def __init__(self, integrator=None, offset=0, coef=1, min_value=None, max_value=None):
self.offset = offset
self.rounded_offset = int(round(self.offset))
self.coef = coef
Expand All @@ -48,11 +52,20 @@ def __init__(self, integrator=None, offset=0, coef=1):
self.integrator = integrator
else:
self.integrator = Integrator(-self.offset, self.offset)
self.set_min_max(min_value, max_value)

def transfert(self, value):
v1 = (value * self.coef) - self.output
v2 = self.integrator.integrate(v1)
self.output = int(round(v2))
def transfer(self, value):
self.output = int(round(
self.integrator.integrate(
(value * self.coef) - self.output
)
))
if self.min is not None and self.min > self.output:
LOG.warning("Low Clipping during integration : %s", self.output)
self.output = self.min
elif self.max is not None and self.max < self.output:
LOG.warning("High Clipping during integration : %s", self.output)
self.output = self.max
return self.output

def reset(self, offset=None, coef=None):
Expand All @@ -63,3 +76,7 @@ def reset(self, offset=None, coef=None):
self.coef = coef
self.output = self.rounded_offset
self.integrator.reset(-self.offset, self.offset)

def set_min_max(self, min_value=None, max_value=None):
self.min = min_value
self.max = max_value
54 changes: 45 additions & 9 deletions sigprocessutils/lib/wavelib.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from functools import cached_property
import logging
import wave
from struct import pack as struct_pack
from struct import unpack as struct_unpack


LOG = logging.getLogger(__name__)


# Bit size struct mapping:
# https://docs.python.org/3.6/library/struct.html#format-characters
# https://stackoverflow.com/questions/3783677/how-to-read-integers-from-a-file-that-are-24bit-and-little-endian-using-python
Expand Down Expand Up @@ -34,6 +40,13 @@
4: 32,
}

WIDTH_MIN_MAX = {
1: (0, 255),
2: (-32768, 32767),
3: (-8388608, 8388607),
4: (-2147483648, 2147483647),
}


class WaveMixin(object):
_filename = None
Expand All @@ -48,42 +61,50 @@ def close(self):
def tell(self):
return self._wfp.tell()

@property
@cached_property
def struct_fmt(self):
return '<%d%s' % (self.nchannels, SAMPLE_WIDTHS[self.sampwidth])

@property
def wfp(self):
return self._wfp

@property
@cached_property
def nchannels(self):
return self._wfp.getnchannels()

@property
@cached_property
def sampwidth(self):
return self._wfp.getsampwidth()

@property
@cached_property
def framerate(self):
return self._wfp.getframerate()

@property
@cached_property
def nframes(self):
return self._wfp.getnframes()

@property
@cached_property
def comptype(self):
return self._wfp.getcomptype()

@property
@cached_property
def compname(self):
return self._wfp.getcompname()

@property
@cached_property
def params(self):
return self._wfp.getparams()

@cached_property
def min_value(self):
return WIDTH_MIN_MAX[self.sampwidth][0]

@cached_property
def max_value(self):
return WIDTH_MIN_MAX[self.sampwidth][1]


class WaveRead(WaveMixin):

Expand Down Expand Up @@ -153,8 +174,23 @@ def setcomptype(self, comptype, compname):
def setparams(self, params):
self._wfp.setparams(params)

def constrain_value(self, value):
if self.min_value <= value <= self.max_value:
return value
elif value < self.min_value:
LOG.warning("Low Clipping : %s", value)
return self.min_value
elif value > self.max_value:
LOG.warning("High Clipping : %s", value)
return self.max_value

def constrain_values(self, values):
return tuple(
self.constrain_value(value) for value in values
)

def pack_data(self, *args):
return struct_pack(self.struct_fmt, *args)
return struct_pack(self.struct_fmt, *self.constrain_values(args))

def writeframesraw(self, data):
self._wfp.writeframesraw(data)
Expand Down
40 changes: 31 additions & 9 deletions sigprocessutils/scripts/audio_downsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ..conf.logconf import configure_logging
from ..lib.integration import DownSamplingLSBIntegration, Integrator
from ..lib.wavelib import WaveRead, WaveWrite, BITS_WIDTHS, WIDTH_BITS
from ..lib.wavelib import WaveRead, WaveWrite, BITS_WIDTHS, WIDTH_BITS, WIDTH_MIN_MAX


def create_argument_parser(parser=None):
Expand All @@ -28,6 +28,15 @@ def create_argument_parser(parser=None):
parser.add_argument(
"-o", "--output", required=True
)
parser.add_argument(
"-c", "--clip", action="store_true",
help="Clip during integration, not only during output"
)
parser.add_argument(
"--no-reduce", action="store_true",
help="Do not reduce of 1 LSB Amplitude. Since Integration produces 1 LSB amplitude noise, we reduce the level "
"of 1 LSB to avoid clipping by default. Set this parameter if you do not want this reduction"
)
return parser


Expand All @@ -43,26 +52,39 @@ def main():
infile = WaveRead(options.input)

input_bits = WIDTH_BITS[infile.sampwidth]
logger.debug('Input sample width = %s bits', input_bits)
logger.debug('Output sample width = %s bits', options.bits)
logger.info('Input sample width = %s bits', input_bits)
logger.info('Output sample width = %s bits', options.bits)
input_offset = 0
if input_bits == 8:
input_offset = 128
logger.debug('Input offset = %s', input_offset)
logger.info('Input offset = %s', input_offset)

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

output_offset = 0
if options.bits == 8:
output_offset = 128
logger.debug('Output offset = %s', output_offset)
divider = math.pow(2, input_bits) / math.pow(2, options.bits)
logger.debug('Divider = %s', divider)
logger.info('Output offset = %s', output_offset)
numerator = math.pow(2, input_bits)/2 - 1
logger.info('Input MAX theoretical level = %s', numerator)
denominator = (math.pow(2, options.bits) / 2) - 1
logger.info('Output MAX theoretical level = %s', denominator)
if not options.no_reduce:
denominator = denominator - 1
logger.info('Reducing Output MAX level to %s to prevent clipping. Use --no-reduce if you do not want this 1 '
'LSB amplitude reduction.', denominator)
divider = numerator / denominator
if options.clip:
min_value, max_value = WIDTH_MIN_MAX[outfile.sampwidth]
logger.info('Clipping values = %s, %s', min_value, max_value)
else:
min_value = max_value = None
logger.info('Divider = %s', divider)
try:

downsample_chans = [
DownSamplingLSBIntegration(Integrator())
DownSamplingLSBIntegration(Integrator(), min_value=min_value, max_value=max_value)
for _ in six.moves.xrange(infile.nchannels)
]

Expand All @@ -71,7 +93,7 @@ def main():
for frame_nb in six.moves.xrange(total_frames): # noqa pylint: disable=W0612
inputs = infile.read_unpacked_frames(1)
outputs = tuple(
integrator.transfert(
integrator.transfer(
(inp - input_offset) / divider
) + output_offset
for integrator, inp in zip(downsample_chans, inputs)
Expand Down