hard times with time

Discuss problems and solutions.
Post Reply
Rolix
Newbie
Posts: 28
Joined: 18 Mar 2013 19:41
Location: Munich

hard times with time

Post by Rolix »

Hi,

I've started coding a little sequencer for HarmEx and would like to get a better understanding how this time variable works. I have:

t1 = floor(time*(bpm*quarters)/minutes) <--(=time*(120*4)/60) for example)
t2 = floor(t1%sizeof(pitches)) <-- pitches are the midi note values of chord, like {36, 40, 43} for a c maj.

Now what I don't understand is this:

assigning "floor(t1%sizeof(pitches))" to t2 in the editor tree works as desired. I can read it's constantly changing values in an OnFrame-triggered function and send notouts of pitches[t2].

However: if I do the formula assignement for t2 in another function and not in the editor tree, the value of t2 never changes. So I wonder what's going on behind the scenes of this time variable?

Thanks in advance!

Cheers,
Roland
Traxus
Regular
Posts: 211
Joined: 30 Nov 2012 06:19
Location: Detroit
Contact:

Re: hard times with time

Post by Traxus »

I've done a bit in this department, but not as far as making a sequencer. However, dosen't lemur have an internal midi clock (sec 13.7 in the manual) and wouldn't that be a better way to go about it?
Rolix
Newbie
Posts: 28
Joined: 18 Mar 2013 19:41
Location: Munich

Re: hard times with time

Post by Rolix »

Probably yes, my mistake, I had thought that the midi clock only works when it's fed with an external clock signal. I'll try that, thanks for the tip.

Yet I'd like to understand the difference between assigning a time calculation at designtime vs assigning it at runtime (in a script) and why the latter doesn't work?
Softcore
Regular
Posts: 1613
Joined: 04 Nov 2012 08:34

Re: hard times with time

Post by Softcore »

Rolix wrote:Probably yes, my mistake, I had thought that the midi clock only works when it's fed with an external clock signal. I'll try that, thanks for the tip.
Frankly, that is my assumption too....it works even without an external clock?
Rolix
Newbie
Posts: 28
Joined: 18 Mar 2013 19:41
Location: Munich

Re: hard times with time

Post by Rolix »

It doesn't work without external clock, just tried it using a Monitor attached to midi_clocks, it gives you a vector of zeroes that don't change at all. Would be a nice feature though to have a single clock source both running free on its own and beeing syncable if required.
Traxus
Regular
Posts: 211
Joined: 30 Nov 2012 06:19
Location: Detroit
Contact:

Re: hard times with time

Post by Traxus »

Rolix wrote:It doesn't work without external clock, just tried it using a Monitor attached to midi_clocks, it gives you a vector of zeroes that don't change at all. Would be a nice feature though to have a single clock source both running free on its own and beeing syncable if required.
Ah okay, bummer.

I am also interested in a lemur only sequencer for a couple of reasons, but here are my thoughts:

Perhaps Mat of sequencoMat might have some advice, his initial version operated completely within lemur. I have a few guess as to why he moved away from this though, most notably is that you are really only going to get 60 intervals a second (usually less for heavier templates) to fire your midi notes with, so the accuracy of timing becomes an issue. If you're down to 30 FPS the problem compounds itself. So first, imagine we are trying to fire four to the floor quarter notes at 120 BPM. Easy enough, a quarter note should fire every 500ms, or every 15 frames at 30 FPS, but what if we are at 41 FPS? For this reason we can't count the frame as an interval, we need to use time, but the frame might fire a few seconds after the exact moment when that note should fire, so then we have to add in some leway that also factors in FPS... Now, lets say we want to run at 132 BPM, or 455ms between each quarter note, and you can see how being limited to even 60 cycles per second can be a difficult way to ensure that a note fires every time, on time, when it should.

So, these are the bottle necks we would have to work around. Using time to modulate a fader or a knob is one thing, I've even managed to control fade in/fade outs that occur over a certain amount of beats in sync with the music, but I'm having a harder time wrapping my head around how to actually manage tempo with it and fire really accurate events...

However, the capability to be reasonably accurate should be there, 60FPS means a frame every 16.7ms, 30FPS means 33.4ms, so I guess you could run an onFrame Script that said "if time is greater than the expected time that this note should fire, fire the note and reset its expected firing time for the next cycle of the loop"
Rolix
Newbie
Posts: 28
Joined: 18 Mar 2013 19:41
Location: Munich

Re: hard times with time

Post by Rolix »

