001package jmri.jmrit.sound;
002
003import java.util.Arrays;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Wrap a byte array to provide WAV file functionality
009 *
010 * @author Bob Jacobsen Copyright (C) 2006
011 */
012public class WavBuffer {
013
014    /**
015     * Create from already existing byte array.
016     *
017     * @param content a WAV format byte array, starting with a RIFF header
018     */
019    public WavBuffer(byte[] content) {
020        buffer = Arrays.copyOf(content, content.length);
021
022        initFmt();
023        initData();
024    }
025
026    /**
027     * Create from contents of file.
028     * The file contents are expected to be in .wav format,
029     * starting with a RIFF header.
030     * @param file file containing the .wav.
031     * @throws java.io.IOException on error.
032     */
033    public WavBuffer(java.io.File file) throws java.io.IOException {
034        if (file == null) {
035            throw new java.io.IOException("Null file during ctor");
036        }
037        java.io.InputStream s = new java.io.BufferedInputStream(
038                new java.io.FileInputStream(file));
039
040        try {
041            buffer = new byte[(int) file.length()];
042            int count = s.read(buffer);
043            if (count != buffer.length) {
044                log.warn("Excepted {} bytes but read {} from file {}", buffer.length, count, file.getName());
045            }
046
047            initFmt();
048            initData();
049        } catch (java.io.IOException e1) {
050            log.error("error reading file", e1);
051            throw e1;
052        } finally {
053            try {
054                s.close();
055            } catch (java.io.IOException e2) {
056                log.error("Exception closing file", e2);
057            }
058        }
059    }
060
061    /**
062     * Find a specific header in the .wav fragment.
063     * @param i1 index 1.
064     * @param i2 index 2.
065     * @param i3 index 3.
066     * @param i4 index 4.
067     * @return offset of the 1st byte of the header in the buffer
068     */
069    public int findHeader(int i1, int i2, int i3, int i4) {
070        // find chunk and set offset
071        int index = 12; // skip RIFF header
072        while (index < buffer.length) {
073            // new chunk
074            if (buffer[index] == i1
075                    && buffer[index + 1] == i2
076                    && buffer[index + 2] == i3
077                    && buffer[index + 3] == i4) {
078                // found it, header in place
079                return index;
080            } else {
081                // skip
082                index = index + 8
083                        + fourByte(index + 4);
084            }
085        }
086        log.error("Didn't find chunk");
087        return 0;
088    }
089
090    /**
091     * Cache info from (first) "fmt " header
092     */
093    private void initFmt() {
094        fmtOffset = findHeader(0x66, 0x6D, 0x74, 0x20);
095        if (fmtOffset > 0) {
096            return;
097        }
098        log.error("Didn't find fmt chunk");
099    }
100
101    /**
102     * Cache info from (first) "data" header
103     */
104    private void initData() {
105        dataOffset = findHeader(0x64, 0x61, 0x74, 0x61);
106        if (dataOffset > 0) {
107            return;
108        }
109        log.error("Didn't find data chunk");
110    }
111
112    int fmtOffset;
113    int dataOffset;
114
115    byte[] buffer;
116
117    public float getSampleRate() {
118        return fourByte(fmtOffset + 12);
119    }
120
121    public int getSampleSizeInBits() {
122        return twoByte(fmtOffset + 22);
123    }
124
125    public int getChannels() {
126        return twoByte(fmtOffset + 10);
127    }
128
129    public boolean getBigEndian() {
130        return false;
131    }
132
133    public boolean getSigned() {
134        return (getSampleSizeInBits() > 8);
135    }
136
137    /**
138     * Offset to the first data byte in the buffer.
139     * @return first data byte offset.
140     */
141    public int getDataStart() {
142        return dataOffset + 8;
143    }
144
145    /**
146     * Size of the data segment in bytes.
147     * @return data size in bytes.
148     */
149    public int getDataSize() {
150        return fourByte(dataOffset + 4);
151    }
152
153    /**
154     * Offset to the last data byte in the buffer.
155     * One more than this points to the next header.
156     * @return data end value.
157     */
158    public int getDataEnd() {
159        return dataOffset + 8 + getDataSize() - 1;
160    }
161
162    int twoByte(int index) {
163        return buffer[index] + buffer[index + 1] * 256;
164    }
165
166    int fourByte(int index) {
167        return (buffer[index] & 0xFF)
168                + (buffer[index + 1] & 0xFF) * 256
169                + (buffer[index + 2] & 0xFF) * 256 * 256
170                + (buffer[index + 3] & 0xFF) * 256 * 256 * 256;
171    }
172
173    public byte[] getByteArray() {
174        return Arrays.copyOf(buffer, buffer.length);
175    }
176
177    private final static Logger log = LoggerFactory.getLogger(WavBuffer.class);
178}