Superfm

Hi! I'd like to play with the SuperDirt superfm synth, but can't understand the syntax. Can anyone give a quick example of how to set the parameters on the six operators?

I've read the docs here but I keep getting "variable out of scope" when I try to use e.g.:

d1 $ sound "superfm" # ratio2 2.3
3 Likes

You need to define the parameters before, e.g.
let ratio2 = pF "ratio2"

3 Likes

Ok that works, thank you! (Grazie mille :wink:)

You can also place the parameters in your BootTidal.hs file, I put this in mine, based on the docs:

-- additions for superfm support
:{
let ratio1 = pF "ratio1"
    ratio2 = pF "ratio2"
    amp1 = pF "amp1"
    amp2 = pF "amp2"
    detune1 = pF "detune1"
    detune2 = pF "detune2"
    mod11 = pF "mod11"
    mod12 = pF "mod12"
    mod21 = pF "mod21"
    mod22 = pF "mod22"
    eglevel11 = pF "eglevel11"
    eglevel12 = pF "eglevel12"
    eglevel21 = pF "eglevel21"
    eglevel22 = pF "eglevel22"
    eglevel31 = pF "eglevel31"
    eglevel32 = pF "eglevel32"
    eglevel41 = pF "eglevel41"
    eglevel42 = pF "eglevel42"
    egrate11 = pF "egrate11"
    egrate12 = pF "egrate12"
    egrate21 = pF "egrate21"
    egrate22 = pF "egrate22"
    egrate31 = pF "egrate31"
    egrate32 = pF "egrate32"
    egrate41 = pF "egrate41"
    egrate42 = pF "egrate42"
:}

I only tried a couple of these; you'll notice that it's only using 2 operators out of the available 6, but for that you'd need to type every operator combination into this list, if I understand it correctly.

I suppose it'd be easier to declare each necessary parameter in your script instead of placing hundreds of them in your boot file. I hope someone more knowledgeable on the Tidal innards can tell about the "proper" way to do things!

2 Likes

I use a secondary Parameters.hs file called from the BootTidal (and another one called Functions.hs).

That's neat :slight_smile:

1 Like

We should add these to the stock tidal parameters.. So what would the full list be like?

It'd also be nice to have some 'presets' to add to superfm, anyone have some interesting ones?

I can ask my friend Dave about evolving some using this https://fo.am/activities/midimutant/

1 Like

Rather than define all 81 parameters individually, I tried to add functions with parameters for the operator numbers:

-- sets the amount of operator 'op' in the superfm output mix
-- (1 <= op <= 6)
fmamp :: Int -> Pattern Double -> ControlPattern
fmamp op = pF ("amp" ++ show op)

-- sets the ratio for operator 'op'.
-- the frequency is note * ratio + detune Hz
-- (1 <= op <= 6)
fmratio :: Int -> Pattern Double -> ControlPattern
fmratio op = pF ("ratio" ++ show op)

-- set the detune for operator 'op'
fmdetune :: Int -> Pattern Double -> ControlPattern
fmdetune op = pF ("detune" ++ show op)

-- set the modulation of oerator opa by operator opb
-- if opa == opb, then the modulation amount is multiplied by the
-- 'feedback' parameter
fmmod :: Int -> Int -> Pattern Double -> ControlPattern
fmmod opa opb = pF ("mod" ++ show opa ++ show opb)

-- feedback
fmfeedback :: Pattern Double -> ControlPattern
fmfeedback = pF "feedback"

-- Envelope definition: each operator has an envelop with 4 steps
fmeglevel :: Int -> Int -> Pattern Double -> ControlPattern
fmeglevel op step = pF ("eglevel" ++ show op ++ show step)

-- Envelope definition: sets the rate at which the envelope moves
-- between steps.  Low numbers are slow, high numbers are fast.
fmegrate :: Int -> Int -> Pattern Double -> ControlPattern
fmegrate op step = pF ("egrate" ++ show op ++ show step)

Then you can use them like this:

