message: pep8 cleaning

diff:
@@ -3,7 +3,6 @@ import warnings
import ffmpeg
import tempfile
from pysndfx import AudioEffectsChain
-from abc import abstractmethod
# import av
# from av.filter import Filter, Graph
@@ -11,7 +10,7 @@ from .constants import LEVEL_MIN, LEVEL_MAX
from .utils import _close_temp_files
-class FilterFunction():
+class FilterFunction:
"""
The FilterFunction class is an abstract class for functions that take
audio processing streams, such as ffmpeg-python and pysndfx
@@ -189,6 +188,7 @@ def pitch_shift(shift):
return SoXFilter("pitch", shift=shift)
+
def _pass_arg_check(freq, poles, width_type, width):
if not np.issubdtype(type(freq), np.number) or freq <= 0:
raise ValueError("lowest_freq should be positive scalar")
@@ -218,6 +218,7 @@ def low_pass(freq, poles=2, width_type="h", width=.707):
'o': octave
's': slope
'k': kHz
+ width: Band width in width_type units
Returns:
filter: A FFmpegFilter object, to be called on an ffmpeg stream
"""
@@ -242,6 +243,7 @@ def high_pass(freq, poles=2, width_type="h", width=.707):
'o': octave
's': slope
'k': kHz
+ width: Band width in width_type units
Returns:
filter: A FFmpegFilter object, to be called on an ffmpeg stream
"""
@@ -309,7 +311,7 @@ def chorus(delays, decays, speeds, depths,
Returns:
filter: A FFmpegFilter object, to be called on an ffmpeg stream
"""
- if (in_gain > 1 or in_gain < 0 or out_gain > 1 or out_gain < 0):
+ if in_gain > 1 or in_gain < 0 or out_gain > 1 or out_gain < 0:
raise ValueError("in_gain and out_gain must be between 0 and 1.")
# Bounds could not be found in ffmpeg docs, but recommendations were given
@@ -353,9 +355,9 @@ def phaser(in_gain=.4, out_gain=.74, delay=3,
"""
if in_gain > 1 or in_gain < 0:
raise ValueError("in_gain must be between 0 and 1.")
- if out_gain > 1 or out_gain < 1:
+ if out_gain > 1 or out_gain < 0:
raise ValueError("out_gain must be between 0 and 1.")
- if delay > 1 or delay < 1:
+ if decay > 1 or decay < 0:
raise ValueError("delay must be between 0 and 1.")
allowed_mod_types = {"triangular", "sinusoidal", "t", "s"}
@@ -383,19 +385,22 @@ def _flanger_argcheck(delay, depth, regen, width,
or depth < 0 or depth > 10
or regen < -95 or regen > 95
or width < 0 or width > 100
- or speed < .1 or speed > 10):
+ or speed < .1 or speed > 10
+ or phase < 0 or phase > 100):
raise ValueError("One of the follow values are not in the accepted ranges"
f"delay: {delay}\n"
f"depth: {depth}\n"
f"regen: {regen}\n"
f"width: {width}\n"
f"speed: {speed}\n"
+ f"phase: {phase}\n"
"The following are the bounds for the parameters to flanger()"
"0 < delay < 30\n"
"0 < depth < 10\n"
"-95 < regen < 95\n"
"0 < width < 100\n"
".1 < speed < 10\n"
+ "0 < phase < 100"
)

diff:
-import numpy as np
-import numpy.random as random
-import nussl.core.augmentation as augment
-import nussl.datasets.hooks as hooks
-import tempfile
-import pytest
-import os
-import os.path as path
-import librosa
-import pytest
-
-REGRESSION_PATH="tests/core/regression/augmentation"
-os.makedirs(REGRESSION_PATH, exist_ok=True)
-
-def test_stretch(mix_and_sources):
- stretch_factor = .8
- mix, _ = mix_and_sources
- augmented = augment.time_stretch(mix, stretch_factor)
-
- assert np.allclose(augmented.audio_data[0, :],
- librosa.effects.time_stretch(np.asfortranarray(mix.audio_data[0, :]), stretch_factor))
-
-def test_pitch_shift(mix_and_sources):
- shift = 2
- mix, _ = mix_and_sources
- sample_rate = mix.sample_rate
-
- augmented = augment.pitch_shift(mix, shift)
-
- assert np.allclose(augmented.audio_data[0, :],
- librosa.effects.pitch_shift(np.asfortranarray(mix.audio_data[0, :]), sample_rate, shift))
-
-def test_params(mix_and_sources):
- audio_signal, _ = mix_and_sources
-
- with pytest.raises(ValueError):
- augment.time_stretch(audio_signal, -1)
-
- with pytest.raises(ValueError):
- augment.time_stretch(audio_signal, "not scalar")
-
- with pytest.raises(ValueError):
- augment.pitch_shift(audio_signal, 1.4)
-
- with pytest.raises(ValueError):
- augment.pitch_shift(audio_signal, "this is a string")
-
-def ffmpeg_regression(audio_data, reg_path, check_against_regression_data):
- scores = {
- 'test_metrics': {
- "mean": [np.mean(audio_data, dtype=float)],
- "t_0_mean": [np.mean(audio_data[:, 0], dtype=float)],
- "variance": [np.var(audio_data, dtype=float)],
- "f_0_mean": [np.mean(audio_data[0, :], dtype=float)],
- "sum": [np.sum(audio_data, dtype=float)]
- }
- }
- check_against_regression_data(scores, reg_path)
-
-def test_tremolo(mix_and_sources, check_against_regression_data):
- f = 15
- d = .5
- mix, _ = mix_and_sources
- reg_path = path.join(REGRESSION_PATH, "tremolo.json")
-
- augmented_signal = augment.tremolo(mix, f, d)
-
- ffmpeg_regression(augmented_signal.audio_data,
- reg_path, check_against_regression_data)
-
-def test_vibrato(mix_and_sources, check_against_regression_data):
- f = 5
- d = .5
- reg_path = path.join(REGRESSION_PATH, "vibrato.json")
- mix, _ = mix_and_sources
- augmented_signal = augment.vibrato(mix, f, d)
-
- ffmpeg_regression(augmented_signal.audio_data,
- reg_path, check_against_regression_data)
-
-# def test_echo(mix_and_sources, check_against_regression_data):
-# # This sounds like an open air concert in the mountains
-# in_gain = .8
-# out_gain = .9
-# delays = [1000]
-# decays = [.3]
-# mix, _ = mix_and_sources
-
-# reg_path = path.join(REGRESSION_PATH, "echo.json")
-# echo_mix = echo(mix, in_gain, out_gain, delays, decays)
-# ffmpeg_regression(echo_mix.audio_data,
-# reg_path, check_against_regression_data)
-
-# # Same as above but with one more mountain
-# delays = [1000, 1800]
-# decays = [.3 , .25]
-# reg_path = path.join(REGRESSION_PATH, "echo2.json")
-# echo_mix = echo(mix, in_gain, out_gain, delays, decays)
-# ffmpeg_regression(echo_mix.audio_data,
-# reg_path, check_against_regression_data)
-
-def test_emphasis(mix_and_sources, check_against_regression_data):
- mix, _ = mix_and_sources
- level_in = 1
- level_out = 2
- types = ['col', 'riaa', 'cd']
- mode = 'production'
-
- for _type in types:
- reg_path = path.join(REGRESSION_PATH, f"emphasis_{_type}.json")
- augmented_signal = augment.emphasis(mix, level_in, level_out, _type, mode=mode)
- ffmpeg_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
-
-def test_compressor(mix_and_sources, check_against_regression_data):
- mix, _ = mix_and_sources
- level_in = 1
-
- reg_path = path.join(REGRESSION_PATH, f"compression.json")
- augmented_signal = augment.compressor(mix, level_in)
- ffmpeg_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
-
-def test_compression_fail(mix_and_sources):
- signal, _ = mix_and_sources
-
- # Out of bounds values
- level_in = -1
- reduction_ratio = 299
- attack = 19999
- release = 89999
- makeup = 69
- knee = 10
- link = "fail"
- detection = "fail"
- mix = 8
- threshold = 0
-
- with pytest.raises(ValueError):
- augment.compressor(signal, level_in)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, knee=knee)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, reduction_ratio=reduction_ratio)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, attack=attack)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, release=release)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, makeup=makeup)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, link=link)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, detection=detection)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, attack=attack)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, mix=mix)
-
- with pytest.raises(ValueError):
- augment.compressor(signal, 1, threshold=threshold)
-
-def test_equalizer(mix_and_sources, check_against_regression_data):
- bands = [
- {
- "chn": [0, 1],
- "f": i,
- "w": (5 * np.log2(i)),
- "g": 5,
- "t": 0
- } for i in [110, 440, 10000]
- ]
-
- signal, _ = mix_and_sources
- augmented_signal = augment.equalizer(signal, bands)
-
- reg_path = path.join(REGRESSION_PATH, "equalizer.json")
- ffmpeg_regression(augmented_signal.audio_data,
- reg_path, check_against_regression_data)
-
-def test_phaser(mix_and_sources, check_against_regression_data):
- mix, _ = mix_and_sources
-
- in_gain = .4
- out_gain = .74
- delay = 3
- speed = .8
-
- augmented_signal = augment.phaser(mix, in_gain, out_gain, delay, speed)
- reg_path = path.join(REGRESSION_PATH, "phaser.json")
- ffmpeg_regression(augmented_signal.audio_data, reg_path,
- check_against_regression_data)
-
-def test_chorus(mix_and_sources, check_against_regression_data):
- mix, _ = mix_and_sources
-
- in_gain = .4
- out_gain = .7
- delays = [45, 55]
- decays = [.6, .2]
- speeds = [.9, .8]
- depths = [2, 1.5]
-
- augmented_signal = augment.chorus(mix, delays, decays,
- speeds, depths, in_gain=in_gain, out_gain=out_gain)
- reg_path = path.join(REGRESSION_PATH, "chorus.json")
- ffmpeg_regression(augmented_signal.audio_data, reg_path,
- check_against_regression_data)
-
-def test_flanger(mix_and_sources, check_against_regression_data):
- mix, _ = mix_and_sources
-
- delay = 10
- depth = 2
- regen = 10
- width = 70
- speed = .5
- phase = 25
-
- augmented_signal = augment.flanger(mix, delay, depth, regen, width,
- speed, phase)
- reg_path = path.join(REGRESSION_PATH, "flanger.json")
- ffmpeg_regression(augmented_signal.audio_data, reg_path,
- check_against_regression_data)
-
-def test_low_high_pass(mix_and_sources):
- mix, _ = mix_and_sources
- f = 512
- idx = 16
- low_mix = augment.low_pass(mix, f)
- mix.stft_data = None
- high_mix = augment.high_pass(mix, f)
- l_stft = low_mix.stft_data
- h_stft = high_mix.stft_data
- assert np.allclose(l_stft[idx:], np.zeros_like(l_stft[idx:]))
- assert np.allclose(h_stft[:idx-1], np.zeros_like(h_stft[:idx-1]))
-
- with pytest.raises(ValueError):
- augment.low_pass(mix, -1)
- with pytest.raises(ValueError):
- augment.low_pass(mix, "fail")
-
- with pytest.raises(ValueError):
- augment.high_pass(mix, -1)
- with pytest.raises(ValueError):
- augment.high_pass(mix, "fail")
-
-def test_misc_param_check(mix_and_sources):
- signal, _ = mix_and_sources
- with pytest.raises(ValueError):
- augment.vibrato(signal, -1, 1)
- with pytest.raises(ValueError):
- augment.vibrato(signal, 1, -1)
-
- with pytest.raises(ValueError):
- augment.tremolo(signal, -1, 1)
- with pytest.raises(ValueError):
- augment.tremolo(signal, 1, -1)
-
- with pytest.raises(ValueError):
- augment.chorus(signal, [1], [2,3],[45,6],[4])
- with pytest.warns(UserWarning):
- augment.chorus(signal, [2000],[.5], [.7], [.4])
- with pytest.raises(ValueError):
- augment.chorus(signal, [1],[30], [.7], [.4], in_gain=-1)
-
- with pytest.raises(ValueError):
- augment.phaser(signal, in_gain=-1)
- with pytest.raises(ValueError):
- augment.phaser(signal, _type="fail")
-
- with pytest.raises(ValueError):
- augment.flanger(signal, delay=-1)
- with pytest.raises(ValueError):
- augment.flanger(signal, depth=-1)
- with pytest.raises(ValueError):
- augment.flanger(signal, regen=-100)
- with pytest.raises(ValueError):
- augment.flanger(signal, width=-1)
- with pytest.raises(ValueError):
- augment.flanger(signal, speed=-1)
- with pytest.raises(ValueError):
- augment.flanger(signal, shape="fail")
- with pytest.raises(ValueError):
- augment.flanger(signal, interp="fail")
-
- with pytest.raises(ValueError):
- augment.emphasis(signal, 1, 1, _type="fail")
- with pytest.raises(ValueError):
- augment.emphasis(signal, 1, 1, mode="fail")
- with pytest.raises(ValueError):
- augment.emphasis(signal, 1, -1)
-
- band = {
- 'chn':[0],
- 'f': 300,
- 'w': 10,
- 'g': 10,
- 't': 5
- }
- with pytest.raises(ValueError):
- augment.equalizer(signal, [band])
- band["g"] = -1
- with pytest.raises(ValueError):
- augment.equalizer(signal, [band])
- band["w"] = -1
- with pytest.raises(ValueError):
- augment.equalizer(signal, [band])
- band["f"] = -1
- with pytest.raises(ValueError):
- augment.equalizer(signal, [band])
- band["chn"].append(100)
- with pytest.raises(ValueError):
- augment.equalizer(signal, [band])
-
-
-def test_filter_debug_mode(mix_and_sources):
- signal, _ = mix_and_sources
- augment.apply_ffmpeg_filter(signal, "tremolo", silent=False)
\ No newline at end of file

diff:
import numpy as np
-import numpy.random as random
import nussl.core.effects as effects
-import nussl.datasets.hooks as hooks
-import tempfile
-import pytest
import os
import os.path as path
-import librosa
import pytest
-import logging
REGRESSION_PATH = "tests/core/regression/augmentation"
os.makedirs(REGRESSION_PATH, exist_ok=True)
+
def fx_regression(audio_data, reg_path, check_against_regression_data):
scores = {
'test_metrics': {
@@ -22,6 +17,7 @@ def fx_regression(audio_data, reg_path, check_against_regression_data):
}
check_against_regression_data(scores, reg_path)
+
def test_stretch(mix_and_sources, check_against_regression_data):
stretch_factor = .8
signal, _ = mix_and_sources
@@ -31,6 +27,7 @@ def test_stretch(mix_and_sources, check_against_regression_data):
reg_path = path.join(REGRESSION_PATH, "time_stretch.json")
fx_regression(augmented.audio_data, reg_path, check_against_regression_data)
+
def test_pitch_shift(mix_and_sources, check_against_regression_data):
shift = 2
signal, _ = mix_and_sources
@@ -40,6 +37,7 @@ def test_pitch_shift(mix_and_sources, check_against_regression_data):
reg_path = path.join(REGRESSION_PATH, "pitch_shift.json")
fx_regression(augmented.audio_data, reg_path, check_against_regression_data)
+
def test_params(mix_and_sources):
with pytest.raises(ValueError):
effects.time_stretch(-1)
@@ -53,8 +51,8 @@ def test_params(mix_and_sources):
with pytest.raises(ValueError):
effects.pitch_shift("this is a string")
+
def test_tremolo(mix_and_sources, check_against_regression_data):
- logging.basicConfig()
f = 15
d = .5
signal, _ = mix_and_sources
@@ -66,6 +64,7 @@ def test_tremolo(mix_and_sources, check_against_regression_data):
fx_regression(augmented_signal.audio_data,
reg_path, check_against_regression_data)
+
def test_vibrato(mix_and_sources, check_against_regression_data):
f = 5
d = .5
@@ -78,6 +77,7 @@ def test_vibrato(mix_and_sources, check_against_regression_data):
fx_regression(augmented_signal.audio_data,
reg_path, check_against_regression_data)
+
def test_emphasis(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
level_in = 1
@@ -91,6 +91,7 @@ def test_emphasis(mix_and_sources, check_against_regression_data):
augmented_signal = effects.build_effects_ffmpeg(signal, filters)
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
+
def test_compressor(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
level_in = 1
@@ -100,8 +101,9 @@ def test_compressor(mix_and_sources, check_against_regression_data):
augmented_signal = effects.build_effects_ffmpeg(signal, filters)
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
-def test_compression_fail(mix_and_sources):
+# noinspection DuplicatedCode
+def test_compression_fail(mix_and_sources):
# Out of bounds values
level_in = -1
reduction_ratio = 299
@@ -123,9 +125,6 @@ def test_compression_fail(mix_and_sources):
with pytest.raises(ValueError):
effects.compressor(1, reduction_ratio=reduction_ratio)
- with pytest.raises(ValueError):
- effects.compressor(1, attack=attack)
-
with pytest.raises(ValueError):
effects.compressor(1, release=release)
@@ -147,6 +146,7 @@ def test_compression_fail(mix_and_sources):
with pytest.raises(ValueError):
effects.compressor(1, threshold=threshold)
+
def test_equalizer(mix_and_sources, check_against_regression_data):
bands = [
{
@@ -166,6 +166,7 @@ def test_equalizer(mix_and_sources, check_against_regression_data):
fx_regression(augmented_signal.audio_data,
reg_path, check_against_regression_data)
+
def test_phaser(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
@@ -180,6 +181,7 @@ def test_phaser(mix_and_sources, check_against_regression_data):
fx_regression(augmented_signal.audio_data, reg_path,
check_against_regression_data)
+
def test_chorus(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
@@ -197,6 +199,7 @@ def test_chorus(mix_and_sources, check_against_regression_data):
fx_regression(augmented_signal.audio_data, reg_path,
check_against_regression_data)
+
def test_flanger(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
@@ -214,6 +217,7 @@ def test_flanger(mix_and_sources, check_against_regression_data):
fx_regression(augmented_signal.audio_data, reg_path,
check_against_regression_data)
+
def test_low_high_pass(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
f = 512
@@ -251,6 +255,7 @@ def test_low_high_pass(mix_and_sources, check_against_regression_data):
with pytest.raises(ValueError):
effects.high_pass(1, width=-1)
+
def test_misc_param_check():
with pytest.raises(ValueError):
effects.vibrato(-1, 1)
@@ -325,10 +330,12 @@ def test_misc_param_check():
with pytest.raises(ValueError):
effects.SoXFilter('fail')
+
def test_silent_mode(mix_and_sources):
signal, _ = mix_and_sources
effects.build_effects_ffmpeg(signal, [], silent=True)
+
def test_hooks(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
@@ -357,7 +364,7 @@ def test_hooks(mix_and_sources, check_against_regression_data):
assert len(signal._ffmpeg_effects_chain) == 10
assert len(signal._sox_effects_chain) == 2
- augmented_signal = signal.build_effect(reset=False)
+ signal.build_effect(reset=False)
assert len(signal._ffmpeg_effects_chain) == 10
assert len(signal._sox_effects_chain) == 2
@@ -367,11 +374,10 @@ def test_hooks(mix_and_sources, check_against_regression_data):
reg_path = path.join(REGRESSION_PATH, "hooks.json")
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
- augmented_signal = augmented_signal.build_effect()
+ augmented_signal.build_effect()
assert len(signal._ffmpeg_effects_chain) == 0
assert len(signal._sox_effects_chain) == 0
with pytest.raises(RuntimeError):
signal.build_effect()
-

original_message: pep8 cleaning


message: syntax issue

change_type: MODIFY

diff:
@@ -382,7 +382,8 @@ def test_hooks(mix_and_sources, check_against_regression_data):
with pytest.raises(RuntimeError):
signal.build_effect()
-def test_filterfunction_pass:
+
+def test_filter_function_pass():
# this test is for 100% coverage
function = effects.FilterFunction
function.func()

license: MIT License

repo: nussl/nussl

original_message: syntax issue


message: fixing filter function test

diff:
@@ -385,5 +385,6 @@ def test_hooks(mix_and_sources, check_against_regression_data):
def test_filter_function_pass():
# this test is for 100% coverage
- function = effects.FilterFunction
- function.func()
+ function = effects.FilterFunction()
+ dummy_item = None
+ function.func(dummy_item)

original_message: fixing filter function test


message: time stretch and pitch shift now use pysox

diff:
@@ -2,7 +2,7 @@ import numpy as np
import warnings
import ffmpeg
import tempfile
-from pysndfx import AudioEffectsChain
+import sox
# import av
# from av.filter import Filter, Graph
@@ -40,17 +40,6 @@ class FFmpegFilter(FilterFunction):
self.func = lambda stream: stream.filter(_filter, **filter_kwargs)
-class SoXFilter(FilterFunction):
- def __init__(self, _filter, **filter_kwargs):
- self.filter = _filter
- if _filter == "tempo":
- self.func = lambda stream: stream.tempo(**filter_kwargs)
- elif _filter == "pitch":
- self.func = lambda stream: stream.pitch(**filter_kwargs)
- else:
- raise ValueError
-
-
def build_effects_ffmpeg(audio_signal, filters, silent=False):
"""
build_effects_ffmpeg takes an AudioSignal object and a list of FFmpegFilter objects
@@ -133,6 +122,17 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
return augmented_signal
+class SoXFilter(FilterFunction):
+ def __init__(self, _filter, **filter_kwargs):
+ self.filter = _filter
+ if _filter == "tempo":
+ self.func = lambda tfm: tfm.tempo(**filter_kwargs)
+ elif _filter == "pitch":
+ self.func = lambda tfm: tfm.pitch(**filter_kwargs)
+ else:
+ raise ValueError
+
+
def build_effects_sox(audio_signal, filters):
"""
build_effects_sox takes an AudioSignal object and a list of SoXFilter objects
@@ -146,12 +146,14 @@ def build_effects_sox(audio_signal, filters):
"""
audio_data = audio_signal.audio_data
- chain = AudioEffectsChain()
+ tfm = sox.Transformer()
+ # tfm.set_output_format(channels=audio_signal.num_channels)
for _filter in filters:
- chain = _filter(chain)
- augmented_data = chain(audio_data)
+ tfm = _filter(tfm)
+ augmented_data = tfm.build_array(input_array=np.transpose(audio_data),
+ sample_rate_in=audio_signal.sample_rate)
- return audio_signal.make_copy_with_audio_data(augmented_data)
+ return audio_signal.make_copy_with_audio_data(np.transpose(augmented_data))
def make_arglist_ffmpeg(lst, sep="|"):
@@ -178,7 +180,7 @@ def pitch_shift(shift):
Returns a SoXFilter, when called on an pysndfx stream, will increase the pitch
of the audio by a number of cents, denoted in shift.
Args:
- shift: The number of cents (1/100th of a half step) to shift the audio.
+ shift: The number of semitones to shift the audio.
Positive values increases the frequency of the signal
Returns:
filter: A SoXFilter object, to be called on an pysndfx stream
@@ -186,7 +188,7 @@ def pitch_shift(shift):
if not isinstance(shift, int):
raise ValueError("shift must be an integer.")
- return SoXFilter("pitch", shift=shift)
+ return SoXFilter("pitch", n_semitones=shift)
def _pass_arg_check(freq, poles, width_type, width):

diff:
@@ -17,5 +17,4 @@ gpytorch
ffmpeg-python
importlib_resources
pyloudnorm
-ffmpeg-python
-pysndfx
+sox --pre

diff:
@@ -342,7 +342,7 @@ def test_hooks(mix_and_sources, check_against_regression_data):
signal = (
signal
.time_stretch(3)
- .pitch_shift(2000)
+ .pitch_shift(2)
.low_pass(512)
.high_pass(512)
.tremolo(5, .4)


message: changed hooks interface to avoid exec calls

diff:
@@ -1653,7 +1653,7 @@ class AudioSignal(object):
# Effect Hooks #
##################################
- def build_effect(self, reset=True, overwrite=False):
+ def build_effects(self, reset=True, overwrite=False):
"""
Builds all effects
"""
@@ -1675,6 +1675,39 @@ class AudioSignal(object):
return new_signal
+ def make_effect(self, effect, **params):
+ if effect == "time_stretch":
+ self.time_stretch(**params)
+ elif effect == "pitch_shift":
+ self.pitch_shift(**params)
+ elif effect == "low_pass":
+ self.low_pass(**params)
+ elif effect == "high_pass":
+ self.high_pass(**params)
+ elif effect == "tremolo":
+ self.tremolo(**params)
+ elif effect == "vibrato":
+ self.vibrato(**params)
+ elif effect == "chorus":
+ self.chorus(**params)
+ elif effect == "phaser":
+ self.phaser(**params)
+ elif effect == "flanger":
+ self.flanger(**params)
+ elif effect == "emphasis":
+ self.emphasis(**params)
+ elif effect == "compressor":
+ self.compressor(**params)
+ elif effect == "equalizer":
+ self.equalizer(**params)
+ else:
+ raise ValueError("Invalid effect passed\n" +
+ "Expected one of the following: " +
+ "[time_stretch, pitch_shift, low_pass, high_pass, tremolo," +
+ "vibrato, chorus, phaser, flanger, emphasis, " +
+ "compressor, equalizer]")
+ return self
+
def reset_effects_chain(self):
self._ffmpeg_effects_chain = []
self._sox_effects_chain = []

diff:
@@ -364,24 +364,55 @@ def test_hooks(mix_and_sources, check_against_regression_data):
assert len(signal._ffmpeg_effects_chain) == 10
assert len(signal._sox_effects_chain) == 2
- signal.build_effect(reset=False)
+ signal.build_effects(reset=False)
assert len(signal._ffmpeg_effects_chain) == 10
assert len(signal._sox_effects_chain) == 2
- augmented_signal = signal.build_effect(reset=False, overwrite=True)
+ augmented_signal = signal.build_effects(reset=False, overwrite=True)
reg_path = path.join(REGRESSION_PATH, "hooks.json")
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
- augmented_signal.build_effect()
+ augmented_signal.build_effects()
assert len(signal._ffmpeg_effects_chain) == 0
assert len(signal._sox_effects_chain) == 0
with pytest.raises(RuntimeError):
- signal.build_effect()
+ signal.build_effects()
+def test_general_hooks(mix_and_sources, check_against_regression_data):
+ signal, _ = mix_and_sources
+
+ (signal
+ .make_effect("time_stretch", factor=3)
+ .make_effect("pitch_shift", shift=2)
+ .make_effect("low_pass", freq=512)
+ .make_effect("high_pass", freq=512)
+ .make_effect("tremolo", mod_freq=5, mod_depth=.4)
+ .make_effect("vibrato", mod_freq=3, mod_depth=.9)
+ .make_effect("chorus", delays=[20, 70], decays=[.9, .4], speeds=[.9, .6], depths=[1, .9])
+ .make_effect("phaser")
+ .make_effect("flanger", delay=3)
+ .make_effect("emphasis", level_in=1, level_out=.5, _type='riaa')
+ .make_effect("compressor", level_in=.9)
+ .make_effect("equalizer", bands=[{
+ 'chn': [0, 1],
+ 'f': 512,
+ 'w': 10,
+ 'g': 4,
+ 't': 0
+ }])
+ )
+ signal.build_effects(reset=False)
+ augmented_signal = signal.build_effects(reset=False, overwrite=True)
+ reg_path = path.join(REGRESSION_PATH, "hooks.json")
+ # This should result in the same signal in test_hooks
+ fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
+
+ with pytest.raises(ValueError):
+ signal.make_effect("fail")
def test_filter_function_pass():
# this test is for 100% coverage


message: cleaned AudioSignal.make_effect and added docs

diff:
@@ -1655,7 +1655,15 @@ class AudioSignal(object):
def build_effects(self, reset=True, overwrite=False):
"""
- Builds all effects
+ Builds all effects in the AudioSignal's effects chains.
+
+ Args:
+ reset: If True, clears out all effects in effect chains. Default=True
+ overwrite: If True, overwrites existing audio_data in AudioSignal. Defult=False
+ Also clears out stft_data
+ Returns:
+ self: If overwrite=True, then returns initially AudioSignal with edited audio_data.
+ Otherwise, returns a new AudioSignal
"""
if not self._ffmpeg_effects_chain and not self._sox_effects_chain:
raise RuntimeError("No effect hooks have been called on this AudioSignal")
@@ -1676,86 +1684,212 @@ class AudioSignal(object):
return new_signal
def make_effect(self, effect, **params):
- if effect == "time_stretch":
- self.time_stretch(**params)
- elif effect == "pitch_shift":
- self.pitch_shift(**params)
- elif effect == "low_pass":
- self.low_pass(**params)
- elif effect == "high_pass":
- self.high_pass(**params)
- elif effect == "tremolo":
- self.tremolo(**params)
- elif effect == "vibrato":
- self.vibrato(**params)
- elif effect == "chorus":
- self.chorus(**params)
- elif effect == "phaser":
- self.phaser(**params)
- elif effect == "flanger":
- self.flanger(**params)
- elif effect == "emphasis":
- self.emphasis(**params)
- elif effect == "compressor":
- self.compressor(**params)
- elif effect == "equalizer":
- self.equalizer(**params)
- else:
+ """
+ Calls another effect hook.
+ Args:
+ effect: Function name of desired effect hook of the AudioSignal
+ params: Additional parameters for given effect.
+
+ """
+ effect_hook = getattr(self, effect, None)
+ if effect_hook is None or not effect_hook.__doc__.strip().startswith("Effect Hook"):
raise ValueError("Invalid effect passed\n" +
"Expected one of the following: " +
"[time_stretch, pitch_shift, low_pass, high_pass, tremolo," +
"vibrato, chorus, phaser, flanger, emphasis, " +
"compressor, equalizer]")
+
+ self.effect_hook(**params)
+
return self
def reset_effects_chain(self):
+ """
+ Clears effects chain of AudioSignal
+ """
self._ffmpeg_effects_chain = []
self._sox_effects_chain = []
+ return self
def time_stretch(self, factor):
+ """
+ Effect Hook
+
+ Adds time stretch effect to AudioSignal's effect chain.
+ Args:
+ factor: Scaling factor for tempo change. Must be positive.
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._sox_effects_chain.append(effects.time_stretch(factor))
return self
def pitch_shift(self, shift):
+ """
+ Effect Hook
+
+ Add pitch shift effect to AudioSignal's effect chain.
+ Args:
+ shift: The number of semitones to shift the audio.
+ Positive values increases the frequency of the signal
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._sox_effects_chain.append(effects.pitch_shift(shift))
return self
def low_pass(self, freq, poles=2, width_type="h", width=.707):
+ """
+ Effect Hook
+
+ Add low pass effect to AudioSignal's effect chain
+ Args:
+ freq: Threshold for high pass. Should be positive scalar
+ poles: Number of poles. should be either 1 or 2
+ width_type: Unit of width for filter. Must be either:
+ 'h': Hz
+ 'q': Q-factor
+ 'o': octave
+ 's': slope
+ 'k': kHz
+ width: Band width in width_type units
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.low_pass(freq, poles=poles,
width_type=width_type, width=width))
return self
def high_pass(self, freq, poles=2, width_type="h", width=.707):
+ """
+ Effect Hook
+
+ Add high pass effect to AudioSignal's effect chain
+ Args:
+ freq: Threshold for high pass. Should be positive scalar
+ poles: Number of poles. should be either 1 or 2
+ width_type: Unit of width for filter. Must be either:
+ 'h': Hz
+ 'q': Q-factor
+ 'o': octave
+ 's': slope
+ 'k': kHz
+ width: Band width in width_type units
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.high_pass(freq, poles=poles,
width_type=width_type, width=width))
return self
def tremolo(self, mod_freq, mod_depth):
+ """
+ Effect Hook
+
+ Add tremolo effect to AudioSignal's effect chain
+ Args:
+ mod_freq: Modulation frequency. Must be between .1 and 20000.
+ mod_depth: Modulation depth. Must be between 0 and 1.
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.tremolo(mod_freq, mod_depth))
return self
def vibrato(self, mod_freq, mod_depth):
+ """
+ Effect Hook
+
+ Add vibrato effect to AudioSignal's effect chain
+ Args:
+ mod_freq: Modulation frequency. Must be between .1 and 20000.
+ mod_depth: Modulation depth. Must be between 0 and 1.
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.vibrato(mod_freq, mod_depth))
return self
def chorus(self, delays, decays, speeds, depths, in_gain=.4, out_gain=.4):
+ """
+ Effect Hook
+
+ Add chorus effect to AudioSignal's effect chain
+ Args:
+ delays: list of delays in ms. Typical Delay is 40ms-6ms
+ decays: list of decays. Must be between 0 and 1
+ speeds: list of speeds. Must be between 0 and 1
+ depths: list of depths. Must be between 0 and 1
+ in_gain: Proportion of input gain
+ out_gain: Proportion of output gain
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.chorus(delays, decays,
speeds, depths, in_gain=.4, out_gain=.4))
return self
def phaser(self, in_gain=.4, out_gain=.74, delay=3, decay=.4,
speed=.5, _type="triangular"):
+ """
+ Effect Hook
+
+ Add phaser effect to AudioSignal's effect chain
+ Args:
+ in_gain: Proportion of input gain. Must be between 0 and 1
+ out_gain: Proportion of output gain. Must be between 0 and 1.
+ delay: Delay of chorus filter in ms. (Time between original signal and delayed)
+ decay: Decay of copied signal. Must be between 0 and 1.
+ speed: Modulation speed of the delayed filter.
+ _type: modulation type. Either Triangular or Sinusoidal
+ "triangular" or "t" for Triangular
+ "sinusoidal" of "s" for sinusoidal
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.phaser(in_gain=.4, out_gain=.74, delay=3,
decay=.4, speed=.5, _type="triangular"))
return self
def flanger(self, delay=0, depth=2, regen=0, width=71,
speed=.5, phase=25, shape="sinusoidal", interp="linear"):
+ """
+ Effect Hook
+
+ Add flanger effect to AudioSignal's effect chain
+ Args:
+ delay: Base delay in ms between original signal and copy.
+ Must be between 0 and 30.
+ depth: Sweep delay in ms. Must be between 0 and 10.
+ regen: Percentage regeneration, or delayed signal feedback.
+ Must be between -95 and 95.
+ width: Percentage of delayed signal. Must be between 0 and 100.
+ speed: Sweeps per second. Must be in .1 to 10
+ shape: Swept wave shape, Must be "triangular" or "sinusoidal".
+ phase: swept wave percentage-shift for multi channel. Must be between 0 and 100.
+ interp: Delay Line interpolation. Must be "linear" or "quadratic".
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.flanger(delay=0, depth=2, regen=0, width=71,
speed=.5, phase=25, shape="sinusoidal", interp="linear"))
return self
def emphasis(self, level_in, level_out, _type="col", mode='production'):
+ """
+ Effect Hook
+
+ Add emphasis effect to AudioSignal's effect chain
+ Args:
+ level_in: Input gain
+ level_out: Output gain
+ _type: medium type to convert/deconvert from
+ mode: reproduction to convert from physical medium,
+ production to convert to physical medium.
+
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.emphasis(level_in,
level_out, _type="col", mode='production'))
return self
@@ -1763,12 +1897,54 @@ class AudioSignal(object):
def compressor(self, level_in, mode="downward", reduction_ratio=2,
attack=20, release=250, makeup=1, knee=2.8284, link="average",
detection="rms", mix=1, threshold=.125):
+ """
+ Effect Hook
+
+ Add compressor effect to AudioSignal's effect chain
+ Args:
+ level_in: Input Gain
+ mode: Mode of compressor operation. Can either be "upward" or "downward".
+ threshold: Volume threshold. If a signal's volume is above the threshold,
+ gain reduction would apply.
+ reduction_ratio: Ratio in which the signal is reduced.
+ attack: Time in ms between when the signal rises above threshold and when
+ reduction is applied
+ release: Time in ms between when the signal fall below threshold and
+ when reduction is decreased.
+ makeup: Factor of amplification post-processing
+ knee: Softens the transition between reduction and lack of thereof.
+ Higher values translate to a softer transition.
+ link: Choose average between all channels or mean. String of either
+ "average" or "mean.
+ detection: Whether to process exact signal of the RMS of nearby signals.
+ Either "peak" for exact or "rms".
+ mix: Proportion of compressed signal in output.
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.compressor(level_in, mode="downward", reduction_ratio=2,
attack=20, release=250, makeup=1, knee=2.8284, link="average",
detection="rms", mix=1, threshold=.125))
return self
def equalizer(self, bands):
+ """
+ Effect Hook
+
+ Add eqaulizer effect to AudioSignal's effect chain
+ Args:
+ bands: A list of dictionaries, for each band. The required values for each dictionary:
+ 'chn': List of channel numbers to apply filter. Must be list of ints.
+ 'f': central freqency of band
+ 'w': Width of the band in Hz
+ 'g': Band gain in dB
+ 't': Set filter type for band, optional, can be:
+ 0, for Butterworth
+ 1, for Chebyshev type 1
+ 2, for Chebyshev type 2
+ Returns:
+ self: Inital AudioSignal with updated effect chains
+ """
self._ffmpeg_effects_chain.append(effects.equalizer(bands))
return self

diff:
@@ -364,26 +364,22 @@ def test_hooks(mix_and_sources, check_against_regression_data):
assert len(signal._ffmpeg_effects_chain) == 10
assert len(signal._sox_effects_chain) == 2
- signal.build_effects(reset=False)
-
- assert len(signal._ffmpeg_effects_chain) == 10
- assert len(signal._sox_effects_chain) == 2
-
- augmented_signal = signal.build_effects(reset=False, overwrite=True)
+ augmented_signal = signal.build_effects(reset=False)
reg_path = path.join(REGRESSION_PATH, "hooks.json")
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
- augmented_signal.build_effects()
+ augmented_signal.time_stretch(.7).build_effects(overwrite=True)
- assert len(signal._ffmpeg_effects_chain) == 0
- assert len(signal._sox_effects_chain) == 0
+ assert len(augmented_signal._ffmpeg_effects_chain) == 0
+ assert len(augmented_signal._sox_effects_chain) == 0
with pytest.raises(RuntimeError):
- signal.build_effects()
+ augmented_signal.build_effects()
-def test_general_hooks(mix_and_sources, check_against_regression_data):
+def test_make_effect(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
+ signal.reset_effects_chain()
(signal
.make_effect("time_stretch", factor=3)
@@ -413,6 +409,27 @@ def test_general_hooks(mix_and_sources, check_against_regression_data):
with pytest.raises(ValueError):
signal.make_effect("fail")
+ signal.make_effect("__init__")
+
+ # The docstrings for every effect hook must start with "Effect Hook"
+ effects = ["time_stretch", "pitch_shift", "low_pass", "high_pass", "tremolo",
+ "vibrato", "chorus", "phaser", "flanger", "emphasis",
+ "compressor", "equalizer"]
+
+ for effect in effects:
+ if not getattr(signal, effect).__doc__.strip().startswith("Effect Hook"):
+ assert False
+
+ # All others should not start with "Effect Hook"
+ for attr in dir(signal):
+ try:
+ objattr = getattr(signal, attr)
+ except:
+ continue
+ docstring = str(getattr(objattr, "__doc__"))
+ if attr not in effects and docstring.strip().startswith("Effect Hook"):
+ assert False
+
def test_filter_function_pass():
# this test is for 100% coverage


message: fixed exception handling in AudioSignal.make_effect

diff:
@@ -1712,7 +1712,7 @@ class AudioSignal(object):
try:
effect_hook = getattr(self, effect, None)
effect_hook(**params)
- except:
+ except Exception:
raise AudioSignalException(f"Effect {effect} not found with parameters {params}")
return self

diff:
import numpy as np
import nussl.core.effects as effects
+from nussl.core.audio_signal import AudioSignalException
import os
import os.path as path
import pytest
@@ -407,7 +408,7 @@ def test_make_effect(mix_and_sources, check_against_regression_data):
# This should result in the same signal in test_hooks
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
- with pytest.raises(ValueError):
+ with pytest.raises(AudioSignalException):
signal.make_effect("fail")
def test_filter_function_pass():


message: using np.subdtype checking

diff:
@@ -88,10 +88,12 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
input_args['loglevel'] = 'quiet'
with _close_temp_files(tmpfiles):
- curr_tempfile = tempfile.NamedTemporaryFile(suffix=".wav")
+ curr_tempfile = tempfile.NamedTemporaryFile(suffix=".flac")
+ out_tempfile = tempfile.NamedTemporaryFile(suffix=".flac")
tmpfiles.append(curr_tempfile)
- audio_signal.write_audio_to_file(curr_tempfile)
+ tmpfiles.append(out_tempfile)
+ audio_signal.write_audio_to_file(out_tempfile)
# This is for applying effects via PyAV
# I was not able to make it work :(
@@ -137,12 +139,12 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
for _filter in filters:
stream = _filter(stream)
(stream
- .output(curr_tempfile.name)
+ .output(out_tempfile.name)
.overwrite_output()
.run()
)
- augmented_signal = AudioSignal(path_to_input_file=curr_tempfile.name)
+ augmented_signal = AudioSignal(path_to_input_file=out_tempfile.name)
return augmented_signal
@@ -211,7 +213,7 @@ def pitch_shift(shift):
Returns:
filter (SoxFilter): A SoXFilter object, to be called on an pysndfx stream
"""
- if not isinstance(shift, int):
+ if not np.issubdtype(type(shift), np.integer):
raise ValueError("shift must be an integer.")
return SoXFilter("pitch", n_semitones=shift)
@@ -609,7 +611,7 @@ def equalizer(bands):
"""
for band in bands:
for chn in band["chn"]:
- if chn < 0 or not isinstance(chn, int):
+ if chn < 0 or not np.issubdtype(type(chn), np.integer):
raise ValueError("All values in band[\"chn\"] must be positive integers")
if band["f"] <= 0:
raise ValueError("band[\"f\"] must be a positive scalar")

original_message: using np.subdtype checking


message: Fixed ffmpeg EOF error

diff:
@@ -93,7 +93,20 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
tmpfiles.append(curr_tempfile)
tmpfiles.append(out_tempfile)
- audio_signal.write_audio_to_file(out_tempfile)
+ audio_signal.write_audio_to_file(curr_tempfile)
+
+ tmpfiles.append(curr_tempfile)
+ stream = ffmpeg.input(curr_tempfile.name, **input_args)
+ for _filter in filters:
+ stream = _filter(stream)
+ (stream
+ .output(out_tempfile.name)
+ .overwrite_output()
+ .run()
+ )
+
+ augmented_signal = AudioSignal(path_to_input_file=out_tempfile.name)
+ return augmented_signal
# This is for applying effects via PyAV
# I was not able to make it work :(
@@ -134,19 +147,6 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
# out_cont.close()
# augmented_signal = AudioSignal(path_to_input_file=output_tempfile.name)
- tmpfiles.append(curr_tempfile)
- stream = ffmpeg.input(curr_tempfile.name, **input_args)
- for _filter in filters:
- stream = _filter(stream)
- (stream
- .output(out_tempfile.name)
- .overwrite_output()
- .run()
- )
-
- augmented_signal = AudioSignal(path_to_input_file=out_tempfile.name)
- return augmented_signal
-
class SoXFilter(FilterFunction):
def __init__(self, _filter, **filter_kwargs):

original_message: Fixed ffmpeg EOF error


message: added numba req

change_type: DELETE

diff: Binary files a/tests/utils/plots/viz_sources_dict.png and /dev/null differ

diff: Binary files a/tests/utils/plots/viz_sources_list.png and /dev/null differ

diff: Binary files a/tests/utils/plots/viz_spectrogram.png and /dev/null differ

diff: Binary files a/tests/utils/plots/viz_waveform.png and /dev/null differ

original_message: added numba req


message: cleaned commented code. removed lazy import

diff:
@@ -4,7 +4,7 @@ import ffmpeg
import tempfile
import sox
-import .audio_signal as audio_signal
+import nussl.core as core
from .constants import LEVEL_MIN, LEVEL_MAX
from .utils import _close_temp_files
@@ -95,7 +95,7 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
.run()
)
- augmented_signal = audio_signal.AudioSignal(path_to_input_file=out_tempfile.name)
+ augmented_signal = core.AudioSignal(path_to_input_file=out_tempfile.name)
return augmented_signal


message: reintroduced lazy load

diff:
@@ -4,7 +4,6 @@ import ffmpeg
import tempfile
import sox
-import nussl.core as core
from .constants import LEVEL_MIN, LEVEL_MAX
from .utils import _close_temp_files
@@ -70,6 +69,8 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
audio_signal after applying filters
"""
+ # lazy load to avoid circular import
+ from . import AudioSignal
tmpfiles = []
@@ -95,7 +96,7 @@ def build_effects_ffmpeg(audio_signal, filters, silent=False):
.run()
)
- augmented_signal = core.AudioSignal(path_to_input_file=out_tempfile.name)
+ augmented_signal = AudioSignal(path_to_input_file=out_tempfile.name)
return augmented_signal

