/*
* SuperColliderPlayer.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.net;
import java.awt.EventQueue;
import java.net.SocketAddress;
import java.io.File;
import java.io.IOException;
import de.sciss.eisenkraut.io.AudioTrail;
import de.sciss.eisenkraut.io.RoutingConfig;
import de.sciss.eisenkraut.realtime.Transport;
import de.sciss.eisenkraut.realtime.TransportListener;
import de.sciss.eisenkraut.session.Session;
import de.sciss.eisenkraut.session.SessionCollection;
import de.sciss.eisenkraut.session.SessionObject;
import de.sciss.eisenkraut.timeline.AudioTrack;
import de.sciss.eisenkraut.timeline.TimelineEvent;
import de.sciss.eisenkraut.timeline.TimelineListener;
import de.sciss.eisenkraut.util.MapManager;
import de.sciss.app.AbstractApplication;
import de.sciss.net.OSCBundle;
import de.sciss.net.OSCListener;
import de.sciss.net.OSCMessage;
import de.sciss.io.Span;
import de.sciss.jcollider.Buffer;
import de.sciss.jcollider.Bus;
import de.sciss.jcollider.Control;
import de.sciss.jcollider.GraphElem;
import de.sciss.jcollider.Group;
import de.sciss.jcollider.NodeWatcher;
import de.sciss.jcollider.OSCResponderNode;
import de.sciss.jcollider.Server;
import de.sciss.jcollider.Synth;
import de.sciss.jcollider.SynthDef;
import de.sciss.jcollider.UGen;
import de.sciss.util.Disposable;
public class SuperColliderPlayer
implements de.sciss.jcollider.Constants,
TransportListener, // RealtimeConsumer,
SessionCollection.Listener, Disposable,
TimelineListener, OSCRouter {
private static final boolean DEBUG_FOLD = false;
private static final int DISKBUF_PAD = 4; // be sure to match with phasor synth def!!
protected final int DISKBUF_SIZE;
protected final int DISKBUF_SIZE_H;
protected final int DISKBUF_SIZE_HM;
private final int MIN_LOOP_LEN = 4410; // rather arbitrary ... we should better check that osc bundles don't overflow
protected final Server server;
private final NodeWatcher nw;
private final Session doc;
protected final Transport transport;
private RoutingConfig oCfg;
protected volatile Context ct = null;
private double serverRate;
private double sourceRate;
private double srcFactor = 1.0;
protected long playOffset; // = -1;
protected int clock;
private static final Span[] emptySpans = new Span[0];
protected Span[][] lastBufSpans = new Span[][] { emptySpans, emptySpans };
private final Group grpRoot;
private final Group grpInput;
private final Group grpOutput;
private final Bus busPhasor;
private final int numInputChannels; // XXX static for now
private final int[][] channelMaps;
private final OSCResponderNode trigResp;
private final GroupSync syncInput;
private final GroupSync syncOutput;
private boolean activeInput = false;
private boolean activeOutput = false;
protected volatile int trigNodeID = -1; // current synthPhasor ID or -1
protected volatile OSCMessage trigMsg = null; // most recently received /tr message
private static final String OSC_SUPERCOLLIDER = "sc";
private final OSCRouterWrapper osc;
private static final float TIMEOUT = 2f;
public SuperColliderPlayer( final Session doc, final Server server, RoutingConfig oCfg )
throws IOException
{
this.server = server;
this.doc = doc;
final AudioTrail at = doc.getAudioTrail();
final Runnable runTrigger;
final SynthDef[] defs;
OSCBundle bndl;
transport = doc.getTransport();
nw = NodeWatcher.newFrom( server );
numInputChannels = at.getChannelNum(); // XXX sync?
channelMaps = at.getChannelMaps();
sourceRate = doc.timeline.getRate(); // XXX sync?
serverRate = server.getSampleRate();
DISKBUF_SIZE = (Math.max( 44100, (int) sourceRate ) + DISKBUF_PAD) << 1; // buffer size in frames
DISKBUF_SIZE_H = DISKBUF_SIZE >> 1;
DISKBUF_SIZE_HM = DISKBUF_SIZE_H - DISKBUF_PAD;
bndl = new OSCBundle();
grpRoot = Group.basicNew( server );
grpRoot.setName( "Root-" + doc.getName() );
nw.register( grpRoot );
bndl.addPacket( grpRoot.addToHeadMsg( server.getDefaultGroup() ));
grpInput = Group.basicNew( server );
grpInput.setName( "Input" );
nw.register( grpInput );
bndl.addPacket( grpInput.addToTailMsg( grpRoot ));
grpOutput = Group.basicNew( server );
grpOutput.setName( "Output" );
nw.register( grpOutput );
bndl.addPacket( grpOutput.addToTailMsg( grpRoot ));
bndl.addPacket( grpOutput.runMsg( false ));
server.sendBundle( bndl );
busPhasor = Bus.audio( server );
runTrigger = new Runnable() {
public void run()
{
final OSCMessage msg = trigMsg; // this way we synchronize the access
final int nodeID = ((Number) msg.getArg( 0 )).intValue();
final int nextClock, fill, bufOff;
final long pos, start;
final OSCBundle bndl2;
final int even;
final Span[] bufSpans;
int numCh;
try {
clock = ((Number) msg.getArg( 2 )).intValue();
//System.err.println( "clock = " + clock );
nextClock = clock + 1;
even = nextClock & 1; // == 0;
bndl2 = new OSCBundle();
if( (ct == null) || (ct.synthPhasor == null) || (nodeID != ct.synthPhasor.getNodeID()) ) return;
if( trigNodeID == -1 ) return; // transport not running anymore
pos = nextClock * DISKBUF_SIZE_HM - ((1 - even) * DISKBUF_PAD) + playOffset;
start = Math.max( 0, pos );
fill = (int) (start - pos);
bufOff = even * DISKBUF_SIZE_H;
if( fill > 0 ) {
for( int j = 0; j < ct.bufsDisk.length; j++ ) {
numCh = ct.bufsDisk[ j ].getNumChannels();
bndl2.addPacket( ct.bufsDisk[ j ].fillMsg( bufOff * numCh, fill * numCh, 0.0f ));
}
}
bufSpans = transport.foldSpans( new Span( start, pos + DISKBUF_SIZE_H ), MIN_LOOP_LEN );
doc.getAudioTrail().addBufferReadMessages( bndl2, bufSpans, ct.bufsDisk, bufOff + fill );
lastBufSpans[ even ] = bufSpans;
if( DEBUG_FOLD ) {
System.out.println( "------C "+ nextClock + ", " + even + ", " + playOffset + ", " + pos );
for( int k = 0, m = bufOff + fill; k < bufSpans.length; k++ ) {
System.out.println( "i = " + k + "; " + bufSpans[ k ] + " -> " + m );
m += bufSpans[ k ].getLength();
}
System.out.println();
}
if( !server.sync( bndl2, TIMEOUT )) {
printTimeOutMsg( "bufUpdate" );
}
}
catch( IOException e1 ) {
printError( "Receive /tr", e1 );
}
catch( ClassCastException e2 ) {
printError( "Receive /tr", e2 );
}
}
};
trigResp = new OSCResponderNode( server, "/tr", new OSCListener() {
public void messageReceived( OSCMessage msg, SocketAddress sender, long time )
{
final int nodeID = ((Number) msg.getArg( 0 )).intValue();
if( nodeID == trigNodeID ) {
trigMsg = msg;
EventQueue.invokeLater( runTrigger );
}
}
});
defs = createInputDefs( channelMaps );
if( defs != null ) {
bndl = new OSCBundle();
for (SynthDef def : defs) {
bndl.addPacket(def.recvMsg());
}
if( !server.sync( bndl, TIMEOUT )) {
printTimeOutMsg( "defs" );
}
}
srcFactor = sourceRate / serverRate;
updateSRC();
setOutputConfig( oCfg );
syncInput = new GroupSync();
syncOutput = new GroupSync();
transport.addTransportListener( this );
doc.audioTracks.addListener( this );
doc.timeline.addTimelineListener( this );
osc = new OSCRouterWrapper( doc, this );
}
private SynthDef[] createInputDefs(int[][] chMaps) {
final int[] numInCh = new int[chMaps.length];
final SynthDef[] defs;
int numDefs = 0;
numDefsLp:
for (int[] chMap : chMaps) {
numInCh[numDefs] = chMap.length;
for (int j = 0; j < numDefs; j++) {
if (numInCh[j] == numInCh[numDefs]) continue numDefsLp;
}
numDefs++;
}
defs = new SynthDef[ numDefs ];
for( int i = 0; i < numDefs; i++ ) {
final Control ctrlI = Control.ir( new String[] { "i_aInBf", "i_aOtBs", "i_aPhBs", "i_intrp" }, new float[] { 0f, 0f, 0f, 1f });
final GraphElem graph;
if( numInCh[ i ] > 0 ) {
final GraphElem phase = UGen.ar( "In", ctrlI.getChannel( "i_aPhBs" ));
final GraphElem bufRd = UGen.ar( "BufRd", numInCh[ i ], ctrlI.getChannel( "i_aInBf" ), phase, UGen.ir( 0f ), ctrlI.getChannel( "i_intrp" ));
graph = UGen.ar( "Out", ctrlI.getChannel( "i_aOtBs" ), bufRd );
} else {
graph = ctrlI;
}
defs[ i ] = new SynthDef( "eisk-input" + numInCh[ i ], graph );
}
return defs;
}
private SynthDef[] createOutputDefs(int numOutputChannels) {
final Control ctrlI = Control.ir( new String[] { "i_aInBs", "i_aOtBs" }, new float[] { 0f, 0f });
final Control ctrlK = Control.kr( new String[] { "pos", "width", "orient", "volume" }, new float[] { 0f, 2f, 0f, 1f });
final GraphElem graph;
final SynthDef def;
if( numOutputChannels > 0 ) {
final GraphElem in = UGen.ar( "In", ctrlI.getChannel( "i_aInBs" ));
final GraphElem pan;
if( numOutputChannels > 1 ) {
pan = UGen.ar( "PanAz", numOutputChannels, in, ctrlK.getChannel( "pos" ), ctrlK.getChannel( "volume" ),
ctrlK.getChannel( "width" ), ctrlK.getChannel( "orient" ));
} else {
pan = UGen.ar( "*", in, ctrlK.getChannel( "volume" ));
}
graph = UGen.ar( "Out", ctrlI.getChannel( "i_aOtBs" ), pan );
} else {
graph = UGen.array( ctrlI, ctrlK );
}
def = new SynthDef( "eisk-pan" + numOutputChannels, graph );
return new SynthDef[] { def };
}
public Session getDocument()
{
return doc;
}
public void dispose() {
osc.remove();
// we free the nodes even if the server is
// not running because we may simply have lost
// connection and in fact it's still playing!
// if( server.isRunning() ) {
try {
trigResp.remove();
grpRoot.free();
disposeServerStuff();
} catch (IOException e1) {
printError("dispose", e1);
}
doc.audioTracks.removeListener(this);
doc.timeline.removeTimelineListener(this);
transport.removeTransportListener(this);
}
private void disposeServerStuff()
throws IOException {
if (ct != null) {
ct.dispose();
ct = null;
}
}
public Bus getInputBus() {
if (!EventQueue.isDispatchThread()) throw new IllegalMonitorStateException();
if (ct != null) {
return ct.busInternal;
} else {
return null;
}
}
public GroupSync getInputSync()
{
return syncInput;
}
public GroupSync getOutputSync()
{
return syncOutput;
}
// public Bus getMeterSource()
// {
// synchronized( this ) {
// if( ct != null ) {
// return ct.busMeter;
// } else {
// return null;
// }
// }
// }
//
// public void setMeterSource( Bus meterInput )
// {
// final int numCh;
// final OSCBundle bndl;
//
// synchronized( this ) {
// if( ct != null ) {
// numCh = Math.min( ct.numInputChannels, meterInput.getNumChannels() );
// bndl = new OSCBundle();
// ct.busMeter = meterInput == null ? ct.busInternal : meterInput;
// for( int ch = 0; ch < numCh; ch++ ) {
// bndl.addPacket( ct.synthsMeter[ ch ].setMsg( "i_aInBus", ct.busMeter.getIndex() + ch ));
// }
// server.sendBundle( bndl );
// }
// }
// }
// sync : attempts shared on DOOR_TRACKS
// public void setOutputConfig( RoutingConfig oCfg, float volume )
public void setOutputConfig( RoutingConfig oCfg )
{
this.oCfg = oCfg;
// this.volume = volume;
if( server.isRunning() ) {
final boolean wasRunning = transport.isRunning();
final double rate = transport.getRateScale();
if( wasRunning ) {
transport.stop();
}
// if( !doc.bird.attemptShared( Session.DOOR_TRACKS, 250 )) {
// return;
// }
// try {
// System.out.println( "setOutputConfig : rebuildSynths" );
rebuildSynths();
// }
// finally {
// doc.bird.releaseShared( Session.DOOR_TRACKS );
// }
if( wasRunning ) {
transport.play( rate );
}
}
}
// public void addMeterListener( MeterListener ml )
// {
// synchronized( collMeterListeners )
// {
// if( !meterListening && keepMetering ) {
// try {
// meterListening = true;
// grpMeter.run( true );
// }
// catch( IOException e1 ) {
// printError( "Run Meter Group", e1 );
// }
// }
// collMeterListeners.add( ml );
// }
// }
//
// public void removeMeterListener( MeterListener ml )
// {
// synchronized( collMeterListeners )
// {
// collMeterListeners.remove( ml );
// if( meterListening && collMeterListeners.isEmpty() ) {
// try {
// grpMeter.run( false );
// meterListening = false;
// }
// catch( IOException e1 ) {
// printError( "Run Meter Group", e1 );
// }
// }
// }
// }
public void setActiveInput( boolean onOff )
{
if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
if( onOff != activeInput ) {
activeInput = onOff;
if( transport.isRunning() ) return;
if( activeInput ) {
syncInput.activate( null );
} else {
syncInput.deactivate( null );
}
}
}
public void setActiveOutput( boolean onOff )
{
if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
if( onOff != activeOutput ) {
activeOutput = onOff;
if( transport.isRunning() ) return;
final OSCBundle bndl = new OSCBundle();
bndl.addPacket( grpOutput.runMsg( activeOutput ));
if( activeOutput ) {
syncOutput.activate( bndl );
} else {
syncOutput.deactivate( bndl );
}
try {
server.sendBundle( bndl );
}
catch( IOException e1 ) {
printError( "Run Output Group", e1 );
}
}
}
// public void setMetering( boolean onOff )
// {
// keepMetering = onOff;
//
// try {
// synchronized( collMeterListeners ) {
// if( onOff ) {
// if( !meterListening && !collMeterListeners.isEmpty() ) {
// meterListening = true;
// grpMeter.run( true );
// }
// } else {
// if( meterListening && collMeterListeners.isEmpty() ) {
// grpMeter.run( false );
// meterListening = false;
// }
// }
// }
// }
// catch( IOException e1 ) {
// printError( "Run Meter Group", e1 );
// }
// }
//
// /**
// * @synchronization must be called in event thread
// */
// public void meterBang()
// {
// if( ct != null ) {
// try {
//// if( meterListening ) server.sendMsg( ct.meterBangMsg );
// if( meterListening ) server.sendBundle( ct.meterBangBndl );
// }
// catch( IOException e1 ) {} // don't print coz the frequency might be high
// }
// }
protected static void printError( String name, Throwable t )
{
System.err.println( name + " : " + t.getClass().getName() + " : " + t.getLocalizedMessage() );
}
// note: includes call to addChannelMuteMessages
// sync: must be called in EventThread
private void rebuildSynths()
{
if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
//new Exception().printStackTrace();
// synchronized( sync ) {
try {
server.sync( TIMEOUT ); // an n_free on a pausing node can crash scsynth otherwise (19-nov-07)
grpRoot.deepFree();
disposeServerStuff();
if( oCfg == null ) return;
ct = new Context( channelMaps, numInputChannels, oCfg.numChannels );
final float orient = -oCfg.startAngle/360 * oCfg.numChannels;
final SynthDef[] defs;
OSCBundle bndl;
defs = createOutputDefs( oCfg.numChannels );
if( defs != null ) {
bndl = new OSCBundle();
for (SynthDef def : defs) {
bndl.addPacket(def.recvMsg());
}
if( !server.sync( bndl, TIMEOUT )) {
printTimeOutMsg( "defs" );
}
}
bndl = new OSCBundle();
for( int i = 0; i < ct.numFiles; i++ ) {
bndl.addPacket( ct.bufsDisk[ i ].allocMsg() );
}
for( int ch = 0; ch < ct.numInChans; ch++ ) {
bndl.addPacket( ct.synthsPan[ ch ].newMsg( grpOutput, new String[] {
"i_aInBs", "i_aOtBs", "orient" }, new float[] {
ct.busInternal.getIndex() + ch, ct.busPan.getIndex(), orient }, kAddToTail ));
nw.register( ct.synthsPan[ ch ]);
}
for( int ch = 0; ch < oCfg.numChannels; ch++ ) {
if( oCfg.mapping[ ch ] < server.getOptions().getNumOutputBusChannels() ) {
bndl.addPacket( ct.synthsRoute[ ch ].newMsg( grpOutput, new String[] {
"i_aInBs", "i_aOtBs" }, new float[] {
ct.busPan.getIndex() + ch, oCfg.mapping[ ch ]}, kAddToTail ));
nw.register( ct.synthsRoute[ ch ]);
}
}
addChannelPanMessages( bndl );
addChannelMuteMessages( bndl );
if( !server.sync( bndl, TIMEOUT )) {
printTimeOutMsg( "alloc" );
}
ct.bufsAllocated = true;
}
catch( IOException e1 ) {
printError( "rebuildSynths", e1 );
}
// }
}
// private void addBufferReadMessages( OSCBundle bndl, Span span, int numFrames, int offset )
// {
// SampledChunk ts;
// int len;
//
//
//
// for( int i = 0; i < tl.size(); i++ ) {
// ts = tl.get( i );
// len = (int) Math.min( numFrames, ts.getLength() );
// if( len > 0 ) {
//// MMM
//// bndl.addPacket( ct.bufDisk.readMsg( ts.f.getFile().getAbsolutePath(), ts.offset, len, offset ));
// offset += len;
// numFrames -= len;
// }
// }
// if( numFrames > 0 ) { // zero the rest
// bndl.addPacket( ct.bufDisk.fillMsg(
// offset * ct.bufDisk.getNumChannels(), numFrames * ct.bufDisk.getNumChannels(), 0.0f ));
// }
// }
// sync : attempts shared on DOOR_TRACKS
private void addChannelMuteMessages( OSCBundle bndl )
{
// Object o;
boolean audible;
if( oCfg == null ) return;
// if( !doc.bird.attemptShared( Session.DOOR_TRACKS, 250 )) return;
// try {
if( doc.audioTracks.size() != ct.numInChans ) {
Server.getPrintStream().println( "Input channel mismatch!" );
return;
}
for( int ch = 0; ch < ct.numInChans; ch++ ) {
// o = doc.audioTracks.get( ch ).getMap().getValue( SessionObject.MAP_KEY_FLAGS );
// if( (o != null) && (o instanceof Number) ) {
// muted = (((Number) o).intValue() & (SessionObject.FLAGS_MUTE | SessionObject.FLAGS_VIRTUALMUTE)) != 0;
// } else {
// muted = false;
// }
audible = ((AudioTrack) doc.audioTracks.get( ch )).isAudible();
bndl.addPacket( ct.synthsPan[ ch ].runMsg( audible ));
}
// }
// finally {
// doc.bird.releaseShared( Session.DOOR_TRACKS );
// }
}
// sync : attempts shared on DOOR_TRACKS
private void addChannelPanMessages( OSCBundle bndl )
{
Object o;
MapManager map;
float pos, width;
if( oCfg == null ) return;
// if( !doc.bird.attemptShared( Session.DOOR_TRACKS, 250 )) return;
// try {
if( doc.audioTracks.size() != ct.numInChans ) {
Server.getPrintStream().println( "Input channel mismatch!" );
return;
}
for( int ch = 0; ch < ct.numInChans; ch++ ) {
map = doc.audioTracks.get( ch ).getMap();
o = map.getValue( AudioTrack.MAP_KEY_PANAZIMUTH );
if( (o != null) && (o instanceof Number) ) {
pos = ((Number) o).floatValue() / 180;
pos = pos < 0.0f ? 2.0f - ((-pos) % 2.0f) : pos % 2.0f;
} else {
pos = 0.0f;
}
o = map.getValue( AudioTrack.MAP_KEY_PANSPREAD );
if( (o != null) && (o instanceof Number) ) {
width = ((Number) o).floatValue();
//System.out.println( "width in : " + width );
if( width <= 0.0f ) {
width = Math.max( 1.0f, width + 2.0f );
} else {
width = Math.min( 1.0f, width ) * (oCfg.numChannels - 2) + 2.0f;
}
} else {
width = 2.0f;
}
//System.out.println( "width out : " + width );
bndl.addPacket( ct.synthsPan[ ch ].setMsg(
new String[] { "pos", "width" }, new float[] { pos, width }));
}
// }
// finally {
// doc.bird.releaseShared( Session.DOOR_TRACKS );
// }
}
protected void printTimeOutMsg( String loc )
{
Server.getPrintStream().println( getResourceString( "errOSCTimeOut" ) + " : " + loc );
}
protected static String getResourceString( String key )
{
return AbstractApplication.getApplication().getResourceString( "errOSCTimeOut" );
}
private void updateSRC()
{
// srcFactor = sourceRate / serverRate;
doc.getFrame().setSRCEnabled( srcFactor != 1.0 );
}
// ------------ OSCRouter interface ------------
public String oscGetPathComponent()
{
return OSC_SUPERCOLLIDER;
}
public void oscRoute(RoutedOSCMessage rom) {
osc.oscRoute(rom);
}
public void oscAddRouter(OSCRouter subRouter) {
osc.oscAddRouter(subRouter);
}
public void oscRemoveRouter(OSCRouter subRouter) {
osc.oscRemoveRouter(subRouter);
}
public Object oscQuery_inputGroup() {
return grpInput.getNodeID();
}
public Object oscQuery_outputGroup() {
return grpOutput.getNodeID();
}
public Object oscQuery_diskBusIndex() {
return ((ct == null) ? null : ct.busInternal.getIndex());
}
public Object oscQuery_diskBusNumChannels() {
return ((ct == null) ? null : ct.busInternal.getNumChannels());
}
public Object oscQuery_panBusIndex() {
return ((ct == null) ? null : ct.busPan.getIndex());
}
public Object oscQuery_panBusNumChannels() {
return ((ct == null) ? null : ct.busPan.getNumChannels());
}
// "createNRTFile", (String) fileName, (int) audioBusOffset, (int) controlBusOffset, (int) bufferOffset, (float) serverRate
// ; audio is written to <diskBusNumChannels> channels, beginning at <audioBusOffset>
public void oscCmd_createNRTFile(RoutedOSCMessage rom) {
final SynthDef[] defs;
final Buffer[] bufsDisk;
final Synth[] synthsBufRd;
final Bus busInternal, busPh;
final Span span = doc.timeline.getSelectionSpan();
final AudioTrail at = doc.getAudioTrail();
final long nrtPlayOffset = span.start;
final Span[] bufSpans = new Span[ 1 ];
final Group nrtGrpRoot, nrtGrpInput;
final Synth synthPhasor;
final float realRate;
final float interpolation;
final double nrtServerRate;
final float rate = 1.0f;
Server nrtServer = null;
NRTFile f = null;
int argIdx = 1;
int audioBusOffset, bufferOffset; //, controlBusOffset;
OSCBundle bndl;
double time = 0.0;
boolean even;
int nrtClock;
long pos = nrtPlayOffset;
if (ct == null) {
try {
rom.replyFailed(1);
} catch (IOException e11) {
OSCRoot.failed(rom, e11);
}
}
try {
f = NRTFile.openAsWrite( new File( rom.msg.getArg( argIdx ).toString() ));
argIdx++;
audioBusOffset = ((Number) rom.msg.getArg( argIdx )).intValue();
argIdx++;
argIdx++;
bufferOffset = ((Number) rom.msg.getArg( argIdx )).intValue();
argIdx++;
nrtServerRate = ((Number) rom.msg.getArg( argIdx )).doubleValue();
nrtServer = new Server( "nrt" );
f.write( SuperColliderClient.getInstance().loadDefsMsg() );
defs = createInputDefs( ct.chanMaps ); // ct.numInputChannels
if( defs != null ) {
for (SynthDef def : defs) {
f.write(def.recvMsg());
}
}
srcFactor = sourceRate / nrtServerRate;
realRate = (float) (rate * srcFactor);
interpolation = realRate == 1.0f ? 1f : 3f;
nrtGrpRoot = Group.basicNew( nrtServer );
f.write( nrtGrpRoot.addToHeadMsg( nrtServer.getDefaultGroup() ));
nrtGrpInput = Group.basicNew( nrtServer );
f.write( nrtGrpInput.addToTailMsg( nrtGrpRoot ));
synthsBufRd = new Synth[ ct.numFiles ];
busInternal = new Bus( nrtServer, kAudioRate, audioBusOffset, ct.numInChans );
audioBusOffset += busInternal.getNumChannels();
busPh = new Bus( nrtServer, kAudioRate, audioBusOffset );
audioBusOffset += busPh.getNumChannels();
bufsDisk = new Buffer[ ct.numFiles ];
for( int i = 0; i < ct.numFiles; i++ ) {
bufsDisk[ i ] = new Buffer( nrtServer, DISKBUF_SIZE, ct.chanMaps[ i ].length, bufferOffset++ );
f.write( bufsDisk[ i ].allocMsg() );
}
for( int i = 0; i < ct.numFiles; i++ ) {
synthsBufRd[ i ] = Synth.basicNew( "eisk-input" + ct.chanMaps[ i ].length, nrtServer );
}
synthPhasor = Synth.basicNew( "eisk-phasor", nrtServer );
for( nrtClock = 0, even = true;; nrtClock++, even = !even ) {
if( even ) {
pos = nrtClock * DISKBUF_SIZE_HM - DISKBUF_PAD + nrtPlayOffset;
} else {
pos = nrtClock * DISKBUF_SIZE_HM + nrtPlayOffset;
}
if( pos >= span.stop ) break;
f.setTime( time );
bndl = new OSCBundle( time );
// if( pos >= DISKBUF_PAD ) {
if( pos < 0 ) {
for (Buffer aBufsDisk : bufsDisk) {
bndl.addPacket(aBufsDisk.fillMsg(0, DISKBUF_PAD * aBufsDisk.getNumChannels(), 0.0f));
}
pos += DISKBUF_PAD;
}
// bufSpans[ 0 ] = new Span( pos - DISKBUF_PAD, pos - DISKBUF_PAD + DISKBUF_SIZE_H );
bufSpans[ 0 ] = new Span( pos, pos + DISKBUF_SIZE_H );
at.addBufferReadMessages( bndl, bufSpans, bufsDisk, even ? 0 : DISKBUF_SIZE_H );
f.write( bndl );
if( nrtClock == 0 ) {
for( int i = 0, off = 0; i < ct.numFiles; i++ ) {
f.write( synthsBufRd[ i ].newMsg( nrtGrpInput, new String[] {
"i_aInBf", "i_aOtBs", "i_aPhBs", "i_intrp" }, new float[] {
bufsDisk[ i ].getBufNum(), busInternal.getIndex() + off, busPh.getIndex(), interpolation }
));
off += ct.chanMaps[ i ].length;
}
if( ct.numFiles > 0 ) {
f.write( synthPhasor.newMsg( nrtGrpInput, new String[] {
"i_aInBf", "rate", "i_aPhBs" }, new float[] {
bufsDisk[ 0 ].getBufNum(), realRate, busPh.getIndex() }));
}
} else {
time = (nrtClock * DISKBUF_SIZE_HM / sourceRate) + 0.1; // a bit beyond that spot to avoid rounding errors
}
}
time = span.getLength() / sourceRate;
f.setTime( time );
f.write( nrtGrpRoot.freeMsg() );
for (Buffer aBufsDisk : bufsDisk) {
f.write(aBufsDisk.freeMsg());
}
f.close();
f = null;
try {
rom.replyDone( 1, new Object[0] );
}
catch( IOException e11 ) {
OSCRoot.failed(rom, e11);
}
} catch (ClassCastException e1) {
OSCRoot.failedArgType(rom, argIdx);
} catch (IndexOutOfBoundsException e1) {
OSCRoot.failedArgCount(rom);
} catch (IOException e1) {
e1.printStackTrace();
try {
rom.replyFailed(1);
} catch (IOException e11) {
OSCRoot.failed(rom, e11);
}
} finally {
if (nrtServer != null) nrtServer.dispose();
if (f != null) f.dispose();
}
}
// ------------- TimelineListener interface -------------
public void timelineChanged(TimelineEvent e) {
final double newSrcFactor;
sourceRate = doc.timeline.getRate();
newSrcFactor = sourceRate / serverRate;
if (newSrcFactor != srcFactor) {
srcFactor = newSrcFactor;
updateSRC();
}
}
public void timelineSelected(TimelineEvent e) { /* ignored */ }
public void timelinePositioned(TimelineEvent e) { /* ignored */ }
public void timelineScrolled(TimelineEvent e) { /* ignored */ }
// ------------- TransportListener interface -------------
public void transportStop(Transport t, long pos) {
trigNodeID = -1;
if (!server.isRunning() || (ct == null)) return;
try {
trigResp.remove();
final OSCBundle bndl = new OSCBundle();
bndl.addPacket(grpInput.freeAllMsg());
if (!activeOutput) {
bndl.addPacket(grpOutput.runMsg(false));
syncOutput.deactivate(bndl);
}
if (!activeInput) {
syncInput.deactivate(bndl);
}
server.sendBundle(bndl);
} catch (IOException e1) {
printError("transportStop", e1);
}
}
// XXX sync
public void transportPosition(Transport t, long pos, double rate) {
transportStop(t, pos);
transportPlay(t, pos, rate);
}
// irgendwie noch nicht so 100% fertig, manchmal scheinen buffer updates
// nicht korrekt (aktuell spielende buffer haelfte -> anschliessend alles ok)
public void transportReadjust( Transport t, long readjusted, double rate )
{
final OSCBundle bndl;
Span[] bufSpans;
long pos, start;
int even, nextClock, fill, bufOff, numCh;
// pos = nextClock * DISKBUF_SIZE_HM - ((1 - even) * DISKBUF_PAD) + playOffset;
playOffset = readjusted;
// now refresh dem buffers to make sure they reflect the new loop!
bndl = new OSCBundle();
for( int i = 0; i < 2; i++ ) {
nextClock = clock + i;
even = nextClock & 1;
// pos = (clock + even) * DISKBUF_SIZE_HM - ((1 - even) * DISKBUF_PAD) + playOffset;
pos = nextClock * DISKBUF_SIZE_HM - ((1 - even) * DISKBUF_PAD) + playOffset;
start = Math.max( 0, pos );
fill = (int) (start - pos);
bufOff = even * DISKBUF_SIZE_H;
bufSpans = t.foldSpans( new Span( start, pos + DISKBUF_SIZE_H ), MIN_LOOP_LEN );
checkSpans: if( bufSpans.length == lastBufSpans[ even ].length ) {
for( int j = 0; j < bufSpans.length; j++ ) {
if( !bufSpans[ j ].equals( lastBufSpans[ even ][ j ])) break checkSpans;
}
continue;
}
if( fill > 0 ) {
for( int j = 0; j < ct.bufsDisk.length; j++ ) {
numCh = ct.bufsDisk[ j ].getNumChannels();
bndl.addPacket( ct.bufsDisk[ j ].fillMsg( bufOff * numCh, fill * numCh, 0.0f ));
}
}
doc.getAudioTrail().addBufferReadMessages( bndl, bufSpans, ct.bufsDisk, bufOff + fill );
if( DEBUG_FOLD ) {
System.out.println( "------A " + nextClock + ", " + even + ", " + playOffset + ", " + pos );
for( int k = 0, m = bufOff + fill; k < bufSpans.length; k++ ) {
System.out.println( "i = " + k + "; " + bufSpans[ k ] + " -> " + m );
m += bufSpans[ k ].getLength();
}
}
lastBufSpans[ even ] = bufSpans;
}
if( bndl.getPacketCount() > 0 ) {
//System.out.println();
try {
if( !server.sync( bndl, TIMEOUT )) {
printTimeOutMsg( "readjust" );
}
}
catch( IOException e1 ) {
printError( "transportPlay", e1 );
}
}
}
// sync : shared on MTE
public void transportPlay( Transport t, long pos, double rate )
{
final float realRate;
final float interpolation;
final Span[] bufSpans;
final long start;
final int fill;
OSCBundle bndl;
realRate = (float) (rate * srcFactor);
interpolation = realRate == 1.0f ? 1f : 3f;
// synchronized( sync ) {
if( !server.isRunning() ) return;
if( ct == null ) { // as of oct '05 may be null if lockmanager timeout in setOutputConfig occurs
// if( !doc.bird.attemptShared( Session.DOOR_TRACKS, 250 )) {
//System.err.println( "OH NO!" );
// return;
// }
// try {
System.out.println( "transportPlay : rebuildSynths" );
rebuildSynths();
if( ct == null ) return;
// }
// finally {
// doc.bird.releaseShared( Session.DOOR_TRACKS );
// }
}
// if( !doc.bird.attemptShared( Session.DOOR_MTE, 500 )) return;
try {
bndl = new OSCBundle();
start = Math.max( 0, pos - DISKBUF_PAD );
fill = (int) (start + DISKBUF_PAD - pos);
if( fill > 0 ) {
for( int i = 0; i < ct.bufsDisk.length; i++ ) {
bndl.addPacket( ct.bufsDisk[ i ].fillMsg( 0, fill * ct.bufsDisk[ i ].getNumChannels(), 0.0f ));
}
}
bufSpans = t.foldSpans( new Span( start, pos - DISKBUF_PAD + DISKBUF_SIZE ), MIN_LOOP_LEN );
doc.getAudioTrail().addBufferReadMessages( bndl, bufSpans, ct.bufsDisk, fill );
if( DEBUG_FOLD ) {
System.out.println( "------P "+ clock + ", X, " + playOffset + ", " + pos );
for( int k = 0, m = fill; k < bufSpans.length; k++ ) {
System.out.println( "i = " + k + "; " + bufSpans[ k ] + " -> " + m );
m += bufSpans[ k ].getLength();
}
System.out.println();
}
lastBufSpans[ 0 ] = emptySpans;
lastBufSpans[ 1 ] = emptySpans;
if( !server.sync( bndl, TIMEOUT )) {
printTimeOutMsg( "play" );
return;
}
}
catch( IOException e1 ) {
printError( "transportPlay", e1 );
}
// finally {
// doc.bird.releaseShared( Session.DOOR_MTE );
// }
bndl = new OSCBundle();
bndl.addPacket( grpInput.freeAllMsg() );
ct.newInputSynths(); // re-create synthsBufRd and synthPhasor
for( int i = 0, off = 0; i < ct.numFiles; i++ ) {
bndl.addPacket( ct.synthsBufRd[ i ].newMsg( grpInput, new String[] {
"i_aInBf", "i_aOtBs", "i_aPhBs", "i_intrp" }, new float[] {
ct.bufsDisk[ i ].getBufNum(), ct.busInternal.getIndex() + off, busPhasor.getIndex(), interpolation }
));
nw.register( ct.synthsBufRd[ i ]);
off += ct.chanMaps[ i ].length;
}
if( ct.numFiles > 0 ) {
bndl.addPacket( ct.synthPhasor.newMsg( grpInput, new String[] {
"i_aInBf", "rate", "i_aPhBs" }, new float[] {
ct.bufsDisk[ 0 ].getBufNum(), realRate, busPhasor.getIndex() }));
nw.register( ct.synthPhasor );
}
bndl.addPacket( grpOutput.runMsg( true ));
playOffset = pos;
clock = 0;
try {
trigResp.add();
if( !activeInput ) syncInput.activate( bndl );
if( !activeOutput ) syncOutput.activate( bndl );
trigNodeID = ct.synthPhasor.getNodeID();
server.sendBundle( bndl );
}
catch( IOException e1 ) {
printError( "transportPlay", e1 );
}
// } // synchronized( sync )
}
public void transportQuit( Transport t )
{
trigNodeID = -1;
}
// -------------- SessionCollection.Listener classes --------------
public void sessionObjectMapChanged(SessionCollection.Event e) {
if (server.isRunning() && (ct != null)) {
OSCBundle bndl = null;
if (e.setContains(AudioTrack.MAP_KEY_PANAZIMUTH) ||
e.setContains(AudioTrack.MAP_KEY_PANSPREAD)) {
bndl = new OSCBundle();
addChannelPanMessages(bndl);
} else if (e.setContains(SessionObject.MAP_KEY_FLAGS)) {
bndl = new OSCBundle();
addChannelMuteMessages(bndl);
}
if ((bndl != null) && (bndl.getPacketCount() > 0)) {
try {
server.sendBundle(bndl);
} catch (IOException e1) {
printError("Set Channel Status", e1);
}
}
}
}
public void sessionCollectionChanged(SessionCollection.Event e) { /* XXX should react (well realtime host does) */ }
public void sessionObjectChanged(SessionCollection.Event e) { /* ignored */ }
// -------------- internal classes --------------
private class Context
{
protected final Synth[] synthsBufRd; // buffer readers for all parallel files
protected final Synth[] synthsPan; // for each input channel a pan synth with numOutputs output channels
protected final Synth[] synthsRoute; // for each pan output one route to the audio interface channel
protected Synth synthPhasor;
protected final Buffer[] bufsDisk;
protected final Bus busInternal; // the buffer-reader writes to this bus (numInputChannels)
protected final Bus busPan;
protected final int numFiles;
protected final int numInChans; // sum over all files
protected final int[][] chanMaps; // re audio input files
protected boolean bufsAllocated = false;
private boolean disposed = false;
/*
* @throws IOException if the server ran out of busses
* or buffers
*/
protected Context(int[][] channelMaps, int numInputChannels, int numConfigOutputs)
throws IOException {
this.chanMaps = channelMaps;
numFiles = channelMaps.length;
this.numInChans = numInputChannels;
synthsBufRd = new Synth[ numFiles ];
synthsPan = new Synth[ numInputChannels ];
for( int i = 0; i < numInputChannels; i++ ) {
synthsPan[ i ] = Synth.basicNew( "eisk-pan" + numConfigOutputs, server );
}
synthsRoute = new Synth[ numConfigOutputs ];
for( int i = 0; i < numConfigOutputs; i++ ) {
synthsRoute[ i ] = Synth.basicNew( "eisk-route", server );
}
busInternal = Bus.audio( server, numInputChannels );
busPan = Bus.audio( server, numConfigOutputs );
if( (busInternal == null) || (busPan == null) ) {
if( busInternal != null ) busInternal.free();
if( busPan != null ) busPan.free();
throw new IOException( getResourceString("scErrNoBuses"));
}
bufsDisk = new Buffer[ numFiles ];
for( int i = 0; i < numFiles; i++ ) {
bufsDisk[ i ] = new Buffer( server, DISKBUF_SIZE, channelMaps[ i ].length );
if( bufsDisk[ i ] == null ) {
for( int j = 0; j < i; j++ ) bufsDisk[ j ].freeMsg(); // cleans up allocator!
throw new IOException( getResourceString( "scErrNoBuffers" ));
}
}
}
protected void newInputSynths()
{
for( int i = 0; i < numFiles; i++ ) {
synthsBufRd[ i ] = Synth.basicNew( "eisk-input" + chanMaps[ i ].length, server );
}
synthPhasor = Synth.basicNew( "eisk-phasor", server );
}
protected void dispose()
throws IOException {
if (disposed) throw new IllegalStateException("Double disposal");
disposed = true;
busInternal.free();
busPan.free();
final OSCBundle bndl = new OSCBundle();
for (Buffer aBufsDisk : bufsDisk) bndl.addPacket(aBufsDisk.freeMsg());
if (bufsAllocated) {
bufsAllocated = false;
if ((bndl.getPacketCount() > 0) && server.isRunning() && !server.sync(bndl, TIMEOUT)) {
printTimeOutMsg("dispose");
}
}
}
}
}