I'm not an audio engineer, so I can't vouch that all of the code below makes sense, is accurate from an acoustical perspective or is efficient, only that it sounds reasonable to my ears. I'm simply gluing together others' code taken at face value, possible warts and all, so assume this isn't production-ready. I welcome feedback and fixes!
For white noise, here's a simplified version of the code in this answer elsewhere in this thread that does away with the unnecessary GUI stuff:
import java.nio.ByteBuffer;
import java.util.Random;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class WhiteNoise {
public static void main(String[] args) {
final int SAMPLE_RATE = 44100;
final int BITS = 16;
final int CHANNELS = 1;
final int SAMPLE_SIZE = 2;
final int PACKET_SIZE = 5000;
AudioFormat format = new AudioFormat(
SAMPLE_RATE,
BITS,
CHANNELS,
true, // signed
true // big endian
);
DataLine.Info info = new DataLine.Info(
SourceDataLine.class,
format,
PACKET_SIZE * 2
);
SourceDataLine line;
try {
line = (SourceDataLine)AudioSystem.getLine(info);
line.open(format);
}
catch (LineUnavailableException e) {
e.printStackTrace();
return;
}
line.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
//line.drain(); // seems to hang my Windows machine
line.close();
}));
ByteBuffer buffer = ByteBuffer.allocate(PACKET_SIZE);
Random random = new Random();
for (;;) {
buffer.clear();
for (int i = 0; i < PACKET_SIZE / SAMPLE_SIZE; i++) {
buffer.putShort((short)(random.nextGaussian() * Short.MAX_VALUE));
}
line.write(buffer.array(), 0, buffer.position());
}
}
}
Now, we can change the color of the noise using a variety of techniques, such as adapting the JavaScript code from How to Generate Noise with the Web Audio API to Java. All of the boilerplate code above is the same; this just changes the code around the for (;;) {...}
block.
Pink:
// ...
double b0 = 0.0;
double b1 = 0.0;
double b2 = 0.0;
double b3 = 0.0;
double b4 = 0.0;
double b5 = 0.0;
double b6 = 0.0;
for (;;) {
buffer.clear();
for (int i = 0; i < PACKET_SIZE / SAMPLE_SIZE; i++) {
double white = random.nextGaussian();
b0 = 0.99886 * b0 + white * 0.0555179;
b1 = 0.99332 * b1 + white * 0.0750759;
b2 = 0.96900 * b2 + white * 0.1538520;
b3 = 0.86650 * b3 + white * 0.3104856;
b4 = 0.55000 * b4 + white * 0.5329522;
b5 = -0.7616 * b5 - white * 0.0168980;
double output = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
output *= 0.05; // (roughly) compensate for gain
b6 = white * 0.115926;
buffer.putShort((short)(output * Short.MAX_VALUE));
}
line.write(buffer.array(), 0, buffer.position());
}
// ...
Brownian:
// ...
double lastOut = 0.0;
for (;;) {
buffer.clear();
for (int i = 0; i < PACKET_SIZE / SAMPLE_SIZE; i++) {
double white = random.nextGaussian();
double output = (lastOut + (0.02 * white)) / 1.02;
lastOut = output;
output *= 1.5; // (roughly) compensate for gain
buffer.putShort((short)(output * Short.MAX_VALUE));
}
line.write(buffer.array(), 0, buffer.position());
}
// ...
Elsewhere in the thread, Mars shared PinkNoise.java, so I might as well put it in the answer as an alternative approach for posterity. One suggestion among many they offer is swapping random.nextGaussian()
for random.nextDouble() - 0.5
to improve performance.
Another possible optimization at the expense of randomness and "acoustical correctness" is pre-generating a bunch of random buffers, then randomly picking from them or cycling through them. This might be sufficiently accurate-sounding for many use cases.
Lastly, the while
loop is probably doing more work in the above examples than it needs to do. Generating an Audio Sine Wave with Java shows code that uses Thread.sleep
to throttle based on line buffer availability. Naively adding a Thread.sleep(20)
into the loop dropped CPU usage for the process massively without any noticable audio dropout, but I'll leave it out of the main code for now.