From the original SynthMaker forum (2004–2014), with its members’ names and dates.
CoreStyler ·
Hi.
i tried to convert a c++ bitcrusher from musicdsp.
So i don't know really how to but maybe something is correct.
Here i post the orginal code:
Lo-Fi Crusher
Type : Quantizer / Decimator with smooth control
References : Posted by David Lowenfels
Notes :
Yet another bitcrusher algorithm. But this one has smooth parameter control.
Normfreq goes from 0 to 1.0; (freq/samplerate)
Input is assumed to be between 0 and 1.
Output gain is greater than unity when bits < 1.0;
Code :
function output = crusher( input, normfreq, bits );
step = 1/2^(bits);
phasor = 0;
last = 0;
for i = 1:length(input)
phasor = phasor + normfreq;
if (phasor >= 1.0)
phasor = phasor - 1.0;
last = step * floor( input(i)/step + 0.5 ); %quantize
end
output(i) = last; %sample and hold
end
end
Here my conversion (but i know that is not correct):
Hi there,
I don't know much C++, but I think I can give you a few pointers...
I see you've already worked out that the 'for...' loop isn't needed, that's good. A lot of the C++ code on the net has that because a lot of VSTs process samples in batches (soundcard buffer size), but SM just does them one at at time - so you already dodged the biggest trap!
Going through the rest a chunk at a time...
Where you've used polyin, it is more versatile to use streamin - the inputs will then do the automatic colour change depending if you connect them to poly or mono. You also don't need the 'floor' input (I'll come to that later).
'float bits' is also not needed - because 'bits' is one of the inputs, it is automatically assumed to be a float.
Again 'floor' is not needed here (more later...). The 'phasor' and 'last' lines are presumably to set initial values, but as SM runs the whole code for every sample, they will keep on getting reset all the time. The easiest way around that is in the previous section. You can write float phasor=0; etc. when declaring the variables to quickly set an initial value (and save a few lines of code!)
The 'steps' maths is also a bit out - I guess you discovered that SM code doesn't do the ^ symbol for powers! SM can do the same function, but it is written very differently... 2^bits would be written pow(2,bits)
Spot on with the first part of this - you got the hang of doing the 'if' statement using '&', cool. However, C++ has a strange syntax for 'if' - the next line up to the word 'end' is also part of the if statement.
I need to explain the 'floor' thing first though. In C++, 'floor' is a function for rounding numbers - it's called floor because it always rounds downwards. To force it to round to the nearest whole number, the original programmer added 0.5 to push the numbers up a little.
In SM, the nearest thing to that is the function 'rndint', which always rounds to the nearest anyway. So you would get the line...
last = step * rndint ( input / step );
But like I said, this is also part of the if statement, so it only has to happen if phasor >= 1.0. Otherwise the value doesn't change, so you need an extra bit to keep the old value if phasor < 1...
last = last & (phasor <1) + rndint (input/step) & (phasor >= 1);
But this needs doing carefully - the previous line has already changed the value of phasor, so the order will need switching - so the end part of the code would look something like this...
phasor=phasor+normfreq;
last = last & (phasor <1) + rndint (input/step) & (phasor >= 1);
phasor = phasor - (phasor >=1.0) & (-1.0);
out = last;
A last little touch is that the final output just equals the value of 'last' all the time - so you could just set 'last' to be a streamout and do without the extra variable 'out'.
Hope I explained that OK - and I hope the code works; I haven't had a chance to test it!
tor ·
I do not understand C++ but this is my understanding of how to do a simple and lightweight bitcrusher simulating fixed point precision:
(NB: Im sitting on my Ubuntu laptop atm so I have not tested this. But it should be really straight forward.)
//// In green ////
//First have a knob and set: min=1, max=32 and set 31 steps. Output goes to a 'power of' primitive like this where 'bits' is the value from the knob:
2^bits*0.5=steps (connect to code and 1/steps)
//and calculae the scale down ratio for later:
1/steps=d (connect to code)
// ^ is a primitive called 'power of' // *0.5 because the range of audiosignal -1 to 1 is 2 in sum//
t=in*steps; //scale up to number of steps
q=rndint(t); //round to nearest integer, quantization
out=q*d; //scale down for output
Hope you find it understandable and useful 🙂
EDIT: trog made a great walkthrough of your code. idid not see it before after i posted this
stw ·
trogluddite wrote: Hope I explained that OK - and I hope the code works; I haven't had a chance to test it!
Nice and detailed explanation trog 😉 !
Some small additions...
This:
phasor = phasor - (phasor >=1.0) & (-1.0);
should read:
phasor = phasor - (phasor >=1.0) & (1.0); or leaner
phasor = phasor + (phasor >=1.0) & (-1.0);
and this:
last = last & (phasor <1) + rndint (input/step) & (phasor >= 1);
lacks the multiplcation:
last = last & (phasor <1) + rndint (input/step)*step & (phasor >= 1);
and last:
If "floor" rounds to the nearest integer (down:<0.5;up:>=0.5) it does exactly what "rndint" does. If there is a "+0.5" inside it would lead to a "round up" because it always rounds to the next integer. So if that belongs to the original code it should be implemented in the SM version too. I think that's important for the code anyway...
tor ·
I see that you do a direct conversion of the presented C++ code. This i do not know much about so i leave that to you. But is there any objections to my code above?
Maybe add a min max line to the output:
out = min(0.997,out)&max(-0.997,out);
?
trogluddite ·
tor wrote: But is there any objections to my code above?
Your code is fine, it reduces the bit depth as expected, and with no stream divisions so good for CPU. However the C++ code also has the function of reducing the sample rate (the stuff involving the 'phasor' variable).
stw wrote: If "floor" rounds to the nearest integer
The C++ function floor(x) always rounds downwards (the floor is down) - there is a complementary function ceil(x) (i.e. ceiling) which always rounds up. So the C++ requires the +0.5 for nearest rounding, whereas SM does not, as it uses nearest rounding anyway.
To be precise, rndint(x) uses 'banker's rounding', which can sometimes be a PITA.
A number exactly half way between integers (i.e. x.5) rounds alternately up and down. The rule is that the 'half' numbers always round to the nearest even number - so 1.5 and 2.5 both round to 2.0, and 3.5 and 4.5 both round to 4, etc...
Trouble is, that if you try using rndint ( x - 0.5 ) to make things always round downwards, integer values of x no longer round properly (you only get even numbers out).
Great for Excel spreadsheets (it averages out the rounding errors when book-keeping), but not usually what's needed when doing DSP. 🙁
Anyhow, thanks for pointing out the bits I missed from CoreStyler's code! 🙂
tor ·
ah.... 😊
I overlooked that.
tor ·
I made a attempt just for the fun and to learn. Think I have managed to simplify the desimator even more. I am still to lazy to fire up my windows computer to check it out and it is good training just to write code. Please tell what you think:
KNOB CONTROLS:
---------------------------------------------------
knob-->newrate // min=1, max=samplerate ( modify knob to get max value from samplerate primitive )
knob-->newbits // min=1, max=32
GREEN MATHS:
---------------------------------------------------
newrate/samplerate=increase--> // calculate desimator count increase value
2^newbits*0.5=steps--> // calculate number of available values for wanted bitrate
1/steps=d--> // calculate scale down value after quantization for signal
You're really getting the hang of this optimising thing 🙂 - beware, it is very addictive! 😉
Maxing the green is always good! - and only doing the comparison once is another really good CPU saver (the comparison operators are quite greedy for CPU!)
Here's one other little cool trick that you can use...
When you do a comparison you get a true/false value that you use for the 'and'ing. The true/false value is really just another binary number (either all ones or all zeros) - and you can store it in a variable, just like any other number.
So it's possible to do this...
c = (i>=1); // (variable c is now just true or false)
out = out + c & (b-out); // sample hold
i = i - c & 1;
Doesn't look like it saves much, does it? - just lost one & and switched the multiplies for &'s.
But, the & operation is more efficient than multiplying! - so yet more CPU savings.
You just have to be careful to remember which variables are true/false (boolean) and which are really numbers.
You can even send them in and out of code blocks by using 'streamboolin' and 'streamboolout' in place of the usual 'streamin' and 'streamout'
Once you have some of these 'boolean' variables you can use & and | (or) on them just like you would expect, but there is no 'not' operator. You can invert (not) a 'boolean' by just doing this...
notbool = (bool==0);
Wouldn't save anything for your code, but it's handy if you have some important comparisons that you need to use over and over again in the same piece of code, as it can sometimes avoid a lot of greedy subtractions. For example...
do = (thistime == yes);
dont = (do==0);
a = a & dont + x & do;
b = b & dont + y & do;
c = c & dont + z & do;
tor ·
Thank you trog!
Always inspiring to learn more. 🙂
CoreStyler ·
This evening i will read accurately all the post here! 😀
I really does not understand something.
for example, i can made a VCA rigth?
so in steps:
1st define in and out
polyin in;
polyin level;
polyout out;
2nd define float... this mean variables rigth?
float a;
float b;
float c;
3rd is float...
a = in;
b = level;
c = out;
the put the math there, so it will be my VCA:
c = a * b;
Tried but not works. maybe is completely wrong 😀
martinvicanek ·
CoreStyler wrote: 1st define in and out
polyin in;
polyin level;
polyout out;
So far so good. Now add this single instruction:
out = level*in;
and you're done!
The trouble with the following code is that "c = out" assigns the current value of "out" to the "c" variable. But you really want the opposite: "out = c" (that would be after you have computed c, of course 😉 ). Anyway, in this little example there is really no need to introduce variables a, b, c. You can do it straight away as in the above line.
CoreStyler wrote: 2nd define float... this mean variables rigth?
float a;
float b;
float c;
3rd is float...
a = in;
b = level;
c = out;
the put the math there, so it will be my VCA:
c = a * b;
CoreStyler ·
Clear! more easy than what i think.
Did you know if using code and tranlating codes in ASM like this VCA there is cpu save?
trogluddite ·
There certainly can be CPU savings from using ASM instead of code - but savings are not guaranteed. Some code doesn't optimise at all - for your VCA, for example, there is no way to make it better in assembly.
SM translates the code into ASM when it compiles a code box - if you connect a text box to the string output of a code primitive, you can see SM's version of the assembly.
Just pasting that text into an ASM primitive uses exactly the same CPU as the code - but SM's version of the assembly is not always the most efficient - often there are surplus instructions. There are also quite a few 'tricks' and optimisations you can do by using assembly commands that SM's compiler doesn't normally bother with.
If you're keen to see what is involved, take a look at Exo's excellent ASM optimisation walk-through on the Wiki.
However...
The savings from assembly are often very small, and it can take a lot of work to do the optimising - especially because assembly is very hard to read! Once your code is translated to assembly, it can also become very hard to make changes to the design.
The best thing is to spend some time learning how to make the most efficient code possible before learning assembly. If your code is super-efficient, then translating to assembly can squeeze the CPU cycles a little bit more - but translating less good code will always result in poor assembly too. In my experience, very optimised code is also much easier to translate.
infuzion ·
trogluddite wrote: There certainly can be CPU savings from using ASM instead of code - but savings are not guaranteed. Some code takes special tricks to speed up - for your VCA, for example, you'll have to send your OSM to infuzion to see if it can be further optimized.
corrected 😉
trogluddite wrote: The savings from assembly are often very small, and it can take a lot of work to do the optimising - especially because assembly is very hard to read! Once your code is translated to assembly, it can also become very hard to make changes to the design.
The best thing is to spend some time learning how to make the most efficient code possible before learning assembly. If your code is super-efficient, then translating to assembly can squeeze the CPU cycles a little bit more - but translating less good code will always result in poor assembly too. In my experience, very optimised code is also much easier to translate.
I'll have to agree with both these tips above. Also:
[old link]
Code tips x a beginner
Newer CPUs self-optimize good enough, & unless you are either good with ASM already, have a long block of Code, need to run the VST on 6 year old CPUs, will have many running copies of said VST (eg an EQ or compressor that will be on every track of a 15 track song), have a huge OSM, &/or use that block of Code several times (eg an envelope), I would say just optimize it in Code & don't worry about the ASM. Otherwise you will spend alot of time for too small performance improvement. Learning ASM will help you know how to optimize better in Code though.
Or just hire someone like me to reduce it for you, so you can focus on design. 😉
I'd like to add one other tip:
+ Comment your Code well, & ASM more.
trogluddite ·
infuzion wrote: for your VCA, for example, you'll have to send your OSM to infuzion to see if it can be further optimized.
He he - you are offering a bespoke 'pimp my schematic' service now? 😉
Here is SM's compiled ASM for Martin's corrected VCA code...
I'll buy you a Ferrari if you can make it faster! 😈
Mo ·
trogluddite wrote: Ferrari..
Careful now. 🙂
-This should actually be faster:
streamin in;
streamin level;
streamout out;
fld in[0];
fld level[0];fmul;
fst out[0];
For one channel only.
trogluddite ·
He he, I should have seen that one coming! 🙂
Mo wrote: For one channel only.
You are only doing one multiply, whereas the original code does four. So, as it doesn't perform the same under all circumstances (erm, read "because I can't afford it") ypu are disqualified from winning a supercar! 😉
You have a good point though - I have wondered before whether the FPU is any more efficient than SSE for mono applications - obviously it is for those op's that require the SSE channels to be split, but for a single channel...?
I had guessed that maybe having less data to shift to and from memory/caches might make it a little better - but I don't really understand how the pipelines work. Interests me because I have a few FX algorithms I'd like to try that need some serious number crunching - e.g. FFT/convolutions in real-time etc.
Mo ·
Seems a bit faster, but i forgot to pop the register and it can crash SM. Here, corrected code for a mono VCA:
trogluddite wrote:
infuzion wrote: for your VCA, for example, you'll have to send your OSM to infuzion to see if it can be further optimized.
He he - you are offering a bespoke 'pimp my schematic' service now?
Like Mo points out, it all depends on the application & what is going on in the particular OSM. One can spend years to learn all you need for ASM & SM's "personality", & that will cost time which is money, or be more focused elsewhere & let someone else help you bypass that training.
Edit: sometimes there isn't much improvement available, but I have reduced SM's compressor by like 80%, while improving performance.... but that was using a real ASM ninja trick.
martinvicanek ·
trogluddite wrote: FFT/convolutions in real-time etc.
How is that possible in SM? 😮
trogluddite ·
martinvicanek wrote: How is that possible in SM? 😮
I don't know if it is possible yet - I'm still getting my head around the maths of the algorithms - and of course 'real-time' is all relative with that kind of thing; due to the big blocks of samples needed there would inevitably be some latency to deal with.
It would certainly be a huge chunk of code/assembly - so I'm doing some serious research first to see if all the coding would be worth the effort. But at the end of the day, even FFT is just a big lump of maths.
There is an excellent reference for these kind of algorithms HERE - but even Katja's superb explanations are seriously stretching my comprehension!
martinvicanek ·
trogluddite wrote: due to the big blocks of samples needed there would inevitably be some latency to deal with.
Sure, I know. I understood "real time" as stream based, as opposed to the green world FFT and iFFT devices. I know that serious attempts exist to implement FFT within the code module, however it is a hell of a job because a number of key syntax elements are missing.
infuzion ·
SM convulsion used here:
http://www.acmebargig.com/product/shred/
Don't waste your time trying get that OSM; he wouldn't even lend to to me to optimize it.
RunBeerRun2 ·
Here's some convolution- Just a test (Realtime Convolution)
It works pretty well for amp sims, low cpu, if I remember right, not so well on long impulse wav's.
trogluddite ·
Thanks RBR, looks like some good pointers in that thread.
tor ·
Thanks for sharing this link: http://www.katjaas.nl/ trogluddite 🙂
Is it only my crappy sløv modem or do other people here experience that many of the illustration images do not load fully?
trogluddite ·
Yes, I also had a few problems with some of the images - most of the content is there as far as I recall, but I had to do a lot of page refreshes before I managed to see all of it.
It's a shame that the site isn't on a better server - it's the only site about FFT etc. where I could understand anything! I think it was DigitalWhiteByte who first showed it to me.
Wrote something here? To have a post removed or your name hidden, write to [email protected].