original_message: reintroduced lazy load


message: removed vibrato regression test

diff:
@@ -75,10 +75,6 @@ def test_vibrato(mix_and_sources, check_against_regression_data):
filters = [effects.vibrato(f, d)]
augmented_signal = effects.build_effects_ffmpeg(signal, filters)
- fx_regression(augmented_signal.audio_data,
- reg_path, check_against_regression_data)
-
-
def test_emphasis(mix_and_sources, check_against_regression_data):
signal, _ = mix_and_sources
level_in = 1

original_message: removed vibrato regression test


message: 100% code-cov

diff:
@@ -110,8 +110,10 @@ def test_compression_fail(mix_and_sources):
knee = 10
link = "fail"
detection = "fail"
+ mode = "fail"
mix = 8
threshold = 0
+ phase = -1
with pytest.raises(ValueError):
effects.compressor(level_in)
@@ -143,6 +145,8 @@ def test_compression_fail(mix_and_sources):
with pytest.raises(ValueError):
effects.compressor(1, threshold=threshold)
+ with pytest.raises(ValueError):
+ effects.compressor(1, mode=mode)
def test_equalizer(mix_and_sources, check_against_regression_data):
bands = [
@@ -294,6 +298,8 @@ def test_misc_param_check():
effects.flanger(shape="fail")
with pytest.raises(ValueError):
effects.flanger(interp="fail")
+ with pytest.raises(ValueError):
+ effects.flanger(phase=phase)
with pytest.raises(ValueError):
effects.emphasis(1, 1, _type="fail")

original_message: 100% code-cov


message: updating docs. not done yet

diff:
@@ -160,6 +160,8 @@ class AudioSignal(object):
path_to_input_file (``str``): Path to the input file. ``None`` if this AudioSignal never
loaded a file, i.e., initialized with a ``np.ndarray``.
label (``str``): A user-definable label for this :class:`AudioSignal` object.
+ applied_effects (``list`` of ``str``): Effects applied to this :class:`AudioSignal`
+ object. For more information, see apply_effects.
"""
@@ -1656,18 +1658,9 @@ class AudioSignal(object):
def apply_effects(self, reset=True, overwrite=False):
"""
- Applies all effects in the AudioSignal's effects chains using the audio data of the calling
- AudioSignal. If reset=True, the current effects chains will be deleted following applying
- them to the audio signal. If overwrite=True, the calling AudioSignal's audio_data will
- be overwritten and the stft_data will be deleted.
-
- FFmpeg effects will be applied AFTER SoX effects. The effects time_stretch and pitch_shift
- are SoX effects. All others are FFmpeg effects.
-
- If no effects have been added to the AudioSignal's effects chains, then apply_effects
- will return itself.
-
- This will add a time stretch effect to the audio signal's effects chains.
+ Effects are stored in an "effects chain", which refers to a queue of effects that will be
+ applied to an AudioSignal object when `apply_effects` is called. We can add an effect to
+ the effects chain by using an effects hook, such as `time_stretch`.
>>> signal.duration
10.0
@@ -1681,18 +1674,46 @@ class AudioSignal(object):
>>> new_signal = signal.apply_effects()
>>> new_signal.duration
5.0
+ >>> # This doesn't change the original signal
+ >>> signal.duration
+ 10.0
+
+ Using `apply_effects` will clear out the current effects chain. This behavior can be
+ avoided by setting `reset` to False. Applied effects can be found in `effects_applied`:
+
+ >>> another_signal = signal.apply_effects()
+ >>> another_signal.duration
+ 10.0
+ >>> new_signal.effects_applied
+ ["time_stretch"]
+
+ To clear out the current effects chain without applying effect, use `reset_effects_chain`.
+ It will not reverse effects already applied!
+
+ If `apply_effects` is called with empty effects chain, then it returns itself.
+
+ >>> another_signal == signal
+ True
You can also chain effects together. This will add a tremolo effect followed by a high pass
- effect to the AudioSignal's effects chains:
+ effect to the AudioSignal's effects chains: (Order Matters!)
>>> audio_signal.tremolo(5, .6).high_pass(12000)
- Using overwrite here change the audio data of audio_signal, rather than create a new signal.
+ Using overwrite here, we change the audio data of audio_signal, rather than create a new signal.
>>> audio_signal.apply_effects(overwrite=True)
+ >>> audio_signal.effects_applied
+ ["tremolo", "highpass"]
+
+ FFmpeg effects will be applied AFTER SoX effects, irregardless of the order the hooks
+ are applied. The effects `time_stretch` and `pitch_shift` are SoX effects. All others
+ are FFmpeg effects. For example, the two statements will result in the same altered signal:
- All effects applied will stored in `self.effects_applied`, a list of FilterFunctions
- in the order that they were applied.
+ >>> signal_1 = audio_signal.pitch_shift(4).tremolo(5, .6).apply_effects()
+ >>> signal_2 = audio_signal.tremolo(5, .6).pitch_shift(4).apply_effects()
+ >>> signal_1.effects_applied == signal_2.effects_applied
+ True
Args:
reset (bool): If True, clears out all effects in effect chains following applying the effects.
@@ -1729,6 +1750,8 @@ class AudioSignal(object):
Is the same as
>>> signal.make_effect("time_stretch", factor=1.5)
+ This effect won't be applied until you call `apply_effect`!
+
Args:
effect (str): Function name of desired effect hook of the AudioSignal
**kwargs: Additional parameters for given effect.
@@ -1746,7 +1769,9 @@ class AudioSignal(object):
def reset_effects_chain(self):
"""
- Clears effects chains of AudioSignal
+ Clears effects chain of AudioSignal.
+
+ This will not remove effects that have already been applied!
"""
self._ffmpeg_effects_chain = []
self._sox_effects_chain = []
@@ -1760,7 +1785,10 @@ class AudioSignal(object):
This is a SoX effect. Please see
https://pysox.readthedocs.io/en/latest/_modules/sox/transform.html#Transformer.tempo
- for more information.
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
factor (float): Scaling factor for tempo change. Must be positive.
Returns:
@@ -1776,7 +1804,9 @@ class AudioSignal(object):
This is a SoX effect. Please see:
https://pysox.readthedocs.io/en/latest/_modules/sox/transform.html#Transformer.pitch
- For more information.
+ For details.
+
+ This effect won't be applied until you call `apply_effect`!
Args:
shift (float): The number of semitones to shift the audio.
@@ -1793,7 +1823,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see:
https://ffmpeg.org/ffmpeg-all.html#lowpass
- for details
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
freq (float): Threshold for low pass. Should be positive
poles (int): Number of poles. should be either 1 or 2
@@ -1817,7 +1850,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see:
https://ffmpeg.org/ffmpeg-all.html#highpass
- for details
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
freq (float): Threshold for high pass. Should be positive scalar
poles (int): Number of poles. should be either 1 or 2
@@ -1841,7 +1877,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#tremolo
- for details
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
mod_freq (float): Modulation frequency. Must be between .1 and 20000.
mod_depth (float): Modulation depth. Must be between 0 and 1.
@@ -1857,7 +1896,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#vibrato
- for details
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
mod_freq (float): Modulation frequency. Must be between .1 and 20000.
mod_depth (float): Modulation depth. Must be between 0 and 1.
@@ -1873,6 +1915,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#chorus
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
delays (list of float): delays in ms. Typical Delay is 40ms-6ms
decays (list of float): decays. Must be between 0 and 1
@@ -1895,6 +1941,9 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#aphaser
for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
in_gain (float): Proportion of input gain. Must be between 0 and 1
out_gain (float): Proportion of output gain. Must be between 0 and 1.
@@ -1918,6 +1967,9 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#flanger
for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
delay (float): Base delay in ms between original signal and copy.
Must be between 0 and 30.
@@ -1947,6 +1999,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#aemphasis
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
level_in (float): Input gain
level_out (float): Output gain
@@ -1979,6 +2035,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#acompressor
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
level_in (float): Input Gain
mode (str): Mode of compressor operation. Can either be "upward" or "downward".
@@ -2011,6 +2071,10 @@ class AudioSignal(object):
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#anequalizer
+ for details.
+
+ This effect won't be applied until you call `apply_effect`!
+
Args:
bands: A list of dictionaries, for each band. The required values for each dictionary:
'chn': List of channel numbers to apply filter. Must be list of ints.

diff:
@@ -115,9 +115,9 @@ class SoXFilter(FilterFunction):
"""
def __init__(self, _filter, **filter_kwargs):
self.filter = _filter
- if _filter == "tempo":
+ if _filter == "time_stretch":
self.func = lambda tfm: tfm.tempo(**filter_kwargs)
- elif _filter == "pitch":
+ elif _filter == "pitch_shift":
self.func = lambda tfm: tfm.pitch(**filter_kwargs)
else:
raise ValueError("Unknown SoX effect passed")
@@ -175,7 +175,7 @@ def time_stretch(factor):
if not np.issubdtype(type(factor), np.number) or factor <= 0:
raise ValueError("stretch_factor must be a positve scalar")
- return SoXFilter("tempo", factor=factor)
+ return SoXFilter("time_stretch", factor=factor)
def pitch_shift(shift):
@@ -199,7 +199,7 @@ def pitch_shift(shift):
if not np.issubdtype(type(shift), np.integer):
raise ValueError("shift must be an integer.")
- return SoXFilter("pitch", n_semitones=shift)
+ return SoXFilter("pitch_shift", n_semitones=shift)
def _pass_arg_check(freq, poles, width_type, width):

original_message: updating docs. not done yet


message: fixed chorus

diff:
# ---
# jupyter:
# jupytext:
-# formats: ipynb,py
+# formats: py,ipynb
# text_representation:
# extension: .py
# format_name: light
@@ -35,14 +35,16 @@ nussl.utils.visualize_spectrogram(mix, y_axis="log")
# applied to an AudioSignal object when `apply_effects` is called. We can add an effect to
# the effects chain by using an effects hook, such as `time_stretch`.
+# + tags=[]
print(mix.signal_duration)
mix.time_stretch(0.5)
print(mix.signal_duration)
+# -
# However, the signal's duration hasn't changed! You will need to call apply_effects to make
# the changes in the signal's effects chains.
-# +
+# + tags=[]
new_signal = mix.apply_effects()
print(new_signal.signal_duration)
@@ -53,9 +55,11 @@ print(mix.signal_duration)
# Using `apply_effects` will clear out the current effects chain. This behavior can be avoided by setting `reset` to False. Applied effects can be found in `effects_applied`:
#
+# + tags=[]
another_signal = mix.apply_effects()
print(another_signal.signal_duration)
print(new_signal.effects_applied)
+# -
# To clear out the current effects chain without applying effect, use `reset_effects_chain`.
# It will not reverse effects already applied!
@@ -193,14 +197,16 @@ nussl.utils.visualize_spectrogram(col_mix, y_axis="log")
# ```
# AudioSignal.chorus(delays, decays, speeds, depths, in_gain=.4, out_gain=.4)
# ```
-# Applies a chorus filter to the audio signal. `decays`, `delays`, `speeds`, and `depths` are lists, where `decays[i]`, `delays[i]`, `speeds[i]`, and `depths[i]` denote the decay, delay, speed, and depth for chorus filter `i`. Delays are in milliseconds, while decay, speed, and depths must be between 0 and 1, as they are factors of the original signal. [Citation needed] `in_gain` and `out_gain` denote input and output gain respectively.
+# Applies a chorus filter to the audio signal. `decays`, `delays`, `speeds`, and `depths` are lists, where `decays[i]`, `delays[i]`, `speeds[i]`, and `depths[i]` denote the decay, delay, speed, and depth for chorus filter `i`. Delays are in milliseconds, while decay, speed, and depths must be between 0 and 1, as they are factors of the original signal. `in_gain` and `out_gain` denote input and output gain respectively.
## Apply two chorus filters
-delays = [40, 50]
-decays = [.9, .8]
-speeds = [.9, .4]
-depths = [.4, .2]
-chor_mix = mix.chorus(delays, decays, speeds, depths).apply_effects()
+delays = [40, 60]
+decays = [.7, .2]
+speeds = [.9, .8]
+depths = [.8, .6]
+in_gain = 1
+out_gain = 1
+chor_mix = mix.chorus(delays, decays, speeds, depths, in_gain, out_gain).apply_effects()
chor_mix.embed_audio()
nussl.utils.visualize_spectrogram(chor_mix, y_axis="log")

original_message: fixed chorus


message: using prem's sox bindings

diff:
@@ -24,7 +24,7 @@ import numpy as np
import warnings
import ffmpeg
import tempfile
-import sox
+import soxbindings as sox
from .constants import LEVEL_MIN, LEVEL_MAX
from .utils import _close_temp_files

diff:
@@ -19,3 +19,4 @@ importlib_resources
pyloudnorm
sox==1.4.0b0
numba==0.48
+soxbindings

original_message: using prem's sox bindings


message: optional sox import

diff:
@@ -24,7 +24,10 @@ import numpy as np
import warnings
import ffmpeg
import tempfile
+try:
import soxbindings as sox
+except ImportError:
+ import sox
from .constants import LEVEL_MIN, LEVEL_MAX
from .utils import _close_temp_files

original_message: optional sox import


message: Filter function __str__ added

old_path: .coveragerc

new_path: .coveragerc

diff:
@@ -8,3 +8,4 @@ exclude_lines =
except Exception:
vamp_imported = False
import sox
+ pass
\ No newline at end of file

diff:
@@ -35,8 +35,17 @@ from .utils import _close_temp_files
class FilterFunction:
"""
The FilterFunction class is an abstract class for functions that take
- audio processing streams, such as ffmpeg-python and pysndfx
+ audio processing streams, such as ffmpeg-python and pysox.
+
+ Don't call this class. It will not do anything.
"""
+ def __init__(self, _filter, **kwargs):
+ self.filter = _filter
+ self.params = kwargs
+
+ def __str__(self):
+ params = ",".join(f"{p}={v}" for p, v in self.params.items())
+ return f"{self.filter} with params {params}"
def __call__(self, stream):
return self.func(stream)
@@ -52,7 +61,7 @@ class FFmpegFilter(FilterFunction):
AudioSignal object.
"""
def __init__(self, _filter, **filter_kwargs):
- self.filter = _filter
+ super().__init__(_filter, **filter_kwargs)
self.func = lambda stream: stream.filter(_filter, **filter_kwargs)
@@ -98,8 +107,8 @@ def apply_effects_ffmpeg(audio_signal, filters, silent=False):
augmented_signal = AudioSignal(path_to_input_file=out_tempfile.name)
augmented_signal.label = audio_signal.label
- augmented_signal.effects_applied = (augmented_signal.effects_applied
- + [f.filter for f in filters])
+ augmented_signal.effects_applied = (audio_signal.effects_applied
+ + [str(f) for f in filters])
return augmented_signal
@@ -110,7 +119,7 @@ class SoXFilter(FilterFunction):
AudioSignal object.
"""
def __init__(self, _filter, **filter_kwargs):
- self.filter = _filter
+ super().__init__(_filter, **filter_kwargs)
if _filter == "time_stretch":
self.func = lambda tfm: tfm.tempo(**filter_kwargs)
elif _filter == "pitch_shift":
@@ -145,8 +154,8 @@ def apply_effects_sox(audio_signal, filters):
augmented_signal = AudioSignal(audio_data_array=np.transpose(augmented_data))
augmented_signal.label = audio_signal.label
- augmented_signal.effects_applied = (augmented_signal.effects_applied
- + [f.filter for f in filters])
+ augmented_signal.effects_applied = (audio_signal.effects_applied
+ + [str(f) for f in filters])
return augmented_signal

diff:
@@ -414,8 +414,3 @@ def test_make_effect(mix_and_sources, check_against_regression_data):
with pytest.raises(AudioSignalException):
signal.make_effect("fail")
-def test_filter_function_pass():
- # this test is for 100% coverage
- function = effects.FilterFunction()
- dummy_item = None
- function.func(dummy_item)

original_message: Filter function __str__ added


message: effects ordering changed

diff:
# extension: .py
# format_name: light
# format_version: '1.5'
-# jupytext_version: 1.5.1
+# jupytext_version: 1.5.0
# kernelspec:
# display_name: Python 3
# language: python
@@ -60,10 +60,11 @@ print(mix.signal_duration)
# + tags=[]
another_signal = mix.apply_effects()
print(another_signal.signal_duration)
+
print(new_signal.effects_applied)
# -
-# To clear out the current effects chain without applying effect, use `reset_effects_chain`.
+# To clear out the current effects chain without applying effects, use `reset_effects_chain`.
# It will not reverse effects already applied!
#
# If `apply_effects` is called with empty effects chain, then it returns itself.
@@ -80,12 +81,11 @@ mix.tremolo(5, .6).high_pass(12000)
mix.apply_effects(overwrite=True)
mix.effects_applied
-# FFmpeg effects will be applied AFTER SoX effects, irregardless of the order the hooks
-# are applied. The effects `time_stretch` and `pitch_shift` are SoX effects. All others
-# are FFmpeg effects. For example, the two statements will result in the same altered signal:
+# If `user_order=False`, FFmpeg effects will be applied AFTER SoX effects, irregardless of the order the hooks
+# are applied. One may want to disable user order for a significant speed up when applying multiple effects. The effects `time_stretch` and `pitch_shift` are SoX effects. All others are FFmpeg effects. For example, the two statements will result in the same altered signal:
-signal_1 = mix.pitch_shift(4).tremolo(5, .6).apply_effects()
-signal_2 = mix.tremolo(5, .6).pitch_shift(4).apply_effects()
+signal_1 = mix.pitch_shift(4).tremolo(5, .6).apply_effects(user_order=False)
+signal_2 = mix.tremolo(5, .6).pitch_shift(4).apply_effects(user_order=False)
signal_1.effects_applied == signal_2.effects_applied
# Now we will list the effects provided in nussl.
@@ -164,12 +164,12 @@ nussl.utils.visualize_spectrogram(high_pmix, y_axis="log")
# Applys tremolo/vibrato filter on the audio signal, with a modulation frequency of `mod_freq` Hz, and modulation amplitude of `mod_depth`.
# Tremolo
-trem_mix = mix.tremolo(5, .4).apply_effects()
+trem_mix = mix.tremolo(4, .4).apply_effects()
trem_mix.embed_audio()
nussl.utils.visualize_spectrogram(trem_mix, y_axis="log")
# Vibrato
-vib_mix = mix.vibrato(8, .7).apply_effects()
+vib_mix = mix.vibrato(4, .4).apply_effects()
vib_mix.embed_audio()
nussl.utils.visualize_spectrogram(vib_mix, y_axis="log")
@@ -208,7 +208,7 @@ nussl.utils.visualize_spectrogram(col_mix, y_axis="log")
## Apply two chorus filters
delays = [40, 60]
-decays = [.7, .2]
+decays = [.4, .2]
speeds = [.9, .8]
depths = [.8, .6]
in_gain = 1

diff:
@@ -201,8 +201,7 @@ class AudioSignal(object):
self.stft_params = stft_params
# Effects
- self._ffmpeg_effects_chain = []
- self._sox_effects_chain = []
+ self._effects_chain = []
self.effects_applied = []
def __str__(self):
@@ -1656,7 +1655,7 @@ class AudioSignal(object):
# Effect Hooks #
##################################
- def apply_effects(self, reset=True, overwrite=False):
+ def apply_effects(self, reset=True, overwrite=False, user_order=True):
"""
Effects are stored in an "effects chain", which refers to a queue of effects that will be
applied to an AudioSignal object when `apply_effects` is called. We can add an effect to
@@ -1706,12 +1705,15 @@ class AudioSignal(object):
>>> audio_signal.effects_applied
["tremolo", "highpass"]
- FFmpeg effects will be applied AFTER SoX effects, irregardless of the order the hooks
- are applied. The effects `time_stretch` and `pitch_shift` are SoX effects. All others
- are FFmpeg effects. For example, the two statements will result in the same altered signal:
+ If user_order is false, FFmpeg effects will be applied AFTER SoX effects, irregardless of
+ the order the hooks are applied. The effects `time_stretch` and `pitch_shift` are SoX effects.
+ All others are FFmpeg effects. This may be done for speed, as applying all FFmpeg effects without
+ interuption will be faster than being interrupted with a SoX effect.
- >>> signal_1 = audio_signal.pitch_shift(4).tremolo(5, .6).apply_effects()
- >>> signal_2 = audio_signal.tremolo(5, .6).pitch_shift(4).apply_effects()
+ For example, the two statements will result in the same altered signal:
+
+ >>> signal_1 = audio_signal.pitch_shift(4).tremolo(5, .6).apply_effects(user_order=False)
+ >>> signal_2 = audio_signal.tremolo(5, .6).pitch_shift(4).apply_effects(user_order=False)
>>> signal_1.effects_applied == signal_2.effects_applied
True
@@ -1720,16 +1722,18 @@ class AudioSignal(object):
Default=True
overwrite (bool): If True, overwrites existing audio_data in AudioSignal. Default=False
Also clears out stft_data.
+ user_order (bool): If True, applied effects in the user provided order. If False, applies SoX
+ effects first, then FFmpeg effects.
Returns:
self or new_signal (AudioSignal): If overwrite=True, then returns initially AudioSignal
with edited audio_data. Otherwise, returns a new AudioSignal new_signal.
"""
- new_signal = self
- if self._sox_effects_chain:
- new_signal = effects.apply_effects_sox(new_signal, self._sox_effects_chain)
- if self._ffmpeg_effects_chain:
- new_signal = effects.apply_effects_ffmpeg(new_signal, self._ffmpeg_effects_chain)
+
+ if user_order:
+ new_signal = self._apply_user_ordered_effects()
+ else:
+ new_signal = self._apply_sox_ffmpeg_ordered_effects()
if reset:
self.reset_effects_chain()
@@ -1741,6 +1745,40 @@ class AudioSignal(object):
return new_signal
+ def _apply_user_ordered_effects(self):
+ new_signal = self
+ next_chain = []
+ idx = 0
+ while idx < len(self._effects_chain) or next_chain:
+ if idx < len(self._effects_chain) and (not next_chain
+ or type(next_chain[-1]) == type(self._effects_chain[idx])):
+ next_chain.append(self._effects_chain[idx])
+ idx += 1
+ else:
+ if isinstance(next_chain[0], effects.SoXFilter):
+ new_signal = effects.apply_effects_sox(new_signal, next_chain)
+ elif isinstance(next_chain[0], effects.FFmpegFilter):
+ new_signal = effects.apply_effects_ffmpeg(new_signal, next_chain)
+ next_chain = []
+ return new_signal
+
+ def _apply_sox_ffmpeg_ordered_effects(self):
+ new_signal = self
+ sox_effects_chain = []
+ ffmpeg_effects_chain = []
+ for f in self._effects_chain:
+ if isinstance(f, effects.FFmpegFilter):
+ ffmpeg_effects_chain.append(f)
+ elif isinstance(f, effects.SoXFilter):
+ sox_effects_chain.append(f)
+
+ if sox_effects_chain:
+ new_signal = effects.apply_effects_sox(new_signal, sox_effects_chain)
+ if ffmpeg_effects_chain:
+ new_signal = effects.apply_effects_ffmpeg(new_signal, ffmpeg_effects_chain)
+
+ return new_signal
+
def make_effect(self, effect, **kwargs):
"""
Calls another effect hook. An example:
@@ -1773,8 +1811,7 @@ class AudioSignal(object):
This will not remove effects that have already been applied!
"""
- self._ffmpeg_effects_chain = []
- self._sox_effects_chain = []
+ self._effects_chain = []
return self
def time_stretch(self, factor):
@@ -1794,7 +1831,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._sox_effects_chain.append(effects.time_stretch(factor))
+ self._effects_chain.append(effects.time_stretch(factor))
return self
def pitch_shift(self, shift):
@@ -1814,7 +1851,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._sox_effects_chain.append(effects.pitch_shift(shift))
+ self._effects_chain.append(effects.pitch_shift(shift))
return self
def low_pass(self, freq, poles=2, width_type="h", width=.707):
@@ -1840,7 +1877,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.low_pass(freq, poles=poles,
+ self._effects_chain.append(effects.low_pass(freq, poles=poles,
width_type=width_type, width=width))
return self
@@ -1867,7 +1904,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.high_pass(freq, poles=poles,
+ self._effects_chain.append(effects.high_pass(freq, poles=poles,
width_type=width_type, width=width))
return self
@@ -1887,7 +1924,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.tremolo(mod_freq, mod_depth))
+ self._effects_chain.append(effects.tremolo(mod_freq, mod_depth))
return self
def vibrato(self, mod_freq, mod_depth):
@@ -1906,7 +1943,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.vibrato(mod_freq, mod_depth))
+ self._effects_chain.append(effects.vibrato(mod_freq, mod_depth))
return self
def chorus(self, delays, decays, speeds, depths, in_gain=.4, out_gain=.4):
@@ -1929,7 +1966,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.chorus(delays, decays,
+ self._effects_chain.append(effects.chorus(delays, decays,
speeds, depths, in_gain=.4, out_gain=.4))
return self
@@ -1956,7 +1993,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.phaser(in_gain=.4, out_gain=.74, delay=3,
+ self._effects_chain.append(effects.phaser(in_gain=.4, out_gain=.74, delay=3,
decay=.4, speed=.5, _type="triangular"))
return self
@@ -1984,7 +2021,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.flanger(delay=0, depth=2, regen=0, width=71,
+ self._effects_chain.append(effects.flanger(delay=0, depth=2, regen=0, width=71,
speed=.5, phase=25, shape="sinusoidal", interp="linear"))
return self
@@ -2023,7 +2060,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.emphasis(level_in,
+ self._effects_chain.append(effects.emphasis(level_in,
level_out, _type="col", mode='production'))
return self
@@ -2060,7 +2097,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.compressor(level_in, mode="downward", reduction_ratio=2,
+ self._effects_chain.append(effects.compressor(level_in, mode="downward", reduction_ratio=2,
attack=20, release=250, makeup=1, knee=2.8284, link="average",
detection="rms", mix=1, threshold=.125))
return self
@@ -2088,7 +2125,7 @@ class AudioSignal(object):
Returns:
self: Inital AudioSignal with updated effect chains
"""
- self._ffmpeg_effects_chain.append(effects.equalizer(bands))
+ self._effects_chain.append(effects.equalizer(bands))
return self

diff:
@@ -45,7 +45,7 @@ class FilterFunction:
def __str__(self):
params = ",".join(f"{p}={v}" for p, v in self.params.items())
- return f"{self.filter} with params {params}"
+ return f"{self.filter} (params: {params})"
def __call__(self, stream):
return self.func(stream)

diff:
@@ -364,18 +364,22 @@ def test_hooks(mix_and_sources, check_against_regression_data):
}])
)
- assert len(signal._ffmpeg_effects_chain) == 10
- assert len(signal._sox_effects_chain) == 2
+ assert len(signal._effects_chain) == 12
- augmented_signal = signal.apply_effects(reset=False)
+ augmented_signal = signal.apply_effects(reset=False, user_order=False)
+ assert len(signal._effects_chain) == 12
+ assert len(augmented_signal.effects_applied) == 12
+ assert len(augmented_signal._effects_chain) == 0
reg_path = path.join(REGRESSION_PATH, "hooks.json")
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
- augmented_signal.time_stretch(.7).apply_effects(overwrite=True)
+ augmented_signal.time_stretch(.7).apply_effects(overwrite=True, user_order=False)
- assert len(augmented_signal._ffmpeg_effects_chain) == 0
- assert len(augmented_signal._sox_effects_chain) == 0
+ assert len(augmented_signal._effects_chain) == 0
+ assert len(augmented_signal.effects_applied) == 25
+
+ assert str(augmented_signal.effects_applied[-1]) == "time_stretch (params: factor=0.7)"
equal_signal = augmented_signal.apply_effects()
assert equal_signal == augmented_signal
@@ -406,7 +410,7 @@ def test_make_effect(mix_and_sources, check_against_regression_data):
}])
)
signal.apply_effects(reset=False)
- augmented_signal = signal.apply_effects(reset=False, overwrite=True)
+ augmented_signal = signal.apply_effects(reset=False, overwrite=True, user_order=False)
reg_path = path.join(REGRESSION_PATH, "hooks.json")
# This should result in the same signal in test_hooks
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
@@ -414,3 +418,15 @@ def test_make_effect(mix_and_sources, check_against_regression_data):
with pytest.raises(AudioSignalException):
signal.make_effect("fail")
+
+def test_order(mix_and_sources):
+ mix, _ = mix_and_sources
+ mix.reset_effects_chain().time_stretch(1.5).tremolo(.3,.4).pitch_shift(5).vibrato(.3, .4)
+ ## User order
+ signal0 = mix.apply_effects(reset=False)
+
+ ## SoX-FFmpeg Order
+ signal1 = mix.apply_effects(reset=False, user_order=False)
+
+ assert signal0.effects_applied != signal1.effects_applied
+ assert len(signal0.effects_applied) == len(signal1.effects_applied)

