SC vs Pure Data, race to the finish line

I would certainly agree with you on all those points, although I don’t think I ever really got into the midi options.

The other thing, although not important in regard to your comment, is that I never really understood why they trashed the Extended version of PD and kept Vanilla only. I thought Extended was really useful for beginners, and others too who needed to get up and running fast. I few years ago I wanted to show some students a musical process (using PD for the visual aspect), and realised that half of my older patches didn’t work because you need re-load various objects (some quite basic too) into PD. I found this not only time consuming, but also couldn’t really see what the advantage was either.

Pd-extended was essentially maintained by a single person. Eventually, the maintainer burned out and nobody stepped in. The spiritual successor to Pd-extended is Purr Data: https://www.purrdata.net/

IMO, having a small core + libraries maintained by different people is much more sustainable in the long run. Sure, a huge monolith with batteries included is nice for beginners, but it quickly becomes unmaintainable. This actually happened with sc3-plugins - they are now frozen and there is a still on-going discussion on how to deal with new UGen plugins in the future.

Have you looked into [switch~]?

Just a couple of things, loosely connected to the preceding discussion.

I had had some exposure to Max before starting with SuperCollider, and this experience did help me to understand more quickly how synthesis graphs work in SC. I would have gotten it eventually but having that mental framework of signals passing from outputs to inputs made sense quickly.

I find in my teaching that precious few students (at this school) are willing to engage with code, but some will engage with graphical environments.

I think that Pd and Max have a pedagogy problem. In the last couple years, I figured out a half dozen or so code design patterns that handle some common (but not obvious) cases. It’s impossible to do anything interesting with boxes and wires without addressing these tricky cases (e.g. just today I had a list with a symbol and a number, right to left the number comes out first but I needed it to be processed second – handled it quickly because I’d hit this case before, a Pd novice would have been lost) – but – here I can’t speak for the Max community, but the Pd community doesn’t talk about them at all. It’s quite strange.

Pd and Max are successful in that people use them to do lovely things… but, if the promise of graphical programming was supposed to be to make the flexibility of programming available to non-programmers, I’d have to say it’s only partially successful: it ends up being just as fussy, fiddly and abstract as any other programming language. For example, one of those design patterns is “initialize - process - finalize” (which can be driven by a [t] object at the top). In imperative code, it reads in line order, easy. In Pd it’s right to left and the first time you see it, you might need it explained to you (and even though I already know a thing or two about programming, it took me over a year to identify this as a general strategy).

hjh

1 Like

Okay, I didn’t know that - about the one person only maintaining Pd-extended. Indeed it does explain what and why that happened.

Thanks for info.

I found these to be very helpful, but going through the tutorial documents for Pd was definitely frustrating. I love how detailed the help browser is for SC and I definitely feel that I can solve more problems on my own with SC than with Pd.

But at the end of the day there is definitely something to be said for (as an example) seeing an envelope generator in Pd vs the equivalent in SC.

I think there’s something to be said for how people think, too.
I am a professional software engineer and I simply cannot think in dataflow languages. They just don’t work with my brain.

1 Like

I’m now starting a bit with pure data. I’ve to say it’s fun. Sure, the down part is certainly the cumbersome fiddling with the graphical boxes. If you compare it with SC, o my!

Makes you wonder as a relative n00b, why there is no text interface for pd, or is that plain C?

But being slow and visual, helps me understand the sound theories better I think. Until now I didn’t encounter too many different objects, I guess that’s the minimal part of pd, people are talking about. For now I think that’s a nice part. In SC there is so many stuff, you’re constantly diving into the helpfiles, that has a influence on creativity I think.

The help system of pd is nice as well. Short lines there to the information you need.

Man, that gui makes the patches art in itself, I’m in love with it. I kinda understand the temptation to start messing with colors and making the gui prettier, but I don’t think that’s a smart thing to do actually, as at the end I think it won’t help to make things easier. Just more distracting.

So I tend to disagree with @jamshark70, if I would teach students I probably would start with pure data. The hot and cold inlets just needs a good explanation. But I’ve no experience teaching it, so it’s just a thought.

