/*
* Bus.java
* JCollider
*
* Copyright (c) 2004-2010 Hanns Holger Rutz. All rights reserved.
*
* This software 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, june 1991 of the License, or (at your option) any later version.
*
* This software 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 (gpl.txt) along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de , or visit http://www.sciss.de/jcollider
*
*
* JCollider is closely modelled after SuperCollider Language,
* often exhibiting a direct translation from Smalltalk to Java.
* SCLang is a software originally developed by James McCartney,
* which has become an Open Source project.
* See http://www.audiosynth.com/ for details.
*
*
* Changelog:
* 04-Aug-05 created
*/
package de.sciss.jcollider;
import java.io.IOException;
import java.io.PrintStream;
import de.sciss.net.OSCMessage;
/**
* Mimics SCLang's Bus class,
* that is, it's a client side
* representation of an audio or control bus
*
* @warning this is a quick direct translation from SClang
* which is largely untested. before all methods have been
* thoroughly verified, excepted some of them to be wrong
* or behave different than expected. what certainly works
* is instantiation
*
* @author Hanns Holger Rutz
* @version 0.31, 08-Oct-07
*
* @todo missing methods (set, setn, fill, get, getn ...)
*/
public class Bus
implements Constants
{
private final Server server;
private Object rate;
private int index;
private int numChannels;
/**
* Creates an mono audio bus on the server at index 0.
* This does not use the server's allocators.
*
* @param server the <code>Server</code> on which the bus resides
*/
public Bus( Server server )
{
this( server, kAudioRate );
}
public Bus( Server server, Object rate )
{
this( server, rate, 0 );
}
public Bus( Server server, Object rate, int index )
{
this( server, rate, index, 1 );
}
public Bus( Server server, Object rate, int index, int numChannels )
{
this.rate = rate;
this.index = index;
this.numChannels = numChannels;
this.server = server;
}
public static Bus control( Server server )
{
return Bus.control( server, 1 );
}
public static Bus control( Server server, int numChannels )
{
final int alloc = server.getControlBusAllocator().alloc( numChannels );
if( alloc == -1 ) {
Server.getPrintStream().println(
"Bus.control: failed to get a control bus allocated. " +
"numChannels: " + numChannels + "; server: " + server.getName() );
return null;
} else {
return new Bus( server, kControlRate, alloc, numChannels );
}
}
public static Bus audio( Server server )
{
return Bus.audio( server, 1 );
}
public static Bus audio( Server server, int numChannels )
{
final int alloc = server.getAudioBusAllocator().alloc( numChannels );
if( alloc == -1 ) {
Server.getPrintStream().println(
"Bus.audio: failed to get a audio bus allocated. " +
"numChannels: " + numChannels + "; server: " + server.getName() );
return null;
} else {
return new Bus( server, kAudioRate, alloc, numChannels );
}
}
public static Bus alloc( Server server, Object rate )
{
return Bus.alloc( server, rate, 1 );
}
public static Bus alloc( Server server, Object rate, int numChannels )
{
if( rate == kAudioRate ) {
return Bus.audio( server, numChannels );
} else if( rate == kControlRate ) {
return Bus.control( server, numChannels );
} else {
throw new IllegalArgumentException( rate.toString() );
}
}
public String toString()
{
return( "Bus(" + server.getName() + ", " + getRate() + ", " + getIndex() + ", " + getNumChannels() + ")" );
}
public Object getRate()
{
return rate;
}
public int getNumChannels()
{
return numChannels;
}
public int getIndex()
{
return index;
}
public Server getServer()
{
return server;
}
private void setRate( Object rate )
{
this.rate = rate;
}
private void setNumChannels( int numChannels )
{
this.numChannels = numChannels;
}
private void setIndex( int index )
{
this.index = index;
}
// for mono
public void set( float value )
throws IOException
{
getServer().sendMsg( setMsg( value ));
}
public void set( int[] offsets, float[] values )
throws IOException
{
getServer().sendMsg( setMsg( offsets, values ));
}
/**
* Set the value of a monophonic bus.
* For multichannel busses, use
* setnMsg instead.
*/
public OSCMessage setMsg( float value )
{
return new OSCMessage( "/c_set", new Object[] {
new Integer( getIndex() ), new Float( value )});
}
/**
* @warning has not been tested
*/
public OSCMessage setMsg( int[] offsets, float[] values )
{
final int numEntries = offsets.length;
if( numEntries != values.length ) {
throw new IllegalArgumentException( "Number of offsets / values must be the same" );
}
final Object[] args = new Object[ numEntries << 1 ];
final int idx = getIndex();
for( int i = 0, j = 0; i < numEntries; i++ ) {
args[ j++ ] = new Integer( idx + offsets[ i ]);
args[ j++ ] = new Float( values[ i ]);
}
return new OSCMessage( "/c_set", args );
}
public void setn( float[] values )
throws IOException
{
getServer().sendMsg( setnMsg( values ));
}
public void setn( int[] offsets, float[][] values )
throws IOException
{
getServer().sendMsg( setnMsg( offsets, values ));
}
/**
* @warning has not been tested
*/
public OSCMessage setnMsg( float[] values )
{
final int numValues = values.length;
final Object[] args = new Object[ numValues + 2 ];
args[ 0 ] = new Integer( getIndex() );
args[ 1 ] = new Integer( numValues );
for( int i = 0, j = 2; i < numValues; i++, j++ ) {
args[ j ] = new Float( values[ i ]);
}
return new OSCMessage( "/c_setn", args );
}
/**
* @warning has not been tested
*/
public OSCMessage setnMsg( int[] offsets, float[][] values )
{
final int numEntries = offsets.length;
if( numEntries != values.length ) {
throw new IllegalArgumentException( "Number of offsets / values must be the same" );
}
int numValues = 0;
for( int i = 0; i < numEntries; i++ ) numValues += values[ i ].length;
final int idx = getIndex();
final Object[] args = new Object[ (numEntries << 1) + numValues ];
for( int i = 0, j = 0; i < numEntries; i++ ) {
args[ j++ ] = new Integer( idx + offsets[ i ]);
final float[] vals = values[ i ];
final int numVals = vals.length;
args[ j++ ] = new Integer( numVals );
for( int k = 0; k < numVals; k++, j++ ) {
args[ j ] = new Float( vals[ k ]);
}
}
return new OSCMessage( "/c_setn", args );
}
public void fill( float value )
throws IOException
{
getServer().sendMsg( fillMsg( value ));
}
public void fill( int offset, int numChans, float value )
throws IOException
{
getServer().sendMsg( fillMsg( offset, numChans, value ));
}
public void fill( int[] numChans, float[] values )
throws IOException
{
getServer().sendMsg( fillMsg( numChans, values ));
}
public void fill( int[] offsets, int[] numChans, float[] values )
throws IOException
{
getServer().sendMsg( fillMsg( offsets, numChans, values ));
}
public OSCMessage fillMsg( float value )
{
return fillMsg( 0, getNumChannels(), value );
}
public OSCMessage fillMsg( int offset, int numChans, float value )
{
return new OSCMessage( "/c_fill", new Object[] {
new Integer( getIndex() + offset ), new Integer( numChans ), new Float( value )});
}
public OSCMessage fillMsg( int[] numChans, float[] values )
{
final int numEntries = numChans.length;
final int[] offsets = new int[ numEntries ];
for( int i = 0, j = 0; i < numEntries; i++ ) {
offsets[ i ] = j;
j += numChans[ i ];
}
return fillMsg( offsets, numChans, values );
}
/**
* @warning has not been tested
*/
public OSCMessage fillMsg( int[] offsets, int[] numChans, float[] values )
{
final int numEntries = offsets.length;
if( (numEntries != numChans.length) || (numEntries != values.length) ) {
throw new IllegalArgumentException( "Number of offsets / numChans / values must be the same" );
}
final Object[] args = new Object[ numEntries * 3 ];
final int idx = getIndex();
for( int i = 0, j = 0; i < numEntries; i++ ) {
args[ j++ ] = new Integer( idx + offsets[ i ]);
args[ j++ ] = new Integer( numChans[ i ]);
args[ j++ ] = new Integer( idx + offsets[ i ]);
}
return new OSCMessage( "/c_fill", args );
}
public void get( GetCompletionAction action )
throws IOException
{
get( 0, action );
}
public void get( int offset, GetCompletionAction action )
throws IOException
{
get( new int[] { offset }, action );
}
/**
* @warning has not been tested
*/
public void get( final int[] offsets, final GetCompletionAction action )
throws IOException
{
final OSCMessage m = getMsg( offsets );
final int idx = getIndex();
final OSCResponderNode resp = new OSCResponderNode( getServer(), "/c_set", new OSCResponderNode.Action() {
public void respond( OSCResponderNode r, OSCMessage msg, long time )
{
final int numVals = msg.getArgCount() >> 1;
if( numVals != offsets.length ) return;
for( int i = 0, j = 0; i < numVals; i++, j += 2 ) {
if( ((Number) msg.getArg( j )).intValue() != idx + offsets[ i ]) return;
}
final float[] vals = new float[ numVals ];
for( int i = 0, j = 1; i < numVals; i++, j += 2 ) {
vals[ i ] = ((Number) msg.getArg( j )).floatValue();
}
r.remove();
action.completion( Bus.this, vals );
}
});
resp.add();
getServer().sendMsg( m );
}
/**
* @warning has not been tested
*/
public void getn( final int[] offsets, final int[] numChans, final GetCompletionAction action )
throws IOException
{
final int numEntries = offsets.length;
if( numEntries != numChans.length ) {
throw new IllegalArgumentException( "Number of offsets / numChans must be the same" );
}
final OSCMessage m = getnMsg( offsets, numChans );
final int idx = getIndex();
final OSCResponderNode resp = new OSCResponderNode( getServer(), "/c_setn", new OSCResponderNode.Action() {
public void respond( OSCResponderNode r, OSCMessage msg, long time )
{
final int numArgs = msg.getArgCount();
int numVals = 0;
for( int i = 0, j = 0; j < numArgs; i++ ) {
if( i >= numEntries ) return;
final int nc = numChans[ i ];
if( ((Number) msg.getArg( j++ )).intValue() != idx + offsets[ i ]) return;
if( ((Number) msg.getArg( j++ )).intValue() != nc ) return;
numVals += nc;
j += nc;
}
final float[] vals = new float[ numVals ];
for( int i = 0, j = 2, k = 0; i < numEntries; i++, j += 2 ) {
for( int m = 0; m < numChans[ i ]; m++ ) {
vals[ k++ ] = ((Number) msg.getArg( j++ )).floatValue();
}
}
r.remove();
action.completion( Bus.this, vals );
}
});
resp.add();
getServer().sendMsg( m );
}
public OSCMessage getMsg()
{
return new OSCMessage( "/c_get", new Object[] { new Integer( getIndex() )});
}
/**
* @warning has not been tested
*/
public OSCMessage getMsg( int[] offsets )
{
final int numOffsets = offsets.length;
final Object[] args = new Object[ numOffsets ];
final int idx = getIndex();
for( int i = 0; i < numOffsets; i++ ) {
args[ i ] = new Integer( idx + offsets[ i ]);
}
return new OSCMessage( "/c_get", args );
}
public OSCMessage getnMsg()
{
return getnMsg( 0, getNumChannels() );
}
public OSCMessage getnMsg( int numChans )
{
return getnMsg( 0, numChans );
}
public OSCMessage getnMsg( int offset, int numChans )
{
return new OSCMessage( "/c_getn", new Object[] {
new Integer( getIndex() + offset ), new Integer( numChans )});
}
/**
* @warning has not been tested
*/
public OSCMessage getnMsg( int[] offsets, int[] numChans )
{
final int numEntries = offsets.length;
if( numEntries != numChans.length ) {
throw new IllegalArgumentException( "Number of offsets / numChans must be the same" );
}
final int idx = getIndex();
final Object[] args = new Object[ numEntries << 1 ];
for( int i = 0, j = 0; i < numEntries; i++ ) {
args[ j++ ] = new Integer( idx + offsets[ i ]);
args[ j++ ] = new Integer( numChans[ i ]);
}
return new OSCMessage( "/c_getn", args );
}
public void free()
{
final int idx = getIndex();
if( idx == -1 ) {
printOn( Server.getPrintStream() );
Server.getPrintStream().println( " has already been freed" );
return;
}
if( getRate() == kAudioRate ) {
getServer().getAudioBusAllocator().free( idx );
} else if( getRate() == kControlRate ) {
getServer().getControlBusAllocator().free( idx );
} else {
throw new IllegalStateException( getRate().toString() );
}
setIndex( -1 );
setNumChannels( -1 );
}
// allow reallocation
public void alloc()
{
if( getRate() == kAudioRate ) {
setIndex( getServer().getAudioBusAllocator().alloc( getNumChannels() ));
} else if( getRate() == kControlRate ) {
setIndex( getServer().getControlBusAllocator().alloc( getNumChannels() ));
} else {
throw new IllegalStateException( getRate().toString() );
}
}
public void realloc()
{
if( getIndex() == -1 ) return;
final Object oldRate = getRate();
final int oldCh = getNumChannels();
free();
setRate( oldRate );
setNumChannels( oldCh );
alloc();
}
// // alternate syntaxes
// setAll { arg value;
// this.fill(value,numChannels);
// }
//
// value_ { arg value;
// this.fill(value,numChannels);
// }
public void printOn( PrintStream stream )
{
stream.print( this.getClass().getName() + "(" + getServer().getName() + "," + getRate() + "," +
getIndex() + "," + getNumChannels() + ")" );
}
public boolean equals( Object o )
{
if( o instanceof Bus ) {
final Bus aBus = (Bus) o;
return( this.getIndex() == aBus.getIndex() &&
this.getNumChannels() == aBus.getNumChannels() &&
this.getRate() == aBus.getRate() &&
this.getServer() == aBus.getServer() );
} else {
return false;
}
}
public int hashCode()
{
return( getIndex() ^ -getNumChannels() ^ getRate().hashCode() ^ getServer().hashCode() );
}
/**
* Queries whether this bus is playing audio
* onto the hardware audio interface channels.
*
* @return <code>true</code> if this bus plays audio on audible interface channels,
* <code>false</code> otherwise
*/
public boolean isAudioOut()
{
return( (rate == kAudioRate) && (getIndex() < getServer().getOptions().getFirstPrivateBus()) );
}
// ar {
// if(rate == \audio,{
// ^In.ar(index,numChannels)
// },{
// //"Bus converting control to audio rate".inform;
// ^K2A.ar( In.kr(index,numChannels) )
// })
// }
//
// kr {
// if(rate == \audio,{
// ^A2K.kr(index,numChannels)
// },{
// ^In.kr(index,numChannels)
// })
// }
// play { arg target=0, outbus, fadeTime, addAction=\addToTail;
// if(this.isAudioOut.not,{ // returns a Synth
// ^{ this.ar }.play(target, outbus, fadeTime, addAction);
// });
// }
// ---------- internal classes and interfaces ----------
/**
* Interface describing an action to take place after
* an asynchronous bus query is completed.
*/
public static interface GetCompletionAction
{
/**
* Executes the completion action.
*
* @param bus the bus whose asynchronous action is completed.
*/
public void completion( Bus bus, float[] values );
}
}