I am working on a Java program, and I really need to be able to play a sound by a certain frequency and duration, similarly to the c# method System.Beep, I know how to use i
I don't think there's a way to play tunes1 with "beep" in portable2 Java. You'll need to use the javax.sound.*
APIs I think ... unless you can find a third-party library that simplifies things for you.
If you want to go down this path, then this page might give you some ideas.
1 - Unless your users are all tone-deaf. Of course you can do things like beeping in Morse code ... but that's not a tune.
2 - Obviously, you could make native calls to a Windows beep function. But that would not be portable.
I've hacked together a function that works for me. It uses a bunch of stuff from javax.sound.sampled
. I've geared it to work with the audio format my system automatically gives a new Clip from AudioSystem.getClip()
. There's probably all kinds of ways it could be made more robust and more efficient.
/**
* Beeps. Currently half-assumes that the format the system expects is
* "PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, big-endian"
* I don't know what to do about the sample rate. Using 11025, since that
* seems to be right, by testing against A440. I also can't figure out why
* I had to *4 the duration. Also, there's up to about a 100 ms delay before
* the sound starts playing.
* @param freq
* @param millis
*/
public static void beep(double freq, final double millis) {
try {
final Clip clip = AudioSystem.getClip();
AudioFormat af = clip.getFormat();
if (af.getSampleSizeInBits() != 16) {
System.err.println("Weird sample size. Dunno what to do with it.");
return;
}
//System.out.println("format " + af);
int bytesPerFrame = af.getFrameSize();
double fps = 11025;
int frames = (int)(fps * (millis / 1000));
frames *= 4; // No idea why it wasn't lasting as long as it should.
byte[] data = new byte[frames * bytesPerFrame];
double freqFactor = (Math.PI / 2) * freq / fps;
double ampFactor = (1 << af.getSampleSizeInBits()) - 1;
for (int frame = 0; frame < frames; frame++) {
short sample = (short)(0.5 * ampFactor * Math.sin(frame * freqFactor));
data[(frame * bytesPerFrame) + 0] = (byte)((sample >> (1 * 8)) & 0xFF);
data[(frame * bytesPerFrame) + 1] = (byte)((sample >> (0 * 8)) & 0xFF);
data[(frame * bytesPerFrame) + 2] = (byte)((sample >> (1 * 8)) & 0xFF);
data[(frame * bytesPerFrame) + 3] = (byte)((sample >> (0 * 8)) & 0xFF);
}
clip.open(af, data, 0, data.length);
// This is so Clip releases its data line when done. Otherwise at 32 clips it breaks.
clip.addLineListener(new LineListener() {
@Override
public void update(LineEvent event) {
if (event.getType() == Type.START) {
Timer t = new Timer((int)millis + 1, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
clip.close();
}
});
t.setRepeats(false);
t.start();
}
}
});
clip.start();
} catch (LineUnavailableException ex) {
System.err.println(ex);
}
}
EDIT: Apparently somebody's improved my code. I haven't tried it yet, but give it a go: https://gist.github.com/jbzdak/61398b8ad795d22724dd