original_message: effects ordering changed


message: cleaned up _apply_user_ordered_effects

diff:
@@ -1787,19 +1787,18 @@ class AudioSignal(object):
def _apply_user_ordered_effects(self):
new_signal = self
- next_chain = []
idx = 0
- while idx < len(self._effects_chain) or next_chain:
- if idx < len(self._effects_chain) and (not next_chain
- or type(next_chain[-1]) == type(self._effects_chain[idx])):
- next_chain.append(self._effects_chain[idx])
- idx += 1
- else:
+ jdx = 0
+ while idx < len(self._effects_chain):
+ jdx += 1
+ if jdx == len(self._effects_chain) or (type(self._effects_chain[idx])
+ != type(self._effects_chain[jdx])):
+ next_chain = self._effects_chain[idx:jdx]
if isinstance(next_chain[0], effects.SoXFilter):
new_signal = effects.apply_effects_sox(new_signal, next_chain)
elif isinstance(next_chain[0], effects.FFmpegFilter):
new_signal = effects.apply_effects_ffmpeg(new_signal, next_chain)
- next_chain = []
+ idx = jdx
return new_signal
def _apply_sox_ffmpeg_ordered_effects(self):

original_message: cleaned up _apply_user_ordered_effects


message: typos in docs

diff:
@@ -1751,7 +1751,7 @@ class AudioSignal(object):
>>> signal_1.effects_applied == signal_2.effects_applied
True
- Refer to the spefic documentation for each effec to determine whether it is a SoX effect
+ Refer to the spefic documentation for each effect to determine whether it is a SoX effect
or an FFmpeg effect.
See Also:
@@ -2140,8 +2140,6 @@ class AudioSignal(object):
sounds from such a medium, a de-emphasis filter is used to de-boost boosted
frequencies.
- It is recommended that users use `AudioSignal.emphasis` rather than this function.
-
This is a FFmpeg effect. Please see
https://ffmpeg.org/ffmpeg-all.html#aemphasis
for details.

