diff options
author | RubbaBoy <[email protected]> | 2020-07-06 02:33:28 -0400 |
---|---|---|
committer | Phil Burk <[email protected]> | 2020-10-30 11:19:34 -0700 |
commit | 46888fae6eb7b1dd386f7af7d101ead99ae61981 (patch) | |
tree | 8969bbfd68d2fb5c0d8b86da49ec2eca230a72ab /src/com/jsyn/util/soundfile | |
parent | c51e92e813dd481603de078f0778e1f75db2ab05 (diff) |
Restructured project, added gradle, JUnit, logger, and more
Added Gradle (and removed ant), modernized testing via the JUnit framework, moved standalone examples from the tests directory to a separate module, removed sparsely used Java logger and replaced it with SLF4J. More work could be done, however this is a great start to greatly improving the health of the codebase.
Diffstat (limited to 'src/com/jsyn/util/soundfile')
-rw-r--r-- | src/com/jsyn/util/soundfile/AIFFFileParser.java | 227 | ||||
-rw-r--r-- | src/com/jsyn/util/soundfile/AudioFileParser.java | 129 | ||||
-rw-r--r-- | src/com/jsyn/util/soundfile/ChunkHandler.java | 49 | ||||
-rw-r--r-- | src/com/jsyn/util/soundfile/CustomSampleLoader.java | 60 | ||||
-rw-r--r-- | src/com/jsyn/util/soundfile/IFFParser.java | 307 | ||||
-rw-r--r-- | src/com/jsyn/util/soundfile/WAVEFileParser.java | 334 |
6 files changed, 0 insertions, 1106 deletions
diff --git a/src/com/jsyn/util/soundfile/AIFFFileParser.java b/src/com/jsyn/util/soundfile/AIFFFileParser.java deleted file mode 100644 index 2b09d78..0000000 --- a/src/com/jsyn/util/soundfile/AIFFFileParser.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.EOFException; -import java.io.IOException; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; -import com.jsyn.util.SampleLoader; - -public class AIFFFileParser extends AudioFileParser { - private static final String SUPPORTED_FORMATS = "Only 16 and 24 bit PCM or 32-bit float AIF files supported."; - static final int AIFF_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'F'; - static final int AIFC_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'C'; - static final int COMM_ID = ('C' << 24) | ('O' << 16) | ('M' << 8) | 'M'; - static final int SSND_ID = ('S' << 24) | ('S' << 16) | ('N' << 8) | 'D'; - static final int MARK_ID = ('M' << 24) | ('A' << 16) | ('R' << 8) | 'K'; - static final int INST_ID = ('I' << 24) | ('N' << 16) | ('S' << 8) | 'T'; - static final int NONE_ID = ('N' << 24) | ('O' << 16) | ('N' << 8) | 'E'; - static final int FL32_ID = ('F' << 24) | ('L' << 16) | ('3' << 8) | '2'; - static final int FL32_ID_LC = ('f' << 24) | ('l' << 16) | ('3' << 8) | '2'; - - int sustainBeginID = -1; - int sustainEndID = -1; - int releaseBeginID = -1; - int releaseEndID = -1; - boolean typeFloat = false; - - @Override - FloatSample finish() throws IOException { - setLoops(); - - if ((byteData == null)) { - throw new IOException("No data found in audio sample."); - } - float[] floatData = new float[numFrames * samplesPerFrame]; - if (bitsPerSample == 16) { - SampleLoader.decodeBigI16ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 24) { - SampleLoader.decodeBigI24ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 32) { - if (typeFloat) { - SampleLoader.decodeBigF32ToF32(byteData, 0, byteData.length, floatData, 0); - } else { - SampleLoader.decodeBigI32ToF32(byteData, 0, byteData.length, floatData, 0); - } - } else { - throw new IOException(SUPPORTED_FORMATS + " size = " + bitsPerSample); - } - - return makeSample(floatData); - } - - double read80BitFloat() throws IOException { - /* - * This is not a full decoding of the 80 bit number but it should suffice for the range we - * expect. - */ - byte[] bytes = new byte[10]; - parser.read(bytes); - int exp = ((bytes[0] & 0x3F) << 8) | (bytes[1] & 0xFF); - int mant = ((bytes[2] & 0xFF) << 16) | ((bytes[3] & 0xFF) << 8) | (bytes[4] & 0xFF); - // System.out.println( "exp = " + exp + ", mant = " + mant ); - return mant / (double) (1 << (22 - exp)); - } - - void parseCOMMChunk(IFFParser parser, int ckSize) throws IOException { - samplesPerFrame = parser.readShortBig(); - numFrames = parser.readIntBig(); - bitsPerSample = parser.readShortBig(); - frameRate = read80BitFloat(); - if (ckSize > 18) { - int format = parser.readIntBig(); - // Validate data format. - if ((format == FL32_ID) || (format == FL32_ID_LC)) { - typeFloat = true; - } else if (format == NONE_ID) { - typeFloat = false; - } else { - throw new IOException(SUPPORTED_FORMATS + " format " + IFFParser.IDToString(format)); - } - } - - bytesPerSample = (bitsPerSample + 7) / 8; - bytesPerFrame = bytesPerSample * samplesPerFrame; - } - - /* parse tuning and multi-sample info */ - @SuppressWarnings("unused") - void parseINSTChunk(IFFParser parser, int ckSize) throws IOException { - int baseNote = parser.readByte(); - int detune = parser.readByte(); - originalPitch = baseNote + (0.01 * detune); - - int lowNote = parser.readByte(); - int highNote = parser.readByte(); - - parser.skip(2); /* lo,hi velocity */ - int gain = parser.readShortBig(); - - int playMode = parser.readShortBig(); /* sustain */ - sustainBeginID = parser.readShortBig(); - sustainEndID = parser.readShortBig(); - - playMode = parser.readShortBig(); /* release */ - releaseBeginID = parser.readShortBig(); - releaseEndID = parser.readShortBig(); - } - - private void setLoops() { - SampleMarker cuePoint = cueMap.get(sustainBeginID); - if (cuePoint != null) { - sustainBegin = cuePoint.position; - } - cuePoint = cueMap.get(sustainEndID); - if (cuePoint != null) { - sustainEnd = cuePoint.position; - } - } - - void parseSSNDChunk(IFFParser parser, int ckSize) throws IOException { - long numRead; - // System.out.println("parseSSNDChunk()"); - int offset = parser.readIntBig(); - parser.readIntBig(); /* blocksize */ - parser.skip(offset); - dataPosition = parser.getOffset(); - int numBytes = ckSize - 8 - offset; - if (ifLoadData) { - byteData = new byte[numBytes]; - numRead = parser.read(byteData); - } else { - numRead = parser.skip(numBytes); - } - if (numRead != numBytes) - throw new EOFException("AIFF data chunk too short!"); - } - - void parseMARKChunk(IFFParser parser, int ckSize) throws IOException { - long startOffset = parser.getOffset(); - int numCuePoints = parser.readShortBig(); - // System.out.println( "parseCueChunk: numCuePoints = " + numCuePoints - // ); - for (int i = 0; i < numCuePoints; i++) { - // Some AIF files have a bogus numCuePoints so check to see if we - // are at end. - long numInMark = parser.getOffset() - startOffset; - if (numInMark >= ckSize) { - System.out.println("Reached end of MARK chunk with bogus numCuePoints = " - + numCuePoints); - break; - } - - int uniqueID = parser.readShortBig(); - int position = parser.readIntBig(); - int len = parser.read(); - String markerName = parseString(parser, len); - if ((len & 1) == 0) { - parser.skip(1); /* skip pad byte */ - } - - SampleMarker cuePoint = findOrCreateCuePoint(uniqueID); - cuePoint.position = position; - cuePoint.name = markerName; - - if (IFFParser.debug) { - System.out.println("AIFF Marker at " + position + ", " + markerName); - } - } - } - - /** - * Called by parse() method to handle FORM chunks in an AIFF specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @exception IOException If parsing fails, or IO error occurs. - */ - @Override - public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { - if ((ckID == IFFParser.FORM_ID) && (type != AIFF_ID) && (type != AIFC_ID)) - throw new IOException("Bad AIFF form type = " + IFFParser.IDToString(type)); - } - - /** - * Called by parse() method to handle chunks in an AIFF specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @exception IOException If parsing fails, or IO error occurs. - */ - @Override - public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { - switch (ckID) { - case COMM_ID: - parseCOMMChunk(parser, ckSize); - break; - case SSND_ID: - parseSSNDChunk(parser, ckSize); - break; - case MARK_ID: - parseMARKChunk(parser, ckSize); - break; - case INST_ID: - parseINSTChunk(parser, ckSize); - break; - default: - break; - } - } - -} diff --git a/src/com/jsyn/util/soundfile/AudioFileParser.java b/src/com/jsyn/util/soundfile/AudioFileParser.java deleted file mode 100644 index e7bb066..0000000 --- a/src/com/jsyn/util/soundfile/AudioFileParser.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2001 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.IOException; -import java.util.HashMap; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; - -/** - * Base class for various types of audio specific file parsers. - * - * @author (C) 2001 Phil Burk, SoftSynth.com - */ - -abstract class AudioFileParser implements ChunkHandler { - IFFParser parser; - protected byte[] byteData; - boolean ifLoadData = true; /* If true, load sound data into memory. */ - long dataPosition; /* - * Number of bytes from beginning of file where sound data resides. - */ - protected int bitsPerSample; - protected int bytesPerFrame; // in the file - protected int bytesPerSample; // in the file - protected HashMap<Integer, SampleMarker> cueMap = new HashMap<Integer, SampleMarker>(); - protected short samplesPerFrame; - protected double frameRate; - protected int numFrames; - protected double originalPitch = 60.0; - protected int sustainBegin = -1; - protected int sustainEnd = -1; - - public AudioFileParser() { - } - - /** - * @return Number of bytes from beginning of stream where sound data resides. - */ - public long getDataPosition() { - return dataPosition; - } - - /** - * This can be read by another thread when load()ing a sample to determine how many bytes have - * been read so far. - */ - public synchronized long getNumBytesRead() { - IFFParser p = parser; // prevent race - if (p != null) - return p.getOffset(); - else - return 0; - } - - /** - * This can be read by another thread when load()ing a sample to determine how many bytes need - * to be read. - */ - public synchronized long getFileSize() { - IFFParser p = parser; // prevent race - if (p != null) - return p.getFileSize(); - else - return 0; - } - - protected SampleMarker findOrCreateCuePoint(int uniqueID) { - SampleMarker cuePoint = cueMap.get(uniqueID); - if (cuePoint == null) { - cuePoint = new SampleMarker(); - cueMap.put(uniqueID, cuePoint); - } - return cuePoint; - } - - public FloatSample load(IFFParser parser) throws IOException { - this.parser = parser; - parser.parseAfterHead(this); - return finish(); - } - - abstract FloatSample finish() throws IOException; - - FloatSample makeSample(float[] floatData) throws IOException { - FloatSample floatSample = new FloatSample(floatData, samplesPerFrame); - - floatSample.setChannelsPerFrame(samplesPerFrame); - floatSample.setFrameRate(frameRate); - floatSample.setPitch(originalPitch); - - if (sustainBegin >= 0) { - floatSample.setSustainBegin(sustainBegin); - floatSample.setSustainEnd(sustainEnd); - } - - for (SampleMarker marker : cueMap.values()) { - floatSample.addMarker(marker); - } - - /* Set Sustain Loop by assuming first two markers are loop points. */ - if (floatSample.getMarkerCount() >= 2) { - floatSample.setSustainBegin(floatSample.getMarker(0).position); - floatSample.setSustainEnd(floatSample.getMarker(1).position); - } - return floatSample; - } - - protected String parseString(IFFParser parser, int textLength) throws IOException { - byte[] bar = new byte[textLength]; - parser.read(bar); - return new String(bar); - } -} diff --git a/src/com/jsyn/util/soundfile/ChunkHandler.java b/src/com/jsyn/util/soundfile/ChunkHandler.java deleted file mode 100644 index 6dfe26d..0000000 --- a/src/com/jsyn/util/soundfile/ChunkHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 1997 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.IOException; - -/** - * Handle IFF Chunks as they are parsed from an IFF or RIFF file. - * - * @see IFFParser - * @see AudioSampleAIFF - * @author (C) 1997 Phil Burk, SoftSynth.com - */ -interface ChunkHandler { - /** - * The parser will call this when it encounters a FORM or LIST chunk that contains other chunks. - * This handler can either read the form's chunks, or let the parser find them and call - * handleChunk(). - * - * @param ID a 4 byte identifier such as FORM_ID that identifies the IFF chunk type. - * @param numBytes number of bytes contained in the FORM, not counting the FORM type. - * @param type a 4 byte identifier such as AIFF_ID that identifies the FORM type. - */ - public void handleForm(IFFParser parser, int ID, int numBytes, int type) throws IOException; - - /** - * The parser will call this when it encounters a chunk that is not a FORM or LIST. This handler - * can either read the chunk's, or ignore it. The parser will skip over any unread data. Do NOT - * read past the end of the chunk! - * - * @param ID a 4 byte identifier such as SSND_ID that identifies the IFF chunk type. - * @param numBytes number of bytes contained in the chunk, not counting the ID and size field. - */ - public void handleChunk(IFFParser parser, int ID, int numBytes) throws IOException; -} diff --git a/src/com/jsyn/util/soundfile/CustomSampleLoader.java b/src/com/jsyn/util/soundfile/CustomSampleLoader.java deleted file mode 100644 index 14efde9..0000000 --- a/src/com/jsyn/util/soundfile/CustomSampleLoader.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; -import com.jsyn.util.AudioSampleLoader; - -public class CustomSampleLoader implements AudioSampleLoader { - - @Override - public FloatSample loadFloatSample(File fileIn) throws IOException { - FileInputStream fileStream = new FileInputStream(fileIn); - BufferedInputStream inputStream = new BufferedInputStream(fileStream); - return loadFloatSample(inputStream); - } - - @Override - public FloatSample loadFloatSample(URL url) throws IOException { - InputStream rawStream = url.openStream(); - BufferedInputStream inputStream = new BufferedInputStream(rawStream); - return loadFloatSample(inputStream); - } - - @Override - public FloatSample loadFloatSample(InputStream inputStream) throws IOException { - AudioFileParser fileParser; - IFFParser parser = new IFFParser(inputStream); - parser.readHead(); - if (parser.isRIFF()) { - fileParser = new WAVEFileParser(); - } else if (parser.isIFF()) { - fileParser = new AIFFFileParser(); - } else { - throw new IOException("Unsupported audio file type."); - } - return fileParser.load(parser); - } - -} diff --git a/src/com/jsyn/util/soundfile/IFFParser.java b/src/com/jsyn/util/soundfile/IFFParser.java deleted file mode 100644 index f429657..0000000 --- a/src/com/jsyn/util/soundfile/IFFParser.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright 1997 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Parse Electronic Arts style IFF File. IFF is a file format that allows "chunks" of data to be - * placed in a hierarchical file. It was designed by Jerry Morrison at Electronic Arts for the Amiga - * computer and is now used extensively by Apple Computer and other companies. IFF is an open - * standard. - * - * @see RIFFParser - * @see AudioSampleAIFF - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -class IFFParser extends FilterInputStream { - private long numBytesRead = 0; - private long totalSize = 0; - private int fileId; - static boolean debug = false; - - public static final int RIFF_ID = ('R' << 24) | ('I' << 16) | ('F' << 8) | 'F'; - public static final int LIST_ID = ('L' << 24) | ('I' << 16) | ('S' << 8) | 'T'; - public static final int FORM_ID = ('F' << 24) | ('O' << 16) | ('R' << 8) | 'M'; - - IFFParser(InputStream stream) { - super(stream); - numBytesRead = 0; - } - - /** - * Size of file based on outermost chunk size plus 8. Can be used to report progress when - * loading samples. - * - * @return Number of bytes in outer chunk plus header. - */ - public long getFileSize() { - return totalSize; - } - - /** - * Since IFF files use chunks with explicit size, it is important to keep track of how many - * bytes have been read from the file. Can be used to report progress when loading samples. - * - * @return Number of bytes read from stream, or skipped. - */ - public long getOffset() { - return numBytesRead; - } - - /** @return Next byte from stream. Increment offset by 1. */ - @Override - public int read() throws IOException { - numBytesRead++; - return super.read(); - } - - /** @return Next byte array from stream. Increment offset by len. */ - @Override - public int read(byte[] bar) throws IOException { - return read(bar, 0, bar.length); - } - - /** @return Next byte array from stream. Increment offset by len. */ - @Override - public int read(byte[] bar, int off, int len) throws IOException { - // Reading from a URL can return before all the bytes are available. - // So we keep reading until we get the whole thing. - int cursor = off; - int numLeft = len; - // keep reading data until we get it all - while (numLeft > 0) { - int numRead = super.read(bar, cursor, numLeft); - if (numRead < 0) - return numRead; - cursor += numRead; - numBytesRead += numRead; - numLeft -= numRead; - // System.out.println("read " + numRead + ", cursor = " + cursor + - // ", len = " + len); - } - return cursor - off; - } - - /** @return Skip forward in stream and add numBytes to offset. */ - @Override - public long skip(long numBytes) throws IOException { - numBytesRead += numBytes; - return super.skip(numBytes); - } - - /** Read 32 bit signed integer assuming Big Endian byte order. */ - public int readIntBig() throws IOException { - int result = read() & 0xFF; - result = (result << 8) | (read() & 0xFF); - result = (result << 8) | (read() & 0xFF); - int data = read(); - if (data == -1) - throw new EOFException("readIntBig() - EOF in middle of word at offset " + numBytesRead); - result = (result << 8) | (data & 0xFF); - return result; - } - - /** Read 32 bit signed integer assuming Little Endian byte order. */ - public int readIntLittle() throws IOException { - int result = read() & 0xFF; // LSB - result |= ((read() & 0xFF) << 8); - result |= ((read() & 0xFF) << 16); - int data = read(); - if (data == -1) - throw new EOFException("readIntLittle() - EOF in middle of word at offset " - + numBytesRead); - result |= (data << 24); - return result; - } - - /** Read 16 bit signed short assuming Big Endian byte order. */ - public short readShortBig() throws IOException { - short result = (short) ((read() << 8)); // MSB - int data = read(); - if (data == -1) - throw new EOFException("readShortBig() - EOF in middle of word at offset " - + numBytesRead); - result |= data & 0xFF; - return result; - } - - /** Read 16 bit signed short assuming Little Endian byte order. */ - public short readShortLittle() throws IOException { - short result = (short) (read() & 0xFF); // LSB - int data = read(); // MSB - if (data == -1) - throw new EOFException("readShortLittle() - EOF in middle of word at offset " - + numBytesRead); - result |= data << 8; - return result; - } - - public int readUShortLittle() throws IOException { - return (readShortLittle()) & 0x0000FFFF; - } - - /** Read 8 bit signed byte. */ - public byte readByte() throws IOException { - return (byte) read(); - } - - /** Read 32 bit signed int assuming IFF order. */ - public int readChunkSize() throws IOException { - if (isRIFF()) { - return readIntLittle(); - } - { - return readIntBig(); - } - } - - /** Convert a 4 character IFF ID to a String */ - public static String IDToString(int ID) { - byte bar[] = new byte[4]; - bar[0] = (byte) (ID >> 24); - bar[1] = (byte) (ID >> 16); - bar[2] = (byte) (ID >> 8); - bar[3] = (byte) ID; - return new String(bar); - } - - /** - * Parse the stream after reading the first ID and pass the forms and chunks to the ChunkHandler - */ - public void parseAfterHead(ChunkHandler handler) throws IOException { - int numBytes = readChunkSize(); - totalSize = numBytes + 8; - parseChunk(handler, fileId, numBytes); - if (debug) - System.out.println("parse() ------- end"); - } - - /** - * Parse the FORM and pass the chunks to the ChunkHandler The cursor should be positioned right - * after the type field. - */ - void parseForm(ChunkHandler handler, int ID, int numBytes, int type) throws IOException { - if (debug) { - System.out.println("IFF: parseForm >>>>>>>>>>>>>>>>>> BEGIN"); - } - while (numBytes > 8) { - int ckid = readIntBig(); - int size = readChunkSize(); - numBytes -= 8; - if (debug) { - System.out.println("chunk( " + IDToString(ckid) + ", " + size + " )"); - } - if (size < 0) { - throw new IOException("Bad IFF chunk Size: " + IDToString(ckid) + " = 0x" - + Integer.toHexString(ckid) + ", Size = " + size); - } - parseChunk(handler, ckid, size); - if ((size & 1) == 1) - size++; // even-up - numBytes -= size; - if (debug) { - System.out.println("parseForm: numBytes left in form = " + numBytes); - } - } - if (debug) { - System.out.println("IFF: parseForm <<<<<<<<<<<<<<<<<<<< END"); - } - - if (numBytes > 0) { - System.out.println("IFF Parser detected " + numBytes - + " bytes of garbage at end of FORM."); - skip(numBytes); - } - } - - /* - * Parse one chunk from IFF file. After calling handler, make sure stream is positioned at end - * of chunk. - */ - void parseChunk(ChunkHandler handler, int ckid, int numBytes) throws IOException { - long startOffset, endOffset; - int numRead; - startOffset = getOffset(); - if (isForm(ckid)) { - int type = readIntBig(); - if (debug) - System.out.println("parseChunk: form = " + IDToString(ckid) + ", " + numBytes - + ", " + IDToString(type)); - handler.handleForm(this, ckid, numBytes - 4, type); - endOffset = getOffset(); - numRead = (int) (endOffset - startOffset); - if (numRead < numBytes) - parseForm(handler, ckid, (numBytes - numRead), type); - } else { - handler.handleChunk(this, ckid, numBytes); - } - endOffset = getOffset(); - numRead = (int) (endOffset - startOffset); - if (debug) { - System.out.println("parseChunk: endOffset = " + endOffset); - System.out.println("parseChunk: numRead = " + numRead); - } - if ((numBytes & 1) == 1) - numBytes++; // even-up - if (numRead < numBytes) - skip(numBytes - numRead); - } - - public void readHead() throws IOException { - if (debug) - System.out.println("parse() ------- begin"); - numBytesRead = 0; - fileId = readIntBig(); - } - - public boolean isRIFF() { - return (fileId == RIFF_ID); - } - - public boolean isIFF() { - return (fileId == FORM_ID); - } - - /** - * Does the following chunk ID correspond to a container type like FORM? - */ - public boolean isForm(int ckid) { - if (isRIFF()) { - switch (ckid) { - case LIST_ID: - case RIFF_ID: - return true; - default: - return false; - } - } else { - switch (ckid) { - case LIST_ID: - case FORM_ID: - return true; - default: - return false; - } - } - } - -} diff --git a/src/com/jsyn/util/soundfile/WAVEFileParser.java b/src/com/jsyn/util/soundfile/WAVEFileParser.java deleted file mode 100644 index ec9350c..0000000 --- a/src/com/jsyn/util/soundfile/WAVEFileParser.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.EOFException; -import java.io.IOException; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; -import com.jsyn.util.SampleLoader; - -class WAVEFileParser extends AudioFileParser implements ChunkHandler { - static final short WAVE_FORMAT_PCM = 1; - static final short WAVE_FORMAT_IEEE_FLOAT = 3; - static final short WAVE_FORMAT_EXTENSIBLE = (short) 0xFFFE; - - static final byte[] KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { - 3, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 - }; - static final byte[] KSDATAFORMAT_SUBTYPE_PCM = { - 1, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 - }; - - static final int WAVE_ID = ('W' << 24) | ('A' << 16) | ('V' << 8) | 'E'; - static final int FMT_ID = ('f' << 24) | ('m' << 16) | ('t' << 8) | ' '; - static final int DATA_ID = ('d' << 24) | ('a' << 16) | ('t' << 8) | 'a'; - static final int CUE_ID = ('c' << 24) | ('u' << 16) | ('e' << 8) | ' '; - static final int FACT_ID = ('f' << 24) | ('a' << 16) | ('c' << 8) | 't'; - static final int SMPL_ID = ('s' << 24) | ('m' << 16) | ('p' << 8) | 'l'; - static final int LTXT_ID = ('l' << 24) | ('t' << 16) | ('x' << 8) | 't'; - static final int LABL_ID = ('l' << 24) | ('a' << 16) | ('b' << 8) | 'l'; - - int samplesPerBlock = 0; - int blockAlign = 0; - private int numFactSamples = 0; - private short format; - - WAVEFileParser() { - } - - @Override - FloatSample finish() throws IOException { - if ((byteData == null)) { - throw new IOException("No data found in audio sample."); - } - float[] floatData = new float[numFrames * samplesPerFrame]; - if (bitsPerSample == 16) { - SampleLoader.decodeLittleI16ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 24) { - SampleLoader.decodeLittleI24ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 32) { - if (format == WAVE_FORMAT_IEEE_FLOAT) { - SampleLoader.decodeLittleF32ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (format == WAVE_FORMAT_PCM) { - SampleLoader.decodeLittleI32ToF32(byteData, 0, byteData.length, floatData, 0); - } else { - throw new IOException("WAV: Unsupported format = " + format); - } - } else { - throw new IOException("WAV: Unsupported bitsPerSample = " + bitsPerSample); - } - - return makeSample(floatData); - } - - // typedef struct { - // long dwIdentifier; - // long dwPosition; - // ID fccChunk; - // long dwChunkStart; - // long dwBlockStart; - // long dwSampleOffset; - // } CuePoint; - - /* Parse various chunks encountered in WAV file. */ - void parseCueChunk(IFFParser parser, int ckSize) throws IOException { - int numCuePoints = parser.readIntLittle(); - if (IFFParser.debug) { - System.out.println("WAV: numCuePoints = " + numCuePoints); - } - if ((ckSize - 4) != (6 * 4 * numCuePoints)) - throw new EOFException("Cue chunk too short!"); - for (int i = 0; i < numCuePoints; i++) { - int dwName = parser.readIntLittle(); /* dwName */ - int position = parser.readIntLittle(); // dwPosition - parser.skip(3 * 4); // fccChunk, dwChunkStart, dwBlockStart - int sampleOffset = parser.readIntLittle(); // dwPosition - - if (IFFParser.debug) { - System.out.println("WAV: parseCueChunk: #" + i + ", dwPosition = " + position - + ", dwName = " + dwName + ", dwSampleOffset = " + sampleOffset); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.position = position; - } - } - - void parseLablChunk(IFFParser parser, int ckSize) throws IOException { - int dwName = parser.readIntLittle(); - int textLength = (ckSize - 4) - 1; // don't read NUL terminator - String text = parseString(parser, textLength); - if (IFFParser.debug) { - System.out.println("WAV: label id = " + dwName + ", text = " + text); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.name = text; - } - - void parseLtxtChunk(IFFParser parser, int ckSize) throws IOException { - int dwName = parser.readIntLittle(); - int dwSampleLength = parser.readIntLittle(); - parser.skip(4 + (4 * 2)); // purpose through codepage - int textLength = (ckSize - ((4 * 4) + (4 * 2))) - 1; // don't read NUL - // terminator - if (textLength > 0) { - String text = parseString(parser, textLength); - if (IFFParser.debug) { - System.out.println("WAV: ltxt id = " + dwName + ", dwSampleLength = " - + dwSampleLength + ", text = " + text); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.comment = text; - } - } - - void parseFmtChunk(IFFParser parser, int ckSize) throws IOException { - format = parser.readShortLittle(); - samplesPerFrame = parser.readShortLittle(); - frameRate = parser.readIntLittle(); - parser.readIntLittle(); /* skip dwAvgBytesPerSec */ - blockAlign = parser.readShortLittle(); - bitsPerSample = parser.readShortLittle(); - - if (IFFParser.debug) { - System.out.println("WAV: format = 0x" + Integer.toHexString(format)); - System.out.println("WAV: bitsPerSample = " + bitsPerSample); - System.out.println("WAV: samplesPerFrame = " + samplesPerFrame); - } - bytesPerFrame = blockAlign; - bytesPerSample = bytesPerFrame / samplesPerFrame; - samplesPerBlock = (8 * blockAlign) / bitsPerSample; - - if (format == WAVE_FORMAT_EXTENSIBLE) { - int extraSize = parser.readShortLittle(); - short validBitsPerSample = parser.readShortLittle(); - int channelMask = parser.readIntLittle(); - byte[] guid = new byte[16]; - parser.read(guid); - if (IFFParser.debug) { - System.out.println("WAV: extraSize = " + extraSize); - System.out.println("WAV: validBitsPerSample = " + validBitsPerSample); - System.out.println("WAV: channelMask = " + channelMask); - System.out.print("guid = {"); - for (int i = 0; i < guid.length; i++) { - System.out.print(guid[i] + ", "); - } - System.out.println("}"); - } - if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - format = WAVE_FORMAT_IEEE_FLOAT; - } else if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_PCM)) { - format = WAVE_FORMAT_PCM; - } - } - if ((format != WAVE_FORMAT_PCM) && (format != WAVE_FORMAT_IEEE_FLOAT)) { - throw new IOException( - "Only WAVE_FORMAT_PCM and WAVE_FORMAT_IEEE_FLOAT supported. format = " + format); - } - if ((bitsPerSample != 16) && (bitsPerSample != 24) && (bitsPerSample != 32)) { - throw new IOException( - "Only 16 and 24 bit PCM or 32-bit float WAV files supported. width = " - + bitsPerSample); - } - } - - private boolean matchBytes(byte[] bar1, byte[] bar2) { - if (bar1.length != bar2.length) - return false; - for (int i = 0; i < bar1.length; i++) { - if (bar1[i] != bar2[i]) - return false; - } - return true; - } - - private int convertByteToFrame(int byteOffset) throws IOException { - if (blockAlign == 0) { - throw new IOException("WAV file has bytesPerBlock = zero"); - } - if (samplesPerFrame == 0) { - throw new IOException("WAV file has samplesPerFrame = zero"); - } - int nFrames = (samplesPerBlock * byteOffset) / (samplesPerFrame * blockAlign); - return nFrames; - } - - private int calculateNumFrames(int numBytes) throws IOException { - int nFrames; - if (numFactSamples > 0) { - // nFrames = numFactSamples / samplesPerFrame; - nFrames = numFactSamples; // FIXME which is right - } else { - nFrames = convertByteToFrame(numBytes); - } - return nFrames; - } - - // Read fraction in range of 0 to 0xFFFFFFFF and - // convert to 0.0 to 1.0 range. - private double readFraction(IFFParser parser) throws IOException { - // Put L at end or we get -1. - long maxFraction = 0x0FFFFFFFFL; - // Get unsigned fraction. Have to fit in long. - long fraction = (parser.readIntLittle()) & maxFraction; - return (double) fraction / (double) maxFraction; - } - - void parseSmplChunk(IFFParser parser, int ckSize) throws IOException { - parser.readIntLittle(); // Manufacturer - parser.readIntLittle(); // Product - parser.readIntLittle(); // Sample Period - int unityNote = parser.readIntLittle(); - double pitchFraction = readFraction(parser); - originalPitch = unityNote + pitchFraction; - - parser.readIntLittle(); // SMPTE Format - parser.readIntLittle(); // SMPTE Offset - int numLoops = parser.readIntLittle(); - parser.readIntLittle(); // Sampler Data - - int lastCueID = Integer.MAX_VALUE; - for (int i = 0; i < numLoops; i++) { - int cueID = parser.readIntLittle(); - parser.readIntLittle(); // type - int loopStartPosition = parser.readIntLittle(); - // Point to sample one after. - int loopEndPosition = parser.readIntLittle() + 1; - // TODO handle fractional loop sizes? - double endFraction = readFraction(parser); - parser.readIntLittle(); // playCount - - // Use lowest numbered cue. - if (cueID < lastCueID) { - sustainBegin = loopStartPosition; - sustainEnd = loopEndPosition; - } - } - } - - void parseFactChunk(IFFParser parser, int ckSize) throws IOException { - numFactSamples = parser.readIntLittle(); - } - - void parseDataChunk(IFFParser parser, int ckSize) throws IOException { - long numRead; - dataPosition = parser.getOffset(); - if (ifLoadData) { - byteData = new byte[ckSize]; - numRead = parser.read(byteData); - } else { - numRead = parser.skip(ckSize); - } - if (numRead != ckSize) { - throw new EOFException("WAV data chunk too short! Read " + numRead + " instead of " - + ckSize); - } - numFrames = calculateNumFrames(ckSize); - } - - @Override - public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { - if ((ckID == IFFParser.RIFF_ID) && (type != WAVE_ID)) - throw new IOException("Bad WAV form type = " + IFFParser.IDToString(type)); - } - - /** - * Called by parse() method to handle chunks in a WAV specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @return number of bytes left in chunk - */ - @Override - public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { - switch (ckID) { - case FMT_ID: - parseFmtChunk(parser, ckSize); - break; - case DATA_ID: - parseDataChunk(parser, ckSize); - break; - case CUE_ID: - parseCueChunk(parser, ckSize); - break; - case FACT_ID: - parseFactChunk(parser, ckSize); - break; - case SMPL_ID: - parseSmplChunk(parser, ckSize); - break; - case LABL_ID: - parseLablChunk(parser, ckSize); - break; - case LTXT_ID: - parseLtxtChunk(parser, ckSize); - break; - default: - break; - } - } - - /* - * (non-Javadoc) - * @see com.softsynth.javasonics.util.AudioSampleLoader#isLittleEndian() - */ - boolean isLittleEndian() { - return true; - } - -} |