Hello all! I made my first YouTube video about SuperCollider! It’s about FM synthesis; my DX7 inspired SynthDef that I used making my recent album “Permutations” released by Biodiversità Records. Here’s the video:
Maybe of interest could be the way I implemented the single sample feedback using Duty, Dbufrd and Dbufwr. It is also possible there is some errors in that part, but at least it makes nice noises, which is the most important thing for me:)
Thx for sharing - regarding single sample feedback, this is not really single sample feedback but still block sized feedback This can also makes nice sounding sounds, but just to keep the record straight and don’t lead others on a wrong path here.
I also didn’t find it really obvious at first and the docs could be better around this - it also took me some time to go through this.
As long as you stay in “duty town” (so within DemandRate) you can actually get single sample feedback, this is also what the example of the docs demonstrate. There is an even simpler example which can show this by building a single sample feedback phasor
(
var buf = Buffer.alloc(s, 1);
{
var read = Dbufrd(buf);
var write = Dbufwr(read + 1.0, buf);
var phasor = Phasor.ar(end: 10e8); // ground truth
var bufVal = Duty.ar(1/SampleRate.ir, 0, write);
(bufVal - phasor).poll; // stays constant at 1 for every sample
}.plot
)
The moment we turn the demand rate UGen into audio rate via Duty, we loose its single sample capabilities b/c we are now in audio rate land, which operates at the block size. The example from above, but now using DC.ar(1.0) as the source for the accumulator …
(
var buf = Buffer.alloc(s, 1);
{
var read = Dbufrd(buf);
// modify the signal with a ugen before writing it back into the buffer
var sig = Duty.ar(1/SampleRate.ir, level: read) + DC.ar(1.0);
var write = Dbufwr(sig, buf);
var bufVal = Duty.ar(1/SampleRate.ir, 0, write);
[bufVal].poll;
}.plot
)
… and stair cases appear in the rate of the block size
So looking at your code, you are doing exactly this
I allowed myself to rewrite the first patch into DynGen which implements single sample feedback, primarily b/c I was interested in the difference that this makes and I’d say it is present
Here I am using a pattern from you, starting with the single sample one and switching to the demand rate version at around 0:03 (and then switching back and forth). The DynGen w/ “true single sample” has a bit more hi-hat/crash feel to it IMO, but both are interesting sounds.
I also find it interesting that everyone seems to remember and attribute Chowning for FM (FM was already possible w/ buchla/moog, he made a digital implementation with modulatable depth), but not a single engineer from Yamaha who actually made digital FM synthesis practical and popular and figuring out that PM is the way to go
For people who are interested in some history on the DX7, check out Yamaha DX7 Technical Analysis - ajxs.me and https://www.youtube.com/watch?v=sXt_NXjc7oY
Great explanation thanks! I think I will leave the code as it is, as it was just more about presenting the way I used it making one of the track on the album. I have almost zero DSP knowledge and I’m just very practice based SuperCollider user. I was actually wondering if the blocksize still makes an effect and clearly it still does. So basically using the duty would be excactly as using the Local ins and Local outs?
You are right about the engineers. Actually I just remembered that I had on my mind to dedicate the album to Chowning and to the anonymous engineers at Yamaha, but I actually forgot to do that:( I knew about Buchla though so I say “digital FM synthesis” in the video. I dedicated to Chowning also because of his music, which I enjoy.
Regarding the single sample, is there any way to achieve it with stock SuperCollider ugens, but still blockSize 64?
Block size 64 rules out single sample feedback, period.
IIRC SC 3.15 will have the ability to override the block size for a SynthDef, so you could run just these synths with one sample blocks while everything else is 64 samples.
Thank you for the explanations.
I’m struggle to understand
IIUC you get single sample feedback in this example:
but you loose single sample feedback in this one:
is this right ?
This is what I’m struggle to understand.
Cause Duty.ar is used in every examples, including the ones which are single sample feedback.
Are we loosing the single sample feedback when we are using Duty.ar before writing the incremented signal back in the buffer ?
But not when we use Duty.ar to get the buffer values (the last step) ?
It’s not clear for me.
And this add even more confusion:
Duty.ar transforms a demand rate signal into an audio rate signal. All Audio rate UGens are calculating samples in chunks, which is called the block size (which is done for performance reasons). Duty.ar is no exception here, so the demand rate source gets evaluated for a number of samples (which is where you loose the ability for single sample feedback b/c you are now evaluating a block of samples instead of evaluating a single sample).
The phasor example never accesses any audio rate values for its internal demand rate calculation, so single sample feedback works fine there.
The second example is transforming demand rate into audio rate before writing it back into a buffer at demand rate - here you loose single sample access b/c you transferred the demand rate into audio rate via Duty.ar. Otherwise you would use demand rate to influence the block size of UGen calculation, which is not happening.
You could indeed just as well use LocalIn/LocalOut in your code for the same effect.
As mentioned, the block size determines the number of samples an audio rate UGen is calculating at a time.
So either your UGen can perform single sample feedback on its own (see e.g. DynGen or sc_faust, which allow you to dynamically write UGens on the fly) or you need to run the server at block size 1.[1]
SC 3.15 will allow you to run Synths at a different block size than the server block size - though if you only have only synths running at block size 1, you could just decrease the block size of the server, it would have the same effect.
Hope this helps, plz write if there are still some things unclear.
There is another possibility/“hack” by using a recursive sledge of UGens connected via Buffers, which is what Fb1 of the miSCellaneous_lib is using. Check its docs for more info on that. ↩︎
Thank you very much,
it helps a lot, it’s much clearer to me now.
What confuse me in this statement:
is the fact that you can actually perform single-sample feedback with a block size of 64 on the server, if you use exclusively demand rate Ugens.
If this condition is met block size of the server doesn’t matter ?
Is this right ?
But from the moment you use an audio rate Ugen anywhere in between the demand rate Ugens chain (before writing it back into a buffer at demand rate) then any server bloc size except a size of 1 will rules out single sample feedback.
Is this correct ?
But single sample feedback using DemandRate is rather obscure I’d say (and ofc limited), I did not come across it before, so I was also really curious about it.
Yup I knew about the FM7 ugen. The code I used in the record was more about an exercise to make my own version of DX7, of course not perfect by any means. I think it was fun to expose all the parameters and think of ways of using them by patterns etc.
Oh actuallly, the code which is at Github, the very end has a version of the code that has Ambisonic panning with
You could try running your server at different block sizes and see how it affects the sound.
And then if you get ambitious, start messing about with feedback generally (distorted feedback is usually a lot of fun - but make sure there’s a limiter at the end of your chain).
With this is was somehow easy to re-implement a single voice of the DX7 within DynGen, and also learned some other stuff on the way that I’ll use Also fixed a bug in DynGen.
See DynGen - Dynamic UGen - #61 by dscheiba if you are interested in how to implement the DX7 algorithm correctly.
The FM7 UGen does not implement feedback like in the DX7. In selector 5, the DX7 uses the average of the past two values via its feedback shift registers, FM7 only uses the last sample. This actually makes some difference in sound since you can crank up the feedback much higher and results in a different timbre of the feedback.
What performs even better in this case is a slope tracking onepole in the feedback path, where you could additionally scale the slope with a filter ratio param.
I used the Reverse-Engineering site also, while I was designing my on version but that Github page finally made the actual design to click and I confirmed just now when I opened Dexed.
So if I understood correctly the modulation indices in the DX7 are envelope values (from 0 99, like Operator 1 Level 1) and then you control the modulation by the rates of different steps of the ADSR and I guess modulation depth.
In SuperCollider I think it’s common to have one index value and then an envelope to scale it with, but that’s not really how it works in DX7. But I actually like to design the FM like that, as then you can have the index values very high and it produces interesting results.
I wonder how the index envelope levels of 0 to 99 scales to SuperCollider world? I guess it’s somewhere in the patent, but that’s a bit too technical for me.
Also I have hard time contextualixing the ADSR rate’s as I don’t really know how they scale, not sure what are the actual values used, like the rate of 1, seems to be something very long.
Yes, but keep in mind that the DX7 does not really follow a traditional ADSR flow where the levels are [0, 1(attackTargetLevel), sustainLevel(decayTargetLevel), 0(releaseTargetLevel)] but allow for arbitrary levels at arbitrary times. This can be easily obtained through an Env with 4 stages.
From my experience, a FB value of above 2 becomes rather noisy, even with the filtered approach in the DX7.
BTW - the DX7 itself does not implement any kind of floating point operation, so everything has to be integer based - i.e. sine levels are obtained via a wave table lookup[1].
If you want to reverse engineer the 0-99 levels, you should read into dexed/Source/msfa/env.h at 2e182b3db85c09083ab13c8b9b00565ce7d9ff85 · asb2m10/dexed · GitHub
But I am also no expert on DX7, just started looking into it thanks to your post
No, this is also what the DX7 is doing - the envelope gets attenuated from 0-99, this does not mean that you are using 99 as a modulation multiplier.