Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASL dyn to allow calculations outside of a to() #433

Open
trentgill opened this issue Aug 13, 2021 · 9 comments
Open

ASL dyn to allow calculations outside of a to() #433

trentgill opened this issue Aug 13, 2021 · 9 comments
Labels
design Issue needs design consideration enhancement New feature or request

Comments

@trentgill
Copy link
Collaborator

At present, in order to change a variable without it taking effect, you have to do some acrobatics.

In order to allow (conditionally) updating a variable without applying the value in a to call, i propose allowing raw dyn operations in an ASL table:

loop{ to(5,dyn{lev=5})
    , to(0,dyn{lev=5})
    , dyn{lev=5}:mul(0.9)
    }

some other ASL helpers:

  • Ability to share a single named dyn variable, without having to re-init it with a default value every time (see above example). logically it should only be 'initialized' in one place, and the others could just refer to it by name.

  • nested loop{loop{}} seems to break (or at least with loop{times{

  • something breaks when you assign dyn to a local variable in an ASL-constructor and pass in the name of the variable, rather than copying the table into each location.

  • all to() calls take twice as long as they should

  • asl._if is weird & unclear what values it matches on (>=1?)

@trentgill trentgill added bug Something isn't working enhancement New feature or request design Issue needs design consideration labels Aug 13, 2021
@HelveticaScenario
Copy link

What is the expected behavior of

loop{ to(5,dyn{lev=5})
    , to(0,dyn{lev=5})
    , dyn{lev=5}:mul(0.9)
    }

@trentgill
Copy link
Collaborator Author

trentgill commented Sep 23, 2021

I realize the naming didn't make sense because lev was in the 'time' position.

loop{ to(5,dyn{t=5})    -- ramp to 5v over dyn.t seconds
    , to(0,dyn{t=5})    -- move to 0v over dyn.t seconds
    , dyn{t=5}:mul(0.9) -- dyn.t *= 0.9
    }

The effect is that of a triangle lfo which increases in speed by 10% every cycle of the whole loop.

This example is not a great example of why you would need the dyn to stand on it's own. It only becomes useful when you are trying to build custom oscillators with state built in. There's some examples in this gist where state changes are inside an asl._if -- the to calls are purely there to wrap the dyn mutation.

@HelveticaScenario
Copy link

HelveticaScenario commented Sep 28, 2021

Personally I think allowing bare dyns in that way muddies things somewhat. The way I understand dyn Is as a placeholder value, a noun to use with a verb to. A bare dyn Is a noun without a verb.

@trentgill
Copy link
Collaborator Author

@HelveticaScenario
I mostly agree with the noun/verb characterization. The finer point is that the dyn method calls (mul, step, wrap) are verbs that operate over a dyn upon each access.

Do you have thoughts on how we could alter the syntax so that it is possible to make the more complex ASLs of the above gist in a way that doesn't use 'dummy' to calls? At present the calls inside of each asl._if are applied instantly, then overwritten back to a known value. This felt very weird to write, so I imagine it would be very hard for less experienced scripters to intuit how to acheive those goals.

@HelveticaScenario
Copy link

@trentgill I have a couple ideas yeah

Basically, we separate the method calls from the dyn into a change verb, and we separate the declaration of a dyn from its usage

The 2 ways I can see this happening is as follows

-- Option 1: `dyn` tables
t = dyn(5)

output[1].action = loop{ to(5, t)
                       , to(0, t)
                       , change(t, mul(0.9))
                       }

-- Because the name of a `dyn` is now just the variable its assigned to we cant
-- have the `output[1].dyn.t` pattern anymore, but because we now have a `dyn`
-- table to play with we can do this
dyn.value = 3 -- `dyn.volts` is another option


-- Option 2: `dyn` strings
output[1].action = loop({t = 5}, 
                        { to(5, 't')
                        , to(0, 't')
                        , change('t', mul(0.9))
                        })

-- `dyn` are still named so we retain the current `dyn` value assignment syntax
output[1].dyn.t = 3

Personally I think option 1 is more interesting and more in line with how I expect programs to work in general, where you explicitly create and use values. It also solves a potential issue that arises when you separate dyn declaration from usage which is how to ensure that the dyns that are used have had a default specified.

As for separating mutation into its own separate verb, we make asl driven mutation explicit, which I personally feel is clearer because it separates assignment from accessing, which is how programs usually work. It is, however, more verbose as it requires an explicit step to do a mutation, but I personally don't feel like it's so verbose as to be a detriment.

One thing to note: change is variadic, so this is valid change(t, mul(0.9), wrap(0.1,5)) as would be any combination of modifiers.

@HelveticaScenario
Copy link

HelveticaScenario commented Sep 28, 2021

If I understand whats going on in that gist, using option 1, you should be able to do it like this

function steps(f1, f2)
    local ff = dyn(f1)
    local lev = dyn(5)
    local count = dyn(-30)

    return loop{ asl._if( count,
                        , { change(lev, mul(lev), mul(lev), mul(lev), wrap(-5,5)) }),
               , change(count, step(1), wrap(-3000,1.1)),
               , to( lev, 0.5/ff)
               , to(-lev, 0.5/ff)
               }
end

One thing that we would need to decide is if you have a variadic change, and you are using the dyn in the mutation multiple time like we are in that if, would the lev change after each step in the change, so the value of lev at the second mul be different that the one in the first mul, so that a variadic change would just be syntactic surgar for a series of single mutation changes?

@HelveticaScenario
Copy link

@trentgill any thoughts on this?

@trentgill
Copy link
Collaborator Author

trentgill commented Oct 7, 2021

I certainly thought about it a lot, but I'm not sure I'm anywhere near a definitive position. There are arguments in many directions, and I worry we are too focused on one particular aspect.

Regarding your proposal it might be helpful to read through the source, at least asl.lua as it will make a few things clearer about what step, mul and wrap actually are. In essence they are just syntactic sugar over some meta-table enabled math operations.

May be of interest: mul(d, operand) is actually just a function-style representation of d * operand. so rather than change( d, mul(operand)) the originally proposed solution is d:mul(operand) which itself turns into #(d*operand) where # is meta-tabled to mean "mutate the value of...".

I have a little trouble with the necessity of an explicit 'verb' function (eg. change). My reasoning is that method calls are, at least throughout the crow codebase (see Public & Sequins), 'verby' actions. Applying a method call to an object changes that object.

As a historical reference, originally all the operations used standard math operators, and any object called mutable would freeze those changes whenever they occurred. This was seen to be too vague and there was a desire to separate the mutation of dynamic vars away from simple arithmetic to be applied to these parameters at runtime.

//

I can see your arguments for the syntax you propose, but equally I see problems both in terms of implementation and structure. Your proposed changes would be breaking to existing scripts, and while this isn't a dealbreaker, it requires there to be a strong benefit to the new option which I don't clearly see.

So perhaps I can ask a slightly different question: What is your motivation related to this problem? It seems you have real enthusiasm for it (great!), but I'm a little vague whether you're interested in the problem of "ASL dyn calculations outside of a to" or something closer to an alternate syntax for dyn altogether? We'd love to have you on board with the project, I'm just trying to tease out where your interest stems from so we don't get stuck debating unrelated semantics.

@HelveticaScenario
Copy link

HelveticaScenario commented Oct 14, 2021

To answer your last question first, I’m still figuring it out. When I think about crow there are some things that I wish were there, but I’m conflicted because (a) I am very new to it and I don't feel like I’ve explored it enough to justify to myself that these things that I’d like need to be added or if they can be achieved in existing ways that work well enough, and (b) at the end of the day it isn’t my project, it’s yours and tehn’s. I don’t meant that in a negative way, and you have been nothing but courteous, but I can tell that y’all have a vision for it and I’m not sure if what I’d like to use crow for is really what y’all made crow for.

Be that as it may, I think its an amazing product and I’m inspired to help.

As for your first point regarding the need for a change function, it isn’t actually a necessity to solve the problem at hand. I added it because I felt that the only real way I could see to solve the problem in a way that I felt was elegant was to separate the declaration of a dyn from it’s usage so that you only had to declare it once with it’s default value and it would be clear where that should be done, and because asl is in effect a way of creating something that describes a pattern of movement and change for the native layer to perform, and less about the lua code running through an imperative process, I felt that having independent functions that create those descriptions instead of methods on a table (which implies to me a direct manipulation of the table itself). This is, however, entirely subjective, and if you replaced the change(t, mul(0.9)) with t:mul(0.9) or change('t', mul(0.9)) with dyn('t'):mul(0.9) that would be just as valid.

@trentgill trentgill removed the bug Something isn't working label Nov 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Issue needs design consideration enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants