/*
* OSCBundle.java
* de.sciss.net (NetUtil)
*
* Copyright (c) 2004-2009 Hanns Holger Rutz. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; 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
*
*
* Changelog:
* 25-Jan-05 created from de.sciss.meloncillo.net.OSCBundle
* 01-Mar-05 sample accurate timing through new setTimeTag() method
* (annot. 12-May-05 : scsynth OSC processing is *not* audiorate accurate)
* 26-May-05 moved to de.sciss.net package ; slight modifications
* 21-Jun-05 extended javadoc
* 28-Apr-07 moved codec stuff to OSCPacketCodec
*/
package de.sciss.net;
import java.util.ArrayList;
import java.util.List;
//import java.util.Map;
/**
* Implementation of the OSC-Bundle
* which assembles several OSC-Packets
* under the same timetag. See <A HREF="http://opensoundcontrol.org/spec-1_0">opensoundcontrol.org/spec-1_0</A> for
* the specification of the bundle format.
* <p>
* The bundle time can be calculated in
* different ways: specifying a <code>long</code>
* which represents the milliseconds since
* 1 jan 1970 as returned by <code>System.currentTimeMillis()</code>,
* produces the required network time tag
* as required by the OSC specification.
* Alternatively, using a <code>float</code>
* for a relative time offset in seconds can
* be used when running SuperCollider in offline
* mode. Third, there is a version that calculates
* a sample accurate time tag. However, scsynth
* doesn't process bundles with this accuracy, so
* it's kind of useless.
* <p>
* To assemble a bundle, you create a new instance of
* <code>OSCBundle</code>, call <code>addPacket</code>
* one or several times, then write the contents of
* the bundle to a <code>ByteBuffer</code> using the
* method <code>encode</code>. The byte buffer can then
* be written to a <code>DatagramChannel</code>. Here is an example:
* <pre>
* OSCBundle bndl;
* DatagramChannel dch = null;
*
* final ByteBuffer buf = ByteBuffer.allocateDirect( 1024 );
* final SocketAddress addr = new InetSocketAddress( "localhost", 57110 );
* final long serverLatency = 50;
* final long now = System.currentTimeMillis() + serverLatency;
*
* try {
* dch = DatagramChannel.open();
* dch.configureBlocking( true );
* bndl = new OSCBundle( now );
* bndl.addPacket( new OSCMessage( "/s_new", new Object[] { "default",
* new Integer( 1001 ), new Integer( 1 ), new Integer( 0 ),
* "out", new Integer( 0 ), "freq", new Float( 666 ), "amp", new Float( 0.1f )}));
* bndl.encode( buf );
* buf.flip();
* dch.send( buf, addr );
* buf.clear();
* bndl = new OSCBundle( now + 2000 ); // two seconds later
* bndl.addPacket( new OSCMessage( "/n_free", new Object[] { new Integer( 1001 )}));
* bndl.encode( buf );
* buf.flip();
* dch.send( buf, addr );
* }
* catch( IOException e2 ) {
* System.err.println( e2.getLocalizedMessage() );
* }
* finally {
* if( dch != null ) {
* try {
* dch.close();
* }
* catch( IOException e4 ) {};
* }
* }
* </pre>
*
* Note that this example uses the old way of sending messages.
* A easier way is to create an <code>OSCTransmitter</code> which
* handles the byte buffer for you. See the <code>OSCReceiver</code> doc
* for an example using a dedicated transmitter.
*
* @author Hanns Holger Rutz
* @version 0.33, 28-Apr-07
*
* @see OSCReceiver
*/
public class OSCBundle
extends OSCPacket
{
/**
* This is the initial string
* of an OSC bundle datagram
*/
public static final String TAG = "#bundle";
/**
* The special timetag value
* to indicate that the bundle be
* processed as soon as possible
*/
public static final long NOW = 1;
private static final long SECONDS_FROM_1900_TO_1970 = 2208988800L;
// private static final byte[] cmd1 = { 0x23, 0x62, 0x75, 0x6E, 0x64, 0x6C, 0x65, 0x00 }; // "#bundle" (4-aligned)
private long timetag; // 64 bit fixed point seconds since 1 jan 1900
protected final List collPackets = new ArrayList();
/**
* Creates a new empty OSCBundle
* with timetag set to "immediately".
* SuperCollider recognizes this special timetime
* to process the bundle just when it arrives.
*/
public OSCBundle()
{
super();
timetag = NOW; // special code
}
/**
* Creates a new empty OSCBundle
* with timetag specified by 'when'
* which is milliseconds since 1 jan 1970
* as returned by System.currentTimeMillis().
* This is converted into an absolute time
* offset since 1 jan 1900 as required by
* the OSC specs.
*
* @param when absolute time tag for the bundle
* @see java.lang.System#currentTimeMillis()
*/
public OSCBundle( long when )
{
super();
setTimeTagAbsMillis( when );
}
/**
* Creates a new empty OSCBundle
* with timetag specified by 'when'
* which is seconds relative to start
* of session. This relative time offset
* with origin of zero is understood by
* SuperCollider in NonRealTime mode.
* (see example in Non-Realtime-Synthesis.rtf).
*
* @param when relative time tag for the bundle
*/
public OSCBundle( double when )
{
super();
setTimeTagRelSecs( when );
}
/**
* Creates a new empty OSCBundle
* with timetag specified by a sample
* frame offset and an absolute time
* in milliseconds since 1 jan 1970
* as returned by <code>System.currentTimeMillis()</code>
*
* @param absMillisOffset time offset as returned by <code>System.currentTimeMillis</code>
* @param sampleFrames this offset is added to the milli second offset.
* @param sampleRate used in conjunction with <code>sampleFrames</code> to
* calculate the time offset.
*/
public OSCBundle( long absMillisOffset, long sampleFrames, int sampleRate )
{
super();
setTimeTagSamples( absMillisOffset, sampleFrames, sampleRate );
}
/**
* Adds a new <code>OSCPacket</code> to the tail
* of the bundle. Passing <code>null</code> is
* allowed in which case no action
* is performed.
*
* @param p the packet to add to the tail of the bundle
*/
public void addPacket( OSCPacket p )
{
if( p != null ) {
synchronized( collPackets ) {
collPackets.add( p );
}
}
}
/**
* Gets the <code>OSCPacket</code> at the provided
* index which must be between 0 inclusive
* and <code>getPacketCount()</code> exclusive.
* If bundles are nested, each nested
* bundle will count as one packet of course.
*
* @param idx index of the packet to get
* @return packet at index <code>idx</code>
*/
public OSCPacket getPacket( int idx )
{
synchronized( collPackets ) {
return (OSCPacket) collPackets.get( idx );
}
}
/**
* Returns the number of packets
* currently assembled in this bundle.
* If bundles are nested, each nested
* bundle will count as one packet of course.
*
* @return number of packets assembled in this bundle
*/
public int getPacketCount()
{
synchronized( collPackets ) {
return collPackets.size();
}
}
/**
* Removes the specified packet
*
* @param idx the index of the packet to remove
*/
public void removePacket( int idx )
{
synchronized( collPackets ) {
collPackets.remove( idx );
}
}
// public int getSize()
// throws IOException
// {
// synchronized( collPackets ) {
// int result = cmd1.length + 8 + (collPackets.size() << 2); // name, timetag, size of each bundle element
//
// for( int i = 0; i < collPackets.size(); i++ ) {
// result += ((OSCPacket) collPackets.get( i )).getSize();
// }
//
// return result;
// }
// }
/**
* Sets the bundle's timetag
* specified by a long
* which is milliseconds since 1 jan 1970
* as returned by <code>System.currentTimeMillis()</code>.
* This is converted into an absolute time
* offset since 1 jan 1900 as required by
* the OSC specs.
*
* @param when absolute time tag for the bundle
* @see java.lang.System#currentTimeMillis()
*/
public void setTimeTagAbsMillis( long when )
{
final long secsSince1900 = when / 1000 + SECONDS_FROM_1900_TO_1970;
final long secsFractional = ((when % 1000) << 32) / 1000;
timetag = (secsSince1900 << 32) | secsFractional;
}
public void setTimeTagRaw( long raw )
{
timetag = raw;
}
/**
* Sets the bundle's timetag
* specified by a double
* which is seconds relative to start
* of session. This relative time offset
* with origin of zero is understood by
* SuperCollider in NonRealTime mode.
* (see example in Non-Realtime-Synthesis.rtf).
*
* @param when relative time tag for the bundle
*/
public void setTimeTagRelSecs( double when )
{
// timetag = ((long) when << 32) | (long) ((when % 1.0) * 0x100000000L + 0.5);
timetag = ((long) when << 32) + (long) ((when % 1.0) * 0x100000000L + 0.5);
}
/**
* Sets the bundle's timetag as
* a combination of system absolute time
* and sample offset. note that
* this is not too useful, because supercollider
* will execute OSC bundles not with
* audiorate but controlrate precision!!
*
* @param absMillisOffset time offset as returned by <code>System.currentTimeMillis</code>
* @param sampleFrames this offset is added to the milli second offset.
* @param sampleRate used in conjunction with <code>sampleFrames</code> to
* calculate the time offset.
*/
public void setTimeTagSamples( long absMillisOffset, long sampleFrames, int sampleRate )
{
final double seconds= (double) sampleFrames / (double) sampleRate + (double) absMillisOffset / 1000;
timetag = (((long) seconds + SECONDS_FROM_1900_TO_1970) << 32) + (long) ((seconds % 1.0) * 0x100000000L + 0.5);
}
/**
* Returns the raw format time tag
* of the bundle
*
* @return the bundle's timetag in OSC format
*
* @todo a utility method to convert this
* to a more useful value
*/
public long getTimeTag()
{
return timetag;
}
}