Discussion:
best/idiomatic way to slew controller input
George Locke
2014-10-03 15:00:01 UTC
Permalink
Hi,

I want to take input from controllers and send it to running synths after
some smoothing. I have some working code below (largely based on
http://www.sussex.ac.uk/Users/nc81/modules/cm1/workshop.html section 4.2 ),
and, as I am very new to SC, I want to check to make sure I'm not
reinventing the wheel in a ham-fisted way.

The signal path is like this: controller -> inputBus -> slewing synthDef ->
control bus -> map control bus to target synth.

The input bus feels like the most useless part of this chain. is there a
way to get the ctrlLagger to work by directly taking the input of slid2d?
I looked for a UGen that would just hold a constant value but I couldn't
find it. (Latch was pretty close but it still requires something like
inBus that takes a .set message afaict.)

Thanks,
George

(
SynthDef(\basicSin,
{
|freq = 100, gain = 0.1, outBus=0|
Out.ar(outBus, SinOsc.ar(freq, 0, gain).dup);
}
).add;
SynthDef(\ctrlLagger,
{
| inBus, outBus=0, lagTime=1 |
Out.kr(outBus, Lag.kr(In.kr(inBus), lagTime))
}
).add;
)

(
var w, slid2d, syn, fInBus, gInBus, fSlew, gSlew, fOutBus, gOutBus, lagTime;

lagTime = 1;

w=Window("My Window", Rect(100,300,200,200));
slid2d= Slider2D(w,Rect(5,5,175,175));

syn=Synth(\basicSin);

fInBus = Bus.control(s, 1); fInBus.set(200);
gInBus = Bus.control(s, 1); gInBus.set(0.05);
fOutBus = Bus.control(s, 1);
gOutBus = Bus.control(s, 1);
fSlew = Synth(\ctrlLagger, [\inBus, fInBus, \outBus, fOutBus, \lagTime,
lagTime]);
gSlew = Synth(\ctrlLagger, [\inBus, gInBus, \outBus, gOutBus, \lagTime,
lagTime]);
syn.map(\freq, fOutBus, \gain, gOutBus);

slid2d.action_({
//[slid2d.x, slid2d.y].postln;

// this seems a little clunky!
fInBus.set(100+(10000*slid2d.x));
gInBus.set(slid2d.y);
});

w.front;

w.onClose={syn.free;};
)
Scott Carver
2014-10-03 18:31:03 UTC
Permalink
I'm not 100% sure I understand what you're trying for but given the code
you copy-pasted, you should be fine just setting params of the slew synth
directly rather than using an input bus:

SynthDef(\ctrlLagger, {
| value = 0, outBus=0, lagTime=1 |
Out.kr(outBus, Lag.kr(value, lagTime))
}).add;
fSlew = Synth(\ctrlLagger);
slid2d.action_({
fSlew.set(\value, slid2d.x);
});


(The .set method for synths takes the parameter name and value). For
regular synth parameters, the value just holds where it is until a new
value is sent - same as a bus. Incidentally, synth params that start with a
t_ (for example, t_freq), will only output values when they receive
something new, and will be 0 the rest of the time.


A few things that could make this less verbose / more clear:

(1)
You don't need a separate .map call - you can use .asMap to directly map
busses to synth parameters when you create it:

syn = Synth(\basicSin, args:[\freq, fOutBus.asMap, \gain, gOutBus.asMap]);

This will save potential headaches too, as your synth is *initialized* with
the right value, whereas a separate map call can happen slightly later,
meaning you might have your synth running for a cycle with an invalid freq,
for example.

(2)
Look at ControlSpec, which is used to scale parameters in the exact way
you're doing in your slider action. Instead you can:

freqSpec = ControlSpec(100, 10000, default: 300);

slid2d.action_({
fSlew.set(\ctrl, freqSpec.map(slid2d.x)); // automatically map your slider
value from 0..1 to 100..10000
});


- Scott C
Post by George Locke
Hi,
I want to take input from controllers and send it to running synths after
some smoothing. I have some working code below (largely based on
http://www.sussex.ac.uk/Users/nc81/modules/cm1/workshop.html section 4.2
), and, as I am very new to SC, I want to check to make sure I'm not
reinventing the wheel in a ham-fisted way.
The signal path is like this: controller -> inputBus -> slewing synthDef
-> control bus -> map control bus to target synth.
The input bus feels like the most useless part of this chain. is there a
way to get the ctrlLagger to work by directly taking the input of slid2d?
I looked for a UGen that would just hold a constant value but I couldn't
find it. (Latch was pretty close but it still requires something like
inBus that takes a .set message afaict.)
Thanks,
George
(
SynthDef(\basicSin,
{
|freq = 100, gain = 0.1, outBus=0|
Out.ar(outBus, SinOsc.ar(freq, 0, gain).dup);
}
).add;
SynthDef(\ctrlLagger,
{
| inBus, outBus=0, lagTime=1 |
Out.kr(outBus, Lag.kr(In.kr(inBus), lagTime))
}
).add;
)
(
var w, slid2d, syn, fInBus, gInBus, fSlew, gSlew, fOutBus, gOutBus, lagTime;
lagTime = 1;
w=Window("My Window", Rect(100,300,200,200));
slid2d= Slider2D(w,Rect(5,5,175,175));
syn=Synth(\basicSin);
fInBus = Bus.control(s, 1); fInBus.set(200);
gInBus = Bus.control(s, 1); gInBus.set(0.05);
fOutBus = Bus.control(s, 1);
gOutBus = Bus.control(s, 1);
fSlew = Synth(\ctrlLagger, [\inBus, fInBus, \outBus, fOutBus, \lagTime,
lagTime]);
gSlew = Synth(\ctrlLagger, [\inBus, gInBus, \outBus, gOutBus, \lagTime,
lagTime]);
syn.map(\freq, fOutBus, \gain, gOutBus);
slid2d.action_({
//[slid2d.x, slid2d.y].postln;
// this seems a little clunky!
fInBus.set(100+(10000*slid2d.x));
gInBus.set(slid2d.y);
});
w.front;
w.onClose={syn.free;};
)
Daniel Mayer
2014-10-03 19:41:58 UTC
Permalink
Hi,

you are modularizing the tasks by inventing a lag synth.
In principle modularization is good, especially
if the isolated task would need real fine-tuning.
But if you just want to do simple lagging for a few params,
this might be an over-complication.

SynthDef's arg 'rates' takes floats as lag times.
So to lag freq and amp with 2 seconds you could write:

(
SynthDef(\basicSin_2, { |out=0, freq = 100, amp = 0.1|
Out.ar(out, SinOsc.ar(freq, 0, amp).dup);
}, [0,2,2]).add;
)

For a simple gui with predefined keyword specs ('amp', 'freq')
you then can do this:

SynthDescLib.global[\basicSin_2].makeGui

Greetings

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------



_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
George Locke
2014-10-03 20:55:24 UTC
Permalink
Scott,

Thanks very much for your input. I hadn't realized I could just put a
float as input to Lag. I assume this works for most other UGens, which
would explain why there's no UGen specifically designed to hold a constant
value set by a float (as there is in ChucK, for instance, Step, or MSP,
sig~). Your two recommendations look spot on. (2) is on my docket already
and (1) is new information that looks helpful.

I have experience in scientific computing, which leads me to want to
identify best practices for programming. The syntax of SC is unlike the
langauges I'm familiar with (primarily Perl, C++, R, also ChucK, Max/MSP),
and its semantics are different too (e.g. message passing, everything is an
object), so advice like this is most welcome.

Daniel,

The project I'm working towards is an oscillator bank controllable by
iPad/TouchOSC -- clearly I'm not there yet!

It seemed appropriate to have separation of concerns between interface and
sound generation, where the sound generator just wants to be told what the
parameters are while the interface handles decides how those parameters
should change. That is the reason I modularized it -- whether or not it
was the right move is obviously a judgment call.

I was not aware of the rates argument to SynthDef; with your prompting,
I've taken a closer look at the help file, and I'm glad for it. I would
guess that if you put the rates arg there, then it's not possible to change
the lag time afterwards? In my desired application, I'll have a lag-time
slider on the interface.

Thanks,
George
Post by Daniel Mayer
Hi,
you are modularizing the tasks by inventing a lag synth.
In principle modularization is good, especially
if the isolated task would need real fine-tuning.
But if you just want to do simple lagging for a few params,
this might be an over-complication.
SynthDef's arg 'rates' takes floats as lag times.
(
SynthDef(\basicSin_2, { |out=0, freq = 100, amp = 0.1|
Out.ar(out, SinOsc.ar(freq, 0, amp).dup);
}, [0,2,2]).add;
)
For a simple gui with predefined keyword specs ('amp', 'freq')
SynthDescLib.global[\basicSin_2].makeGui
Greetings
Daniel
-----------------------------
www.daniel-mayer.at
-----------------------------
_______________________________________________
sc-users mailing list
http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Daniel Mayer
2014-10-03 23:48:37 UTC
Permalink
It seemed appropriate to have separation of concerns between interface and sound generation, where the sound generator just wants to be told what the parameters are while the interface handles decides how those parameters should change. That is the reason I modularized it -- whether or not it was the right move is obviously a judgment call.
This sounds absolutely reasonable.
And it's an interesting problem, pointing
to a wish, for which I'm not aware of a
general solution ATM
(please tell me if there is such, JITLib maybe, some extension ?)

I'd formulate it like this:
given an audio SynthDef it would be helpful
to have an automated procedure (method)
to rebuild/reconnect the SynthDef/Synth
so that you could immediately either
set all control args with one global controlable lagTime
or (extended version) with independant controlable lagTimes
or (most extended) with independant controlable lagTimes and warps.

I have a hack in mind for this, but too tired to work out right now.
Just a short sketch for independant lagTimes:

SynthDef \basicSin_2 can be rewritten with some additional lines
that invent lagTime args with arg names + suffix 'Lag'.
aSymbol.kr is a shortcut syntax for NamedControl, a way
to invent further args in a SynthDef,

(
SynthDef(\basicSin_3, { |out = 0, freq = 400, amp = 0.1|
out = Lag.kr(out, \outLag.kr);
freq = Lag.kr(freq, \freqLag.kr);
amp = Lag.kr(amp, \ampLag.kr);
Out.ar(out, SinOsc.ar(freq, 0, amp).dup);
}).add;
)

Then you can do:

x = Synth(\basicSin_3)

x.set(\freqLag, 1, \freq, 600)

x.set(\freqLag, 0.1, \freq, 400)

x.free;


Of course it'd be arkward to write these additional lines for every new SynthDef.
But as the source code of every SynthDef is stored you can hack it like working with
a lisp macro. I've suggested a debugging tool based on that principle, bottom of this thread:

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/sclang-debugging-options-td7609812.html


Then for a SynthDef \x you could write something like that

\x.rebuildWithLags

to reconstruct the SynthDef with lag args


Also the gui could then automatically add lag sliders for all control args.
This solution would avoid the bookkeeping of busses and node ordering.
I'll look at it when I've some time left.

Greetings

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------





_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Daniel Mayer
2014-10-03 23:52:41 UTC
Permalink
Post by Daniel Mayer
But as the source code of every SynthDef is stored
... if a SynthDesc exists (e.g. SynthDef has been 'added')





_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Julian Rohrhuber
2014-10-04 06:36:35 UTC
Permalink
Post by Daniel Mayer
(
SynthDef(\basicSin_3, { |out = 0, freq = 400, amp = 0.1|
out = Lag.kr(out, \outLag.kr);
freq = Lag.kr(freq, \freqLag.kr);
amp = Lag.kr(amp, \ampLag.kr);
Out.ar(out, SinOsc.ar(freq, 0, amp).dup);
}).add;
)
The out argument of the Out UGen is discrete, so you a lag has no lagging effect.
_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Daniel Mayer
2014-10-04 11:52:55 UTC
Permalink
Post by Julian Rohrhuber
Post by Daniel Mayer
(
SynthDef(\basicSin_3, { |out = 0, freq = 400, amp = 0.1|
out = Lag.kr(out, \outLag.kr);
freq = Lag.kr(freq, \freqLag.kr);
amp = Lag.kr(amp, \ampLag.kr);
Out.ar(out, SinOsc.ar(freq, 0, amp).dup);
}).add;
)
The out argument of the Out UGen is discrete, so you a lag has no lagging effect.
It's right that it probably doesn't make sense to use it,
as you get a jump, nevertheless the defined delaytime
is used. I see no harm to formally include it in such a solution,
though you might an omit 'outLag' arg if you want.

Regards

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------




_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Daniel Mayer
2014-10-04 14:49:10 UTC
Permalink
Post by Daniel Mayer
Then for a SynthDef \x you could write something like that
\x.rebuildWithLags
to reconstruct the SynthDef with lag args
Here's first attempt - implemented it slightly different now:

method 'addLag' works like 'add' for SynthDefs and additionally
generates a variant of SynthDef \xy named \xyLag
with lagged controls.


////////////////////// save in Extensions as .sc file and recompile

+SynthDef {
addLag { |libname, completionMsg, keepDef = true|
var src = this.func.def.sourceCode.as(Array), srcLag, varPositions,
argPositions, barPositions, varPos, varEndPos, semiColonPositions,
firstSemiColonAfterVarPos, insertPos, eqlPositions, doAddLag = true, controlNames;

this.add(libname, completionMsg, keepDef);

controlNames = SynthDescLib.global[this.name.asSymbol]
.def.allControlNames.select { |x| x.rate == 'control' }.collect(_.name);
(controlNames.size == 0).if { "no SynthDef controls to be lagged".warn; doAddLag = false};

varPositions = src.findAll([$v, $a, $r, $ ]);
(varPositions.size > 1).if {
"addLag expects only one keyword 'var' in SynthDef".warn;
doAddLag = false
};
varPos = varPositions.first;

argPositions = src.findAll([$a, $r, $g, $ ]);
(argPositions.size > 0).if {
"addLag expects args given within bar characters".warn;
doAddLag = false
};

varPos.notNil.if {
semiColonPositions = src.findAll([$;]);
firstSemiColonAfterVarPos = semiColonPositions.select(_>varPos).first;
eqlPositions = src.findAll([$=]);
(eqlPositions.any { |x| (x > varPos) && (x < firstSemiColonAfterVarPos) }).if {
"addLag expects var definitions without immediate assignments".warn;
doAddLag = false;
};
insertPos = firstSemiColonAfterVarPos + 1;
}{
barPositions = src.findAll([$|]);
(barPositions.size < 2).if {
(argPositions.size == 0).if { "no args to be lagged".warn; doAddLag = false }
}{
insertPos = barPositions[1] + 1
}
};
doAddLag.not.if {
"no lagged variant of SynthDef has been compiled !".postln
}{
srcLag = src[..(insertPos-1)] ++ "\n\t";
controlNames.do { |name|
srcLag = srcLag ++ name ++ " = Lag.kr(" ++ name ++ ", \\" ++ name ++ "Lag.kr);\n\t";
};
srcLag = (srcLag ++ src[insertPos..]).join;
SynthDef(this.name.asSymbol ++ \Lag, srcLag.interpret,
variants: this.variants, metadata: this.metadata).add(libname, completionMsg, keepDef
)
}
}
}

//////////////////////

// after recompile

(
SynthDef(\basicSin, { |out = 0, gate = 1, freq = 400, amp = 0.1|
Out.ar(out, SinOsc.ar(freq, 0, amp).dup * EnvGen.ar(Env.asr, gate));
}).addLag;
)

// use standard SynthDef

x = Synth(\basicSin)

x.release


// use lagged variant

y = Synth(\basicSinLag)

y.set(\freq, 500, \freqLag, 2)

y.set(\freq, 400, \freqLag, 0.1, \amp, 0.03, \ampLag, 5)

y.release



The parsing procedure relies on some conventions in the SynthDef function
to be fulfilled, but probably none of those is a serious restriction for most use cases.

.) var definitions without immediate assignments (do it later if necessary)
.) args between bars (no keyword 'arg', could be allowed though with some changes)
.) define audio, initial and trigger rate by prefix rather than rates arg
.) doesn't work with controls invented within source code by NamedControl (e.g. by EnvGate)
.) no comments before end of var definition