original_message: typos in docs


message: effect_chains and effects_applied are now lists of filter functions

diff:
@@ -21,7 +21,7 @@ from . import effects
__all__ = ['AudioSignal', 'STFTParams', 'AudioSignalException']
STFTParams = namedtuple('STFTParams',
- ['window_length', 'hop_length', 'window_type'],
+ ['window_length', 'hop_length', 'window_type']
)
STFTParams.__new__.__defaults__ = (None,) * len(STFTParams._fields)
"""
@@ -160,8 +160,10 @@ class AudioSignal(object):
path_to_input_file (``str``): Path to the input file. ``None`` if this AudioSignal never
loaded a file, i.e., initialized with a ``np.ndarray``.
label (``str``): A user-definable label for this :class:`AudioSignal` object.
- applied_effects (``list`` of ``str``): Effects applied to this :class:`AudioSignal`
- object. For more information, see apply_effects.
+ applied_effects (``list`` of ``effects.FilterFunction``): Effects applied to this
+ :class:`AudioSignal` object. For more information, see apply_effects.
+ effects_chain (``list`` of ``effects.FilterFunction``): Effects queues to be applied to
+ this :class:`AudioSignal` object. For more information, see apply_effects.
"""
@@ -202,7 +204,7 @@ class AudioSignal(object):
# Effects
self._effects_chain = []
- self.effects_applied = []
+ self._effects_applied = []
def __str__(self):
dur = f'{self.signal_duration:0.3f}' if self.signal_duration else '[unknown]'
@@ -714,6 +716,28 @@ class AudioSignal(object):
'because self.stft_data is None')
return 20 * np.log10(np.abs(self.stft_data) + 1e-8)
+ @property
+ def effects_chain(self):
+ """
+ (``list`` of ``nussl.core.FilterFunction``): Returns a copy of the AudioSignal's
+ effect chain.
+
+ See Also:
+ * :func:`apply_effects`
+ """
+ return self._effects_chain.copy()
+
+ @property
+ def effects_applied(self):
+ """
+ (``list`` of ``nussl.core.FilterFunction``): Returns a list of effects applied to
+ the AudioSignal.
+
+ See Also:
+ * :func:`apply_effects`
+ """
+ return self._effects_applied.copy()
+
##################################################
# I/O
##################################################
@@ -1135,6 +1159,7 @@ class AudioSignal(object):
return ipd, ild
+
##################################################
# Utilities
##################################################
@@ -1672,7 +1697,7 @@ class AudioSignal(object):
Notes:
The effects will be added in the order that they are added to the effects chain, unless
- `user_order=False`, in case the order is not gauranteed to be preserved. Setting
+ `user_order=False`, in case the order is not guaranteed to be preserved. Setting
`user_order=False` will apply all SoX effects first, then FFMpeg effects, which can
sped up processing time by ~30% in our experiments.
@@ -1698,24 +1723,40 @@ class AudioSignal(object):
>>> signal.signal_duration
10.0
+ You can find this effect in the AudioSignal's effects chain.
+
+ >>> effect = signal.effects_chain[0]
+ >>> str(effect)
+ "time_stretch (params: {factor=0.5})"
+
However, the signal's duration hasn't changed! You will need to call `apply_effects()`
- to apply the changes in the signal's effects chains.
+ to apply the changes in the signal's effects chains. Applied effects can be found in
+ `effects_applied`.
>>> new_signal = signal.apply_effects()
>>> new_signal.signal_duration
5.0
+ >>> str(new_signal.effects_applied[0])
+ "time_stretch (params: {factor=0.5})"
+
>>> # This doesn't change the original signal
>>> signal.signal_duration
10.0
+ You can iterate through effects_chain to use the properties of FilterFunction
+ objects as arguments to `make_effect`:
+
+ >>> for effect in signal1.effects_applied:
+ >>> filter_ = effect.filter
+ >>> params = effect.params
+ >>> signal2.make_effect(filter_, **params)
+
Using `apply_effects()` will clear out the current effects chain. This behavior can be
- avoided by setting `reset` to False. Applied effects can be found in `effects_applied`:
+ avoided by setting `reset` to False.
>>> another_signal = signal.apply_effects()
>>> another_signal.signal_duration
10.0
- >>> new_signal.effects_applied
- ["time_stretch"]
To clear out the current effects chain without applying effect, use
`reset_effects_chain()`. It will not revert effects already applied (i.e., your audio
@@ -1779,7 +1820,7 @@ class AudioSignal(object):
self.reset_effects_chain()
if overwrite:
self.audio_data = new_signal.audio_data
- self.effects_applied += new_signal.effects_applied
+ self._effects_applied += new_signal.effects_applied
self.stft_data = None
return self
@@ -1828,6 +1869,14 @@ class AudioSignal(object):
Is the same as
>>> signal.make_effect("time_stretch", factor=1.5)
+ The attributes of a FilterFunction in the lists effects_applied or effects_chain may
+ used with `make_effect`.
+
+ >>> for effect in signal1.effects_applied:
+ >>> filter_ = effect.filter
+ >>> params = effect.params
+ >>> signal2.make_effect(filter_, **params)
+
Notes:
This effect won't be applied until you call `apply_effect()`!
@@ -1888,10 +1937,11 @@ class AudioSignal(object):
self._effects_chain.append(effects.time_stretch(factor))
return self
- def pitch_shift(self, shift):
+ def pitch_shift(self, n_semitones):
"""
Add pitch shift effect to AudioSignal's effect chain.
- A positive shift will raise the pitch of the signal by `shift` semitones.
+ A positive shift will raise the pitch of the signal by `n_semitones`
+ semitones.
This is a SoX effect. Please see:
https://pysox.readthedocs.io/en/latest/_modules/sox/transform.html#Transformer.pitch
@@ -1901,7 +1951,7 @@ class AudioSignal(object):
This effect won't be applied until you call `apply_effect()`!
Args:
- shift (float): The number of semitones to shift the audio.
+ n_semitones (integer): The number of semitones to shift the audio.
Positive values increases the frequency of the signal
Returns:
self: Initial AudioSignal with updated effect chains
@@ -1911,7 +1961,7 @@ class AudioSignal(object):
* :func:`make_effect`: Syntactic sugar for adding an effect to the chain by name.
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
- self._effects_chain.append(effects.pitch_shift(shift))
+ self._effects_chain.append(effects.pitch_shift(n_semitones))
return self
def low_pass(self, freq, poles=2, width_type="h", width=0.707):

diff:
@@ -24,6 +24,7 @@ import numpy as np
import warnings
import ffmpeg
import tempfile
+import copy
try:
import soxbindings as sox
except Exception:
@@ -36,12 +37,17 @@ from .utils import _close_temp_files
class FilterFunction:
"""
The FilterFunction class is an abstract class for functions that take
- audio processing streams, such as ffmpeg-python and pysox.
+ audio processing streams, such as ffmpeg-python and pysox effects.
- Don't call this class. It will not do anything.
+ Don't call this class. It will not do anything. Please use either FFmpegFilter
+ or SoXFilter.
+
+ Attributes:
+ filter: Name of filter used
+ params: Parameters for filter
"""
- def __init__(self, _filter, **kwargs):
- self.filter = _filter
+ def __init__(self, filter_, **kwargs):
+ self.filter = filter_
self.params = kwargs
def __str__(self):
@@ -61,9 +67,10 @@ class FFmpegFilter(FilterFunction):
To use them, apply_effects_ffmpeg can take a list of effects, and apply them onto an
AudioSignal object.
"""
- def __init__(self, _filter, **filter_kwargs):
- super().__init__(_filter, **filter_kwargs)
- self.func = lambda stream: stream.filter(_filter, **filter_kwargs)
+ def __init__(self, filter_, ffmpeg_name=None, **filter_kwargs):
+ super().__init__(filter_, **filter_kwargs)
+ ffmpeg_name = self.filter if ffmpeg_name is None else ffmpeg_name
+ self.func = lambda stream: stream.filter(ffmpeg_name, **filter_kwargs)
def apply_effects_ffmpeg(audio_signal, filters, silent=False):
@@ -99,8 +106,8 @@ def apply_effects_ffmpeg(audio_signal, filters, silent=False):
tmpfiles.append(curr_tempfile)
stream = ffmpeg.input(curr_tempfile.name, **input_args)
- for _filter in filters:
- stream = _filter(stream)
+ for filter_ in filters:
+ stream = filter_(stream)
(stream
.output(out_tempfile.name)
.overwrite_output()
@@ -109,22 +116,20 @@ def apply_effects_ffmpeg(audio_signal, filters, silent=False):
augmented_signal = AudioSignal(path_to_input_file=out_tempfile.name)
augmented_signal.label = audio_signal.label
- augmented_signal.effects_applied = (audio_signal.effects_applied
- + [str(f) for f in filters])
+ augmented_signal._effects_applied = audio_signal.effects_applied + filters
return augmented_signal
-
class SoXFilter(FilterFunction):
"""
- SoXFilter is an object returned by FFmpeg effects in effects.py
+ SoXFilter is an object returned by SoX effects in effects.py
To use them, apply_effects_sox can take a list of effects, and apply them onto an
AudioSignal object.
"""
- def __init__(self, _filter, **filter_kwargs):
- super().__init__(_filter, **filter_kwargs)
- if _filter == "time_stretch":
+ def __init__(self, filter_, **filter_kwargs):
+ super().__init__(filter_, **filter_kwargs)
+ if filter_== "time_stretch":
self.func = lambda tfm: tfm.tempo(**filter_kwargs)
- elif _filter == "pitch_shift":
+ elif filter_== "pitch_shift":
self.func = lambda tfm: tfm.pitch(**filter_kwargs)
else:
raise ValueError("Unknown SoX effect passed")
@@ -147,8 +152,8 @@ def apply_effects_sox(audio_signal, filters):
audio_data = audio_signal.audio_data
tfm = sox.Transformer()
- for _filter in filters:
- tfm = _filter(tfm)
+ for filter_ in filters:
+ tfm = filter_(tfm)
augmented_data = tfm.build_array(
input_array=np.transpose(audio_data),
sample_rate_in=audio_signal.sample_rate
@@ -156,8 +161,7 @@ def apply_effects_sox(audio_signal, filters):
augmented_signal = AudioSignal(audio_data_array=np.transpose(augmented_data))
augmented_signal.label = audio_signal.label
- augmented_signal.effects_applied = (audio_signal.effects_applied
- + [str(f) for f in filters])
+ augmented_signal._effects_applied = audio_signal.effects_applied + filters
return augmented_signal
@@ -187,11 +191,11 @@ def time_stretch(factor, **kwargs):
return SoXFilter("time_stretch", factor=factor, **kwargs)
-def pitch_shift(shift, **kwargs):
+def pitch_shift(n_semitones, **kwargs):
"""
Returns a SoXFilter, when called on an pysox stream, will increase the pitch
- of the audio by a number of semitones, denoted in shift. A positive shift will
- raise the pitch of the signal.
+ of the audio by a number of semitones, denoted in n_semitones. A positive n_semitones
+ will raise the pitch of the signal.
It is recommended that users use `AudioSignal.pitch_shift` rather than this function.
@@ -200,15 +204,15 @@ def pitch_shift(shift, **kwargs):
for details.
Args:
- shift (float): The number of semitones to shift the audio.
+ n_semitones (integer): The number of semitones to shift the audio.
Positive values increases the frequency of the signal
Returns:
filter (SoxFilter): A SoXFilter object, to be called on an pysndfx stream
"""
- if not np.issubdtype(type(shift), np.integer):
+ if not np.issubdtype(type(n_semitones), np.integer):
raise ValueError("shift must be an integer.")
- return SoXFilter("pitch_shift", n_semitones=shift, **kwargs)
+ return SoXFilter("pitch_shift", n_semitones=n_semitones, **kwargs)
def _pass_arg_check(freq, poles, width_type, width):
@@ -247,8 +251,11 @@ def low_pass(freq, poles=2, width_type="h", width=.707):
"""
_pass_arg_check(freq, poles, width_type, width)
- return FFmpegFilter("lowpass", f=freq, p=poles,
+ filter_ = FFmpegFilter("low_pass", ffmpeg_name="lowpass", f=freq, p=poles,
t=width_type, w=width)
+ filter_.params = {"freq": freq, "poles": poles,
+ "width_type": width_type, "width":width}
+ return filter_
def high_pass(freq, poles=2, width_type="h", width=.707):
@@ -276,7 +283,11 @@ def high_pass(freq, poles=2, width_type="h", width=.707):
"""
_pass_arg_check(freq, poles, width_type, width)
- return FFmpegFilter("highpass", f=freq, p=poles, t=width_type, w=width)
+ filter_ = FFmpegFilter("high_pass", ffmpeg_name="highpass", f=freq, p=poles,
+ t=width_type, w=width)
+ filter_.params = {"freq": freq, "poles": poles,
+ "width_type": width_type, "width":width}
+ return filter_
def tremolo(mod_freq, mod_depth):
@@ -301,7 +312,9 @@ def tremolo(mod_freq, mod_depth):
if not np.issubdtype(type(mod_depth), np.number) or mod_depth < 0 or mod_depth > 1:
raise ValueError("mod_depth should be positve scalar between 0 and 1.")
- return FFmpegFilter("tremolo", f=mod_freq, d=mod_depth)
+ filter_ = FFmpegFilter("tremolo", f=mod_freq, d=mod_depth)
+ filter_.params = {"mod_freq": mod_freq, "mod_depth": mod_depth}
+ return filter_
def vibrato(mod_freq, mod_depth):
@@ -326,7 +339,9 @@ def vibrato(mod_freq, mod_depth):
if not np.issubdtype(type(mod_depth), np.number) or mod_depth < 0 or mod_depth > 1:
raise ValueError("mod_depth should be positve scalar between 0 and 1.")
- return FFmpegFilter("vibrato", f=mod_freq, d=mod_depth)
+ filter_ = FFmpegFilter("vibrato", f=mod_freq, d=mod_depth)
+ filter_.params = {"mod_freq": mod_freq, "mod_depth": mod_depth}
+ return filter_
def chorus(delays, decays, speeds, depths,
@@ -363,14 +378,20 @@ def chorus(delays, decays, speeds, depths,
!= len(speeds) or len(speeds) != len(depths)):
raise ValueError("Delays, decays, depths, and speeds must all be the same length.")
- delays = make_arglist_ffmpeg(delays)
- speeds = make_arglist_ffmpeg(speeds)
- decays = make_arglist_ffmpeg(decays)
- depths = make_arglist_ffmpeg(depths)
+ ffmpeg_delays = make_arglist_ffmpeg(delays)
+ ffmpeg_speeds = make_arglist_ffmpeg(speeds)
+ ffmpeg_decays = make_arglist_ffmpeg(decays)
+ ffmpeg_depths = make_arglist_ffmpeg(depths)
- return FFmpegFilter("chorus", in_gain=in_gain,
- out_gain=out_gain, delays=delays,
- speeds=speeds, decays=decays, depths=depths)
+ filter_ = FFmpegFilter("chorus", in_gain=in_gain,
+ out_gain=out_gain, delays=ffmpeg_delays,
+ speeds=ffmpeg_speeds, decays=ffmpeg_decays,
+ depths=ffmpeg_depths)
+ filter_.params["delays"] = delays
+ filter_.params["speeds"] = speeds
+ filter_.params["decays"] = decays
+ filter_.params["depths"] = depths
+ return filter_
def phaser(in_gain=.4, out_gain=.74, delay=3,
@@ -412,9 +433,14 @@ def phaser(in_gain=.4, out_gain=.74, delay=3,
"type": type_
}
- return FFmpegFilter("aphaser", in_gain=in_gain, out_gain=out_gain,
+ filter_ = FFmpegFilter("phaser", ffmpeg_name="aphaser", in_gain=in_gain, out_gain=out_gain,
delay=delay, speed=speed, decay=decay, **type_kwarg)
+ filter_.params = copy.deepcopy(filter_.params)
+ del filter_.params["type"]
+ filter_.params["type_"] = type_
+ return filter_
+
def _flanger_argcheck(delay, depth, regen, width,
speed, phase, shape, interp):
@@ -528,8 +554,13 @@ def emphasis(level_in, level_out, type_="col", mode='production'):
'type': type_
}
- return FFmpegFilter("aemphasis", level_in=level_in, level_out=level_out,
- mode=mode, **type_kwarg)
+ filter_ = FFmpegFilter("emphasis", ffmpeg_name="aemphasis", level_in=level_in,
+ level_out=level_out, mode=mode, **type_kwarg)
+
+ filter_.params = copy.deepcopy(filter_.params)
+ del filter_.params["type"]
+ filter_.params["type_"] = type_
+ return filter_
def _compressor_argcheck(level_in, mode, reduction_ratio,
@@ -611,10 +642,14 @@ def compressor(level_in, mode="downward", reduction_ratio=2,
_compressor_argcheck(level_in, mode, reduction_ratio,
attack, release, makeup, knee, link, detection, mix, threshold)
- # TODO: for some reason the mode arg doesn't work in ffmpeg. figure out why
- return FFmpegFilter("acompressor", level_in=level_in,
+ filter_ = FFmpegFilter("compressor", ffmpeg_name="acompressor", level_in=level_in,
ratio=reduction_ratio, attack=attack, release=release, makeup=makeup,
knee=knee, link=link, detection=detection, mix=mix, threshold=threshold)
+ filter_.params = copy.deepcopy(filter_.params)
+ del filter_.params["ratio"]
+ filter_.params["reduction_ratio"] = reduction_ratio
+ return filter_
+
def equalizer(bands):
@@ -662,4 +697,7 @@ def equalizer(bands):
for band in bands
])
- return FFmpegFilter("anequalizer", params=params)
+ filter_ = FFmpegFilter("equalizer", ffmpeg_name="anequalizer", params=params)
+ filter_.params = {"bands": bands}
+ return filter_
+

diff:
@@ -8,6 +8,8 @@ import pytest
REGRESSION_PATH = "tests/core/regression/augmentation"
os.makedirs(REGRESSION_PATH, exist_ok=True)
+# Note: When changing these tests, please do not call `mix_and_sources["mix"].apply_effects`
+# with overwrite=True
def fx_regression(audio_data, reg_path, check_against_regression_data):
scores = {
@@ -364,19 +366,19 @@ def test_hooks(mix_and_sources, check_against_regression_data):
}])
)
- assert len(signal._effects_chain) == 12
+ assert len(signal.effects_chain) == 12
augmented_signal = signal.apply_effects(reset=False, user_order=False)
- assert len(signal._effects_chain) == 12
+ assert len(signal.effects_chain) == 12
assert len(augmented_signal.effects_applied) == 12
- assert len(augmented_signal._effects_chain) == 0
+ assert len(augmented_signal.effects_chain) == 0
reg_path = path.join(REGRESSION_PATH, "hooks.json")
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
augmented_signal.time_stretch(.7).apply_effects(overwrite=True, user_order=False)
- assert len(augmented_signal._effects_chain) == 0
+ assert len(augmented_signal.effects_chain) == 0
assert len(augmented_signal.effects_applied) == 25
assert str(augmented_signal.effects_applied[-1]) == "time_stretch (params: factor=0.7)"
@@ -391,7 +393,7 @@ def test_make_effect(mix_and_sources, check_against_regression_data):
(signal
.make_effect("time_stretch", factor=3)
- .make_effect("pitch_shift", shift=2)
+ .make_effect("pitch_shift", n_semitones=2)
.make_effect("low_pass", freq=512)
.make_effect("high_pass", freq=512)
.make_effect("tremolo", mod_freq=5, mod_depth=.4)
@@ -409,24 +411,39 @@ def test_make_effect(mix_and_sources, check_against_regression_data):
't': 0
}])
)
- signal.apply_effects(reset=False)
- augmented_signal = signal.apply_effects(reset=False, overwrite=True, user_order=False)
+
+ augmented_signal = signal.apply_effects(user_order=False)
reg_path = path.join(REGRESSION_PATH, "hooks.json")
# This should result in the same signal in test_hooks
fx_regression(augmented_signal.audio_data, reg_path, check_against_regression_data)
+ for effect in augmented_signal.effects_applied:
+ _filter = effect.filter
+ params = effect.params
+ signal.make_effect(_filter, **params)
+
+ augmented_signal2 = signal.apply_effects(user_order=False)
+
with pytest.raises(AudioSignalException):
signal.make_effect("fail")
+ assert np.allclose(augmented_signal.audio_data, augmented_signal2.audio_data)
def test_order(mix_and_sources):
mix, _ = mix_and_sources
mix.reset_effects_chain().time_stretch(1.5).tremolo(.3,.4).pitch_shift(5).vibrato(.3, .4)
## User order
- signal0 = mix.apply_effects(reset=False)
+ signal_user = mix.apply_effects(reset=False)
## SoX-FFmpeg Order
- signal1 = mix.apply_effects(reset=False, user_order=False)
+ signal_sf = mix.apply_effects(reset=False, user_order=False)
+
+ assert len(signal_user.effects_applied) == len(signal_sf.effects_applied)
+
+ user_order = ["time_stretch", "tremolo", "pitch_shift", "vibrato"]
+ for effect, _filter in zip(signal_user.effects_applied, user_order):
+ assert effect.filter == _filter
- assert signal0.effects_applied != signal1.effects_applied
- assert len(signal0.effects_applied) == len(signal1.effects_applied)
+ sox_ffmpeg_order = ["time_stretch", "pitch_shift", "tremolo", "vibrato"]
+ for effect, _filter in zip(signal_sf.effects_applied, sox_ffmpeg_order):
+ assert effect.filter == _filter


