Modifying and compiling synthdefs

I’m starting a new topic to carry on the tangent I started in another topic over here.

For background, I noticed that the piano synth, which in Sonic Pi only supports whole midi notes, actually has a tune parameter that looks like it should allow setting fractional notes (here in the Supercollider source).

So, I wanted to try compiling a synthdef that connected up the tune input and see if I could get it working. However, I have no experience with Supercollider/Overtone/synthdefs etc., so there’s a bit of a learning curve.

After a bit of trial and error I managed to get the existing piano synthdef recompiled (I created a new Clojure Leiningen project with a dependency on overtone, and eval’d this line in a repl after pulling in overtone.live, which wrote a new sonic-pi-piano.scsyndef file).

My initial try at deriving the tune parameter from the fractional part of the note input caused it to fail to compile, with:

CompilerException java.lang.ClassCastException:
overtone.sc.machinery.ugen.sc_ugen.ControlProxy cannot be cast to java.lang.Character,
compiling:(sonic_pi/synths/traditional.clj:19:1)

I’m not sure exactly what that means, bit I think it means I can’t just use standard Clojure functions in the synthdef; is that the case?

In any case, to verify that the tune parameter actual does what I suspect/hope, I decided for now to just add a parameter to the synthdef and wire it directly to the same parameter in the Supercollider synth. After recompiling the synthdef I copied the resulting file over the one in my downloaded Sonic Pi app, but changing the tune parameter didn’t seem to do anything to the sound. I also tried modifying synthinfo.rb to add the parameter but that didn’t help. The changes I made are here, and the code I tried in Sonic Pi is:

[-1, 0, 0.5, 1, 2].each do |t|
  synth :piano, note: :c4, tune: t
  sleep 0.2
end

I’m not sure that the parameter I’m setting in Sonic Pi is actually making it into the synth. Do I have to do anything else to make Sonic Pi aware of the new synth param?
Can I just patch my changes into a downloaded Sonic Pi release as I did, or do I need to compile it myself with the changes? I’ve spent a bit of time trying to get the build working, but have been unsuccessful so far.

Any help/pointers would be appreciated.

Thanks,
Emlyn

1 Like

My first bit of advice would be to avoid Overtone altogether and just focus on SuperCollider code for defining synthdefs. We’ll be moving over to SuperCollider going forward as it’s way easier to set up and has a lot more community support.

2 Likes

What Sam said. I’m in the process of designing several new synths and FX myself, and the idea is that from now on they will all be designed using SuperCollider’s language directly. I’m happy to do what I can if you’d like any assistance in that direction.

3 Likes

Overtone seemed less daunting as I’m already familiar with Clojure, but I’ve had a bit of a look into SuperCollider and managed to get a basic synthdef working, with most of the parameters hardcoded for now, but with the tune parameter set from the fractional part of the note. I’ve loaded it into Sonic Pi with load_synthdefs, and it can play fractional tuning!

My synthdef so far looks like:

(
SynthDef(\testPiano, {|
	note = 52, decay = 0, release = 1,
	velcurve = 0.8, stereo_width = 0,
	out_bus = 0 |

	var n = round(note, 1);
	var tune = note - n + 0.5;
	var snd = MdaPiano.ar(freq: n.midicps, tune: tune, gate: 1, vel: 64,
		decay: decay, release: release, hard: 0.5, velhard: 0.8,
		muffle: 0.8, velmuff: 0.8, velcurve: velcurve, stereo: stereo_width,
		random: 0, stretch: 0, sustain: 0.1);
	Out.ar(out_bus, snd)}
).writeDefFile("/output/path/")
)

I still need to implement the rest of the functionality from the built-in piano synth, but it’s looking promising :smiley:. I don’t suppose you’ve got an example I could use as a reference for how things like the varlag, clipping/scaling, envelope etc. would be done in SuperCollider?

Thanks a lot for your help!
Emlyn

1 Like

OK, i think I’ve got it working! There was quite a bit of trial and error involved, so it might not be 100% correct and/or might not follow best practise, but it seems to work alright in my limited testing.
@ethancrawford / @samaaron does this look reasonable:

