Closed capital-G closed 2 years ago
s.boot;
// create signal
a = Ndef(\foo, {SinOsc.ar(200)*0.5});
=> general comment:normally, you don't have to double-keep the Ndef, you can just reference it by Ndef(\foo)
// create some analyzers
b = ThatFreqAnalyzer(\freqFoo, callback: {});
c = ThatAmpAnalyzer(\ampFoo, callback: {|amp| amp;});
ok.
// add analyzers to input to account 1:n realtionship
ThatInput(\abc, a).add(b, c)
// an analyzer can also be cleared
c.clear;
If you want it to work like Ndefs
you should be able to clear it like this:
ThatAmpAnalyzer(\ampFoo).clear
// each analyzer can now be modified and the latest value can
// be accesed via
b.v;
This saves typing but is hard to explain and a bit unidiomatic. Better you full names (think "Smalltalk"), like prevValue
, or prevAmp
.
// or attached to a new source
d = ThatInput(\bar, Ndef(\aoeu, {SinOsc.ar(400)*0.1})).add(b);
b.v
// returns now 400
// a minimal and quick example looks like this
Ndef(\baz, {SinOsc.ar(LFNoise2.kr(10, SinOsc.kr(0.02, mul: 1000), 1000), SinOsc.kr(0.2))*0.1}).play;
ThatInput(\bazIn, Ndef(\baz)).add(ThatFreqAnalyzer(\foo3, {|f| f.postln;}));
// via the base class ThatAnalyzer it is possible to create dynamically new analyzers in the interpreter runtime
I don't understand yet what ThatInput
will do, but I can guess that it collects analyzers? But why do you have to do that?
In general, this is a good direction.
P.S.
In general, every class that works according to the def
schema (like Ndef
, OSCdef
etc.) has an anonymous base class that can be instantiated without a name (like NodeProxy
, OSCFunc
etc.).
I tend to use the syllable def
in classes that work like this.
// add analyzers to input to account 1:n realtionship ThatInput(\abc, a).add(b, c)
I don't understand this "1:n relationship"?
- can you also add signals that are not Ndefs?
well, you have 1 signal and up to n analyzers on one signal - much like a proxychain.
the motivation was the intuition of calling Ndef(\foo).analyzers.amp
, which could alternatively implemented via Analyzer(Ndef(\foo)).amp
. But at the same time we do not want to limit ourselves to Ndefs, so I created this.
I am not convinced if this was a good Idea, I need to sleep over it.
// an analyzer can also be cleared c.clear;
If you want it to work like
Ndefs
you should be able to clear it like this:ThatAmpAnalyzer(\ampFoo).clear
It is indeed implemented in such a way.
// each analyzer can now be modified and the latest value can // be accesed via b.v;
This saves typing but is hard to explain and a bit unidiomatic. Better you full names (think "Smalltalk"), like
prevValue
, orprevAmp
.
I am also not convinced by v
- the obvious value
should probably not overwritten here and previousValue
seemed a bit long for SC standards, but I also prefer longer and more precise names.
Another name for this that was suggested to me was simply .get
.
I don't understand yet what
ThatInput
will do, but I can guess that it collects analyzers? But why do you have to do that?
Yeah, I think it is unnecessary. It is nice if you want to run multiple analyzers on one source and move them all to another source. But this principle contradicts with the ownership of who changes the input, see https://github.com/musikinformatik/that/blob/a1f3f3e46de4ff153c8ff28ead4ef3a4e2f4ac90/classes/that.sc#L38-L42
Regarding the Def problematic: Yeah, I am aware of this, probably there should be a non-def version of it as well.
Does sclang offer something like Python MixIns? This would creating such things like ...def
really easy.
And btw - for nostalgia sake: Why are those called def
? The functionality mimics more a dict or a cache and not a "def" (which stands for definition?) ?
Will continue with dev tomorrow.
well, you have 1 signal and up to n analyzers on one signal - much like a proxychain.
you don't need this because you can route the signals. For this, I wouldn't follow the example of ProxyChain. That is only useful when order is important, and here it isn't.
But at the same time we do not want to limit ourselves to Ndefs, so I created this.
This is why I used a function as an input. If you want more signals, just let the function return the sum of them (e.g. { Ndef.ar(\x) + Ndef.ar(\y) }
, but also just { SoundIn.ar(0) }
if you don't work with node proxies).
Another name for this that was suggested to me was simply
.get
.
You could also write value
. In general, you may want to return an event.
Does sclang offer something like Python MixIns?
No. But for this, subclassing is fine – you can check out how Pdefn
is implemented, I think that is the simplest case.
https://github.com/musikinformatik/that/pull/2/commits/57908cab23f3f0933f09fc0910794a1d57d23f1b removed ThatInput
.
Maybe it is ok to solely provide the def version as we need an unique identifier for the OSC communication channels? one could generate UUIDs under the hood for this but I am not a fan of such implicitness.
On another note: https://github.com/musikinformatik/that/blob/743d7ec6142634a1d847f1c95349835bab916e77/library/that-system-functions.scd#L55
Is there a reason that this is only done on global triggers and not everytime? Having access to an array of all values makes more sense to me than to filter out all events manually.
I am also confused what the best way would be to handle triggers
input
OR Impulse.kr(1.0)
)AND Impulse.kr(1.0)
)Impulse.kr(1.0)
Providing a lambda function could maybe allow such things, python example ahead
def amp_analysis(name: str, input: UGen, callback: Callable, trigger: Callable=None):
trigger = trigger(input, StanardTrigger()) if trigger else StandardTrigger()
pass
def my_callback(amp):
print(amp)
amp_analysis(
name="foo",
input=SinOsc.ar(200),
callback=my_callback,
trigger=lambda input, internalTrigger: input>0.5 or internalTrigger
)
https://github.com/musikinformatik/that/pull/2/commits/851c9fd1c8e2dcd97d1dafe6a36d3da644fcdda7 implemented lambda triggers which now can be used like
ThatAmp(
name: \aAmp,
input: Ndef(\a, {
SinOsc.ar(200)*EnvGen.kr(Env.perc, gate: Impulse.kr(0.2))
}),
callback: {|a| a.postln},
trigger: {|in, trig| (trig+Impulse.kr(5.0))>=1} // arithmetic or
)
One thing that came to my mind and I do not know what the best way to implement is:
q = ();
// create a ampanalyzer
q[\amp] = ThatAmp(
name: \a,
input: Ndef(\a, {
SinOsc.ar(200)*EnvGen.kr(Env.perc, gate: Impulse.kr([1.2, 1.0]))
}),
callback: {|a| a.postln},
trigger: {|in, trig| Impulse.kr(1.0)}
);
// now lets create a freq analzyer as well
q[\freq] = ThatFreq(
name: \a,
input: Ndef(\a),
callback: {|a| a.postln},
trigger: {|in, trig| Impulse.kr(1.0)}
);
this now clashes/fails as ThatFreq
and ThatAmp
use the name a
but share the same That
namespace which is not obvious!
A benefit of using a common namespace in That
is that we can access all available analyzers via the base class That
. We could still make this work by prepending e.g. an amp_
on the name of an AmpAnalyzer to fake a separated namespace - but this is not obvious when one wants to access this from That
, see
ThatAmp(\a, Ndef(\foo), {|a| a.postln});
// does not work
That(\a)
// does work but not obvious
That(\amp_a)
Another solution would be to create a static function for each analyzer that serves as a contstructor. this makes it more obvious that naming analyzers share the same namespace of That
.
a = That.amp(
name: \a,
input: Ndef(\a, {
SinOsc.ar(200)*EnvGen.kr(Env.perc, gate: Impulse.kr([1.2, 1.0]))
}),
callback: {|a| a.postln},
trigger: {|in, trig| Impulse.kr(1.0)}
)
That(\a).latestValue
but how to access/modify parameters specific to an amp analyzer?
edit: on another note: lets say the def interface of That
allows to replace the analyzer while running, so
ThatAmp(\foo, input: Ndef(\foo), callback: {|a| a.postln;});
That(\amp_foo).analyzer = // set to e.g. ThatFreq somehow or something custom
Now the namespace is completely messed up.
I gone now for the constructor approach as the parameters of each analyzer function can be accessed from the Node and do not need to be managed by the class.
Currently it looks like this
That.freq(
name: \aFoo,
input: Ndef(\a, {SinOsc.ar(200) * SinOsc.ar(0.1)}),
callback: {|a| a[0].postln},
trigger: {Impulse.kr(1.1)}
);
That.all;
// returs -> ( 'aFoo': a That )
// edit analyzer specific arguments as ndef, e.g.
That(\aFoo).analyzer.gui;
Except for the missing tests and not finished documentation I think the quark in #2 is in a finished state.
I started today to transform the code into OO code and here is a first draft of the interface
Questions
does this make sense (as this is designed on how I would use it)?
how the 1:n relationship of input signal and analyzer should be obeyed - because creating an Analyzer beforehand and interchangeable is interesting but also leads to ambiguity as the internal state can easily be messed up as the input is attached when called via
ThatInput.add
and not by the analyzer itself.currently multi-channel expansion is omitted for sake of simplicity during design phase but will be added. but changing the number of input channels dynamically is not that easy with running ndefs. is there a best-practice for such cases?
Is the naming of the instances like in Ndefs really necessary or not that interesting? Could allow for dynamically accesing values
maybe a first PR will be submitted during this weekend