Object Attributes

Is there a way to retrieve the attributes of the objects defined in supercollider?, this means, If I do x = SinOsc.ar(440), I would like to be able to retrieve x.freq and get 440 like in any other language. I hope this can be done in some way, or add it in future versions of collider.

I have encountered myself many times in the exact same situation where I want to just do some simple logic between objects like: “if x.freq > something do this” or even the most basic “print x.freq” but its almost impossible or super convoluted.

I would appreciate some insights in the matter.

Thanks.

Im able to retrieve information by doing:

 x =  {SinOsc.ar(SinOsc.ar(1,0,10,440))}.play
x.trace

and It prints this in the console:

TRACE 1444  temp__132    #units: 9
  unit 0 SinOsc
    in  1 0
    out 0.964298
  unit 1 MulAdd
    in  0.964298 10 440
    out 449.643
  unit 2 SinOsc
    in  449.643 0
    out 0.35296
  unit 3 Control
    in 
    out 0.02
  unit 4 BinaryOpUGen
    in  0.02 0
    out 0
  unit 5 Control
    in 
    out 1
  unit 6 EnvGen
    in  1 1 0 0.02 2 0 2 1 -99 1 1 1 0 0 1 1 0
    out 1
  unit 7 BinaryOpUGen
    in  1 0.35296
    out 0.35296
  unit 8 Out
    in  0 0.35296
    out,

this is the actual information I want but I dont know how to access it, it is just printing it to the console, I would like to be able to access it and store it in a variable or somewhere.

What musical thing are you trying to do? You are approaching this the wrong way and it might be easier to answer with more information.

x = SinOsc.ar(LFDNoise3.kr(8).exprange(200, 800));

What do you think x.freq should return in this case? A number, or a description of the formula for the input?

like in any other language.

The evaluation chain here is very very different from Python – audio processing isn’t like typical imperative languages.

In Max and Pd, you’d have to pass a signal generator’s output into a snapshot~ object, and bang it when you want to poll the signal. SC can do something similar with SendReply.

hjh

Could the following code snippet be used?

(
s.waitForBoot {
	~xDumped = [];
	o = OSCFunc({ |msg|
		~xDumped = ~xDumped.add(Dictionary.newFrom([
			\sendPerSec, msg[3], 
			\freqM, msg[4], 
			\phaseM, msg[5], 
			\ampM, msg[6], 
			\offsetM, msg[7], 
			\modulator, msg[8], 
			\carrier, msg[9]
		]));
		("\n~xDumped size:" + ~xDumped.size ++ "\nlast added:" + ~xDumped[~xDumped.size - 1]).postln
	}, '/dump');
	
	~queryX = {
		postf("~xDumped.size % freqM: % offsetM: % \n",
			~xDumped.size,
			~xDumped[~xDumped.size - 1][\freqM],
			~xDumped[~xDumped.size - 1][\offsetM]
		)
	};
	
	x = { |sendPerSec=5, freqM=1, phaseM=0, ampM=10, offsetM=440|
		var modulator = SinOsc.ar(freqM, phaseM, ampM, offsetM);
		var carrier = SinOsc.ar(modulator);
		var values = [sendPerSec, freqM, phaseM, ampM, offsetM, modulator, carrier];
		SendReply.kr(Impulse.kr(sendPerSec.reciprocal), '/dump', values);
		carrier
	}.play;
}
)

~queryX.()

x.set(\sendPerSec, 5, \freqM, 2, \offsetM, 550)

~queryX.()

x.set(\freqM, 1, \offsetM, 440)

~queryX.()

o.free; x.free
1 Like

In case helpful:

x = SinOsc.ar(440);
x.inputs // -> [ 440, 0.0 ]
x = SinOsc.ar(LFDNoise3.kr(8).exprange(200, 800));
x.inputs // -> [ a LinExp, 0.0 ]

and here’s an idiomatic way of “if x.freq > something do this”:

(
~synth = {
  var freq = LFDNoise3.kr(8).exprange(200, 800);
  var x = SinOsc.ar(freq);
  
  var trig = freq > 500;
  SendReply.kr(trig, "/x");
  
  x * 0.1!2
}.play;

OSCdef(\x, { |msg|
  "freq of x is bigger than 500".postln;
}, "/x");
)
1 Like

well, I would like to do if x.freq do something, but if I do if SinOsc.ar(modulator) then this doesn’t work because if works with Ugen but in the range of 0 to 1, this means that SinOsc.ar(400), will execute true and false 400 times in a second. innstead of if x.freq > 300 then do something

You will understand SuperCollider, when you know what is the SmallTalk OOP language (sclang), and that when you build SynthDefs that is already another language. It’s a little language that tries to stay very close to sclang. Also, you can’t consider communication between language and server as they were based on the same framework of an OOP model.

