This tutorial demonstrates how
the callback timing can be varied at runtime. In this example the score
callback is varied at random between 0.5 and 1.0 beats in length. On
each callback a note is played and piano-roll score display updated.
Pitch is selected using the pitch class (pc) utilites and constants
from SoundCipher.
The score reflects the pitch, dynamic, and duration attributes of the
note.
If the pitch goes very low on some sounds they may be hard to hear. The
pcGaussianWalk() method does constrain pitches between 0 - 127 and so
eventually the pitch will return into the soundable range for the
instrument. Also, the default Processing sketch area size is 100 pixels
square so the extreme pitches may be drawn outside the window.
The first three lines import SoundCipher then create an instance of the
SoundCipher and SCScore classes. The sc instance will be used to play
notes and access constants. The score instance will be used to control
the timing, the score is repeated indefinitely while its length is
dynamically
varied.
Following this, a number of class variables are declared to control
various parameters of the music and timing. Varying these settings and
re-running is a good way of experimenting with what they do in the
program.
In the setup() method, Processing's draw loop is halted using noLoop().
The drawing area background is cleared and the instrument (MIDI program
change) for the sc instance is set using one of the SoundCipher
constants - notice that SoundCipher constants are identifiable
because they appear in UPPER CASE. The score's tempo is set and
callbacks are registered, which requires that a handleCallbacks()
method is written - see description below. The score is started with
the -1 argument indicating that it should loop indenfinitely. and the
draw()
method is called once using redraw().
The draw() method checks first to see if the score drawing area needs
to be refreshed. Then, using stroke(), it sets the drawing colour to be
a shade of grey matching the note's dynamic value. A line is drawn
reflecting the note attributes of pitch (height), dynamic
(color), and duration (length). A note is played by SoundCipher based
on the same variables.
The handleCallbacks() method is called each time a callback event
occours during the score playback. In this case there is just one
callback in
the score - in fact the callback is the only object in the score, thus
it is the last object in the score and so is used to indicate the end
of the score. This method calls the draw() method - adding a
line to the score and playing a note - then calulates the new pitch,
dynamic
and duration values for the next note. Finally, it resets the score
values for
its next loop, adding a callback event to correspond with the newly
calulated
duration.
Pitch calculation uses random and gaussian walks from the SoundCipher
Unitilites class so that some reasonable melodic contour is
established. The gaussian walk allows for larger leaps from time to
time while the random walk produces a more consistent meandering. The
pitch selection is taken from different pitch class sets (a
scale for most notes and a triad for notes on the downbeat). The
downbeat is calulated using modulo - beatCount%4.
Duration values ensure there is always a 1 beat note on the downbeat
(to provide it with some emphasis) and otherwise a probabilistic choice
between 1 and 0.5 beat values.
The dynamic value of the notes is controlled by a cosine function that
uses the beatCount variable to ensure that the cosine frequency
matches the musical beats. This results in a pulsing dynamic over a two
beat cycle. This is a pretty useful technique for adding expression to
the music.
This program may seem somewhat convoluted, but is the basis for an
important callback programming pattern that is very useful for realtime
generative media art where music and image need to be synchronised, but
time needs to be in musically sensible units, such as beats and tempo.
See the DrawNotes
tutorial for a simpler version of this method that uses Processing's
draw() loop to detemine tempo.
|