-
Notifications
You must be signed in to change notification settings - Fork 63
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
add fi.svf.morph #166
base: master
Are you sure you want to change the base?
add fi.svf.morph #166
Conversation
Nice ! Have you checked that previously existing modes still generate the same code ? |
I didn't check C++ for example but I did listen to the other modes and check the diagrams. |
I guess pattern matching should to the job and generated C++ should be the same, but always better be sure (-; |
Ok I checked the C++ generated from the previous svf.lp versus the new, and they're the same. |
If you notice, the filter curves go a bit up and down during the mix due to the non-aligned phases. You see that more clearly if you feed the filter a sine at the same frequency as the CF; you will see that the peaks are not constant. We can improve the mix a bit easily: the LP and HP are 180deg out of phase, so you can invert one of them for perfect phase alignment; the BP is trickier and it'd require a Hilbert filter to align the whole frequency range, which I find overkill, but we can at least align the frequency at CF with a one-pole allpass.
Dario |
@dariosanfilippo Thanks for double checking it. Should I take the outputs of onePoleTPT and blend between lp, (ap-lp-hp), hp with the mixLinearClamp thing I did in the PR? So if |
Hi, David. I was referring to correcting the phases of the SVF outputs that you mix together. So we invert the sign of the SVF HP output to make its phase match that of the SVF LP perfectly, and then we shift the phase of the SVF BP by 90deg at CF with a one-pole allpass to make it match that of the other filters. You could also seamlessly morph back from HP to LP, circularly, without getting a notch in your magnitude response along the way. See this example code here. |
I tried to reply via email, but it doesn't seem to work. Let me copy-and-paste my emails here. On 10/13, David Braun wrote: Hi, thanks for letting me know,
...
Hmm. Perhaps I missed something, but I don't think you need to If I read this code and the new mixLinearClamp() correctly you
and this should generate more or less the same code. faust is clever enough, it should factor out the common code Oleg. |
On 10/14, Oleg Nesterov wrote:
Let me check this. FYI, I had to add
into misceffects.lib, otherwise ef.mixLinearClamp() doesn't compile. Test-case:
C++ code:
See? at least in this case m1() and m2() generate the same So no, I don't think this patch makes sense... Oleg. |
@oleg-nesterov @dariosanfilippo Thank you both. I've pushed another update which I think properly incorporates Dario's suggestion while doing very little modification to the existing svf codebase. |
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.
@DBraun I think that the lpc
, bpc
, hpc
, and AP1
should all take f
as argument instead of the hard-coded 1000
, which was just for testing purposes. Ideally, I'd put the one-pole multi-mode and derived AP1 filters outside of the svf
scope and just available in the fi.
env. For the time-invariant case, the LP and HP outputs of the onePoleTPT should be equivalent to the first-order Butterworth filters in the libraries.
Woops! I fixed the frequency issue now. Would you mind typing up a doc blurb for onePoleTPT and AP1. Under what section would they go in filters.lib? |
I still can't understand why do you want to insert morph() into the And what exactly this morph() should do? to me it looks like a very OK. Just for example. We know that
so perhaps we can use this as another basis for mixing. Can you explain why is the new svf.morph() "better" than
and in what sense ? I'd say that no one is "better", they just differ. And neither Oleg. |
I'd agree that morph doesn't need to be in the svf environment and that it could just be another function in the filters lib. The phase alignment makes the resulting mix better because you'd have a consistent peak throughout the whole mixing coefficient, equal to the Q setting, which is also the peak at CF for the individual LP/BP/HP filters. |
The reason I want this function is to emulate the filter at 11:00-12:00 in this video https://youtu.be/ouWu89G5tGI?si=1Y_ITwyWWw-9U1Ve I’m not certain that it’s SVF, but upon looking at the SVF environment source this seemed like an efficient way to do it. I would also be interested in the same LP-BP-HP morphing thing with other filters. I’d be ok with the name svf_morph but I figured the svf prefix already exists, and this function uses svf behind the scenes, so why not just give it the same prefix. |
I guess you mean the same peak at CF. Yes, I understand. But why is it so important if you use morph() as audio effect? But this doesn't matter. Sorry if I was not clear, I didn't even try to My only point is that whatever it does I don't think it should live in svf is the collection of "standard" tools with the clear semantics, imo |
Those look more like 4-pole ladder filters to me. |
Speaking of svf... Sorry for being offtopic, but what do you think about
This way you you can do
|
The @oleg-nesterov way seems quite "hackish" to me. |
Yes I know. But this affects all users of tan(). While this trivial change a) doesn't change the default behavior, and b)
OK. lets forget it. Oleg. |
I wouldn't say that constant peak is a vital feature to have. My thinking was that, in some cases, with high resonances, you might hear loudness variations when moving/modulating the mixing coefficient. Other than that, it generally feels more correct as the derived morph filter behaves more like the elementary building blocks and it's very little overhead as However, I wonder if Faust understands that tan(2piF/SR) and tan(2piF*T) are the same thing. |
Perhaps. I simply do not know. But let me repeat this once again: sorry for confusion, I didn't try to argue with the filter's design!
I don't think I can answer, but could you spell please? I mean... example? |
It does seem to optimise; the following:
results in this code:
Looks right, correct? |
Ah. I think I can answer but may be Stephane will correct me ;) I think you can forget about tan() in this case. Consider
C++ code:
simplification()->normalizeAddTerm() does a good job (even if it is not perfect). And. if you have
and faust can deduce that expr1 and expr2 are "equal" you can expect the same optimization. Oleg. |
So what is the conclusion ? Should |
Let me remind what fi.svf is. It is the collection of the standard For example, IIRC vi.svf.bp(f,q) is "equivalent" to fi.resonbp(f,q,1) But. fi.svf works better under modulation, better than even tf2snp. See And it is more numerically stable, see What does morph() have to do with standard filters? It doesn't. That There are other tools in filters.lib that are built on top of svf.xxx(), |
Sorry for noise, I'd like to add to my previous comment... Of course, svf has another advantage. If you use, say, svf.lp And this is what makes it a good choice for the new morph() tool. |
OK this makes sense, then since the generated code should be the same, @DBraun better code it separately ? |
@oleg-nesterov Speaking of SVFs in general, Simper's design is remarkable: it requires 1 MUL less than Zavalishin's one, and it's stable for CFs from 0 to Nyquist. Did I count correctly that, assuming CF and Q time-variant, you could implement yours in 4 MULs, 2 DIVs, and 9 ADDs? I'm doing a trade of 2v1 and 2v2 for v1+v1 and v2+v2, as I'd assume that they'd be computed faster if we "const" v1 and v2 for fast access. This is Zavalishin's SVF; the LP will have a flat response only up to about 99.9% of Nyquist, and it will blow up at CF=Nyquist:
Faust will convert my attempt at defining signals as sums of the same vars into a multiplication of the vars by "2". I haven't looked at the assembly and I'm not sure what the C++ compiler goes for in the end. If I take the LP2 output only, this is the C++, which still seems suboptimal unless I counted FLOPS wrong:
|
Dario, et al, this is going off-topic and I am a bit tired of the web interface ;) I hope you won't mind if I reply on faudiostream-users. |
Sure, no problem. |
The code is now outside the svf environment and the function is called |
Cough ;) I'm afraid I have already made too much noise here, but I can't resist. This section is not new. IIUC both svf and the new onePoleTPT fall into this And not only "These are useful when the frequency is modulating quickly", they Sorry for nitpicking. |
So ? what is the next step? |
With the last commit we have
and to me this looks confusing/misleading a bit. svf and onePoleTPT are the same thing in that they use the same We can even rewrite onePoleTPT() using Andrew Simper's method of
I have no idea which version is "beter", may be Dario can take a look, And... whatever implementation we use, I'd suggest to make it environment,
this makes it more symmetrical with svf, and to me it would be more
than
But of course I won't insist, I leave this to David and Dario. Oleg. |
@DBraun I'm afraid that I can't work on the ladder filters at the moment as it's busy at work (when has it not been busy? I know.). Though if it's useful for you, there's a four-pole ladder LP implementation in the vaeffects lib: Line 157 in a50c12d
@oleg-nesterov Both Zavalishin's and Simper's one-pole appear to be stable for CFs from 0 to Nyquist and they seem to require the same operations, so I'm happy to use Simper's for continuity and have them in an environment as you suggested. Cheers, |
@oleg-nesterov
I've added a new
fi.svf.morph
function that allows seamless blending between LP, BP, and HP via a newblend
parameter. The implementation uses the new ef.mixLinearClamp function to blend the weights. All other svf modes are backwards compatible.Faust IDE Demo.