I’m reading this book by the way: Programming Electronic Music in Pd

Oh and to be able to use libpd, makes me excited too. Would be nice if that would be possible with SC as well. And a advantage of pd might be the permissive license (BSD).

And the modularity of pd seems to be great. The possibility to use subpatches and abstractions, might be also something that SC is missing.

I think it becomes quite clear that the developer of pd comes from a C/Unix/Linux background.

2 Likes

I do teach students Pure Data rather than SuperCollider… my point was not about the usefulness of Pd in the classroom, but rather about Pd’s ability to extend into more complex cases.

To take a concrete example, if we expand Array.fill(n, { |i| i }) out to its components, then you can see every element of the SC code represented in Pd.

One of the claims that is sometimes made, or a rationale given for teaching with Max / Pd, is that it’s easier than code. Once you get into list operations (and, let’s not kid ourselves, there’s not much you can do with algorithmic composition that doesn’t depend on list operations), I don’t think this claim is justified. If you think through initialization – processing – final output, then the structure of the Pd patch is logical. But, you can find many code tutorials online that will explain how to build a list this way (straightforward to translate to SC), while AFAICS there is significantly less work being done to establish graphical programming best practices. One result is that it’s fairly common for the question to come up in the Pd forum, “how do I count?” … and I’m convinced that it comes up so often because it isn’t obvious how to do this. (Perhaps the code version isn’t obvious either – but coders have bothered to make standard models for common cases such as this, while Pd/Max users seem not to be interested in such.)

Capable? Sure. Easier than code… I have my doubts.

hjh

2 Likes

???

… Functions, Classes (pseudo UGens, custom Patterns), SynthDef factories …

Sure, I’ve still a lot to learn. :wink:

I should add that the “how to count” question doesn’t come up extremely frequently (and this might be because you can do a lot of fun things without counting), but when it does, it makes me wonder why it isn’t covered in tutorials. (Then again, SC tutorials don’t really cover this either… hmm…)

One complaint that’s sometimes raised about SC is that you can’t use SynthDefs within SynthDefs (whereas in Pd, signal-processing abstractions can be nested as many layers deep as you need). A possible solution to that is to think in terms of signal-processing functions, rather than SynthDefs. Function calls can be nested easily, and merely wrapped in a SynthDef at the end:

~saw = { |freq| Saw.ar(freq) };

~lpf = { |sig, ffreq, rq| BLowPass4.ar(sig, ffreq, rq) };

~mainEg = { |gate| EnvGen.kr(Env.adsr, gate, doneAction: 2) };

SynthDef(\demo, { |out = 0, gate = 1, freq = 440, ffreq = 2000, rq = 1, amp = 0.1|
	var sig = ~saw.(freq);
	var eg = ~mainEg.(gate);
	sig = ~lpf.(sig, ffreq, rq);
	Out.ar(out, (sig * eg * amp).dup);
}).add;

^^ EDIT: Fixed the below-referenced typo.

Much of our documentation is oriented around the SynthDef as a self-contained unit. I’m gradually coming to feel that this may lead to some perhaps-unnecessary limitations in thinking – such as, the above suggests that SC synthesis can be conceived in a modular way, but often isn’t. (Another is the idea that SynthDef itself should be responsible for more interesting mapping between signals and inputs – I think mapping should be implemented in a superstructure over SynthDef.)

~~

In the control layer, a big difference between SC and Pd is that SC follows a “pull” model of computation, while Pd is “push.”

SC: Complex(2, 1).squared – you’re “pulling” a result from this method. In order to calculate the result, the method must “pull” results from other methods and so on.

Pd: Put data in at the top, and it’s “pushed” down through the cables until it comes out as something else at the bottom. This is great for e.g. MIDI or OSC response, less so for math.

A problem with the push model is that the code/patch needs to know in advance where the data will be pushed to. This makes it difficult for Pd to implement a resource that can be queried from multiple locations in a general way.

SC: p = Point(1, 2) and any expression within p’s scope can ask p.x or p.y at any time.

Pd: (Hypothetically) [point 1 2] → something, but how does another dataflow branch ask this [point] instance what its ‘x’ is? You can’t have one [point] instance sometimes pass its value in one direction and sometimes in another direction, depending on who the caller is. Workarounds for this become irritating, quickly.

hjh

3 Likes

Are the .ar necessary here, or left over from a find/replace?

Typo, good catch, I’ll correct it.

Somewhere on the forum, I had posted an example of lazy-resolution of a synthesis graph, similar in concept but cooler :grin: edit: found – Order of execution - #21 by jamshark70

hjh

Still, adding some extra inlets and outlets is just some extra layman work. Writing classes a different kind of labor I think. You need to know how to write classes and I’m not sure how fast writing one can be done, or how nice such a workflow is. One would think the way you can import modules in Python, would be a nice fit for SC. Not sure if that’s possible.

Interesting. How would you re-use those functions in other projects?

There is definitely “fine print” about making Pd abstractions beyond adding inlets and outlets, which you won’t find until you get a bit deeper into it and you want to make your objects work more like the built-in objects.

  • Argument handling: object boxes with $1, $2 etc get you part of the way, but if you want to supply defaults for arguments that the user didn’t specify, then you run into the problem of generating messages for the absence of input. I eventually figured out that you can do “args 0” → [pdcontrol] → [pack … your… defaults… here] → [unpack … type tags…] (btw tricks like this are less well documented than SC class writing – a forum user pointed me toward pdcontrol but I had to come up with the pack trick on my own).

  • Initializing signal rate inlets. A lot of older abstractions don’t do this as it’s a relatively new feature of [inlet~] and it’s really annoying to use those abstractions. I’m trying in my own abstractions to be more user friendly.

  • Oh, and if you need to use an abstraction’s local ID in a message box, get ready to retrieve that ID from a [f $0] box and pack it in with the other data bc $0 doesn’t work transparently in message boxes.

Off the top of my head. (Things like this are what I meant when I said Pd and Max are no less fussy than text languages – I can see that the graphical paradigm may fit some people’s thinking style better, but it is no utopia. Wait till you run into “list” and “symbol” tags…)

There’s not much syntax to learn about SC classes, really (and those are covered in a help file). The concepts of object oriented design are the biggest chunk of it, and this is really a matter of experience.

Not with classes as such, but I use prototype-based pseudo-classes in a lot of my work, specifically because I want to load object definitions dynamically.

Probably you’d load a persistent library of functions at startup time and they would always be available.

If they are closed, pure functions (no reference to outside variables, and no side effects, only operations on arguments returning a result), then they don’t depend on hidden context and can be reused freely.

hjh

1 Like

Functions are a starting point. It’s often totally underestimated how much work one can save by defining a set of very simple Functions and using them in different projects. The cool thing about Functions is that they can be used for virtually anything, from custom Pattern design to UGen combinations.
Here’s a simple example: SC doesn’t contain a lowpass gate (this was a very good student question – it shows a nice and easy abstraction possibility). The first attempt could be trying such in a SynthDef resp. Function-play:

x = { 
	var src = Saw.ar([50, 51], 0.1);
	var mul = SinOsc.ar(1).range(0, 1);
	LPF.ar(src, mul.linexp(0, 1, 20, 20000), mul)
}.play;

x.release

The essential structure here is the parallel mapping of a parameter to cutoff frequency and
amplitude, including some consideration about the frequency scaling (linexp). Now this can easily be wrapped into a Function, ready to be reused.

~lpg = { |in, mul = 1, add = 0|
	LPF.ar(in, mul.linexp(0, 1, 20, 20000), mul, add)
};

x = { ~lpg.(Saw.ar([50, 51], 0.1), SinOsc.ar(1).range(0, 1)) }.play;

x.release

An analogous class definition is not very complicated. It has to be stored in a .sc file in the Extension folder.

LPG : UGen {
		*ar { |in, mul = 1, add = 0|
		^LPF.ar(in, mul.linexp(0, 1, 20, 20000), mul, add)
	}
}

After a new start of SC it can be used (‘Writing Classes’ help file)

