Calculate Beats
Calculate Beats
Some high level parts of the timeline, such as unmetered Hits, Spans, or Actions use time
in absolute seconds to specify when they occur. However, most other areas use beat
s because it allows events to be more easily coordinated in “musical time,” taking into account tempo, meter, phrasing, etc. There are often times when it is useful to to know what beat
is at a given time
and vice versa.
What Is Tempo?
All tempo information in the timeline is provided in beats per minute (BPM), which is the standard unit for tempo. BPM (and tempo in general) measures speed.
Although BPM is the natural unit for talking about tempo, when doing calculations, it is far easier to work with beats per second (BPS). The reason is that 60 BPM = 1 BPS and this prevents needing to divide every result by 60.
Note: It is important to keep in mind that since tempo is a rate, you are often working with the difference (delta) between two points in time, and not necessarily absolute time.
The Anatomy of Tempo
Tempo information lives in two places in the timeline.
First is the tempo
key of a metered Span. This is the initial tempo and will
always be present since metered Spans by definition must have a tempo. When
creating a timeline, it is possible to omit the initial tempo, in which case an
appropriate one will be chosen for you. However, any timeline returned from
creating a Composition (see Composition vs. Render) is guaranteed to have a tempo
value.
The second place where tempo information is defined is in the tempo_changes
key of a metered Span. Where tempo
defines the initial value, tempo_changes
provides a list of times at which the tempo changes. Starting from the initial
tempo, which is at the time
of the Span, the tempo at any given time is
linearly interpolated between two points in time: the one at or before the time
in question, and the one at a time after.
Note: While these are the only places where persistent information about tempo exists, it is important to remember that some Actions can alter tempo (specifically
add_region
,copy_region
,copy_tempo_from_time
,ramp_tempo
, andset_tempo
). If you are using Actions that alter tempo and you are also interested in calculating beats, you will be required to create a Composition and do your calculations based on the resulting timeline, especially if you are asking for any random tempo.
Conversions
Here are the basic steps for finding the beat at the next time (in pseudocode):
Beat find_target_beat(BPM bpm, Second start_time, Second target_time, Beat start_beat) {
// convert to BPS
bps = bpm / 60
// calculate the change in time between the start and target second
delta_time = target_time - start_time
// calculate the change in beats based on the tempo
delta_beats = delta_time * bps
// calculate the next beat
target_beat = start_beat + delta_beats
return target_beat
}
where
bpm
is the the “current” BPM value from the timeline (see later sections)start_time
is the second you are starting fromtarget_time
is the second you wish to find the beat ofstart_beat
is the beat that corresponds tostart_time
Note: The only piece of information for the above function that is not explicitly in a timeline is
start_beat
. The beat at the start of any Span is always 0.0!
By using this, you can find any beat at any time after any beat which you already know the time of.
There are also times when it is useful to convert a beat into a time. For instance, since anything inside a Span is defined in terms of beats, you may wish to display that event in relation to other things which are defined in seconds.
Here are the basic steps for finding the time at the next beat (in pseudocode):
Second find_target_time(BPM bpm, Beat start_beat, Beat target_beat, Second start_time) {
// convert to BPS
bps = bpm / 60
// convert to seconds per beat (SPB)
spb = 1 / bps
// calculate the change in beats between the start and target
delta_beats = target_beat - start_beat
// calculate the change in beats based on the tempo
delta_time = delta_beats * spb
// calculate the next beat
target_time = start_time + delta_time
return target_time
}
where
bpm
is the the “current” BPM value from the timeline (see later sections)start_beat
is the beat you are starting fromtarget_beat
is the beat you wish to find the second ofstart_time
is the second that corresponds tostart_beat
By using either find_target_beat
and find_target_time
you can freely convert
seconds to beats and beats to seconds.
Constant Tempo
The simplest, and most common, situation to calculate is a constant tempo. If
you are manually setting the tempo
key of your metered Span and not using any
tempo_changes
, then the BPM at any time will always be the same. In this case
(using the variable names from the above pseudocode):
start_time
will always be thetime
of the Spanbpm
will always be thetempo
of the Spanstart_beat
will always be 0.0 (since the beat at the start of any metered Span is always 0.0).
With that, every target_time
can be easily converted it to a target_beat
and
vice versa for anywhere in the Span.
Abrupt Tempo Changes
You may want to create sudden dramatic tempo changes that make beat/time calculations more complicated. The main difficulty is that, unlike with a constant tempo, the bpm
, start_time
, and start_beat
values are different every time the tempo changes.
When finding the beats of events in a timeline, you already know the bpm
and
start_time
because they will either be the tempo
and time
of the Span or
of the Tempo Change at or before the second in question. What you do not
know directly is the start_beat
. As with a constant tempo, the beat at the
start of any Span is always 0.0, but in order to know the start_beat
for any Tempo Change, you must calculate the beat at the time
of it. Since you can
only calculate beats at times when the BPM is constant, this means that you must
always start from the beginning of the Span and calculate the beat
for
every Tempo Change before you can do anything else.
Tempo Ramps
The last situation that can happen is gradually changing from one tempo to an other. This is useful for creating smooth transitions between seconds, adding expressive speedups and slowdowns, etc. It does, however, add one more level of complication when calculating beats.
The tempo between any two Tempo Changes (or between the start of the Span and the first Tempo Change) is linearly interpolated between the times of the changes. This means that, unlike with abrupt changes, the BPM at any given time between Tempo Changes is changing.
Here are the basic steps for finding the BPM at any second (in pseudocode):
BPM interpolated_bpm(BPM start_bpm, BPM end_bpm, Second start_time, Second end_time, Second target_time) {
// calculate the change in BPM
delta_bpm = end_bpm - start_bpm
// calculate the change in time
delta_time = end_time - start_time
// calculate the slope of the line
slope = delta_bpm / delta_time
// y = mx+b
target_bpm = slope * target_time + start_bpm
return target_bpm
}
where
start_bpm
is the BPM at or before the time in questionend_bpm
is the next known BPMstart_time
is the time ofstart_bpm
end_time
is the time ofend_bpm
target_time
is the time at which you want to know the BPM
Most of the information needed for these calculations comes from Tempo Changes. The two special cases are:
- the initial values, which are the
tempo
andtime
of the Span - the final values, which are the last BPM in the Span and the
time
of the next Span
The only other question is “how often should the BPM be calculated?” This depends on how accurately you wish to represent the mapping between beats and seconds. Typically 8 times per second is a good balance between accuracy and effort.
Note: Since the constant and abrupt cases are subsets of what is required to support ramping tempo, if you ever plan on supporting all three it may make sense to treat every case as if it contains ramps.
Tempo Memoization
Since converting between beats and seconds always requires knowing the same information, and only altering Tempo Changes has any effect on that, it sometimes makes sense to calculate the arguments required for all other conversions when you receive a timeline and store it for later (at least until receiving a new timeline or altering any Tempo Changes). By storing the (second,beat,BPM) triple from every time the BPM was sampled, you can easily create a lookup for the (second,BPM) at any beat or the (bead,BPM) at any second for all events that altered the tempo, which is everything you need to convert between beats and seconds.