rockbox/songdbj/org/tritonus/share/sampled/FloatSampleTools.java
Michiel Van Der Kolk 9fee0ec4ca Songdb java version, source. only 1.5 compatible
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657
2005-07-11 15:42:37 +00:00

696 lines
25 KiB
Java

/*
* FloatSampleTools.java
*
* This file is part of Tritonus: http://www.tritonus.org/
*/
/*
* Copyright (c) 2000,2004 by Florian Bomers <http://www.bomers.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
package org.tritonus.share.sampled;
import java.util.*;
import javax.sound.sampled.*;
import org.tritonus.share.TDebug;
/**
* Utility functions for handling data in normalized float arrays.
* Each sample is linear in the range of [-1.0f, +1.0f].
* <p>
* Currently, the following bit sizes are supported:
* <ul>
* <li>8-bit
* <li>16-bit
* <li>packed 24-bit (stored in 3 bytes)
* <li>32-bit
* </ul>
* 8-bit data can be unsigned or signed. All other data is only
* supported in signed encoding.
*
* @see FloatSampleBuffer
* @author Florian Bomers
*/
public class FloatSampleTools {
/** default number of bits to be dithered: 0.7f */
public static final float DEFAULT_DITHER_BITS = 0.7f;
private static Random random = null;
// sample width (must be in order !)
static final int F_8=1;
static final int F_16=2;
static final int F_24=3;
static final int F_32=4;
static final int F_SAMPLE_WIDTH_MASK=F_8 | F_16 | F_24 | F_32;
// format bit-flags
static final int F_SIGNED=8;
static final int F_BIGENDIAN=16;
// supported formats
static final int CT_8S=F_8 | F_SIGNED;
static final int CT_8U=F_8;
static final int CT_16SB=F_16 | F_SIGNED | F_BIGENDIAN;
static final int CT_16SL=F_16 | F_SIGNED;
static final int CT_24SB=F_24 | F_SIGNED | F_BIGENDIAN;
static final int CT_24SL=F_24 | F_SIGNED;
static final int CT_32SB=F_32 | F_SIGNED | F_BIGENDIAN;
static final int CT_32SL=F_32 | F_SIGNED;
// ////////////////////////////// initialization /////////////////////////////// //
/** prevent instanciation */
private FloatSampleTools() {
}
// /////////////////// FORMAT / FORMAT TYPE /////////////////////////////////// //
/**
* only allow "packed" samples -- currently no support for 18, 20, 24_32 bits.
* @throws IllegalArgumentException
*/
static void checkSupportedSampleSize(int ssib, int channels, int frameSize) {
if ((ssib*channels) != frameSize * 8) {
throw new IllegalArgumentException("unsupported sample size: "+ssib
+" stored in "+(frameSize/channels)+" bytes.");
}
}
/**
* Get the formatType code from the given format.
* @throws IllegalArgumentException
*/
static int getFormatType(AudioFormat format) {
boolean signed = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
if (!signed &&
!format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
throw new IllegalArgumentException
("unsupported encoding: only PCM encoding supported.");
}
if (!signed && format.getSampleSizeInBits() != 8) {
throw new IllegalArgumentException
("unsupported encoding: only 8-bit can be unsigned");
}
checkSupportedSampleSize(format.getSampleSizeInBits(),
format.getChannels(),
format.getFrameSize());
int formatType = getFormatType(format.getSampleSizeInBits(),
signed, format.isBigEndian());
return formatType;
}
/**
* @throws IllegalArgumentException
*/
static int getFormatType(int ssib, boolean signed, boolean bigEndian) {
int bytesPerSample=ssib/8;
int res=0;
if (ssib==8) {
res=F_8;
} else if (ssib==16) {
res=F_16;
} else if (ssib==24) {
res=F_24;
} else if (ssib==32) {
res=F_32;
}
if (res==0) {
throw new IllegalArgumentException
("FloatSampleBuffer: unsupported sample size of "
+ssib+" bits per sample.");
}
if (!signed && bytesPerSample>1) {
throw new IllegalArgumentException
("FloatSampleBuffer: unsigned samples larger than "
+"8 bit are not supported");
}
if (signed) {
res|=F_SIGNED;
}
if (bigEndian && (ssib!=8)) {
res|=F_BIGENDIAN;
}
return res;
}
static int getSampleSize(int formatType) {
switch (formatType & F_SAMPLE_WIDTH_MASK) {
case F_8: return 1;
case F_16: return 2;
case F_24: return 3;
case F_32: return 4;
}
return 0;
}
/**
* Return a string representation of this format
*/
static String formatType2Str(int formatType) {
String res=""+formatType+": ";
switch (formatType & F_SAMPLE_WIDTH_MASK) {
case F_8:
res+="8bit";
break;
case F_16:
res+="16bit";
break;
case F_24:
res+="24bit";
break;
case F_32:
res+="32bit";
break;
}
res+=((formatType & F_SIGNED)==F_SIGNED)?" signed":" unsigned";
if ((formatType & F_SAMPLE_WIDTH_MASK)!=F_8) {
res+=((formatType & F_BIGENDIAN)==F_BIGENDIAN)?
" big endian":" little endian";
}
return res;
}
// /////////////////// BYTE 2 FLOAT /////////////////////////////////// //
private static final float twoPower7=128.0f;
private static final float twoPower15=32768.0f;
private static final float twoPower23=8388608.0f;
private static final float twoPower31=2147483648.0f;
private static final float invTwoPower7=1/twoPower7;
private static final float invTwoPower15=1/twoPower15;
private static final float invTwoPower23=1/twoPower23;
private static final float invTwoPower31=1/twoPower31;
/**
* Conversion function to convert an interleaved byte array to
* a List of interleaved float arrays. The float arrays will contain normalized
* samples in the range [-1.0f, +1.0f]. The input array
* provides bytes in the format specified in <code>format</code>.
* <p>
* Only PCM formats are accepted. The method will convert all
* byte values from
* <code>input[inByteOffset]</code> to
* <code>input[inByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
* to floats from
* <code>output(n)[outOffset]</code> to
* <code>output(n)[outOffset + frameCount - 1]</code>
*
* @param input the audio data in an byte array
* @param inByteOffset index in input where to start the conversion
* @param output list of float[] arrays which receive the converted audio data.
* if the list does not contain enough elements, or individual float arrays
* are not large enough, they are created.
* @param outOffset the start offset in <code>output</code>
* @param frameCount number of frames to be converted
* @param format the input format. Only packed PCM is allowed
* @throws IllegalArgumentException if one of the parameters is out of bounds
*
* @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat)
*/
public static void byte2float(byte[] input, int inByteOffset,
List<float[]> output, int outOffset, int frameCount,
//List output, int outOffset, int frameCount,
AudioFormat format) {
for (int channel = 0; channel < format.getChannels(); channel++) {
float[] data;
if (output.size() < channel) {
data = new float[frameCount + outOffset];
output.add(data);
} else {
data = output.get(channel);
if (data.length < frameCount + outOffset) {
data = new float[frameCount + outOffset];
output.set(channel, data);
}
}
byte2floatGeneric(input, inByteOffset, format.getFrameSize(),
data, outOffset,
frameCount, format);
inByteOffset += format.getFrameSize() / format.getChannels();
}
}
/**
* Conversion function to convert an interleaved byte array to
* an interleaved float array. The float array will contain normalized
* samples in the range [-1.0f, +1.0f]. The input array
* provides bytes in the format specified in <code>format</code>.
* <p>
* Only PCM formats are accepted. The method will convert all
* byte values from
* <code>input[inByteOffset]</code> to
* <code>input[inByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
* to floats from
* <code>output[outOffset]</code> to
* <code>output[outOffset + (frameCount * format.getChannels()) - 1]</code>
*
* @param input the audio data in an byte array
* @param inByteOffset index in input where to start the conversion
* @param output the float array that receives the converted audio data
* @param outOffset the start offset in <code>output</code>
* @param frameCount number of frames to be converted
* @param format the input format. Only packed PCM is allowed
* @throws IllegalArgumentException if one of the parameters is out of bounds
*
* @see #byte2float(byte[],int,List,int,int,AudioFormat)
*/
public static void byte2floatInterleaved(byte[] input, int inByteOffset,
float[] output, int outOffset, int frameCount,
AudioFormat format) {
byte2floatGeneric(input, inByteOffset, format.getFrameSize() / format.getChannels(),
output, outOffset, frameCount * format.getChannels(),
format);
}
/**
* Generic conversion function to convert a byte array to
* a float array.
* <p>
* Only PCM formats are accepted. The method will convert all
* bytes from
* <code>input[inByteOffset]</code> to
* <code>input[inByteOffset + (sampleCount * (inByteStep - 1)]</code>
* to samples from
* <code>output[outOffset]</code> to
* <code>output[outOffset+sampleCount-1]</code>.
* <p>
* The <code>format</code>'s channel count is ignored.
* <p>
* For mono data, set <code>inByteOffset</code> to <code>format.getFrameSize()</code>.<br>
* For converting interleaved input data, multiply <code>sampleCount</code>
* by the number of channels and set inByteStep to
* <code>format.getFrameSize() / format.getChannels()</code>.
*
* @param sampleCount number of samples to be written to output
* @param inByteStep how many bytes advance for each output sample in <code>output</code>.
* @throws IllegalArgumentException if one of the parameters is out of bounds
*
* @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat)
* @see #byte2float(byte[],int,List,int,int,AudioFormat)
*/
static void byte2floatGeneric(byte[] input, int inByteOffset, int inByteStep,
float[] output, int outOffset, int sampleCount,
AudioFormat format) {
int formatType = getFormatType(format);
byte2floatGeneric(input, inByteOffset, inByteStep,
output, outOffset, sampleCount,
formatType);
}
/**
* Central conversion function from
* a byte array to a normalized float array. In order to accomodate
* interleaved and non-interleaved
* samples, this method takes inByteStep as parameter which
* can be used to flexibly convert the data.
* <p>
* E.g.:<br>
* mono->mono: inByteStep=format.getFrameSize()<br>
* interleaved_stereo->interleaved_stereo: inByteStep=format.getFrameSize()/2,
* sampleCount*2<br>
* stereo->2 mono arrays:<br>
* ---inByteOffset=0, outOffset=0, inByteStep=format.getFrameSize()<br>
* ---inByteOffset=format.getFrameSize()/2, outOffset=1, inByteStep=format.getFrameSize()<br>
*/
static void byte2floatGeneric(byte[] input, int inByteOffset, int inByteStep,
float[] output, int outOffset, int sampleCount,
int formatType) {
//if (TDebug.TraceAudioConverter) {
// TDebug.out("FloatSampleTools.byte2floatGeneric, formatType="
// +formatType2Str(formatType));
//}
int endCount = outOffset + sampleCount;
int inIndex = inByteOffset;
for (int outIndex = outOffset; outIndex < endCount; outIndex++, inIndex+=inByteStep) {
// do conversion
switch (formatType) {
case CT_8S:
output[outIndex]=
((float) input[inIndex])*invTwoPower7;
break;
case CT_8U:
output[outIndex]=
((float) ((input[inIndex] & 0xFF)-128))*invTwoPower7;
break;
case CT_16SB:
output[outIndex]=
((float) ((input[inIndex]<<8)
| (input[inIndex+1] & 0xFF)))*invTwoPower15;
break;
case CT_16SL:
output[outIndex]=
((float) ((input[inIndex+1]<<8)
| (input[inIndex] & 0xFF)))*invTwoPower15;
break;
case CT_24SB:
output[outIndex]=
((float) ((input[inIndex]<<16)
| ((input[inIndex+1] & 0xFF)<<8)
| (input[inIndex+2] & 0xFF)))*invTwoPower23;
break;
case CT_24SL:
output[outIndex]=
((float) ((input[inIndex+2]<<16)
| ((input[inIndex+1] & 0xFF)<<8)
| (input[inIndex] & 0xFF)))*invTwoPower23;
break;
case CT_32SB:
output[outIndex]=
((float) ((input[inIndex]<<24)
| ((input[inIndex+1] & 0xFF)<<16)
| ((input[inIndex+2] & 0xFF)<<8)
| (input[inIndex+3] & 0xFF)))*invTwoPower31;
break;
case CT_32SL:
output[outIndex]=
((float) ((input[inIndex+3]<<24)
| ((input[inIndex+2] & 0xFF)<<16)
| ((input[inIndex+1] & 0xFF)<<8)
| (input[inIndex] & 0xFF)))*invTwoPower31;
break;
default:
throw new IllegalArgumentException
("unsupported format="+formatType2Str(formatType));
}
}
}
// /////////////////// FLOAT 2 BYTE /////////////////////////////////// //
private static byte quantize8(float sample, float ditherBits) {
if (ditherBits!=0) {
sample+=random.nextFloat()*ditherBits;
}
if (sample>=127.0f) {
return (byte) 127;
} else if (sample<=-128.0f) {
return (byte) -128;
} else {
return (byte) (sample<0?(sample-0.5f):(sample+0.5f));
}
}
private static int quantize16(float sample, float ditherBits) {
if (ditherBits!=0) {
sample+=random.nextFloat()*ditherBits;
}
if (sample>=32767.0f) {
return 32767;
} else if (sample<=-32768.0f) {
return -32768;
} else {
return (int) (sample<0?(sample-0.5f):(sample+0.5f));
}
}
private static int quantize24(float sample, float ditherBits) {
if (ditherBits!=0) {
sample+=random.nextFloat()*ditherBits;
}
if (sample>=8388607.0f) {
return 8388607;
} else if (sample<=-8388608.0f) {
return -8388608;
} else {
return (int) (sample<0?(sample-0.5f):(sample+0.5f));
}
}
private static int quantize32(float sample, float ditherBits) {
if (ditherBits!=0) {
sample+=random.nextFloat()*ditherBits;
}
if (sample>=2147483647.0f) {
return 2147483647;
} else if (sample<=-2147483648.0f) {
return -2147483648;
} else {
return (int) (sample<0?(sample-0.5f):(sample+0.5f));
}
}
/**
* Conversion function to convert a non-interleaved float audio data to
* an interleaved byte array. The float arrays contains normalized
* samples in the range [-1.0f, +1.0f]. The output array
* will receive bytes in the format specified in <code>format</code>.
* Exactly <code>format.getChannels()</code> channels are converted
* regardless of the number of elements in <code>input</code>. If <code>input</code>
* does not provide enough channels, an </code>IllegalArgumentException<code> is thrown.
* <p>
* Only PCM formats are accepted. The method will convert all
* samples from <code>input(n)[inOffset]</code> to
* <code>input(n)[inOffset + frameCount - 1]</code>
* to byte values from <code>output[outByteOffset]</code> to
* <code>output[outByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
* <p>
* Dithering should be used when the output resolution is significantly
* lower than the original resolution. This includes if the original
* data was 16-bit and it is now converted to 8-bit, or if the
* data was generated in the float domain. No dithering need to be used
* if the original sample data was in e.g. 8-bit and the resulting output
* data has a higher resolution. If dithering is used, a sensitive value
* is DEFAULT_DITHER_BITS.
*
* @param input a List of float arrays with the input audio data
* @param inOffset index in the input arrays where to start the conversion
* @param output the byte array that receives the converted audio data
* @param outByteOffset the start offset in <code>output</code>
* @param frameCount number of frames to be converted.
* @param format the output format. Only packed PCM is allowed
* @param ditherBits if 0, do not dither. Otherwise the number of bits to be dithered
* @throws IllegalArgumentException if one of the parameters is out of bounds
*
* @see #DEFAULT_DITHER_BITS
* @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float)
*/
//public static void float2byte(List<float[]> input, int inOffset,
public static void float2byte(List input, int inOffset,
byte[] output, int outByteOffset,
int frameCount,
AudioFormat format, float ditherBits) {
for (int channel = 0; channel < format.getChannels(); channel++) {
float[] data = (float[]) input.get(channel);
float2byteGeneric(data, inOffset,
output, outByteOffset, format.getFrameSize(),
frameCount, format, ditherBits);
outByteOffset += format.getFrameSize() / format.getChannels();
}
}
/**
* Conversion function to convert an interleaved float array to
* an interleaved byte array. The float array contains normalized
* samples in the range [-1.0f, +1.0f]. The output array
* will receive bytes in the format specified in <code>format</code>.
* <p>
* Only PCM formats are accepted. The method will convert all
* samples from <code>input[inOffset]</code> to
* <code>input[inOffset + (frameCount * format.getChannels()) - 1]</code>
* to byte values from <code>output[outByteOffset]</code> to
* <code>output[outByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
* <p>
* Dithering should be used when the output resolution is significantly
* lower than the original resolution. This includes if the original
* data was 16-bit and it is now converted to 8-bit, or if the
* data was generated in the float domain. No dithering need to be used
* if the original sample data was in e.g. 8-bit and the resulting output
* data has a higher resolution. If dithering is used, a sensitive value
* is DEFAULT_DITHER_BITS.
*
* @param input the audio data in normalized samples
* @param inOffset index in input where to start the conversion
* @param output the byte array that receives the converted audio data
* @param outByteOffset the start offset in <code>output</code>
* @param frameCount number of frames to be converted.
* @param format the output format. Only packed PCM is allowed
* @param ditherBits if 0, do not dither. Otherwise the number of bits to be dithered
* @throws IllegalArgumentException if one of the parameters is out of bounds
*
* @see #DEFAULT_DITHER_BITS
* @see #float2byte(List,int,byte[],int,int,AudioFormat,float)
*/
public static void float2byteInterleaved(float[] input, int inOffset,
byte[] output, int outByteOffset,
int frameCount,
AudioFormat format, float ditherBits) {
float2byteGeneric(input, inOffset,
output, outByteOffset, format.getFrameSize() / format.getChannels(),
frameCount * format.getChannels(),
format, ditherBits);
}
/**
* Generic conversion function to convert a float array to
* a byte array.
* <p>
* Only PCM formats are accepted. The method will convert all
* samples from <code>input[inOffset]</code> to
* <code>input[inOffset+sampleCount-1]</code>
* to byte values from <code>output[outByteOffset]</code> to
* <code>output[outByteOffset + (sampleCount * (outByteStep - 1)]</code>.
* <p>
* The <code>format</code>'s channel count is ignored.
* <p>
* For mono data, set <code>outByteOffset</code> to <code>format.getFrameSize()</code>.<br>
* For converting interleaved input data, multiply <code>sampleCount</code>
* by the number of channels and set outByteStep to
* <code>format.getFrameSize() / format.getChannels()</code>.
*
* @param sampleCount number of samples in input to be converted.
* @param outByteStep how many bytes advance for each input sample in <code>input</code>.
* @throws IllegalArgumentException if one of the parameters is out of bounds
*
* @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float)
* @see #float2byte(List,int,byte[],int,int,AudioFormat,float)
*/
static void float2byteGeneric(float[] input, int inOffset,
byte[] output, int outByteOffset, int outByteStep,
int sampleCount,
AudioFormat format, float ditherBits) {
int formatType = getFormatType(format);
float2byteGeneric(input, inOffset,
output, outByteOffset, outByteStep,
sampleCount,
formatType, ditherBits);
}
/**
* Central conversion function from normalized float array to
* a byte array. In order to accomodate interleaved and non-interleaved
* samples, this method takes outByteStep as parameter which
* can be used to flexibly convert the data.
* <p>
* E.g.:<br>
* mono->mono: outByteStep=format.getFrameSize()<br>
* interleaved stereo->interleaved stereo: outByteStep=format.getFrameSize()/2, sampleCount*2<br>
* 2 mono arrays->stereo:<br>
* ---inOffset=0, outByteOffset=0, outByteStep=format.getFrameSize()<br>
* ---inOffset=1, outByteOffset=format.getFrameSize()/2, outByteStep=format.getFrameSize()<br>
*/
static void float2byteGeneric(float[] input, int inOffset,
byte[] output, int outByteOffset, int outByteStep,
int sampleCount, int formatType, float ditherBits) {
//if (TDebug.TraceAudioConverter) {
// TDebug.out("FloatSampleBuffer.float2byteGeneric, formatType="
// +"formatType2Str(formatType));
//}
if (inOffset < 0
|| inOffset + sampleCount > input.length
|| sampleCount < 0) {
throw new IllegalArgumentException("invalid input index: "
+"input.length="+input.length
+" inOffset="+inOffset
+" sampleCount="+sampleCount);
}
if (outByteOffset < 0
|| outByteOffset + (sampleCount * outByteStep) > output.length
|| outByteStep < getSampleSize(formatType)) {
throw new IllegalArgumentException("invalid output index: "
+"output.length="+output.length
+" outByteOffset="+outByteOffset
+" sampleCount="+sampleCount
+" format="+formatType2Str(formatType));
}
if (ditherBits!=0.0f && random==null) {
// create the random number generator for dithering
random=new Random();
}
int endSample = inOffset + sampleCount;
int iSample;
int outIndex = outByteOffset;
for (int inIndex = inOffset;
inIndex < endSample;
inIndex++, outIndex+=outByteStep) {
// do conversion
switch (formatType) {
case CT_8S:
output[outIndex]=quantize8(input[inIndex]*twoPower7, ditherBits);
break;
case CT_8U:
output[outIndex]=(byte) (quantize8((input[inIndex]*twoPower7), ditherBits)+128);
break;
case CT_16SB:
iSample=quantize16(input[inIndex]*twoPower15, ditherBits);
output[outIndex]=(byte) (iSample >> 8);
output[outIndex+1]=(byte) (iSample & 0xFF);
break;
case CT_16SL:
iSample=quantize16(input[inIndex]*twoPower15, ditherBits);
output[outIndex+1]=(byte) (iSample >> 8);
output[outIndex]=(byte) (iSample & 0xFF);
break;
case CT_24SB:
iSample=quantize24(input[inIndex]*twoPower23, ditherBits);
output[outIndex]=(byte) (iSample >> 16);
output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF);
output[outIndex+2]=(byte) (iSample & 0xFF);
break;
case CT_24SL:
iSample=quantize24(input[inIndex]*twoPower23, ditherBits);
output[outIndex+2]=(byte) (iSample >> 16);
output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF);
output[outIndex]=(byte) (iSample & 0xFF);
break;
case CT_32SB:
iSample=quantize32(input[inIndex]*twoPower31, ditherBits);
output[outIndex]=(byte) (iSample >> 24);
output[outIndex+1]=(byte) ((iSample >>> 16) & 0xFF);
output[outIndex+2]=(byte) ((iSample >>> 8) & 0xFF);
output[outIndex+3]=(byte) (iSample & 0xFF);
break;
case CT_32SL:
iSample=quantize32(input[inIndex]*twoPower31, ditherBits);
output[outIndex+3]=(byte) (iSample >> 24);
output[outIndex+2]=(byte) ((iSample >>> 16) & 0xFF);
output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF);
output[outIndex]=(byte) (iSample & 0xFF);
break;
default:
throw new IllegalArgumentException
("unsupported format="+formatType2Str(formatType));
}
}
}
}