Android has a built-in class called AudioTrack that can be used to play or synthesize audio.
Here is a code snippet:
final int SAMPLE_RATE = 11025;
int minSize = 0;
short[] buffer = {8130,15752,22389,27625,31134,32695,32210,29711,25354,19410,12253, 4329,-3865,-11818,-19032,-25055,-29511,-32121,-32722,-31276,-27874, -22728,-16160,-8582,-466};
boolean playing = false;
android.media.AudioTrack audioTrack;
void setup() {
minSize = android.media.AudioTrack.getMinBufferSize(SAMPLE_RATE, android.media.AudioFormat.CHANNEL_CONFIGURATION_MONO, android.media.AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new android.media.AudioTrack(android.media.AudioManager.STREAM_MUSIC,
SAMPLE_RATE, android.media.AudioFormat.CHANNEL_CONFIGURATION_MONO,
android.media.AudioFormat.ENCODING_PCM_16BIT, minSize,
android.media.AudioTrack.MODE_STREAM);
audioTrack.play();
}
void draw() {
}
void mousePressed() {
if (!playing) {
playing = true;
while (true) {
audioTrack.write(buffer, 0, buffer.length);
}
}
}
If you run this, it should generate a static tone but you will notice a couple of problems. The first is that the app becomes unresponsive and the second is that Android wants you to quit it.
This is because we are running the sound playback in an infinite loop and it is tying up the “UI thread”.
A Thread is a concurrently running piece of code that your program can create to handle tasks that would otherwise occupy the main part of the program. In a sense, it is running these other portions of your program in the background, simultaneously. Of course, this is a very simplified explanation, see Java’s Thread documentation and the Wikipedia Thread article.
What we really want to do is run the audio playback on a separate thread.
AudioSynth Class:
class AudioSynth
{
final int SAMPLE_RATE = 11025;
int minSize = 0;
short[] buffer = {8130,15752,22389,27625,31134,32695,32210,29711,25354,19410,12253, 4329,-3865,-11818,-19032,-25055,-29511,-32121,-32722,-31276,-27874, -22728,-16160,-8582,-466};
android.media.AudioTrack audioTrack;
boolean keepGoing = false;
public AudioSynth() {
minSize = android.media.AudioTrack.getMinBufferSize(SAMPLE_RATE, android.media.AudioFormat.CHANNEL_CONFIGURATION_MONO, android.media.AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new android.media.AudioTrack(android.media.AudioManager.STREAM_MUSIC,
SAMPLE_RATE, android.media.AudioFormat.CHANNEL_CONFIGURATION_MONO,
android.media.AudioFormat.ENCODING_PCM_16BIT, minSize,
android.media.AudioTrack.MODE_STREAM);
}
public void stop() {
keepGoing = false;
}
public void play() {
keepGoing = true;
audioTrack.play();
new Thread(new Runnable() {
public void run() {
while (keepGoing) {
audioTrack.write(buffer, 0, buffer.length);
}
audioTrack.stop();
}
}
).start();
}
void setBuffer(short[] newbuffer) {
buffer = newbuffer;
}
}
Main Processing Sketch:
boolean playing = false;
AudioSynth synth;
void setup() {
synth = new AudioSynth();
}
void draw() {
}
void mousePressed() {
if (!playing) {
playing = true;
synth.play();
} else {
playing = false;
synth.stop();
}
}
Now that’s all well and good but what if we want to dynamically generate the sound to play?
Here is a really annoying version:
boolean playing = false;
AudioSynth synth;
void setup() {
synth = new AudioSynth();
synth.play();
}
void draw() {
}
void mousePressed() {
short[] buffer = {(short)(mouseX*100),(short)(mouseY*100)};
synth.setBuffer(buffer);
}
In many cases, people will use math to generate samples. Here is a sine wave (at mouseX frequency):
boolean playing = false;
AudioSynth synth;
void setup() {
synth = new AudioSynth();
synth.play();
}
void draw() {
}
void mousePressed() {
short[] buffer = new short[synth.minSize];
float angle = 0;
float angular_frequency = (float)(2*Math.PI) * mouseX / synth.SAMPLE_RATE;
// 2 x PI is the angle in radians of 360 degrees
// multiply that by the mouseX/frequency of samples, you get the angle in radians of a specific frequency (C, 440Hz)
// http://www.motionscript.com/mastering-expressions/simulation-basics-1.html
for (int i = 0; i < buffer.length; i++)
{
buffer[i] = (short)(Short.MAX_VALUE * ((float) Math.sin(angle)));
angle += angular_frequency;
}
synth.setBuffer(buffer);
}