/*
*
* Copyright (c) 2006-2007 Paul John Leonard
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Frinika is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Jul 18, 2006
*
*
* Based on code from
* http://www.jensign.com/JavaScience/www/mmedia/riffread/riffread.java
* by M. Gallant 2/14/97
*
*Mods by pjl
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* Frinika is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.audio.io;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.org.toot.audio.core.AudioBuffer;
import uk.org.toot.audio.core.AudioProcess;
/*
* Wraps up a wav file and allows access using a RandomAccessFileIF. Provides a
* view of the file that allows reads before the start and after the end of the
* file you'll just get zeros returned.
*
* Implements reading of 16 signed 1 or 2 channel littleendian wav files.
*
* Job of initializing the file is done by AudioWavReader super class
*/
public class AudioReader extends AudioWavReader implements BlockableAudioProcess,AudioProcess,LimitedAudioReader {
RandomAccessFileIF bfis;
protected long startByte;
protected long endByte;
long startFrame;
byte byteBuff[];
// Used to point to current read position
// bytes from the start of audio data in the file
// NOT THE START OF THE FILE (see audioDataStartBytePtr)
protected long fPtrBytes;
double sampleRate;
private boolean closed;
public AudioReader(RandomAccessFileIF fisIF,float Fs) throws IOException {
super(fisIF.getRandomAccessFile());
sampleRate = format.getSampleRate();
if (sampleRate != Fs) {
try {
throw new Exception(" audio file is not correct sample rate." +sampleRate+" should be" + Fs);
} catch (Exception ex) {
Logger.getLogger(AudioReader.class.getName()).log(Level.SEVERE, null, ex);
}
}
startByte = 0;
startFrame =0;
endByte = audioDataByteLength;
closed = endByte!=0;
bfis = fisIF;
fisIF.seek(audioDataStartBytePtr, false); // non real time seek fills
// the buffers
}
public final long milliToByte(double milli) {
return nChannels * 2 * (long) (milli * sampleRate / 1000000.0);
}
public void seekTimeInMicros(double micros, boolean realTime)
throws IOException {
long framePos = (long) (micros * sampleRate / 1000000.0);
seekFrame(framePos, realTime);
}
/**
*
* @param framePos
* frame postition reltive to start of audio. e.g. zero is start
* of audio.
*
* @throws IOException
*/
public void seekFrame(long framePos, boolean realTime) throws IOException {
// pointer into audio data section
fPtrBytes = framePos * 2 * nChannels;
if (fPtrBytes >= startByte) {
// Offset the seek into audio section
if (fPtrBytes < audioDataByteLength && fPtrBytes < endByte)
bfis.seek(fPtrBytes + audioDataStartBytePtr, realTime);
} else {
// Seek to the start of audio section
bfis.seek(audioDataStartBytePtr + startByte, realTime);
}
}
public boolean eof() {
try {
return fPtrBytes - audioDataStartBytePtr >= bfis.length();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
public void setBoundsInMicros(double start,double end) {
assert(start<=end);
startByte = Math.max(0, milliToByte(start));
startFrame=startByte/2/nChannels;
endByte = Math.min(audioDataByteLength,milliToByte(end));
}
public void close() {
// TODO Auto-generated method stub
}
public void open() {
// TODO Auto-generated method stub
}
/**
*
* this version will block if the file is being written to and there is not enough
* data to fill the buffer
*
* @param buffer
* @return
* @throws IOException
*/
public void processAudioBlock(AudioBuffer buffer) throws Exception {
if (closed) {
processAudio(buffer);
return;
}
int n=buffer.getSampleCount();
// wait for data of file closed
while(n+fPtrBytes - audioDataStartBytePtr >= bfis.length()) {
if (getLengthInFrames() > 0 ) {
closed=true;
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
int nBytes = nChannels * 2 * buffer.getSampleCount();
boolean realTime = buffer.isRealTime();
if (byteBuff == null || byteBuff.length != nBytes) {
byteBuff = new byte[nBytes];
}
int nread = bfis.read(byteBuff, 0, nBytes, false);
fill(buffer, 0, n);
// return AUDIO_OK;
}
/**
*
*
* Read from file into byte buffer and advance the fPtrBytes pointer it is
* OK to read before/after start/end of the file you'll just get zeros.
* fPtrBytes is advanced by appropriate byte count.
*
* @param byteBuffer
* buffer to fill
* @param offSet
* offset into byteBuffer
* @param n
* number of bytes to be read
* @throws IOException
*/
public int processAudio(AudioBuffer buffer) {
int nBytes = nChannels * 2 * buffer.getSampleCount();
boolean realTime = buffer.isRealTime();
if (byteBuff == null || byteBuff.length != nBytes) {
byteBuff = new byte[nBytes];
}
// valid limits for the chunk to be read.
int startChunk = 0; // first valid byte of data
int endChunk = nBytes; // last byte + 1
long minEndByte = Math.min(endByte, audioDataByteLength);
try {
if (fPtrBytes < startByte) {
int nRead = (int) (nBytes + fPtrBytes - startByte); // bytes of
// this read
// after
// start of audioData
if (nRead > 0) {
startChunk = nBytes - nRead; // read into the last
// portion of
bfis.read(byteBuff, startChunk, nRead, realTime);
} else {
fPtrBytes += nBytes;
return AUDIO_OK;
}
} else if (fPtrBytes <= minEndByte) {
// Zeros after the end of the data
int nExtra = (int) (fPtrBytes + nBytes - minEndByte);
if (nExtra > 0) {
// Some bytes after end of the audio section ?
endChunk = nBytes - nExtra;
bfis.read(byteBuff, 0, endChunk, realTime);
} else {
// all data in audio section
int nread = bfis.read(byteBuff, 0, nBytes, realTime);
if (nread != nBytes)
try {
throw new Exception(" Ooops only read " + nread
+ " out of " + nBytes);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else {
fPtrBytes += nBytes;
return AUDIO_OK;
}
processAudioImp(buffer,startChunk,endChunk);
} catch (IOException e) {
e.printStackTrace();
}
fPtrBytes += nBytes;
return AUDIO_OK;
}
protected void processAudioImp(AudioBuffer buffer, int startChunk,
int endChunk) {
fill(buffer, startChunk, endChunk);
}
/**
*
*
* @param buffer
* @param startChunk
* @param endChunk
* @param gain1
* @param gain2
*/
protected void fillLinearInterpolate(AudioBuffer buffer, int startChunk,
int endChunk, double gain1, double gain2) {
double dG = (gain2 - gain1) / (endChunk - startChunk) / nChannels / 2.0;
if (nChannels == 2) {
float[] left = buffer.getChannel(0);
float[] right = buffer.getChannel(1);
for (int n = startChunk / 2; n < endChunk / 2; n++) {
float sample = ((short) ((0xff & byteBuff[(n * 2) + 0]) + ((0xff & byteBuff[(n * 2) + 1]) * 256)) / 32768f);
sample *= gain1;
if (n % 2 == 0)
left[n / 2] += sample;
else
right[n / 2] += sample;
gain1 += dG;
}
} else {
float[] left = buffer.getChannel(0);
for (int n = startChunk; n < endChunk; n += 2) {
float val = ((short) ((0xff & byteBuff[n]) + ((0xff & byteBuff[n + 1]) * 256)) / 32768f);
left[n / 2] += val * gain1;
gain1 += dG;
}
}
}
protected void fillConstantGain(AudioBuffer buffer, int startChunk,
int endChunk, double gain) {
if (nChannels == 2) {
float[] left = buffer.getChannel(0);
float[] right = buffer.getChannel(1);
for (int n = startChunk / 2; n < endChunk / 2; n++) {
float sample = ((short) ((0xff & byteBuff[(n * 2) + 0]) + ((0xff & byteBuff[(n * 2) + 1]) * 256)) / 32768f);
sample *= gain;
if (n % 2 == 0)
left[n / 2] += sample;
else
right[n / 2] += sample;
}
} else {
float[] left = buffer.getChannel(0);
for (int n = startChunk; n < endChunk; n += 2) {
float val = ((short) ((0xff & byteBuff[n]) + ((0xff & byteBuff[n + 1]) * 256)) / 32768f);
left[n / 2] += val * gain;
}
}
}
protected void fill(AudioBuffer buffer, int startChunk,int endChunk) {
if (nChannels == 2) {
float[] left = buffer.getChannel(0);
float[] right = buffer.getChannel(1);
for (int n = startChunk / 2; n < endChunk / 2; n++) {
float sample = ((short) ((0xff & byteBuff[(n * 2) + 0]) + ((0xff & byteBuff[(n * 2) + 1]) * 256)) / 32768f);
if (n % 2 == 0)
left[n / 2] += sample;
else
right[n / 2] += sample;
}
} else {
float[] left = buffer.getChannel(0);
for (int n = startChunk; n < endChunk; n += 2) {
float val = ((short) ((0xff & byteBuff[n]) + ((0xff & byteBuff[n + 1]) * 256)) / 32768f);
left[n / 2] += val ;
}
}
}
public int getEnvelopedLengthInFrames() {
return (int) ((endByte - startByte) / nChannels / 2);
}
public void seekEnvelopeStart(boolean b) throws IOException {
bfis.seek(audioDataStartBytePtr + startByte, b);
// pointer into audio data section
fPtrBytes= startByte;
}
public void seekFrameInEnvelope(long framePtr, boolean b) throws IOException {
seekFrame(startFrame+framePtr,b);
}
public double getSampleRate() {
return sampleRate;
}
}