// https://github.com/s4l4x/audio-analysis/blob/master/src/com/badlogic/audio/io/WaveDecoder.java /** * A simple class that can read in the PCM data from a * Wav file, converting the data to signed 32-bit floats * in the range [-1,1], merging stereo channels to a mono * channel for processing. This only supports 16-bit signed * stereo and mono Wav files with a sampling rate of 44100. * * @author mzechner * */ sclass WAVDecoder /*implements com.badlogic.audio.io.Decoder*/ implements AutoCloseable { /** the input stream we read from **/ EndianDataInputStream in; /** number of channels **/ int channels; /** sample rate in Herz**/ float sampleRate; bool ignoreSampleRate = true; bool twentyFourBit; // data length in bytes according to header int specifiedDataLength; int remainingDataBytes; static int defaultBufferSize = 128*1024; //1024*1024 *(File wavFile) { this(fileInputStream(wavFile)); } *(InputStream stream) ctex { in = new EndianDataInputStream( new BufferedInputStream( stream, defaultBufferSize) ); if( !in.read4ByteString().equals( "RIFF" ) ) throw new IllegalArgumentException( "not a wav" ); in.readIntLittleEndian(); if( !in.read4ByteString().equals( "WAVE" ) ) throw new IllegalArgumentException( "expected WAVE tag" ); S s = in.read4ByteString(); if (eq(s, "bext")) { // skip extension block in.skip(in.readIntLittleEndian()); s = in.read4ByteString(); } if (!s.equals("fmt ")) throw new IllegalArgumentException( "expected fmt tag, got: " + s); if( in.readIntLittleEndian() != 16 ) throw new IllegalArgumentException( "expected wave chunk size to be 16" ); int format; if ((format = in.readShortLittleEndian()) != 1) throw new IllegalArgumentException( "expected format to be 1, got: " + format); channels = in.readShortLittleEndian(); //print("Channels: " + channels); sampleRate = in.readIntLittleEndian(); if (sampleRate != 44100) if (ignoreSampleRate) { // print("Sample rate: " + sampleRate); } else fail("Not 44100 sampling rate: " + sampleRate); in.readIntLittleEndian(); in.readShortLittleEndian(); int fmt = in.readShortLittleEndian(); if (fmt == 24) twentyFourBit = true; else if (fmt != 16) fail("Only 16/24-bit samples supported: " + fmt); S tag; while (!(tag = in.read4ByteString()).equals("data")) { //print("Skipping tag " + tag); //fail( "expected data tag, got: " + tag); int len = in.readIntLittleEndian(); in.skip(len); } remainingDataBytes = specifiedDataLength = in.readIntLittleEndian(); } /** * Tries to read in samples.length samples, merging stereo to a mono * channel by averaging and converting non float formats to float 32-bit. * Returns the number of samples actually read. Guarantees that samples.length * samples are read in if there was enough data in the stream. * * @param samples The samples array to write the samples to * @return The number of samples actually read. */ public int readMonoSamples(short[] samples, int n default samples.length) { int readSamples = 0; for (int i = 0; i < n && remainingDataBytes > 0; i++) { double sample = 0; try { for j to channels: sample += readSample(); sample /= channels; samples[i] = (short) iround(max(-32768, min(32767, sample))); readSamples++; } catch (Exception ex) { break; } // ouch } ret readSamples; } public int readStereoSamples(short[] samples, int n default samples.length) { int readSamples = 0; for (int i = 0; i < n && remainingDataBytes > 0; i += 2) { double sample = 0; try { short left = readSample(); short right; if (channels > 1) right = readSample(); else right = left; samples[i] = left; samples[i+1] = right; readSamples += 2; } catch (Exception ex) { break; } // ouch } ret readSamples; } short readSample() ctex { if (twentyFourBit) { --remainingDataBytes; in.read(); } remainingDataBytes -= 2; ret in.readShortLittleEndian(); } int bytesPerValue() { ret twentyFourBit ? 3 : 2; } int totalValues() { ret specifiedDataLength/bytesPerValue(); } // skips this number of samples (what is the terminology again // for 1 value vs values) public void skipSamples(long samples) ctex { long bytesToSkip = samples*channels*bytesPerValue(); remainingDataBytes -= bytesToSkip; in.skip(bytesToSkip); } public void close() ctex { in.close(); } }