/*
* MultiMappedAudioStake.java
* Eisenkraut
*
* Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*/
package de.sciss.eisenkraut.io;
import de.sciss.io.CacheManager;
import de.sciss.io.InterleavedStreamFile;
import de.sciss.io.Span;
import de.sciss.jcollider.Buffer;
import de.sciss.net.OSCBundle;
import de.sciss.timebased.Stake;
import java.io.IOException;
public class MultiMappedAudioStake extends AudioStake {
private final InterleavedStreamFile[] fs;
private final Span[] fileSpans;
private final Span[] maxFileSpans;
private final int[][] channelMaps;
private final float[][][] mappedDatas;
private final int numChannels;
private final String[] fileNames;
private final static int BUFSIZE = 8192;
public MultiMappedAudioStake( Span span, InterleavedStreamFile[] fs, Span[] fileSpans )
{
this( span, fs, fileSpans, createChannelMaps( fs ));
}
// make sure channelMaps is never modified!
public MultiMappedAudioStake( Span span, InterleavedStreamFile[] fs, Span[] fileSpans, int[][] channelMaps )
{
this( span, fs, fileSpans, fileSpans, channelMaps, getFileNames( fs ));
}
private MultiMappedAudioStake( Span span, InterleavedStreamFile[] fs, Span[] fileSpans,
Span[] maxFileSpans, int[][] channelMaps, String[] fileNames )
{
super( span );
int numCh = 0;
this.fs = fs;
this.fileSpans = fileSpans;
this.maxFileSpans = maxFileSpans;
this.channelMaps = channelMaps;
mappedDatas = new float[ fs.length ][][];
for( int i = 0; i < fs.length; i++ ) {
mappedDatas[ i ] = new float[ fs[ i ].getChannelNum() ][];
numCh += channelMaps[ i ].length;
}
this.numChannels = numCh;
this.fileNames = fileNames;
}
public void close()
throws IOException {
for (InterleavedStreamFile f : fs) f.close();
}
public void cleanUp() {
for (InterleavedStreamFile f : fs) {
try {
f.close();
} catch (IOException e1) { /* ignore */ }
}
}
private static String[] getFileNames(InterleavedStreamFile[] fs) {
final String[] fileNames = new String[fs.length];
// final StringBuffer sb = new StringBuffer();
for (int i = 0; i < fs.length; i++) {
final String s = fs[i].getFile().getAbsolutePath();
fileNames[i] = s; // fileNameNormalizer.normalize(s, sb);
// sb.setLength(0);
}
return fileNames;
}
// consider result read-only!!!
public int[][] getChannelMaps() {
return channelMaps;
}
private static int[][] createChannelMaps(InterleavedStreamFile[] fs) {
final int[][] channelMaps = new int[fs.length][];
int[] channelMap;
for (int i = 0; i < fs.length; i++) {
channelMap = new int[fs[i].getChannelNum()];
for (int j = 0; j < channelMap.length; j++) {
channelMap[j] = j;
}
channelMaps[i] = channelMap;
}
return channelMaps;
}
public Stake duplicate() {
return new MultiMappedAudioStake(span, fs, fileSpans, maxFileSpans, channelMaps, fileNames);
}
public Stake replaceStart(long newStart) {
final long delta = newStart - span.start;
final Span[] newFileSpans = new Span[fileSpans.length];
final Span newSpan = span.replaceStart(newStart);
if (newSpan.getLength() < 0) throw new IllegalArgumentException(String.valueOf(newStart));
for (int i = 0; i < fileSpans.length; i++) {
newFileSpans[i] = fileSpans[i].replaceStart(fileSpans[i].start + delta);
if ((newFileSpans[i].getLength() < 0) || !maxFileSpans[i].contains(newFileSpans[i])) {
throw new IllegalArgumentException(String.valueOf(newStart));
}
}
return new MultiMappedAudioStake(newSpan, fs, newFileSpans, maxFileSpans, channelMaps, fileNames);
}
public Stake replaceStop(long newStop) {
final long delta = newStop - span.stop;
final Span newSpan = span.replaceStop(newStop);
final Span[] newFileSpans = new Span[fileSpans.length];
if (newSpan.getLength() < 0) throw new IllegalArgumentException(String.valueOf(newStop));
for (int i = 0; i < fileSpans.length; i++) {
newFileSpans[i] = fileSpans[i].replaceStop(fileSpans[i].stop + delta);
if ((newFileSpans[i].getLength() < 0) || !maxFileSpans[i].contains(newFileSpans[i])) {
throw new IllegalArgumentException(String.valueOf(newStop));
}
}
return new MultiMappedAudioStake(newSpan, fs, newFileSpans, maxFileSpans, channelMaps, fileNames);
}
public Stake shiftVirtual(long delta) {
return new MultiMappedAudioStake(span.shift(delta), fs, fileSpans, maxFileSpans, channelMaps, fileNames);
}
public int readFrames(float[][] data, int offset, Span readSpan)
throws IOException {
final int len = (int) readSpan.getLength();
if (len == 0) return 0;
InterleavedStreamFile f;
long fOffset;
int[] channelMap;
float[][] mappedData;
synchronized( fs ) { // protects mappedDatas
for( int i = 0, j = 0; i < fs.length; i++ ) {
f = fs[ i ];
channelMap = channelMaps[ i ];
mappedData = mappedDatas[ i ];
fOffset = fileSpans[ i ].start + readSpan.start - span.start;
if ((fOffset < fileSpans[i].start) || ((fOffset + len) > fileSpans[i].stop)) {
throw new IllegalArgumentException(fOffset + " ... " + (fOffset + len) + " not within " + fileSpans[i].toString());
}
synchronized (f) {
if (f.getFramePosition() != fOffset) {
f.seekFrame(fOffset);
}
for (int k = 0; k < channelMap.length; k++, j++) {
mappedData[channelMap[k]] = data[j];
}
f.readFrames(mappedData, offset, len);
}
}
clearMappedData(); // avoid memory footprint
}
return len;
}
public int writeFrames(float[][] data, int offset, Span writeSpan)
throws IOException {
final int len = (int) writeSpan.getLength();
if (len == 0) return 0;
InterleavedStreamFile f;
long fOffset;
int[] channelMap;
float[][] mappedData;
synchronized( fs ) { // protects mappedDatas
for( int i = 0, j = 0; i < fs.length; i++ ) {
f = fs[ i ];
channelMap = channelMaps[ i ];
mappedData = mappedDatas[ i ];
fOffset = fileSpans[ i ].start + writeSpan.start - span.start;
if( (fOffset < fileSpans[ i ].start) || ((fOffset + len) > fileSpans[ i ].stop) ) {
throw new IllegalArgumentException( fOffset + " ... " + (fOffset + len) + " not within " + fileSpans[ i ].toString() );
}
synchronized( f ) {
if( f.getFramePosition() != fOffset ) {
f.seekFrame( fOffset );
}
for( int k = 0; k < channelMap.length; k++, j++ ) {
mappedData[ channelMap[ k ]] = data[ j ];
}
f.writeFrames( mappedData, offset, len );
}
}
clearMappedData(); // avoid memory footprint
}
return len;
}
public long copyFrames( InterleavedStreamFile target, Span readSpan )
throws IOException
{
final long len = readSpan.getLength();
if( len == 0 ) return 0;
InterleavedStreamFile f;
long fOffset;
int[] channelMap;
float[][] mappedData;
int chunkLen;
float[][] data = new float[ numChannels ][ (int) Math.min( BUFSIZE, len )];
long framesCopied = 0;
do {
synchronized( fs ) { // protects mappedDatas
for( int i = 0, j = 0; i < fs.length; i++ ) {
f = fs[ i ];
channelMap = channelMaps[ i ];
mappedData = mappedDatas[ i ];
fOffset = fileSpans[ i ].start + readSpan.start - span.start;
if( (fOffset < fileSpans[ i ].start) || ((fOffset + len) > fileSpans[ i ].stop) ) {
throw new IllegalArgumentException( fOffset + " ... " + (fOffset + len) + " not within " + fileSpans[ i ].toString() );
}
chunkLen = (int) Math.min(BUFSIZE, len - framesCopied);
synchronized (f) {
if (f.getFramePosition() != fOffset) {
f.seekFrame(fOffset);
}
for (int k = 0; k < channelMap.length; k++, j++) {
mappedData[channelMap[k]] = data[j];
}
f.readFrames(mappedData, 0, chunkLen);
}
target.writeFrames(data, 0, chunkLen);
framesCopied += chunkLen;
}
}
clearMappedData(); // minimize memory footprint
} while( framesCopied < len );
return len;
}
public void addBufferReadMessages( OSCBundle bndl, Span readSpan, Buffer[] bufs, int bufOff )
{
final int len = (int) readSpan.getLength();
if( len == 0 ) return;
long fOffset;
if( bufs.length != fs.length ) {
throw new IllegalArgumentException( "Wrong # of buffers (" + bufs.length + " != " + fs.length + ")" );
}
for( int i = 0; i < fs.length; i++ ) {
fOffset = fileSpans[ i ].start + readSpan.start - span.start;
if( (fOffset < fileSpans[ i ].start) || ((fOffset + len) > fileSpans[ i ].stop) ) {
throw new IllegalArgumentException( fOffset + " ... " + (fOffset + len) + " not within " + fileSpans[ i ].toString() );
}
if( (bufs[ i ].getNumChannels() != fs[ i ].getChannelNum()) ) {
throw new IllegalArgumentException( "Channel mismatch (" + bufs[ i ].getNumChannels() + " != " + fs[ i ].getChannelNum() );
}
bndl.addPacket( bufs[ i ].readMsg( fileNames[ i ], fOffset, len, bufOff ));
}
}
// synchronization : caller must have sync on fs
private void clearMappedData() {
for (int i = 0; i < mappedDatas.length; i++) {
for (int j = 0; j < mappedDatas[i].length; j++) {
mappedDatas[i][j] = null;
}
}
}
public void flush()
throws IOException {
for (InterleavedStreamFile f : fs) {
synchronized (f) {
f.flush();
}
}
}
public void addToCache(CacheManager cm) {
for (InterleavedStreamFile f : fs) {
cm.addFile(f.getFile());
}
}
public int getChannelNum() {
return numChannels;
}
public void debugDump() {
debugDumpBasics();
System.err.print(" ; fs = [ ");
for (int i = 0; i < fs.length; i++) {
System.err.print((i > 0 ? ", " : "") + fs[i].getFile().getName() + " (file span " + fileSpans[i].toString() + " )");
}
System.err.println(" ]");
}
}