d1 $ stut 2 0.7 0.125 $ slow 3 $ s "superfm" 
  |+| note (arp "pinkyup" "[0,4,7,12] [0,5,7,9]" )
  # fmfeedback 1    -- global multiplier for 'fmod x x' feedback parameters.
  # fmamp 1 1      -- amplitudes default to '1', so probably need to set all 6 of them...
  # fmamp 2 0
  # fmamp 3 1
  # fmamp 4 0
  # fmamp 5 0
  # fmamp 6 0
  # fmratio 1 1 -- oscillator frequency is note * ratio + detune Hz.
  # fmratio 2 0.5 -- (range 0.25 20 (slow 3 $ sine))
  # fmdetune 2 1
  # fmratio 3 0.26
  # fmmod 1 1 "0"   
  # fmmod 1 2  (range 0 4 (slow 4 $ sine))   -- fmod a b Pattern  = modulate op a with op b..
  # fmmod 1 3  (range 0 4 (slow 3 $ sine))
  # fmmod 3 2  (range 0 3 (slow 2 $ sine))
  # fmeglevel 1 1 "1" -- envelope generator has 4 rates and 4 steps for each operator...
  # fmeglevel 1 2 "0.5"
  # fmeglevel 1 3 "0"
  # fmeglevel 1 4 "0"
  # fmegrate 1 1 "10"  
  # fmegrate 1 2 "0.1"
  # fmegrate 1 3 "0.1"
  # fmegrate 1 4 "1"
  # fmeglevel 2 1 "1" -- envelope generator has 4 rates and 4 steps for each operator...
  # fmeglevel 2 2 "0"
  # fmeglevel 2 3 "0"
  # fmeglevel 2 4 "0"
  # fmegrate 2 1 "1"  
  # fmegrate 2 2 "0.3"
  # fmegrate 2 3 "0.7"
  # fmegrate 2 4 "1"
  # fmeglevel 3 1 "1" -- envelope generator has 4 rates and 4 steps for each operator...
  # fmeglevel 3 2 "0.2"
  # fmeglevel 3 3 "0"
  # fmeglevel 3 4 "1"
  # fmegrate 3 1 "10"  
  # fmegrate 3 2 "0.5"
  # fmegrate 3 3 "0.4"
  # fmegrate 3 4 "1"
  # room 0.3

The end result is pretty verbose though.

