// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.resource.sound;
import java.nio.ByteBuffer;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.util.io.StreamUtils;
/**
* Abstract base class provides access to uncompressed PCM WAV audio data.
*/
public abstract class AudioBuffer
{
/**
* Contains uncompressed PCM WAV audio data (including WAV header)
*/
protected byte[] data = null;
public AudioBuffer(ResourceEntry entry) throws Exception
{
this(entry, null);
}
public AudioBuffer(ResourceEntry entry, AudioOverride override) throws Exception
{
if (entry != null) {
convert(StreamUtils.toArray(entry.getResourceBuffer()), 0, override);
} else
throw new NullPointerException();
}
public AudioBuffer(byte[] buffer, int offset) throws Exception
{
this(buffer, offset, null);
}
public AudioBuffer(byte[] buffer, int offset, AudioOverride override) throws Exception
{
convert(buffer, offset, override);
}
/**
* Returns the buffer of uncompressed PCM data including WAV header.
* @return Buffer containing uncompressed PCM WAV data.
*/
public byte[] getAudioData()
{
return data;
}
/**
* Converts the source audio data into uncompressed PCM WAV data.
* @param buffer Buffer containing source audio data.
* @param offset Start offset into buffer.
* @param override An optional override object to force certain properties of the audio format.
* @throws Exception
*/
protected abstract void convert(byte[] buffer, int offset, AudioOverride override) throws Exception;
/**
* Creates and returns a valid PCM WAV header structure.
* @param samplesPerChannel Total number of samples per channel.
* @param channels Number of sound channels in audio clip.
* @param sampleRate Sample rate in Hz of audio clip.
* @param bitsPerSample Bits per sample (8..32 bit supported)
* @return A complete PCM WAV header
* @throws Exception
*/
protected static byte[] createWAVHeader(int samplesPerChannel, int channels, int sampleRate,
int bitsPerSample) throws Exception
{
final int ID_CHUNK = 0x46464952; // 'RIFF'
final int ID_FORMAT = 0x45564157; // 'WAVE'
final int ID_SUBCHUNK1 = 0x20746d66; // 'fmt '
final int ID_SUBCHUNK2 = 0x61746164; // 'data'
// sanity checks
if (samplesPerChannel < 1)
throw new Exception("Invalid number of samples: " + samplesPerChannel);
if (channels < 1 || channels > 2)
throw new Exception("Unsupported number of channels: " + channels);
if (sampleRate < 4096 || sampleRate > 192000)
throw new Exception("Unsupported sample rate: " + sampleRate);
switch (bitsPerSample) {
case 8: case 16: case 24: case 32: break;
default: throw new Exception("Unsupported bits per sample: " + bitsPerSample);
}
// setting required WAVE fields
short blockAlign = (short)(channels * bitsPerSample / 8);
int totalSize = samplesPerChannel * blockAlign;
int byteRate = sampleRate * blockAlign;
int chunkSize = 36 + totalSize;
// writing header data
ByteBuffer bb = StreamUtils.getByteBuffer(44);
bb.putInt(ID_CHUNK);
bb.putInt(chunkSize);
bb.putInt(ID_FORMAT);
bb.putInt(ID_SUBCHUNK1);
bb.putInt(16); // SubChunk1 size
bb.putShort((short)1); // PCM type
bb.putShort((short)channels);
bb.putInt(sampleRate);
bb.putInt(byteRate);
bb.putShort(blockAlign);
bb.putShort((short)bitsPerSample);
bb.putInt(ID_SUBCHUNK2);
bb.putInt(totalSize);
return bb.array();
}
//-------------------------- INNER CLASSES --------------------------
/**
* Use to override autodetected properties of the source audio format.
* Audio buffers are not required to honor the properties defined in the AudioOverride object.
*/
public static class AudioOverride
{
protected int numChannels;
protected int sampleRate;
protected int bitsPerSample;
/**
* Creates an audio channel override object.
* (A value of zero signals the audio decoder to ignore that parameter.
* A negative value lets the AudioBuffer object decide what to do with that parameter.)
* @param numChannels Force to use {@code numChannels} audio channels.
* @return An AudioOverride object initialized with the specified parameters.
*/
public static AudioOverride overrideChannels(int numChannels)
{
return new AudioOverride(numChannels, 0, 0);
}
/**
* Creates a sample rate override object.
* (A value of zero signals the audio decoder to ignore that parameter.
* A negative value lets the AudioBuffer object decide what to do with that parameter.)
* @param sampleRate Force a sample rate of {@code sampleRate} Hz.
* @return An AudioOverride object initialized with the specified parameters.
*/
public static AudioOverride overrideSampleRate(int sampleRate)
{
return new AudioOverride(0, sampleRate, 0);
}
/**
* Creates a bits per sample override object.
* (A value of zero signals the audio decoder to ignore that parameter.
* A negative value lets the AudioBuffer object decide what to do with that parameter.)
* @param bitsPerSample Force {@code bitsPerSample} bits per sample.
* @return An AudioOverride object initialized with the specified parameters.
*/
public static AudioOverride overrideBitsPerSample(int bitsPerSample)
{
return new AudioOverride(0, 0, bitsPerSample);
}
/**
* Creates an override object with the specified forced properties.
* (A value of zero signals the audio decoder to ignore that parameter.
* A negative value lets the AudioBuffer object decide what to do with that parameter.)
* @param numChannels Force to use {@code numChannels} audio channels.
* @param sampleRate Force a sample rate of {@code sampleRate} Hz.
* @param bitsPerSample Force {@code bitsPerSample} bits per sample.
* @return An AudioOverride object initialized with the specified parameters.
*/
public static AudioOverride override(int numChannels, int sampleRate, int bitsPerSample)
{
return new AudioOverride(numChannels, sampleRate, bitsPerSample);
}
private AudioOverride(int numChannels, int sampleRate, int bitsPerSample)
{
this.numChannels = numChannels;
this.sampleRate = sampleRate;
this.bitsPerSample = bitsPerSample;
}
}
}