Skip to content

Instantly share code, notes, and snippets.

@trentgill
Last active June 24, 2021 18:54
Show Gist options
  • Save trentgill/19109bca849f5fcf39f2124228087bdd to your computer and use it in GitHub Desktop.
Save trentgill/19109bca849f5fcf39f2124228087bdd to your computer and use it in GitHub Desktop.
working through some issues in asl2's mutable / iterating type
-- feels different if you separate the ASL structure from the data
function modulator(height, risetime, falltime)
return loop{ to( height, risetime )
, to( 0, falltime)
}
end
function init()
starting_rise = 1
output[1]( modulator( dyn{height = 10}
, (mutable(starting_rise)-0.1) % dyn{new_rise=starting_rise}
, dyn{fall_time = 0.2}))
end
output[1].dyn.height = 4 -- change to 4V tall
output[1].dyn.new_rise = 2 -- rise time resets to 2 seconds (not 1)
------------------------------------------------
-- proposed version with mutate as a verb
-- mdyn is just a dyn with mutation precedence
function init()
starting_rise = 1
output[1]( modulator( dyn{height = 10}
, mutate( (mdyn(starting_rise)-0.1) % dyn{new_rise=starting_rise})
, dyn{fall_time = 0.2}))
end
------------------------------------------------
-- it's weird that mdyn is modified by the function that calls it
-- too much magic & thus hard to follow
--
-- more natural would be if mutate were a function that takes a dyn argument
-- original snippet
mutate( (mdyn(starting_rise)-0.1) % dyn{new_rise=starting_rise}) + 0.01 -- addition is just to demonstrate scope
-- consider 'on_step' as an alternate to 'mutate'
dyn(1):on_step( function(self) return (self-0.1) % reset_rise end ) + 0.01
-- which we can format:
dyn(1):on_step( function(self)
return (self-0.1) % reset_rise end
) + 0.01
-- or name the function
function dec(self)
return (self-0.1) % reset_rise
end
dyn(1):on_step(dec) + 0.01
-- this is tough because we no longer have access to the 'reset_rise' variable
--------------------------------------------------------
-- let's look at the function style, in-lined, in the full context
function init()
starting_rise = 1
output[1]( modulator( dyn{height = 10}
, dyn(starting_rise):on_step( function(self)
return (self-0.1) % dyn{new_rise=starting_rise} end
) + 0.01
, dyn{fall_time = 0.2}))
end
----------------------------------------------------------------------------------------------------
-- or we could take this same idea but use the 'dec' function but paramterize it
function modulator(height, risetime, falltime)
return loop{ to( height, risetime )
, to( 0, falltime)
}
end
-- returns a function as we're describing an action that will happen in the future
function dec(self, step, reset)
return function(self) (self - step) % reset end
end
function init()
starting_rise = 1
output[1](
modulator( dyn{height = 10}
, dyn(starting_rise):on_step( dec(0.1, dyn{reset_rise=starting_rise}) ) + 0.01
, dyn{fall_time = 0.2}))
end
-- alternate form without method call, and just paramaterized on_step
function init()
starting_rise = 1
output[1](
modulator( dyn{height = 10}
, on_step( dyn(starting_rise), dec(0.1, dyn{reset_rise=starting_rise}) ) + 0.01
, dyn{fall_time = 0.2}))
end
-------------------------------
-- return to the mutate as verb option
function init()
starting_rise = 1
output[1](
modulator( dyn{height = 10}
, mutate( (mdyn(starting_rise)-0.1) % dyn{new_rise=starting_rise})
, dyn{fall_time = 0.2}
))
end
---- another form where dyn_iter produces an ASL number with an associated iteration behaviour
-- a super-charged *dyn* that is modified on each access
output[1](
modulator( dyn{height = 10}
, dyn_iter( starting_rise -- initial value (can also be a table for named arg: {rise=starting_rise})
, function(i) -- this function gets called on each access. arg 'i' is the dynamic variable to modify
return (i-0.1) % dyn{new_rise=starting_rise} end
) + 0.01 -- dyn_iter produces a number we can perform arithmetic on (like dyn)
, dyn{fall_time = 0.2})
)
-- the above is nice in that codifies the mutation as an iteration (a concept known to crow scripters)
-- the *problem* is that it looks like a normal function, BUT IT'S NOT
-- *the function is only allowed to perform arithmetic as it must be compiled into a pure C function*
-- i worry that this caveat makes this whole approach counter-intuitive.
-- it *looks* like a function, and acts like one in many ones, but not in *all* the ways
--------------------------
-- new thoughts post discussion with brian
-- 1. 2nd arg to dyn is a mutation-description-table
output[1](
modulator( dyn{height = 10}
, dyn( {rise=starting_rise}
, {inc=-0.1, range={0, dyn{new_rise=starting_rise}}}
)
, dyn{fall_time = 0.2})
)
-- 2. dyn can be method-chained to per-step-modifiers, and range-limiters
output[1](
modulator( dyn{height = 10}
, dyn{rise=starting_rise}:step(-0.1):wrap(0, dyn{new_rise=starting_rise})
, dyn{fall_time = 0.2})
)
-- can also break up the method chains for readability
output[1](
modulator( dyn{height = 10}
, dyn{rise=starting_rise}
:step(-0.1)
:wrap(0, dyn{new_rise=starting_rise})
, dyn{fall_time = 0.2})
)
-- per-step modifiers
:step(increment) -- increments the dyn value per step
:mul(multiplier) -- scales the dyn value per step
-- range limits applied *after* per-step modifiers
:wrap(min, max) -- sawtooth modulation
:clamp(min, max) -- one-shot attacks (eg pitch enveloping)
:bounce(min, max) -- triangle modulation
-- i like this second idea because it aligns with the approach from sequins & public
-- naming requires some thought obviously, but this illustrates an approach
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment