This article will explain how to generate basic sound in Java using javax.sound.sampled package. We will use it further to generate ‘Jingle Bell’ music.
Java and sound
Java package javax.sound.sampled provides facility to record & playback of sound by interacting with audio devices of system. For this article, we will use
- javax.sound.sampled.AudioFormat to specify audio format with common digital sampling rate 44,100_Hz
- javax.sound.sampled.SourceDataLine for playing back audio in specified format to the audio device.
Basic sound generation
In simplest way, sound can be visualized in format of sine wave of different amplitudes. We can use this simplest idea to create a basic sound in Java.
- We can iterate through angles 0 to 360 & take sin(angle) & pass it to javax.sound.sampled library to generate basic sound.
- Just to make sound audible, we will multiply sin(angle) by 1000 so that audio becomes audible.
- Since single iteration from 0 to 360 will be very brief to hear properly, we will do this multiple times using another iteration to have 100 cycles of above iteration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; public class BasicSound { public static void main(String[] args) throws LineUnavailableException, InterruptedException { byte[] buf = new byte[1]; AudioFormat af = new AudioFormat((float) 44100, 8, 1, true, false); SourceDataLine sdl = AudioSystem.getSourceDataLine(af); sdl.open(); sdl.start(); for (int cycle = 0; cycle < 100; cycle++) { for (int angle = 0; angle < 360; angle++) { buf[0] = (byte) (Math.sin(angle) * 1000); sdl.write(buf, 0, 1); } } sdl.drain(); sdl.stop(); } } |
Sound properties
Above program creates one random sound. But different sounds have different properties which are used to generate specific sounds. Here is quick description of those properties.
- Frequency: Different sounds are identified using the frequency of its wave. This is measured in cycles per seconds or Hertz i.e. “cycles per seconds”.
- Angular frequency: If frequency is measured in circular rotation rate format, it is noted in angular frequency i.e. angles passed in given time. Angular frequency = Normal Frequency * 2 * PI & unit is “radians per second”
In digital world, the sound units are not measured against time like above definitions. In digital world frequencies need to be converted to “per sample” instead of “per seconds”. This is done using something called “Sampling rate” which is “samples per second” & resulting frequency is called “Normalized frequency”
- Normalized frequency [aka digital frequency]:
- Normalized frequency = Frequency / Sample rate
- Measured in “cycles per sample”
- We use most common sample rate which is 44,100_Hz in our examples.
- Normalized Angular frequency
- Normalized angular frequency = (Angular frequency / Sample rate) * 2 * PI
- Measured in “radians per sample”
So using these properties & conversion mechanism we modify earlier program to generate a musical note C in octave 5 which has normal frequency 523 Hz. We will convert this frequency into digital frequency using above formulas & generate sound.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package com.itsallbinary.audio; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.SourceDataLine; public class GenerateMusicNote { /** * Samples per seconds */ private static float SAMPLE_RATE = 44100; private static int MILLSECONDS = 500; private static int VOLUME = 20; public static void main(String[] args) throws Exception { AudioFormat af = new AudioFormat(SAMPLE_RATE, 8, 1, true, false); SourceDataLine sdl = AudioSystem.getSourceDataLine(af); sdl.open(af); sdl.start(); byte[] buf = new byte[1]; int analogFrequency = 523; double normalizedFrequency = (analogFrequency / SAMPLE_RATE) * 2.0 * Math.PI; /* * samples per second / 1000 = samples per milliseconds * * numberOfSamples = sample per milliseconds * milliseconds */ float numberOfSamples = (SAMPLE_RATE / 1000) * MILLSECONDS; for (int i = 0; i < numberOfSamples; i++) { double angleForThisSample = i * normalizedFrequency; buf[0] = (byte) (Math.sin(angleForThisSample) * VOLUME); sdl.write(buf, 0, 1); } sdl.drain(); sdl.stop(); sdl.close(); } } |
Musical notes representation.
As you might know, music is comprised of different musical notes. Each musical note has a specific frequency. Refer music note frequencies here @ Table of Musical Notes Frequencies.
Lets create an enum to represent each note & its frequencies in different octaves as shown below. PAUSE is not a musical note, but added it to give a gap between music notes in simpler way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
enum MusicNote { C(new int[] { 16, 32, 65, 130, 261, 523, 1046, 2093, 4186, 8372 }), D(new int[] { 18, 36, 73, 146, 293, 587, 1174, 2349, 4698, 9397 }), E(new int[] { 20, 41, 82, 164, 329, 659, 1318, 2637, 5274, 10548 }), F(new int[] { 21, 43, 87, 174, 349, 698, 1396, 2793, 5587, 11175 }), G(new int[] { 24, 48, 97, 195, 391, 783, 1567, 3135, 6271, 12543 }), A(new int[] { 27, 55, 110, 220, 440, 880, 1760, 3520, 7040, 14080 }), B(new int[] { 30, 61, 123, 246, 493, 987, 1975, 3951, 7902, 15804 }), PAUSE(new int[] {}); private int[] analogFrequencies; private MusicNote(int[] frequencies) { this.analogFrequencies = frequencies; } public int analogFrequency(int octave) { return analogFrequencies[octave]; } } |
Now we will modify earlier program to take Musical Note as input, do conversions & play musical note sound. We will also add pause facility. This is how it will look now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public static void generateMusicalNotes(MusicNote... notes) throws Exception { for (MusicNote note : notes) { if (PAUSE.equals(note)) { Thread.sleep(500); } else { generateMusicalNote(note.analogFrequency(OCTAVE)); } } } public static void generateMusicalNote(int analogFrequency) throws LineUnavailableException { byte[] buf = new byte[1]; double normalizedFrequency = (analogFrequency / SAMPLE_RATE) * 2.0 * Math.PI; /* * samples per second / 1000 = samples per milliseconds * * numberOfSamples = sample per milliseconds * milliseconds */ float numberOfSamples = (SAMPLE_RATE / 1000) * MILLSECONDS; for (int i = 0; i < numberOfSamples; i++) { double angleForThisSample = i * normalizedFrequency; buf[0] = (byte) (Math.sin(angleForThisSample) * VOLUME); sdl.write(buf, 0, 1); } sdl.drain(); } |
Jingle Bell
Now that we have program ready to play musical notes, let’s compose track for “Jingle Bell” using musical notes. Musical notes for jingle bell can be found in several musical websites. Here is one with due credit & appreciation.
Now we just put all these notes with proper pauses in method calls to generateMusicalNotes in a java main method then run program & play the track !! Now you can enjoy Jingle bell track in basic sounds generated through Java.
You can adjust OCTAVE, VOLUME, MILLSECONDS to try different varieties. Or even better, you can add your own musical notes & enjoy the music !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private static float SAMPLE_RATE = 44100; private static int OCTAVE = 5; private static int MILLSECONDS = 500; private static int VOLUME = 20; private static SourceDataLine sdl; public static void main(String[] args) throws Exception { AudioFormat af = new AudioFormat(SAMPLE_RATE, 8, 1, true, false); sdl = AudioSystem.getSourceDataLine(af); sdl.open(af); sdl.start(); generateMusicalNotes(E, E, E, PAUSE, E, E, E, PAUSE, E, G, C, D, E, PAUSE); generateMusicalNotes(F, F, F, F, F, E, E, E, E, D, D, E, D, G); generateMusicalNotes(E, E, E, PAUSE, E, E, E, PAUSE, E, G, C, D, E, PAUSE); generateMusicalNotes(F, F, F, F, F, E, E, E, E, D, D, E, D, G); generateMusicalNotes(G, G, F, D, C); sdl.stop(); sdl.close(); } |
You can find complete source code here JingleBell.java. MERRY CHRISTMAS !!!!