Well, I figured out how to output multichannel at least. You can get access to a “SourceDataLine” (output) which allows you to output (bitdepth/8) * channels bytes at a time. Here’s a sketch which outputs a sinewave. I am on a Mac and used the BlackHole virtual audio device for testing.
import javax.sound.sampled.*;
static final String MIXER_NAME = "BlackHole 16ch";
//static final String MIXER_NAME = "MOTU UltraLite mk3 Hybrid";
static final int BIT_DEPTH = 16;
static final int SAMPLE_RATE = 44100;
Mixer.Info[] mixerInfo;
Mixer mixer;
Line line;
AudioFormat af;
SourceDataLine sdl;
void setup()
{
size(200, 200);
mixerInfo = AudioSystem.getMixerInfo();
// find our interface by name
for (int i=0; i<mixerInfo.length; i++) {
if (mixerInfo[i].getName().equals(MIXER_NAME)) {
mixer = AudioSystem.getMixer(mixerInfo[i]);
print (String.format("Found mixer %s!\n",MIXER_NAME));
break;
}
}
if (mixer==null) {
print (String.format ("Can't find mixer %s, quitting\n", MIXER_NAME));
print ("Available mixers are:\n");
printArray(mixerInfo);
return;
}
// find the audio format we want
Line.Info[] lineInfo = mixer.getSourceLineInfo();
try {
for (Line.Info li : lineInfo) {
Line line = mixer.getLine (li);
if (line instanceof SourceDataLine) {
SourceDataLine source = (SourceDataLine) line;
DataLine.Info i = (DataLine.Info) source.getLineInfo();
for (AudioFormat format : i.getFormats()) {
if (format.getChannels()>2 && format.getSampleSizeInBits()==BIT_DEPTH && format.isBigEndian() && format.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) {
print(String.format ("Found this format: %s\n", format));
af=new AudioFormat (44100, BIT_DEPTH, format.getChannels(), true, true);
}
}
}
}
}
catch (LineUnavailableException e) {
print ("line unavailable, exiting\n");
return;
}
if (af==null) {
print("Couldn't find the desired audio format\n");
return;
}
try {
// now we have found a multi-channel audio format in the mixer, open and start a SourceDataLine (output).
DataLine.Info sdlInfo = new DataLine.Info (SourceDataLine.class, af);
sdl = (SourceDataLine)mixer.getLine(sdlInfo);
println (String.format("got the line, %d bytes frame", af.getFrameSize()));
sdl.open();
sdl.start();
// generate some PCM data (a sine wave for simplicity)
byte[] buffer = new byte[128];
float step = (TWO_PI*2/buffer.length);
float angle = 0;
int i = buffer.length;
while (i > 0) {
float sine = sin(angle);
int sample = (int) Math.round(sine*32767);
buffer[--i] = (byte) sample;
buffer[--i] = (byte) (sample >> 8);
angle += step;
}
// write the sinewave 2000x to the buffer
for (int n=0; n<2000; ++n) {
writeToOutput(4, buffer); // write to channel 5 (channels start at 0)
}
// shut down audio
sdl.drain();
sdl.stop();
sdl.close();
}
catch (LineUnavailableException e) {
println (e);
}
}
void writeToOutput (int channel, byte[] buffer) {
// writes a buffer to aparticular channel of the sound card. (only will work with 16 bit atm)
// for example, an 8-channel soundcard will give you a 16 byte "frame size". To output to channel 5
// you have to insert your 16-bit audio data into bytes 8+9:
//
// 00 00 00 00 00 00 00 00 LS MS 00 00 00 00 00 00
//
// then write the whole buffer to the SourceDataLine we opened earlier.
// This writes it one frame at a time which is probably horribly inefficient.
byte[] b = new byte[af.getFrameSize()]; // make a buffer the same size as the frame
int lsb = (channel*2); // compute position of LSB
for (int i=0; i<buffer.length; i+=2) { // 2 bytes at a time
b[lsb] = buffer[i]; // copy in LSB
b[lsb+1] = buffer[i+1]; // copy in MSB
sdl.write(b, 0, b.length); // write to soundcard
}
}
// just for debugging
void dumpBuffer(int w, byte[] buf) {
for (int i=0; i<buf.length; i++) {
print (String.format ("%02X ", buf[i]));
if ((i+1)%w==0) println ();
}
}
Of course to use this you need the raw sample data. Not a huge deal for me because all I needed was to output a sine wave anyway, but I don’t think raw samples are available in the Sound library.
Taking a look at the jsyn source code I don’t think it would be too difficult to extend, but beyond my capabilities for sure. I’ll leave this here in the hope that someone can manage it!