message: removed stray imports, added soxbindings install option, and updated docs

diff:
@@ -110,16 +110,25 @@ $ brew install ffmpeg # on mac
$ apt-get install ffmpeg # on linux
```
-To use the Melodia separation algorithm, you'll need one additional package:
+To use the Melodia separation algorithm, you'll need to specify this:
```
-$ pip install vamp
+$ pip install "nussl[melodia]"
```
`vamp` doesn't install on some MacOS versions, so we can't include it by default.
To actually use Melodia, you'll also need the Melodia plugin. For installation
instructions, look [here](https://github.com/justinsalamon/melodia_python_tutorial/blob/master/melodia_python_tutorial.ipynb).
+To call SoX functions directly when applying effects, you will also need to specify this:
+
+```
+$ pip install "nussl[soxbindings]"
+```
+
+`soxbindings` can't be installed from PyPI on Windows, but it may be installable from source.
+For source installation instructions, look [here](https://github.com/pseeth/soxbindings)
+
# Citing
If you are using nussl for your research project, we please ask that you cite it using one of the

diff:
@@ -4,7 +4,8 @@ Changelog
v1.1.1
------
- Allowed MixSourceFolder to mix sources on the fly.
-- Added effects utilities.
+- Added effects utilities for data augmentation.
+- Fixed bug with ignite 0.4.0
v1.1.0
------

diff:
@@ -697,7 +697,7 @@ class AudioSignal(object):
"""
(:obj:`np.ndarray`): Returns a real valued ``np.array`` with log magnitude spectrogram data.
- The log magnitude spectrogram is defined as 20*log10(Abs(STFT)).
+ The log magnitude spectrogram is defined as 20 * log10(abs(stft)).
Same shape as :attr:`stft_data`.
Raises:
@@ -720,7 +720,11 @@ class AudioSignal(object):
def effects_chain(self):
"""
(``list`` of ``nussl.core.FilterFunction``): Returns a copy of the AudioSignal's
- effect chain.
+ effect chain. Editing this property will not result in a change to the effects chain
+ of the AudioSignal.
+
+ Please use the effects hooks (e.g. :func:`tremolo`, :func:`make_effect`) to make changes
+ to the Audiosignal's effects chain.
See Also:
* :func:`apply_effects`
@@ -730,8 +734,11 @@ class AudioSignal(object):
@property
def effects_applied(self):
"""
- (``list`` of ``nussl.core.FilterFunction``): Returns a list of effects applied to
- the AudioSignal.
+ (``list`` of ``nussl.core.FilterFunction``): Returns a copy of the list of effects
+ applied to the AudioSignal. Editing this property will not result in a change to the
+ effects aplied to the AudioSignal.
+
+ Please use :func:`apply_effects` to apply effects to the AudioSignal.
See Also:
* :func:`apply_effects`

diff:
@@ -97,9 +97,8 @@ STEM_TARGET_DICT = OrderedDict([
('linear_mixture', 'vocals+bass+drums+other')
])
-##### augmentation.py
-# These values are found in the ffmpeg documentation in filters
-# that use the level_in argument, however, not all filters that
-# use this state these bounds in documentation with pytest.raises(ValueError):
+# ################## Effects ################## #
+# These values are found in the ffmpeg documentation for filters
+# that use the level_in argument:
LEVEL_MIN = .015625
LEVEL_MAX = 64

diff:
@@ -3,7 +3,6 @@ import shutil
import logging
import random
from collections import OrderedDict
-import librosa
import torch
import zarr
@@ -12,7 +11,6 @@ import numpy as np
from sklearn.preprocessing import OneHotEncoder
from .. import utils
-from ..core import AudioSignal
# This is for when you're running multiple
# training threads

diff:
@@ -17,6 +17,6 @@ gpytorch
ffmpeg-python
importlib_resources
pyloudnorm
-sox==1.4.0b0
+sox==1.4.0
numba==0.48
-soxbindings
+

diff:
@@ -11,7 +11,7 @@ with open('extra_requirements.txt') as f:
setup(
name='nussl',
- version='1.1.0',
+ version='1.1.1',
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
@@ -30,9 +30,9 @@ setup(
description='A flexible sound source separation library.',
long_description=long_description,
long_description_content_type='text/markdown',
- author='E. Manilow, P. Seetharaman, F. Pishdadian, N. Shelly, B. Pardo',
+ author='E. Manilow, P. Seetharaman, F. Pishdadian, N. Shelly, A. Bugler, B. Pardo',
author_email='ethanmanilow@u.northwestern.edu',
- maintainer='E. Manilow, P. Seetharaman, F. Pishdadian, N. Shelly, B. Pardo',
+ maintainer='E. Manilow, P. Seetharaman, F. Pishdadian, N. Shelly, A. Bugler, B. Pardo',
maintainer_email='ethanmanilow@u.northwestern.edu',
url='https://github.com/interactiveaudiolab/nussl',
license='MIT',
@@ -44,6 +44,9 @@ setup(
'melodia': [
'vamp'
],
- 'extras': extra_requirements
+ 'extras': extra_requirements,
+ 'soxbindings': [
+ 'soxbindings>=1.0.1'
+ ],
}
)