monome / norns

norns is many sound instruments.
http://monome.org
GNU General Public License v3.0
633 stars 147 forks source link

add "parameter" interface with control spec #123

Closed antonhornquist closed 6 years ago

antonhornquist commented 6 years ago

Idea:

But perhaps wise to simplify and not leverage every possible item in ControlSpec.

Items are directly from ControlSpec, descriptions:

minval: The minimum value of the range.
maxval: The maximium value of the range.
warp: a Warp, a symbol (e.g. \lin or \exponential: Default value is \lin), or something else that returns a Warp when sent the message .asWarp. A CurveWarp is defined by a number.
step: The smallest possible increment.
default: The default value.
units: The units, e.g. "hz". Used by some gui's as a unit label
catfact commented 6 years ago

in general i agree that some kind of parameter range/warp descriptor would be great. it's not obvious exactly how to fit it into the command descriptors though.

see, engine commands aren't necessarily just setting individual parameters. they can be OSC messages formatted with any combination of string, int and float arguments. and engine command handlers can execute arbitrary sclang functions.

for example, there are engine commands to read a named file to a buffer, or trim a buffer to a range.

having an arbitrary spec for each argument in a command, generally, would be impossible.

one idea: "commands" and "parameters" could be separate. the former is arbitrarily formatted. the latter is always a single float. i kind of like this actually, because it would lend itself to the possibility of matron sending c_set messages directly to scsynth - that is, a "parameter" descriptor would include a name, a control bus index, and a spec.

tehn commented 6 years ago

i definitely appreciate this last idea of commands vs. params, where params could have range/warp. in a way it could simplify the sclang guts of an engine as well-- granted you'd need to provide range/warp info, but the osc callbacks wouldn't be necessary, right?

catfact commented 6 years ago

right, sclang wouldn't handle OSC for params at all.

this isn't as big a performance saving as it might seem, since sclang can set control busses via shmem interface. so there is one OSC transaction happening either way.

the really crazy next step would be to see if we can hack scsynth shmem support into matron.

anyway i'm going to consider this an action item

antonhornquist commented 6 years ago

Good points. I thought of the specs discussed above as optional command argument metadata rather than mandatory.

But when I think of it again, ControlSpecs as used in sc is not 1:1 to crone command arguments. It’s more of a description of a float.

A really nice thing about SC Specs is you get some for free (ie. \freq.asSpec), they are easily used for scaling Ugens in SynthDefs (\freq.asSpec.map()) and it’s possible to build a library of reusable specs for norns (Spec.add(...)) pushing them out to where needed (lua/ui).

I can explore these idea further, if needed.

That would be ”/report/parameters” et cetera. Right?

catfact commented 6 years ago

right

antonhornquist commented 6 years ago
  1. Would parameter communicated matron->sc be mapped 0...1 value or unmapped value?

    ~unmapped = 440.0; //hz
    ~mapped = \freq.asSpec.unmap(~unmapped); // 0..1
  2. If parameters are to 1:1 correspond to c_set commands for Engine controlbusses adding allocation/reference of control busses (similar to ctlBus in PolySub) in CroneEngine would make implementation trivial.

  3. Map/unmap translations are obviously not exact.

    g = ControlSpec(0.01, 2000, \exp, 0.1, 220, "Hz");
    g.unmap(g.map(0.5));
catfact commented 6 years ago

yeah i think that's about right.

indeed, if we send c_set directly from matron then it has to be scaled correctly in matron/lua.

which is fine - UI is in lua, so lua has to know both mapped and unmapped values anyway (for display and input, respectively)

in SC engine, something like:

this.addParameter(name: 'cutoff', range:[10, 20000], warp: 'exp', unit:'hz');

(i've always thought 'exp' was a little too vague personally.... maybe 'log2' or something... whatever)

then, some C glue between matron "parameter registry" and lua, very similar to existing command registry (don't worry about this, i can do it)

finally in lua i think the setup of something like ControlSpec would be done automatically for each parameter on engine load. we can set up the scripting API however we want; i imagine @tehn will have some ideas. the current simplicity for commands is kind of nice i think; Engine module automatically creates clean setter functions like engine.cutoff(hz).

with scaling there's necessarily gonna be a little more complexity. something very approximately like:

val = engine.mapParam('cutoff', 0.5)
displaystring = "cutoff: " .. val .. engine.unit('cutoff')
engine.setParam('cutoff', val)

... can probably be a little simpler than that... maybe map and set all happen under the hood in one call. lua funcs can have multiple return values, this is possible:

val, unit = engine.cutoff(0.5) -- maps the argument and sends it right away
displaystring = 'cuttoff: ' val .. unit

anyways, in the meantime i'll build out the guts when i get a chance

antonhornquist commented 6 years ago

re: clean setter functions like engine.cutoff(hz).

Nice, but we'd have to to handle command - parameter name clashes some way, then?

re: if we send c_set directly from matron then it has to be scaled correctly in matron/lua.

Not necessarily. AFAIK both what I refer to as unmapped (440.0 hz) and mapped (a float between 0...1) values may be used in control busses. SynthDefs would have to be written differently depending on what the lua side uses when invoking c_set.

Hmm. I dunno about this. My initial thought with Spec/ControlSpec'ish usage was more from a descriptive point of view, to have metadata to clamp / constrain values lua-side, not necessarily unmapping/mapping.

catfact commented 6 years ago

Nice, but we'd have to to handle command - parameter name clashes some way, then?

yes, engine shouldnt have any name clashes

i still think its useful in lua to have both mapped and unmapped. so might as well use unmapped on sc side to avoid converting twice

antonhornquist commented 6 years ago

btw error by me above, I believe unmapped is 0..1 and mapped is the ControlSpec range.

~mapped = 440.0; //hz
~unmapped = \freq.asSpec.unmap(~mapped); // 0..1
tehn commented 6 years ago

@antonhornquist how complete does this feel as of hello_ack?

as i said earlier my intention is to have this functionality pulled into the parameters menu on the home screen. but i don't want to interrupt any work you're doing on it now.

antonhornquist commented 6 years ago

I’m fine with using any of this stuff for other purposes.

The ControlSpec lua class is stable since it’s based on the SuperCollider class of the same name (yet does not have all features implemented yet). I felt a need for map and unmap in lua which is why i started the port. The rest (Param, Scroll) just happened after refactoring code to remove boilerplatey stuff and in needing to expose parameters quickly for testing engines.

The Param lua class can of course be used to store parameters, if needed.

Param and Scroll happened quite quickly so naming of things can be improved.

I should probably submit a revised pr for sc reports of parameters to lua this.

antonhornquist commented 6 years ago

parameters implemented in lua, let's skip sc side