Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding mixing functionality #196

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Conversation

pseeth
Copy link
Collaborator

@pseeth pseeth commented Dec 15, 2020

This PR adds convolution reverb functionality to AudioSignal, following discussion in #172. The functionality is:

Convolves signal one with signal two. There are three
cases:

1. s1 is multichannel and s2 is mono.
   -> s1's channels will all be convolved with s2.
2. s1 is mono and s2 is multichannel.
   -> s1 will be convolved with each channel of s2.
3. s1 and s2 are both multichannel.
   -> each channel will be convolved with the matching 
      channel. If they don't have the same number of
      channels, an error will be thrown.

It also adds a method to mix two signals together at a specified signal-to-noise ratio.

Checklist:

  • Bumped nussl version in setup.py
  • Updated changelog
  • Implemented core functionality for convolution
  • Added test for coverage for convolution
  • Implemented core functionality for mixing
  • Added test for coverage for mixing
  • Tests pass with 100% coverage
  • Add an impulse response to external file zoo for demo
  • Add usage example to docstring

@pseeth pseeth changed the title Adding convolution reverb function Adding mixing functionality Dec 15, 2020
nussl/core/mixing.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@ethman ethman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. Comments inline.

##################################################

def convolve(self, other, method='auto', normalize=True,
scale=True):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs needed for all of these methods

@@ -102,3 +102,7 @@
# that use the level_in argument:
LEVEL_MIN = .015625
LEVEL_MAX = 64

MIN_LOUDNESS = -70
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment with units.

direct: The convolution is determined directly from sums, the definition of convolution.
fft: The Fourier Transform is used to perform the convolution by calling fftconvolve.
auto: Automatically chooses direct or Fourier method based on an estimate of which is faster (default).
normalize: Whether to apply a normalization factor which will prevent clipping. Defaults to True.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think normalize and scale are very similar, and happen at different points in this function, so it'd be nice to have some clarity about the difference between these two args in the docs.

s1_ch, s2_ch / factor, mode='full', method=method)
output.append(convolved_ch)
else:
for i, s1_ch in enumerate(signal.get_channels()):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need these enumerates()? You're not using the index vars.

convolved_ch = scipy.signal.convolve(
s1_ch, s2_ch / factor, mode='full', method=method)
output.append(convolved_ch)
else:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pedantic: Maybe add a comment saying that at least one of these for loops is over 1 item. Wasn't clear to me at first.

n_loudness = max(MIN_LOUDNESS, bg_signal.loudness())
loudness = max(MIN_LOUDNESS, fg_signal.loudness())

if loudness - snr < MIN_LOUDNESS:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this logic be replaced with np.clip()? If not, an explanatory comment would be nice to get an overview.

bg_signal.zero_pad(0, pad_len)
bg_signal.truncate_samples(fg_signal.signal_length)

n_loudness = max(MIN_LOUDNESS, bg_signal.loudness())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you find more descriptive variable names for n_loudness, loudness, and t_loudness? They're hard to keep straight @ line 126 when you do the computation.

pad_len = max(0, fg_signal.signal_length - bg_signal.signal_length)
bg_signal.zero_pad(0, pad_len)
bg_signal.truncate_samples(fg_signal.signal_length)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like here we should be doing some checks to make sure things like sample_rate, n_channels, & length are OK. Maybe call utils.verify_audio_signal_list_strict([fg_signal, bg_signal]) here.

fg_signal = copy.deepcopy(fg_signal)
bg_signal = copy.deepcopy(bg_signal)

pad_len = max(0, fg_signal.signal_length - bg_signal.signal_length)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  1. I think you should bubble up whether to do min(len(fg), len(bg)) and truncate the longer one or max(len(fg), len(bg)) and pad the shorter one. (I don't think the former is useful, but I figure why not give the option to the user?) I think the opinionated way you've written it now is too subtle and could cause unintended side effects.
  2. Should that be it's own subroutine? Not sure if it's much use outside this function, but maybe def _match_signal_lengths(signal_list, mode='pad') or even something on AudioSignal like signal1.match_length(signal2). Just spitballing, lmk what you think

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw: By "bubble up" I mean add an arg for the user.

normalize=normalize, scale=scale)

def mix(self, other, snr=10):
from . import mixing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this lazy import is to avoid a circular import?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants