/* * Copyright © 2007-2011 Rebecca G. Bettencourt / Kreative Software * <p> * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a> * <p> * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * <p> * Alternatively, the contents of this file may be used under the terms * of the GNU Lesser General Public License (the "LGPL License"), in which * case the provisions of LGPL License are applicable instead of those * above. If you wish to allow use of your version of this file only * under the terms of the LGPL License and not to allow others to use * your version of this file under the MPL, indicate your decision by * deleting the provisions above and replace them with the notice and * other provisions required by the LGPL License. If you do not delete * the provisions above, a recipient may use your version of this file * under either the MPL or the LGPL License. * @since KSFL 1.0 * @author Rebecca G. Bettencourt, Kreative Software */ package com.kreative.rsrc; import java.io.*; import com.kreative.ksfl.*; import com.kreative.rsrc.misc.IMA4Decoder; import com.kreative.rsrc.misc.MACEDecoder; /** * The <code>SoundResource</code> class represents a Mac OS sound resource. * @author Rebecca G. Bettencourt, Kreative Software */ public class SoundResource extends MacResource { /** * The resource type of a Mac OS sound resource, * the four-character constant <code>snd </code>. */ public static final int RESOURCE_TYPE = KSFLConstants.snd; public static final int SQUAREWAVESYNTH = (short)0x0001; public static final int WAVETABLESYNTH = (short)0x0003; public static final int SAMPLEDSYNTH = (short)0x0005; public static final int INITCHANLEFT = (int)0x0002; public static final int INITCHANRIGHT = (int)0x0003; public static final int WAVECHANNEL0 = (int)0x0004; public static final int WAVECHANNEL1 = (int)0x0005; public static final int WAVECHANNEL2 = (int)0x0006; public static final int WAVECHANNEL3 = (int)0x0007; public static final int INITMONO = (int)0x0080; public static final int INITSTEREO = (int)0x00C0; public static final int INITMACE3 = (int)0x0300; public static final int INITMACE6 = (int)0x0400; public static final int INITNOINTERP = (int)0x0004; public static final int INITNODROP = (int)0x0008; public static final int INITPANMASK = (int)0x0003; public static final int INITSRATEMASK = (int)0x0030; public static final int INITSTEREOMASK = (int)0x00C0; public static final int INITCOMPMASK = (int)0xFF00; public static final int NULLCMD = (short)0x0000; public static final int QUIETCMD = (short)0x0003; public static final int FLUSHCMD = (short)0x0004; public static final int REINITCMD = (short)0x0005; public static final int WAITCMD = (short)0x000A; public static final int PAUSECMD = (short)0x000B; public static final int RESUMECMD = (short)0x000C; public static final int CALLBACKCMD = (short)0x000D; public static final int SYNCCMD = (short)0x000E; public static final int AVAILABLECMD = (short)0x0018; public static final int VERSIONCMD = (short)0x0019; public static final int TOTALLOADCMD = (short)0x001A; public static final int LOADCMD = (short)0x001B; public static final int FREQDURATIONCMD = (short)0x0028; public static final int RESTCMD = (short)0x0029; public static final int FREQCMD = (short)0x002A; public static final int AMPCMD = (short)0x002B; public static final int TIMBRECMD = (short)0x002C; public static final int GETAMPCMD = (short)0x002D; public static final int VOLUMECMD = (short)0x002E; public static final int GETVOLUMECMD = (short)0x002F; public static final int WAVETABLECMD = (short)0x003C; public static final int SOUNDCMD = (short)0x0050; public static final int BUFFERCMD = (short)0x0051; public static final int RATECMD = (short)0x0052; public static final int GETRATECMD = (short)0x0055; public static final int DATAOFFSETFLAG = (short)0x8000; public static final int RATE_44KHZ = (int)0xAC440000; public static final int RATE_22KHZ = (int)0x56EE8BA3; public static final int RATE_22050KHZ = (int)0x56220000; public static final int RATE_11KHZ = (int)0x2B7745D1; public static final int RATE_11025KHZ = (int)0x2B110000; public static final int STDSH = (byte)0x00; public static final int EXTSH = (byte)0xFF; public static final int CMPSH = (byte)0xFE; private static final int RIFF = KSFLUtilities.fcc("RIFF"); private static final int WAVE = KSFLUtilities.fcc("WAVE"); private static final int FMT = KSFLUtilities.fcc("fmt "); private static final int DATA = KSFLUtilities.fcc("data"); private static final int CYNH = KSFLUtilities.fcc("Cynh"); private static final int FORM = KSFLUtilities.fcc("FORM"); private static final int AIFC = KSFLUtilities.fcc("AIFC"); private static final int FVER = KSFLUtilities.fcc("FVER"); private static final int COMM = KSFLUtilities.fcc("COMM"); private static final int SSND = KSFLUtilities.fcc("SSND"); private static final short EXPONENT_44KHZ = 0x400E; private static final long MANTISSA_44KHZ = 0xAC44000000000000L; private static final short EXPONENT_22KHZ = 0x400D; private static final long MANTISSA_22KHZ = 0xADDD1745D145826BL; private static final short EXPONENT_22050KHZ = 0x400D; private static final long MANTISSA_22050KHZ = 0xAC44000000000000L; private static final short EXPONENT_11KHZ = 0x400C; private static final long MANTISSA_11KHZ = 0xADDD1745D145826BL; private static final short EXPONENT_11025KHZ = 0x400C; private static final long MANTISSA_11025KHZ = 0xAC44000000000000L; private static final short COMPID_NONE = 0; private static final short COMPID_ACE_2TO1 = 1; private static final short COMPID_ACE_8TO3 = 2; private static final short COMPID_MACE_3TO1 = 3; private static final short COMPID_MACE_6TO1 = 4; private static final short COMPID_USE_FORMAT = -1; private static final int FORMAT_NONE = 0x4E4F4E45; // KSFLUtilities.fcc("NONE"); private static final int FORMAT_IMA4 = 0x696D6134; // KSFLUtilities.fcc("ima4"); private static final int FORMAT_ACE_2TO1 = 0x41434532; // KSFLUtilities.fcc("ACE2"); private static final int FORMAT_ACE_8TO3 = 0x41434538; // KSFLUtilities.fcc("ACE8"); private static final int FORMAT_MACE_3TO1 = 0x4D414333; // KSFLUtilities.fcc("MAC3"); private static final int FORMAT_MACE_6TO1 = 0x4D414336; // KSFLUtilities.fcc("MAC6"); // 0 1 2 3 4 5 6 7 8 9 a b c d e private static final byte[] COMPNAME_NONE = {0x0E,'n','o','t',' ','c','o','m','p','r','e','s','s','e','d', 0 }; private static final byte[] COMPNAME_IMA4 = {0x05,'i','m','a','4', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }; private static final byte[] COMPNAME_ACE_2TO1 = {0x0A,'A','C','E',' ','2','-','t','o','-','1', 0 , 0 , 0 , 0 , 0 }; private static final byte[] COMPNAME_ACE_8TO3 = {0x0A,'A','C','E',' ','8','-','t','o','-','3', 0 , 0 , 0 , 0 , 0 }; private static final byte[] COMPNAME_MACE_3TO1 = {0x0B,'M','A','C','E',' ','3','-','t','o','-','1', 0 , 0 , 0 , 0 }; private static final byte[] COMPNAME_MACE_6TO1 = {0x0B,'M','A','C','E',' ','6','-','t','o','-','1', 0 , 0 , 0 , 0 }; /** * Checks if a resource type is one this class knows how to handle. * The default implementation is to always return true. * It is recommended that subclasses override this. * @param type A resource type to check. * @return True if this class can handle this resource type, false otherwise. */ public static boolean isMyType(int type) { return (type == RESOURCE_TYPE); } /** * Constructs a new resource of type <code>snd </code> with the specified ID and data. * The name is set to an empty string, and all attributes are cleared. * @param id The resource ID. * @param data The resource data. */ public SoundResource(short id, byte[] data) { super(RESOURCE_TYPE, id, data); } /** * Constructs a new resource of type <code>snd </code> with the specified ID, name, and data. * All attributes are cleared. * @param id The resource ID. * @param name The resource name. * @param data The resource data. */ public SoundResource(short id, String name, byte[] data) { super(RESOURCE_TYPE, id, name, data); } /** * Constructs a new resource of type <code>snd </code> with the specified ID, attributes, and data. * The name is set to an empty string. * @param id The resource ID. * @param attr The resource attributes as a byte. * @param data The resource data. */ public SoundResource(short id, byte attr, byte[] data) { super(RESOURCE_TYPE, id, attr, data); } /** * Constructs a new resource of type <code>snd </code> with the specified ID, attributes, name, and data. * @param id The resource ID. * @param attr The resource attributes as a byte. * @param name The resource name. * @param data The resource data. */ public SoundResource(short id, byte attr, String name, byte[] data) { super(RESOURCE_TYPE, id, attr, name, data); } /** * Constructs a new resource with the specified type, ID, and data. * The name is set to an empty string, and all attributes are cleared. * @param type The resource type as an integer. * @param id The resource ID. * @param data The resource data. */ public SoundResource(int type, short id, byte[] data) { super(type, id, data); } /** * Constructs a new resource with the specified type, ID, name, and data. * All attributes are cleared. * @param type The resource type as an integer. * @param id The resource ID. * @param name The resource name. * @param data The resource data. */ public SoundResource(int type, short id, String name, byte[] data) { super(type, id, name, data); } /** * Constructs a new resource with the specified type, ID, attributes, and data. * The name is set to an empty string. * @param type The resource type as an integer. * @param id The resource ID. * @param attr The resource attributes as a byte. * @param data The resource data. */ public SoundResource(int type, short id, byte attr, byte[] data) { super(type, id, attr, data); } /** * Constructs a new resource with the specified type, ID, attributes, name, and data. * @param type The resource type as an integer. * @param id The resource ID. * @param attr The resource attributes as a byte. * @param name The resource name. * @param data The resource data. */ public SoundResource(int type, short id, byte attr, String name, byte[] data) { super(type, id, attr, name, data); } /** * Returns the format of the sound. * @return the format of the sound. */ public int getFormat() { return KSFLUtilities.getShort(data, 0); } /** * Returns the reference constant for a type 2 sound. * @return the reference constant for a type 2 sound. */ public int getRefCon() { switch (KSFLUtilities.getShort(data, 0)) { case 2: return KSFLUtilities.getShort(data, 2); default: return 0; } } /** * Returns the number of data formats for a type 1 sound. * @return the number of data formats for a type 1 sound. */ public int getDataFormatCount() { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getShort(data, 2); default: return 0; } } /** * Returns the data format ID for a type 1 sound. * @param index the index of the data format. * @return the data format ID. */ public int getDataFormatID(int index) { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getShort(data, 4 + 6*index); default: return 0; } } /** * Returns the data format init option for a type 1 sound. * @param index the index of the data format. * @return the data format init option. */ public int getDataFormatInitOption(int index) { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getInt(data, 6 + 6*index); default: return 0; } } /** * Returns the number of sound commands. * @return the number of sound commands. */ public int getCommandCount() { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getShort(data, 4 + 6*KSFLUtilities.getShort(data, 2)); case 2: return KSFLUtilities.getShort(data, 4); default: return 0; } } /** * Returns the sound command. * @param index the index of the sound command. * @return the sound command. */ public int getCommand(int index) { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getShort(data, 6 + 6*KSFLUtilities.getShort(data, 2) + 8*index); case 2: return KSFLUtilities.getShort(data, 6 + 8*index); default: return 0; } } /** * Returns the sound command first parameter. * @param index the index of the sound command. * @return the sound command first parameter. */ public int getCommandParam1(int index) { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getShort(data, 8 + 6*KSFLUtilities.getShort(data, 2) + 8*index); case 2: return KSFLUtilities.getShort(data, 8 + 8*index); default: return 0; } } /** * Returns the sound command second parameter. * @param index the index of the sound command. * @return the sound command second parameter. */ public int getCommandParam2(int index) { switch (KSFLUtilities.getShort(data, 0)) { case 1: return KSFLUtilities.getShort(data, 10 + 6*KSFLUtilities.getShort(data, 2) + 8*index); case 2: return KSFLUtilities.getShort(data, 10 + 8*index); default: return 0; } } /** * Returns the offset where sound data begins. * @return the offset where sound data begins. */ public int getSoundDataOffset() { switch (KSFLUtilities.getShort(data, 0)) { case 1: return 6 + 6*KSFLUtilities.getShort(data, 2) + 8*KSFLUtilities.getShort(data, 4 + 6*KSFLUtilities.getShort(data, 2)); case 2: return 6 + 8*KSFLUtilities.getShort(data, 4); default: return 2; } } /** * Returns the name of the codec used to compress the sound data. * If the codec is unrecognized, returns null. * @return the name of the codec used to compress the sound data. */ public String getCodecName() { if (getFormat() < 1 || getFormat() > 2) return null; for (int i = 0; i < getCommandCount(); i++) { int cmd = getCommand(i) & ~DATAOFFSETFLAG; if (!(cmd == NULLCMD || cmd == SOUNDCMD || cmd == BUFFERCMD)) return null; } int o = getSoundDataOffset(); int encoding = data[o+20]; if (encoding == -2) { int format = KSFLUtilities.getInt(data, o+40); short compressionID = KSFLUtilities.getShort(data, o+56); switch (compressionID) { case COMPID_NONE: return new String(COMPNAME_NONE).trim(); case COMPID_ACE_2TO1: return new String(COMPNAME_ACE_2TO1).trim(); case COMPID_ACE_8TO3: return new String(COMPNAME_ACE_8TO3).trim(); case COMPID_MACE_3TO1: return new String(COMPNAME_MACE_3TO1).trim(); case COMPID_MACE_6TO1: return new String(COMPNAME_MACE_6TO1).trim(); case COMPID_USE_FORMAT: switch (format) { case FORMAT_NONE: return new String(COMPNAME_NONE).trim(); case FORMAT_IMA4: return new String(COMPNAME_IMA4).trim(); case 0x59444B4A: return new String(COMPNAME_IMA4).trim(); // equals YDKJ. case FORMAT_ACE_2TO1: return new String(COMPNAME_ACE_2TO1).trim(); case FORMAT_ACE_8TO3: return new String(COMPNAME_ACE_8TO3).trim(); case FORMAT_MACE_3TO1: return new String(COMPNAME_MACE_3TO1).trim(); case FORMAT_MACE_6TO1: return new String(COMPNAME_MACE_6TO1).trim(); default: return null; } default: return null; } } else if (encoding == -1 || encoding == 0) { return new String(COMPNAME_NONE).trim(); } else { return null; } } /** * Converts this sound resource to the WAV format, if possible, and returns the WAV data. * If the sound cannot be converted, returns null. * @return WAV data. */ public byte[] toWav() { if (getFormat() < 1 || getFormat() > 2) return null; for (int i = 0; i < getCommandCount(); i++) { int cmd = getCommand(i) & ~DATAOFFSETFLAG; if (!(cmd == NULLCMD || cmd == SOUNDCMD || cmd == BUFFERCMD)) return null; } int o = getSoundDataOffset(); int numBytes = KSFLUtilities.getInt(data, o+4); int padding = 0; while (((numBytes+padding)&3)!=0) padding++; int sampleRate = KSFLUtilities.getInt(data, o+8); int loopStart = KSFLUtilities.getInt(data, o+12); int loopEnd = KSFLUtilities.getInt(data, o+16); int encoding = data[o+20]; int baseFrequency = data[o+21]; if (encoding == -2) { // compressed int numFrames = KSFLUtilities.getInt(data, o+22); int format = KSFLUtilities.getInt(data, o+40); short compressionID = KSFLUtilities.getShort(data, o+56); short sampleSize = KSFLUtilities.getShort(data, o+62); int sampleBytes = (sampleSize + 7) / 8; int clength = data.length -84;//strip header and junk byte[] newdata; if (format != FORMAT_IMA4 && format != 0x59444B4A) {// we can calculate a different way clength = numBytes * numFrames * sampleBytes; newdata = KSFLUtilities.copy(data, o+22, clength); } else { newdata = KSFLUtilities.copy(data, o+64, clength); } switch (compressionID) { case COMPID_NONE: // nothin' doin' break; case COMPID_MACE_3TO1: newdata = MACEDecoder.decompressMACE3(newdata, numBytes); break; case COMPID_MACE_6TO1: newdata = MACEDecoder.decompressMACE6(newdata, numBytes); break; case COMPID_USE_FORMAT: switch (format) { case FORMAT_NONE: // nothin' doin' break; case FORMAT_IMA4: case 0x59444B4A: newdata = IMA4Decoder.decompressIMA4(newdata, numBytes); break; case FORMAT_MACE_3TO1: newdata = MACEDecoder.decompressMACE3(newdata, numBytes); break; case FORMAT_MACE_6TO1: newdata = MACEDecoder.decompressMACE6(newdata, numBytes); break; default: return null; } break; default: return null; } int flength = newdata.length; int fpadding = 0; while (((flength+fpadding)&3)!=0) fpadding++; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream out2 = new DataOutputStream(out); // RIFF header out2.writeInt(RIFF); out2.writeInt(Integer.reverseBytes(56 + flength + fpadding)); // length of following data out2.writeInt(WAVE); // WAVE format // FORMAT chunk, 24 bytes total out2.writeInt(FMT ); out2.writeInt(0x10000000); // length of following data, little-endian out2.writeShort(0x0100); // codec; 01 = PCM out2.writeShort(Short.reverseBytes((short)numBytes)); // number of channels out2.writeInt(Integer.reverseBytes(sampleRate >>> 16)); // sample rate in Hz out2.writeInt(Integer.reverseBytes(numBytes * (sampleRate >>> 16) * sampleBytes)); // bytes per second out2.writeShort(Short.reverseBytes((short)(numBytes * sampleBytes))); // bytes per sample out2.writeShort(Short.reverseBytes((short)sampleSize)); // bits per sample // DATA chunk, (8 + numBytes + padding) bytes total out2.writeInt(DATA); out2.writeInt(Integer.reverseBytes(flength + fpadding)); // number of bytes to follow if (sampleBytes == 1) { for (int i = 0; i < newdata.length; i++) newdata[i] ^= 0x80; out2.write(newdata); for (int i = 0; i < fpadding; i++) out2.writeByte(0x80); } else { for (int i = 0; i < newdata.length; i += sampleBytes) { for (int j = 0, k = sampleBytes-1; j < sampleBytes/2; j++, k--) { byte t = newdata[i+j]; newdata[i+j] = newdata[i+k]; newdata[i+k] = t; } } out2.write(newdata); for (int i = 0; i < fpadding; i++) out2.writeByte(0); } // CYNTH chunk, 20 bytes total out2.writeInt(CYNH); out2.writeInt(0x0C000000); // length of following data, little-endian out2.writeInt(Integer.reverseBytes(loopStart)); // loop start, little-endian out2.writeInt(Integer.reverseBytes(loopEnd)); // loop end, little-endian out2.writeInt(Integer.reverseBytes(baseFrequency)); // base frequency, little-endian // done out2.close(); out.close(); return out.toByteArray(); } catch (IOException ioe) { return null; } } else if (encoding == -1) { // extended int numFrames = KSFLUtilities.getInt(data, o+22); short sampleSize = KSFLUtilities.getShort(data, o+48); int sampleBytes = (sampleSize + 7) / 8; byte[] newdata = KSFLUtilities.copy(data, o+64, numBytes * numFrames * sampleBytes); int flength = newdata.length; int fpadding = 0; while (((flength+fpadding)&3)!=0) fpadding++; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream out2 = new DataOutputStream(out); // RIFF header out2.writeInt(RIFF); out2.writeInt(Integer.reverseBytes(56 + flength + fpadding)); // length of following data out2.writeInt(WAVE); // WAVE format // FORMAT chunk, 24 bytes total out2.writeInt(FMT ); out2.writeInt(0x10000000); // length of following data, little-endian out2.writeShort(0x0100); // codec; 1 = PCM out2.writeShort(Short.reverseBytes((short)numBytes)); // number of channels out2.writeInt(Integer.reverseBytes(sampleRate >>> 16)); // sample rate in Hz out2.writeInt(Integer.reverseBytes(numBytes * (sampleRate >>> 16) * sampleBytes)); // bytes per second out2.writeShort(Short.reverseBytes((short)(numBytes * sampleBytes))); // bytes per sample out2.writeShort(Short.reverseBytes((short)sampleSize)); // bits per sample // DATA chunk, (8 + numBytes + padding) bytes total out2.writeInt(DATA); out2.writeInt(Integer.reverseBytes(flength + fpadding)); // number of bytes to follow if (sampleBytes == 1) { for (int i = 0; i < newdata.length; i++) newdata[i] ^= 0x80; out2.write(newdata); for (int i = 0; i < fpadding; i++) out2.writeByte(0x80); } else { for (int i = 0; i < newdata.length; i += sampleBytes) { for (int j = 0, k = sampleBytes-1; j < sampleBytes/2; j++, k--) { byte t = newdata[i+j]; newdata[i+j] = newdata[i+k]; newdata[i+k] = t; } } out2.write(newdata); for (int i = 0; i < fpadding; i++) out2.writeByte(0); } // CYNTH chunk, 20 bytes total out2.writeInt(CYNH); out2.writeInt(0x0C000000); // length of following data, little-endian out2.writeInt(Integer.reverseBytes(loopStart)); // loop start, little-endian out2.writeInt(Integer.reverseBytes(loopEnd)); // loop end, little-endian out2.writeInt(Integer.reverseBytes(baseFrequency)); // base frequency, little-endian // done out2.close(); out.close(); return out.toByteArray(); } catch (IOException ioe) { return null; } } else if (encoding == 0) { // uncompressed try { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream out2 = new DataOutputStream(out); // RIFF header out2.writeInt(RIFF); out2.writeInt(Integer.reverseBytes(56 + numBytes + padding)); // length of following data out2.writeInt(WAVE); // WAVE format // FORMAT chunk, 24 bytes total out2.writeInt(FMT ); out2.writeInt(0x10000000); // length of following data, little-endian out2.writeShort(0x0100); // codec; 1 = PCM out2.writeShort(0x0100); // number of channels; 1 = mono out2.writeInt(Integer.reverseBytes(sampleRate >>> 16)); // sample rate in Hz out2.writeInt(Integer.reverseBytes(sampleRate >>> 16)); // bytes per second out2.writeShort(0x0100); // bytes per sample; 1 = 8-bit mono out2.writeShort(0x0800); // bits per sample; 8 = 8-bit mono // DATA chunk, (8 + numBytes + padding) bytes total out2.writeInt(DATA); out2.writeInt(Integer.reverseBytes(numBytes + padding)); // number of bytes to follow out2.write(data, o+22, numBytes); for (int i = 0; i < padding; i++) out2.writeByte(0x80); // CYNTH chunk, 20 bytes total out2.writeInt(CYNH); out2.writeInt(0x0C000000); // length of following data, little-endian out2.writeInt(Integer.reverseBytes(loopStart)); // loop start, little-endian out2.writeInt(Integer.reverseBytes(loopEnd)); // loop end, little-endian out2.writeInt(Integer.reverseBytes(baseFrequency)); // base frequency, little-endian // done out2.close(); out.close(); return out.toByteArray(); } catch (IOException ioe) { return null; } } else { return null; } } /** * Converts this sound resource to the AIFF format, if possible, and returns the AIFF data. * If the sound cannot be converted, returns null. * @return AIFF data. */ public byte[] toAiff() { if (getFormat() < 1 || getFormat() > 2) return null; for (int i = 0; i < getCommandCount(); i++) { int cmd = getCommand(i) & ~DATAOFFSETFLAG; if (!(cmd == NULLCMD || cmd == SOUNDCMD || cmd == BUFFERCMD)) return null; } int o = getSoundDataOffset(); int encoding = data[o+20]; int baseFrequency = data[o+21]; int sampleRate = KSFLUtilities.getInt(data, o+8); int loopStart = KSFLUtilities.getInt(data, o+12); int loopEnd = KSFLUtilities.getInt(data, o+16); int numBytes = KSFLUtilities.getInt(data, o+4); int padding = 0; while (((numBytes+padding)&1)!=0) padding++; if (encoding == -2) { // compressed numBytes = KSFLUtilities.getInt(data, o+4);//actually channels in this case int numFrames = KSFLUtilities.getInt(data, o+22); short aiffSampleRateExponent = KSFLUtilities.getShort(data, o+26); long aiffSampleRateMantissa = KSFLUtilities.getLong(data, o+28); int format = KSFLUtilities.getInt(data, o+40); short compressionID = KSFLUtilities.getShort(data, o+56); short sampleSize = KSFLUtilities.getShort(data, o+62); int flength = data.length -84;//-140; //strip header and junk int fpadding = 0; while (((flength+fpadding)&1)!=0) fpadding++; if (format != FORMAT_IMA4 && format != 0x59444B4A) { int sampleBytes = (sampleSize + 7) / 8; flength = numBytes * numFrames * sampleBytes; fpadding = 0; while (((flength+fpadding)&1)!=0) fpadding++; } try { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream out2 = new DataOutputStream(out); // form chunk out2.writeInt(FORM); out2.writeInt(98 + flength + fpadding); out2.writeInt(AIFC); // format version chunk out2.writeInt(FVER); out2.writeInt(4); out2.writeInt(0xA2805140); // common chunk out2.writeInt(COMM); out2.writeInt(38); out2.writeShort(numBytes); // number of channels out2.writeInt(numFrames); // number of frames out2.writeShort(sampleSize); // bits per sample out2.writeShort(aiffSampleRateExponent); out2.writeLong(aiffSampleRateMantissa); switch (compressionID) { case COMPID_NONE: out2.writeInt(FORMAT_NONE); out2.write(COMPNAME_NONE); break; case COMPID_ACE_2TO1: out2.writeInt(FORMAT_ACE_2TO1); out2.write(COMPNAME_ACE_2TO1); break; case COMPID_ACE_8TO3: out2.writeInt(FORMAT_ACE_8TO3); out2.write(COMPNAME_ACE_8TO3); break; case COMPID_MACE_3TO1: out2.writeInt(FORMAT_MACE_3TO1); out2.write(COMPNAME_MACE_3TO1); break; case COMPID_MACE_6TO1: out2.writeInt(FORMAT_MACE_6TO1); out2.write(COMPNAME_MACE_6TO1); break; case COMPID_USE_FORMAT: switch (format) { case FORMAT_IMA4: out2.writeInt(FORMAT_IMA4); out2.write(COMPNAME_IMA4); break; case 0x59444B4A: out2.writeInt(FORMAT_IMA4); out2.write(COMPNAME_IMA4); break; case FORMAT_NONE: out2.writeInt(FORMAT_NONE); out2.write(COMPNAME_NONE); break; case FORMAT_ACE_2TO1: out2.writeInt(FORMAT_ACE_2TO1); out2.write(COMPNAME_ACE_2TO1); break; case FORMAT_ACE_8TO3: out2.writeInt(FORMAT_ACE_8TO3); out2.write(COMPNAME_ACE_8TO3); break; case FORMAT_MACE_3TO1: out2.writeInt(FORMAT_MACE_3TO1); out2.write(COMPNAME_MACE_3TO1); break; case FORMAT_MACE_6TO1: out2.writeInt(FORMAT_MACE_6TO1); out2.write(COMPNAME_MACE_6TO1); break; default: out2.writeInt(format); out2.write(new byte[16]); break; } break; default: return null; } // cynth chunk out2.writeInt(CYNH); out2.writeInt(12); out2.writeInt(loopStart); out2.writeInt(loopEnd); out2.writeInt(baseFrequency); // sound data chunk out2.writeInt(SSND); out2.writeInt(8 + flength); out2.writeInt(0); // offset out2.writeInt(0); // block size out2.write(data, o+64, flength); for (int i = 0; i < fpadding; i++) out2.writeByte(0); // done out2.close(); out.close(); return out.toByteArray(); } catch (IOException ioe) { return null; } } else if (encoding == -1) { // extended int numFrames = KSFLUtilities.getInt(data, o+22); short aiffSampleRateExponent = KSFLUtilities.getShort(data, o+26); long aiffSampleRateMantissa = KSFLUtilities.getLong(data, o+28); short sampleSize = KSFLUtilities.getShort(data, o+48); int sampleBytes = (sampleSize + 7) / 8; int flength = numBytes * numFrames * sampleBytes; int fpadding = 0; while (((flength+fpadding)&1)!=0) fpadding++; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream out2 = new DataOutputStream(out); // form chunk out2.writeInt(FORM); out2.writeInt(98 + flength + fpadding); out2.writeInt(AIFC); // format version chunk out2.writeInt(FVER); out2.writeInt(4); out2.writeInt(0xA2805140); // common chunk out2.writeInt(COMM); out2.writeInt(38); out2.writeShort(numBytes); // number of channels out2.writeInt(numFrames); // number of frames out2.writeShort(sampleSize); // bits per sample out2.writeShort(aiffSampleRateExponent); out2.writeLong(aiffSampleRateMantissa); out2.writeInt(FORMAT_NONE); out2.write(COMPNAME_NONE); // cynth chunk out2.writeInt(CYNH); out2.writeInt(12); out2.writeInt(loopStart); out2.writeInt(loopEnd); out2.writeInt(baseFrequency); // sound data chunk out2.writeInt(SSND); out2.writeInt(8 + flength); out2.writeInt(0); // offset out2.writeInt(0); // block size out2.write(data, o+64, flength); for (int i = 0; i < fpadding; i++) out2.writeByte(0); // done out2.close(); out.close(); return out.toByteArray(); } catch (IOException ioe) { return null; } } else if (encoding == 0) { // uncompressed try { ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream out2 = new DataOutputStream(out); // form chunk out2.writeInt(FORM); out2.writeInt(98 + numBytes + padding); out2.writeInt(AIFC); // format version chunk out2.writeInt(FVER); out2.writeInt(4); out2.writeInt(0xA2805140); // common chunk out2.writeInt(COMM); out2.writeInt(38); out2.writeShort(1); // number of channels out2.writeInt(numBytes); // number of frames out2.writeShort(8); // bits per sample switch (sampleRate) { case RATE_44KHZ : out2.writeShort(EXPONENT_44KHZ ); out2.writeLong(MANTISSA_44KHZ ); break; case RATE_22KHZ : out2.writeShort(EXPONENT_22KHZ ); out2.writeLong(MANTISSA_22KHZ ); break; case RATE_22050KHZ: out2.writeShort(EXPONENT_22050KHZ); out2.writeLong(MANTISSA_22050KHZ); break; case RATE_11KHZ : out2.writeShort(EXPONENT_11KHZ ); out2.writeLong(MANTISSA_11KHZ ); break; case RATE_11025KHZ: out2.writeShort(EXPONENT_11025KHZ); out2.writeLong(MANTISSA_11025KHZ); break; default: return null; } out2.writeInt(FORMAT_NONE); // compression type out2.write(COMPNAME_NONE); // compression name // cynth chunk out2.writeInt(CYNH); out2.writeInt(12); out2.writeInt(loopStart); out2.writeInt(loopEnd); out2.writeInt(baseFrequency); // sound data chunk out2.writeInt(SSND); out2.writeInt(8 + numBytes); out2.writeInt(0); // offset out2.writeInt(0); // block size byte[] newdata = KSFLUtilities.copy(data, o+22, numBytes); for (int i = 0; i < newdata.length; i++) newdata[i] ^= 0x80; out2.write(newdata); for (int i = 0; i < padding; i++) out2.writeByte(0); // done out2.close(); out.close(); return out.toByteArray(); } catch (IOException ioe) { return null; } } else { return null; } } }