/*
* MarkerTrack.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.timeline;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.tree.TreeNode;
import de.sciss.eisenkraut.edit.BasicCompoundEdit;
import de.sciss.eisenkraut.io.MarkerTrail;
import de.sciss.eisenkraut.net.OSCRoot;
import de.sciss.eisenkraut.net.OSCRouter;
import de.sciss.eisenkraut.net.OSCRouterWrapper;
import de.sciss.eisenkraut.net.RoutedOSCMessage;
import de.sciss.eisenkraut.session.Session;
import de.sciss.app.AbstractApplication;
import de.sciss.io.Marker;
import de.sciss.io.Span;
import de.sciss.timebased.MarkerStake;
import de.sciss.timebased.Stake;
import de.sciss.timebased.Trail;
public class MarkerTrack
extends Track
implements OSCRouter {
private final MarkerTrail trail;
private static final String OSC_MARKERS = "markers";
private final OSCRouterWrapper osc;
private final Session doc;
public MarkerTrack( Session doc )
{
super();
// this.trail = new MarkerTrail( doc );
this.trail = new MarkerTrail();
setName( AbstractApplication.getApplication().getResourceString( "labelMarkers" ));
this.doc = doc;
osc = new OSCRouterWrapper( doc, this );
}
public Trail getTrail()
{
return trail;
}
public Class<?> getDefaultEditor()
{
return null; // XXX
}
private String getResourceString( String key )
{
return AbstractApplication.getApplication().getResourceString( key );
}
// ---------------- TreeNode interface ----------------
public TreeNode getChildAt( int childIndex )
{
return trail.get( childIndex );
}
public int getChildCount()
{
return trail.getNumStakes();
}
public TreeNode getParent()
{
return null;
}
public int getIndex( TreeNode node )
{
if( node instanceof Stake ) {
return trail.indexOf( (Stake) node, true );
} else {
return -1;
}
}
public boolean getAllowsChildren()
{
return true;
}
public boolean isLeaf()
{
return false;
}
public Enumeration<?> children()
{
return trail.children();
}
// ------------- OSCRouter interface -------------
public String oscGetPathComponent()
{
return OSC_MARKERS;
}
public void oscRoute( RoutedOSCMessage rom )
{
osc.oscRoute( rom );
}
public void oscAddRouter( OSCRouter subRouter )
{
osc.oscAddRouter( subRouter );
}
public void oscRemoveRouter( OSCRouter subRouter )
{
osc.oscRemoveRouter( subRouter );
}
/**
* This command queries the index of a marker
* specified by its position. If no marker is found
* at this position, the resulting index is
* (-(insertion point) - 1). If more than one
* marker is found at this position, the smallest
* index is returned.
* The reply message looks as follows:
* <pre>
* [ "/get.reply", <getID>, [ <(int) idx> * N ]]
* </pre>
* Quick calculation : (1 << 31 - 1) / 96000 / 60 / 60 --> with 32bit signed ints, audio at 96 kHz can be represented if length doesn't exceed about 6 hours
*/
public Object[] oscGet_indexOf( RoutedOSCMessage rom )
{
final Object[] values = new Object[ rom.msg.getArgCount() - 3 ];
int argIdx = 3;
long n1;
int idx1;
try {
for( int i = 0; i < values.length; argIdx++, i++ ) {
n1 = ((Number) rom.msg.getArg( argIdx )).longValue();
idx1 = trail.indexOf( n1, true );
if( idx1 > 0 ) {
idx1 = trail.editGetLeftMostIndex( idx1, true, null );
}
values[ i ] = idx1;
}
return values;
}
catch( ClassCastException e1 ) {
OSCRoot.failedArgType( rom, argIdx );
}
return null;
}
public Object[] oscGet_span( RoutedOSCMessage rom )
{
if( rom.msg.getArgCount() != 5 ) {
OSCRoot.failedArgCount( rom );
return null;
}
int argIdx = 3;
try {
final long n1 = ((Number) rom.msg.getArg( argIdx )).longValue();
final long n2 = ((Number) rom.msg.getArg( ++argIdx )).longValue();
return oscGetMarkers( trail.getRange( new Span( n1, n2 ), true ));
}
catch( ClassCastException e1 ) {
OSCRoot.failedArgType( rom, argIdx );
}
return null;
}
public Object[] oscGet_range( RoutedOSCMessage rom )
{
if( rom.msg.getArgCount() != 5 ) {
OSCRoot.failedArgCount( rom );
return null;
}
int argIdx = 3;
try {
final int idx1 = Math.max( 0, ((Number) rom.msg.getArg( argIdx )).intValue() );
final int idx2 = Math.max( idx1, Math.min( trail.getNumStakes(), ((Number) rom.msg.getArg( ++argIdx )).intValue() ));
// return oscGetMarkers( new ArrayList( trail.editGetCollByStart( null ).subList( idx1, idx2 )));
return oscGetMarkers( trail.getAll( idx1, idx2, true ));
}
catch( ClassCastException e1 ) {
OSCRoot.failedArgType( rom, argIdx );
}
return null;
}
public Object[] oscGet_at(RoutedOSCMessage rom) {
final List<Object> coll = new ArrayList<Object>();
int argIdx = 3;
int idx1;
try {
for (; argIdx < rom.msg.getArgCount(); argIdx++) {
idx1 = ((Number) rom.msg.getArg(argIdx)).intValue();
if ((idx1 >= 0) && (idx1 < trail.getNumStakes())) {
coll.add(trail.get(idx1, true));
} else {
coll.add(new Marker(-1, ""));
}
}
return oscGetMarkers(coll);
} catch (ClassCastException e1) {
OSCRoot.failedArgType(rom, argIdx);
}
return null;
}
/*
* These commands query a range of markers.
* The reply message looks as follows:
*
* [ "/get.reply", <getID>, [ <(int) pos>, <(String) markName> ] * N ]]
*
* NOTE: as soon as SuperCollider supports 64bit OSC tags,
* the reply will send <pos> as a 64bit long!!
*
* Quick calculation : (1 << 31 - 1) / 96000 / 60 / 60 --> with 32bit signed ints, audio at 96 kHz can be represented if length doesn't exceed about 6 hours
*/
private Object[] oscGetMarkers(List<?> coll) {
final Object[] args = new Object[coll.size() << 1];
Marker m;
for (int i = 0, j = 0; i < args.length; j++) {
m = (Marker) coll.get(j);
args[i++] = m.pos;
args[i++] = m.name;
}
return args;
}
public Object oscQuery_count()
{
return trail.getNumStakes();
}
public Object oscQuery_spanStart()
{
return trail.getSpan().start;
}
public Object oscQuery_spanStop()
{
return trail.getSpan().stop;
}
public Object oscQuery_trackSelected()
{
return doc.selectedTracks.contains(doc.markerTrack) ? 1 : 0;
}
/**
* <pre>
* [ <address>, "add", [ <(long) pos>, <(String) name> ] * N ]
* </pre>
*/
public void oscCmd_add( RoutedOSCMessage rom )
{
final int num = rom.msg.getArgCount() >> 1;
if( num == 0 ) return;
// final long timelineLen = doc.timeline.getLength(); // XXX sync
final BasicCompoundEdit ce;
final List<Stake> coll = new ArrayList<Stake>(num);
int argIdx = 1;
long pos;
String name;
try {
for( int i = 0; i < num; i++ ) {
pos = Math.max( 0, ((Number) rom.msg.getArg( argIdx )).longValue() );
argIdx++;
name = rom.msg.getArg( argIdx ).toString();
argIdx++;
coll.add( new MarkerStake( pos, name ));
}
ce = new BasicCompoundEdit(getResourceString(num > 1 ? "editAddMarkers" : "editAddMarker"));
trail.editBegin(ce);
try {
trail.editAddAll(this, coll, ce);
} finally {
trail.editEnd(ce);
}
ce.perform();
ce.end();
doc.getUndoManager().addEdit( ce );
}
catch( ClassCastException e1 ) {
OSCRoot.failedArgType( rom, argIdx );
}
catch( IOException e1 ) { // should never happen
System.err.println( e1.getLocalizedMessage() );
}
}
/**
* either of:
* <pre>
* [ <address>, "remove", "span", <(long) startPos>, <(long) stopPos> ]
* [ <address>, "remove", "range", <(int) startIdx>, <(int) stopIdx> ]
* [ <address>, "remove", "at", [ <(int) idx> ] * N ]
* </pre>
*/
public void oscCmd_remove( RoutedOSCMessage rom )
{
final BasicCompoundEdit ce;
final List<Stake> coll;
final long n1, n2;
int i1, i2;
int argIdx = 1;
try {
if( rom.msg.getArg( argIdx ).equals( "span" )) {
argIdx++;
n1 = ((Number) rom.msg.getArg( argIdx )).longValue();
argIdx++;
n2 = ((Number) rom.msg.getArg( argIdx )).longValue();
coll = trail.getRange( new Span( n1, n2 ), true );
} else if( rom.msg.getArg( argIdx ).equals( "range" )) {
argIdx++;
i1 = Math.max( 0, ((Number) rom.msg.getArg( argIdx )).intValue() );
argIdx++;
i2 = Math.min(trail.getNumStakes(), ((Number) rom.msg.getArg(argIdx)).intValue());
coll = new ArrayList<Stake>(Math.max(1, i2 - i1));
while (i1 < i2) {
coll.add(trail.get(i1++));
}
} else if( rom.msg.getArg( argIdx ).equals( "at" )) {
argIdx++;
coll = new ArrayList<Stake>( rom.msg.getArgCount() - argIdx );
for( ; argIdx < rom.msg.getArgCount(); argIdx++ ) {
i1 = ((Number) rom.msg.getArg( argIdx )).intValue();
if( (i1 >= 0) && (i1 < trail.getNumStakes()) ) {
coll.add( trail.get( i1 ));
}
}
} else {
OSCRoot.failedArgValue( rom, argIdx );
return;
}
if( coll.isEmpty() ) return;
ce = new BasicCompoundEdit(getResourceString(coll.size() > 1 ? "editDeleteMarkers" : "editDeleteMarker"));
trail.editBegin(ce);
try {
trail.editRemoveAll(this, coll, ce);
} finally {
trail.editEnd(ce);
}
ce.perform();
ce.end();
doc.getUndoManager().addEdit( ce );
}
catch( IndexOutOfBoundsException e1 ) {
OSCRoot.failedArgCount( rom );
}
catch( ClassCastException e1 ) {
OSCRoot.failedArgType( rom, argIdx );
}
catch( IOException e1 ) { // should never happen
System.err.println( e1.getLocalizedMessage() );
}
}
}