hydra-synth / hydra

Livecoding networked visuals in the browser
https://hydra.ojack.xyz
GNU Affero General Public License v3.0
2.18k stars 268 forks source link

Passing functions as parameters #8

Open ojack opened 6 years ago

ojack commented 6 years ago

Robust and flexible way to handle all types of parameters

  1. javascript functions that compile to glsl functions, i.e: osc().add(osc())

  2. javascript functions that are passed in at every render pass: osc((t)=>(3*t)) How can this syntax be improved? should certain functions be provided by default? can this be used for passing osc parameters and/or beat detection, etc?

  3. Static values: osc(2.0)

Ideally, the user should not have to think too much about types, and each function should accept all three kinds of inputs and adapt accordingly

echophon commented 6 years ago

In regards to 2. I have found it useful to define functions as variables for reuse. This has some tradeoffs from inline reference that CTRL+Shift+Enter helps with, as changes to the function will now need to be evaluated in multiple places.

x1 = (t)=>(10 Math.sin(t 0.1)) x2 = (t)=>(5 Math.sin(t 0.1))

o0.osc(x1) o1.osc(x2)

ojack commented 6 years ago

thanks for the feedback! I would like to add a way to evaluate blocks of code for situations like that.

Another one I like is to define an extra function return, to be able to reuse the functions with different variables. i.e.: x1 = (y)=>(t)=>(y * Math.sin(t * 0.01))

and then use it like: o0.osc(x1(20))

It makes me wonder if some common functions could be predefined...i.e.

sin = (amplitude, period)=>(t)=>(amplitude* Math.sin(t * period))
o0.osc(sin(20, 0.01))
echophon commented 6 years ago

Thanks for this amazing tool! I like the ability to create them on the fly, this will be good for anyone who wants to go beyond the common functions, but having some common functions predefined opens doors & makes things more accessible to newcomers. I think having these available & examples of their use would certainly be beneficial.

I could see a whole class of wave function generators to supplement the set of image functions that you've established. Taking cues from the modular synth world (I think this is a great theme to continue!), these could be quite useful as 'control voltages' of sorts. Sine, triangle, square, saw, etc to start - a lot of complex shapes could be constructed from those simple building blocks

going from your example (I need to try this) It looks like we can get a waveform with a complex period by nesting the function

sin = (amplitude, period)=>(t)=>(amplitude* Math.sin(t * period))
o0.osc(sin(20, sin(0.02, 0.5)))
ojack commented 6 years ago

I like the idea of control voltages, and the complex periods is exciting. It doesnt seem to work yet...i think because of the hacky way that i am passing in the time variable.. But I am not totally sure and am going to play around later today.

ojack commented 6 years ago

ok, it works if the function takes care of the case where it is passed in a function:

sin = (_amplitude, _period)=>(t)=>{
  let period = _period
  let amplitude = _amplitude
  if(typeof _period == 'function') period = _period(t)
  if(typeof _amplitude == 'function') amplitude = _amplitude(t)
  return amplitude*Math.sin(period*t)
}

o0.osc(sin(20, sin(0.02, 0.5)))

I wonder if there is a nicer syntax for this

echophon commented 6 years ago

Ah, this makes sense.

It could also be written with a ternary but I'm not sure this qualifies as nicer syntax. It obfuscates it a bit, but I think this adds weight to the idea of predefining some of these functions. Have proper type checking on the built-ins and let people write quick & dirty functions in the editor.

written as a ternary

sin = (_amplitude, _period)=>(t)=>{
  let period = typeof _period == 'function' ? _period(t) : _period
  let amplitude = typeof _amplitude == 'function' ? _amplitude(t) : _amplitude
  return amplitude*Math.sin(period*t)
}
ojack commented 6 years ago

I have been letting this sit to see how it works in practice...

In the dev branch of hydra-synth I have included some utility functions for fading and handling arrays of parameters. https://github.com/ojack/hydra-synth/blob/dev/src/timingUtils.js