Same could be extended with VarLag, would probably make sense for a number of params.


Greetings

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------





_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Daniel Mayer
2014-10-04 19:54:35 UTC
Permalink
Post by Daniel Mayer
Same could be extended with VarLag, would probably make sense for a number of params.
Not much to change for a version with VarLag.
Inserted assignments at begin of SynthDef core code would look like this:

freq = VarLag.kr(freq, \freqLag.kr, \freqCurvature.kr, \freqWarp.kr(5));

Controls xWarp need default value 5 to be compliant with VarLag convention,
xCurvature and xLag default to zero if not defined explicitely.

Differences to method 'addLag' begin with the loop 'controlNames.do':

////////////////////// save in Extensions as .sc file and recompile

+SynthDef {
addVarLag { |libname, completionMsg, keepDef = true|
var src = this.func.def.sourceCode.as(Array), srcLag, varPositions,
argPositions, barPositions, varPos, varEndPos, semiColonPositions,
firstSemiColonAfterVarPos, insertPos, eqlPositions, doAddLag = true, controlNames;

this.add(libname, completionMsg, keepDef);

controlNames = SynthDescLib.global[this.name.asSymbol]
.def.allControlNames.select { |x| x.rate == 'control' }.collect(_.name);
(controlNames.size == 0).if { "no SynthDef controls to be lagged".warn; doAddLag = false};

varPositions = src.findAll([$v, $a, $r, $ ]);
(varPositions.size > 1).if {
"addLag expects only one keyword 'var' in SynthDef".warn;
doAddLag = false
};
varPos = varPositions.first;

argPositions = src.findAll([$a, $r, $g, $ ]);
(argPositions.size > 0).if {
"addLag expects args given within bar characters".warn;
doAddLag = false
};

varPos.notNil.if {
semiColonPositions = src.findAll([$;]);
firstSemiColonAfterVarPos = semiColonPositions.select(_>varPos).first;
eqlPositions = src.findAll([$=]);
(eqlPositions.any { |x| (x > varPos) && (x < firstSemiColonAfterVarPos) }).if {
"addLag expects var definitions without immediate assignments".warn;
doAddLag = false;
};
insertPos = firstSemiColonAfterVarPos + 1;
}{
barPositions = src.findAll([$|]);
(barPositions.size < 2).if {
(argPositions.size == 0).if { "no args to be lagged".warn; doAddLag = false }
}{
insertPos = barPositions[1] + 1
}
};
doAddLag.not.if {
"no lagged variant of SynthDef has been compiled !".postln
}{
srcLag = src[..(insertPos-1)] ++ "\n\t";
controlNames.do { |name|
srcLag = srcLag ++ name ++ " = VarLag.kr(" ++ name ++ ", \\" ++ name ++ "Lag.kr, " ++
"\\" ++ name ++ "Curvature.kr, " ++ "\\" ++ name ++ "Warp.kr(5));\n\t"
};
srcLag = (srcLag ++ src[insertPos..]).join;
SynthDef(this.name.asSymbol ++ \VarLag, srcLag.interpret,
variants: this.variants, metadata: this.metadata).add(libname, completionMsg, keepDef
)
}
}
}

