pouët.net

64k synths

category: music [glöplog]
...

How???
added on the 2016-07-03 00:21:24 by noby noby
64klang2!
added on the 2016-07-03 01:09:14 by xTr1m xTr1m
Woow, found lots of music done with the tunefish synth from Brain Control:
https://soundcloud.com/groups/kvr-one-synth-challenge-1
added on the 2016-08-03 20:16:11 by Virgill Virgill
beautifull zax!
wow, nice tunes!
added on the 2016-08-03 23:23:04 by mad mad
Optimisation (speed) questions for the guys who have made modern 64k synths...

You all doing everything in doubles or floats or a mixture? (I am concerned about cumulative precision errors with floats and/or having to constantly type cast variables).

Using specific SSE or vectorising code or just plain x87? (I am struggling to find synth code that can be parallelised besides obviously stereo signals).

Worried about denormals? (Is this even an issue on modern cpu's?).

Modulation matrix seems to be a bit of a performance killer but I see many vst's where every paramater can be sent anywhere. Is modulation pathing just always going to be expensive or is it a case of finding an elegant/optimised solution?

It is quite possible I have gone down the wrong path with this but unfortunately it is easy to find explanations of how to code core components like oscillators and filters, it is much harder to find general information on synth architecture. Presumably everyone does it their own way and there is no "right" way of doing it.

I realise I can find all this out by writing dozens of different codepaths and profiling everything but I am trying to speed up my learning process by leveraging some existing knowledge, if anyone is willing to share their experience. So far my only source of info is searching through the KVR dsp forum but it is hard to find all the good info hidden in 10 years worth of forum posts.
added on the 2016-11-28 06:03:07 by drift drift
Some insights from my synth:

Quote:
You all doing everything in doubles or floats or a mixture? (I am concerned about cumulative precision errors with floats and/or having to constantly type cast variables).


Generally the audio signal itself is all floats, and any time-related signals/accumulators are doubles (global time, oscillator phase/delta, etc). When talking about precision when your numeric range is low enough (read: between -1 - 1 mostly) S/N is what's important, and it's pretty damn good for floats. Most of your precision is lost when you get into higher number ranges (in the 100's or so, and quite sensibly), which other than time-related signals is a pretty rare corner case to consider really. Also note you're not really casting in that many places, and always from high precision to low precision.

Quote:
Using specific SSE or vectorising code or just plain x87? (I am struggling to find synth code that can be parallelised besides obviously stereo signals).


WaveSabre adopts the "make it fast, then make it fast" ideology, ignoring the latter half :) I have yet to do most of the speed optimizations, actually. Although the voices in some bits of the synth are quite heavy, it's still quite workable and for the most part as predictable as it has to be.

Disclaimers aside, the synth doesn't use any vectorization explicitly; only what the compiler gives us for free... except most builds don't use that either, due to some intrinsic implementations that need to be filled in by hand (the synth is C++ and we're using msvcrt6) that I also haven't dug into yet. Some of my tests indicate a ~20% speed increase just enabling these optimizations in larger builds (and as I haven't linked it properly I don't know what kind of size impact that has). That's a significant figure, but certainly not a showstopper when you don't have it. But my advice generally would not be to worry about it too much. Make something that sounds good and is usable first. At the 64k level you have a lot of wiggle room and sound quality should start taking precedence over these details to some degree (take this comment with a grain of salt and some assumptions about how much free time you have :) ).

Quote:
Worried about denormals? (Is this even an issue on modern cpu's?).


FUCK. DENORMALS.

Seriously.

They are totally a pain in the ass on modern CPU's. synths have lots of feedback loops at very high "FPS" and you will run into denormals. When/where they pop up will likely be a bit less predictable than you think, so that's fun :)

Generally I think you can not worry about it for the most part until it happens though, just be aware they will happen and be prepared to consider where they're most likely to have occurred when they do happen and how to correct them. Practically speaking you'll end up with fixes sprinkled in a lot of places (including some you don't expect) but it won't affect the sound noticeably and will save you from a lot of hair-pulling in the composition phase.

Quote:
Modulation matrix seems to be a bit of a performance killer but I see many vst's where every paramater can be sent anywhere. Is modulation pathing just always going to be expensive or is it a case of finding an elegant/optimised solution?


Tried not doing a monolithic VST with a mod matrix? :)

I think V2 gets around this by only evaluating the modulations every 100 samples or something like that, which (unless you want to use your mod matrix for something silly like FM synthesis) should be quite sensible. If you do want to use your mod matrix for crazy stuff, you should probably be considering a more optimized dynamic runtime for synth bits a la Reaktor :)

