rsc3/doc-schelp/HelpSource/Classes/Quant.scrbl

143 lines
5.8 KiB
Text
Raw Normal View History

2022-08-24 13:53:18 +00:00
#lang scribble/manual
@(require (for-label racket))
@title{Quant}
@section{categories}
Scheduling
encapsulate quantization issues associated with EventStreamPlayer and TempoClock
@section{description}
Represents the standard scheduling model for Routines, Tasks and Patterns. A Quant object stores the parameters needed to calculate the precise time when a Routine/Task/Pattern will start playing on a specified TempoClock.
The standard scheduling model uses quant and phase to locate the starting time. They are evaluated with reference to the TempoClock's baseBarBeat, which is normally zero but is updated when you change the clock's meter using the clock's setMeterAtBeat method. Thus scheduling still makes sense even after a meter change. See the link::Classes/TempoClock:: help file for details on its representation of time.
@section{CLASSMETHODS}
@section{method}
new
Explicitly create an instance of Quant, which may be used and reused. Phase and offset may be nil, in which case they are treated as 0. If quant is nil, it will schedule for the current time exactly.
@section{INSTANCEMETHODS}
@section{method}
quant
Quantization granularity. The routine will begin on the next integer multiple of this number after the baseBarBeat. If negative, it indicates the number of bars in the future to schedule (where the bar length is taken from the clock's beatsPerBar variable).
@section{method}
phase
An offset to push the scheduling time into the middle of the bar. +1 is one beat later, -1 is one beat earlier. A negative phase is legal, but it might result in a scheduling time that is later than the current time, in which case scheduling will be incorrect. It's your responsibility to take this into account.
@section{method}
timingOffset
For use with patterns only -- this enables patterns to run slightly ahead of their sounding time on the clock, giving you control over the order in which threads execute.
@section{EXAMPLES}
@section{definitionlist}
## quant = 1 || schedule for the next whole beat
## quant = 4, phase = -1 || a one beat pick-up to the next 4/4 barline
::
Suppose the clock's meter was 3/4 for 3 bars (starting at 0). Then:
@section{definitionlist}
## quant = 3, phase = 1 || would schedule for 1, 4, or 7 beats
::
During this time, clock.setMeterAtBeat(4, 9) is executed. Then:
@section{definitionlist}
## quant = 4, phase = 0 || would schedule for 9, 13, 17, 21 etc. beats
::
Every point in time can be precisely identified this way, and it can be related back easily to the Western concept of meter or time signature.
@section{subsection}
Automatic instantiation
Certain objects convert themselves into Quant objects when used with link::Classes/Routine#-play::, link::Classes/Task#-play:: or link::Classes/Pattern#-play::.
@section{definitionlist}
## link::Classes/SimpleNumber::
||
@racketblock[ 4.0 --> Quant(4.0, nil, nil) ::
## link::Classes/Array:: ||
]
@racketblock[ [4.0, 1.0] --> Quant(4.0, 1.0, nil) ::
]
@racketblock[ [4.0, 1.0, 0.1] --> Quant(4.0, 1.0, 0.1) ::
## link::Classes/Nil::
|| ]
@racketblock[ nil --> Quant(nil, nil, nil) ::
::
This simplifies the syntax:
]
@racketblock[
Routine({ ... }).play(quant: 4.0):: vs. ]
@racketblock[Routine({ ... }).play(quant: Quant(4.0))
::
]
@section{subsection}
Timing offset in Patterns
In some cases, you might want two patterns that are sounding at the same time to evaluate in a specific order -- for instance, the second pattern might depend upon data calculated by the first. If they are scheduled on the clock for exactly the same time, you have no control over the order of execution: the second pattern might evaluate first, in which case it would be using stale data for the pattern that should have run first.
The timing offset is a positive number, usually small, that pushes the scheduling time slightly earlier, guaranteeing that patterns with larger timing offsets will execute earlier than others. The timing offset value is saved in the event prototype, which then delays its messages to the server by exactly that number of beats.
Two patterns, scheduled for the same quant and phase but with different timing offsets, should sound exactly together.
@racketblock[
(
// timing offset = 0
p = Pbind(\freq, 440, \pan, -1, \delta, 1.0, \sustain, 0.1).play(quant: [2, 0, 0]);
// timing offest = 0.1
q = Pbind(\freq, 880, \pan, 1, \delta, 0.5, \sustain, 0.1).play(quant: [2, 0, 0.1]);
)
// p's nextBeat is x.0 - q's is x.4 or x.9 (e.g., halves of a beat minus 0.1)
[p.nextBeat, q.nextBeat]
p.stop; q.stop;
::
]
@section{subsection}
Extensibility: adding custom scheduling models
While the standard scheduling model should be sufficient for most uses, the point of using an object to encapsulate scheduling details is that you can use a different object to schedule Routines or Patterns differently. (Users are not forced to use the standard scheduling model in every case.)
If it's a kind of scheduling you expect to use often, you can create a subclass of Quant that implements the following methods:
*new(...): create a new instance, with whatever arguments you need
nextTimeOnGrid(clock): calculate the exact beat number on the clock
Your class should also have methods asQuant, offset and offset_. If your class is a subclass of Quant, it will inherit those methods automatically.
You can also use an Event for one shot scheduling. It should at least have an entry for nextTimeOnGrid, which will usually be a function taking the arguments "self" and "clock" that returns the absolute scheduling time. Any other values needed for that calculation should also be present in the Event.
@racketblock[
// schedule for a random number of beats after the next integer
Pfuncn({ thisThread.clock.beats.debug("scheduled for"); nil }, 1)
.play(quant: (
nextTimeOnGrid: { |self, clock|
clock.beats.roundUp(1).debug("clock beats") + rrand(self.lo, self.hi).debug("rand")
},
lo: 0, hi: 4
));
::
]