x = { LPG.ar(Saw.ar([50, 51], 0.1), SinOsc.ar(1).range(0, 1)) }.play

x.release

Maybe one wants to explore this with inverted mappings and high pass filters as well, not much extra effort as new Functions or Classes:

LPGI : UGen {
		*ar { |in, mul = 1, add = 0|
		^LPF.ar(in, mul.linexp(0, 1, 20, 20000), 1 - mul, add)
	}
}


HPG : UGen {
		*ar { |in, mul = 1, add = 0|
		^HPF.ar(in, mul.linexp(0, 1, 20, 20000), 1 - mul, add)
	}
}

HPGI : UGen {
		*ar { |in, mul = 1, add = 0|
		^HPF.ar(in, mul.linexp(0, 1, 20, 20000), mul, add)
	}
}

4 new sound modules with a few lines of code …

4 Likes

Funnily, I didn’t get that James was posting a very similar example – or I read it unconciously and it reminded me to my old use case – however, the point is that Functions can make your live a lot easier and they don’t require much extra knowledge.
It’s crucial though to realize where the potential of an abstraction lies, which is probably a matter of experience. One indicator might be this: whenever you observe yourself in the situation of typing something, that you believe you have already typed many times before, then a powerful abstraction could be found around the corner.

3 Likes

I’m now diving into ProxySpace and that seems to get me somewhere more easily in a modular way indeed.

I used PD for maybe about six months in college before I found SC… just happened to be going thru old emails and found a short demo I made in that time using granular playback of two short recorded samples and it was so unlike anything I have ever made in SC that I was inspired to download PD again and try to replicate that wonderfully naive crunchy aliased thing that also somehow sounds very tactile/human. I am quite surprised and happy to say it is being quite fun. (especially knowing if I want to do anything serious with these sounds I will probably either record them or recreate it all in SC so I don’t have to worry about the tedious patch management stuff that put me off from PD to begin with)

Thought I’d report back here in case anyone cares :slight_smile:

I am finding the interface very elegant and tactile, I’ve been using arrays for everything (control data, waveforms, whatever) and being able to programmatically fill them but then just get in there with a mouse and mess them up is great. And they save with the patch! Just very fluid and seamless, no extra thinking required. I really want to think about how to make a similar experience in SC. If anyone has any tips… And it’s cool that you can update one object without disrupting the rest of the patch, in realtime, without setting up a lot of overhead in advance. Even the slow pace of creation is kind of meditative and fun for me, I get to admire the stark visual beauty as I go.

I somehow feel more like I’m making “computer music” than when I’m writing SC code, weirdly - probably partly because vanilla PD does so little to help save you from aliasing or other digital artifacts. I remember this really annoyed me and was another reason I switched to SC but now I’m enjoying leaning into it. Even little things like it’s easier to type “tabread” than “tabread4”. Also the apparent lack of almost any built in musical or human abstraction (I have these notes in the corner of one patch: 44.1k sample: speed 45.9375 pitch 172.266 / 48k sample: speed 50 pitch 187.5 – no BufRateScale etc in sight and I have no idea what those pitch numbers mean anymore except that they play the sample at its original pitch) And it’s kind of a fun game to try to figure out on my own how best to fill an array with a formula, for example.

I am finding I am able to use feedback without having to think so much (about buses, block size, etc) as I do in SC. Like I very intuitively made a phasor scan thru a table and then use that value to modulate the speed of the phasor. I think this is one thing that visual patching is great for. And since I have no real agenda here I am not miffed about avoiding most of the things that are easy in SC and that I would have no idea how to do in PD. (treating it more like just a creative synth than like a total workhorse tool, I guess)

I also admire the minimalist collection of built-in objects, easily referenced from a single not-overwhelming help page.

And maybe the best thing about it is how this all leads me in totally different directions than SC does into some very fertile-feeling territory, that I will nonetheless (probably) be able to port back to SC if I want to. Looking forward to trying anyway.

Just some notes from a beginner mind… maybe I want to share my unexpected happiness before it subsides :slight_smile:
now back to patching…

2 Likes