Repository navigation
Timing control
"Score time" is the coordinate system in which note durations are originally specified. The unit is a 4/4 measure; e.g., 1/4 is a quarter note.
"Performance time" is the system in which notes are performed. The unit is seconds; i.e. real time.
Each note has a start time and duration (sounding time) in each system:
n.time # score time
n.dur
n.perf_time # performance time
n.perf_durThe computation of note timing involves these steps:
- Make adjustments in score time
- Call
ns.done()(ns is the Score object) - Make adjustments in performance time.
You can vary the tempo of some or all notes in a Score using a PFT.
The value of the PFT is a multiplicative factor that can be thought of as beats per minute.
The PFT segments are typically Linear or ExpCurve.
An additional PFT primitive is available:
from numula.nuance import *
Delta(dt, after=False)This inserts a pause of dt seconds at the current PFT time.
If after is True, the pause occurs after notes that begin at this time.
To adjust the tempo of a set of notes:
ns.tempo_adjust_pft(pft, t0, pred=None, normalize=False, bpm=True)This applies the tempo adjustment defined by the given PFT, starting at score time t0, to the selected notes. If no selector is given, the adjustment is also applied to pedal events during the domain of the PFT.
The value of the PFT is in units of beats per minute.
This is typically used to set the overall (time-varying) tempo of a piece. Additional fluctuations can be layered on top of this. If "normalize" is True, the adjustment is scaled so that the adjusted notes synch up with other notes at the end of the period. This can be used, for example, to apply rubato to right hand notes without modifying the left hand.
The same units (beats per minute) are used in specifying these additional fluctuations, but their meaning is different: 120 means speed up by a factor of two, 30 means slow down by a factor of two.
Example:
ns.tempo_adjust_pft(
[
Linear(60, 120, 4/4),
Delta(.1),
Linear(120, 60, 4/4)
], normalize=True,
pred=lambda n: 'rh' in n.tags
)causes the right hand notes (tagged with 'rh') to speed up and slow down over 2 measures, with a slight pause in the middle, synching up with other notes at the end.
As an example, consider the following from Chopin's 1st Nocturne:

We can use Numula to play the 11 against 6 precisely, but that sounds robotic.
Instead, we use tempo_adjust_pft() with normalize=True to speed up and then slow down the RH notes,
while not changine the LH.
The source code is here;
the audio result is here.
ns.pause_before(t, dt, connect=True)Add a pause of dt seconds before score time t.
If connect is true,
earlier notes that end at or after t are elongated;
e.g. legato is preserved.
ns.pause_after(t, dt)Add a pause of dt seconds after score time t.
Notes that start at t are elongated.
ns.pause_before_list(ts, dts)ts is a list times, and dts is a same-sized list of gap durations.
Insert gaps of those durations before those times.
This is the same as a sequence of pause_before(... connect=False calls,
but it's more efficient because the score is traversed just once.
ns.roll(t, offsets, is_up=True, is_delay=False)offsets is a list of time offsets (typically negative).
These offsets are added to the performance start times of notes that start at score time t.
If is_up is true, they are applied from bottom pitch upwards;
otherwise from top pitch downward.
If is_delay is True, the range of the offsets is added to
subsequent notes, so that the roll adds a delay.
You can use the NumPy linspace() function to generate evenly-spaced lists, e.g.
import numpy as np
ns.roll(t, np.linspace(-.5, .1, 6))does a roll with 6 offsets ranging from -.5 to .1.
ns.t_adjust_list(offsets, selector)offsets is a list of time offsets (seconds).
They are added to the start times of notes satisfying the selector, in time order.
ns.t_adjust_notes(offset, selector)The given time offset (seconds) is added to the start times of all notes satisfying the selector.
ns.t_adjust_func(func, selector):For each note satisfying the selector, the given function is called with that note, and the result is added to the note's start time.
ns.t_random_uniform(min, max, pred=None):
ns.t_random_normal(stddev, max_sigma=2, pred=None):These functions change the start time of selected notes by a random amount; the end time is not changed.
For t_random_uniform(), the offset is chosen from a uniform distribution between min and max.
For t_random_normal(), the offset is chosen from a normal distribution with mean zero
and the given standard deviation. Offsets with sigma > max_sigma are not used.
If you use textual notation to specify a score, each note's duration is the time until the next note. In other words, the default is perfect legato. You can add articulation - staccato, portamento, etc. - by adjusting note durations, in either score or performance time.
The following functions let you change note durations:
ns.perf_dur_abs(dur, selector)
ns.perf_dur_rel(factor, selector)perf_dur_abs() sets the duration of the selected notes to the given value;
pref_dur_rel() multiplies the duration by the given factor.
ns.perf_dur_func(func, selector)This calls the given function to compute the duration based on note attributes.
You can adjust the articulation of a set of notes using a PFT.
ns.perf_dur_pft(pft, t0, selector=None, rel=True)-
pftis a PFT that describes time-varying articulation according torel -
t0: the score time when the adjustment begins -
selector: an optional selector -
rel: if True, the duration of a note at time T is multiplied by the value of the PFT at T. If False, the duration is set to the value of the PFT.
For example:
ns.perf_dur_pft([
Linear(.1, 1.2, 4/4)
], 0, lambda n: 'rh' in n.tags
)varies the articulation of right-hand notes linearly from staccato to legato over a 4/4 measure, starting at the beginning of the score.
The above primitives adjust performance time. You can adjust note durations in score time using:
ns.score_dur_abs(dur, selector)
ns.score_dur_rel(factor, selector)score_dur_abs() sets the duration of the selected notes to the given value;
score_dur_rel() multiplies the duration by the given factor.
ns.score_dur_func(func, selector)This calls the given function to compute the duration based on note attributes. For example:
ns.score_dur_func(lambda n: n.dur-1/16, lambda n: n.dur>1/8)shortens all notes longer than an eighth by a sixteenth; i.e. it leaves a small gap until the next note.