(
SynthDef(\testPiano, {|
	note = 52, amp = 1, amp_slide = 0, amp_slide_shape = 1, amp_slide_curve = 0,
	pan = 0, pan_slide = 0, pan_slide_shape = 1, pan_slide_curve = 0,
	attack = 0, decay = 0, sustain = 0, release = 1,
	attack_level = 1, decay_level = -1, sustain_level = 1, env_curve = 1,
	vel = 0.2, hard = 0.5, velcurve = 0.8, stereo_width = 0,
	out_bus = 0 |

	var note_ = round(note, 1);
	var tune = note - note_ + 0.5;
	var amp_ = amp.varlag(amp_slide, amp_slide_curve, amp_slide_shape);
	var pan_ = amp.varlag(pan_slide, pan_slide_curve, pan_slide_shape);
	var decay_level_ = Select.kr(decay_level = -1, [decay_level, sustain_level]);
	var vel_ = vel.clip(0, 1).linlin(0, 1, 0, 4 * 127);
	var hard_ = hard.clip(0, 1).linlin(0, 1, -3, 3);
	var snd = MdaPiano.ar(freq: note_.midicps, tune: tune, gate: 1, vel: vel_,
		decay: decay, release: release, hard: hard_, velhard: 0.8,
		muffle: 0.8, velmuff: 0.8, velcurve: velcurve, stereo: stereo_width,
		random: 0, stretch: 0, sustain: 0.1);
	var snd_ = Balance2.ar(snd[0], snd[1], pan, amp);
	var env = EnvGen.kr(Env.new([0, attack_level, decay_level_, sustain_level, 0],
		                        [attack, decay, sustain, release],
		                        env_curve), doneAction: 2);
	Out.ar(out_bus, env * snd_)}
).writeDefFile("/Users/emcorrin/dev/personal/synthdefs/compiled/")
)

If it looks OK, I’ll open a PR on Sonic Pi to replace the existing :piano synth with this. In which directory should I put it, etc/synthdefs/designs? Do I need to generate an updated dot file somehow, or is that not possible with SuperCollider synthdefs?

Apologies for the delay - looks good so far! Have added a few comments on your PR. Also, maybe I just don’t remember, but I’ve been using the Varlag.kr() command instead of .varlag() - so I learnt something new today haha :+1:

1 Like

Hi,

I am slowly working my way into Supercollider and have started experimenting with syntdefs. Now an obvious (and probably) newby questions popped up.

I am working with the following synthdef:

// Thanks to snappizz, http://sccode.org/1-522 thanks to hint and help by Alexandre Enkerli
// At least you can specify frequency as a midi note
(
SynthDef(\rhodes, {
    |
    // standard meanings
    out_bus = 0, note = 70, amp = 1, out = 0, gate = 1, pan = 0,
    // all of these range from 0 to 1
    vel = 0.5, modIndex = 0.2, mix = 0.2, lfoSpeed = 0.4, lfoDepth = 0.1
    |
	var freq = note.midicps;
    var env1, env2, env3, env4;
    var osc1, osc2, osc3, osc4, snd;

    lfoSpeed = lfoSpeed * 12;

    freq = freq * 2;

    env1 = EnvGen.ar(Env.adsr(0.001, 1.25, 0.0, 0.04, curve: \lin));
    env2 = EnvGen.ar(Env.adsr(0.001, 1.00, 0.0, 0.04, curve: \lin));
    env3 = EnvGen.ar(Env.adsr(0.001, 1.50, 0.0, 0.04, curve: \lin));
    env4 = EnvGen.ar(Env.adsr(0.001, 1.50, 0.0, 0.04, curve: \lin));

    osc4 = SinOsc.ar(freq * 0.5) * 2pi * 2 * 0.535887 * modIndex * env4 * vel;
    osc3 = SinOsc.ar(freq, osc4) * env3 * vel;
    osc2 = SinOsc.ar(freq * 15) * 2pi * 0.108819 * env2 * vel;
    osc1 = SinOsc.ar(freq, osc2) * env1 * vel;
    snd = Mix((osc3 * (1 - mix)) + (osc1 * mix));
    snd = snd * (SinOsc.ar(lfoSpeed) * lfoDepth + 1);

    // using the doneAction: 2 on the other envs can create clicks (bc of the linear curve maybe?)
    snd = snd * EnvGen.ar(Env.asr(0, 1, 0.1), gate, doneAction: 2);
    snd = Pan2.ar(snd, pan, amp);

    Out.ar(out_bus, snd);
}).writeDefFile("/home/marty/tmp/")

)

This works quite well but I am a bit unsure about how the standard way to specify a note to be played and - on the other hand - the synthdef arg note are related:

load_synthdefs "/home/marty/tmp/" # containing the file rhodes.synthdef
use_synth :rhodes
play :c4

will obviously not work. On the other hand …

load_synthdefs "/home/marty/tmp/"
use_synth :rhodes
play note: 60, vel: 0.2

… obviously will. It is clear to me, that I can (and have to) address the specified arguments defined in my synthdef.

Is there a way do convince my :rhodes to accept the param given to play?

