Open davidedc opened 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:
frame
time
There is always a default BPM, but it can be overruled by connecting to a midi server.
beat
= continuously increasing real valued number that indicates the current bar/beat. (Name subject to discussion, I don't think beat
is immediately clear, but I can't come up with an alternative)wave
= sin(beat * pi)pulse
= like wave but with more punch to it.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.
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.
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.
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!
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.
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).
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.
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).
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.
so with the timekeeper updates pull request merged, I think that this is now all working as we need and can be closed right?
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).