Does java have built in libraries for audio _synthesis_?
Note: I do NOT want to "read audio file foo.bar and play it."
I want to programmatically generate audio files on the fly and play them.
Does Java have built in libraries for this, or does this fall into the system-dependent libraries?
Thanks!
Using Andrew's approach, here's an example that plays an equal tempered scale.
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class Tone {
public static void main(String[] args) throws LineUnavailableException {
final AudioFormat af =
new AudioFormat(Note.SAMPLE_RATE, 8, 1, true, true);
SourceDataLine line = AudioSystem.getSourceDataLine(af);
line.open(af, Note.SAMPLE_RATE);
line.start();
for (Note n : Note.values()) {
play(line, n, 500);
play(line, Note.REST, 10);
}
line.drain();
line.close();
}
private static void play(SourceDataLine line, Note note, int ms) {
ms = Math.min(ms, Note.SECONDS * 1000);
int length = Note.SAMPLE_RATE * ms / 1000;
int count = line.write(note.data(), 0, length);
}
}
enum Note {
REST, A4, A4$, B4, C4, C4$, D4, D4$, E4, F4, F4$, G4, G4$, A5;
public static final int SAMPLE_RATE = 16 * 1024; // ~16KHz
public static final int SECONDS = 2;
private byte[] sin = new byte[SECONDS * SAMPLE_RATE];
Note() {
int n = this.ordinal();
if (n > 0) {
double exp = ((double) n - 1) / 12d;
double f = 440d * Math.pow(2d, exp);
for (int i = 0; i < sin.length; i++) {
double period = (double)SAMPLE_RATE / f;
double angle = 2.0 * Math.PI * i / period;
sin[i] = (byte)(Math.sin(angle) * 127f);
}
}
}
public byte[] data() {
return sin;
}
}
This low-level approach may be suitable for older, less capable platforms. Also consider javax.sound.midi
; a complete example is shown here and the Synthesizing Sound tutorial is cited here.
The easiest way to do this is with java's in built MIDI libraries:
int channel = 0; // 0 is a piano, 9 is percussion, other channels are for other instruments
int volume = 80; // between 0 et 127
int duration = 200; // in milliseconds
try {
Synthesizer synth = MidiSystem.getSynthesizer();
synth.open();
MidiChannel[] channels = synth.getChannels();
// --------------------------------------
// Play a few notes.
// The two arguments to the noteOn() method are:
// "MIDI note number" (pitch of the note),
// and "velocity" (i.e., volume, or intensity).
// Each of these arguments is between 0 and 127.
channels[channel].noteOn( 60, volume ); // C note
Thread.sleep( duration );
channels[channel].noteOff( 60 );
channels[channel].noteOn( 62, volume ); // D note
Thread.sleep( duration );
channels[channel].noteOff( 62 );
channels[channel].noteOn( 64, volume ); // E note
Thread.sleep( duration );
channels[channel].noteOff( 64 );
Thread.sleep( 500 );
// --------------------------------------
// Play a C major chord.
channels[channel].noteOn( 60, volume ); // C
channels[channel].noteOn( 64, volume ); // E
channels[channel].noteOn( 67, volume ); // G
Thread.sleep( 3000 );
channels[channel].allNotesOff();
Thread.sleep( 500 );
synth.close();
}
catch (Exception e) {
e.printStackTrace();
}
Java's Built-in Midi Capabilities
The off-the-shelf Java 8 JRE definitely has what you specifically requested: A built-in synth library. It is described in some detail in Synthesizing Sound.
A quite refined example provides a visual keyboard access to a sampled music synth.
The javax.sound.midi library contains instruments that and the ability to play notes on them, based on MIDI and sampled instrument technology. The sounds are not as authentic as those of the classic Kurzweil musical instrument line, but the framework supports that level of sophistication if you wish to do your own sampling in multiple pitch ranges for a single instrument and work out the details of a fairly seamless transition between ranges.
Trivial Example for Quick View of Usage
Here's a trivial example class.
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.MidiChannel;
public class PlayMidiNote
{
private static void sleep(int iUSecs)
{
try
{
Thread.sleep(iUSecs);
}
catch (InterruptedException e)
{
}
}
public static void main(String[] args) throws Exception
{
if (args.length < 3 && args.length > 4)
{
System.out.println("usage: java PlayNote
<8.bit.midi.note.number> <8.bit.velocity>
<usec.duration> [<midi.channel>]");
System.exit(1);
}
int iMidiKey = Math.min(127, Math.max(0,
Integer.parseInt(args[0])));
int iVelocity = Math.min(127, Math.max(0,
Integer.parseInt(args[1])));
int iUSecsDuration = Math.max(0,
Integer.parseInt(args[2]));
int iChannel = args.length > 3
? Math.min(15, Math.max(0,
Integer.parseInt(args[3])))
: 0;
Synthesizer synth = MidiSystem.getSynthesizer();
synth.open();
MidiChannel[] channels = synth.getChannels();
MidiChannel channel = channels[iChannel];
channel.noteOn(iMidiKey, iVelocity);
sleep(iUSecsDuration);
channel.noteOff(iMidiKey);
synth.close();
}
}
Using multi-threading or an implementations of javax.sound.midi.Sequencer like those available on GitHub.com will provide the structure necessary to actually make music.
Waveform Generation
If you wish to generate your own waveforms rather than using samples, then the answer to your question is, "No," however you can play with this tone synthesizer I wrote for this question. It has several features.
- Control of pitch (in cycles per second)
- Control of amplitude (in digital steps)
- Min and max statistics to indicate when the amplitude is too high (which causes the distortion of the tone from audio clipping)
- Control of tone duration (in seconds)
- Control of the relative amplitude of an arbitrary number of harmonics (which determines tone quality or waveform, which is one of several factors that create a musical note's timbre)
This synth lacks many features of high end waveform-generating synths.
- Real time modification of the amplitude of the principle harmonic and other harmonics over the duration of a note
- Does not play sequences of notes (but could be modified to do so with relative ease)
- No facility to phase shift the harmonics either (but that is a somewhat irrelevant shortcoming, since phase characteristics of harmonics is not something the human ear is capable of detecting)
SimpleSynth is a good demonstration of
- The principles of audio synthesis
- The timbral effect of harmonics
- The use of Java to generate audio sample arrays
- The placing of samples into a byte array
- The submittal of such a byte array to the OS through a line
Standard digital audio terminology was used for both constant and variable naming. The math is explained in comments.
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.SourceDataLine;
class SimpleSynth
{
private static int SAMPLE_BITS = 16;
private static int CHANNELS = 1;
private static boolean SIGNED_TRUE = true;
private static boolean BIG_ENDIAN_FALSE = false;
private static float CDROM_SAMPLE_FREQ = 44100;
private SourceDataLine line;
private double dDuration;
private double dCyclesPerSec;
private double dAmplitude;
private double[] adHarmonics;
private double dMin;
private double dMax;
public SimpleSynth(String[] asArguments) throws Exception
{
dDuration = Double.parseDouble(asArguments[0]);
dCyclesPerSec = Double.parseDouble(asArguments[1]);
dAmplitude = Double.parseDouble(asArguments[2]);
adHarmonics = new double[asArguments.length - 3];
for (int i = 0; i < adHarmonics.length; ++ i)
adHarmonics[i] = Double.parseDouble(
asArguments[i + 3]);
AudioFormat format = new AudioFormat(
CDROM_SAMPLE_FREQ, SAMPLE_BITS,
CHANNELS, SIGNED_TRUE, BIG_ENDIAN_FALSE);
line = AudioSystem.getSourceDataLine(format);
line.open();
line.start();
}
public void closeLine()
{
line.drain();
line.stop();
}
public void playSound()
{
// allocate and prepare byte buffer and its index
int iBytes = (int) (2.0 * (0.5 + dDuration)
* CDROM_SAMPLE_FREQ);
byte[] ab = new byte[iBytes];
int i = 0;
// iterate through sample radian values
// for the specified duration
short i16;
double dSample;
double dRadiansPerSample = 2.0 * Math.PI
* dCyclesPerSec / CDROM_SAMPLE_FREQ;
double dDurationInRadians = 2.0 * Math.PI
* dCyclesPerSec * dDuration;
dMin = 0.0;
dMax = 0.0;
for (double d = 0.0;
d < dDurationInRadians;
d += dRadiansPerSample)
{
// add principle and the dot product of harmonics
// and their amplitudes relative to the principle
dSample = Math.sin(d);
for (int h = 0; h < adHarmonics.length; ++ h)
dSample += adHarmonics[h]
* Math.sin((h + 2) * d);
// factor in amplitude
dSample *= dAmplitude;
// adjust statistics
if (dMin > dSample)
dMin = dSample;
if (dMax < dSample)
dMax = dSample;
// store in byte buffer
i16 = (short) (dSample);
ab[i ++] = (byte) (i16);
ab[i ++] = (byte) (i16 >> 8);
}
// send the byte array to the audio line
line.write(ab, 0, i);
}
public void printStats()
{
System.out.println("sample range was ["
+ dMin + ", " + dMax + "]"
+ " in range of [-32768, 32767]");
if (dMin < -32768.0 || dMax > 32767.0)
System.out.println("sound is clipping"
+ "(exceeding its range),"
+ " so use a lower amplitude");
}
public static void main(String[] asArguments)
throws Exception
{
if (asArguments.length < 3)
{
System.err.println("usage: java SimpleSynth"
+ " <duration>"
+ " <tone.cycles.per.sec>"
+ " <amplitude>"
+ " [<relative.amplitude.harmonic.2>"
+ " [...]]");
System.err.println("pure tone:"
+ " java SimpleSynth 1 440 32767");
System.err.println("oboe-like:"
+ " java SimpleSynth 1 440 15000 0 1 0 .9");
System.err.println("complex:"
+ " java SimpleSynth 1 440 800 .3"
+ " .5 .4 .2 .9 .7 5 .1 .9 12 0 3"
+ " .1 5.2 2.5 .5 1 7 6");
System.exit(0);
}
SimpleSynth synth = new SimpleSynth(asArguments);
synth.playSound();
synth.closeLine();
synth.printStats();
System.exit(0);
}
}
Topics of Study to Augment Music Synthesis Expertise
There are a few topics to read up on if you wish to become a digital synth expert.
- Digital audio
- Signal sampling
- Nyquist criteria
- How to pluck harmonics on a stringed instrument (such as a guitar)
- Basic trigonometry, specifically the sine function