Java Sound – Make A Noise

The JDK has a audio integration as standard – but for some reason it is ill used and poorly documented.

When I started coding Sonic Field it was silent. Yes, it could synthesis audio signals and processes recordings; however, playing an arbitrary piece of audio with javax.sound was beyond me and even Google did not seem able to help. Well, I did eventually figure it out and so I hope this post can help others avoid my pain to come to bring Java to audio and the other way around.
The first step is to get the javax.sound api imported. For the example here, the imports are:


import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

That was the easy bit. The hard bit is figuring out what to do with it. The api was designed around using pre-cooked audio sources and performing effects and mixes on them. What is really hard to figure out is how to play audio you have made programmatically. However, it is possible using the AudioFormat class. This is able to understand some simple formats and make audio sources from byte arrays holding them. For audio output 16 bit signed linear encoding is really simple and good enough for listening to. So, in the example below I show how to code up 16 bit signal and play it on the default output device.


// Please note that this code is under AGPL 3.0
// within Sonic Field.

// I have relaxed this snippet and this snippet only
// to the CC license for the Java Advent Calendar project.

// The key thing to understand is the AudioFormat.
// This is actually the
// class which understands the binary format of
// audio which the computer cna
// use to make sound. Here we are specifying
// sixteen bit signed audio
AudioFormat af = new AudioFormat((float) SFConstants.SAMPLE_RATE, 16, 1, true, true);
// SFConstants,SAMPLE_RATE is in
// samples per second - it is part of Sonic Field
// here it is 96000, which is a good choice.
// 44100 is CD rate as an alternative.

// These two lines link the AudioFormat class
// to the default output device for
// the computer. It is possible choose between
// different audio output devices
// but that is for another post.
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);
source.open(af);
source.start();
// OK dataIn is a Sonic Field type.
// However, it is just a wrapped float array.
// what we need to do is convert a series of
// floats into the 16 bit signed format
// we specified. javax.sound does not really
// help us much here. We have to
// do the conversion out selved. Fortunately, it is easy.
byte[] buf = new byte[dataIn.getLength() * 2];
for (int i = 0; i < buf.length; ++i)
{
short sample = (short) (dataIn.getSample(i / 2) * 32767.0);
buf[i] = (byte) (sample >> 8);
buf[++i] = (byte) (sample & 0xFF);
}
// Now we have a byte array with
// 16 bit signed audio, we can just play it.
// This is done via the 'write' method on our SourceDataLine
source.write(buf, 0, buf.length);
// The source plays asynchronously using buffering.
// If we want to know when it
// has finished we need to call the drain method
// which only returns when all current audio as played
source.drain();
// Finally we can shut everything down
source.stop();
source.close();

I hope that the comments above help explain what is happening. I noticed that they do make reading the code a little tricky, so here is the code without the comments.

AudioFormat af = new AudioFormat((float) SFConstants.SAMPLE_RATE, 16, 1, true, true);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);
source.open(af);
source.start();
byte[] buf = new byte[dataIn.getLength() * 2];
for (int i = 0; i < buf.length; ++i)
{
short sample = (short) (dataIn.getSample(i / 2) * 32767.0);
buf[i] = (byte) (sample >> 8);
buf[++i] = (byte) (sample & 0xFF);
}
source.write(buf, 0, buf.length);
source.drain();
source.stop();
source.close();

Sonic Field is an open source (AGPL) audio processing and synthesis system written in pure Java (no JNI, only standard Java). For more information on the project please feel free to check out the links below. The source code and associated sites will provide a large amount of useful information on audio and Java.

By: Dr Alexander J Turner: I would like to thank Attila for contacting me and organising this interesting project. Feel free to pop over to my blog at Nerds-Central at any time 🙂
Meta: this post is part of the Java Advent Calendar and is licensed under the Creative Commons 3.0 Attribution license. If you like it, please spread the word by sharing, tweeting, FB, G+ and so on! Want to write for the blog? We are looking for contributors to fill all 24 slot and would love to have your contribution! Contact Attila Balazs to contribute!