soxbindings 1.2.3
Python bindings for sox.

SoxBindings
==============

[![Downloads](https://pepy.tech/badge/soxbindings)](https://pepy.tech/project/soxbindings)
![Tests](https://github.com/pseeth/soxbindings/workflows/Run%20tests/badge.svg)
![Wheels built](https://github.com/pseeth/soxbindings/workflows/Build%20wheels/badge.svg)

Python bindings for SoX. An attempt to bind a subset of the capabilities of
the SoX command line utility but in Python via bindings for speed. This 
avoids costly exec calls when using augmentations in SoX. This is a
work in progress! Help welcome.

soxbindings only supports Unix systems (Linux and OSX), due to how
one builds sox. A related library (torchaudio) has similar problems:
https://github.com/pytorch/audio/issues/425.

Install from pip
----------------

If on MacOS or Linux, just do:

`pip install soxbindings`

If on Windows, it's not supported but you *could* install sox from source, 
and then link libsox and get everything working possibly. If you do and figure
out an automated way to do it using `cibuildwheel`, please put in a PR adding
Windows support!

Installation from source
------------------------

**On Unix (Linux, OS X) using Anaconda**

 - clone this repository
 - Make a conda environment
 - `conda install -c conda-forge sox`
 - If on Linux: 
    - Option 1: `conda install gcc_linux-64 gxx_linux-64`
    - Option 2: `sudo apt-get install sox libsox-dev`
    - Option 3: build and install sox from source (e.g. as in `.github/workflows/build_install_sox_centos.sh`).
 - `pip install -e .`

Run the tests to make sure everything works:

```bash
pip install -r extra_requirements.txt
python -m pytest .
```

The tests run a large variety of commands, all pulled from the pysox test 
cases. SoxBindings output is then compared with pysox output.

Usage
------

SoxBindings is built to be a drop-in replacement for the sox command
line tool, avoiding a costly exec call. Specifically, the way it works
is to provide an alternative backend to the excellent library that wraps
the command line tool [pysox](https://github.com/rabitt/pysox). SoxBindings
simply re-implements the `build` function in pysox `Transformer` classes. 

Note that `Combiner` classes in pysox are NOT supported.

If you have a script that works with pysox, like so:

```python
import sox
# create transformer
tfm = sox.Transformer()
# trim the audio between 5 and 10.5 seconds.
tfm.trim(5, 10.5)
# apply compression
tfm.compand()
# apply a fade in and fade out
tfm.fade(fade_in_len=1.0, fade_out_len=0.5)
# create an output file.
tfm.build_file('path/to/input_audio.wav', 'path/to/output/audio.aiff')
# or equivalently using the legacy API
tfm.build('path/to/input_audio.wav', 'path/to/output/audio.aiff')
# get the output in-memory as a numpy array
# by default the sample rate will be the same as the input file
array_out = tfm.build_array(input_filepath='path/to/input_audio.wav')
# see the applied effects
tfm.effects_log
> ['trim', 'compand', 'fade']
```

Then, all you have to do is change the import:

```python
import soxbindings as sox
```

and everything should work, but be faster because of the direct bindings
to libsox!

Multithreading
--------------

SoxBindings requires some special care when being used in a multi-threaded program
(i.e. a TensorFlow data loader). This [issue](https://github.com/pseeth/soxbindings/issues/4) 
has more discussion. To use SoxBindings in a multi-threaded program, you must use
the SoxBindings context manager: `soxbindings.sox_context`.

Note below that anything related to SoxBindings is called in the context
block `with sox.sox_context():`.


```python
import numpy as np
import soxbindings as sox

y1 = np.zeros((4000, 1))
y2 = np.zeros((3000, 1))

def do_transform(y):
   tfm = sox.Transformer()
   tfm.vol(0.5)
   y_out = tfm.build_array(input_array=y, sample_rate_in=1000)
   return y_out

# multithread
pool = ThreadPool(2)
with sox.sox_context():
   multi_thread = pool.map(do_transform, [y1, y2]) 
   for a1, a2 in zip(single_thread, multi_thread):
      assert np.allclose(a1, a2)
```

The other option is to wrap your program into a function, and then decorate
the function:

```python
import numpy as np
import soxbindings as sox

@sox.sox_context()
def run():
   y1 = np.zeros((4000, 1))
   y2 = np.zeros((3000, 1))

   def do_transform(y):
      tfm = sox.Transformer()
      tfm.vol(0.5)
      y_out = tfm.build_array(input_array=y, sample_rate_in=1000)
      return y_out

   # multithread
   pool = ThreadPool(2)
   multi_thread = pool.map(do_transform, [y1, y2]) 
   for a1, a2 in zip(single_thread, multi_thread):
      assert np.allclose(a1, a2)
```

If your program is single-threaded, no changes are needed. SoxBindings
checks to see if SoX has been initialized already before initializing
again.

Deploying to PyPI
-----------------

The Github action workflow "Build wheels" gets run every time there is a commit
to master. When it's done, the wheels for OSX and Linux are created and place in
an artifact. For example:

https://github.com/pseeth/soxbindings/actions/runs/169544837

Download the artifact zip, then do the following steps from the root of the
soxbindings repo:

```bash
unzip [/path/to/artifact.zip]
# clear out dist
rm -rf dist/
# create source distribution
python setup.py sdist
cp -r [/path/to/artifact]/* dist/
```

The dist folder should look something like:

```
dist
├── soxbindings-0.0.1-cp35-cp35m-macosx_10_9_x86_64.whl
├── soxbindings-0.0.1-cp35-cp35m-manylinux2010_i686.whl
├── soxbindings-0.0.1-cp35-cp35m-manylinux2010_x86_64.whl
├── soxbindings-0.0.1-cp36-cp36m-macosx_10_9_x86_64.whl
├── soxbindings-0.0.1-cp36-cp36m-manylinux2010_i686.whl
├── soxbindings-0.0.1-cp36-cp36m-manylinux2010_x86_64.whl
├── soxbindings-0.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
├── soxbindings-0.0.1-cp37-cp37m-manylinux2010_i686.whl
├── soxbindings-0.0.1-cp37-cp37m-manylinux2010_x86_64.whl
├── soxbindings-0.0.1-cp38-cp38-macosx_10_9_x86_64.whl
├── soxbindings-0.0.1-cp38-cp38-manylinux2010_i686.whl
├── soxbindings-0.0.1-cp38-cp38-manylinux2010_x86_64.whl
├── soxbindings-0.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl
├── soxbindings-0.0.1-pp27-pypy_73-manylinux2010_x86_64.whl
├── soxbindings-0.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl
├── soxbindings-0.0.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl
└── soxbindings-0.0.1.tar.gz
```

Upload it to the test server first (requires a version bump):

```
twine upload --repository testpypi dist/*
```

Make sure you can pip install it on both Linux and OSX:

```
pip install -U --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple -U soxbindings
```

Use the demo script included in this repo to try it out. 
Finally, upload it to the regular PyPi server:

```
twine upload dist/*
```


License
-------

soxbindings is under an MIT license.