haha yes, probably. When I add the other presets I will keep at least one random. I like the suprises it give, as well as the unpredictability of it.

Great! There are some people interested in it in the community here in Barcelona, so I'll start working on it. Will let you know when it happens.

This is wonderful! I actually built the synth to be able to learn and practice FM myself in a fast and fun way.


Hey all! I was trying another approach, based on @paul1's code. Since everything uses numerical indices, the parameters work nicely with arrays.

These are the necessary definitions

-- Parameters
let fmamp op = pF ("amp" ++ show op)
    fmratio op = pF ("ratio" ++ show op)
    fmdetune op = pF ("detune" ++ show op)
    fmmod opa opb = pF ("mod" ++ show opa ++ show opb)
    fmegrate op step = pF ("egrate" ++ show op ++ show step)
    fmeglevel op step = pF ("eglevel" ++ show op ++ show step)
    fmfeedback = pF "feedback"

-- Array functions
let fmparam function = foldr (#) (gain 1) . zipWith function [1..]
    lfma = fmparam fmamp
    lfmr = fmparam fmratio
    lfmd = fmparam fmdetune
    lfmer op = fmparam (fmegrate op)
    lfmel op = fmparam (fmeglevel op)
    lfmm opa = fmparam (fmmod opa) -- didn't test, should work

And this is how you use it:

d1 $ stut 2 0.7 0.125 $ s "superfm"
  |+| note (arp "pinkyup" "[0,4,8,12] [0,3,7,11]" )
  # fmfeedback 1
  # fmdetune 2 1
  # lfmel 1 [1, 0.5, 0, 0]     -- EG Level (Operator 1)
  # lfmer 1 [10, 0.1, 0.1, 1]  -- EG Rate (Operator 1)
  # lfmel 2 [1, 0, 0, 0]       -- EG Level (Operator 2)
  # lfmer 2 [1, 0.3, 0.7, 1]   -- EG Rate (Operator 2)
  # lfmel 3 [1,0.2,0,1]        -- EG Level (Operator 3)
  # lfmer 3 [10,0.5,0.4,1]     -- EG Rate (Operator 3)
  # lfma [1, 1, 0, 0, 0, 1]    -- Amps (Operators 1..6)
  # lfmr [1, 0.26, 0.5]        -- Ratios (Operators 1..3)

Sorry about the names, they're very confusing right now. Also, big shoutout @loopier , great job with this SynthDef :slight_smile:

edit: the (gain 1) on fmparam is a placeholder - I couldn't find a neutral ControlMap function (like id)
edit2: adding lfmm for the fmmod function


This is superb. That was my initial thought but didn't find a way to implement it. Looks beautiful. Thanks!

1 Like

The array functions are much nicer than setting everything individually.

Does this work?

-- Array functions
let fmparam function (x:xs) = foldr (#) (function 1 x) (zipWith function [2..] xs)
    lfma = fmparam fmamp
    lfmr = fmparam fmratio
    lfmd = fmparam fmdetune
    lfmer op = fmparam (fmegrate op)
    lfmel op = fmparam (fmeglevel op)

My haskell is rusty and was never very good to start with, so there's probably a more elegant way to do it.


It works very well and it's a lot more compact, thank you @paul1 !

    $ stut 2 0.7 0.125
    $ slow 3 $ s "superfm" 
    |+| note (arp "pinkyup" "[0,4,7,12] [0,5,7,9]" )
    # fmfeedback 1
    # lfma [1, 1, 1, 0, 0, 0]
    # lfmr [1, (range 0 4 (slow 4 sine)), 0.26, 0.5, 0.5, 0.5]
    # lfmd [0, 1, 0, 0, 0, 0]
    # fmmod 1 1 "<0 1.25>"
    # fmmod 1 2 (range 0 4 (slow 4 sine))
    # fmmod 1 3 (range 0 4 (slow 3 saw))
    # fmmod 3 2 (range 0 3 (slow 2 sine))
    # lfmel 1 [1, 0.5, 0, 0, 0, 0]
    # lfmer 1 [10, 0.1, 0.1, 1, 0, 0]
    # lfmel 2 [1, 0, 0, 0, 0, 0]
    # lfmer 2 [1, 0.3, 0.7, 1, 0, 0]
    # lfmel 3 [1, 0.2, 0, 1, 0, 0]
    # lfmer 3 [10, 0.5, 0.4, 1, 0, 0]
    # lpf 1000
    # room 0.3

I just posted an anouncement for a streaming I'll be broadcasting next Thrusday to give an overview of the synth. Hoping to see you there.


Interesting syntax.

What does the l mean in lfma?

Isn't env more clear then eg? In SuperCollider I don't think I ever saw 'eg', but 'env' seems to be common to use in relation with envelopes. For now this seems more clear to me as a 'fm-newbie':


Shorter is not always better :wink:

Maybe even:


In SuperCollider you would write a envelope like

([0, 1, 1, 0], [2, 1, 3])

Where the first numbers are the levels and the last two numbers are the time or rate (att, sus, rel) in this case. A syntax like that isn't possible in Tidal/Haskell?

Although this syntax is shorter, I find myself having to think what each parameter is every time I have to change something. So, for me, is more convenient to have them separated at least between levels and rates. Just a matter of taste, I guess.

I like this one. Maybe it should be envlevels, envrates, fmmods and fmratios to denote the array, shouldn't it?
And how about op instead of fm as prefix?
As for the envelopes, envr and envl might be a nice short version.

Thinking about the single-value syntax I figured a more descriptive alternative would be to make a mix of ADSR and 4-stage-envelope naming. Maybe something like:

atkl1 -- attack level for operator 1, instead of eglelvel11
atkr1 -- attack rate for operator 1, instead of egrate11
decl1 -- decay level for operator 1, instead of eglelvel12
decr1 -- decay rate for operator 1, instead of egrate12
susl1 -- sustain level for operator 1, instead of eglelvel13
susr1 -- sustain rate for operator 1, instead of egrate13
rell1 -- release level for operator 1, instead of eglelvel14
relr1 -- release rate for operator 1, instead of egrate14

or maybe

-- or
1 Like

Good thoughts.

You have x amount of levels for the envelope, including the initial and end level. Then there are the timevalues between them, which is always levels -1 (so 4 -1 = 3 in this case).

For me it feels a bit strange to tear the envelope apart in individual arguments, which you can sort the way you want


for instance. In the Supercollider syntax, it's relation and position in the enveloppe is more clear to me at least.

Also using larger numbers for faster rates, feels a bit contractional to me. In most languages/software a smaller number would give you a faster attack. You told that you copied it from the original DX7, but I wonder if converting it to software should stick with that. That's just what my initial thoughts are, I'm not really qualified to judge this properly, just some thoughts/feedback.

Is there any relation of superfm with this DX7 implementation I found for supercollider?

I guess it's now time to start experimenting with the superfm synth. Thanks for the synth implementation and the livestream. I'm having quite some fun with it already. :sunglasses:

1 Like

I understand what you mean. For me, when live coding, I usually just tweak one or two envelope parameters, not the hole envelope. If I just want to change the decay time, for example, it makes more sense to me to just write the value for that parameter than the whole envelope thing because I have to consider the other parameters too, and remember their values.

I must confess that is still confusing for me too, because I'm used to Supercolllider's syntax with times instead of rates. The advantage of keeping the DX7 scheme is that is easier to port the loads of documented patches out there, which are all set in rates.

No, there isn't. That implementation is a serious one, really cool and sounds awesome, but it's quite complex. Superfm is just using Supercollider's FM7 Ugen, with added envelopes and mappings to the control and mod matrices. I haven't implemented fixed operator frequencies, mod sensitivity or keyboard breakpoints, band depths and curves (yet ; P).

But having you all enjoying it as you do is motivating to do it!


Personally I'm more fan of a bit longer but clearer names likes these, compared to the very short ones (ar1 etc.)

But it the end it's your synth, you decide :wink: @loopier

1 Like

It's short for "list" :clown_face:
I thought it would be intuitive at first, but yeah, fmamps is obviously way clearer!

1 Like

Where does op stands for?

1 Like

It stands for "operator"


Ok, if that's technically a more appropriate naming, it might be better indeed. It's up to you I guess.

1 Like

that's the thing though - none of this is standard library, so you can call it whatever you find more fitting :slight_smile:


Sure, but it might be nice too have some nice default settings :slight_smile:

1 Like

Yep it'd be nice for it to be in the standard library I think!


It'd be good to get an improved superfm UI in the next tidal release, did we arrive at consensus?

1 Like

For what my opinion is worth, I think @ghales's version is very sane. I'm currently using @paul1's version, but I find it to be very verbose, especially when you're only using a couple oscillators at a time but you still have to define the others at 0.