aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/jsyn/util/soundfile/IFFParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/jsyn/util/soundfile/IFFParser.java')
-rw-r--r--src/com/jsyn/util/soundfile/IFFParser.java307
1 files changed, 307 insertions, 0 deletions
diff --git a/src/com/jsyn/util/soundfile/IFFParser.java b/src/com/jsyn/util/soundfile/IFFParser.java
new file mode 100644
index 0000000..f429657
--- /dev/null
+++ b/src/com/jsyn/util/soundfile/IFFParser.java
@@ -0,0 +1,307 @@
+/*
+ * 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;
+ }
+ }
+ }
+
+}