Package de.sciss.eisenkraut.realtime

Source Code of de.sciss.eisenkraut.realtime.Transport

/*
*  Transport.java
*  Eisenkraut
*
*  Copyright (c) 2004-2014 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
*
*
*  Changelog:
*    25-Jan-05  created from de.sciss.meloncillo.realtime.Transport
*    15-Jul-05  timeline insertion follows playback
*    18-Jul-05  fixes a wrong return statement in the run() method
*    22-Jul-05  doesn't extend Thread any more (allows re-running if thread dies)
*    02-Aug-05  conforms to new document handler
*    08-Sep-05  modified stopAndWait as to return directly if transport wasn't running
*          ; uses floating point rates
*    21-Jan-06  added OSC support
*    25-Feb-06  moved to double precision
*    20-Sep-06  radically stripped down, removed realtime consumer stuff, everything in event thread now
*/

package de.sciss.eisenkraut.realtime;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import de.sciss.app.AbstractApplication;
import de.sciss.io.Span;
import de.sciss.util.Disposable;

import de.sciss.eisenkraut.net.OSCRouter;
import de.sciss.eisenkraut.net.OSCRouterWrapper;
import de.sciss.eisenkraut.net.OSCRoot;
import de.sciss.eisenkraut.net.RoutedOSCMessage;
import de.sciss.eisenkraut.session.Session;
import de.sciss.eisenkraut.timeline.TimelineEvent;
import de.sciss.eisenkraut.timeline.TimelineListener;
import de.sciss.eisenkraut.util.PrefsUtil;

