I set dx = freq*0.2, which is just experimental, and modified the module "pitch to 0-1" as for scaling of temperament, since the standard "pitch to 0-1" would produce 1/4 tone progression in this case.
trogluddite ·
Nice, it seems very stable (I didin't see any change to the peak value over even a couple of minutes) and will use much less CPU than the regular sin1 function.
One thing I did notice is that it needs some sort of scaling - the amplitude is very frequency dependent, small at high frequencies, and well over the -1...+1 range for low notes. But still, it's very promising, and I imagine that the scale factor is probably a simple function of the frequency value.
martinvicanek ·
Kaj,
if you are into sine oscillators here is a nice summary of different approaches and their pros and cons:
Digital Harmonic Oscillators
yours is a recursive oscillator, and this species is found less on the forum than the others. So thank you for bringing this up!
Here is some good reading on recursive oscillators specifically:
Recursive Discrete-Time Sinusoidal Oscillators
It is as fast as the usual second order form and also remains stable. However, it performs better at low frequencies. You have to supply k externally using the green math cosine function which is more accurate than the cos1() in the Code Module.
I have also a nice stable recursive quad oscillator if anybody is interested.
kaj ·
Hello, trogluddite.
Thanks for advice.
You are right. Certainly when the frequency becomes low, its amplitude becomes loud.
So, I tried to add scaling factor, which is still experimental.
Hello, martinvicanek.
Thanks for information.
I am reading that articles, which are quite interesting.
kaj ·
To martinvicanek.
I tried your code. Yours is much more proper than mine as for manipulating frequency (I did not know K = 2*cos(2pi*freq/SR) in the equation Y[n+1] = K*Y[n] - Y[n-1]). Thanks.
martinvicanek ·
Oops, I made a mistake. I actually posted the code for the common second order form. What I wanted to post instead was this:
stage(0) // initialize
{
x = 0;
y = sqrt(k*(1 - 0.25*k)); // sin(2*Pi*freq/SR)
}
stage(2) // iterate
{
x = x + y;
y = y - k*x;
}
To make up for it, here is the OSM (including an optimized ASM 😉 ):
infuzion ·
martinvicanek wrote: Oops, I made a mistake. I actually posted the code for the common second order form. What I wanted to post instead was this:
To make up for it, here is the OSM (including an optimized ASM 😉 ):
ASM seems broken (try 200Hz), but your Code seems to work well!
trogluddite ·
martinvicanek wrote: What I wanted to post instead was this
Wow, that's small!
For the ultimate in low CPU, if you calculate -k in stage(0) (or outside in the green) then you can remove a movaps, replace a subtract with an add, and do it all with only one register...
And thanks for those links - really helped me to undestand what these oscillators can be used for in terms of accuracy, stability and modulation.
martinvicanek ·
Good catch, Trog! BTW how much CPU does a moveaps instruction require compared to addps or mulps?
Infuzion, are you sure my ASM is broken? I thought I had tested it, but well, there's always a chance to mess up. One "error" that I can confirm from here is in the comment:
streamout x; // sin(2+Pi*freq*t)
should of course read
streamout x; // sin(2*Pi*freq*t)
kaj ·
martinvicanek
Wow super minimal! What is excellent in your code is that you properly set k's value. Thanks.
kaj ·
Now, I tried to implement sine oscillator via Chebyshev polynomial approximation, which runs faster than sin1().
y = -4.03269040428981204e-14 + x * (0.9992758718784105 + x * (1.53554110115136817e-13 + x * (-0.165666986 + x * (-7.45854078295546902846e-14 + x * (7.9580620708197569e-3 + x * (8.2768291285043e-15 + x * (-1.450770066e-4)))))))
[-3.1415 <= x <= 3.1415]
trogluddite ·
martinvicanek wrote: Good catch, Trog! BTW how much CPU does a moveaps instruction require compared to addps or mulps?
He he, well I was being a bit of a purist.
There are several web pages around with cycles-per-opcode tables, but they are notoriously unreliable with modern CPU''s due to the way that different opcodes use different channels etc. For example, on many CPUs an add and a multiply can be executed simultaneously, as long as neither of them is dependent on the result of the other.
When ASM optimising, II generally just work on the following rules of thumb (plus lots of empirical testing of CPU consumption!)...
- Make full use of registers to save on memory (variable) accesses. On large code blocks, SMs code compiler is hopeless at this; it only goes beyond xmm1 when really pushed by a long maths expression. Using the full range of registers up to xmm7 can sometimes eliminate a lot of intermediate 'temp' variables. Ideally the 'essential' variables, that must be saved between samples, would only be read and written once per iteration.
- Hierarchy of maths functions - i.e. the ones that were tricky in lessons at school are tougher on CPUs too!
- For arrays and tables, keeping 'associated' values closer together to cut down on cache misses (e.g. interleave left and right stereo data in one array rather than using two separate ones). A single cache miss can cost tens of clock cycles waiting for the main memory to be accessed - another reason to be as economical as possible with 'temp' variables.
Plus, there are many 'tricks' that massively tighten up SMs compiled code, for example...
int ABSmask = 2147483647
movaps xmm0,MyFloat;
andps xmm0,ABSmask; //Masks off the sign bit to give abs(MyFloat)
movaps xmm0,MyFloat;
cvtps2dq xmm0,xmm0; //Convert to integer data type
cvtdq2ps xmm0,xmm0; //Convert back to float to give rndint(MyFloat);
Fortunately, you are obviously already very good at the hardest bit of all - which is to have a nice tight algorithm with optimally factored equations. If you don't start there, then ASM optimising will be like trying to push a piece of string!
lesobrod ·
Hi, guys! Now I'm study a very similar things. So in fact there are several step-by-step (recursive) Sine OSC modells.
Very good explanation I found here:
http://www.dsprelated.com/dspbooks/pasp ... ators.html
It seems SinOsc in attach is as simple as possible (?_?)
martinvicanek ·
Here is a three line recursive quadrature oscillator. It is at least as fast as the coupled form oscillator, however this one has better stability (no exponential growth or decay). The amplitude will remain constant even if you change the frequency while it is running.
stage(2)
{
tmp = y + k1*x;
x = x + k2*tmp;
y = tmp + k1*x;
}
I haven't seen this algo in the literature, although I am sure somebody else invented it long before me 🙂
I am attaching an OSM with optimized ASM
trogluddite ·
Great work as ever, Martin.
I can get the amplitude to change sometimes by swinging the frequency rapidly up to the very highest values - but I guess without double precision for the trig functions, that is near impossible to achieve without power correction.
martinvicanek ·
trogluddite wrote: I can get the amplitude to change sometimes by swinging the frequency rapidly up to the very highest values - but I guess without double precision for the trig functions, that is near impossible to achieve without power correction.
I guess when you spin the knob too fast then k1 and k2 are not updated simultaneously but rather in a sequence of green triggers, hence for a short time k1 and k2 will not be consistent which causes the amplitude to change. Trog, is there a way of supplying simultaneous values for k1 and k2 in green? Or does it have to be done in code (that would be CPU demanding)?
trogluddite ·
martinvicanek wrote: Trog, is there a way of supplying simultaneous values for k1 and k2 in green?
The way that 'green' values are passed to the stream sections is not at all well explained, but there are definitely issues with the synchronisation when passing values - presumably because green and stream operate on different CPU threads.
You can see our experiments on the Green 2 Stream - Missed Triggers thread.
As the tests there show, it is even possible for a green value to be missed completely by the stream parts. The randomness in when the error occurs definitely points to something like that - so I'm thinking that your analysis of the oscillator problem is probably right, and it will require the coefficients to be done in stream to fix the issue.
That requires solving...
- The coefficient code must be hopped, otherwise the efficiency of your algorithm is undone.
- Ideally the co-oeff calculations should not be processed unless the input value is changed.
- Code trig functions are only 65536 entry lookup tables (not interpolated), and maybe this is not accurate enough?
- A larger, more accurate coefficient lookup table in a code array can't be built in stage(0) from green data (or loading a file).
Minimising the CPU footprint of the hopping, table lookup etc. is not too hard too solve, I think - but the maths for creating more accurate trig values I will have to leave you to ponder; it is way beyond my ability with numbers!
martinvicanek ·
trogluddite wrote: it will require the coefficients to be done in stream to fix the issue.
OK, I added a version which ensures consistent coefficients (and hence constant amplitude) even for fast modulation. The trigonometry is done in green math, whereas inside the code one can exploit some trig identities which involve only simple algebra. The coeffs are updated at hop(32) but I think it could be set to a smaller value if necessary.
lesobrod ·
My sincere congratulations, Martin (and Trog)!!
This OSC is the best "iteration-form", open-source Sine OSC;
please note that I've checked 5-6 like this.
FFT, amplitude, stability - OK. 😀 😀
Only if I start to move Freq knob madly fast up and down, ampiltude change on ~10%,
while another just give "#INF".
I look for open code form, cause it can be easily cnahge for different kinds of processes.
For example, if we take small (1e-6..1e-4) Damping coeff "d", prepare k3 = (1-d)/(1+d)
and add one operation to code:
stage(2)
{
tmp = y + k1*x;
x = k3*x + k2*tmp;
y = tmp + k1*x;
}
than we will have decaying OSC with natural, exponential decay (without modulation by some time-envelope).
(I forget about denormals... Just checking If abs(y)<1e-5 Then y=0)
Well, Martin, can I use this OSC on my new version of Bell-sound VST? 😇
Hope it will be good sound 😉
martinvicanek ·
lesobrod wrote: if I start to move Freq knob madly fast up and down, ampiltude change on ~10%,
while another just give "#INF".
Please try the fast modulation version in my last post on Sat Oct 29, 2011 5:24 pm. That one should withstand even the most furious knob spinning. 😉
lesobrod wrote: Well, Martin, can I use this OSC on my new version of Bell-sound VST? 😇
Sure, no problem, glad you find it useful! 🙂
cyto ·
If anyone is interested, I put a version of this great little oscillator together that accepts a normalized frequency input value. This may come in handy if you want to use this as part of a stream path or you want to modulate the frequency without resorting to the green math tangent function. There's nothing magical in the new version. Just a four-term Maclaurin series to get the K1 value. This obviously adds a few more operations, but (as in Martin's last version) they are hopped at 32. It is still significantly faster than the stock sine oscillator or SM's sin(1) function and is pretty dead accurate up to about half of Nyquist. Above that, it begins to drift ever so slightly.
-cyto
tester ·
Great sine'atics because they very fast; I will need a lot of such units in my projects (more than my CPU can handle using regular sine primitive; range from c.a. 0.001Hz on modulators to c.a. 10kHz on sound - which ones for which purpose?). Could someone include a node for phase offset and for external resync/reset state?
martinvicanek ·
Here is an update with added phase offset. I have included two versions: The first has green inputs for frequency/phase (good for static settings or asynchronous (=knob driven) modulation). The other one has stream modulation inputs hopped at 32 (adjust to your needs). Since the requirement was for 10 kHz I have replaced Cyto's Taylor expansion for the tan(x) function by a Pade approximation (a bit heavier on CPU but more accurate in the upper frequency range).
I haven't included the resync but that should be straight forward to do. The quad oscillator is more CPU efficient than the stock sine oscillator in its present form, however there is room for further optimization.
Time for me to hit the sack. 😝
tester ·
Thanks! Playing with accurate phase offsets at small freq differences or when multiple tones are running - can really change everything. Two quick questions. What is the difference (approximate level of inaccuracy) between "Taylor expansion for the tan(x) function" and "Pade approximation" in sinematic production? What is the practical meaning/result of changing hop values? (i.e. unpredictable/unwanted side effects)?
p.s.: green osc has something wrong with phase calculation (disconnect phase from SM sine to see it) and blue changes phase in opposite direction if I see correctly (but this one is a small detail).
martinvicanek ·
tester wrote: What is the difference (approximate level of inaccuracy) between "Taylor expansion for the tan(x) function" and "Pade approximation" in sinematic production?
The accuracy of the approximation determines the oscillator's frequency. I made a plot for 44.1 kHz sample rate below.
As you can see, the Taylor expansion yields good accuracy up to 10 kHz but shows some deviation above that while the Pade approximation shows very little error overall. So in fact the Taylor approximation is probably sufficient for most musical applications.
tester wrote: What is the practical meaning/result of changing hop values? (i.e. unpredictable/unwanted side effects)?
Hopping code saves CPU, however it also means that modulation input is updated less frequently. For fast modulation, especially in transients, too large a hop may result in jagged or choppy sound.
tester wrote: green osc has something wrong with phase calculation
Yup, well spotted, thanks! I divided instead of multiplied by 2Pi. Fixed in my original post.
tester wrote: blue changes phase in opposite direction if I see correctly.
Can't confirm that though. You sure?
tester ·
Thanks for explaining in details and modifications. I asked about hop values in a context of "how much is too much", because I have no image how this hop value refers to time and frequencies from audible range (where is the crucial point). I also asked form the difference between accuracy, because when involving multiple sines in one schematic - CPU starts to matter. I know that this one is very efficient, but I'm also aware, that in my typical scenario (simulating a soundscape made of multiple instruments) - on C2D 2GHz it will eat somewhere between 60 and 90 percent of resources.
Regarding phase direction. If phase of reference sine is constant (=zero; just disconnect knob), and second sine shifts left when it receives positive offset value, then according to my understanding/thinking - phase difference between two sines is negative (opposite to knob direction). Or did I missed something?
trogluddite ·
tester wrote: I asked about hop values in a context of "how much is too much", because I have no image how this hop value refers to time and frequencies from audible range (where is the crucial point).
The hop value relates directly to the sample counter - so for hop(N), the code is executed once every N samples (where N must be a power of two) - so values will update at a frequency of (SampleRate / HopValue).
Finding the ideal hop value is best determined by careful listening tests, as there are several factors which will affect the perceived quality...
- Speed of modulation change - for a sufficiently slow modulation source, larger hop values become more acceptable, as the difference between successive values becomes vanishingly small. So an LFO spanning -1/+1 at 50Hz would require a smaller hop value than an envelope decaying over many seconds.
- Modulation target - If modulating an amplifier, hopped modulation changes will create discontinuities in the output waveform for each "step" of the change. A "step" in the waveshape is equivalent to adding a single sample "pulse" to the waveform - such pulses exhibit a (theoretically) infinite frequency bandwidth (i.e. brief burst of aliasing distortion!). In contrast, modulating Cyto's oscillator causes the phase to advance at a different rate, causing a "bend" in the waveshape rather than a discontinuity - and thus may be more tolerant of larger hops.
- Algorithm - certain iterative algorithms can become unstable if modulation changes have too great an amplitude. This is a big reason why this oscillator is so cool - it is incredibly stable compared to many iterative oscillator designs.
For code, changing the hop value is as simple as editing the hop(N) line in the code. If you have an assembly block that you wish to alter, look for the block...
mov eax,ecx;
and eax,NNN;
cmp eax,0; //This line may sometime be absent, it is not strictly needed.
jnz Label;
In this case the NNN value should be the hop interval minus one (i.e. valid values are (2^n)-1).
However, you must be careful - changing the hop may require changes to other values in the code to compensate (look out for float variables which may be related to the hop value or its reciprocal).
From my experience, I tend to use hop(32) as my "default" value, and then tweak from there - but at hop(32) for all modulation sources, I've yet to find anything where I can perceive the difference.
tester ·
Thanks, slowly something begins to emerge in my head, what it's about 😉 although it still will need some time and practice to ground.
One thing regarding resync node. My guess is, that to reset externally - disconnect and reconnect the oscillator via some simple zero-timed multiplexer is required. But because I'm planning to use many of such sine units in one schematic - I'm carefully predicting that there might be some problem with that (with triggers and/or glitches). I'm afraid, I don't know how/where to add resync (reset state) function inside the code box. Could you help on that?
trogluddite ·
Resyncing is probably best done within the code.
If you use multiplexer/selector to force stage(0), it will do this for the entire schematic - you cannot selectively execute stage(0) for a single module (even poly signals can only do it for a whole "voice".) Resetting the entire schematic also has the disadvantage of generating a huge CPU peak, as it forces the whole schematic to be re-compiled.
Here's a version of the earlier schematic with triggering added...
The oscillator is reset to phase zero on the leading edge of a pulse at the trigger input. A lot of the new code is within the hop routine, so it shouldn't add too much extra CPU.
The new lines at the start of stage(2) are put there in case the trigger pulse is very short (falling completely during the non-executed hop cycles). If you know that your trigger pulses will be longer in duration, even these lines could probably go inside the hopping, for further CPU saving.
tester ·
Okay, thanks for that. Now it's time for me to do some homework 😉
cyto ·
Wow! This thing just keeps getting better and better. Martin, that pade approximation is great. I don't have any experience with them, but I will certainly be doing my research. Trog, excellent sync function, too! I couldn't resist doing a couple "optimizations" on the osc. I didn't really add anything new, but cleaned up a couple things to shave off a few more cycles.
Here are the changes: First, I put everything in one asm box. Now, the routines can "share" some of their variables. I simplified the resync routine a tiny bit. I replaced the sin1/cos1 approximation with just a generously hopped FPU routine (fsincos). My tests show this slightly faster than the approx. I think that's it. Because these are so lightweight to begin with, it's hard to tell what kind of CPU savings are to be had. But I chained like 20 or 30 of them together to compare to the older versions, and this one runs slightly faster than the last version.
This is such a great little oscillator. Thank you, Martin, for sharing it with us to begin with!
-cyto
martinvicanek ·
Great! Now it's all neat and tidy in one box. Thanks Trog and Cyto!
Just one minor note: In the latest version the phase modulation input is hopped at 256, while the freq modulation and resync inputs are both hopped at 32. As Trog explained, frequency modulation is less critical wrt hopping than the other two parameters. So for specific use one might want to adjust the hopping.
And since the whole thing is in ASM, wouldn't it be cool to have the hopping frequencies for the three inputs as module properties? 😉
tester ·
I have a different question. Here are two bundles, one filled with SM sines (100 units), other with modified sines. What CPU usage is showing your SM? Or - what CPU usage is showing your windows task manager? I ask for this, because in my test schematic (not included here) - I see confusing readings... Or is the difference appearing when sines receive modulation routines (i.e. input dynamic/fast modifications) ? One difference however is noticable on my laptop. SM tends to crash (when switching on/off audio) with multiple sinusoids from SM toolbox, no such issue with modified unit. So this one is very stable 😉
trogluddite ·
Hmm, the CPU meter readings are interesting here on my little Atom netbook (a stern test indeed!) - and they seem to confirm what you say about stablity.
The quad-osc's give a very stable reading of around 60% +/-3%. On the other hand, the reading for the stock oscillators is very unstable - oscillating wildly between about 55% and 80%.
This is very likely a reflection of the contrasting methods. The Quad-osc is iterating a handful of variables entirely using SSE instructions -so the memory addresses that require access are always in the same place, which makes for very efficient use of the CPU cache.
OTOH, the stock sine is almost certainly a lookup table. Firstly, array access can only be achieved using FPU/x86 instructions, which require the four channels to be "unpacked" and processed separately. More crucially, the lookup table will span a much wider area of memory, which is unlikely to all fit within the CPU cache simultaneously - generating far more "cache misses" and more regular access to the (much slower) main memory.
Assuming that this is correct, I would predict...
- Stock sines when locked in sync (i.e. all in phase), may be quite efficient, as they would then all be accessing the same index of the lookup table - once loaded for the first oscillator, it could remain in the cache for all the others, making the value very fast to access.
In this situation, the stock sine could possibly be more efficient that the Quad-osc - because the quad osc likely has more variables than a lookup oscillator, so slightly more jumping between memory addresses.
- Once we give the oscillators independent frequency and phase, the situation reverses. The lookup becomes very inefficient because each oscillator will be accessing wildly different array indices, making life very hard for the cache.
But the quad-osc should show very little change, as the memory addresses of the variables are no different than the "locked phase" case.
To put this in context - sometimes we can save a handful of CPU cycles by optimising an opcode here and there. Removing an unnecessary memory/variable access can save a lot more - it takes of the order of 5-10 cycles to access a memory value held in the top level CPU cache. But if the value you want is no longer in the cache, you can be looking at 50-100 CPU cycles to wait for the main memory to respond!!
I have made a few delays etc. where simply re-arranging the data structure has made more difference to performance than any amount of opcode tweaking. For example, for a stereo buffer, we could either use two arrays (left and right), or we could interleave the two channels within the same array. The interleaved case will almost always be more efficient, because the memory addresses for left and right indices will be sequential, and so much more likely to both be in the cache simultaneously.
So, if I am correct, the quad osc is not stable and efficient purely because the code is more lightweight - it happens also to be much kinder to the memory and CPU cache, especially in the case of large multiples with independent parameters.
cyto ·
Tested on my netbook (atom-don't know speed) and also my tower (i5 - quad - 3.0GHz). The atom machine showed same results as trog reported. The i5 showed in SM about 5.8% for stock and about 6.2% for the new osc. However, when I looked at the task manager CPU meter, both versions only (barely) registered 1%. So about the same for each. Interestingly, though, when I erased about 20 of the oscillator modules from each version, the SM reading was 1.3% for the new version and about 2.7% for the stock (TM reading 0-1% for both). Go figure! I should also note that I have two separate (ASIO) soundcards on the tower and each of them showed slightly different readings in SM (+/-2%).
I think in the grand scheme of things, as they are now, the stock and the new version are pretty comparable CPU-wise. However, in situations where you are using this many oscillators, you will no doubt be able to trim out some of the code in the new version (something you can't do in the stock osc). For example, I would imagine that there is no need to test for 100+ different sync triggers. More than likely, the oscillators will be "grouped" and can share a single trigger for each group. Likewise, I would imagine from (some of the discussion in other threads) that each osc will be free-running at a set frequency and/or phase offset. Taking out the "K" and "sin/cos1" calculations will save a ton of CPU. That's why I am so enamored with these little guys. You can add in or take out features to suit your project. You can't do this with the stock sines.
The other night, i was working on something that required the frequency analysis of an incoming signal. I was only interested in about 12 specific frequencies. I was able to use these oscillators as the basis functions for a correlation DFT with great success. I think situations like that are where these oscillators really excel. Simple, fast, accurate, and highly flexible!
-cyto
trogluddite ·
cyto wrote: pretty comparable CPU-wise
Although I think that is about right for the "average" CPU use, when streaming audio, the peak CPU has huge significance. It only takes one really big peak to temporarily "break" the stream - leading to distortion, or worse...
This is very likely what has been happening with tester's machine - some soundcard drivers are not at all resilient when an expected buffer refresh does not happen. Even if CPU peaks from different modules have only a 1:10,000 chance of coinciding, things ain't going to be stable for long!
It's interesting to look at both osc's using the CPU Analyser. When running in "continuous" mode the graphs make pretty interesting 'reading' (hmm, maybe I should get out more!)
The stock osc shows high CPU for quite a few cycles at the start of each test period - just what I would expect from new lookup table data populating the cache. After that, there are sporadic peaks in CPU use, which don't seem to follow any obvious pattern - and seem to vary in severity at difference frequencies (when the wavelength is a simple factor of the lookup size maybe?) Some peaks are sustained for a good few samples - too many of these "overlapping" on multiple oscillators would really drive your CPU nuts.
OTOH, the quad-osc, shows a very steady "baseline" with regularly spaced peaks corresponding to the hop periods. You can even see the difference between the small peaks of the hop(32), and much bigger peaks at each hop(256) from the phase calculation (bigger peaks presumably because the sin1/cos1 routines are relatively greedy.)
This all bodes well for using custom hopping - we should be able to virtually guarantee that the CPU peaks never coincide, which should take us even further into the realm of stability and using big multiples.
As you may have guessed if you looked inside my recent "Toolkit", hopping heaven should not be too far away! - and I also have another new little trick I came up with that will help with the many identical modules......... 😉
martinvicanek ·
trogluddite wrote: because the sin1/cos1 routines [used in the phase calculation] are relatively greedy
I went back and optimized the original polynomial approximation. It seems to be quite a lot faster now. Here are the readings of a comparison:
• FPU fsincos:1800 to 2200 cycles depending on x
• SM sin1(x) and cos1(x): 840 cycles
• polynomial approx. code: 360 cycles
• polynomial approx. ASM: 250 cycles
So with the optimized approximation one could afford a smaller hopping for the phase calculation. I am attaching my test setup.
cyto ·
Updated with Martin's (excellent) new approximation...
-cyto
trogluddite ·
That's great work - those approximations deserve to go in the toolbox in their own right.
Now that you two guys have done all the hard work (maths - aargh) - here another little contribution from me in the form of some "Assembly Voodoo".
Same routine, but reduced to about 160 cycles by doing nothing more than interleaving the two functions and removing a couple of memory reads by copying registers...
With such efficient phase calculations, I'd suggest hopping the phase and freq' calc's as a unit at the same rate.
That's because jumps can cause breaks in the CPU instruction pipeline - the CPU tries to predict which way a jump will go, and preloads (and even starts executing) opcodes from the predicted new program pointer. If the prediction is wrong, all that stuff gets flushed out, and the CPU starts again at the correct pointer.
So although the hop routine doesn't look like much (just three opcodes), cutting down their number can help streamline execution a little bit as there are less chances of incorrect branch predictions.
Also, the hop routine itself obviously runs at sample rate - so for the vast majority of samples, combining the hops will take three less opcodes to execute.
cyto ·
trogluddite wrote: With such efficient phase calculations, I'd suggest hopping the phase and freq' calc's as a unit at the same rate.
Good point and excellent optimization! Just a couple little nit-picky things (let's see how deep down the rabbit hole we can get 🙂 )...
When used in the current version of the oscillator, two of the registers (xmm5 and xmm6, I think) are already holding the sync bools. If I counted right, this optimization uses 7 out of 8. So, just a warning to others reading, you might need to do some creative shifting to free up a register before dropping in the new code (or just move the trigger test to after the sin/cos calc). Also, if you look at the last version I put up, you'll see how you can get rid of all of the subps's by declaring the 0.25 and 0.5 as negative values. The absmask takes care of the one instance (0.5 - someValue) where you might expect problems. Don't know how much this will help, but since we're cutting out all of the fat, I thought I'd point it out.
-cyto
martinvicanek ·
Thanks Trog, I think I get the idea, although your ASM doesn't seem to run faster on my Athlon II X4 640 CPU. 😮 Maybe it is more efficient on Intel?
While bbrowsing through Trog's updated wonderful jewel case I got inspired by the fast rounding routine and thought I could use it to make the sin1(x) cos1(x) approximations valid also for x outside the 0-1 range.
Cyto, yes I noticed you declared -0.5 etc. to avoid subps occurrences. I realize that that can be an advantage when subps instructions go against the flow and require additional memory reads and copying of registers. Is there an additional saving to it?
trogluddite ·
martinvicanek wrote: although your ASM doesn't seem to run faster on my Athlon II X4 640 CPU.
Ah yes, very easy with that kind of optimisation to forget that different CPU architectures will behave very differently.
The interleaving approach depends very much on how much your CPU buffers the incoming instruction codes - the buffering allows the CPU to look ahead for non-dependent instructions that it can execute when things stall because of e.g. waiting for a value from memory. The interleaving makes it more likely that the buffer does not contain a long run of operations that form a "chain" of operations that absolutley must be executed in sequence.
So the interleaving is very unlikely to ever make a routine less efficient - but any gains are very dependent on the way that a particular CPU handles this "out of order" processing.
Re: subps.
There was a time when subtraction was inherently a slower process than addition, but for any recent CPU there will be no difference in performance (typically, the latency will be a single clock cycle on modern processors) . So unless there is some other reason to use -ve numbers (i.e. fewer variables etc.), there's no reason to avoid 'subps'.
tester ·
Another thing (related to primitives or to code/asm box solution). If I understand correctly, this sine schematic produces both at the same time - correlated sin and cos function. Is there a simple way to interconnect these outputs and produce user controlled phase difference between these outs? Wouldn't that boost signifficantly performance in which stereo modules are used? I think using 600 units and only 300 in one design - this can make a difference 😉
To clarify one thing: common phase offset in input is needed when combining/syncing multiple slow (very slow) modulators, phase difference is important inside one stereo set, so both are useful.
tester ·
What is the accuracy of SM stock sine oscillators? I'm doing comparative measurements, and having interesting results...
Two sonic layers, 20 sine waves (10 per channel), fixed freqs (0.5Hz step, range used is 100-1000Hz)), phases, gains. One produced with cool edit. Second one produced with SM application (via VST). Both 5 minutes long, 44.1kHz/16bit/stereo.
Cooledit-made example is perfectly loopable with no glitches. SM-made example has a glitch when looping (at 0.5Hz step, zero crossing should be also at 5 mins time index). When comparing side-by-side, i.e. inverted phase mixing - if sounds are precise, then output should be zero or almost zero (dithering trafic, shape inaccuracy). Well... It isn't. Inverted mixing starts from zero and slowly fades in (starts to be audible after c.a. 10 seconds, all sine waves) and after 5 minutes is only 4dB quieter than pre-mix tracks. So it appears, that initial syncing is ok (free running oscillators), but something is happening with the frequency/time domain. Where from this inaccurcy is? How to avoid/remove it?
Modified sines not tested yet. But I'm not sure if this is related maybe to frequency vs sample rate handling or float primitive approximations during Hz to 0-1 conversion?
//edit: Just quickly tested biquad6 from uploads above, and this one seems to work fine, no frequency difference. So the problem is in approximations in stock sine primitive. Puff... What a relief... I thought that I will be acoustically not compatible with my recent projects... Such differences just crash the sound; not only implemented patterns, but they also produce differential variations and chord-like unpredictable changes, which results that the whole soundspace may simply rotate (and this is not minor) and become blurry. So biquads for VST exporting 😉 Thanks!
tester ·
Found a problem.
Schematic is made on both - biquad6 and SM stock sine. There is a sound oscillator, amplitude modulator and another modulator connected to phase node of AM. While stock sine works that way - modified version produces on my computer noise or rising loud dirty sound, that disappears.
SM stock sine accepts phase offset greater than 0-1, which produces greater modulation on phase node. Modified sine (biquad6) has a problem with that. Can this be repaired?
I also noticed, that sometimes modified sines (tested on biquad5, biquad6) stop working (or give a short sound and stop sounding) with no particular reason while other works fine (only some units, not all, usually one side), and this does not depends on input values in 0-1 ranges if there is suggestion on that. They get "restarted" when inserting modules into schematic (not always?), as if there was some triggering issue. Tested on intel CPU C2D.
martinvicanek ·
tester wrote: SM stock sine accepts phase offset greater than 0-1, which produces greater modulation on phase node. Modified sine (biquad6) has a problem with that. Can this be repaired?
Yes, with the sin1 cos1 approximations which I posted here.
tester ·
Thanks, but a project I'm working on - requires 3 input nodes - frequency, phase and resync. I have no idea where to add what in ASM code from "QuadOsc6.osm" uploaded above (cyto's upload on page 3) or how to combine them correctly. These asm parts are still beyond my comprehension.
trogluddite ·
tester wrote: SM stock sine accepts phase offset greater than 0-1,
While the code tinkers are at work looking into that one, here is a quick module that will "wrap" floats to the 0-1 range (i.e. getting just the fractional part)...
Re: The stock sine innacuracy...
Don't forget also that the stock sine is only really a sin1 lookup table (65536 entries) with very basic interpolation (to keep CPU down). The amount of error in the interpolation will also fluctuate cyclically as the indices wander closer and further from the "datum points" at the integer indices. At very low frequencies, the samples will be spread out so far that the sine is approximated by a series of straight-line "segments".
martinvicanek ·
Yup, that's the quickest solution! By the way, you could even omit the "fiddle" trick as the ASM integer conversion works correctly for negatives. I know that some other implementations don't, and that's probably why Trog introduced this remedy in the first place.
tester ·
Doing a simple test on 50Hz tone and 400Hz tone - I can say, that frequency handling is inacurate, but this is regular pattern. Either frequency/time gets shorter or longer than real one. Is the wavetable responsible for only shape inaccuracy (stepped) or also for input frequency (time lenght of single cycle) handling? Something like +/- error depending on frequency? Isn't the time scaling beyond wavetable?
Regarding the conversions to 0-1 (in slow modulation module) - I think I forgot to add "trimmers" (min/max) on phase node in sound module or the intentnion mixed with imagination and logic failed. The general idea of smod was to trim values above thresholds, but I'm not sure if on phase node wasn't to just get smooth back and forth transition.
Now - in the project - there are c.a. 400 sine oscillators accessible right now. Can the amount of oscillators be related to the problem, that some of them break down or do not initialize? I'm trying to isolate the problem right now. Or can this be related somehow to mono4 primitive? (it is used to store left and right channel; left channel connected to node1 - tends to break down).
tester ·
Maybe this question sounds stupid to some people, but I ask it after finding the differences with my own ear and brief listening on non-studio headphones (so this is not a theoretical matter). What is the phase offsets in/accuracy (versus frequency I guess, range 50-1000 or to 3500Hz) in above developed sine productions?
I ask, because comparing audible differences between sonic patterns (10 pairs of stereo frequencies) created in cool edit and VST made of sinquad6 - the sinquad6 produces very unpleasant and noticable vibration/fluctuation. This could be either due to frequency instability over time or due to wrong phase settings.
But comparative/inverted mixng results - are stable in frequency domain (differential beats are much weaker than with using SM sine, although they are present too; small enough to make 5 minutes sound perfectly loopable), and audible sounds are very loud from beginning (different loudness on different frequencies), so this is phase offsets related issue. Can this be fixed? Are phase offsets from 0-1 range - linear (like time delay) or in degrees?
How the sine is created in cool edit then, so that there is so stable, precise and accurate? Can this accuracy be replicated? Even for offline VST exporting, so that digitally created sounds are fine (generally regarding phase offsets, there are simple math formulas working wround to make right sounds; so these values can be applied almost blindly).
It's not audiophilism, it's a simple gemoetry of simple sounds. Butterfly effect so to speak.
tester ·
I know that this topic is about making sine without using sin(), but after my initial experiments on sound and sound quality - I think it is worth to back to the origins. Remembering that there something was - I just found this:
[old link]
and following code:
streamin frequency;
streamin phase;
streamin volume;
streamout out;
float a,b;
a = a + frequency;
a = a - (a > 1) & 2;
b = sin1(a + phase);
out = volume*b;
Not tested yet for quality (I check the freqency/phase accuracy when I'm on another computer), but I have following questions.
1. This one is accurate sine production, not wavetable based?
2. Can someone remind where to put resync node? 😉
3. Can this be optimized (via ASM code) without loosing freq/phase accuracy?
Regarding optimizations I have a thought. Instead of optimizing single sine unit - wouldn't the theme work faster - if there was a cluster of sine oscillators created in one code/asm? Such multisine generator so to speak. I don't know - 16 or 32 per code? One resync piece of code for all - at least one optimization 😉 But maybe there are some common routines.
//edit:
Inaccurate frequency handling in code above too. Not loopable results. What is wrong with these codes? Is there no simple way to produce precise sine with precise frequency handling and precise phase handling?
trogluddite ·
tester wrote: 1. This one is accurate sine production, not wavetable based?
2. Can someone remind where to put resync node? 😉
3. Can this be optimized (via ASM code) without loosing freq/phase accuracy?
1) No - the sin1 funstion is the same 65536 entry lookup table.
The precision of the counter is what will determine the phase/frequency accuracy. This can be improved by using double precision numbers (e.g. look inside some of the sample player modules - you will find a "precise" counter in double maths).
But double precision is not available in code or assembly, and cannot work with mono4 data - so, in the interests of low CPU, I would imagine that the stock oscillators stick with single precision (though I don't know for sure).
This possibly explains the greater accuracy of CoolEdit - it may be using maths with greater precision for the counter (and possibly the sine function too).
In terms of the sine function itself - the only way to increase accuracy would be either a larger lookup table, or to use the super-precise fsincos opcode. As we saw earlier in the thread, using fsincos, while accurate, is extremely greedy for CPU - several orders of magnitude slower than a lookup table.
in fact, your code could well be less accurate than the stock osc', as it reads a only single lookup value directly without interpolation (i.e. there may be 'steps' in the waveform at low frequencies).
2) To resync, you just need to set the counter (a) back to zero, according to whatever rule you want for the re-sync input. The usual way it to look for rising edges (i.e. current sync input is greater than the previous one). Something like...
a = a & (Trigger <= OldTrigger);
OldTrigger = Trigger;
3) I have done an optimisation on the sin1 function in the past - I'll see if I can find it. However, the CPU savings were quite modest, and it is not possible to improve the accuracy/function of the sine function this way. Again, it is possible that the stock oscillator uses more efficient code already (the dev's possibly have access to a wider set of instruction than we can use in code/assembly).
tester ·
Thanks for explaining.
I sent an e-mail to Outsim, we see how they react and when (next update)? If they are focused on Flowstone as a research tool, then they shoould be interested in improving research related tools. Otherwise - expensive toys not tools; sending a satelite to Venus will result that the rocket will land on Jupiter 😉 These small adjustments are important when dealing with frequency and time and predictable control of spatial (differential aka binaural which are slow rotations) and vibrational (chord-like) behaviours. For example - there is a huge difference whetner after 15 minues of playback - you still hear the audio track in center or everything is somewhere letf. Or whether you hear a progression of phantom tones or just a blurry blurness.
Anyway - right now, the stock sine seems to be the best choice due to proper phase offset handling (which is based on different than 2^n divisions). Not loopable tracks and lack of backward and intersoftware compatibility. After all these months - what a disappointment and shame... No wonder, that many market plugins (like harmonizers) have great cover descriptions with good intentions and poor quality inside. But if this quadsine will develop further, then there is a hope (as i said - frequency handling is much much better, allows to create loopable tracks, but phase handling still needs to be corrected) for better sound quality.
cyto ·
I'll admit to not having followed the most recent discussions here too diligently. But I noticed a few things in the last schematic tester posted that may be the source of some of the problems being discussed. First of all, there certainly is the issue of the "phase" going out of range. Any of the appropriate "wrapping" methods should fix this. But more importantly, I noticed that in the schematic, the phase offset is being continuously modulated by another sine. Even at the relatively fast hop of 32 samples, this will no-doubt introduce some distortion into the resulting audio signal. You are not getting smooth phase modulation, but rather stepped phase modulation. Could this be contributing to the latest issues you are having with the sine osc? One way to test is to go in and replace all of the lines that read and eax,31; and replace them temporarily with and eax,0;. If this alleviates some of your problems, I'd be happy to re-write the code for you to get rid of the hopping and also integrate the "wrapping" function. If I'm way off, then forgive me for bringing it up. 😉
-cyto
tester ·
Cyto - whatever you do to improve the modified sine - I'd be happy to test it. I'm sending you PM something to test (PM system has some delay, so you probably get it tomorrow).
DigitalWhiteByte ·
Before putting all the pieces together, wondering if anyone has already done.
🙂
cyto ·
DigitalWhiteByte wrote: Before putting all the pieces together, wondering if anyone has already done.
🙂
Sadly, not me. Too many pots on the stove right now!
-cyto
DigitalWhiteByte ·
ok then, I'll try to put together all these wonderful optimizations!
infuzion ·
martinvicanek wrote: Here is a three line recursive quadrature oscillator. ..
streamin k1; // -tan(omega/2/SR)
streamin k2; // sin(omega/SR)
streamout x; // sin(omega*t)
streamout y; // cos(omega*t)
float tmp;
stage(0)
{
x = 0;
y = 1;
}
stage(2)
{
tmp = y + k1*x;
x = x + k2*tmp;
y = tmp + k1*x;
}
...I am attaching an OSM with optimized ASM
I have used this as an example: Tips for Code & ASM
Not sure if this is your latest version; I am confused by this whole thread anyway. 😊
Wrote something here? To have a post removed or your name hidden, write to [email protected].