And how would I achieve the :rhodes` synth to play e. g.:

load_synthdefs "/home/marty/tmp/"
use_synth :rhodes
play (chord :c, :M), vel: 0.2
# play_chord (chord :c, :M), vel: 0.2 # or this one

?

I’d be very grateful for some hints and/or explanations.

PS.: Can we add sclang for code highlighting in this forum? I guess it is not yet supported but seems to make sense to me.
PPS.: From the Supercollider discourse forum: Javascript works quite well for highlighting.

1 Like

Hey Martin,

You are right that something extra is needed to make any custom synth accept things like note names.
(For a little more context, a similar question has been raised previously at Trouble adding new Synthdefs for example)
However, off the top of my head, as far as I remember it would currently be necessary to add a new section into the synthinfo.rb file that contains the metadata for your synth, and within this, you would override a particular function which ‘munges’ the opts before they are sent to SuperCollider, where you would in turn call the function that converts the note names to midi numbers. A bit vague as instructions go, but I’ll add a bit more detail tomorrow if desired and someone else doesn’t chime in first! (It’s late here and I’m off to work in the morning after 3 week’s break) :scream:

Hi Ethan,

thanks for commenting!

I will check this out.

Oh yes, I would appreciate this very much as I am serious in learning how to handle Supercollider and - of course - also, how to extend SP.

By the way, Eli Fieldsteel MUS 499C Fall 2019: Audio Coding with SuperCollider is - at least for me - a brilliant and entertaining introduction. I also noticed that Supercollider’s sclang is in some respects quite similar to Ruby (though in others not at all :wink: ).

So, yeah. It turns out that in order for Sonic Pi to automatically handle note names, it needs to be treated like a built in synth. As I remembered above, you do indeed need to ensure that the synth has metadata in the synthinfo.rb file (but the bit about overriding a function that munges the synth opts was me muddling that up with something else :stuck_out_tongue:)
Here’s a rough outline of how you’d set this up:
To add synth metadata into synthinfo.rb, add a new class representing your synth, that inherits from SonicPiSynth. This is one such example, for the :piano synth:

Once that is in there, you also want to add a line further down that instantiates a new object of your synth, (here’s the corresponding entry for the piano synth):

Once these two things are done, the synth function should automatically handle note names, as seen here:

Is that helpful enough? let me know how you get on, always happy to answer any synth/synthdef questions if I can :slight_smile:

3 Likes

A quick and dirty hack that I’ve used in the past, is to just name the new synth the same as one of the built-in synths and overwrite the synthdef file for that synth. This is obviously completely unsupported, so might well fail/break, and you’ll need to put the original synthdef back in place afterwards to get the old synth back, but it could be handy if you just want to quickly try something out as if it were a built-in synth.

2 Likes

Hi @ethancrawford,

thanks a lot! Will try that out as soon as possible. From you description seems to be clear and (more or less) straight forward thing to do.

1 Like

@emlyn and/or anyone else: if you have had success in the past (re)compiling one of the overtone synth designs, or gotten close, or are willing to give it a go, I’d appreciate a hand testing a recompile of some of the overtone based synths. If you’re up for it, can you take the changes made here:


and try recompiling those synths?

(I don’t seem to have any luck getting it running properly, so can’t test those changes…)

If nobody beats me to it, I’m happy to look into this next week after the release of v3.2.1 :slight_smile:

2 Likes

I was able to get them to compile by changing the (:require [sonic-pi.synths.core :as core]) at the top of the file to (:require [sonic-pi.core :as core]), and creating a /Users/sam/Development/ folder with a link inside pointing to my sonic-pi working dir.

EDIT: like this.

2 Likes

Oh, excellent. Can you try controling the pulse_width parameter over time with a pulse_width_slide value?

Yes, that seems to work. I ran this:

use_synth :pulse

play :e, pulse_width: 0.5, release: 1
sleep 1
s = play :e, pulse_width: 0.5, release: 1
control s, pulse_width: 0.99, slide: 1
sleep 1
s = play :e, pulse_width: 0.01, release: 1
control s, pulse_width: 0.5, slide: 1

and you can hear the timbre changing in the second two. I can’t attach a wav file here so here’s a spectrogram:

1 Like

Thanks @emlyn! good to know.

Does someone know how to declare and load synthdefs as effects? The tick in preferences suggests that external effects seem to be possible. But personally I do not see how a
loaded SC synthdef could be differentiated such that I could load it via with_fx in sonic pi.
And a clear explanation how to generate a SC synthdef that it is ready to be used as an effect with the ‘with_fx’-command in sonic pi seems also not available. The only thing possibly close to this are the SC-documents
makeFX and
ping_pong
that unfortunately do not have an extra line of documentation.

Hello @buce,

You can still use custom FX and synths via with_fx or synth/use_synth/with_synth. The only differences between those and built-in FX and synths that are integrated into the app are that you call them slightly differently, and things like autocompletion and safety-checking for opts are not available - until you add metadata into a certain file. (sonic-pi/synthinfo.rb at dev · sonic-pi-net/sonic-pi · GitHub)

In case you have not already seen it, instructions for creating and using custom Synthdefs are available at the following link:

If you decide you would like to add the few extra benefits that integrating your Synthdefs into synthinfo.rb provides, and you need help to do so, I’m sure someone would be able to help.

1 Like