Quote:
It is quite possible I have gone down the wrong path with this but unfortunately it is easy to find explanations of how to code core components like oscillators and filters, it is much harder to find general information on synth architecture. Presumably everyone does it their own way and there is no "right" way of doing it.

I realise I can find all this out by writing dozens of different codepaths and profiling everything but I am trying to speed up my learning process by leveraging some existing knowledge, if anyone is willing to share their experience. So far my only source of info is searching through the KVR dsp forum but it is hard to find all the good info hidden in 10 years worth of forum posts.


Grab a beer, write some code, make some rad noise. Relax :)
added on the 2016-11-28 09:30:09 by ferris ferris
Also worth noting on the "mod matrix" point is WaveSabre actually evaluates a lot of things per-sample in a "don't give a fuck" fashion. You can really go quite far by not necessarily ignoring speed, but using your intuition on where it's going to be most problematic and worrying only when you really have to, and spending that newly-freed headspace on usability/workflow and making things sound nice :)
added on the 2016-11-28 09:34:26 by ferris ferris
Synth architecture heavily depends on your standpoint when it comes to versatility vs. usability. And that standpoint is affected by your usage scenarios.
A commercial synth definately needs to focus on the latter.
And in my opinion for a 64k context it can shift way to the versatility side.

Therefore the answers to your questions from a 64klang2 perspective:

- it is doubles all the way down
- SSE4.1 (auto stereo processing. auto denormals)
- no modulation matrix. the synth nodes build a directed cyclic graph.
- per sample processing (but still some heavy calculations depending on input run at lower precision like 16 samples)
added on the 2016-11-28 10:38:06 by gopher gopher
Quote:
Synth architecture heavily depends on your standpoint when it comes to versatility vs. usability. And that standpoint is affected by your usage scenarios.
A commercial synth definately needs to focus on the latter.


I totally agree with this :)

Quote:
And in my opinion for a 64k context it can shift way to the versatility side.


I agree with this in principle in that it can, but it's not the synth I want to make. And I'm very happy you see it differently, so we have some decent synth variety :)
added on the 2016-11-28 12:40:00 by ferris ferris
also the stuff virgill is rocking with 64klang2 is just brilliant :D
added on the 2016-11-28 12:48:09 by ferris ferris
What Ferris said about V2 and the mod matrix - everything except the final voice volume is calculated only every x samples (x around 128ish depending on sample rate). But remember that I wrote the thing when CPUs were clocked at 300MHz and I wanted to go full realtime while an intro was running on a first-gen T&L GPU, so raw performance was way more important than it is today.

So today I'd probably go for a hybrid approach - nodes would still render a whole buffer/frame full of stuff instead of only one sample but you could use the buffers of all nodes as inputs for the "modulation list" I had in V2 (basically per sample: initialize parameters with defaults, then for (list: {src, amount, dest}) param[dest]+=amount*input[src][sample]; then clamp). You could add another list for low frequency stuff like automations, EGs and interpolate them between frames.

The only thing not possible with this approach would be sample exact feedback loops between nodes. So if you want to do complicated FM setups you'd have to hardcode them but a lot of EDMish filter trickery should be pretty doable :)
added on the 2016-11-28 12:57:59 by kb_ kb_
Quote:
Presumably everyone does it their own way and there is no "right" way of doing it.


That's basically it. If your architecture works for you then why change it?
In the end it depends on what type of sounds you want to make and the overall quality.

For the bread and butter virtual analog stuff, the tried-and-tested VCO->VCF->VCA->FX->OUT works well. For physical modeling stuff (bowed, struck, plucked or blown instruments), you can architect/code every algorithm separately, or make a very flexible synth like 64klang2. Then there's PM/FM, wavetable synthesis, sample playback synthesis etc, all having different sound characteristics, architecture details and challenges.

Quote:
Modulation matrix seems to be a bit of a performance killer but I see many vst's where every parameter can be sent anywhere. Is modulation pathing just always going to be expensive or is it a case of finding an elegant/optimised solution?


How much performance do you expect? There are the usual optimizations like using lookup tables, polynomials and other approximations for complex functions. As long as your approximation isn't "steppy", it's amazing how much imprecision you can have in modulation sources without affecting the resulting sound too much, especially with fast modulation.