It makes some nice noises! (I have no idea what I'm doing, so I just put in numbers until something interesting came out...)

6 Likes

Oh boy, this is a lot saner! The syntax looks more straightforward this way -- using the envelope numbers as parameters instead of part of the function name seems to make much more sense. Thank you so much <3

1 Like

This is beaufitul, thank you so much :sparkles:

When I try to evaluate the functions it gives me the following error:

Warning: GHCi | mytidalfuncs.tidal:76:1: error:
Warning: GHCi |     • No instance for (Show (Int -> Pattern Double -> ControlPattern))
Warning: GHCi |         arising from a use of ‘print’
Warning: GHCi |         (maybe you haven't applied a function to enough arguments?)
Warning: GHCi |     • In a stmt of an interactive GHCi command: print it

Any idea why?

Could you post the code that is going wrong?

I can only guess at what you are doing otherwise, but one way to get that error message is to execute one of the functions on its own with no parameters.

e.g.


fmamp

will produce:

Warning: GHCi | <interactive>:247:1: error:
Warning: GHCi |     • No instance for (Show (Int -> Pattern Double -> ControlPattern))
Warning: GHCi |         arising from a use of ‘print’
Warning: GHCi |         (maybe you haven't applied a function to enough arguments?)
Warning: GHCi |     • In a stmt of an interactive GHCi command: print it

That is just Haskell telling you it doesn't know how to print a value of that type (in GHCi 'it' is the result of the last expression executed, and 'print it' is trying to print the result).

I just pasted your code in a new file called mytidalfuncs.tidal and then evaluated it from another file with:

:script mytidalfuncs.tidal

You need to make the loaded script into a multiline command by wrapping it in :{ :} like this:

:{
    -- sets the amount of operator 'op' in the superfm output mix
    -- (1 <= op <= 6)
    fmamp :: Int -> Pattern Double -> ControlPattern
    fmamp op = pF ("amp" ++ show op)

    -- sets the ratio for operator 'op'.
    -- the frequency is note * ratio + detune Hz
    -- (1 <= op <= 6)
    fmratio :: Int -> Pattern Double -> ControlPattern
    fmratio op = pF ("ratio" ++ show op)

    -- set the detune for operator 'op'
    fmdetune :: Int -> Pattern Double -> ControlPattern
    fmdetune op = pF ("detune" ++ show op)

    -- set the modulation of oerator opa by operator opb
    -- if opa == opb, then the modulation amount is multiplied by the
    -- 'feedback' parameter
    fmmod :: Int -> Int -> Pattern Double -> ControlPattern
    fmmod opa opb = pF ("mod" ++ show opa ++ show opb)

    -- feedback
    fmfeedback :: Pattern Double -> ControlPattern
    fmfeedback = pF "feedback"

    -- Envelope definition: each operator has an envelop with 4 steps
    fmeglevel :: Int -> Int -> Pattern Double -> ControlPattern
    fmeglevel op step = pF ("eglevel" ++ show op ++ show step)

    -- Envelope definition: sets the rate at which the envelope moves
    -- between steps.  Low numbers are slow, high numbers are fast.
    fmegrate :: Int -> Int -> Pattern Double -> ControlPattern
    fmegrate op step = pF ("egrate" ++ show op ++ show step)
:}

If I save that as fm.tidal and load it using :script fm.tidal it seems to work (no errors at least). I can't check if it actually works because my SuperCollider install is currently broken due to updating my mac to Big Sur beta....

1 Like

That was definitely the problem, thank you :slightly_smiling_face:

Hi all,

I'm the developer of the Superfm synth for SuperDirt. I am very happy and touched to see other people using it. This is very exciting.

Sorry for the late reply, I've been more into Supercollider than Tidal lately. I wasn't aware of this thread. A friend pointed it to me just now.

I see there's quite a bit of confusion on this synth, so I'm planning on setting up an online workshop, or tutorial, or stream, or whatever, to give some guidelines on how this synth works. I have a busy month ahead, but I'll try to do it sometime during October. I don't think it's possible to do it before. Would you be up for it? Maybe a YouTube stream? What do you people think?

I'll try to give some answers to the different questions.

That would be absolutely awsome! This is the full list:

-- modulators
mod11 = pF "mod11"
mod12 = pF "mod12"
mod13 = pF "mod13"
mod14 = pF "mod14"
mod15 = pF "mod15"
mod16 = pF "mod16"
mod21 = pF "mod21"
mod22 = pF "mod22"
mod23 = pF "mod23"
mod24 = pF "mod24"
mod25 = pF "mod25"
mod26 = pF "mod26"
mod31 = pF "mod31"
mod32 = pF "mod32"
mod33 = pF "mod33"
mod34 = pF "mod34"
mod35 = pF "mod35"
mod36 = pF "mod36"
mod41 = pF "mod41"
mod42 = pF "mod42"
mod43 = pF "mod43"
mod44 = pF "mod44"
mod45 = pF "mod45"
mod46 = pF "mod46"
mod51 = pF "mod51"
mod52 = pF "mod52"
mod53 = pF "mod53"
mod54 = pF "mod54"
mod55 = pF "mod55"
mod56 = pF "mod56"
mod61 = pF "mod61"
mod62 = pF "mod62"
mod63 = pF "mod63"
mod64 = pF "mod64"
mod65 = pF "mod65"
mod66 = pF "mod66"
-- operator envelope generator levels
eglevel11 = pF "eglevel11"
eglevel12 = pF "eglevel12"
eglevel13 = pF "eglevel13"
eglevel14 = pF "eglevel14"
eglevel21 = pF "eglevel21"
eglevel22 = pF "eglevel22"
eglevel23 = pF "eglevel23"
eglevel24 = pF "eglevel24"
eglevel31 = pF "eglevel31"
eglevel32 = pF "eglevel32"
eglevel33 = pF "eglevel33"
eglevel34 = pF "eglevel34"
eglevel41 = pF "eglevel41"
eglevel42 = pF "eglevel42"
eglevel43 = pF "eglevel43"
eglevel44 = pF "eglevel44"
eglevel51 = pF "eglevel51"
eglevel52 = pF "eglevel52"
eglevel53 = pF "eglevel53"
eglevel54 = pF "eglevel54"
eglevel61 = pF "eglevel61"
eglevel62 = pF "eglevel62"
eglevel63 = pF "eglevel63"
eglevel64 = pF "eglevel64" 
-- operator envelope generator rates
egrate11 = pF "egrate11"
egrate12 = pF "egrate12"
egrate13 = pF "egrate13"
egrate14 = pF "egrate14"
egrate21 = pF "egrate21"
egrate22 = pF "egrate22"
egrate23 = pF "egrate23"
egrate24 = pF "egrate24"
egrate31 = pF "egrate31"
egrate32 = pF "egrate32"
egrate33 = pF "egrate33"
egrate34 = pF "egrate34"
egrate41 = pF "egrate41"
egrate42 = pF "egrate42"
egrate43 = pF "egrate43"
egrate44 = pF "egrate44"
egrate51 = pF "egrate51"
egrate52 = pF "egrate52"
egrate53 = pF "egrate53"
egrate54 = pF "egrate54"
egrate61 = pF "egrate61"
egrate62 = pF "egrate62"
egrate63 = pF "egrate63"
egrate64 = pF "egrate64"
-- operator output levels
amp1 = pF "amp1"
amp2 = pF "amp2"
amp3 = pF "amp3"
amp4 = pF "amp4"
amp5 = pF "amp5"
amp6 = pF "amp6"
-- operator frequency ratios
ratio1 = pF "ratio1"
ratio2 = pF "ratio2"
ratio3 = pF "ratio3"
ratio4 = pF "ratio4"
ratio5 = pF "ratio5"
ratio6 = pF "ratio6"
-- operator frequency detuners
detune1 = pF "detune1"
detune2 = pF "detune2"
detune3 = pF "detune3"
detune4 = pF "detune4"
detune5 = pF "detune5"
detune6 = pF "detune6"
-- other
feedback = pF "feedback"
lfofreq = pF "lfofreq"
lfodepth = pF "lfodepth"

Yes, it'd be great! There are 6 presets already in the # voice parameter, but since I didn't really know what to put in them at first I let them be random for the time being. 0 is the default customizable one; [1-5] are random presets that are consistent for one boot - they are generated every time you boot SuperDirt and cleared when quitting the server, so you never get the same. Anyhow, by playing with it I came across a couple of nice presets that I will store in the # voice at some point, but will share here for now.

Double saw:

d1 $ s "superfm"
# n 0
# amp1 1
# amp2 0
# amp3 1
# amp4 0
# amp5 0
# amp6 0
# ratio2 1.01
# mod12 0.75
# mod34 0.7
# mod45 0.6

Double pulse:

d1 $ s "superfm"
# n 0
# amp1 1
# amp2 0
# amp3 1
# amp4 0
# amp5 0
# amp6 0
# ratio2 2.01
# ratio4 2
# ratio5 2
# mod12 0.75
# mod34 0.7
# mod45 0.6

FM bass:


d1 $ s "superfm"
# octave 4
# n 0
# amp1 1
# amp2 1
# amp3 0
# amp4 0
# amp5 0
# amp6 1
# ratio2 2
# ratio3 3
# ratio4 4
# ratio5 0.5
# ratio6 0.25
# feedback 1
# mod11 1
# mod16 1
# mod23 1
# mod34 1
# mod45 1
# mod66 1
# egrate61 1
# egrate62 10
# egrate63 1
# eglevel62 0.13
# eglevel63 1.5
# room 0.5

Wow! That's very very interesting. I love it. It would be so cool to have these.

This is absolutely brilliant. Thank you so much for posting it. I like it much better than the original function syntax, too. I'm not a Haskell person, so I very much appreciate this.

I think that's it for now. I'm open to further suggestions and improvements. I'll do my best on keeping track of any input. And would definitely love to see what interesting presets come out and may add them as voices, too. I myself discovered FM not long ago and am still in dipers, so any help will be very much appreciated.

Cheers!

9 Likes

This is fantastic, thank you for posting this!!

I've been in love with superfm even though I have no idea what I'm doing by fiddling with the parameters. I'd love to get a better notion of what's going on, and finally try to get FM theory by having a practical application.

+1 for a Youtube workshop, I'll definitely be there if it happens.

(as an aside, I found the random preset feature really interesting, in that you cannot keep the same sound over work sessions. There were a couple of times that I stumbled into some really interesting presets, but since I don't know how to analyse their parameters, they were bound to be lost on reboot. I'm sure there's a life analogy in this.)

1 Like

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.

3 Likes

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

5 Likes

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

1 Like