davidedc / livecodelab

a web based livecoding environment
http://livecodelab.net/
MIT License
329 stars 63 forks source link

"scale", "move", "rotate" and "wave" defaults should be somewhat aligned to the bpm #206

Open davidedc opened 10 years ago

davidedc commented 10 years ago

the bpm function sets a rhythm to which several other "defaults" of functions should align.

In other words, the default "scale", "move", "rotate" and "wave" defaults should be somewhat aligned to the bpm (or multiples of, if the default/typical bpms are too slow/too fast).

noio commented 10 years ago

I have been thinking of a good way to do this with a set of 'orthogonal' tools. I'd love to work on this integration of bpm with the rest of LCL, I'll see if I can get a pull request up soon (fixing #106 in the progress). My ideas below:

Time-based variables:
Beat-based variables:

There is always a default BPM, but it can be overruled by connecting to a midi server.

Both wave and pulse can easily be generated from beat. However, both are nice to supply because they are common and thus save some typing.

Defaults:

Then, the defaults for scale, move and rotate could be expressed in terms of those variables, respectively:

scale wave
//or 
scale pulse + 0.5

,

move wave 

and

rotate beat % 1 
//or
rotate beat % 4 

The last would correspond to the "spin" function mentioned earlier.

"Humanize":

I've been thinking it might be nice to provide variables at a more immediately useful scale. What I really like is that the unit cube (1x1x1) just looks right on the screen. This is a bit out there, but perhaps the same principle could be applied to time, by making it in seconds in stead of milliseconds, (there is a lot of /1000 in most of my sketches).

A similar something applies to the rotation angles, mostly in combination with the beat variable, I imagine users wanting to rotate beat * Math.PI. Perhaps rotate could have the multiplication by pi built in. I do realize that both of these—but especially the last suggestion—might muddle the API and alienate users who are more familiar with the underlying tech, so take these as a very light suggestion.

davidedc commented 10 years ago

yes for everything apart two things and a note. 1) I am still not convinced that midi rhythm should override bpm. Will discuss separately, I have some examples of cases that don't seem to be handled well by that mechanism. To be discussed in separate longer thread. 2) pre-PI multiplied rotate: 1 meaning 180 degrees is kind of weird... If it was 1 as 2*PI i.e. 360 degrees then it would be more natural. Would that work? To be discussed in separate longer thread as there might be a few more options.

A note about "beat" - I can think of one way of implementing it where beat varies smoothly based on the time at which the frame is running - that would make it difficult to handle a comparison like

if beat == 2

because this would be likely to happen: one frame a little less than two, the next frame a little more than 2. And yet, I bet people would expect that comparison to work. That said, I don't know what you have in mind exactly, I'm just bringing up a possible scenario for a particular implementation I'm guessing.

I like the idea of using seconds and the defaults as you suggested.

I reckon that even though there might be more thinking on the two points above, sure go ahead with everything else, that would be very cool and very helpful indeed!

davidedc commented 10 years ago

also, I don't know if it applies in this case, but spare a thought on that floats are tricky:

# gives "yes"
if 3.25 % 1 == 0.25
 alert "yes"
else
 alert "no"

but

# gives "no"
if 3.1 % 1 == 0.1
 alert "yes"
else
 alert "no"

Comparisons with "quarters" work i.e. 0,0.25,0.5,0.75 , but other comparisons are tricky cause of the usual 0.1 approximation in binary is infinite etc etc. I don't know why one would want to compare with 0.1, but just saying.

noio commented 10 years ago

Agreed about the overriding, there needs to be some kind of elegant mechanism for when the user both sets a bpm and connects. Also, yes, 2*pi is what I meant.

Good point about the beat. The implementation I was thinking of would be similar to the one in pulse (perhaps with some interpolation to protect against sudden changes). In my mind, I was not expecting users to go if beat == 1. Because even if the beat would be exactly 1.0 at any single frame, whatever that block does will only be executed once, so it becomes more of a "timed" doOnce. I don't see a scenario where you'd want to do that.

But maybe the user expects beat == 1 to mean floor(beat % 4) == 1 ? But then this 4 is kind of an assumption.

What I did in my demo sketch was something like:

if beat % 2 < 1
    background black

Which is easy enough to type and understand. There could be a shorthand for that like oddBeat/evenBeat, but I don't think it saves much typing, and it doesn't even include scenarios where you want to count to 4 (like the pulsing shapes in the demo).

davidedc commented 10 years ago

Hmmm I see what you mean about the beat. Yes let's do it as you say. Interestingly, if we create suitable odd and even functions that "floor" their argument, user can simply type if odd beat. (Note that if odd beat == true wouldn't work as coffeescript transforms that into if (odd(beat === true)), so one can either write if odd beat or if (odd beat) == true ). I so like it about English that it's mostly a pre-positional language, all the qualifiers and modifiers come before the noun, so all these constructs just work!

Yes I think 1 for a complete turn is easy to grasp. If we need, we can always introduce an angleMode for advanced users. We need to figure out what to do with the "radians" and "degrees" functions though, 'cause if there are only two angle modes it's unambiguous, if we introduce a third one we need to disambiguate.

noio commented 10 years ago

I've been doing some planning, and I think the most maintainable way to implement a unified bpm system would be to have the TimeKeeper emit a (quarter?)"beat" event that the SoundSystem picks up on (in stead of the latter having its own internal setInterval loop).

To do this, I'd like to make TimeKeeper into an EventEmitter subclass (in stead of abusing the central EventRouter), including this snippet. And then I thought, better to have consistency and turn the EventRouter into an EventEmitter too.

And then I thought; why not just make the LiveCodeLabCore into an EventEmitter subclass, isn't that actually what the EventRouter does? A singleton to manage 'core' events?

However—before I start typing now—I'd like your approval first since this EventRouter is a central part of LCL. I could also implement the same system without merging the EventRouter (just keeping it as a single instance of an EventEmitter).

rumblesan commented 10 years ago

so it's a little while since i've looked at that code and personally I'd like to have another rummage around to remind myself what it all does before making any large design decisions.

That said, I think that it's a good idea to create a separate eventing system for the sound system. Over time there's going to be enough separate functionality there that ideally we don't clog the main eventing system up with it all.

However, I'm not so sure about turning the LiveCodeLabCore class into an event emitter. Looking at it quickly, I think the initial aim was just to have that as the single place where all the sub-systems get instantiated and joined together. Maybe it would simplify things, but I quite like the idea of having it really just be a structural thing that ties creates things, with a separate eventing object that gets passed around as need be.

Please feel free to try and convince me otherwise though :)

I'll have a dig into the code over the weekend hopefully so I can give a better opinion.

rumblesan commented 9 years ago

so with the timekeeper updates pull request merged, I think that this is now all working as we need and can be closed right?