Layering Sounds
This tutorial will demonstrate how waveforms of different
types can be added together at the instrument level.
Hear the result of this tutorial's music below.
BreathyFluteTest.au
[731K]
View / Download
source - Instrument source code
View / Download
source - Composition source code
Let's have a closer look.
import jm.audio.io.*; import jm.audio.synth.*; import jm.music.data.Note; import jm.audio.AudioObject; public final class BreathyFluteInst extends jm.audio.Instrument{ private EnvPoint[] pointArray = new EnvPoint[10]; private EnvPoint[] pointArray2 = new EnvPoint[10]; private int channels; private int sampleRate; |
The import statements are giving us access to all the other classes
that we need to construct this instrument.
The attributes or "fields", that are declared here are used later on
for various purposes.
You can see what the purpose is quite easily, because they are
well commented.
public BreathyFluteInst(int sampleRate){ this(sampleRate, 1); } public BreathyFluteInst(int sampleRate, int channels){ this.sampleRate = sampleRate; this.channels = channels; EnvPoint[] tempArray = { new EnvPoint((float)0.0, (float)0.0), new EnvPoint((float)0.2, (float)1.0), new EnvPoint((float)0.5, (float)0.7), new EnvPoint((float)0.8, (float)0.5), new EnvPoint((float)1.0, (float)0.0) }; pointArray = tempArray; EnvPoint[] tempArray2 = { new EnvPoint((float)0.0, (float)0.0), new EnvPoint((float)0.05, (float)0.7), new EnvPoint((float)0.2, (float)0.1), new EnvPoint((float)1.0, (float)0.0) }; pointArray2 = tempArray2; } |
This is an example of "overloaded" constructors. It means that
you can create a BreathyFluteInst object in two different ways:
new BreathyFluteInst( **desired sample
rate**)
or
new BreathyFluteInst(**desired sample rate**,
**desired channel**)
In the first method, there is no way of specifying the number of channels,
so a default value of 1 is used.
Andrew has done a little trick that enables code to be reused
rather than copied.
You'll note that the first constructor method is very short, in
fact it only has one line.
What it does is call the second constructor method, passing the
information that was given to it as well as extra default information
(channel = 1).
Remember that from outside, the command
new BreathyFluteInst(,)
is effectively a call to the constructor method of class.
So if you are
inside the class, it makes sense that the
command
this(,)
calls the same constructor. (the only difference
is that
this(,)
doesn't return a new instance of the class)
public void createChain(){ Oscillator wt = new Oscillator(this, Oscillator.SINE_WAVE, this.sampleRate, channels);
Volume vol = new Volume(wt, (float)1.0); Oscillator wt2 = new Oscillator(this, Oscillator.SINE_WAVE, this.sampleRate, channels); wt2.setFrqRatio((float)2.001); Volume vol2 = new Volume(wt2, (float)0.5); Oscillator wt3 = new Oscillator(this, Oscillator.SINE_WAVE, this.sampleRate, channels); wt3.setFrqRatio((float)4.0); Volume vol3 = new Volume(wt3, (float)0.1); Noise noise = new Noise(this, Noise.WHITE_NOISE, this.sampleRate ); Volume vol4 = new Volume(noise, (float)0.2); AudioObject[] parts = {vol, vol2, vol3, vol4}; Add add = new Add(parts); Envelope env = new Envelope(add, pointArray); StereoPan span = new StereoPan(env); SampleOut sout = new SampleOut(span); } } |
You should know by now that createChain is the fun method.
This part will explain how to have multilayered fun!
This time, a slightly different approach is being used.
There are four different sounds, and rather than linking them
sequentially they are run in parallel.
Each of them (the sounds) take "this"
as
their first argument.
As you know, the first argument into an AudioObject has to be
another AudioObject that feeds into it.
You also know that feeding the AudioObject "this"
,
signifies it to be at the start of the audio chain.
Looking at the above code, you might be able to see that
all of the sounds are at
the start of the audio chain.
This means that they will be played in parallel.
To mix them together into the same audio line, the special
AudioObject Add is used.
Add takes an array of AudioObjects.
As you can see it takes all of the AudioObjects that were running
in parallel above.
Some stereo panning is done, and then signal is sent out
to sample output.
import jm.JMC; import jm.music.data.*; import jm.audio.*; import jm.util.*; public final class BreathyFluteTest implements JMC{ public static void main(String[] args){ Score score = new Score("JMDemo - Audio test"); Part part = new Part("wave", 0); Phrase phr = new Phrase(0.0); int sampleRate = 44100; Instrument inst = new BreathyFluteInst(sampleRate); Note rootNote = new Note(60, 0.25, (int)(Math.random() * 70 + 40)); for (int i = 1; i < 24; i++) { Note note = new Note(i + 60, 0.25, (int)(Math.random() * 70 + 40)); if (note.isScale(MINOR_SCALE)) { phr.addNote(rootNote); phr.addNote(note); } } Note lastNote = new Note(84, 2.0, (int)(Math.random() * 70 + 40)); phr.addNote(lastNote); part.addPhrase(phr); score.addPart(part); Write.au(score, "BreathyFluteTest.au", inst); } }
|
This class uses the instrument that we dissected above to play a simple
stochastic melody. For more info on algorithmic composition click
here.