/*
* Created on Mar 10, 2007
*
* Copyright (c) 2006-2007 P.J.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
*/
package com.frinika.audio.io;
import com.frinika.audio.io.RandomAccessFileIF;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Provides an implementation of RandomAccessFileIF that uses a cyclic cache.
* Collaborates with a BufferedRandomAccessFileManager.
* This should be used to prefetch data and avoid disk seek glitches
*
*
* File relative pointers
*
* ptr1 -> (ptr2-1) files sample in buffer
* endPtr end of file samples (+1)
* ptr being read from the cache.
*
* @author pjl
*
*/
public class BufferedRandomAccessFile implements RandomAccessFileIF {
private RandomAccessFile in;
private byte[] cyclicCache;
private int size; // size of the buffer
private long ptr1; // position of my readPtr in samples from start of file
private long ptr;
private long endPtr;
private boolean closing = false; // flag to say close me.
private long ptr2; //
private boolean reading = false; // true if audioProcess is reading
private long ptr0;
private BufferedRandomAccessFileManager manager;
private boolean xrun=false;
// private int nMissed=0; // how many bytes the audioProcess has lost and we
// have not taken into account yet.
public BufferedRandomAccessFile(RandomAccessFile in, int buffSize,
BufferedRandomAccessFileManager manager) {
this.in = in;
this.cyclicCache = new byte[buffSize];
this.ptr1 = 0;
this.ptr2 = 0;
try {
this.endPtr = in.length();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.manager = manager;
this.size = buffSize;
assert (size > 0);
try {
this.ptr = in.getFilePointer();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
manager.addClient(this);
}
/**
* Called from the thread filling buffer.
* Makes sure audio thread is not using buffer.
*
*/
private void waitForRead() {
while (reading) { // Can this ever happen if the processAudio is
// real priority ?
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
*
* ptr should already be set to postion in the file which we want to read
* from
*
* @throws IOException
*/
void fillBuffer() throws IOException {
waitForRead();
// System.out.println(" fillBuffer " + available());
if (ptr > ptr2 || ptr < ptr1 || xrun ) {
xrun=false;
// System.out.println(" SEEK " + ptr);
if (ptr < endPtr-size) { // plenty
in.seek(ptr);
int nread=in.read(cyclicCache, 0, size);
assert(nread==size);
ptr1 = ptr;
ptr0 = ptr1 % size;
ptr2 = ptr1 + size;
} else if (ptr < endPtr ){ //
int nn=(int) (endPtr-ptr);
in.seek(ptr);
int nread=in.read(cyclicCache, 0,nn);
assert(nread==nn);
ptr1 = ptr;
ptr0 = ptr1 % size;
ptr2 = ptr1 + nn;
} else {
try {
throw new Exception(" CAN NOT FILL BUFFER AFTER END OF FILE ");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else {
assert(ptr >= ptr1);
ptr1 = ptr; // make room for more data
int cPtr1 = (int) ((ptr1 - ptr0) % size);
int cPtr2 = (int) ((ptr2 - ptr0) % size);
// if (cPtr1 == cPtr2) {
// System.out.println(" p1 = p2 should this be happening ? ");
// }
//
long navail=(int) (endPtr-ptr2);
assert(navail >= 0);
if (cPtr2 > cPtr1) { // wrap around
int n1 = size - cPtr2;
if (n1 > 0) {
if (n1 > navail) {
int nn = in.read(cyclicCache, cPtr2, (int) navail);
assert(nn==navail);
ptr2 = ptr2+navail;
return;
}
int nn = in.read(cyclicCache, cPtr2, n1);
assert(nn == n1);
ptr2 += n1;
navail -=n1;
}
if (cPtr1 > 0) {
if (cPtr1 > navail) {
int nn = in.read(cyclicCache, 0 , (int) navail);
assert(nn==navail);
ptr2 = ptr2+navail;
return;
}
int nn = in.read(cyclicCache, 0, cPtr1);
ptr2+=cPtr1;
assert (nn == cPtr1);
}
} else { // catch up
int n1 = cPtr1 - cPtr2;
if (n1 > 0) {
if (n1 > navail) {
try {
int nn = in.read(cyclicCache, cPtr2, (int) navail);
assert(nn==navail);
ptr2+=navail;
} catch(Exception e) {
System.out.println(cPtr1 + " " +cPtr2 + " " +navail+ " "+ size);
e.printStackTrace();
xrun=true;
return;
}
return;
}
int nn = in.read(cyclicCache, cPtr2, n1);
assert(nn==n1);
ptr2+=n1;
}
}
}
}
final public boolean isFull() {
if (ptr2 >=endPtr) return true;
if (xrun) return false;
return availableInCache() == size;
}
public RandomAccessFile getRandomAccesFile() {
return in;
}
final public int availableInCache() {
return (int) (ptr2 - ptr);
}
/**
* read the next n bytes
*
* @param byteBuffer
* @param offSet
* @param n
* @return
* @throws IOException
*/
public int read(byte[] byteBuffer, int offSet, int n, boolean realTime)
throws IOException {
reading = true;
if ( ptr >= endPtr) return 0; // TODO zeroize
if (availableInCache() < n || (ptr > ptr2 || ptr < ptr1)) {
if (realTime) {
// System.out.println("audio cache: xrun");
try {
throw new Exception("audio cache: xrun ");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
xrun=true;
manager.wakeup();
} else {
System.out.println(" REALTIME read ");
reading = false;
fillBuffer();
reading = true;
}
}
// Here then we have enough data
// . . . feed off the cache
int cPtr1 = (int) ((ptr - ptr0) % size);
int cPtr2 = (int) ((ptr2 - ptr0) % size);
// System. out.println("pp " + ptr1 + " " + ptr2 + " " + ptr0);
if (size - cPtr1 > n) { // one bite
// System. out.println(cPtr1 + " " + offSet + " " + n + " " + size);
System.arraycopy(cyclicCache, cPtr1, byteBuffer, offSet, n);
} else { // two bite
// System. out.println("Two bites");
int nn = size - cPtr1;
System.arraycopy(cyclicCache, cPtr1, byteBuffer, offSet, nn);
int nn2 = n - nn;
System.arraycopy(cyclicCache, 0, byteBuffer, offSet + nn, nn2);
}
ptr += n; // next read postion;
reading = false;
manager.wakeup();
return n;
}
public long getFilePointer() {
return ptr;
}
public long length() throws IOException {
return in.length();
}
/**
* if (!realTime) may block. the buffer is filled else seek for processAudio
* thread. only sets the pointer (avoid synchronisation issues)
*
* @param l
* new file position
* @throws IOException
*/
public void seek(long l, boolean realTime) throws IOException {
if (realTime) {
ptr = l;
fillBuffer();
} else if (ptr != l) {
ptr = l;
manager.wakeup();
}
}
public void close() {
closing = true;
manager.removeClient(this);
}
public RandomAccessFile getRandomAccessFile() {
return in;
}
}