//////////////////////

// after recompile


(
SynthDef(\basicSin, { |out = 0, gate = 1, freq = 400, amp = 0.1|
Out.ar(out, SinOsc.ar(freq, 0, amp).dup * EnvGen.ar(Env.asr, gate));
}).addVarLag;
)

// use of varLagged variant

x = Synth(\basicSinVarLag);

x.set(\freq, 500, \freqLag, 5); // linear change upwards by default warp = 5, curvature = 0

x.set(\freq, 400, \freqLag, 5, \freqCurvature, -5); // decelarating down

x.set(\freq, 500, \freqLag, 5, \freqCurvature, 5); // accelarating up

x.set(\freq, 400, \freqLag, 5, \freqCurvature, 5); // accelarating down

x.set(\freq, 500, \freqLag, 5, \freqCurvature, -5); // decelarating up

// keep in mind that freqCurvature has been set,
// so a call without freqCurvature now has no linear lag effect like at the first time

x.set(\freq, 400, \freqLag, 5);

x.set(\freq, 500, \freqLag, 5);


x.release;


Greetings

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------







_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
adam4
2014-10-12 20:17:37 UTC
Permalink
traditionally this kind of thing was done using sample and hold

Latch looks useful

{ Blip.ar(Latch.ar(WhiteNoise.ar, Impulse.ar(9)) * 400 + 500, 4, 0.2)
}.play;

you could also use sclang to hold the value (or manipulate it) instead of a
ugen



--
View this message in context: http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/best-idiomatic-way-to-slew-controller-input-tp7613739p7613959.html
Sent from the SuperCollider Users New (Use this!!!!) mailing list archive at Nabble.com.

_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/

Loading...