When you get that right, you will improve your skills a lot.

when I do x.trace or send the message /n_trace, it prints the information of inputs and outputs of the object, this is perfect cause you can see which numbers are being ingested in the object and which numbers are going out of the object, almost like in an analogue modular setting. The problem is that I can’t find the reply message for n_trace, its just printing the information to the console. Then in the case

x = SinOsc.ar(LFDNoise3.kr(8).exprange(200, 800));

I would like to ask x.freq and have a number, or like with n_trace. For example x.input gives a list of numbers at that time with the length of the list equal to the number of inputs, also x.output.

For example, in Max there is something that I would like to do. If you have a phasor, you have to specify the rate, there are objects that takes the phasor and “divide” the rate of the given phasor and this second phasor will go twice as fast. I could do this if im able to retrieve the attribute rate from the first phasor, this means, phasor.rate and retrieve the value to divide it mathematically and set it to another phasor.

I think this functionalities are really basic in any OOP language but maybe Im missing something, or it can’t be done by some reason.

I tried SendReply, but I find it really convoluted cause then I have to set a SendReply for each attribute of the synthdefs, and also send multiple messages at a given frequency to the server. So you can imagine that if you want to do this for lots of attributes, the server will crash or something, I guess.

ohhhhh this is almost what I wanted, sadly x.outputs doest work jaja :(. thank you very much also!, cause this solves half of my problem! :slight_smile:

this is a solution, I think its the only way around by now, but it would really be more simple to do x.inputs like @Eric_Sluyter pointed out. And return a simple list of numbers. I will use a variation of your implementation :).
thanks

I understand that sclang is not the typical OOP language, but what I dont get is that if I ask to trace a node with the message /n_trace, it prints in the console the actual numbers of the inputs and output

  unit 0 SinOsc
    in  1 0
    out 0.964298
  unit 1 MulAdd
    in  0.964298 10 440
    out 449.643
  unit 2 SinOsc
    in  449.643 0
    out 0.35296
  unit 3 Control
    in 
    out 0.02
  unit 4 BinaryOpUGen
    in  0.02 0
    out 0
  unit 5 Control
    in 
    out 1
  unit 6 EnvGen
    in  1 1 0 0.02 2 0 2 1 -99 1 1 1 0 0 1 1 0
    out 1
  unit 7 BinaryOpUGen
    in  1 0.35296
    out 0.35296
  unit 8 Out
    in  0 0.35296
    out,

but its not sending a reply message back to the client. In this case you can see the output freq is 449 as well as the input cause it is being modulated by another. If this data is being printed, then it means it can be requested to the server. Im printing the frequency but I don’t understand why there is no way to retrieve it easily. Or maybe I haven’t found the right reply message from the server or the right method like x.inputs.

Thank you :slight_smile:

No, quite the opposite. Sclang is very much close to the original OOP implementation model from SmallTalk. Java came much later and has a different conception, and C++ is barely OOP nowadays, it can be if you really want that.

The communication between client and server is not a programming paradigm. Maybe you can have an insight if you compare sclang with other clients written in Haskell, Python, Scheme, etc. Each language has to create its model to interface that.

1 Like

According to your problem specification, then, this requirement would be impossible in Max because Max and Pd don’t even provide the opportunity to imagine that it’s possible to ask an object for its “attributes” (and a [phasor~]'s rate is not a Max attribute anyway).

Here’s plugdata – structurally the same as Max.

There is no request you can make of [phasor~] to get its current frequency. That’s the requirement you’ve stated, but neither Max nor Pd provide any way to do this in the way that you’re talking about. If you try to ask [phasor~] for “freq”, it will simply complain “no method for ‘freq’.”

Instead, you have access to the object(s) that are providing the frequency to [phasor~]. And you can even do something like “trace” – to sample the signal’s value on demand – using [snapshot~] and a bang.

The SuperCollider approach is fundamentally the same. You have access to the source of the phasor’s rate – so, just use it.

(
SynthDef(\grab, { |out|
	var phasorFreq = SinOsc.kr(0.5) * 2 + 3;
	
	var phasor = LFSaw.kr(phasorFreq);
	// here, phasor has no output for an atomic value
	// so you cannot ask phasor for its freq as an atomic value!
	
	// but you can grab the frequency *input*
	// and send that back to the language, on demand
	var trig = NamedControl.tr(\trig);  // "bang"
	SendReply.kr(trig, '/phasorFreq', phasorFreq);  // [snapshot~]
	
	Out.kr(out, phasor)
}).add;
)

x = Synth(\grab);

OSCdef(\phasorFreq, { |msg|
	msg.postln;
	// and in here you can do anything you like
	// with the phasor freq -- this is accessible as msg[3]
	// -- this is the stuff after [snapshot~]
}, '/phasorFreq', s.addr);

x.set(\trig, 1);  // equivalent of the "bang" above

I have a feeling that it’s only going to slow you down if you insist that a UGen’s inputs be available via the UGen’s method interface – those inputs are already available as the outputs of the input sources, so you can meet the requirement today by using those, no need for new features.

hjh

@jamshark70 I was describing this:

for what I can see, its passing the signal from the phasor to the div function, I don’t know how it actually makes the division, but the phasor is running at a “rate”, not a frequency (it may sound the same but one is sampled based and the other is cycles per seconds), so the only way to divide the signal of a phasor is to know at which rate is running, or maybe there is a way to measure the rate of a signal that Im not aware of.

My main problem is that I see computer music as a stream of numbers, and the timing should always be sample based to ensure synchronicity. But Collider’s architecture makes it impossible cause sending a message and retrieving, delays the information. Or in your example, you will have to trigger the SendReply each sample to be sure the information you are getting is the one you wanted. And of course this is not possible cause you will saturate the server with thousands of requests.

Im kind of starting to see that maybe supercollider is not adequate for what I want to do, I really want to go sample based synthesis and in supercollider is really difficult to do this.

For example:

(
p = ProxySpace(
    clock: t = TempoClock(90/60).permanent_(true)
).push;
)
p.quant_(4);

~clock1 = { Impulse.ar(2, 0) };
~clock2 = { Impulse.ar(4, 0) };

~clock1.quant_(4);
~clock2.quant_(4);
~clock1.play;
~clock2.play;

~clock2 = { Impulse.ar(2, 0) }; 

As far as I know, this would make the changes in clock2 and clock1 stay in sync when I change the NodeProxy, but it doesn’t happen, they get out of sync when I change ~clock2 at the last line. You can execute the last line several times to see what I mean, even if it waits to the quantization.

I want to create phase distortion synthesis, or hard sync with custom oscillators but really is super difficult. I like that supercollider has the biggest community, and that it has a huge library of objects, but it really lacks in sample precision. It’s just my opinion, maybe it’s because I don’t know certain tricks or Im thinking everything wrong.

Thanks for the reply btw.

It’s a bit too convoluted to leave it here too. Splitting.

I try to touch at least one of the confusions here:

1 Like

How about this?

(
s.waitForBoot {
	x = {
		var x, valReg, identityDict;
		
		x = { |sendPerSec=5, freqM=1, phaseM=0, ampM=10, offsetM=440|
			var modulator = SinOsc.ar(freqM, phaseM, ampM, offsetM);
			var carrier = SinOsc.ar(modulator);
			var values = [sendPerSec, freqM, phaseM, ampM, offsetM, modulator, carrier];
			SendReply.kr(Impulse.kr(sendPerSec.reciprocal), '/x_dump', values);
			carrier
		};
		
		valReg = {
			OSCFunc({ |msg|
				identityDict.put(\sendPerSec, msg[3]);
				identityDict.put(\freqM, msg[4]);
				identityDict.put(\phaseM, msg[5]); 
				identityDict.put(\ampM, msg[6]);
				identityDict.put(\offsetM, msg[7]); 
				identityDict.put(\modulator, msg[8]); 
				identityDict.put(\carrier, msg[9]);
				msg.postln;
			}, '/x_dump')
		};
		identityDict = ();
		identityDict.put(\valReg, valReg.());
		identityDict.put(\x, { x });
		identityDict.put(\free, valReg.free); // identityDict.put(\free, [valReg, x].do { |val| val.free });
		identityDict
	}.()
}
)

y = x.x.play

x.sendPerSec
x.freqM
x.phaseM
x.ampM
x.offsetM
x.modulator
x.carrier

y.set(\freqM, 20, \offsetM, 550, \sendPerSec, 2)
x.freqM
x.offsetM
x.sendPerSec

x.valReg.free
y.free

You could also combine this code snippet with the code by @jamshark70 or/and my previous code.

1 Like

Could you use demand rate and write all the logic on the server? Again, it’s hard to answer without a concrete problem.

2 Likes

phasor~'s argument is literally a frequency.

Rate is always proportional to frequency.

You can measure the rate of a phasor by looking at the difference between successive samples (ignoring the negative jumps) – I’m pretty sure subdiv is doing this. In SC, this difference is HPZ1.ar(signal) * 2 and the corresponding frequency would multiply that by the sample rate (or, since LFSaw’s range spans two units, -1 to 1, HPZ1.ar(lfsaw) * SampleRate.ir should do it).

You don’t need any “attributes” apparatus to do subdiv in SC – nor is MSP magically transferring “attributes” over audio cables. I think there’s already a forum thread about a subdiv~ style operation. One forum member, dietcv, is something of a resident expert on ramp-based techniques.

Edit: I was close, the thread was about rate~ – Trying to re-create the Max rate~ object

hjh

3 Likes