-
Notifications
You must be signed in to change notification settings - Fork 20
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
generate coefficients for WFS FIR prefilters #29
base: master
Are you sure you want to change the base?
Conversation
sfs/time/drivingfunction.py
Outdated
Pre-delay in seconds. | ||
|
||
""" | ||
N = 2*(int(N + 1)//2) # for odd N, use N+1 instead |
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 only use once below, where it is divided by two and one is added. That's a lot of dividing by 2 and adding one, isn't it?
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.
It's ugly, but intentional.
The body uses only numbins
, but that would be a rather uncommon parameter.
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 wasn't talking about that. But I admit that I wasn't quite clear ...
I was talking about this:
bins = int(2*(int(N + 1)//2)/2 + 1)
This makes me dizzy.
Is this really the canonical way to write this?
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.
Got rid of it by raising an error for odd N
, as you suggest below.
sfs/time/drivingfunction.py
Outdated
if c is None: | ||
c = defs.c | ||
|
||
numbins = int(N/2 + 1) |
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 don't like "num" prefixes. What about just calling it bins
?
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.
Hmm. bins
sounds like a list of bins to me.
I suppose you would not like nbins
either?
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.
Indeed, nbins
is as bad as numbins
. This is of course very much a matter of taste, but I think it's not Pythonic.
Do you know any Python code of the standard library (or in code of any of the Python core contributors) that uses this naming convention?
I understand that bins
sounds like a list of bins, but it should be immediately clear from its usage that it's not.
I personally wouldn't see this as a problem.
If you want to make it abundantly clear that it is a quantity rather than a list of things, you should call it something like filtersize
. Or simply size
, since it's the only "size" thats relevant in this context.
What do you think about this?
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.
but I think it's not Pythonic.
You are probably right. bins
it shall be.
sfs/time/drivingfunction.py
Outdated
desired[:l_index] = low_shelf | ||
desired[u_index:] = min(high_shelf, desired[u_index]) | ||
|
||
h = np.fft.ifft(np.concatenate((desired, desired[-1:0:-1]))) |
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.
Can't you use irfft() 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.
done.
sfs/time/drivingfunction.py
Outdated
l_index = int(np.ceil(fl / delta_f)) | ||
u_index = int(min(np.ceil(fu / delta_f), numbins - 1)) | ||
desired[:l_index] = low_shelf | ||
desired[u_index:] = min(high_shelf, desired[u_index]) |
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.
Can desired
even be smaller here? Isn't min()
superfluous?
Also, do you really need to calculate both *_shelf
and *_index
?
Couldn't you just clip desired
to low_shelf
and high_shelf
(without needing the indices)?
Or, alternatively, use desired[l_index]
and desired[u_index]
without calculating low_shelf
and high_shelf
?
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! It is a lot cleaner using clip
.
sfs/time/drivingfunction.py
Outdated
Parameters | ||
---------- | ||
N : int, optional | ||
Filter order, shall be even. For odd N, N+1 is used. |
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.
Why don't you just raise a ValueError
if the user provides wrong input?
Thanks, but this isn't a filter! Anyway, it would be interesting to see how this is supposed to be used. |
Do you mean a function called "filter" should actually apply a filter? For the moment, it would be applied manually by the user. |
In general, I would expect any function |
Then I guess @mgeier : Could you point me to a better way to convolve N (or 1) signals with N (or 1) filters? def fftconvolve_1d(a, v, axis=1):
"""1-d Convolution along axis.
"""
if a.ndim > 2 or v.ndim > 2:
raise ValueError('dim mismatch');
output_length = a.shape[axis] + v.shape[axis] -1
nfft = int(2**np.ceil(np.log2(output_length)))
A = np.fft.rfft(a, nfft, axis)
V = np.fft.rfft(v, nfft, axis)
out = np.fft.irfft(A * V, nfft, axis)
return np.delete(out, np.s_[output_length:], axis) |
Since the filter is specific to 2.5-dimensional synthesis I would expect 25D in the function name. |
|
Yes, IMHO a function called "filter" should either be a filter or create and return a filter. In a Pythonic naming scheme, the word "get" should be avoided as much as possible. Most of the times, it's utterly meaningless. Unless the "getting" is really one of the main tasks of a function, e.g. re FFT convolution: As long as it stays 1D, scipy.signal.fftconvolve() should be the weapon of choice. However, for 2D arrays, this does a two-dimensional convolution, which is normally not what we want. |
Then what about Since this only about filter coefficients, no assumptions about usage are necessary. @mgeier: Thanks for clarifying the FFT convolution situation! |
This is not true. |
I don't understand, can you please elaborate? |
No. It is merely assumed that filter coefficients are needed for FIR filtering. Personally, I'd use them directly: signal , _ = wfs_prefiter_fir()
d = driving_signals(..., signal, ...) But I do not suggest this or any particular usage.
Sorry, I should not have brought it up as it does not belong here. |
Does that mean that Your usage above looks like a "shortcut". I think we actually should suggest a certain usage, and this usage should include an explicit filtering step. Initially, I thought we could just use generic filter functions everywhere, which could be FIR or IIR filters or whatever nonlinear stuff. Or should we limit ourselves to FIR filters? |
By "generic filter function" you mean a function that takes a signal and yields a signal? Then the FIR filter might be used like this: def wfs_fir_prefilter(sig, ...):
h = _wfs_prefilter_fir(...)
return fftconvolve(h, sig) re IR length: |
Exactement! And the question about the API would be if we should have the impulse-reponse-generating function as a user-callable function of just as an implementation detail (or not at all).
AFAICT, the What's the similarity there?
That sounds reasonable. |
Implementation detail is fine I think. As long as we have only 2.5D WFS it does not have to be a separate function. But it doesn't hurt either.
Sorry, that was a mistake. Somehow I forgot that
Decay below some threshold maybe? |
Change default parameters of filter design.
I've tried to pick up the open ends:
Any objections? Anything else to do? import numpy as np
import sfs
grid = sfs.util.xyz_grid([-0.5, 3.5], [-2, 2], 0, spacing=0.01)
x0, n0, a0 = sfs.array.linear(21, 0.1)
delays, weights = sfs.time.drivingfunction.wfs_25d_point(x0, n0, [-1, 0, 0])
sig = [1]
# apply pre-equalization & individual delays/weights
sig, t_offset_filter = sfs.time.drivingfunction.wfs_25d_fir_prefilter(sig)
d, t_offset_delays = sfs.time.drivingfunction.driving_signals(delays, weights, sig)
t = 0.01
t -= t_offset_delays + t_offset_filter
p = sfs.time.soundfield.p_array(x0, d, a0, t, grid)
sfs.plot.level(p, grid, vmin=-80, vmax =-40, cmap='YlOrRd')
sfs.plot.loudspeaker_2d(x0, n0) |
I just tried it, too. Works pretty nice! As a little remak: I was wondering, if it's worth shifting the call of |
We could plug a filter function into |
FYI: overlap-add convolution has been implemented in SciPy: scipy/scipy#10869 |
A simple FIR prefilter for WFS, as in the Matlab version.
TODO