Sampled Audio SynthesisSampled audio is encoded as a series of samples in a byte array, which is sent through a SourceDataLine to the mixer. In previous examples, the contents of the byte array came from an audio file though you saw that audio effects can manipulate and even add to the array. In sampled audio synthesis, the application generates the byte array data without requiring any audio input. Potentially, any sound can be generated at runtime. Audio is a mix of sine waves, each one representing a tone or a note. A pure note is a single sine wave with a fixed amplitude and frequency (or pitch). Frequency can be defined as the number of sine waves that pass a given point in a second. The higher the frequency, the higher the note's pitch; the higher the amplitude, the louder the note. Before I go further, it helps to introduce the usual naming scheme for notes; it's easier to talk about note names than note frequencies. Note NamesNotes names are derived from the piano keyboard, which has a mix of black and white keys, shown in Figure 10-1. Keys are grouped into octaves, each octave consisting of 12 consecutive white and black keys. The white keys are labeled with the letters A to G and an octave number. Figure 10-1. Part of the piano keyboardFor example, the note named C4 is the white key closest to the center of the keyboard, often referred to as middle C. The 4 means that the key is in the fourth octave, counting from the left of the keyboard. A black key is labeled with the letter of the preceding white key and a sharp (#). For instance, the black key following C4 is known as C#4.
Figure 10-2 shows the keyboard fragment of Figure 10-1 again but labeled with note names. I've assumed that the first white key is C4. Figure 10-2. Piano keyboard with note namesFigure 10-2 utilizes the C Major scale, where the letters appear in the order C, D, E, F, G, A, and B.
After B4, the fifth octave begins, starting with C5 and repeating the same sequence as in the fourth octave. Before C4 is the third octave, which ends with B3. Having introduced the names of these notes, it's possible to start talking about their associated frequencies or pitches. Table 10-1 gives the approximate frequencies for the C4 Major scale (the notes from C4 to B4).
When I move to the next octave, the frequencies double for all the notes; for instance, C5 will be 523.26 Hz. The preceding octave contains frequencies that are halved, so C3 will be 130.82 Hz. A table showing all piano note names and their frequencies can be found at http://www.phys.unsw.edu.au/tildjw/notes.html. It includes the corresponding MIDI numbers, which I consider later in this chapter. Playing a NoteA note can be played by generating its associated frequency and providing an amplitude for loudness. But how can this approach be implemented in terms of a byte array suitable for a SourceDataLine? A pure note is a single sine wave, with a specified amplitude and frequency, and this sine wave can be represented by a series of samples stored in a byte array. The idea is shown in Figure 10-3. This is a simple form of analog-to-digital conversion. So, how is the frequency converted into a given number of samples, i.e., how many lines should the sample contain? Figure 10-3. From single note to samplesA SourceDataLine is set up to accept a specified audio format, which includes a sample rate. For example, a sample rate of 21,000 causes 21,000 samples to reach the mixer every second. The frequency of a note, e.g., 300 Hz, means that 300 copies of that note will reach the mixer per second. The number of samples required to represent a single note is one of the following
For the previous example, a single note would need 21,000/300 = 70 samples. In other words, the sine wave must consist of 70 samples. This approach is implemented in sendNote( ) in the NotesSynth.java application, which is explained next. Synthesizing NotesNotesSynth generates simple sounds at runtime without playing a clip. The current version outputs an increasing pitch sequence, repeated nine times, each time increasing a bit faster and with decreasing volume.
Here is the main( ) method:
createOutput( ) opens a SourceDataLine that accepts stereo, signed PCM audio, utilizing 16 bits per sample in little-endian format. Consequently, 4 bytes must be used for each sample:
play( ) creates a buffer large enough for the samples, plays the pitch sequence using sendNote( ), and then closes the line:
maxSize must be big enough to store the largest number of samples for a generated note, which occurs when the note frequency is the smallest. Therefore, the MIN_FREQ value (250 Hz) is divided into SAMPLE_RATE. Creating samplessendNote( ) translates a frequency and amplitude into a series of samples representing that note's sine wave. The samples are stored in a byte array and sent along the SourceDataLine to the mixer:
numSamplesInWave is obtained by using the calculation described above, which is to divide the note frequency into the sample rate. A sine wave value is obtained with Math.sin( ) and split into two bytes since 16-bit samples are being used. The little-endian format determines that the low-order byte is stored first, followed by the high-order one. Stereo means that I must supply two bytes for the left speaker, and two for the right; in my case, the data are the same for both. Extending NotesSynthA nice addition to NotesSynth would be to allow the user to specify notes with note names (e.g., C4, F#6), and translate them into frequencies before calling sendNote( ). Additionally, play( ) is hardwired to output the same tones every time it's executed. It would be easy to have it read a notes files, perhaps written using note names, to play different tunes. Another important missing element is timing. Each note is played immediately after the previous note. It would be better to permit periods of silence as well.
|
Monday, January 4, 2010
Sampled Audio Synthesis
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment