package gui.sonar.sample;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
public class SampleLoader
{
/**
* Loads a sample from an url
*/
public static SonarSample loadSample(String resourceName) throws UnsupportedAudioFileException, IOException
{
// Hack to prevent "mark/reset not supported" on some systems
//byte[] d = rip(SampleLoader.class.getResourceAsStream(resourceName));
byte[] d = rip(new FileInputStream(resourceName));
AudioInputStream ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(d));
return buildSample(rip(ais), ais.getFormat());
}
/**
* Rips the entire contents of an inputstream into a byte array
*/
private static byte[] rip(InputStream in) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[4096];
int read = 0;
while ((read = in.read(b)) > 0)
{
bos.write(b, 0, read);
}
bos.close();
return bos.toByteArray();
}
/**
* Reorganizes audio sample data into the intenal sonar format
*/
private static SonarSample buildSample(byte[] b, AudioFormat af) throws UnsupportedAudioFileException
{
// Rip audioformat data
int channels = af.getChannels();
int sampleSize = af.getSampleSizeInBits();
float rate = af.getFrameRate();
boolean signed = af.getEncoding() == AudioFormat.Encoding.PCM_SIGNED;
// Sanity checking
if (channels != 1) throw new UnsupportedAudioFileException("Only mono samples are supported");
if (!(sampleSize == 8 || sampleSize == 16 || sampleSize == 32)) throw new UnsupportedAudioFileException("Unsupported sample size");
if (!(af.getEncoding() == AudioFormat.Encoding.PCM_UNSIGNED || af.getEncoding() == AudioFormat.Encoding.PCM_SIGNED)) throw new UnsupportedAudioFileException("Unsupported encoding");
// Wrap the data into a bytebuffer, and set up the byte order
ByteBuffer bb = ByteBuffer.wrap(b);
bb.order(af.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
int s = b.length / (sampleSize / 8);
float[] buf = new float[s];
// Six different cases for reordering the data. Can this be improved without slowing it down?
if (sampleSize == 8)
{
if (signed)
{
for (int i = 0; i < s; i++)
buf[i] = bb.get() / (float)0x80;
}
else
{
for (int i = 0; i < s; i++)
buf[i] = ((bb.get()&0xFF)-0x80) / (float)0x80;
}
}
else if (sampleSize == 16)
{
if (signed)
{
for (int i = 0; i < s; i++)
buf[i] = bb.getShort() / (float)0x8000;
}
else
{
for (int i = 0; i < s; i++)
buf[i] = ((bb.getShort()&0xFFFF)-0x8000) / (float)0x8000;
}
}
else if (sampleSize == 32)
{
if (signed)
{
for (int i = 0; i < s; i++)
buf[i] = bb.getInt() / (float)0x80000000;
}
else
{
// Nasty.. check this.
for (int i = 0; i < s; i++)
buf[i] = ((bb.getInt()&0xFFFFFFFFl)-0x80000000l) / (float)0x80000000;
}
}
// Return the completed sample
return new SonarSample(buf, rate);
}
}