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;
}