Seeing the audio data
This class prints the sine wave data to the
standard output rather than to an audio file.
View / Download source
Lets have a closer look.
import jm.music.data.*;
import jm.JMC;
import jm.audio.*;
import jm.util.*;
public class WaveformPrintOut implements JMC {
public static void main(String[] args) {
new WaveformPrintOut();
}
public WaveformPrintOut() {
Note n = new Note(C4, QUAVER);
Score score = new Score(new Part(new Phrase(n)));
Instrument printout = new PrintSineInst(44100);
Write.au(score, "WaveformExample.au", printout);
}
}
|
So what's really going on when an audio file
is rendered?
This tutorial sets out to begin answering that question. The class
above prints the sample values out to the screen rather than to a file,
so you can see what's going on.
The results of running the program will look
similar to:
0.2359
0.2167
0.1986
...
-0.0178
-0.0438
...
There are a few of important things to notice
about the data.
- The numbers are floating point values. All audio processing in jMusic
happens as floating point numbers which ensures
that the resolution (quality) is extremely high.
- The numbers are both positive and negative. The audio values centre
around zero with louder values being those most distant from zero.
- The numbers are close to zero in value. Numbers produced by each
note in jMusic are between -1.0 and 1.0 in value and so if three notes
play
at the same time their combined maximum value will be 3.0 or -3.0.
Because the largest floating point values in Java can be in the millions
you can see that there is plenty of head room in the use of floating
point values
(we can have very dense textures without the risk of distortion from
clipping).
How are these values generated?
By whatever maths you like; is the short
answer. In the case of a sine wave the following code segment does the
work.
float increment = (float)(Math.PI*2) / (float)numOfSamples;
float x = (float)0.0;
for(int i=0; i<numOfSamples; i++){
sampleArray[i] = (float)((float)Math.sin(x+((float)2.0*(float)Math.PI)));
x += increment;
}
Notice the use of Math.PI and Math.sin()
which are required to get the characteristic wave-like ascending and
descending values of the sine wave.
float[] sampleArray = new float[numOfSamples];
These values are calculated for a single cycle of the sine wave and
stored in an array established by the line of code above.
This array is a 'table' of values that is then used to render the notes.
The use of a table of values is very widely used in digital sound synthesis
and commonly referred to as WaveTable synthesis.
Wavetable Synthesis
In Java wave tables are usually arrays of values filled with calculated
values (as above) or with values from digital recordings of acoustic
sounds.
Wavetables are of a specified length and notes of different lengths
and pitches read from the table in different ways.
As an oscillator, the wavetable is read over and over from front to
back as many times as required (usually hundreds or thousands of times
per second).
In our sine wave example, one cycle of the sine wave is kept in the
wavetable so the rendering of the note A4 (frequency 440 hz) requires
that the
wavetable be read 440 times for every second of sound.
This diagram shows how a table can be filled
with various values and after it is read from beginning to end reading
starts again at the beginning.
More complicated readings of the wave table can be done. For example
a section of the wavetable can be looped, rather than the whole table.
To play the waveform at higher pitches some samples can be skipped -
for example reading every second sample will raise the playback pitch
an octave.
Further details
For more detailed information on wavetable synthesis read chapter 3
of "The Computer Music Tutorial" by Curtis Roads and chapters
3 and 4 of "Computer Music" by Dodge and Jerse.