-
Notifications
You must be signed in to change notification settings - Fork 80
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
Use Transformer in-memory with stdin/stdout #102
Conversation
The import of numpy could probably be made optional (if it's deemed to be too "heavy" of a dependency for this package). Great work though! |
I got this up to 100% coverage in I also added docstrings for the new function. I can easily make numpy optional by checking for it or importing it dynamically but I'm guessing everyone using pysox for something might already have numpy installed, anyway? |
@pseeth this is great, thank you! 🚀 I'm reviewing now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all the work on this @pseeth ! Take a look at my comments and let me know what you think. Seeing the PR I'm reconsidering if it's better as part of build
directly or as a separate function. Something else to consider - does it make sense to support the same functionality now in the Combiner, and in the preview functions?
If you need/want, I'm happy to help with any of this, let me know if you want me to and I'll add some commits on this PR.
setup.py
Outdated
@@ -20,12 +20,14 @@ | |||
keywords='audio effects SoX', | |||
license='BSD-3-Clause', | |||
install_requires=[ | |||
'numpy', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the minimum version we can get away with here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's 1.9.0. According to this: https://numpy.org/doc/1.18/reference/generated/numpy.ndarray.tobytes.html, it's new in 1.9.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!
setup.py
Outdated
], | ||
extras_require={ | ||
'tests': [ | ||
'pytest', | ||
'pytest-cov', | ||
'pytest-pep8', | ||
'pysoundfile', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just for tests, but I think we need at least 0.9.0 to get the dtype
functionality:
https://pysoundfile.readthedocs.io/en/latest/#breaking-changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!
sox/core.py
Outdated
process_handle = subprocess.Popen( | ||
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE | ||
) | ||
if src_array is not None and isinstance(src_array, np.ndarray): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Raise a TypeError if not isinstance(src_array, np.ndarray)
- otherwise the behavior will be confusing if a user passes e.g. a list and it gets ignored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - swap the order of this if/else to start with the "standard" case - if src_array is None: ...
, elif isinstance(src_array, np.ndarray): ...
else ... raise TypeError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah makes sense! I updated this.
sox/transform.py
Outdated
extra_args : list or None, default=None | ||
If a list is given, these additional arguments are passed to SoX | ||
at the end of the list of effects. | ||
Don't use this argument unless you know exactly what you're doing! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the returns
sox/transform.py
Outdated
if encoding_out is None: | ||
encoding_out = encoding | ||
|
||
self.set_input_format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want to call these functions inside build_array - they change the internal state of the Transformer. The way it's written now, calling build_array
will silently change the behavior of build
.
Brainstorming how to get around this, we could:
- write a function that builds the args list for input/output formats, which gets called by
set_input_format
/set_output_format
and by this function. - reduce the number of inputs to this function. as far as I can tell, the only needed input format argument is
sample_rate_in
. The rest can (should!?) be inferred from the input array. The output format support is already built into sox.build in several ways, either by callingset_output_format
or by transformer commands likerate
andchannels
. The only one I'd leave optionally specified here isencoding_out
.
so, in short this function would become
build_array(self, input_array, sample_rate_in, encoding_out=None,
extra_args=None, return_output=True)
Thoughts/objections?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the argument list for build_array
was initially what you said, but had to be expanded quite a bit to get through all of the test cases. The ones I can get rid of are channels_in
and bits_in
as they can be inferred from the numpy array. But the output types can't be inferred.
What if we had set_output_format
and set_input_format
return (optionally) the arg list rather than set it as a variable in the object? Then we could extend it dynamically in build_array
instead without side effects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified set_[input, output]_format
with a flag return_only
where the arg list will be built without saving it to self
. I had to keep some of the arguments to build_array
, but as you'll see further down, build_array
is now merged with build
.
sox/transform.py
Outdated
at the end of the list of effects. | ||
Don't use this argument unless you know exactly what you're doing! | ||
''' | ||
output_filepath = '-' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking out loud... does it also make sense to support array in --> file out, and file in --> array out? My thinking is yes, in which case maybe all or this function should be part of build
? What do you think @pseeth ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally possible. Do you have a suggested function signature for build
? It seems like a lot of the arguments will become optional, or do type-checking on input/output (in which case the arguments will be renamed). Then we'll have some sort of merge between the build_array
and build
functions, which I think would look reasonable. Let me know your thoughts!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is supported now! I merged build
and build_array
to be able to support this. There's some finicky typechecking that needed to happen, and a flag that tells the core.sox
function whether to decode out
with utf-8
but it now works! I expanded the test cases to test file -> array
, array -> array
, and array -> file
. All of these are checked against the original file -> file
.
by the way, I'm totally OK with adding numpy as a dependency. |
I'm not sure if I can get this to work for the Combiner as there are multiple input arrays and I'm not sure how that works with stdin. Seems like there might be some black magic...but if a user is using numpy arrays after the transformer, they really should just use Thanks for the review, @rabitt! Quick question - do you know why the coverage test is failing? |
…tputs to be either arrays or filepaths.
Updated the PR in response to comments! And it seems coverage checks are now passing. :) |
I think this will require a version bump and associated text in the changelog. Let me know what to do there, or if you'd like, you can add it yourself once this PR is ready to be merged! |
I was reviewing and had some comments requiring nontrivial changes so I'm going to push to this branch -
working on it now! |
@pseeth just opened a PR on your fork with those changes. |
refactor build
Looks good to me! Thanks! I merged it into my PR, so now we wait on tests... |
It looks like the docs build is failing because we added numpy, and there are a few lines I added that aren't tested. Going to make one more PR to your PR to fix! Could you add the changelog notes and maybe add an example to the docs for how to use the new functionality? |
Sounds good! I'll update the changelog. |
Actually, some guidance on this would be helpful. What should the version bump to? |
I found some issues with the docs build (the returns aren't properly documented so they're not showing up in the docs) - I'm going ahead and merging this without bumping the version. I'll fix the docs and do the version bump in a separate PR. Thanks! |
This PR tries to address #6. I basically followed the code by @carlthome in pysndfx here: https://github.com/carlthome/python-audio-effects/blob/master/pysndfx/dsp.py#L472. I followed the discussion in the issue and implemented a new function that belongs to Transformer called
build_array
, which takes in a numpy array. The main issue is keeping track of all of the information that would normally be in the header of the audio file. Instead, these have to be passed as arguments to build_array, both for the input and output.I modified all of the tests. Each piece of the Transformer is tested by taking an input file and passing it through sox to write to an output file. So in the tests, what I do is load both the input file and the output file as numpy arrays, pass the input array into
tfm.build_array
and collect the output array (which is the second argument in the tuple, matching the API oftfm.build
). Then, I do annp.allclose
between the loaded output array from the output file and the output array. Every test was modified in this way, except for one, which I can't get working in the time I tried to put this together today. That test istest_bitdepth_valid
, here: https://github.com/pseeth/pysox/blob/master/tests/test_transform.py#L1347.This change to the tests is encapsulated in a single function
tfm_assert_array_to_file_output
which you'll see calls to sprinkled throughout the test.To do all this, I modified the
sox
function in a backwards compatible way. I probably need to up coverage a bit still but this is hopefully okay to PR now for feedback.Let me know if this is a good start and how to get this merged, if possible! It would go a long way to making pysox super efficient for Scaper, thus why I'm here. :)
Thanks!