Now it is possible to do osc([23, 200, 39]).out() and it cycles through the values. osc([23, 299, 39].fast(2)).out() cycles through them twice as fast.

ojack commented 6 years ago

What I would like to do is to generalize the idea of modulating a set of parameters with a texture so that ANY parameter can accept a 2d texture (or generator function such as osc()). For example,

src(s0).modulate(s1)

is using the r and g channels of the texture s1 to affect the x and y coordinates of s0.

Instead of that, I would like to be able to use something like:

src(s0).rotate(s1)

Where the colors of s1 would then accept the rotation of s0 (in addition to parameters as numbers or parameters as timing functions).

This requires a substantial refactor of hydra so is a more long-term thing! But just putting it here in case anyone has any thoughts.

echophon commented 6 years ago

In the dev branch of hydra-synth I have included some utility functions for fading and handling arrays of parameters. https://github.com/ojack/hydra-synth/blob/dev/src/timingUtils.js

Now it is possible to do osc([23, 200, 39]).out() and it cycles through the values. osc([23, 299, 39].fast(2)).out() cycles through them twice as fast.

I'm just now seeing this & the cycling works great. I also got WebMIDI working from the snippet you posted in #26. I wasn't able to get the fadeIn / fadeOut working, but the concept struck me as similar to a lag processor, which might be a reasonable option to easing curves.

A lag processor is another analog control voltage concept & can help create smooth transitions between values much like an easing curve. It might also be useful with WebMIDI as I'm noticing that changing values creates a lot of abrupt, stepped changes, which is not always desirable.

In the abstract, a lag processor works in code by updating a goal value instead of the actual value. The actual value then continuously moves toward the goal, some amount every frame.

jarmitage commented 5 years ago

Continuing this discussion...

Just want to add these for reference of how I am borrowing my Tidal approach to generalising continuous functions.

sin = (min=0,max=1,freq=1) => ({time}) => Math.sin(time*freq) * max + min
sq = (min=0,max=1,freq=1) => ({time}) => ((Math.sin(time*freq) < 0) ? 0 : 1) * max + min
saw = (min=0,max=1,freq=1) => ({time}) => (((time * freq) % 1) * 2 - 1) * max + min 
rand = (min=0,max=1) => Math.random() * max + min

// examples...

osc(10,0,0)
  .rotate(sin(0,0.5,32))
  .out()

noise(sq(5,10,8),0.1).out(o0)

osc(1,1,2).invert(sq(0,1,8)).out(o0)

And also, attempts at porting some Tidal pattern functions...

Choose

// This needs to update using `getBpm()`
choose = (array) => {
  return array[Math.floor(Math.random() * array.length)];
}

shape(choose([2,5,8,12,15,5])).out()

Run

run = (end,step=1,direction=1) => {
    const len = Math.floor(end / step) + 1
    if (direction === 1)
        return Array(len).fill().map((_, idx) => (idx * step))  
    else if (direction === 0)
        return Array(len).fill().map((_, idx) => (idx * step)).reverse()
}

shape(run(5)).out()

shape(run(15,0.66,0)).out()

@ojack suggested function chaining for this sort of thing, e.g.:

shape(run(15,0.66,0).choose().fast()).out()

Another issue (sorry this is a long comment) is how to avoid polluting the global namespace. Two ideas discussed related to this:

ojack commented 5 years ago

Continuing from @jarmitage 's comment I really like the idea of being able to load a set of custom functions (both glsl and utility functions) in a boot file or something similar. I think it is really easy to pollute the global namespace and I like the idea of sharing sets of functions specific to a certain purpose.

ojack commented 5 years ago

Also, I haven't had a chance to dig into this example by Charlie running tidalCycles mini-notation inside of Hydra, but it looks promising:

https://twitter.com/gibber_cc/status/1145207647190171653

charlieroberts commented 5 years ago

Here's the snippet to mess with the mini-notation: https://gist.github.com/charlieroberts/3aa779329c49e10557a8ca66093eeb0e