- A random selection of 4 notes from the combined pelog and slendro tunings.
JMSL's Tuning class provides for an array of double precision floating point numbers which represent frequencies
in Hertz. Here is the constructor method that realized Son of Lion's tuning.
public LionTuning() {
double[] t = {274.8, 298.8, 316.6, 330.0, 358.4, 368.2, 421.1, 452.4, 482.6, 533.9 };
setPitches(t);
}
Barbara also mentions a 5 cent stretch per octave. The Tuning class provides a method called getFreqOctave() which transposes a given pitch into
a given octave. The LionTuning class overrides this method to provide the 5 cent stretch. It is listed
below. Note the reference to method freqPlusCents, which is a general tool belonging to the Tuning class. It adds the
specified number of cents to the specified pitch and returns the result.
public double getFreqOctave(double pitch, int octaveNumber) {
int cents = octaveNumber * 5;
return Tuning.freqPlusCents(pitch * Math.pow(2.0, octaveNumber), cents);
}
- A randomly selected timbre chosen from 10 presets. All voices use the same timbre for the given section.
The ten timbres should sound distinctly different from each other and should sound "electronic." Because of the multiple
voices, avoid buzzy timbres.
While JMSL is responsible for the piece's scheduling and algorithmic implementation, JSyn delivers the sound. Each timbre
is realized as a JSyn "SynthNote", which is a virtual synthesis circuit with a noteOn() method, so that it can be "played"
within a pitch/loudness/duration paradigm.
To build an instrument, I worked from the bottom up: first designing the raw JSyn Circuit (say, a simple FM pair), then
wrapping that up in a JSyn SynthNote subclass so that it can be played with a noteOn() method, finally wrapping
the SynthNote up inside a JMSL Instrument subclass, which can be scheduled in a JMSL hierarchy, and fired by classes of
a higher organizational level.
This implementation of Yudishthira's Quartet collects ten such instruments in a class called InstrumentFactory, which
instantiates one of these ten Instruments by token (ie calling InstrumentFactory.getInstrument(int index) with an index ranging
from 0..9 will return a newly allocated one of the 10 instruments it knows how to "manufacture").
A few words about each instrument...
- SINE_WAVE_INS
A sine wave with an exponentially decaying amplitude envelope applied to it. Stereo.
- SQUARE_WAVE_INS
A square wave with an exponentially decaying amplitude envelope applied to it. Stereo.
- FILTERED_SAW_BL_INS
A subclass of a SynthNote that is already implemented in JSyn's circuits package (FilteredSawtoothBL),
adding an envelope to its amplitude to gracefully quiet down.
Band limited sawtooth wave table played through a state variable resonant filter.
- Phil Burk
- RING_MOD_BELL
A SynthNote that is already implemented in JSyn's circuits package (RingModBell).
Bell generated by ring modulating two triangle waves.
Ring modulation simply involves multiplying two signals together.
Output = Osc2.output * ( (Osc1.output * ExpLag.output) + ExpLag.output);
Osc1.Frequency = Osc2.frequency * modIndex;
- Phil Burk
- CHOW_BELL
An FM Pair with an exponentially decaying envelope applied to both carrier amp and modulating amp.
The ratio of Carrier Frequency to Modulating Frequency (Fc:Fm) is maintained at 5:7, with a maximum index of modulation of 10.
After Chowning as described in Dodge/Jerse's "Computer Music, Synthesis, Composition, and Performance" (Schirmer Books), page 113.
- CHOW_BRASS
An FM Pair with an ADSR envelope applied to both carrier amp and modulating amp.
Fc:Fm is maintained at 1:1, with a maximum index of modulation of 5.
After Chowning as described in Dodge/Jerse's "Computer Music, Synthesis, Composition, and Performance" (Schirmer Books), page 113.
- CHOW_RANDOM
An FM Pair with the independently randomized ADSR envelopes applied to carrier amp and to modulating amp.
Fc:Fm is randomized when the instrument is instantiated, and maintained throughout. Both Fc and Fm are integers that range from 1..10.
Index of modulation is also randomized when the instrument is instantiated, and maintained throughout. Index is a double that ranges from 1.0 .. 25.0
Note that a quartet of this instrument will have varying timbres, since each player will likely have a different
sounding random instrument. This is a liberty I took when programming Yudishthira, despite the request for timbres
that are invariant from one instrument to another in a given section. But it sounds so wonderful I couldn't help myself.
- CHEBYSHEV_RANDOM
A WaveShaper whose transfer function (a Chebyshev polynomial) is randomized to realize between 4 and 12 harmonics, whose relative amplitudes are
also chosen at random. Randomly generated ADSR envelopes are applied to the sine driving the transfer function, and to the
amplitude of the waveshaper's output. This instrument's sound can vary radically. As with CHOW_RANDOM, I am jumping
outside the strict specification of the piece.
- CHEBYSHEV_ODD_RANDOM
Like CHEBYSHEV_RANDOM, except that only odd harmonics are used.
- CHEB_RING_RANDOM
Like CHEBYSHEV_RANDOM, whose output is ring modulated with a sine oscillator (after Dodge/Jerse, p 142). A Fc:Fm ratio
is randomized when the instrument is instatiated and maintained. Here, Fc refers to the frequency of the sine oscillator
which is multiplied by the shaper, while Fm refers to the frequency of the waveshaper's sine which drives the transfer function.
Again, the sound varies dramatically, with good effect!
- A randomly selected number of players from 1 to 4. These players represent the four octave range of the gamelan
balungen instruments. Also select which of the four voices shall play, ie if there is only one voice, it need not be player #1.
- Player #1 plays continuous 1 second whole notes, octave 1 (slentem)
- Player #2 plays continuous 0.5 second half notes, octave 2 (demung)
- Player #3 plays continuous 0.25 second quarter notes, octave 3 (saron)
- Player #4 plays continuous 0.125 second eighth notes, octave 4 (peking)
Yudishthira's PlayerSelector class is responsible for flagging which of four Players will perform in a particular section.
JMSL's Player class contains an Instrument and musical data (JMSL's MusicShape), for it to perform (ie pitches, loudnesses, durations, etc).
A YudishthiraPlayer is a subclass of Player which adds an Active flag, which is simply true or false, depending on
whether that player shall perform or remain silent. The PlayerSelector select() algorithm accepts a collection of four
YudishthiraPlayer's, writes them into an ordered list, scrambles the list, then loops from 1 to a random number
between 1 and 4, setting each player's flag to true as it loops. Players outside the range of the loop remain inactive.
- Random melody: every note a player hits is randomly chosen from among the four notes that characterize the section.
YudishthiraPlayer also has a unique octave number and a unique speed. These are set when the Yudishthira Collection is
first built. Both speed and octave number are used by the ShapeBuilder class, which is responsible for building each
section's MusicShapes that contain the actual pitches, loudnesses, and durations to be performed. ShapeBuilder is handed a
four-pitch subset of the LionTuning, the total duration of the section, and the loudness of the section. It uses these to
generate the appropriate data, handing a long rest to players that are inactive.
/** Build and deliver a new 3 dimensional (dur, pitch, amp) MusicShape using uniform random processes. An extra dimension
for optional stereo panning is added. */
public MusicShape getShape(double eventDur, int octave, boolean active) {
MusicShape s = new MusicShape(4);
double pitch = 0;
double pan = 0.0;
double[] pitches = subset.getPitches();
if (!active) {
s.add(totalDur, 0.0, 0.0, 0.0);
} else {
for (int i=0; i<(int)(totalDur/eventDur); i++) {
pitch = tuning.getFreqOctave(pitches[JMSLRandom.choose(pitches.length)], octave);
pan = (double)octave/2.0;
s.add(eventDur, pitch, amp, pan);
}
}
return s;
}
- A randomly selected loudness level out of four possible.
The AmpSelector class generates a loudness level at random for a particular section. The amplitude is divided by the total number
of players to keep from clipping.
/** 0..1 divided into discrete dynamic levels. For choices p, mp, mf, f, use numDynamics=4
@return one of these non-zero levels chosen at random */
public static double getRandomDynamic(int numDynamics, int numPlayers) {
return (JMSLRandom.choose(numDynamics) + 1) / (double)(numPlayers*numDynamics);
}
- A randomly selected duration of the sound section, of 16, 32, 64, or 128 seconds.
The SectionBuilder class is responsible for putting all the above elements together, by calling on the appropriate
classes to provide the random data in which they specialize. SectionBuilder implements the PlayFunction interface,
which means it can be plugged into the repeatFunction "slot" of the Yudishthira Parallel Collection. So
every time Yudishthira repeats, it automatically fires a SectionBuilder's play() method which prepares the
next section.
SectionBuilder computes a few values directly, without consulting specially designed classes. One of these values is
the duration of the sound section, which is calculated with the following algorithm:
// choose duration for sounded section: 16, 32, 64, or 128 seconds
double soundDuration = 16 * (double) (1 << JMSLRandom.choose(4)); ;
The values of soundDuration is passed to ShapeBuilder.
- A silence of randomly selected duration of 4, 8, 12, or 16 seconds after each section.
Just as the sound duration is directly calculated by SectionBuilder, so is the duration of its silences.
// choose duration of pause: 4, 8, 12, or 16 seconds
double pauseDuration = 4 * (JMSLRandom.choose(4)+1);