Source code for muda.deformers.sox

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# CREATED:2015-03-25 08:13:01 by Brian McFee <brian.mcfee@nyu.edu>
'''Sox-based deformations'''

import os
import six
import subprocess
import tempfile
import librosa
import json
import soundfile as psf
from pkg_resources import resource_filename

from ..base import BaseTransformer

__all__ = ['DynamicRangeCompression', 'PRESETS']

# Presets pulled from
# https://web.archive.org/web/20140828122713/http://forum.doom9.org/showthread.php?t=165807
# and http://forum.doom9.org/showthread.php?p=779165#post779165
PRESETS = json.load(open(resource_filename(__name__, 'data/drc_presets.json')))


def __sox(y, sr, *args):
    '''Execute sox

    Parameters
    ----------
    y : np.ndarray
        Audio time series

    sr : int > 0
        Sampling rate of `y`

    *args
        Additional arguments to sox

    Returns
    -------
    y_out : np.ndarray
        `y` after sox transformation
    '''

    assert sr > 0

    fdesc, infile = tempfile.mkstemp(suffix='.wav')
    os.close(fdesc)
    fdesc, outfile = tempfile.mkstemp(suffix='.wav')
    os.close(fdesc)

    # Dump the audio
    librosa.output.write_wav(infile, y, sr)

    try:
        arguments = ['sox', infile, outfile, '-q']
        arguments.extend(args)

        subprocess.check_call(arguments)

        y_out, sr = psf.read(outfile)
        y_out = y_out.T
        if y.ndim == 1:
            y_out = librosa.to_mono(y_out)

    finally:
        os.unlink(infile)
        os.unlink(outfile)

    return y_out


def drc(y, sr, preset):
    '''Apply a preset DRC

    Parameters
    ----------
    y : np.ndarray
        Audio time series

    sr : int > 0
        Sampling rate of `y`

    preset : str
        Preset keyword (see PRESETS)

    Returns
    -------
    y_out : np.ndarray
        `y` after applying preset DRC
    '''

    return __sox(y, sr, 'compand', *PRESETS[preset])


[docs]class DynamicRangeCompression(BaseTransformer): '''Dynamic range compression. For each DRC preset configuration, one deformation is generated. This transformation affects the following attributes: - Audio Attributes ---------- preset : str or list of str One or more supported preset values: - radio - film standard - film light - music standard - music light - speech Examples -------- >>> # A single preset >>> drc = muda.deformers.DynamicRangeCompression(preset='radio') >>> # Multiple presets >>> drc = muda.deformers.DynamicRangeCompression(preset=['film standard', ... 'film light']) >>> # All presets >>> drc = muda.deformers.DynamicRangeCompression(preset=muda.deformers.PRESETS.keys()) ''' def __init__(self, preset=None): BaseTransformer.__init__(self) if isinstance(preset, six.string_types): preset = [preset] for p in preset: assert p in PRESETS self.preset = preset def states(self, jam): for p in self.preset: yield dict(preset=p) @staticmethod def audio(mudabox, state): mudabox._audio['y'] = drc(mudabox._audio['y'], mudabox._audio['sr'], state['preset'])