Download the file

/** * wavdump: a utility to parse and dump WAV audio files. * <br>© 2019 Ian Cameron Smith. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.hermit.wavdump; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * WAV file dumper. * * The purpose of this class is to dump the content of a WAV file * for illustrative purposes only; in other words, to illustrate how * a WAV file is structured. * * THIS IS NOT PRODUCTION CODE. It has not been tested on most WAV * file formats, such as 8-bit audio, mono, multi-channel, etc. It * is purely for educational purposes, and is probably flawed even * for that. Use at your own risk. * * References: * http://soundfile.sapp.org/doc/WaveFormat/ * http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html */ public class Dumper { // ******************************************************************** // // Public Types. // ******************************************************************** // /** * Enumeration of the data display modes supported for various types * of lengthy data. */ public enum DataMode { /** Display all the data. */ FULL, /** Display a summary of the first and last items. */ SHORT, /** Don't display data. */ NONE; } /** * User-initiated exception. * * <p>This exception class represents an error based on what the * user has asked us to do; such as loading a non-existent file. * This error should be reported as a simple information message. */ public class UserException extends Exception { /** * Create an exception with a String message. * * @param s Error message. */ public UserException(String s) { super(s); } /** * Create an exception with a String message. * * @param s Error message. * @param e Exception which indicates the specific problem. */ public UserException(String s, Throwable e) { super(s, e); } // UUID. private static final long serialVersionUID = 1L; } /** * Application-initiated exception. * * <p>This exception class represents an error caused by a problem * within the application; ie. something that shouldn't happen. * This error should be reported as an internal error, with appropriate * debugging. */ public class AppException extends RuntimeException { /** * Create an exception with a String message. * * @param s Error message. */ public AppException(String s) { super(s); } /** * Create an exception with a String message. * * @param s Error message. * @param e Exception which indicates the specific problem. */ public AppException(String s, Throwable e) { super(s, e); } // UUID. private static final long serialVersionUID = 1L; } // ******************************************************************** // // Constructor. // ******************************************************************** // /** * Create a dumper to parse the given WAV file. * * @param file File to parse; must be a WAV file. * @param smode Audio sample display mode. * @param xmode XML display mode. */ public Dumper(File file, DataMode smode, DataMode xmode) { wavFile = file; sampleMode = smode; xmlMode = xmode; } // ******************************************************************** // // WAV File Parsing Interface. // ******************************************************************** // /** * Dump the file. * * @throws UserException An error occurred in the given file. */ public void dump() throws UserException { try (FileInputStream fis = new FileInputStream(wavFile); DataInputStream dis = new DataInputStream(fis)) { dump(dis); } catch (IOException e) { throw new UserException("Can't open " + wavFile.getPath(), e); } } // ******************************************************************** // // WAV File Parsing. // ******************************************************************** // /** * Dump the given stream. * * @param dis The input stream to dump. * @throws UserException An error occurred in the given stream. */ private void dump(DataInputStream dis) throws UserException { // Dump chunks from the file. Generally there should be 1 // chunk, and it should be of type RIFF; but we will dump // whatever we find. for (int index = 0; ; ++index) if (dumpChunk(dis, 1, index) == 0) break; } /** * Dump a chunk (or sub-chunk, etc) from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @param index Index of this chunk in its parent. * @return The total size of the chunk we found, * including its headers; zero if we hit EOF. * @throws UserException An error occurred in the given stream. */ private long dumpChunk(DataInputStream dis, int in, int index) throws UserException { // Read the sub-chunk ID. String id = read4Char(dis); if (id == null) { System.out.printf("<EOF>\n"); return 0L; } // Get the sub-chunk size. This is the size of the chunk after // this number. long size = readUInt32(dis); // Dump the content. dumpContent(dis, in, id, size); // If the content is odd-sized, skip the padding byte. if (size % 2 != 0) { skipAByte(dis); // Account for the padding size in our parent list. ++size; } // Return the total size taken up by this sub-chunk, // including the format and size headers. return size + 8; } /** * Dump the content of a sub-chunk from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @param id The sub-chunk ID. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpContent(DataInputStream dis, int in, String id, long size) throws UserException { switch (id) { // The top-level chunk should be RIFF, which is a list of sub- // chunks; a LIST chunk is basically the same thing. case "RIFF": case "LIST": dumpChunkList(dis, in, id, size); break; case "fmt ": // Note: F M T <space> System.out.printf("%s%4s: %,d bytes:\n", indent(in), id, size); dumpWavFmt(dis, in + 1, size); break; case "data": dumpWavData(dis, in, size); break; case "bext": System.out.printf("%s%4s: %,d bytes:\n", indent(in), id, size); dumpBextData(dis, in + 1, size); break; // These are from the INFO chunk. case "IARL": case "IART": case "ICMS": case "ICMT": case "ICOP": case "ICRD": case "IENG": case "IGNR": case "IKEY": case "IMED": case "INAM": case "IPRD": case "ISBJ": case "ISFT": case "ISRC": case "ISRF": case "ITCH": dumpTextData(dis, in, id, size); break; // "iXML" is BWF extended meta-data, in XML format, // so it's basically a chunk of text. case "iXML": dumpBigTextData(dis, in, id, size); break; // "_PMX" is an Adobe special -- it's basically XMP meta-data, // which is XML, so it's basically a chunk of text. case "_PMX": dumpBigTextData(dis, in, id, size); break; default: System.out.printf("%s%4s: %,d bytes (unknown)\n", indent(in), id, size); // Just skip it. skipBytes(dis, size); break; } } /** * Dump the content of a compound chunk from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @param parent The name of the parent chunk: usually * RIFF or LIST. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpChunkList(DataInputStream dis, int in, String parent, long size) throws UserException { // We must have the sub-format. if (size < 4) throw new UserException("Invalid chunk list size " + size + " in " + wavFile.getPath()); // Get the format. Deduct it from the size. String fmt = read4Char(dis); size -= 4; System.out.printf("%s%4s / %4s: %,d bytes:\n", indent(in), parent, fmt, size); // Dump sub-chunks up to the given size. If we hit EOF during // this, that's an error. for (int index = 0; size > 0; ++index) { long sub = dumpChunk(dis, in + 1, index); if (sub == 0L) throw new UserException("Unexpected EOF in " + parent + " in " + wavFile.getPath()); size -= sub; } } /** * Dump the content of a WAV "fmt " sub-chunk from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpWavFmt(DataInputStream dis, int in, long size) throws UserException { // Get the audio format. 1 is PCM; 3 FLOAT; others unknown. wavFormat = readUInt16(dis); // Get the number of channels. wavChannels = readUInt16(dis); // Get the sample rate. wavSampleRate = readUInt32(dis); // Get the byte rate. long brate = readUInt32(dis); // Get the bytes per sample across all channels. int bytes = readUInt16(dis); // Get the bits per sample per channel. wavSampleSize = readUInt16(dis); // Sanity check it. if (wavSampleSize % 8 != 0) throw new UserException("WAV fmt: bits non byte-aligned: " + wavSampleSize); if (wavSampleSize < 8 || wavSampleSize > 32) throw new UserException("WAV fmt: absurd bits: " + wavSampleSize); if (bytes != wavChannels * (wavSampleSize / 8)) throw new UserException("WAV fmt: bytes mismatch: " + bytes); if (brate != wavSampleRate * wavChannels * (wavSampleSize / 8)) throw new UserException("WAV fmt: byte rate mismatch: " + brate); // Print it all out. String fmtDesc; switch (wavFormat) { case 1: fmtDesc = "PCM"; break; case 3: fmtDesc = "FLOAT"; break; default: fmtDesc = "UNK:" + wavFormat; break; } System.out.printf("%sWAV %s: %d chan %d bits %d/sec\n", indent(in), fmtDesc, wavChannels, wavSampleSize, wavSampleRate); // If we haven't read it all (can be true for non-PCM data), // skip the remainder. size -= 16; if (size > 0) skipBytes(dis, size); } /** * Dump the content of a WAV "data" sub-chunk from the given stream. * Because this is a huge mess of numbers, we'll just print the * start and end chunks. * * @param dis The input stream to dump. * @param in The indent level. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpWavData(DataInputStream dis, int in, long size) throws UserException { // We need to have the format info. if (wavFormat == 0) throw new UserException("WAV data: missing format in WAV file"); // If the format is not PCM or FLOAT, just say so. if (wavFormat != 1 && wavFormat != 3) { System.out.printf("%sWAV data format %d unknown\n", indent(in), wavFormat); skipBytes(dis, size); return; } if (wavFormat == 3 && wavSampleSize != 32) { System.out.printf("%sWAV float data size %d not supported\n", indent(in), wavSampleSize); skipBytes(dis, size); return; } // Display some lines of audio data, if required. final int bytesPerLine = wavChannels * (wavSampleSize / 8); switch (sampleMode) { case NONE: // Don't display anything but the size. System.out.printf("%s%4s: %,d bytes.\n", indent(in), "data", size); break; case SHORT: System.out.printf("%s%4s: %,d bytes:\n", indent(in), "data", size); // Display some lines from the start. final int dispSize = bytesPerLine * AUDIO_DATA_LINES; for (int i = 0; i < AUDIO_DATA_LINES && size >= bytesPerLine; ++i) { dumpWavLine(dis, in + 1); size -= bytesPerLine; } // Display some lines from the end, if there's enough. if (size > dispSize) { // Skip the stuff in the middle. final long middle = size - dispSize; final long lines = middle / bytesPerLine; System.out.printf("%s... (%,d lines)\n", indent(in + 1), lines); skipBytes(dis, middle); size -= middle; // Dump the last lines. for (int i = 0; i < AUDIO_DATA_LINES && size >= bytesPerLine; ++i) { dumpWavLine(dis, in + 1); size -= bytesPerLine; } } else if (size > 0) { long lines = size / bytesPerLine; System.out.printf("%s... (%,d lines)\n", indent(in + 1), lines); } break; case FULL: System.out.printf("%s%4s: %,d bytes:\n", indent(in), "data", size); // Display all the lines of samples. while (size >= bytesPerLine) { dumpWavLine(dis, in + 1); size -= bytesPerLine; } break; } // Skip any remaining data. skipBytes(dis, size); } /** * Dump the a line of data (i.e. one sample across all channels) * from a WAV "data" sub-chunk from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpWavLine(DataInputStream dis, int in) throws UserException { System.out.printf("%s", indent(in)); switch (wavFormat) { case 1: dumpWavLineInt(dis); break; case 3: dumpWavLineFloat(dis); break; default: throw new AppException("dumpWavLine got format " + wavFormat); } System.out.printf("\n"); } /** * Dump the a line of integer data (i.e. one sample across all channels) * from a WAV "data" sub-chunk from the given stream. * * @param dis The input stream to dump. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpWavLineInt(DataInputStream dis) throws UserException { for (int c = 0; c < wavChannels; ++c) { long samp = wavSampleSize == 8 ? readUIntN(dis, wavSampleSize) : readSIntN(dis, wavSampleSize); System.out.printf(" %,12d", samp); } } /** * Dump the a line of float data (i.e. one sample across all channels) * from a WAV "data" sub-chunk from the given stream. The sample * size must be 32. * * @param dis The input stream to dump. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpWavLineFloat(DataInputStream dis) throws UserException { assert wavSampleSize == 32; for (int c = 0; c < wavChannels; ++c) { int samp = (int) readUIntN(dis, 32); int mantissa = (samp & 0x007fffff) | 0x00800000; double fmantissa = (double) mantissa / (double) 0x007fffff; int exponent = ((samp & 0x7f800000) >>> 23) - 127; int sign = ((samp & 0x80000000) >>> 31) * -2 + 1; System.out.printf(" %c%,8fx2^%04d", sign < 0 ? '-' : '+', fmantissa, exponent); } } /** * Dump the content of a WAV "bext" (BWF meta-data) sub-chunk * from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpBextData(DataInputStream dis, int in, long size) throws UserException { String description = readNtString(dis, 256); size -= 256; String originator = readNtString(dis, 32); size -= 32; String origRef = readNtString(dis, 32); size -= 32; String origDate = readNtString(dis, 10); size -= 10; String origTime = readNtString(dis, 8); size -= 8; @SuppressWarnings("unused") long timeRefLow = readUInt32(dis); @SuppressWarnings("unused") long timeRefHigh = readUInt32(dis); size -= 8; int version = readUInt16(dis); size -= 2; @SuppressWarnings("unused") byte[] umid = readNBytes(dis, 64); size -= 64; @SuppressWarnings("unused") int loudVal = readSInt16(dis); @SuppressWarnings("unused") int loudRange = readSInt16(dis); @SuppressWarnings("unused") int maxTruePeak = readSInt16(dis); @SuppressWarnings("unused") int maxMomLoudness = readSInt16(dis); @SuppressWarnings("unused") int maxShortLoudness = readSInt16(dis); size -= 10; // Skip the reserved space. skipBytes(dis, 180); size -= 180; if (size < 0) throw new UserException("WAV bext: size too small"); // Read the remaining characters as the coding history. String coding = readNtString(dis, (int) size); // Print it all out. System.out.printf("%sBEXT version %d:\n", indent(in), version); if (description != null) System.out.printf("%sdescription: %s\n", indent(in + 1), description); if (originator != null) System.out.printf("%soriginator: %s\n", indent(in + 1), originator); if (origRef != null) System.out.printf("%sorig. ref : %s\n", indent(in + 1), origRef); if (origDate != null) System.out.printf("%sdate: %s\n", indent(in + 1), origDate); if (origTime != null) System.out.printf("%stime: %s\n", indent(in + 1), origTime); if (coding != null) System.out.printf("%scoding hist: %s\n", indent(in + 1), coding); } /** * Dump the content of a textual sub-chunk from the given stream. * * @param dis The input stream to dump. * @param in The indent level. * @param id The name of this sub-chunk. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpTextData(DataInputStream dis, int in, String id, long size) throws UserException { String text = readNtString(dis, (int) size); // Print it all out. System.out.printf("%s%4s: %s\n", indent(in), id, text); } /** * Dump the content of a large (multi-line) textual sub-chunk from * the given stream. We trim blank lines -- unfortunately Adobe _PMX * chunks include lots of blank lines. * * @param dis The input stream to dump. * @param in The indent level. * @param id The name of this sub-chunk. * @param size The size of the sub-chunk content. * @throws UserException An error occurred in the given stream, * including an EOF in the content. */ private void dumpBigTextData(DataInputStream dis, int in, String id, long size) throws UserException { // Read the text, and break it into lines. String text = readNtString(dis, (int) size); String lines[] = text.split("\\r?\\n"); // Trim trailing white space from each line. for (int i = 0 ; i < lines.length; ++i) lines[i] = lines[i].replaceAll("\\s+$",""); switch (xmlMode) { case NONE: // Display nothing but the line count. System.out.printf("%s%4s: %d lines.\n", indent(in), id, lines.length); break; case SHORT: // Display the head and tail. System.out.printf("%s%4s: %d lines:\n", indent(in), id, lines.length); // Display the first TEXT_DATA_LINES non-blank lines. int i = 0; for (int n = 0; i < lines.length && n < TEXT_DATA_LINES; ++i) { String l = lines[i]; // Print non-blank lines. We don't actually trim the // white space, to preserve indentation. if (l.length() > 0) { System.out.printf("%s%s\n", indent(in + 1), l); ++n; } } // If there are lines left, display the last TEXT_DATA_LINES lines. if (i < lines.length) { // Count TEXT_DATA_LINES non-blank lines back from the end. int end = lines.length; int count = 0; while (end > i && count < TEXT_DATA_LINES) { --end; if (lines[end].length() > 0) ++count; } // Push the index up to the tail-end section. if (i < end) { System.out.printf("%s... (%,d lines)\n", indent(in + 1), end - i); i = end; } // Print the tail-end lines. for ( ; i < lines.length; ++i) { String l = lines[i]; if (l.length() > 0) System.out.printf("%s%s\n", indent(in + 1), l); } } break; case FULL: // Print it all out. System.out.printf("%s%4s: %d lines:\n", indent(in), id, lines.length); for (String l : lines) { // Print non-blank lines. We don't actually trim the // white space, to preserve indentation. if (l.length() > 0) System.out.printf("%s%s\n", indent(in + 1), l); } break; } } // ******************************************************************** // // File Utilities. // ******************************************************************** // /** * Read a 4-char identifier from the given stream. * * @param dis The input stream to read from. * @return A String containing the 4-char identifier; * null on EOF. * @throws UserException An error occurred in the given stream. */ private String read4Char(DataInputStream dis) throws UserException { StringBuffer id = new StringBuffer(4); // Read the first byte. An EOF is valid here. try { int b1 = dis.readUnsignedByte(); id.append((char) b1); } catch (EOFException eof) { return null; } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } // Read the remaining 3 bytes. try { int b2 = dis.readUnsignedByte(); id.append((char) b2); int b3 = dis.readUnsignedByte(); id.append((char) b3); int b4 = dis.readUnsignedByte(); id.append((char) b4); } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } return id.toString(); } /** * Read a null-terminated string from a fixed-size field * in the given stream. * * @param dis The input stream to read from. * @param field The field size; this is the maximum * size of the string, but if shorter it * will be null-terminated. * @return The String read from the field; null * if it's empty. * @throws UserException An error occurred in the given stream, * including EOF. */ private String readNtString(DataInputStream dis, int field) throws UserException { StringBuffer sval = new StringBuffer(field); try { for ( ; field > 0; --field) { int b = dis.readUnsignedByte(); if (b == 0) { --field; break; } sval.append((char) b); } if (field > 0) skipBytes(dis, field); } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } return sval.length() == 0 ? null : sval.toString(); } /** * Read a 16-bit unsigned integer from the given stream. * * @param dis The input stream to read from. * @return The integer value. * @throws UserException An error occurred in the given stream, * including EOF. */ private int readUInt16(DataInputStream dis) throws UserException { int val = 0; for (int i = 0; i < 2; ++i) { try { int b = dis.readUnsignedByte(); val |= b << 8 * i; } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } return val; } /** * Read a 16-bit signed integer from the given stream. * * @param dis The input stream to read from. * @return The integer value. * @throws UserException An error occurred in the given stream, * including EOF. */ private int readSInt16(DataInputStream dis) throws UserException { int val = 0; try { int b1 = dis.readUnsignedByte(); val |= b1; int b2 = dis.readByte(); val |= b2 << 8; } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } return val; } /** * Read a 32-bit unsigned integer from the given stream. * * @param dis The input stream to read from. * @return The integer value. * @throws UserException An error occurred in the given stream, * including EOF. */ private long readUInt32(DataInputStream dis) throws UserException { long val = 0; for (int i = 0; i < 4; ++i) { try { int b = dis.readUnsignedByte(); val |= b << 8 * i; } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } return val; } /** * Read an N-bit unsigned integer from the given stream. * * @param dis The input stream to read from. * @param n The number of bits in the integer. Must * be a multiple of 8, and 8 - 32. * @return The integer value. * @throws UserException An error occurred in the given stream, * including EOF. */ private long readUIntN(DataInputStream dis, int n) throws UserException { long val = 0; for (int i = 0; i < n / 8; ++i) { try { int b = dis.readUnsignedByte(); val |= b << 8 * i; } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } return val; } /** * Read an N-bit signed integer from the given stream. * * @param dis The input stream to read from. * @param n The number of bits in the integer. Must * be a multiple of 8, and 8 - 32. * @return The integer value. * @throws UserException An error occurred in the given stream, * including EOF. */ private long readSIntN(DataInputStream dis, int n) throws UserException { long val = 0; for (int i = 0; i < (n / 8) - 1; ++i) { try { int b = dis.readUnsignedByte(); val |= b << 8 * i; } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } try { int b = dis.readByte(); val |= b << n - 8; } catch (EOFException e) { throw new UserException("Unexpected EOF in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } return val; } /** * Read a number of bytes from the given stream. * * @param dis The input stream to read from. * @param size The number of bytes to read. * @return A new buffer containing the bytes read. * @throws UserException An error occurred in the given stream, * including EOF. */ private byte[] readNBytes(DataInputStream dis, int size) throws UserException { try { byte[] buf = new byte[size]; int r = dis.read(buf); if (r != size) throw new UserException("Unexpected EOF in " + wavFile.getPath()); return buf; } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } /** * Skip a number of bytes from the given stream. * * @param dis The input stream to read from. * @param size The number of bytes to skip. * @throws UserException An error occurred in the given stream, * including EOF. */ private void skipBytes(DataInputStream dis, long size) throws UserException { try { long rem = size; while (rem > 0) { int r = dis.read(skipBuf, 0, (int) Math.min(rem, skipBuf.length)); if (r < 0) throw new UserException("Unexpected EOF skipping " + (size - rem) + "/" + size + " bytes in " + wavFile.getPath()); rem -= r; } } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } /** * Skip a single byte from the given stream. * * @param dis The input stream to read from. * @throws UserException An error occurred in the given stream, * including EOF. */ private void skipAByte(DataInputStream dis) throws UserException { try { dis.readByte(); } catch (EOFException e) { throw new UserException("Unexpected EOF skipping byte in " + wavFile.getPath(), e); } catch (IOException e) { throw new UserException("Error in " + wavFile.getPath(), e); } } /** * Create a padding string to indent the output by a given amount. * * @param n Levels of indent. * @return A String containing the required padding. */ private String indent(int n) { StringBuffer sb = new StringBuffer(n * 4); for (int i = 0; i < n; ++i) sb.append(" "); return sb.toString(); } // ******************************************************************** // // Private Constants. // ******************************************************************** // // The size of the buffer we allocate for skipping junk. private static final int SKIP_BUF_SIZE = 1024 * 64; // The number of lines of audio data to show. private static final int AUDIO_DATA_LINES = 8; // The number of lines of big text fields to show. private static final int TEXT_DATA_LINES = 8; // ******************************************************************** // // Private Data. // ******************************************************************** // // The WAV input file we're reading. private final File wavFile; // Audio sample display mode. private final DataMode sampleMode; // XML display mode. private final DataMode xmlMode; // Junk buffer for skipping data. private final byte[] skipBuf = new byte[SKIP_BUF_SIZE]; // The WAV data format, read from the file. private int wavFormat = 0; // The number of channels in the WAV data, read from the file. private int wavChannels = 0; // The sample rate of the WAV data, read from the file. private long wavSampleRate = 0; // The sample size in bits of the WAV data, read from the file. // Ths is checked to be a multiple of 8. private int wavSampleSize = 0; }