Reputation: 42697
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!
Upvotes: 20
Views: 21903
Reputation: 3671
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.
This synth lacks many features of high end waveform-generating synths.
SimpleSynth is a good demonstration of
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.
Upvotes: 5
Reputation: 205885
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.
Upvotes: 46
Reputation: 109
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();
}
Upvotes: 8
Reputation: 2591
Jcollider is a Java interface to the SuperCollider synthesis server. If you want to synthesize music, this will make things much easier (it abstracts away from the tone generator to a synthesizer, takes care of things like graph generation, deleting muted synths from the synthesis graph until they are needed again, patching signals between synths dynamically, etc.).
Upvotes: 1
Reputation: 12630
This Sun forum post has some interesting code for generating sin tones. Also, given that the WAV file format is not overly complicated, you could create a table representing the desired waveform and then write it to a file. There are a few examples around, e.g. a raw audio converter and how to write a wav file.
Upvotes: 1
Reputation: 44093
Have you looked at JSyn? I don't think the Java Core libraries can do what you want.
Upvotes: 3