Skip to content

Commit

Permalink
Add distortion effect. (#2)
Browse files Browse the repository at this point in the history
* Add distortion effect.

* Bump version.

* Add tests for distortion.

* Reduce memory usage in tests to avoid failures.

* Update pedalboard/plugins/Distortion.h

Co-authored-by: David Rubinstein <[email protected]>

* Update tests/test_native_module.py

Co-authored-by: David Rubinstein <[email protected]>

* Add more tests for distortion.

Co-authored-by: David Rubinstein <[email protected]>
  • Loading branch information
psobot and drubinstein authored Aug 2, 2021
1 parent 8f0fa90 commit 009d7f3
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 6 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
- `Convolution`
- `Compressor`
- `Chorus`
- `Distortion`
- `Gain`
- `HighpassFilter`
- `Reverb`
- `LadderFilter`
- `Limiter`
- `LowpassFilter`
- `Phaser`
- Supports VST3 plugins on macOS, Windows, and Linux
- `Reverb`
- Supports VST3® plugins on macOS, Windows, and Linux
- Supports Audio Units on macOS
- Strong thread-safety, memory usage, and speed guarantees
- Releases Python's Global Interpreter Lock (GIL) to allow use of multiple CPU cores
Expand Down Expand Up @@ -158,3 +159,5 @@ Contributions to `pedalboard` are welcomed! See [CONTRIBUTING.md](https://github

`pedalboard`'s logo contains artwork called
["guitar pedals" by Jino from the Noun Project](https://thenounproject.com/term/guitar-pedals/3605562), and the wordmark uses modified glyphs from [Victor Mono](https://github.com/rubjo/victor-mono).

_VST is a registered trademark of Steinberg Media Technologies GmbH._
71 changes: 71 additions & 0 deletions pedalboard/plugins/Distortion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* pedalboard
* Copyright 2021 Spotify AB
*
* Licensed under the GNU Public License, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/gpl-3.0.html
*
* 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 <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

#include "../JucePlugin.h"

namespace Pedalboard {
template <typename SampleType>
class Distortion
: public JucePlugin<juce::dsp::ProcessorChain<
juce::dsp::Gain<SampleType>, juce::dsp::WaveShaper<SampleType>>> {
public:
void setDriveDecibels(const float f) noexcept { driveDecibels = f; }
float getDriveDecibels() const noexcept { return driveDecibels; }

virtual void prepare(const juce::dsp::ProcessSpec &spec) override {
JucePlugin<juce::dsp::ProcessorChain<juce::dsp::Gain<SampleType>,
juce::dsp::WaveShaper<SampleType>>>::
prepare(spec);
this->getDSP().template get<gainIndex>().setGainDecibels(
getDriveDecibels());
this->getDSP().template get<waveshaperIndex>().functionToUse =
[](SampleType x) { return std::tanh(x); };
}

private:
SampleType driveDecibels;

enum { gainIndex, waveshaperIndex };
};

inline void init_distortion(py::module &m) {
py::class_<Distortion<float>, Plugin>(
m, "Distortion", "Apply soft distortion with a tanh waveshaper.")
.def(py::init([](float drive_db) {
auto plugin = new Distortion<float>();
plugin->setDriveDecibels(drive_db);
return plugin;
}),
py::arg("drive_db") = 25)
.def("__repr__",
[](const Distortion<float> &plugin) {
std::ostringstream ss;
ss << "<pedalboard.Distortion";
ss << " drive_db=" << plugin.getDriveDecibels();
ss << " at " << &plugin;
ss << ">";
return ss.str();
})
.def_property("drive_db", &Distortion<float>::getDriveDecibels,
&Distortion<float>::setDriveDecibels);
}
}; // namespace Pedalboard
2 changes: 2 additions & 0 deletions pedalboard/python_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace py = pybind11;
#include "plugins/Chorus.h"
#include "plugins/Compressor.h"
#include "plugins/Convolution.h"
#include "plugins/Distortion.h"
#include "plugins/Gain.h"
#include "plugins/HighpassFilter.h"
#include "plugins/LadderFilter.h"
Expand Down Expand Up @@ -125,6 +126,7 @@ PYBIND11_MODULE(pedalboard_native, m) {

init_compressor(m);
init_convolution(m);
init_distortion(m);
init_gain(m);
init_highpass(m);
init_ladderfilter(m);
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@

setup(
name='pedalboard',
version='0.3.3',
version='0.3.4',
author='Peter Sobot',
author_email='[email protected]',
description='A Python library for adding effects to audio.',
Expand Down
2 changes: 1 addition & 1 deletion tests/test_locking.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import pedalboard


@pytest.mark.parametrize("num_concurrent_chains", [10, 24, 50])
@pytest.mark.parametrize("num_concurrent_chains", [2, 10, 20])
def test_multiple_threads_using_same_plugin_instances(num_concurrent_chains: int):
"""
Instantiate a large number of stateful plugins, then run audio through them
Expand Down
17 changes: 15 additions & 2 deletions tests/test_native_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import os
import pytest
import numpy as np
from pedalboard import process, Gain, Compressor, Convolution

from pedalboard import process, Distortion, Gain, Compressor, Convolution
from librosa import db_to_amplitude

IMPULSE_RESPONSE_PATH = os.path.join(os.path.dirname(__file__), "impulse_response.wav")

Expand Down Expand Up @@ -77,3 +77,16 @@ def test_throw_on_inaccessible_convolution_file():
# Should fail:
with pytest.raises(RuntimeError):
Convolution("missing_impulse_response.wav")


@pytest.mark.parametrize("gain_db", [-12, -6, 0, 1.1, 6, 12, 24, 48, 96])
@pytest.mark.parametrize("shape", [(44100,), (44100, 1), (44100, 2), (1, 4), (2, 4)])
def test_distortion(gain_db, shape, sr=44100):
full_scale_noise = np.random.rand(*shape).astype(np.float32)

# Use the Distortion transform with ±0dB, which should change nothing:
result = process(full_scale_noise, sr, [Distortion(gain_db)])

np.testing.assert_equal(result.shape, full_scale_noise.shape)
gain_scale = db_to_amplitude(gain_db)
np.testing.assert_allclose(np.tanh(full_scale_noise * gain_scale), result, rtol=4e-7, atol=2e-7)

0 comments on commit 009d7f3

Please sign in to comment.