To get around performance problems you could have fast (evaluate every sample) and slow (evaluate every N samples) modulation slots. This has been used in commercial synths.

If you evaluate the mod matrix every N samples, you can always linearly interpolate the value every sample on the destination side. You will hear the difference between steppy and interpolated modulation, especially on stuff with transients, such as drums.

Then there is sound quality: do you accept some aliasing or do you want the best quality money can buy. Do you use oversampling or more elaborate processing? Of course, the number of voices you can support will depend on these choices.

I fix most of my denormal problems by adding a very small constant, noise or -1,1 alternating sequence at the input of the blocks, depending on whether they have a lowpass, bandpass or highpass characteristic. I don't know if the denormal problem still exists with 100% SSE code.
added on the 2016-11-28 12:58:28 by trc_wm trc_wm
Quote:
I don't know if the denormal problem still exists with 100% SSE code.


As far as i can tell this problem is no longer existing with SSE.
It depends on setting the relevant bits in the MXCSR register though.

I'm using "Denormals-Are-Zero" and "Flush-To-Zero" as explained here:
https://software.intel.com/en-us/articles/x87-and-sse-floating-point-assists-in-ia-32-flush-to-zero-ftz-and-denormals-are-zero-daz
added on the 2016-11-28 13:51:41 by gopher gopher
Thanks all so much for the advice. I started writing a massive long reply but a lot of it was just me talking out loud and waffling on. I might get back to some specific points when I get time to digest everything but I greatly appreciate all the information shared here. I already got a lot if info and inspiration from Ferris' seminars and Gophers write up on 4klang, even kb's tragically short series of articles on V2 were useful.

Just briefly I will say I already have a synth made and working which I have made a short song with and it sounds ok to me. I am just at the point of refinement and fussing over details but a lot of what has been said here has made me feel a bit more happy and comfortable with what I have done so far. I still have the hard part of having to parse midi data from some DAW and making a custom data format and player for use in demos/intros. But it works ok as a vst, well besides that I can only save patches through the DAW.

Some details: It's a subtractive synth modeled on features of old roland and korg analogue synths which I used to own in the 90's/2000's and is what I am familiar with, I know they make the sounds I like for the sort of music I like. So along with what Ferris said, it's not the most innovative or flexible synth but it will be "my" synth and that is kinda the most important thing since realistically it will probably only ever be used by me and maybe a couple of close friends. I'm certainly not doing any physical modelling or making an infinitely variable modular graph interface, or even FM (tried to use a DX7 back in the day and FM was like witchcraft to me). Well I do have some audible frequency rate modulation options and hard sync so kinda sort FM... a little bit.

I am using doubles for everything. No SSE specific code unless the compiler decides to do something. Everything is processed at sample rate. The modulation matrix is built from a list of sources and destinations. A modulations list is then built with a source, destination, amount and an enumerator which denotes if the source needs to be converted somehow. Say for example some sources are 0 to 1, some -1 to 1, some is midi data, some is oscillator frequency. So it processes the source from a list of converter functions before sending to the destination.

Interestingly the mod matrix is fully working but due to the GUI coding for it being such a fucking headache I just hardcoded some basic source to destinations just like on old synths.

Oscillators are ok I think, sine is a parabolic approximation. The saw and pulse waves are modeled on waveforms I have seen on old analogue synths rather than just pure perfect mathematical representations. The saw ramp has a nice tanh curve using a fairly fast tanh approximation. I use 2 sample polyBLEPs to reduce aliasing which is a decent compromise between speed/quality and not rolling off the high frequency harmonics too much like some antialiasing solutions do. Waveforms morph using a couple of different tricks which is nice because you get some interesting mixtures half way between waveforms. Combined with PWM and modulating the waveform morphing you can get some nice sounds. Filter is multimode 2-pole with a weird saturation thingy you can adjust. Filter is continuously adjustable between LP, Notch, HP which again gives some nice potential for modulation and the bandpass is just activated with a hard switch.

trc_wm: I read on KVR about adding some low level noise to prevent denormals just like you said. I also read people just using a simple conditional check for threshold like: if(x > -smallValue && x < smallValue) return 0; else return x; They seem like simple solutions.

