From the original SynthMaker forum (2004–2014), with its members’ names and dates.
cyto ·
Hello all.
In searching these forums for just a simple strereo phaser effect, I discovered a glaring lack of anything that fits the bill. So this past weekend I threw this thing together. Its about as straight forward as they come. Rate and Width are adjustable from the front panel. Open the properties to set the number of stages (2, 4, 6, or 8), the modulation range between 1/2 and 4 octaves, and the individual frequency bands (up to 8/left 8/right).
The whole thing is built around true all-pass filters (not those silly delay-line ones that are in the stock reverb). I designed the filters for maximum stability and computational efficiency and as such the transistion bands are relatively wide. You won't get that over-the-top-make-your-head-spin-swirly goodness, but nevertheless this thing should thicken up your signal quite nicely. The effect theoretically should be pretty CPU friendly, especially considering the sheer amount of math that you'd expect from 16 2nd order AP filters (8 left/8 right) and a couple of modulation carriers. I've only tested it within SM and only on my weak little single core netbook (which usually craps out pretty easily on anything but the simplest OSM) and the load seemed pretty negligible even revving on all eight cylinders. I'd be curious to hear how it does on other people's systems.
The OSM attached is just a bare-bones little synth with the stock multi-osc so you get the idea of how this effect sounds. There are plenty of comments within the module to give an insight into what's going on and ideas for modification. I've added a few GUI elements for the heck of it, but obviously you'll want to swap those out for something more appropriate for your project.
Let me know what you guys think and if you notice any glaring problems. I'm mostly curious about performance on multi-core systems and behavior of the filters at higher sampling rates (I've only tested at 44.1kHz).
-Brad (cyto)
PS. it occurred to me that some of you might be thrown by my radially actuated knobs. If anybody wants a version of this with the stock vertically actuated knobs, just let me know and I'll whip one up. (EDIT-new version has stock knobs)
MyCo ·
very cool version, but I really hate the knobs 😈
For the LFO in the phaser you use lookups, there is propably a better way: using a self oscillating filter. The code is well known, eg: http://musicdsp.org/showone.php?id=10
I've attached my Phaser from TranceDrive... There you can also find this LFO, for testing, but it's already optimized to the limits, and that's why it's hard to read.
aliasant ·
Thanx for posting and welcome!
Lots of little things to learn from!
Just opened it but i dont have much time 🙁 In the middle of my vacation... stressed out washing and repacking...
Looking at your odd/funky/flexible knobs I can hint of a problem though.
Its not your design but a hostapp that will cause them to fail if the host is not playing.
Its Wavelab. ( might be more that behaves the same? )
I have a customer who uses it and my plug didnt work at all and its is because Wavelab, when not playing, shuts down all blue streams 🙁 causing the knobs and other parts depending on streams to freeze.
I had to remake my knob smoothers so that they use green math instead.
The new improved triggers broke that green smoothing thing so I had to remake them yet again.. I have something that works now but its not as good a feel as using stream smoothers.
About the Allpass filters.
They do use some cpu (190cycles on my i7 920 quad) but I guess the ASM gurus here can get that down to about half the cycles?
Dont have time to test them at the moment but they look nice 🙂
Again
Thanx for sharing your brain with the rest of us .. 😀
aliasant ·
Another thing found that could cause problems is the delay you use.
It adds 2 samples overhead.
I made a small schematic that shows different delays from my toolbox and they are all hokked up to a nifty delaytime tester that Theun made so you can see the difference.
rl ·
great first post, cyto! I will have a closer look, later....
@aliasant: are you sure there is no bug in your sample delay code?
this should work with 4 samples delay:
...
temp=inc-(index-one);
...
aliasant ·
rl wrote: great first post, cyto! I will have a closer look, later....
@aliasant: are you sure there is no bug in your sample delay code?
this should work with 4 samples delay:
...
temp=inc-(index-one);
...
Its not my code 🙂
Cytos sampledelay code dlays the signal by an additional 2 samples and that was what I was trying to show.
If he sets it to delay by 8 to compensate for the rest it will actually delay it by 10 samples instead.
Thats at least what it looks like to me comparing that code with some other delay codes set to the same value.
Or did i miss something again?
stw ·
Some great and well documented examples. Thanks for sharing!
cyto ·
aliasant wrote: Looking at your odd/funky/flexible knobs I can hint of a problem though.
Its not your design but a hostapp that will cause them to fail if the host is not playing.
If you go into the Properties panel of the knobs, you can turn off the smoothing function. That will completely remove the mono stream from it. I made these knobs awhile back for my own personal prototyping, etc. (not necessarily for public consumption).
aliasant wrote: Another thing found that could cause problems is the delay you use.
It adds 2 samples overhead.
I made a small schematic that shows different delays from my toolbox and they are all hokked up to a nifty delaytime tester that Theun made so you can see the difference.
Excellent catch, aliasant! I would have never caught that on my own but you are absolutely right. Without going to deep into it, there are a couple "spleens" in that little bit of code from a previous incarnation of this thing that I forgot to remove when I changed things up. I'll put up a fixed version when I get around to it.
-Brad
trogluddite ·
Welcome cyto!
Quite possibly the best 1st post I've ever seen - great sound, some cool algorithms and a beautifully arranged and annotated schematic.
I would agree with MyCo regarding the LFO - your method is perfectly sound in principle, but the way that SM implements arrays in its internal assembly tends to be somewhat clunky. Despite that, I still like the coeff lookup arrays - they save a significant amount of maths where you require audio rate modulation (since cos and sin are lookups anyway inside SM code).
A simple enhancement would be to only read coeff1[lookup]and coeff2[lookup] once each, and pop them into temp variables - although that might look more messy, it will save two instances of SM's greedy array reading routine.
I'll maybe take a look at doing some assembly tinkering on the code - it will lend itself well to some array optimisations that I've been working on recently, and is a great 'building block' to have around.
BTW) For optimum accuracy, an array size of 65536 will match the length of SM's internal tables for the sin1 & cos1 functions.
Looking forward to your future posts,
The Luddite.
cyto ·
MyCo wrote: For the LFO in the phaser you use lookups, there is propably a better way: using a self oscillating filter. The code is well known, eg: http://musicdsp.org/showone.php?id=10
I really dislike circular (trig) function LFOs especially when they modulate frequency data. They sound horribly UN-musical to my ears. This is just my personal opinion. Having said that, there probably is room for improvement in that section of the effect. Honestly, I didn't put too much thought into the LFO, hence the quick and dirty counter/lookup. But I don't think its eating up too much resource and (to me) it certainly sounds better than a sin/cos curve.
-Brad
MyCo ·
on low frequencies, it is impossible to hear a difference. There are better chances that you'd hear the aliasing of a lookup table Osc. And as trog wrote, S|M uses big lookup tables for the code trig functions, so it's not very efficient, to build a lookup of a lookup 😈
cyto ·
trogluddite wrote: Welcome cyto!
I would agree with MyCo regarding the LFO - your method is perfectly sound in principle, but the way that SM implements arrays in its internal assembly tends to be somewhat clunky.
Good insight. I just assumed arrays were pretty efficient. If I have time later, I'll look into switching things up in this section of the effect, so you can get the smoothness of the base-2 curves without having to resort to the crazy arrays. There has to be an approximation floating around that will avoid all the pow(2,x) that would be necessary.
trogluddite wrote: A simple enhancement would be to only read coeff1[lookup]and coeff2[lookup] once each, and pop them into temp variables - although that might look more messy, it will save two instances of SM's greedy array reading routine.
Excellent suggestion.
trogluddite wrote: BTW) For optimum accuracy, an array size of 65536 will match the length of SM's internal tables for the sin1 & cos1 functions.
Good to know.
Thanks. This is excellent insight. I really appreciate it.
-Brad
trogluddite ·
cyto wrote: Thanks. This is excellent insight. I really appreciate it.
That's no problem - you seem to have some interesting DSP to share, it's only fair to reciprocate with a few pointers to some of SM's little 'quirks'.
cyto wrote: base-2 curves
Ah yes, think I see where you're coming from now - your LFO curves are exponentially scaled to give a 'balanced' pitch (as opposed to frequency) shift either side of the centre, to avoid that 'swooping' effect where the modulation seems to go too deep on the negative part of the cycle.
I seem to recall a similar problem on some of the old Japanese analogue synths that used Herts-per-Volt pitch CV's - it made getting modulation amounts balanced across the notes a complete nightmare compared to the nice exponential Volt/Octave way (thanks again to Dr. Moog).
In which case, your instinct about using pow(2,x) is spot on - not only a greedy bit of maths, but (like the arrays) something that SM has to use the FPU for, rather than nice speedy SSE instructions. So a stage(0) filled lookup like your allpass may actually be a quite decent solution. As you only require two values to be read, it would also optimise very effectively using some assembly.
DigitalWhiteByte ·
@cyto
Welcome & 😉
aliasant wrote: I made a small schematic that shows different delays from my toolbox and they are all hokked up to a nifty delaytime tester that Theun made so you can see the difference.
I use my tools to check the latency delay of the samples, but not always the same results as that of Theun, I do not think I have problems, but for example in the module "SAMPLE DELAY 4" of it is not alisiant equal.
which is right?
cyto ·
DigitalWhiteByte wrote: I use my tools to check the latency delay of the samples, but not always the same results as that of Theun, I do not think I have problems, but for example in the module "SAMPLE DELAY 4" of it is not alisiant equal.
which is right?
I'm 99% sure yours is right. As it stands now that little bit of code generates an extra one sample of delay, which is what your analyzer states. The culprit is in this line...
temp=inc-(index+one);
... which should simply be...
temp=inc-index;
Like I said earlier, that little "one" was significant in an earlier instance, but I forgot to take it out when made some changes there. I'm still amazed that aliasant found that tiny error in such short time. You guys certainly are thorough.
-Brad
aliasant ·
DigitalWhiteByte wrote: @cyto
Welcome & 😉
aliasant wrote: I made a small schematic that shows different delays from my toolbox and they are all hokked up to a nifty delaytime tester that Theun made so you can see the difference.
I use my tools to check the latency delay of the samples, but not always the same results as that of Theun, I do not think I have problems, but for example in the module "SAMPLE DELAY 4" of it is not alisiant equal.
which is right?
YOURS 😊 🙂
I checked the difference between the output of 4 chained 1-sample delays from the toolbox with my own 4 sample delay and subtracted one from the other. If they match I should get 0 but I didnt. My 4 sample delay is actually a 3 sample delay !?
THEUUUUUN! I trusted you 😈 🙂
CORRECTION: Cytos delay code only adds 1 overhead sample NOT 2.
Thank you DWB!
Now I need to run thru my osm's to see were I used my sample delay...
cyto ·
trogluddite wrote: Ah yes, think I see where you're coming from now - your LFO curves are exponentially scaled to give a 'balanced' pitch (as opposed to frequency) shift either side of the centre, to avoid that 'swooping' effect where the modulation seems to go too deep on the negative part of the cycle.
YES! You are absolutely correct (and btw better at explaining it than I would be). I worked out a solution to this little dilemma with a little trial and error and a graphing calculator. Since I was really only interested in about a four octave span (-2 to 2 octave transposition), I was able to wrangle a rectangular hyperbola into a usable approximation. Below you can see the results. The red line is just a 2^x curve which essentially was what my original arrays were. The green line is the new approximation:1-((2x/(x-3.3333)). This only works in the limited -2 to 2 x-range, where x represents the pitch transposition (in octaves) and y represents the multiplication necessary (hence 0 trans=1*freq, 1 oct trans=2*freq, etc).
I've already coded it out in SM (and got rid of those crazy arrays) and it seems to really well for this purpose, cutting out a healthy amount of CPU usage. I'll upload a new version of this thing later when I have some time to incorporate some of the other suggestions.
-Brad
trogluddite ·
cyto wrote: a usable approximation
Very usable indeed, a lovely cheap way to make modulation effects (and more) sound 'prettier', thankyou 🙂
infuzion ·
aliasant wrote: Looking at your odd/funky/flexible knobs I can hint of a problem though.
..... I have something that works now but its not as good a feel as using stream smoothers.
I like the knobs very much also! Took me a while to figure out that they are the "move the mouse around the edge" type. And yes, you will have issues with that knob-mono code; the "Tick 25" is inaccurate, & as is the "Mono to Float" output is unpredictable! Also, any knob-smoothing more than a fast de-zipper may frustrate live performers; where whipping a knob back & forth is expected to give radical value changes, but does not because of the knob smoother not able to catch up.
aliasant wrote: ...but I guess the ASM gurus here can get that down to about half the cycles?
Did someone say optimize? 😉 I'm suprized that Trog hasn't reworked the packer plant with his shufps tools yet 😉 The note inside is LOL though:
Welcome to SM cyto!
trogluddite ·
infuzion wrote: I'm suprized that Trog hasn't reworked the packer plant
The weekend is almost here... 😉
Mo ·
Hi cyto, thanks for sharing!
Here's a ASM optimized version of your packer module:
Thank you, everyone, for lending your expertise to this little guy. I've come up with a new version that hopefully addresses some of the inherent issues with the first go-round. The following are some of the major changes:
FILTER CODE - Completely reworked to calculate the coefficients on-the-fly. I was able to (pretty successfully) derive algebraic approximations of the coefficient calculations to eliminate entirely the need for trigonomic math. After a healthy bit of wrangling, I was able to get the expressions (three in total) into a pretty standardized form with lots of redundant factors among them (good for coding as these factors only need to be calculated once). If you download the new OSM, the approximations are noted there for your perusal. Also in the new filter code is a DC offset corrector. I noticed that the filters would sometimes give you about a ~+/- 1e-008 DC offset when no signal was present. Now there is hop(4096) routine that removes this (just 2 lines).
MODULATION - No more curve arrays! New method uses parallel counters and (you guessed it) another algebraic approximation. I certainly got my geek on this week 🙂 . Because the new method is expression-based and allows for continuous adjustment of the modulation range, I also added a "range" knob to the GUI.
PACKING - New packing scheme elimates the one sample per stage delay that was inherent in the first design. Now the effect has just a one sample latency in total (not 2-8smp delay like the first design). BTW @Mo- Thank you very much for the optimized packer module. Though I was not able to use it as-is in this new version, it will serve as an excellent reference example for me in the future (I have no experience in ASM).
GUI - I ditched my knobs for the standard SM ones so the appearance of the module now is consistent with the other stock effects in SM. I also moved the "stage" selector to the front panel and, as stated earlier, added a "range" control.
Anything else you might need to know can be found in the OSM. I'm hoping some of these new changes will be positive steps in making this guy a little less resource intensive. I encourage any advice or criticism. Your comments so far have taught me a tremendous amount about SM. Thanks again!
-Brad
trogluddite ·
That's great work - on my little Intel Atom machine, it's showing almost half the CPU load of your original post (when all 8 stages are engaged), with further reductions as the number of stages is decreased.
One simple optimisation of your current code would be to replace as many of the subtractions as possible with additions, by defining some negative value constants - that ought to squeeze the CPU load a little further.
cyto ·
Earlier this week, I decided it was finally time to learn a little about assembly language. Figuring this project was as good as any to experiment with, I revisited it with the intent of getting my hands a little dirty in some ASM boxes. What follows is a new version that is the fastest yet. Basically, the math is pretty much the same as in the last update. It's just processed more efficiently.
It's a pretty straight-forward interpretation, just using "common sense" and a handful of basic opcodes, but I'm truly amazed at the performance difference. Now I know why everybody goes gaga over ASM optimization. I'd be happy if any of the ASM wizards out there could give me some pointers for future learning.
As before, the schematic is pretty well commented with the source code in purgeable modules that you can drill-down to view.
-cyto
Important Lesson Learned: rcpps cannot be relied upon for critical calculations! 😕
trogluddite ·
cyto wrote: Earlier this week, I decided it was finally time to learn a little...
cyto wrote: I'd be happy if any of the ASM wizards out there could give me some pointers for future learning.
Wow, the speed you've learned that, you'll be teaching us new tricks in a week or two!
That's a great optimising job - even my little Atom CPU notebook shows only 3-4% CPU.
Had a quick look through your assembly, and it looks like you've pretty much nailed every technique in the book!
The only thing I can think of that might save a teeny weeny bit more would be to pack your Left and Right signals into mono4 to remove a few duplicate modules and the tiny overhead for the extra links - this might also enable some further optimisation of the pre- and post- packing using the shufps opcode (you'll find plenty of good threads about that one).
But that is all quite unfair nit-picking of the highest order given what you've achieved in such a short time using SM, and I doubt that any savings would even be measurable on any sensible CPU.
infuzion ·
I'm not sure about this:
//(prevent subnormal math)
addps xmm6,one;
addps xmm6,negOne;
Should be usually:
//(prevent subnormal math)
addps xmm6,dcFloor; //9.9999997e-006
subps xmm6,dcFloor;
Not sure what you were doing there; don't have the Code handy to compare.
ASM is very good, but a quick scan shows some places for minor improvement.
cyto wrote: Important Lesson Learned: rcpps cannot be relied upon for critical calculations! 😕
0.00089073181 is the largest difference I could find, might not be accurate. -100dB = 0.00001, so I guess depends on the application. Better with whole numbers like the SampleRate you did.
DigitalWhiteByte ·
cyto wrote: Important Lesson Learned: rcpps cannot be relied upon for critical calculations! 😕
Seeing the description, I noticed the specification of the approximation:
RCPPS xmm1, xmm2/m128
Returns to xmm1 the packed approximations of the reciprocals of the packed single-precision floating-point values in xmm2/m128.
Description:
Performs a SIMD computation of the approximate reciprocals of the four packed single-precision floating-point values in the source operand (second operand) stores the packed single-precision floating-point results in the destination operand. The maximum error for this approximation is (
1.5 * 2-12). The source operand can be an XMM register or a 128-bit memory location. The destination operand is an XMM register. See Figure 10-5 in the IA-32 Intel(R) Architecture Software Developer's Manual, Volume 1 for an illustration of a SIMD single-precision floating-point operation.
The RCPSS instruction is not affected by the rounding control bits in the MXCSR register. When a source value is a 0.0, an of the sign of the source value is returned. A denormal source value is treated as a 0.0 (of the same sign). Underflow results are always flushed to 0.0, with the sign of the operand. When a source value is an SNaN or QNaN, the SNaN converted to a QNaN or the source QNaN is returned.
cyto ·
infuzion wrote: I'm not sure about this:
//(prevent subnormal math)
addps xmm6,one;
addps xmm6,negOne;
Should be usually:
//(prevent subnormal math)
addps xmm6,dcFloor; //9.9999997e-006
subps xmm6,dcFloor;
Not sure what you were doing there; don't have the Code handy to compare.
Shouldn't make too much of difference what value is used. My understanding is that any simple arithmetic operation will effectively wipe out those pesky little digits. Your version and mine are essentially the same thing, just with different values. If you need proof of its effectiveness, just comment out those two little lines and watch your CPU meter spike through the roof! I got into the habit of adding and subtracting ones to passed variables awhile ago in designing IIR filters. I used to run that routine on every variable I passed. However, once I made the jump into the ASM box, where every little calculation seems so precious, I rethought my approach and realized that you can still effectively prevent subnormals just by doing the routine on the y[n-1] variable. It makes sense if you think about how the transfer function is calculated.
infuzion wrote:
cyto wrote: Important Lesson Learned: rcpps cannot be relied upon for critical calculations! 😕
0.00089073181 is the largest difference I could find, might not be accurate. -100dB = 0.00001, so I guess depends on the application.
Because the filter coefficients all have the same denominator, I originally performed the rcpps command on its value, then multiplied that with each of the numerators (thinking I could save a few instances of divides). This made the filters horribly unstable! Took me a few hours to realize what the culprit was. In this case, a difference of a few thousandths was disastrous.
-cyto
trogluddite ·
cyto wrote: Shouldn't make too much of difference what value is used.
The value used will make a difference to the accuracy of the result after the subtraction.
The aim is to push sufficient bits out of the mantissa to force a denorm to zero - but ideally to keep the number as small as possible to avoid pushing bits out of the mantissa of a desired normal number, reducing it's precision.
If I remember my maths correctly - assuming we don't have all our faders pushed up to 11, we are adding one to a number that we know will have a magnitude less than one, giving us 1.nnnnnn for a positive input. The single-precision float representation will have an exponent of zero, and a mantissa of 0.nnnnnnn (omitting the normalising leading one). The mantissa 0.nnnn has only 23 bits - i.e. you are creating what is effectively a 24 bit fixed point number.
In the worst possible case, it is quite possible to add and subtract a number so large (> 2^23) that all of our 'useful' values (i.e. -1 to +1 used for audio) would return zero.
So you can make much better use of the precision of floats if you use a smaller 'denormaliser', since denorm's by definition must have an exponent less than -126 - you have massively more dynamic range at your disposal for those IIR exponential decays!
P.S.) Maybe this also explains your DC offset - by adding one, you are making all the numbers inside the de-normalising routine positive, very small negative inputs will end up as large +ve numbers and vice versa. So the effects of any truncation will be very asymmetrical either side of zero.
adamszabo ·
Very nice phaser! How would I be able to add a feedback control? Should I route the signal back in the chain?
infuzion ·
DigitalWhiteByte wrote: The maximum error for this approximation is (1.5 * 2-12).
According to my tests it seems to be bigger when compared rccps,multiply with divide. Even when I slow down the triggers to prevent missed Mono reads, it still is ~0.0007 difference.
cyto ·
trogluddite wrote: The value used will make a difference to the accuracy of the result after the subtraction.
The aim is to push sufficient bits out of the mantissa to force a denorm to zero - but ideally to keep the number as small as possible to avoid pushing bits out of the mantissa of a desired normal number, reducing it's precision.
...
So you can make much better use of the precision of floats if you use a smaller 'denormaliser', since denorm's by definition must have an exponent less than -126 - you have massively more dynamic range at your disposal for those IIR exponential decays!
Interesting. I think one of my biggest stumbling blocks is having a hard time "envisioning" the representation of the numbers inside the computer. Your explanation (as always) makes perfect sense. If I have time later, I'll look into tweaking that part a little and seeing if I can improve on the resolution of the effect. If your suspicions are correct about the DC offset (and from your explanation, it seems perfectly sound), that would make me very happy as that little "side effect" has always driven me crazy! The hackjob solution for removing the offset was essentially me waving the white flag on the issue. Thank you for your insight.
-cyto
trogluddite ·
re: rcpps
I think we need to be clear quite what we are measuring here - the absolute difference is not a particularly useful guide, because the magnitude of the result depends on what number you divide by...
e.g. in infuzion's test schematic, the denominator knob is scaled -1 to +1 - but the resolution of the knob is limited by the mouse resolution - you can get as big an error as you like by reducing the knob range to ever decreasing values. Try dividing by 1e-12... wow the error is about 1.9e+08!!
What matters is how many bits of precision there are, which we can then scale according to the expected magnitude of the result. So DWB's description missed one important word - the figure he quoted is the relative error - the 2^12 part of which strongly hints that rcpps uses only 12 bits (makes sense - so does the sqrtps opcode, IIRC).
More Info HERE.
re: denormalising
Excellent article HERE. - which also includes a nice description of how rounding errors can lead to stable non-zero values in filters.
I also found recommended by well-regarded DSP guru Toby Bear,, that his own preferred denorm value is 1.0e-24.
cyto ·
adamszabo wrote: Very nice phaser! How would I be able to add a feedback control? Should I route the signal back in the chain?
I'm glad you like it 🙂
Regarding a feedback control. As-is, the effect really doesn't have an obvious place to implement such a control. One of my main reasons for starting this little guy was to develop a phaser that didn't rely on delay-line AP filters. The signal pretty much just goes straight through. Without any delay lines, there really is no good place to inject a feedback signal.
Having said that, I am intrigued by the idea. How to go about it would depend on the desired result. Going through the signal flow in my head, I don't think think there's any way to integrate this into the actual design of the effect. What I would suggest (and this is all just theoretical) is to introduce an external delay line. Go into the "Scaler" module and take a tap from there. I would then incorporate a delay (either fixed or modulated) on that tap and send it back into the the inputs of the "Stage1-2" module. Obviously you would need to mess around with the summing math in there to prevent the signal from getting too out of control. You could then integrate a knob to adjust the amount of tapped signal that gets fed back into the effect.
I recommend a delay because I worry that by just sending a direct signal back into the AP filters, you run the risk of negating there effect. Ideally, an AP filter inverts the phase of a defined band of frequencies. If you run the signal back through again, (theoretically) that frequency band will be inverted again and the result would be the same as the original signal. Now, I can attest that these filters are FAR from ideal, but nevertheless I think going the route of direct feedback would yield pretty unusable results.
If you try the external delay line, you'd be moving into "flanger" territory. Depending on how its implemented, it may give this effect some additional character. Please feel free to experiment and post your results back here.
-cyto
adamszabo ·
I guess i need to forget about the feedback, but I have another question instead. How would I be able to get odd stages? Like 1, 3, 5?
cyto ·
adamszabo wrote: I guess i need to forget about the feedback, but I have another question instead. How would I be able to get odd stages? Like 1, 3, 5?
Hey Adam.
This is definitely do-able. In order to accomplish this, you need to understand how the filter sections process the stream data. Essentially, both left and right channels get processed twice (once for each "stage"). In each filter block, incoming data from the left input gets processed in channel [0] of the "thru" variable. Incoming data from the right input gets processed in channel [2] of the "thru" variable. The assignment happens in the first few lines of code ("PrePack")...
After the input data is processed through the filter algo, instead of being sent directly out, it gets assigned into channel [1] and [3] of the "thru" variable respectively in the "PostPack" routine...
By grouping the "stages" into two like this, it is possible to use all four SSE channels to process the data (though it does also introduce one sample of latency). So in order to access the "odd" stages of the effect, all you need to do is assign the data to an output after it has gone through the filter once, but before it goes through the second time. Though instinct might tell you the obvious place to do this is in the PostPack routine, I think it would be better to do it in the PrePack routine so the outputs (stage1 and stage2) are not offest by a sample. This is how I would do it (I renamed the outputs so its easier to keep track of what's what)...
You can copy and paste the above code into an assembler primitive and use it as a direct replacement for the filters in the effect. I indicated all added/changed lines with a "//!!!" comment. With the new code, instead of just having the left and right ouputs, you'll have stage 1 outputs (outLeft1/outRight1) and stage 2 outputs (outLeft2/outRight2) that you can send to the selectors. If you use this, make sure it's the stage 2 outputs that get sent to the next filter block.
Hope this all makes sense.
-cyto
adamszabo ·
Ah thank you for that explanation! I am still learning about all of this packing, and eax, so I jumped into the deep water before I could swim but I am learning how to swim fast 🙂
I am trying to make it mono only, I do not need separate calculations for left and right, so I tried to remove some code. IS this correct? Or I can remove more stuff?
also I just noticed, couldnt we save an even more tiny cpu by removing "movaps modPass,xmm1;" and connecting the same lfo directly to all? Or are you doing something special with the modpass?
trogluddite ·
To make it mono, rather than removing some code, it would be better to add a tiny bit of code so that each module computes four stages rather than two - you would only need two assembly blocks then, and a lot less CPU.
cyto ·
trogluddite wrote: To make it mono, rather than removing some code, it would be better to add a tiny bit of code so that each module computes four stages rather than two - you would only need two assembly blocks then, and a lot less CPU.
Yeah, there are a number of different ways to route this, depending on your needs (some more efficient than others). The thing to keep in mind, though, is that pesky one sample latency per each additional stage. If you route it mono (four times through), you'll end up with three samples latency. If this is a concern, then you'd need to take that into consideration in your switching/summing sections.
-cyto
cyto ·
woops! Adam, I didn't see your reply earlier and was just responding to Trog's.
adamszabo wrote: Ah thank you for that explanation! I am still learning about all of this packing, and eax, so I jumped into the deep water before I could swim but I am learning how to swim fast 🙂
I'm new to this ASM stuff, too, and still learning 😉
adamszabo wrote: I am trying to make it mono only, I do not need separate calculations for left and right, so I tried to remove some code. IS this correct? Or I can remove more stuff?
Your code looks perfectly fine, but like Trog said, you can optimize it even further by making a few more assignments to use the last couple of SSE channels and only using two filter blocks (ie. move final[1] to thru [2], final[2] to thru[3] in the PostPack routine, thru[2] to out3[0], thru[3]to out4[0] in the PrePack routine) or something of that nature. Doing so would change it from a 2channel X 2stage filter into a 1channel X 4stage filter. Keep in mind the latency issue that i brought up in the previous post, though.
adamszabo wrote: also I just noticed, couldnt we save an even more tiny cpu by removing "movaps modPass,xmm1;" and connecting the same lfo directly to all? Or are you doing something special with the modpass?
Yeah, that line is in there for the sole purpose of keeping the schematic a little cleaner. There's nothing magical going on. You can remove it, no problem, and make the connections directly.
-cyto
cyto ·
It just dawned on me after writing the previous post: if you were going to make a mono version this, you would need to address some of the issues of channel assignment in the LFO code and in the frequency band assignment code. Right now, they are configured to exploit the 2 X 2 method of processing the incoming data. Moving to a straight mono (1 X 4) scheme would necessitate changing the channel assignments around a little bit. You could get away with removing the "counter2" routines entirely in the modulation block and just assigning the counter1 results into all four channels of the LFO output. Likewise, the function of the frequency band assignments would remain essentially the same, but the channel assignments and labeling would need to be addressed. I don't have the schematic in front of me right now, but if I have time later, I'll open it up and see what else this might entail. It shouldn't be too difficult to re-make this entirely in mono (and it would certainly be faster).
-cyto
trogluddite ·
Here's a generic little bit of assembly for placing four stages in series using the SSE channels.
It uses the shufps command, which is a very economical way to swap the SSE channels around all in one go...
(EDIT: This download has been updated to correct the mistakes mentioned in the following posts)
...as before, the penalty is that each extra stage incurs a one sample delay
PS) Another handy use for the 'channel rotating' technique - you can chain four delays. Although it may seem that there is little advantage (why not just increase the delay time of one stage of delay?), it can have a few advantages...
1) You get 4 taps from the delay line - which can all be different lengths.
2) Smaller buffer size - buffers always allocate memory for all 4 SSE channels, so you can save memory if you use all four.
3) Less cache misses - these really slow access to the buffer contents and can cost a lot of CPU clocks on large buffers.
aliasant ·
trogluddite wrote: Here's a generic little bit of assembly for placing four stages in series using the SSE channels.
It uses the shufps command, which is a very economical way to swap the SSE channels around all in one go...
...as before, the penalty is that each extra stage incurs a one sample delay
PS) Another handy use for the 'channel rotating' technique - you can chain four delays. Although it may seem that there is little advantage (why not just increase the delay time of one stage of delay?), it can have a few advantages...
1) You get 4 taps from the delay line - which can all be different lengths.
2) Smaller buffer size - buffers always allocate memory for all 4 SSE channels, so you can save memory if you use all four.
3) Less cache misses - these really slow access to the buffer contents and can cost a lot of CPU clocks on large buffers.
¨There was a small error in the code 🙂
The unpacker in the output stage read "stage1" instead of "stage2"
Great work as usuall !
aliasant ·
About the "four stages in series"
I made a little code were I want to add all the channels together at the end.
Instead of sending out each channel on its own monoout I want to do this: SUM = stage0 + stage1 + stage2 + stage3;
For testing I used a Packer and then this code to do it but I would like to do it all in the same code.
How do I do that?
//Add 4 channels together
streamin in;
monoout out;
The fld and fstp instruction are just using the floating point unit's stack as a temp variable so that you can isolate each channel (same principle as a push followed by a pop)- the important thing is that the mono in's and out's will always use SSE channel [0].
There's also an alternative way, using the eax register...
mov eax, in1[0]; mov packed[0],eax; etc.etc.
...I've never noticed a significant CPU difference between this and using the FPU stack, but maybe someone will know if one or the other option in better under whatever circumstances. I just use the fld/fstp method coz it is less typing!
Oh, and thanks for correcting my typo! 🙂
trogluddite ·
@aliasant
Not sure I read your question correctly...
If your code is already mono4 all the way through, then just put your 'adder' section instead of the 'unpack' section of my "chainer".
As long as the output value is copied into the "thru" variable before doing the adding, the chaining will still work, as the hard work is all done in the first bit of code, before the effect routine - the unpacking is purely to prepare the mono outs, it has no effect on the feedback loop.
aliasant ·
trogluddite wrote: @aliasant
Not sure I read your question correctly...
If your code is already mono4 all the way through, then just put your 'adder' section instead of the 'unpack' section of my "chainer".
As long as the output value is copied into the "thru" variable before doing the adding, the chaining will still work, as the hard work is all done in the first bit of code, before the effect routine - the unpacking is purely to prepare the mono outs, it has no effect on the feedback loop.
Im pretty horrible when it comes to explaining myself.
Heres an osm.
I want to get rid of the packer and move the second asm into the first and still get the same result.
I have tried different ways with no success.
trogluddite ·
Ah yes, wise move - silly to unpack and then pack again.
It's how I described in my second post - here's the modified schematic...
The important thing here is that with those shufps values, the adder ends up with the right answer in all four SSE channels, no matter which order the stages get processed in. That makes t certain that sum[0] will contain the correct answer, so there's no need for any extra 'channel swapping', it can go straight to the mono out.
aliasant ·
trogluddite wrote: Ah yes, wise move - silly to unpack and then pack again.
It's how I described in my second post - here's the modified schematic...
The important thing here is that with those shufps values, the adder ends up with the right answer in all four SSE channels, no matter which order the stages get processed in. That makes t certain that sum[0] will contain the correct answer, so there's no need for any extra 'channel swapping', it can go straight to the mono out.
Aah.
I tried doing that but before the thru feedback thing.
That is the one I dont really get but thats ok.
This worked out but theres another problem with cycles usage when i combine this code with a simple HighShelving filter i have.
Cycles for your code is about 11
Cycles for my Shelving thing is 13.
If I connect the shelving AFTER yor shuffle code I get something like 1249 cycles ! ?
If I connect the shelving BEFORE your shuffle I get about 30 cycles.
Problem is I HAVE to have it after :/
Is there some kind of xmm register loop or something?
trogluddite ·
Aaah, I have the answer.
Partly a problem with your filter, and partly a "Trogluddite Special Feature" 😉
Connect up Float->Filter->Cycle Analyser - type an audio range value into the float (say, 0.1) - now clear that by entering zero - you will see the CPU sky-rocket!
Likewise if you feed in any non-zero value, the other problems with the high CPU usage disappear.
That is because the filter has no protection against de-normals. As long as there is a non-zero input value it will be fine, but as soon as the input falls back to zero, the decay of the filter impulse response will always lead to de-normals.
This will need fixing- otherwise your filter will use mega-CPU every time your audio track goes silent, or when you stop the host transport.
The quickest fix for that is to define a very small float, and also the negative of that value
float Fix=1.0e-24;
float negFix=-1.0e-24;
Then, in your filter calculations...
addps xmm0,Fix;
addps xmm0,negFix;
Where xmm0 will be one of the filter 'buffer' variables - which one(s) varies according to the algorithm, so just experiment till you find the right place to put the anti-denormal code.
Earlier in this thread, I posted a link to some articles that describe denormals in more detail, and why that fix works.
Now, his also means that the "chainer" must be sending a brief non-zero value whenever the audio is started up (or when the analyser triggers it).
When I looked at the code, I saw the reason...
float thru=9;
So at startup, the chainer sends multiples 9, for four samples until the input value works its way though the four SSE channels. Then, in the absence of an input, it drops to zero, and triggers the denormals n your filter!
Of course, that declaration should read...
float thru=0;
...obviously, I did that on purpose to test you all - as I have never ever released code with tpying mshtakes in before!! 😊
Oh dear, time to apologise to the 20 people who already downloaded the code. Sorry everyone!
Here's the fixed schematic...
aliasant ·
LOL!
I was wondering about that mysterious 9 🙂
Thought it might be some kind of ASM voodo trick so I didnt dare touch it.
Thank you Trogg.
You are an ACE!
adamszabo ·
I have dived into the phaser again, and I am still having troubles understanding how the frequencies work here. In the properties I have set very narrow frequencies, I wanted to see how it sounds when the bands are close to each other, but they are still pretty far away. I have posted a spectrum of how it looks like. I was expecting something like a red curve, which I drew myself. Is it possible to achieve something like that? Note: I am only working with the left channel, disregard the right.
cyto ·
Adam,
That's not good 😮 I should have done more extensive testing. I have figured out what's going on and how to fix it, but I don't quite know exactly why this is happening (maybe somebody here can shed some light on the phenomenon). Basically, the problem is occurring because of the series topology of the design (AP->AP->AP->etc). For some reason, by feeding one into the next, the frequency response of each one affects the response of all others in the series (change the critical freq of one and they all change 😕) . This is absolutely not good esp. with all the modulation going on.
The solution I found that works really well is instead of mixing the original signal back in cumulatively after the series of filters, you have to mix it in incrementally after each filter. Which shouldn't be that hard (give me a couple days and I'll post a new version).
I made a quick little demonstration so you can see what's happening.
It's just a white noise osc with an FFT display. In between, there is a module that basically has two AP filters (the exact same ones from the phaser, just stripped down to remove all the packing stuff). There is a switch below that selects between the two different methods so you can see the difference. The "cumulative" mode is the old (wrong) way. The "stepped" mode is the new right way. As you'll see, you will be able to get the critical bands right up in each others' faces.
I'll post a new version (if anyone has ideas for other improvements, now would be a good time to speak up). And if anyone could explain the phenomenon in mathematical terms or otherwise, I'd be very grateful (I have some ideas, but they are just conjectures).
Thanks, Adam, for pointing this out!
-cyto
infuzion ·
Cyto, I don't understand why you use a whole number, rather than a tiny number that denormals are usually used for:
//(prevent subnormal math)
addps xmm6,one;
addps xmm6,negOne;
I kinda prefer to see the Code or C/pseduocode version with the ASM; makes it easier to trouble shoot & optimize. I kinda can figure it out since I'm so used to ASM & it is small, but try reading an ASM version of say an HAHDSR envelope... a well I see 2 ways to improve the ASM anyway.
cyto ·
infuzion wrote: Cyto, I don't understand why you use a whole number, rather than a tiny number that denormals are usually used for
Infusion, this isn't the first time you've nagged me about this (see earlier in this thread - for the exact same block of code even) 😉 . Honestly, I just wanted to get a quick example up (so as not to leave Adam hanging) and I grabbed an older version of the code. When I put up the fix, everything will be right in the world 😇 .
Regarding the psuedo-code, see the latest download of the effect. Everything is commented very well in their. Essentially, as a frame of reference, this is just using the standard "direct-form 1" filter. The coeffs are calculated "quadratically" instead of "trigonometrically" (my method is significantly faster), but the results are exactly the same as the RBJ method which I'm guessing most people around here are intimately familiar with. So for point of reference, just assume those values.
I think the issue here is more "conceptual" than in implementation errors. I mean, it shouldn't matter where the original gets mixed back in (at least mathematically in my mind). But it does. And the way this messes with the subsequent filters just seems odd to me. They function perfectly normally, but just at different critical bands. My thought is that there is some convolution in the recursion of these things that is somehow rectified by the presence of the original 😕 , but I just can't seem to wrap my brain around it.
Anyway, thanks for taking the time to look (and for the nagging 🙂 ). If you have any ideas, please let me know.
-cyto
adamszabo ·
Thank you cyto! That works much better indeed! I cant wait to see your new version!
martinvicanek ·
[*]
cyto wrote: if anyone could explain the phenomenon in mathematical terms or otherwise, I'd be very grateful
-cyto
Hey Cyto,
there is nothing wrong with the cumulative architecture, that's how phasers usually work. As you know, the resulting frequency response is an overall flat spectrum with a series of notches at frequencies that are not just multiples as in a common comb filter.
One (2nd order) allpass unit will give you a phase shift as a function of frequency, going from 0 at low freq up to 2Pi at Nyquist freq. At some freq - the one you set with the knob - the phase shift will be Pi. That's where you would get a notch if you add the output and the input, as in the stepped mode.
Now if you stack say two allpasses in a series, the total phase shift will be the sum of the two phase shifts and will go from 0 to 4Pi. At some freq along the way you'll get Pi and at another freq you'll get 3Pi/2, that's where the notches are in your cumulative signal path. As you correctly point out, these notch frequencies are different from the single-unit frequencies. That's because in the cumulative path, it's always both allpass units that contribute to the total phase shift. If the phase shift for one allpass is Pi, the other phase shift will generally be nonzero, hence the total phase shift will not be equal to Pi at that freq.
It is straight forward to apply that reasoning to more than two stages.
I understand the objective is to get the notch frequencies closer together. You can do that with your stepped approach, however then the frequency response in between will not reach unity. Eventually, if you choose the notch frequencies very close to each other, they will coalesce into a single notch.
If you stay in the cumulative path, you can vary the Q value to change the notch spacing. Attached is a demo.
cyto ·
Martin,
Thank you, thank you, thank you! 🙂
It's one thing to witness a phenomenon, it's quite another to understand it. You're explanation makes perfect sense and is more than I could've hoped for. Now, to apply it to a practical solution for this thing...
-cyto
cyto ·
New version!
This new version offers a lot more flexibility than the older ones. Though the main panel still retains the same controls as before, the properties panel now has many new options that allow far greater control over the effect. Highlights include:
ADJUSTABLE Q; Now you can get those "head spinning" psychedelic sounds that you know you've been craving! Allows for adjustments from .5 (which was the original fixed Q in the first versions) all the way up to 64 (in case you want to get that surgically precise). This new version also has an (experimental) auto-Q function that analyses the frequency settings of the effect and automatically ajusts the individual bandwidths of the AP filters for minimal phase-shift interaction (I have not experienced any problems using this, but still you should fully test your project if you use this setting).
FEEDBACK CONTROL: Adjust the mix of the original signal to the AP signal (including negative feedback!).
TWO MODES: You can select between the traditional "cumulative feedback" topology or the "stepped feedback" topology (each mode has a distinct "color" that may be more appropriate for your project).
In addition to those obvious changes, there are many more "behind-the-scenes" improvements. More "elegant" signal chain, cleaner/more robust algorithms, a more effective subnormal prevention mechanism, etc, etc, etc. This version is not nearly as well commented as the other ones, but if you have any questions abut what's doing what, ask them here and I'll be happy to answer (you can also look at the older versions which are very well commented to see the progression to this point).
All of the changes are in the properties panel, but it should be pretty obvious how to move them to the front panel if you so desire (hint: go to the bottom of the main->phaserGuts->Math,etc module).
Please let me know what you think. I did not do a whole lot of extensive testing, but what testing I did do did not expose any problems with the effect. However, if you do notice anything horribly wrong, please report it here and I'll address it.
Enjoy!
-cyto
adamszabo ·
Thats really nice Cyto!
I tried it with a noise, and I am hearing a lot of pops? I presume its not supposed to be like that? Maybe it has some denormal problems or so? Oh, do you think you can post an alpass in mono for me, I would like to experiment a bit with it, but I really dont dare to touch it haha.
infuzion ·
Looks good!
cyto ·
adamszabo wrote: I tried it with a noise, and I am hearing a lot of pops? I presume its not supposed to be like that? Maybe it has some denormal problems or so? Oh, do you think you can post an alpass in mono for me, I would like to experiment a bit with it, but I really dont dare to touch it haha.
Oh! You discovered a weakness in my subnormal prevention thingy 😉 . Those "pops" are the signal detector turning on and off (you can hear the same thing with long, sustained sine waves). I'll figure out way to address that. Later tonight, when I have a little time to devote to this, I'll post a fix and also a general-purpose AP filter for you to play around with. After Martin's explanation about the nature of the series AP architecture, I found this link very helpful: https://ccrma.stanford.edu/files/papers/stanm21.pdf. Maybe you'll find it useful as well.
-cyto
trogluddite ·
Keeps on getting better! - and thanks for the link, that looks like a very useful recipe to add to the cookbook. 🙂
Not so sure about your denormalising method - IMHO it seems rather complex, I have always got good results from using the 'add/sub' method within the bi-quads, or adding a tiny bit of 'dither' to the input. But admittedly, I haven't tried this with an allpass design like yours; would need testing to be sure that they don't compromise the beautiful sound quality.
cyto ·
trogluddite wrote: Not so sure about your denormalising method - IMHO it seems rather complex, I have always got good results from using the 'add/sub' method within the bi-quads, or adding a tiny bit of 'dither' to the input. But admittedly, I haven't tried this with an allpass design like yours; would need testing to be sure that they don't compromise the beautiful sound quality.
Yeah, I'm probably going to go back to an add/sub type thing. My only issue with that type of method in bi-quads is that (as of yet) I haven't found a good way of implementing it that doesn't result in sometimes a tiny bit of dc offset. I've had pretty good luck with this "signal detector" method in some much larger projects I've been working on that have a lot of filters that need correction as a function of the input. This way, I can just take one little tap from the input signal and send a "status" line out on every sse channel to all the filters that need their coefficients "cleared out". The problem, as Adam astutely noticed, is that certain signals (repetitive in nature) "lock in" with the timing of the detection and cause it to switch on and off. Not good! Should have tested it more thoroughly. A fix is in the works.
-cyto
trogluddite ·
Yes, it makes more sense to me now - in a sufficiently complex schematic, the CPU load of the 'detector' would be offset by the fewer opcodes needed in the many other modules, and you get rid of even the tiniest of artefacts.
Thanks for the explanation: always good to see a problem approached from a different direction.
cyto ·
Okay, I fixed the pop problems that were apparent in the latest version. I just went back to the add/sub subnormal removal technique. I have updated the files in both the first post of this thread and the post (a couple ones back) with the "third update" file. Let me know if anymore problems show up.
Per Adam's request, here's just a generic version of the AP filter from the phaser for you to do with whatever you like (can be used for any stream type - mono, poly, etc). Any questions, ask them here and I'll be happy to help!
ATTENTION! this is a new file (oct 23, 2011) that fixes a tiny error (I'm too embarrassed to admit what it was). Sorry to the 20some people that have already downloaded it.
-cyto
adamszabo ·
Thanks cyto its working great! Now lets see if I can try to emulate the phaser from my virus :P
adamszabo ·
Hey cyto, I have been experimenting with your suggestion, where I can use the packing of stereo signals to have odd or even stages. I tried to see if it works, but I get wierd artefacts. You can see if I have the single ap filters after each other, I get it correct, but the packed one is strange.
Ideally I would have liked to use: fld in; and fstp thru; but I dont know how to use those yet. Can anyone also shed some light what those numbers in the square brackets are when packing, unpacking: [0], [1]? It is not documented anywhere.
trogluddite ·
The figures in brackets represent the four channels of a mono4 stream.
Normal SSE code (the ____ps opcodes) process all four in one go, so no brackets are needed.
However the FPU maths (f____ opcodes) can only do one stream at a time, so when you use those instructions you have to tell it which channel to load or store the values from.
For a mono stream, only channel [0] is used.
Note that you can only 'fld' (push to FPU stack) and 'fstp' (pop from FPU stack) from memory (i.e. a variable or input/output), you can't do it direct from the xmm registers.
cyto ·
To expand a little on what trog already stated...
I like to think of each of the SSE channels a a "chute" through which data flows. At any given time, you have 4 "chutes" to work with and each one gets processed simultaneously. You'll notice in the generic AP filter, there is no assigning of data to any channel, so one of three things happens:
1. If you hook up a mono stream to it, data only gets processed in the [0] "chute." All other channels just pass 0's.
2. If you hook up a pack-4 mono stream, data gets processed simultaneously: in[0] gets processed and the result is sent to out[0], in[1] gets processed and is sent to out[1], etc. If there are any empty channels, they just pass 0's.
3. A poly stream is processed the same as a pack-4 mono and it's up to SM to determine which channel any given voice is assigned to, but it still gets processed consistently in[0]--(process)-->out[0], in[1]--(process)-->out[1], etc.
The difference between the generic AP filter and the one that is used in the phaser, is the "in" and "out" variables are not directly linked to the filter effect. They are simply input and output "holding areas". Instead, the phaser filter has "thru" and "final" variables. So it is processed like: thru[0]--(process)-->final[0], thru[1]--(process)-->final[1], etc. The "prePack" and "postPack" routines are basically just shuffling the data around. Essentially, the process looks similar to this:
in[0]--->thru[0]--(process)-->final[0]--->thru[1]--(process)-->final[1]--->out[0] (for the left side)
or in[2]--->thru[2]--(process)-->final[2]--->thru[3]--(process)-->final[3]--->out[2] (for the right side).
Or, to continue the metaphor, the left signal comes in, gets sent down the [0] chute then (instead of going out) gets sent down the [1] chute. Then it gets sent out.
One thing that is important to understand is the distinction between "registers" and "variables." Each block of code is processed at sample rate. Registers (xmm0, xmm1, etc.) have no memory between samples. For instance, if at the end of a certain block of code some value gets assigned to xmm0. The next time the code runs (the subsequent sample) xmm0 will not remember the previous value. Variables, on the other hand, hold their values until they are reassigned. So if at the end of a block of code, you assign a value to thru (or any of its channels-thru[0], thru[1],etc.), those values will be available to you the next time the code is run. So, in the AP code, at the end of the routine, I assign some of the "final" channels to channels of the "thru" variable. When the code runs for the next sample, those values are still there. That's how I go about routing the output of channel [0] to the input of channel[1].
Does this make sense?
Hopefully, that will give you a better understanding of how to "route" your data thru the filter. Each channel is a "chute" and you can use any type of assignment to move the data from chute to chute, using the packing commands that trog mentioned previously. Just remember that each "process" takes one sample. So if you put the output of one channel into the input of another, you are introducing one sample of latency into the effect. Also, you should know that these two operations are effectively the same...
fld InA[0]; fstp Out[0];
///IS EFFECTIVELY THE SAME AS///
mov eax,InA[0]; mov Out[0],eax;
Hope that helps.
-cyto
cyto ·
Adam,
I should have looked at your schematic before I went on that long diatribe 😊 . But hopefully after reading that you can better understand the fix to your "weird artifacts". What you are hearing (or seeing) is the result of not "lining up" the dry signal with the wet signal. As I (hope I) illustrated in the previous post, the shuffling around of the signal through the different channels results in some latency (one sample per iteration). In your schematic, each of the "stage 1 & 2" modules introduces such latency. So to fix the weirdness, you just need to delay the dry signal by the same amount, like so...
-cyto
adamszabo ·
Thank you! You guys shoud definitely write a book about these things 🙂
One last question (I hope 🙂 )
When I add a little feedback loop, so I get peaks instead of dips, they look different. If you change the float variable "a" to zero, they will look the same, but when you enable the peaks the packed one gets and extra annoying peak, and does not look like the ap filters below. Is this due to delay too?
cyto ·
adamszabo wrote: When I add a little feedback loop, so I get peaks instead of dips, they look different. If you change the float variable "a" to zero, they will look the same, but when you enable the peaks the packed one gets and extra annoying peak, and does not look like the ap filters below. Is this due to delay too?
Hey Adam,
I've been racking my brain to come up with a solution for this, but unfortunately I don't think it's possible. The problem lies in the fact that the "summing" code block is contained in a feedback loop, where the input is dependent on the output. In the bottom example of your schematic, the signals are offset by one sample due to the feedback delay. In the top example, they are offset by three samples (one feedback delay+two samples from the AP code). Once the feedback loop gets "set in motion", there is no way to go back in time and recover those samples. You can't delay the dry signal going into the summing box, because that would effectively just be a group delay for the whole effect. Sorry, I really wanted to find a solution, but I don't think there is one that uses the "packing" schemes we've been talking about.
I will point out, though, that you can create those "peaks" without introducing any feedback by simply inverting either the wet or the dry signal before they are mixed together. You can also mix the summing at the end of the effect to change the "depth" of the peaks or notches. Right now they are 50/50. There's no reason they can't be 20/80, 45/55, -50/50, -90/10, etc. I integrated that feature into the latest version of the effect in the "properties" panel, but the math for it gets really obscured in various assembly blocks. It really is just simply a matter of adjusting the mix ratio of the two signals (and optionally inverting one).
-cyto
adamszabo ·
Hi Cyto,
Thank you for taking your time, and helping. It cant be done? Ah well...
Are you talking about inverting the signal like this? Because it doesnt really give me the peaks like the top one.
cyto ·
adamszabo wrote: Thank you for taking your time, and helping. It cant be done? Ah well...
Never say never 😉 , but right now my brain can't figure it out.
adamszabo wrote: Are you talking about inverting the signal like this? Because it doesnt really give me the peaks like the top one.
Yes, that's exactly what I was talking about. With a little attention to the individual stages, you can get it somewhat close...
Don't be afraid to crank that Q! 😈
-cyto
Wrote something here? To have a post removed or your name hidden, write to [email protected].