Traxus wrote: [...]
However, the capability to be reasonably accurate should be there, 60FPS means a frame every 16.7ms, 30FPS means 33.4ms, so I guess you could run an onFrame Script that said "if time is greater than the expected time that this note should fire, fire the note and reset its expected firing time for the next cycle of the loop"
Have a look at the chord sequencer in HarmEx 1.3, that's the best I could come up with. I set a time calculation to the tempo selected by the user and query changes of that value in an onframe-event, so that each change fires the next step. The problem of this method becomes obvious when you try to start the sequencer at the beat of an external app like SampleTank, no way as long as I'm limited to querying a free running time variable in a free running unsynced onframe event.
Traxus
Regular
Posts: 211
Joined: 30 Nov 2012 06:19
Location: Detroit
Contact:

Re: hard times with time

Post by Traxus »

Hrm, I'll give it a look when I get home.

What is your FPS clocking at? Are you storing the start time of when play is initiated? Are you storing the expected value of each note individually?

The more I think about it, a deviation of up to 33ms seems like it could sneak by the human ear but perhaps I'm wrong.
Rolix
Newbie
Posts: 28
Joined: 18 Mar 2013 19:41
Location: Munich

Re: hard times with time

Post by Rolix »

I have a variable defined:
t1 = floor(time/minutes*bpm*quarters)
t2 = empty

I have an onframe Event where I do
if (t1 != t2)
{ t2 = t1;
... do the sequencing stuff
}

The resolution of the timing depends on the bpm and quarters settings. I don't think that omitting t1 and calculating the complete timing in the onframe would be a good idea, you'd have to compensate for frame rate variations all the time. It may be possible, but, like missing string functions, I refuse to code workarounds for features that should be provided by Lemur itself.
Traxus
Regular
Posts: 211
Joined: 30 Nov 2012 06:19
Location: Detroit
Contact:

Re: hard times with time

Post by Traxus »

I got preoccupied last night and didn't have time to check out your template but I think you can get away with calculating on the fly. You also do not need to factor in FPS, just compare the times on frame...

So, lets imagine were firing on the whole notes on a 4/4 time signature at 120 BPM with a loop that lasts four bars.

A quarter note is 500ms at 120 BPM, a whole note is 2000ms. This of course can be calculated, as described here
i can only find bpm to milliseconds and i dont remember how to switch around algebraic equasions that have 2 variables.
this is what someone said in a forum:
y = (240,000 * (d / t)) / x

where t is the divisions of a bar (e.g. 16)
and d is the delay time (e.g. 3)
and x is the bpm of your song

y is the delay time in milliseconds

so what would it look like the other way around please.
First, we need a vector to store the expected firing times of all of the notes. We're over simplifying it for discussion so there will only be 4.

Code: Select all

noteTime[1];
noteTime[2];
noteTime[3];
noteTime[4];
Lets also pretend we've already calculated our expected note length time, its 2000ms in this example so

Code: Select all

noteLength = 2000;

Next, we need a variable to store the start time for when we press the play button. So we'll call that startTime. Lets just say we hit it at 500,000ms. We can then use this value, plus our expected note length value, to construct the expected time that the note length should fire. Again, the code is over simplified (should be a for loop) but the principal is here. We are also going to store which note should be the next one to fire. IRL we again want to run a for loop and compare the times, but we will just say it is note 1 for now.

Code: Select all

//on expression startButton.x == 1
startTime = time;
//startTime = 500000;

noteTime[1] = startTime + (noteLength * 1);   //502,000ms
noteTime[2] = startTime + (noteLength * 2);   //504,000ms
noteTime[3] = startTime + (noteLength * 3);   //506,000ms
noteTime[4] = startTime + (noteLength * 4);   //508,000ms

nextNote = 1;

So we've got all that stored and prepped, now all we need is the onFrame event to check whether its time to fire a note. If the time is at all greater than the note time, then we fire the note, re-calculate its next firing time, as well as advance the nextNote counter so that the onFrame will begin checking for that one. Now we are checking for whether its time to fire the note independent of frame rate. Each individual note could fire up to 16-33ms later than it should, but that is a very nominal diffrence. By comparing the expected versus the actual time on every frame, we effectively negate the issues of fluctuating frame rates.

Code: Select all

//onFrame
if (time > noteTime[nextNote] {
    //fire the note here
    //now we need to advance the firing time of this note for the next cycle of the loop.
    noteTime[nextNote] =  noteTime[nextNote] + (noteLength * 4);
   //this note has fired for this iteration, so tell the onFrame which to listen for next.
    nextNote = nextNote + 1;

}
Again, this is all very static math to show the principals but it will work and I highly doubt it will be too heavy for lemur to handle, and what we end up with is a sequencer where each note may deviate by a few ms, but because each one is independent of the next the overall integrity of the tempo is kept intact.
Post Reply