I know about the Flush To Zero and Denormal As Zero flags in SSE which seems like a nice solution but I am not good with SIMD stuff. I am sure a better coder would have no trouble with it but yeah...

With what kb said regards to performance, I am testing on the shittiest cpu I have which is an old core 2 running at 2GHz and it runs ok on that but I get some weird slow downs when certain parameters are swept through certain ranges. Not sure if that is denormals, also not sure how much is the DAW and the shitty vst gui is sucking performance. Actually I don't know how much performance to expect, with every synth and every computer being different how do you measure synth performance? 16 notes of polyphony with all features maxed out on each voice? 32 notes? 64? I'm not writing orchestral scores but a few 3 or 4 note cords with overlapping attack/release times will chew up polyphony pretty quickly I guess.

Best part is the GUI design for sure, it's a masterpiece...

BB Image

Going to do some more ease of use refinements over the next couple of days and write some docs then send it out to a few musicians for evaluation.

Ended up being a long and rambling post anyway hey.
added on the 2016-11-28 14:05:19 by drift drift
Also for the sake of completeness and expressing my gratitude to revivalizer for the article that basically inspired me to start the 2nd incarnation of 64klang using SSE intrinsics:

http://revivalizer.dk/blog/2013/07/26/art-of-softsynth-development-simd-parallelization/
and
http://revivalizer.dk/blog/2013/07/28/art-of-softsynth-development-using-sse-in-c-plus-plus-without-the-hassle/
added on the 2016-11-28 14:07:39 by gopher gopher
Drift: That looks like something a musician CAN use! :)
added on the 2016-11-28 14:08:45 by leGend leGend
gopher: man those articles are exactly what I need. Awesome stuff.

leGend: yeah I am a simple guy so I need simple tools. Ever seen that movie Forrest Gump where the main character is a bit slow but he just tries to copy what other people do and somehow get's lucky and things work out ok? That's basically my life. Wanna help test my synth?
added on the 2016-11-28 14:18:41 by drift drift
@drift: looks fine to me!
added on the 2016-11-28 14:25:00 by trc_wm trc_wm
The performance fluctuations are most likely denormals, yeah. Adding a small constant to the output of each node (0.000003814697265625 aka 2^-18 in my case) should fix them, and if you're afraid of that dc offset, just add it and then subtract it again. This pushes the denormals out of the mantissa and gets you a nice zero. Just make sure the compiler doesn't optimize it out :)
added on the 2016-11-28 14:40:57 by kb_ kb_
Oh also while have my mind on the topic I wanted to also ask...

What are the cool kids doing for reverb these days? I made one with series of delayed comb filters but it's kinda complicated and doesn't sound that good tbh.

Tell me how you all feel about doing a precalced IR by simulating a burst of white noise in a virtual space and then using a convolution filter?
added on the 2016-11-28 14:41:54 by drift drift
never tried convolution based reverb, so i'm not a cool kid :)

64klang uses 8 parallel comb delays and 4 sequentiall allpass delays.
4klang uses the same setting (only for 1 channel) and just skipping the allpass sequence at the end.

basically this with some minor changes:
https://ccrma.stanford.edu/~jos/pasp/Freeverb.html
added on the 2016-11-28 15:01:55 by gopher gopher
IMO, convolutional reverbs are only worth doing if you want any kind of physical accuracy.

Reverbs using comb filter-like structures can sound good, as the Lexicon Model 200 shows https://youtu.be/EdFcKoEY1Ig?t=58.

They can give you high reflection densities without the computational load of a convolutional reverb. But you're right, the standard Moorer reverb sounds like a piece of wet newspaper. :)
added on the 2016-11-28 15:09:48 by trc_wm trc_wm
The other thing to note is that there isn't one reverb algorithm that sounds good on all types of material. What sounds good on tonal stuff such as vocals may not sound good on impulsive material like drums. Also, EQing can perfom miracles.
added on the 2016-11-28 15:14:24 by trc_wm trc_wm
Right so I just need to tweak my existing reverb since other people are getting good results with the same method. It's exactly the same as you described gopher with 8 comb into 4 allpass and also some predelay, attenuation, damping, whatever. AFAIK it's an extended version of Schroeder's reverb.

Everything is tweakable but I don't want 20 different variables exposed so I will have to find a "sweet spot" I guess.

This is great, getting more information in one night that usually takes me weeks of random googling. I owe you all a beer or something.
added on the 2016-11-28 15:39:21 by drift drift

login