The getSamples method repeatedly calls getSample where the actual samples are generated. getSample returns a double that must be scaled and converted to a short integer value (because of our sample definition in SamplePlayer uses short integers) before being broken into two bytes, which are stored sequentially in the sample buffer. When the buffer is full, the getSamples method returns, which effectively passes the oscillator samples to the next component in the signal chain.
The getSample method is where sample generation takes place. Here a counter is set up. It counts from zero to the number of sample times required for a complete period of the selected wave shape at the selected frequency. Sine wave samples are calculated using the sin math function. A square wave is produced by setting the output to +1.0 for the first half of the waveform period and to -1.0 for the second half. A sawtooth wave shape is produced using characteristics of the floor math function. The wave shape produced is determined by the waveshape variable in conjunction with the switch statement in the code.
BasicOscillator's setFrequency method is called to set the frequency produced by the oscillator. The setWaveshape method is called to set the type of wave shape the oscillator produces.
To hear a BasicOscillator in action it must be coupled with a SamplePlayer as the following code illustrates.
// Create an oscillator sample producer
BasicOscillator osc = new BasicOscillator();
// Set the frequency
osc.setFrequency(500);
// Set the waveashape
osc.setWaveshape(WAVESHAPE.SIN);
// Create a sample player
SamplePlayer player = new SamplePlayer();
// Sets the player's sample provider
player.setSampleProvider(osc);
// Start the player
player.startPlayer();
// Delay so oscillator can be heard
delay(1000 * 4);
// Stop the player
player.stopPlayer();
Remember, SamplerPlayer will pull samples from its sample provider (the BasicOscillator) at the rate it needs for uninterrupted sound production.
The AdvancedOscillator
As functional as the BasicOscillator is, it lacks some features that would make it useful for electronic music. If, however, you combine two BasicOscillator instances and some glue code you end up with a very versatile device. PSynth's oscillator, which has a superset of the AdvancedOscillator functionality, is shown in Figure 1.
AdvancedOscillator features include:
- The ability to set the frequency of the oscillator
- The ability to set the wave shape of the oscillator
- The ability to alter the range of the oscillator independent of the frequency selected
- The ability to detune the oscillator by any number of
cents - Incorporation of a Low Frequency Oscillator (LFO) as the modulation source. Along with the ability to set the LFO's frequency, wave shape and modulation depth.
- The ability to use amplitude modulation (discussed in Part 1 of this series).
- The ability to use frequency modulation (see Part 1).
The AdvancedOscillator's code is too long to be reproduced here so see the Resources section at the end of this article to obtain the code.
The important thing to understand is one of the BasicOscillator instances (call it osc #1) produces the sound that will be heard while the other oscillator (osc #2) functions as an LFO for modulating osc #1.
Implementation wise, AdvancedOscillator extends BasicOscillator for osc #1 functionality and it has a BasicOscillator (lfo) for use as osc #2.
AdvancedOscillator's getSample method shows how osc #2 can be made for amplitude or frequency modulation of osc #1.:
/**
* Return the next sample of the oscillator's waveform
*
* @return Next oscillator sample
*/
protected double getSample() {
double freq = frequency;
// Are we frequency modulating
if ((modulationType == MOD_TYPE.FM) && (modulationDepth != 0.0)) {
double lfoValue = lfo.getSample() * modulationDepth;
freq *= Math.pow(2.0, lfoValue);
}
// Apply frequency multiplier
freq *= rangeMultiplier;
// Apply detuning multiplier
freq *= detuneMultiplier;
// Set frequency of osc
super.setFrequency(freq);
// Get an osc sample
double sample = super.getSample();
// Are we amplitude modulating
if (modulationType == MOD_TYPE.AM) {
double lfoOffset = (lfo.getSample() + 1.0) / 2.0;
double m = 1.0 - (modulationDepth * lfoOffset);
sample *= m;
}
// Return the osc sample
return sample;
}
While examining this code remember that FM modulation varies the frequency produced by osc #1 whereas AM modulation varies the amplitude produced by osc #1.
The Envelope Generator
An envelope generator (EG), which may also be called an ADSR for Attack, Decay, Sustain and Release, generates a time varying control signal used to control some other device. PSynth's envelope generator / amplitude module UI is shown in Figure 2. The EG described here is implemented by the EnvelopeGenerator class. See the javadocs for the complete API. Here, I’ll first describe what an EG does and then describe how it does it.


