/*
* SpectStream.java
* (FScape)
*
* Copyright (c) 2001-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.fscape.spect;
import java.io.EOFException;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Vector;
/**
* Klangstromklasse, mit der alle Operatoren arbeiten;
* gearbeitet wird mit Frames, wobei das SpectStream-Objekt
* einen Puffer mit Frames verwaltet. Wenn der Puffer voll
* ist, muss auch der Writer warten. Die Frames werden wg.
* der Geschwindigkeit nicht kopiert, sondern "weitergereicht";
* freigegebene Frames wandern zurueck in einen internen Puffer
*/
public class SpectStream
{
// -------- public variables --------
/**
* linear frequency bands
*/
public static final int MODE_LIN = SpectralFile.PVA_LIN;
/**
* logarithmic frequency bands
*/
public static final int MODE_LOG = SpectralFile.PVA_EXP;
/**
* number of associated channels
* alle folgenden Variablen sind ReadOnly,
* zum Schreiben die entsprechenden methods benutzen!
*/
public int chanNum;
/**
* number of frequency bands
*/
public int bands;
/**
* lowest represented frequency (Hz)
*/
public float loFreq;
/**
* highest represented freq (Hz)
*/
public float hiFreq;
/**
* MODE_...
*/
public int freqMode;
/**
* e.g. 44,100
*/
public float smpRate;
/**
* time resolution
*/
public int smpPerFrame;
/**
* estimated(!) number of frames; don't rely on this
*/
public long frames;
/**
* number of frames written to pipe (total, NOT number currently in buffer)
*/
public long framesWritten = 0L;
/**
* number of frames read from the pipe (total)
*/
public long framesRead = 0L;
public static final String ERR_NOREADER = "Reader closed the stream";
public static final String ERR_NOWRITER = "Writer closed the stream";
// -------- private variables --------
protected Thread readerThread = null;
protected Thread writerThread = null;
protected static final int STATE_UNKNOWN = 0;
protected static final int STATE_ACTIVE = 1;
protected static final int STATE_DEAD = 2;
protected int readerState = STATE_UNKNOWN;
protected int writerState = STATE_UNKNOWN;
protected static final String ERR_BUFEMPTY = "Streambuffer empty";
protected static final String ERR_BUFFULL = "Streambuffer full";
protected static final int DEFAULT_BUF_SIZE = 8; // number of frames
protected Vector<SpectFrame> activeBuf; // geschriebene Frames
protected Vector<SpectFrame> deadBuf; // nicht mehr benoetigte Frames; darauf kann alloc() zurueckgreifen!
protected int bufSize; // max. Frame-Zahl in activeBuf und deadBuf
// -------- public methods --------
/**
* @param origin Strom, dessen Daten wie Kanalzahl, Baender,
* Frequenzen etc. uebernommen werden
* @param bufSize Zahl der Frames, die der Puffer des Stroms maximal enthaelt
*/
public SpectStream(SpectStream origin, int bufSize) {
if (origin != null) {
synchronized (origin) {
// copy params
this.chanNum = origin.chanNum;
this.bands = origin.bands;
this.loFreq = origin.loFreq;
this.hiFreq = origin.hiFreq;
this.freqMode = origin.freqMode;
this.smpRate = origin.smpRate;
this.smpPerFrame = origin.smpPerFrame;
this.frames = origin.frames;
}
} else {
// initialize to null
this.chanNum = 0;
this.bands = 0;
this.loFreq = 0.0f;
this.hiFreq = 0.0f;
this.freqMode = MODE_LIN;
this.smpRate = 0.0f;
this.smpPerFrame = 0;
this.frames = 0;
}
this.bufSize = bufSize;
activeBuf = new Vector<SpectFrame>(bufSize);
deadBuf = new Vector<SpectFrame>(bufSize);
}
public SpectStream(SpectStream origin) {
this(origin, origin.bufSize);
}
public SpectStream() {
this(null, DEFAULT_BUF_SIZE);
}
public SpectStream(int bufSize) {
this(null, bufSize);
}
/**
* registriert den aufrufenden Thread als Schreibberechtigten;
* DIES MUSS EIN EIGENTSTAENDIG LAUFENDER OPERATOR SEIN!
* AUF KEINEN FALL MEHRMALS AUFRUFEN
*/
public void initWriter() {
synchronized (this) {
activeBuf.removeAllElements();
writerThread = Thread.currentThread();
writerState = STATE_ACTIVE;
framesWritten = 0;
}
}
/**
* registriert den aufrufenden Thread als Leseberechtigten;
* DIES MUSS EIN EIGENTSTAENDIG LAUFENDER OPERATOR SEIN!
* AUF KEINEN FALL MEHRMALS AUFRUFEN
*/
public void getDescr() {
synchronized (this) {
readerThread = Thread.currentThread();
readerState = STATE_ACTIVE;
framesRead = 0;
}
}
/**
* Zahl der Kanaele festlegen
* AUF KEINEN FALL MEHR NACH initWriter() / getDescr() AUFRUFEN
*
* @param chanNum Zahl der Kanaele, 1 = mono, 2 = stereo etc.
*/
public void setChannels(int chanNum) {
synchronized (this) {
activeBuf.removeAllElements(); // Sicherheitsmassnahme
deadBuf .removeAllElements();
this.chanNum = chanNum;
}
}
/**
* Zahl der Baender und Frequenzbereich festlegen
* AUF KEINEN FALL MEHR NACH initWriter() / getDescr() AUFRUFEN
*
* @param loFreq Frequenz des untersten Bandes
* @param hiFreq Frequenz des obersten Bandes
* @param bands Anzahl der Baender
* @param mode Verteilungsmodus, MODE_LIN = linear, MODE_LOG = logarithmisch
*/
public void setBands(float loFreq, float hiFreq, int bands, int mode) {
synchronized (this) {
activeBuf.removeAllElements(); // Sicherheitsmassnahme
deadBuf .removeAllElements();
this.loFreq = loFreq;
this.hiFreq = hiFreq;
this.bands = bands;
this.freqMode = mode;
}
}
/**
* Samplingrate und Framebreite ermitteln
* AUF KEINEN FALL MEHR NACH initWriter() / getDescr() AUFRUFEN
*
* @param smpRate Samplingfrequenz in Hz
* @param smpPerFrame Zahl der Samples pro Frame
*/
public void setRate(float smpRate, int smpPerFrame) {
synchronized (this) {
this.smpRate = smpRate;
this.smpPerFrame = smpPerFrame;
}
}
/**
* Geschaetzte Laenge (im Frames) festlegen
*/
public void setEstimatedLength(long frames) {
this.frames = frames;
}
/**
* Erzeugt einen neuen Frame;
*
* DER AUFRUFER SOLLTE java.lang.OutOfMemoryError CATCHEN!
*/
public SpectFrame allocFrame() {
SpectFrame fr;
synchronized (this) {
if (!deadBuf.isEmpty()) {
fr = deadBuf.firstElement();
deadBuf.removeElement(fr);
fr.gainAccess();
} else {
fr = new SpectFrame(chanNum, bands);
}
}
return fr;
}
/**
* Erzeugt ein neues Array, das als Puffer fuer mehrere Frames dient;
*
* DER AUFRUFER SOLLTE java.lang.OutOfMemoryError CATCHEN!
*
* @param frameNum Anzahl der Frames (Index der ersten Dimension des Arrays)
*/
public static SpectFrame[] allocFrames(SpectStream stream, int frameNum) {
SpectFrame frames[] = new SpectFrame[frameNum];
for (int i = 0; i < frameNum; i++) {
frames[i] = stream.allocFrame();
}
return frames;
}
/**
* Gibt ein nicht mehr benoetigtes Frame frei;
* der Aufruf dieser Methode ist wichtig, weil das Frame wieder
* per allocFrame() zur Verfuegung gestellt werden kann, ohne
* dass ueberfluessige Garbage entsteht!
*
* Anschliessend sollten alle Referenzen dieses Frames auf NULL gesetzt werden!
*/
public void freeFrame(SpectFrame fr) {
fr.looseAccess();
synchronized (fr) {
if (fr.accessCount == 0) {
synchronized (this) {
if (deadBuf.size() < bufSize) {
deadBuf.addElement(fr);
}
}
}
}
}
/**
* Gibt mehrere Frames zurueck; vgl. freeFrame()
*/
public static void freeFrames(SpectStream stream, SpectFrame frames[]) {
for (int i = 0; i < frames.length; i++) {
stream.freeFrame(frames[i]);
}
}
/**
* Schreibt ein Frame; dass heisst, es wird weitergereicht
* wenn der Writer das Frame nicht mehr benoetigt, muss
* er weiterhin freeFrame() aufrufen!
*
* METHODE DARF NUR VOM REGISTRIERTEN WRITER AUFGERUFEN WERDEN!
*
* wenn der Puffer voll ist, wird eine IndexOutOfBoundsException ausgeloest
* wenn der Strom readerseitig geschlossen wurde, wird eine EOFException ausgeloest
* (in diesem Fall wird closeWriter() automatisch aufgerufen!)
*
* die IOException muss generell abgefangen werden, weil der SpectStream moeglicherweise
* in der Zukunft auf temporaere Dateien zugreift!
*
* @param fr sollte mit allocFrame(s)() beschafft worden sein!
*/
public void writeFrame(SpectFrame fr)
throws IOException,
IndexOutOfBoundsException {
switch (framesWriteable()) {
case -1:
closeWriter();
throw new EOFException(ERR_NOREADER);
case 0:
throw new IndexOutOfBoundsException(ERR_BUFFULL);
default:
synchronized (this) {
fr.gainAccess(); // Frame nicht freigeben, bis vom Reader verarbeitet!
activeBuf.addElement(fr);
framesWritten++;
}
break;
}
}
public void writeDummy(SpectFrame fr) {
framesWritten++;
}
/**
* Schreibt mehrere Frames; vgl. writeFrame()
*
* @return -1, wenn readerseitig der Strom geschlossen wurde,
* sonst Zahl der geschriebenen Frames
*/
public static int writeFrames(SpectStream stream, SpectFrame frames[])
throws IOException {
int frameNum = 0;
try {
for (int i = 0; i < frames.length; i++) {
stream.writeFrame(frames[i]);
frameNum++;
}
} catch (IndexOutOfBoundsException ignored) {
} catch (EOFException e) {
return -1;
}
return frameNum;
}
/**
* Liest ein Frame aus dem Puffer
* ; wenn das Frame verarbeitet ist, muss freeFrame() aufgerufen werden!
*
* METHODE DARF NUR VOM REGISTRIERTEN READER AUFGERUFEN WERDEN!
*
* wenn der Puffer leer ist, wird eine NoSuchElementException ausgeloest
* wenn ausserdem der Strom versiegt ist, eine EOFException
* (in diesem Fall wird closeReader() automatisch aufgerufen!)
*
* die IOException muss generell abgefangen werden, weil der SpectStream moeglicherweise
* in der Zukunft auf temporaere Dateien zugreift!
*/
public SpectFrame readFrame()
throws IOException,
NoSuchElementException {
SpectFrame fr;
switch (framesReadable()) {
case -1:
closeReader();
throw new EOFException(ERR_NOWRITER);
case 0:
throw new NoSuchElementException(ERR_BUFEMPTY);
default:
synchronized (this) {
fr = activeBuf.firstElement();
activeBuf.removeElement(fr);
framesRead++;
}
return fr;
}
}
/**
* Liest mehrere Frames; vgl. readFrame()
*
* @param frames die erste Dimension gibt die Zahl der frames an,
* die gelesen werden sollen
* @return -1, wenn writerseitig der Strom geschlossen wurde und KEIN
* frame mehr verfuegbar war, sonst Zahl der gelesenen Frames
*/
public static int readFrames(SpectStream stream, SpectFrame frames[])
throws IOException {
int frameNum = 0;
try {
for (int i = 0; i < frames.length; i++) {
frames[i] = stream.readFrame();
frameNum++;
}
} catch (NoSuchElementException ignored) {
} catch (EOFException e) {
if (frameNum == 0) {
frameNum = -1;
}
}
return frameNum;
}
/**
* Schliesst den Strom, markiert damit dessen Ende
* DARF NUR VOM REGISTRIERTEN WRITER AUFGERUFEN WERDEN!
* Kann auch aufgerufen werden, wenn der Strom schon geschlossen wurde
*
* Anschliessend sollten alle Referenzen des SpectStreams auf NULL gesetzt werden
*/
public void closeWriter()
throws IOException {
synchronized (this) {
writerThread = null;
writerState = STATE_DEAD;
deadBuf.removeAllElements();
}
}
/**
* Schliesst Reader-seitig den Strom
* DARF NUR VOM REGISTRIERTEN READER AUFGERUFEN WERDEN!
* sollte nur im Falle eines IO-Fehlers beim CleanUp aufgerufen werden;
* Kann auch aufgerufen werden, wenn der Strom schon geschlossen wurde
*
* Anschliessend sollten alle Referenzen des SpectStreams auf NULL gesetzt werden
*/
public void closeReader()
throws IOException {
synchronized (this) {
writerThread = null;
writerState = STATE_DEAD;
activeBuf.removeAllElements();
}
}
/**
* Liefert die Zahl der ohne Blockierung lesbaren frames
*
* @return -1 bedeutet, dass keine Frames mehr gelesen werden koennen
* UND auch keine mehr kommen werden, weil der Writer den
* Strom geschlossen hat
*/
public int framesReadable() {
int frameNum;
synchronized (this) {
frameNum = activeBuf.size();
if ((frameNum == 0) && (writerState == STATE_DEAD)) {
frameNum = -1;
}
}
return frameNum;
}
/**
* Liefert die Zahl der ohne Blockierung schreibbaren frames
*
* @return -1 bedeutet, dass keine Frames mehr geschrieben werden koennen
* UND auch keine mehr abgeholt werden, weil der Reader den
* Strom geschlossen hat
*/
public int framesWriteable() {
int frameNum;
synchronized (this) {
frameNum = bufSize - activeBuf.size();
if (readerState == STATE_DEAD) {
frameNum = -1;
}
}
return frameNum;
}
/**
* Besorgt Zeit-Offset des naechsten Frames in Millisekunden
*/
public double getTime() {
if (Thread.currentThread() == readerThread) {
return (framesRead * ((double) smpPerFrame / smpRate) * 1000);
} else {
return (framesWritten * ((double) smpPerFrame / smpRate) * 1000);
}
}
/**
* Format-String erzeugen
*/
public static String getFormat(SpectStream stream) {
String chanTxt = null;
String lengthTxt;
int min, sec, millis;
switch (stream.chanNum) {
case 1:
chanTxt = "Mono ";
break;
case 2:
chanTxt = "Stereo ";
break;
default:
chanTxt = stream.chanNum + "-chn. ";
break;
}
millis = (int) (framesToMillis( stream, stream.frames ) + 0.5);
min = millis / 60000;
sec = (millis / 1000) % 60;
millis = millis % 1000;
lengthTxt = "; " + min + ':' + String.valueOf( sec + 100 ).substring( 1 ) + '.' +
String.valueOf( millis + 1000 ).substring( 1 );
return( chanTxt + stream.bands + " bands (" + (int) (stream.loFreq + 0.5f) + '-' +
(int) (stream.hiFreq + 0.5f) + ((stream.freqMode == SpectStream.MODE_LIN) ?
" Hz linear)" : " Hz exp.)") + lengthTxt );
}
/**
* Berechnet die Frame-Nummer nach angebenen Millisekunden
*/
public static double millisToFrames(SpectStream stream, double ms) {
// seconds * framesPerSec
return ((ms / 1000) * ((double) stream.smpRate / stream.smpPerFrame));
}
/**
* Rechnet Framezahl in Millisekunden um
*/
public static double framesToMillis(SpectStream stream, long frames) {
return (frames * ((double) stream.smpPerFrame / stream.smpRate) * 1000);
}
}