/**
*  The realtime "motor" or "clock". The transport
*  deals with realtime playback of the timeline.
*  It provides means for registering and unregistering
*  realtime consumers and communicates with a
*  RealtimeProducer which is responsible for the
*  actual data production. Transort clocking is
*  performed within an extra thread from within
*  the consumer's methods are called and registered
*  transport listeners are informed about actions.
*
@author    Hanns Holger Rutz
@version  0.70, 26-Aug-08
*
*  @todo  the methods for adding and removing consumers should
*      be moved to the realtime host interface?
*
*  @todo  changing sample rate while playing doesn't have an effect
*
*  @synchronization  all methods must be executed in event thread!
*/
public class Transport
implements TimelineListener, OSCRouter, Disposable
{
  protected final Session        doc;

    private  boolean            looping      = false;
  private boolean            loopInPlay    = false;
  private long            loopStart, loopStop;
  private double            rate;
  private double            frameFactor;
  private long            lastUpdate;
 
  // high level listeners
  private final List          collListeners  = new ArrayList();

  // realtime control
  private long            startFrame;
  private long            stopFrame;
  private long            currentFrame;
  private long            startTime;
 
  private double            rateScale  = 1.0;
 
  private boolean            running    = false;
       
  // --- actions ---
 
  private static final String      OSC_TRANSPORT = "transport";
  private final OSCRouter        osc;

  // sync : call in event thread!
  /**
   *  Creates a new transport. The thread will
   *  be started and set to pause to await
   *  transport commands.
   *
   *  @param  doc    Session document
   */
    public Transport( Session doc )
    {
        this.doc    = doc;
       
    doc.timeline.addTimelineListener( this );

    osc        = new OSCRouterWrapper( doc, this );
    rate      = doc.timeline.getRate();
    frameFactor    = rateScale * rate / 1000;
    }
 
  public void dispose()
  {
    collListeners.clear();
    running = false;
    doc.timeline.removeTimelineListener( this );
  }
 
//  public AbstractAction getPlayAction()
//  {
//    return actionPlay;
//  }

//  public AbstractAction getStopAction()
//  {
//    return actionStop;
//  }

  public Session getDocument()
  {
    return doc;
  }
 
  /**
   *  Registers a new transport listener
   *
   *  @param  listener  the listener to register for information
   *            about transport actions such as play or stop
   */
  public void addTransportListener( TransportListener listener )
  {
    if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
 
    collListeners.add( listener );
    if( running ) {
      listener.transportPlay( this, updateCurrentFrame(), rateScale );
    }
  }

  /**
   *  Unregisters a transport listener
   *
   *  @param  listener  the listener to remove from the event dispatching
   */
  public void removeTransportListener( TransportListener listener )
  {
    if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();

    collListeners.remove( listener );
  }

  private void dispatchStop( long pos )
  {
    for( Iterator iter = collListeners.iterator(); iter.hasNext(); ) {
      ((TransportListener) iter.next()).transportStop( this, pos );
    }
    if( AbstractApplication.getApplication().getUserPrefs().getBoolean(
      PrefsUtil.KEY_INSERTIONFOLLOWSPLAY, false )) {

      doc.timeline.editPosition( this, pos );
    } else {
      doc.timeline.setPosition( this, doc.timeline.getPosition() );
    }
  }

  private void dispatchPosition( long pos )
  {
    for( Iterator iter = collListeners.iterator(); iter.hasNext(); ) {
      ((TransportListener) iter.next()).transportPosition( this, pos, rateScale );
    }
  }

  private void dispatchPlay( long pos )
  {
    for( Iterator iter = collListeners.iterator(); iter.hasNext(); ) {
      ((TransportListener) iter.next()).transportPlay( this, pos, rateScale );
    }
  }

  private void dispatchReadjust( long pos )
  {
    for( Iterator iter = collListeners.iterator(); iter.hasNext(); ) {
      ((TransportListener) iter.next()).transportReadjust( this, pos, rateScale );
    }
  }

  private void dispatchQuit()
  {
    for( Iterator iter = collListeners.iterator(); iter.hasNext(); ) {
      try {
        ((TransportListener) iter.next()).transportQuit( this );
      }
      catch( Exception e1 ) {
        System.err.println( "[@transport]" + e1.getLocalizedMessage() );
      }
    }
  }
 
//  /**
//   *  The transport core is
//   *  executed within the thread's run method
//   */
//    public void run()
//    {
//    // all initial values are just here to please the compiler
//    // who doesn't know commandLp is exited only after at least
//    // one CMD_PLAY (see assertion in CMD_CONFIG_RESUME)
//        long      startTime = 0, sysTime;
//        long      frameCount = 0, oldFrameCount = 0;
//        double      currentRate, targetRate = 1.0;
//    int        i;
////    UndoableEdit  edit;
//    RealtimeConsumerRequest  r;
//
//    do {
//      synchronized( this ) {
//commandLp:    do {
//          switch( rt_command ) {
//          case CMD_CONFIG_PAUSE:
//            notifyAll();
//            break;
//           
//          case CMD_CONFIG_RESUME:
//            assert startTime > 0 : startTime;
//            notifyAll();
//            break commandLp;
//           
//          case CMD_STOP:
//            dispatchStop( currentFrame );
//            // translate into a valid time offset
//            if( !doc.bird.attemptExclusive( Session.DOOR_TIME, 400 )) break;
//            try {
//              currentFrame  = Math.max( 0, Math.min( doc.timeline.getLength(), currentFrame ));
//              if( AbstractApplication.getApplication().getUserPrefs().getBoolean(
//                PrefsUtil.KEY_INSERTIONFOLLOWSPLAY, false )) {
//
////                doc.getUndoManager().addEdit( TimelineVisualEdit.position( this, doc, currentFrame ));
//                doc.timeline.editPosition( this, currentFrame );
////                doc.timeline.setPosition( this, currentFrame );
//              } else {
//                // this is for notifying objects for visual update
//                doc.timeline.setPosition( this, doc.timeline.getPosition() );
//              }
////              edit  = new EditSetTimelinePosition( this, doc, currentFrame );
////              doc.getUndoManager().addEdit( edit );
//            }
//            finally {
//              doc.bird.releaseExclusive( Session.DOOR_TIME );
//            }
//            notifyAll();
//            break;
//           
//          case CMD_PLAY:
//          case CMD_POSITION:
//            if( rt_command == CMD_PLAY ) {
//              dispatchPlay( startFrame );
//            } else {
//              dispatchPosition( startFrame );
//            }
//            // THRU
//            targetRate    = rt_context.getSourceRate() * rateScale;
//            // e.g. bufSizeH == 512 --> 0x1FF . Maske fuer frameCount
//            // wir geben dem producer einen halben halben buffer zeit (in millisec)
//            // d.h. bei 1000 Hz und halber buffer size von 512 sind das 256 millisec.
//            startTime    = System.currentTimeMillis() - 1;   // division by zero vermeiden
//            frameCount    = 0;
//            currentFrame      = startFrame;
//            notifyAll();
//            break commandLp;
//           
//          case CMD_QUIT:
//            dispatchQuit();
//            notifyAll();
//            return;
//           
//          default:
//            assert rt_command == CMD_IGNORE : rt_command;
//            break;
//          }
//          // sleep until next rt_command arrives
//          try {
//            wait();
//          }
//          catch( InterruptedException e1 ) {}
//        } while( true );
//      } // synchronized( this )
//
//rt_loop:  while( threadRunning ) {
//        frameCount += rt_notifyTickStep;
//        currentFrame     += rt_notifyTickStep;
//        sysTime    = System.currentTimeMillis();
//        currentRate = (double) (1000 * frameCount) / (sysTime - startTime);
//        while( currentRate > targetRate ) { // wir sind der zeit voraus
//          Thread.yield();
//          sysTime    = System.currentTimeMillis();
//          currentRate = (double) (1000 * frameCount) / (sysTime - startTime);
//        }
//
//        // handle stop + loop
//        if( currentFrame >= stopFrame ) {
//          if( isLooping() ) {
//            startFrame   = loopStart;
//            if( startFrame >= stopFrame ) {
//              goStop();
//              break rt_loop;
//            }
//            dispatchPosition( startFrame );
//            currentFrame    = startFrame;
//            startTime  = System.currentTimeMillis() - 1;
//            frameCount  = 0;
////            rt_producer.requestProduction(
////              new Span( startFrame, startFrame + rt_producer.source.bufSizeH ),
////              true, sysTime + deadline );
////            rt_producer.requestProduction(
////              new Span( startFrame + rt_producer.source.bufSizeH,
////                    startFrame + rt_producer.source.bufSize ),
////              false, sysTime + deadline );
//
//          } else {
//            goStop();
//            break rt_loop;
//          }
//        }
//       
//        for( i = 0; i < rt_numConsumers; i++ ) {
//          // XXX performativer mit bitshifted mask + AND ?
//          r = rt_requests[ i ];
//          if( r.active && r.notifyTicks && (frameCount % r.notifyTickStep == 0) ) {
//            rt_consumers[ i ].realtimeTick( rt_context, currentFrame );
////            rt_consumers[ i ].realtimeTick( rt_context, rt_producer.source, currentFrame );
//          }
//        }
//
//        try {
//          Thread.sleep( 0, 1 );
//        } catch( InterruptedException e1 ) {}
//      } // while( threadRunning )
//    } while( true );
//    }
   
  /**
   *  Requests the thread to start
   *  playing. TransportListeners
   *  are informed when the
   *  playing really starts.
   *
   *  @synchronization  To be called in the event thread.
   */
    public void play( double scale )
    {
    playSpan( new Span( doc.timeline.getPosition(), doc.timeline.getLength() ), scale )// XXX sync?
    }

    public void playSpan( Span span, double scale )
  {
    if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();

    if( running ) return;

    startFrame    = span.start;
    loopInPlay    = isLooping() && loopStop > startFrame;
    stopFrame    = loopInPlay ? loopStop : span.stop;
    this.rateScale  = scale;
    frameFactor    = scale * rate / 1000;
    currentFrame  = startFrame;
    running      = true;
    dispatchPlay( startFrame );
    startTime    = System.currentTimeMillis();
  }
 
  public double getRateScale()
  {
    return rateScale;
  }
   
  /**
   *  Sets the loop span for playback
   *
   *  @param  loopSpan  Span describing the new loop start and stop.
   *            Passing null stops looping.
   *
   *  @synchronization  If loopSpan != null, the caller must have sync on doc.timeline!
   */
  public void setLoop( Span loopSpan )
  {
    if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();

    long  testFrame;
//    boolean readjust = false;
   
    if( loopSpan != null ) {
      if( !looping || (loopStart != loopSpan.start) || (loopStop != loopSpan.stop) ) {
        loopStart   = loopSpan.start;
        loopStop  = loopSpan.stop;
        looping    = true;
        if( running ) {
          if( currentFrame < loopStop ) {
            loopInPlay  = true;
            stopFrame  = loopStop;
          }
          // check for possible jumps
          testFrame = startFrame + (long) ((lastUpdate - startTime) * frameFactor + 0.5);
          if( loopInPlay && (testFrame >= loopStop) ) {
            testFrame = ((testFrame - loopStart) % (loopStop - loopStart)) + loopStart;
          }
          // seemingless re-adjustment of startFrame
          // so currentFrame doesn't jump
          if( testFrame != currentFrame ) { 
//System.err.println( "testFrame is " + testFrame + "; should be " + currentFrame );
            startFrame -= testFrame - currentFrame;
          }
          dispatchReadjust( startFrame );
        }
      }
    } else {
      if( looping ) {
        if( running && loopInPlay ) {
          // check for possible jumps
          testFrame = startFrame + (long) ((lastUpdate - startTime) * frameFactor + 0.5);
          // seemingless re-adjustment of startFrame
          // so currentFrame doesn't jump
          if( testFrame != currentFrame ) {
// System.err.println( "testFrame is " + testFrame + "; should be " + currentFrame );
            startFrame -= testFrame - currentFrame;
          }
        }
        loopInPlay  = false;
        looping    = false;
        if( running ) {
          stopFrame  = doc.timeline.getLength();
          dispatchReadjust( startFrame );
        }
      }
    }
  }
/*
  currentFrame = startFrame + (long) ((now - startTime) * frameFactor + 0.5);
  if( loopInPlay ) {
    if( currentFrame >= loopStop ) {
      currentFrame = ((currentFrame - loopStart) % (loopStop - loopStart)) + loopStart;
    }
*/
  /**
   *  Returns whether looping
   *  is active or not
   *
   *  @return  <code>true</code> if looping is used
   */
  public boolean isLooping()
  {
    return looping;
  }

// we can uncomment this any time when the method is really needed
//  /**
//   *  Returns whether current playback
//   *  involves the loop region.
//   *  (i.e. loop is active and playback was
//   *  started with position <= loop end; it needn't be
//   *  that the playback position is >= loop start though!)
//   *
//   *  @return  <code>true</code> if loop is relevant in current playback
//   */
//  public boolean isInLoop()
//  {
//    return loopInPlay;
//  }

  /**
   *  'Folds' a time span with regard to current loop settings.
   *  That is, if a transport listener is calculating linear increasing
   *  time spans from transport play offset, this method checks against
   *  active and relevant (loopInPlay) loop settings and clips back
   *  the span or portions of the span to the loop region if necessary.
   *  <p>
   *  This does not check against the document length so span stops
   *  beyond doc.timeline.getLength() are possible and allowed.
   *
   *  @param  unfolded  the linear extrapolated time span from transport play
   *  @param  loopMin    a minimum length of the loop such as to prevent cpu overload or
   *            osc message overflow (imagine the user would make a 1 sample long loop).
   *            leave to zero if no minimum required.
   *  @return        an array of folded spans (array length is >= 1)
   *
   *  @todo  this method is not thread safe, hence should be called in the event
   *      thread. this means, the trigger responder in SuperColliderPlayer
   *      must be deferred!!!
   */
  public Span[] foldSpans( Span unfolded, int loopMin )
  {
    // the quick one
    if( !loopInPlay || (unfolded.stop <= loopStop)) return new Span[] { unfolded };
   
    final long loopLen    = Math.max( loopMin, loopStop - loopStart );
    final long loopMinStop  = loopStart + loopLen;
    final long foldStart  = (unfolded.start < loopMinStop) ? unfolded.start : ((unfolded.start - loopStart) % loopLen) + loopStart;
    final long attemptStop  = foldStart + unfolded.getLength();
   
    // no splitting up required
    if( attemptStop <= loopMinStop ) return new Span[] { new Span( foldStart, attemptStop )};
   
    // pseudo-code:
    // numSpans        = (attemptStop - loopMinStop + loopLen-1) / loopLen + 1
    final int numSpans    = (int) ((attemptStop - loopStart - 1) / loopLen) + 1;
    final long foldStop    = ((attemptStop - loopStart) % loopLen) + loopStart;
    final Span[] folded    = new Span[ numSpans ];
    folded[ 0 ]        = new Span( foldStart, loopMinStop );
    for( int i = 1, j = numSpans - 1; i <= j; i++ ) {
      folded[ i ]      = new Span( loopStart, i < j ? loopMinStop : foldStop );
    }
    return folded;
  }
 
  /**
   *  Requests the thread to stop
   *  playing. TransportListeners
   *  are informed when the
   *  playing really stops.
   */
    public void stop()
    {
    if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();

    if( running ) {
      running = false;
      updateCurrentFrame();
      dispatchStop( currentFrame );
    }
  }

  /**
   *  Sends quit rt_command to the transport
   *  returns only after the transport thread
   *  stopped!
   */
    public void quit()
    {
    running = false;
    dispatchQuit();
    }

  public long getCurrentFrame()
  {
    return updateCurrentFrame();
  }

  private long updateCurrentFrame()
  {
    final long now = System.currentTimeMillis();
    if( (now == lastUpdate) || !running ) return currentFrame;
   
    currentFrame  = startFrame + (long) ((now - startTime) * frameFactor + 0.5);
    lastUpdate    = now;
    if( loopInPlay ) {
      if( currentFrame >= loopStop ) {
        currentFrame = ((currentFrame - loopStart) % (loopStop - loopStart)) + loopStart;
      }
    } else if( currentFrame > stopFrame ) {
//      final boolean dispatch = (currentFrame - stopFrame) >= 128;
      currentFrame  = stopFrame;
      running      = false;
//      if( dispatch ) {
        dispatchStop( currentFrame );
//      }
    }
    return currentFrame;
  }

// ---------------- TimelineListener interface ----------------

  public void timelinePositioned( TimelineEvent e )
  {
    if( e.getSource() == this ) return;
   
    if( running ) {
      startFrame    = doc.timeline.getPosition();   // XXX sync?
      loopInPlay    = isLooping() && loopStop > startFrame;
      stopFrame    = loopInPlay ? loopStop : doc.timeline.getLength();
//      rateScale    = rate;
      currentFrame  = startFrame;
      dispatchPosition( startFrame );
      startTime    = System.currentTimeMillis();
      lastUpdate    = startTime;
    } else {
      currentFrame  = doc.timeline.getPosition();
    }
  }

  public void timelineChanged( TimelineEvent e ) {
    rate      = doc.timeline.getRate();
    frameFactor    = rateScale * rate / 1000;
  }
 
  public void timelineSelected( TimelineEvent e ) { /* ignored */ }
    public void timelineScrolled( TimelineEvent e ) { /* ignored */ }

// --------------- RealtimeHost interface ---------------

  /**
   *  Returns whether the
   *  thread is currently playing
   *
   *  @return  <code>true</code> if the transport is currently playing
   */
  public boolean isRunning()
  {
    return running;
  }

  public void  showMessage( int type, String text )
  {
    System.err.println( text );
//    ((ProgressComponent) root.getComponent( Main.COMP_MAIN )).showMessage( type, text );
  }

  // ------------- OSCRouter interface -------------
 
  public String oscGetPathComponent()
  {
    return OSC_TRANSPORT;
  }
 
  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_position()
  {
    return new Long( getCurrentFrame() );
  }
 
  public Object oscQuery_running()
  {
    return new Integer( isRunning() ? 1 : 0 );
  }
 
  public void oscCmd_play( RoutedOSCMessage rom )
  {
    try {
      final float r = rom.msg.getArgCount() == 1 ? 1.0f :
        Math.max( 0.25f, Math.min( 4f, ((Number) rom.msg.getArg( 1 )).floatValue() ));
//      actionPlay.perform( r );
      play( r );
    }
    catch( ClassCastException e1 ) {
      OSCRoot.failedArgType( rom, 1 );
    }
  }

  public void oscCmd_stop( RoutedOSCMessage rom )
  {
    stop();
  }
}
TOP

Related Classes of de.sciss.eisenkraut.realtime.Transport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.