Package de.sciss.meloncillo.realtime

Source Code of de.sciss.meloncillo.realtime.RealtimeTransport

/**
*  RealtimeTransport.java
*  Meloncillo
*
*  Copyright (c) 2004-2008 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
*
*
*  Changelog:
*    22-Jul-04   moved to realtime package. Renamed to
*          Transport.
*    23-Jul-04   implements RealtimeHost; widely rewritten.
*    25-Jul-04   catches exceptions in the transport listeners
*    28-Jul-04   tracks prefs changes
*    01-Aug-04   bugfix in removeRealtimeConsumer
*    16-Aug-04   debug dump action
*    01-Sep-04  additional comments. consumer's active state now fixed
*    02-Feb-05  bugfix : before calling offhandProduction, ensure that
*          a realtime context exists!
*    15-Jul-08  created from old cillo transport
*/

package de.sciss.meloncillo.realtime;

import java.util.ArrayList;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;

import de.sciss.app.AbstractApplication;
import de.sciss.app.DynamicPrefChangeManager;
import de.sciss.gui.ProgressComponent;
import de.sciss.io.Span;
import de.sciss.meloncillo.Main;
import de.sciss.meloncillo.receiver.Receiver;
import de.sciss.meloncillo.session.Session;
import de.sciss.meloncillo.session.SessionCollection;
import de.sciss.meloncillo.session.SessionObject;
import de.sciss.meloncillo.timeline.TimelineEvent;
import de.sciss.meloncillo.timeline.TimelineListener;
import de.sciss.meloncillo.transmitter.Transmitter;
import de.sciss.meloncillo.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.75, 15-Jul-08
*
*  @todo  stop shouldn't use new EditSetTimelinePosition
*      because it makes Redos impossible.
*  @todo  the methods for adding and removing consumers should
*      be moved to the realtime host interface?
*/
public class RealtimeTransport
extends Thread
implements RealtimeHost, TimelineListener, PreferenceChangeListener, TransportListener
{
  private static final int  CMD_IGNORE  = 0;
  private static final int  CMD_STOP  = 1;
  private static final int  CMD_PLAY  = 2;
  private static final int  CMD_QUIT  = -1;

  private static final int  CMD_CONFIG_PAUSE  = 4// pause but don't tell the listeners
  private static final int  CMD_CONFIG_RESUME  = 5// go on where we stopped but don't tell the listeners
  private static final int  CMD_POSITION    = 6// go on at the context's timespan start

  // low level threading
    private boolean threadRunning   = false;
//    private  boolean looping      = false;
  private int    rt_command    = CMD_IGNORE;
 
    private final Session   doc;
//  private long      loopStart, loopStop;
 
  // high level listeners
//  private final List  collTransportListeners  = new ArrayList();

  // realtime control
  private RealtimeContext        rt_context;
  private RealtimeProducer      rt_producer;
  private RealtimeConsumer[]      rt_consumers    = new RealtimeConsumer[ 4 ]// array will grow
  private RealtimeConsumerRequest[]  rt_requests      = new RealtimeConsumerRequest[ 4 ]; // ...automatically
  private int              rt_numConsumers    = 0;
  private int              rt_notifyTickStep;
  private long            rt_startFrame;
  private long            rt_stopFrame;
  private long            rt_pos;
  private int              rt_senseBufSize;
 
//  private final RealtimeContext    fakeContext = new RealtimeContext( this, Collections.EMPTY_LIST, Collections.EMPTY_LIST,
//                                     new Span(), 1000 );
 
  private final Preferences      plugInPrefs;
 
  private final Object        sync  = new Object();
 
  private long timelineLen;

  // sync : call in event thread!
  /**
   *  Creates a new transport. The thread will
   *  be started and set to pause to await
   *  transport commands.
   *
   *  @param  root  Application root
   *  @param  doc    Session document
   */
    public RealtimeTransport( Object NOTUSEDANYMORE, Session doc )
    {
        super( "Transport" );
       
        this.doc    = doc;
   
    rt_producer    = new RealtimeProducer();
    rt_context    = null;
        
    timelineLen    = doc.timeline.getLength();
   
    // listeners
    doc.getTransmitters().addListener( new SessionCollection.Listener() {
      public void sessionCollectionChanged( SessionCollection.Event e )
      {
        synchronized( sync ) {
          createContext();
        }
      }
     
      public void sessionObjectChanged( SessionCollection.Event e )
      {
        if( e.getModificationType() == Transmitter.OWNER_TRAJ ) {
          synchronized( sync ) {
            if( !isRunning() ) {
              if( rt_context == null ) {
                createContext()// will invoke offhandProduction!
// EEE
//              } else {
//                offhandProduction();
              }
            }
          }
        }
      }

      public void sessionObjectMapChanged( SessionCollection.Event e ) {}
    });

    doc.getReceivers().addListener( new SessionCollection.Listener() {
      public void sessionCollectionChanged( SessionCollection.Event e )
      {
        synchronized( sync ) {
          createContext();
        }
      }
     
      public void sessionObjectChanged( SessionCollection.Event e )
      {
        switch( e.getModificationType() ) {
        case Receiver.OWNER_SENSE:
        case SessionObject.OWNER_VISUAL:
          synchronized( sync ) {
            if( !isRunning() ) {
              if( rt_context == null ) {
                createContext()// will invoke offhandProduction!
// EEE
//              } else {
//                offhandProduction();
              }
            }
          }
          break;
       
        default:
          break;
        }
      }

      public void sessionObjectMapChanged( SessionCollection.Event e ) {}
    });
   
    doc.timeline.addTimelineListener( this );
    doc.getTransport().addTransportListener( this );
   
    plugInPrefs = AbstractApplication.getApplication().getUserPrefs().node( PrefsUtil.NODE_PLUGINS );
    // fires KEY_SENSEBUFSIZE and thus causes calcSenseBufSize() and createContext() to be invoked
    new DynamicPrefChangeManager( plugInPrefs, new String[] { PrefsUtil.KEY_RTSENSEBUFSIZE },
                      this ).startListening();
 
        this.setDaemon( true );
        this.setPriority( getPriority() + 1 );
    this.start();
    }

// EEE
//  /**
//   *  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 )
//  {
//    synchronized( sync ) {
//      if( !collTransportListeners.contains( listener )) collTransportListeners.add( listener );
//    }
//  }
//
//  /**
//   *  Unregisters a transport listener
//   *
//   *  @param  listener  the listener to remove from the event dispatching
//   */
//  public void removeTransportListener( TransportListener listener )
//  {
//    synchronized( sync ) {
//      collTransportListeners.remove( listener );
//    }
//  }
//
//  // sync: to be called inside synchronized( sync ) !
//  private void dispatchStop( long pos )
//  {
//    for( int i = 0; i < collTransportListeners.size(); i++ ) {
//      try {
//        ((TransportListener) collTransportListeners.get( i )).transportStop( pos );
//      }
//      catch( Exception e1 ) {
//        System.err.println( "[@"+getName()+"]" + e1.getLocalizedMessage() );
//      }
//    }
//  }
//
//  // sync: to be called inside synchronized( sync ) !
//  private void dispatchPosition( long pos )
//  {
//    for( int i = 0; i < collTransportListeners.size(); i++ ) {
//      try {
//        ((TransportListener) collTransportListeners.get( i )).transportPosition( pos );
//      }
//      catch( Exception e1 ) {
//        System.err.println( "[@"+getName()+"]" + e1.getLocalizedMessage() );
//      }
//    }
//  }
//
//  // sync: to be called inside synchronized( sync ) !
//  private void dispatchPlay( long pos )
//  {
//    for( int i = 0; i < collTransportListeners.size(); i++ ) {
//      try {
//        ((TransportListener) collTransportListeners.get( i )).transportPlay( pos );
//      }
//      catch( Exception e1 ) {
//        System.err.println( "[@"+getName()+"]" + e1.getLocalizedMessage() );
//      }
//    }
//  }
//
//  // sync: to be called inside synchronized( sync ) !
//  private void dispatchQuit()
//  {
//    for( int i = 0; i < collTransportListeners.size(); i++ ) {
//      try {
//        ((TransportListener) collTransportListeners.get( i )).transportQuit();
//      }
//      catch( Exception e1 ) {
//        System.err.println( "[@"+getName()+"]" + e1.getLocalizedMessage() );
//      }
//    }
//  }
 
  public void addTrajectoryReplacement( RealtimeProducer.TrajectoryReplacement tr )
  {
    rt_producer.requestAddTrajectoryReplacement( tr );
  }

  public void removeTrajectoryReplacement( RealtimeProducer.TrajectoryReplacement tr )
  {
    rt_producer.requestRemoveTrajectoryReplacement( tr );
  }
 
  /**
   *  Registers a new realtime consumer.
   *  If transport is running, it will be interrupted briefly
   *  and the realtime producer is reconfigured on the fly.
   *
   *  @param  consumer  the consumer to add to the realtime process.
   *            it's <code>createRequest</code> will be
   *            called to query the profile.
   *
   *  @synchronization  to be called from event thread
   *
   *  @see  RealtimeConsumer#createRequest( RealtimeContext )
   *  @see  RealtimeProducer#requestAddConsumerRequest( RealtimeConsumerRequest )
   */
  public void addRealtimeConsumer( RealtimeConsumer consumer )
  {
    int            i;
    RealtimeConsumerRequest[]  oldRequests;
    RealtimeConsumer[]    oldConsumers;
    RealtimeConsumerRequest  request;
    boolean          wasPlaying;

    synchronized( sync ) {
      for( i = 0; i < rt_numConsumers; i++ ) {
        if( rt_consumers[ i ] == consumer ) return;
      }

      // pause
      wasPlaying      = isRunning();
      if( wasPlaying ) {
        rt_command    = CMD_CONFIG_PAUSE;
        threadRunning   = false;
        sync.notifyAll();
        if( isAlive() ) {
          try {
            sync.wait();
          }
          catch( InterruptedException e1 ) {}
        }
      }
     
      // add
      if( rt_numConsumers == rt_consumers.length ) {
        oldConsumers  = rt_consumers;
        rt_consumers  = new RealtimeConsumer[ rt_numConsumers + 5 ];
        System.arraycopy( oldConsumers, 0, rt_consumers, 0, rt_numConsumers );
        oldRequests    = rt_requests;
        rt_requests    = new RealtimeConsumerRequest[ rt_numConsumers + 5 ];
        System.arraycopy( oldRequests, 0, rt_requests, 0, rt_numConsumers );
      }
      rt_consumers[ rt_numConsumers ] = consumer;

      if( rt_context != null ) {  // add new request on the fly
        request  = consumer.createRequest( rt_context );
        rt_requests[ rt_numConsumers ] = request;
        if( request.notifyTicks ) {
          rt_notifyTickStep = Math.min( rt_notifyTickStep, request.notifyTickStep );
        }
        rt_producer.requestAddConsumerRequest( request );
      } else {
        rt_requests[ rt_numConsumers ] = null;
      }
     
      rt_numConsumers++;

      // resume
      if( wasPlaying ) {
        rt_command    = CMD_CONFIG_RESUME;
        threadRunning   = true;
        if( isAlive() ) {
          sync.notifyAll();
        } else {
//          goPlay();
          goPlay( doc.getTransport().getCurrentFrame() );
        }
      }
    } // synchronized( sync )
  }

  /**
   *  Unregisters a realtime consumer.
   *  If transport is running, it will be interrupted briefly
   *  and the realtime producer is reconfigured on the fly.
   *
   *  @param  consumer  the consumer to remove from the realtime process.
   *
   *  @synchronization  to be called from event thread
   *
   *  @see  RealtimeProducer#requestRemoveConsumerRequest( RealtimeConsumerRequest )
   */
  public void removeRealtimeConsumer( RealtimeConsumer consumer )
  {
    int            i;
//    RealtimeConsumerRequest[]  oldRequests;
    RealtimeConsumerRequest  request;
    boolean          wasPlaying;

    synchronized( sync ) {
      for( i = 0; i < rt_numConsumers; i++ ) {
        if( rt_consumers[ i ] == consumer ) break;
      }
      if( i == rt_numConsumers ) return;
     
      // pause
      wasPlaying      = isRunning();
      if( wasPlaying ) {
        rt_command    = CMD_CONFIG_PAUSE;
        threadRunning   = false;
        sync.notifyAll();
        if( isAlive() ) {
          try {
            sync.wait();
          }
          catch( InterruptedException e1 ) {}
        }
      }
     
      // remove
      request  = rt_requests[ i ];
      System.arraycopy( rt_consumers, i + 1, rt_consumers, i, rt_numConsumers - i - 1 );
      System.arraycopy( rt_requests,  i + 1, rt_requests,  i, rt_numConsumers - i - 1 );

      rt_numConsumers--;
      rt_consumers[ rt_numConsumers ] = null;
      rt_requests[ rt_numConsumers = null;

      if( rt_context != null ) {
        // eventuell wieder hoeher gehen
        if( request.notifyTicks && request.notifyTickStep <= rt_notifyTickStep ) {
          rt_notifyTickStep = rt_producer.source.bufSizeH;
          for( i = 0; i < rt_numConsumers; i++ ) {
            if( rt_requests[ i ].notifyTicks ) {
              rt_notifyTickStep = Math.min( rt_notifyTickStep, rt_requests[ i ].notifyTickStep );
            }
          }
        }
        rt_producer.requestRemoveConsumerRequest( request );
      }

      // resume
      if( wasPlaying ) {
        rt_command    = CMD_CONFIG_RESUME;
        threadRunning   = true;
        if( isAlive() ) {
          sync.notifyAll();
        } else {
//          goPlay();
          goPlay( doc.getTransport().getCurrentFrame() );
        }
      }
    } // synchronized( sync )
  }
 
  private void calcSenseBufSize()
  {
    int optimum, above, below;
   
    optimum = Math.max( 16, (int) (((double) plugInPrefs.getInt( PrefsUtil.KEY_RTSENSEBUFSIZE, 0 )
                    / 1000.0) * doc.timeline.getRate() + 0.5) );
    // muss 2er potenz sein
    for( above = 2; above < optimum; above <<= 1 ) ;
    below  = above >> 1;
 
    if( (double) above / optimum <= (double) optimum / below ) {
      rt_senseBufSize = above;
    } else {
      rt_senseBufSize = below;
    }
  }

  // will sync shared on timetrnsrcv
  // to be called inside synchronized( sync ) block!
  // to be called in event thread
  private void createContext()
  {
    final boolean      wasPlaying;
    final ArrayList      collRequests;
    RealtimeConsumerRequest  request;
//    RealtimeContext      newContext;
 
//    if( !doc.bird.attemptShared( Session.DOOR_TIMETRNSRCV, 250 )) {
//      destroyContext();
//      return;
//    }
//    try {
//      // check if new context is needed
//      newContext  = new RealtimeContext( this, doc.receivers.getAll(),
//                         doc.transmitterCollection.getAll(),
//                         new Span( 0, doc.timeline.getLength() ),
//                         doc.timeline.getRate() );
//      if( rt_context != null &&
//        newContext.getTransmitters().equals( rt_context.getTransmitter() ) &&
//        newContext.getReceivers().equals( rt_context.getReceivers() ) &&
//        newContext.getTimeSpan().equals( rt_context.getTimeSpan() ) &&
//        newContext.getSourceRate().equals( rt_context.getSourceRate() )
//      }
   
      // pause
      wasPlaying      = isRunning();
      if( wasPlaying ) {
        rt_command    = CMD_CONFIG_PAUSE;
        threadRunning   = false;
        sync.notifyAll();
        if( isAlive() ) {
          try {
            sync.wait();
          }
          catch( InterruptedException e1 ) {}
        }
      }

      // ------------------------- recontext -------------------------
      rt_context    = new RealtimeContext( this, doc.getReceivers().getAll(),
                           doc.getTransmitters().getAll(),
                           new Span( 0, doc.timeline.getLength() ),
                           doc.timeline.getRate() );
      rt_context.setSourceBlockSize( rt_senseBufSize );
      rt_producer.changeContext( rt_context );
      rt_notifyTickStep   = rt_producer.source.bufSizeH;
      collRequests    = new ArrayList( rt_numConsumers );
      for( int i = 0; i < rt_numConsumers; i++ ) {
        request      = rt_consumers[ i ].createRequest( rt_context );
        rt_requests[ i ]= request;
        if( request.notifyTicks ) {
          rt_notifyTickStep = Math.min( rt_notifyTickStep, request.notifyTickStep );
        }
        collRequests.add( request );
      }
      if( wasPlaying ) {
        rt_producer.requestAddConsumerRequests( collRequests );
      } else {
        rt_producer.addConsumerRequestsNow( collRequests );
        activateConsumers( collRequests );
// EEE
//        offhandProduction();
      }

      // resume
      if( wasPlaying ) {
        rt_command    = CMD_CONFIG_RESUME;
        threadRunning   = true;
        if( isAlive() ) {
          sync.notifyAll();
        } else {
//          goPlay();
          goPlay( doc.getTransport().getCurrentFrame() );
        }
      }
//    }
//    finally {
//      doc.bird.releaseShared( Session.DOOR_TIMETRNSRCV );
//    }
  }
 
// EEE
//  // sync : to be called within synchronized( sync )
//  // to be called in event thread
//  private void offhandProduction()
//  {
//    long  prodStart;
//    int    i;
//   
//    prodStart = doc.timeline.getPosition();
//    rt_producer.produceOffhand( prodStart );
//    for( i = 0; i < rt_numConsumers; i++ ) {
//      if( rt_requests[ i ].notifyOffhand ) {
//        rt_consumers[ i ].offhandTick( rt_context, rt_producer.source, prodStart );
//      }
//    }
//  }
 
//  private void destroyContext()
//  {
//    int i;
// 
//    synchronized( sync ) {
//      if( isRunning() ) stopAndWait();
//     
//      rt_producer.changeContext( fakeContext );
//      rt_context = null;
//      for( i = 0; i < rt_requests.length; i++ ) {
//        rt_requests[ i ] = null;
//      }
//    }
//  }

  /**
   *  Returns the current realtime context.
   *  If no such context exists, it will be
   *  created adhoc.
   *
   *  @return  the current realtime context
   */
  public RealtimeContext getContext()
  {
    synchronized( sync ) {
      if( rt_context == null ) {
        createContext();
      }
      return rt_context;
    }
  }

  /**
   *  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, productionMask = 1;
// EEE
//      long      oldFrameCount  = 0;
        int        currentRate, i;
        double      targetRate  = 1.0; // was: int
        long      deadline  = 0; // was: long
// EEE
//    UndoableEdit  edit;
    RealtimeConsumerRequest  r;

    do {
      synchronized( sync ) {
commandLp:    do {
          switch( rt_command ) {
          case CMD_CONFIG_PAUSE:
            sync.notifyAll();
            break;
           
          case CMD_CONFIG_RESUME:
            assert startTime > 0 : startTime;
            sync.notifyAll();
            break commandLp;
           
          case CMD_STOP:
// EEE
//            dispatchStop( rt_pos );
            // translate into a valid time offset
//            if( !doc.bird.attemptExclusive( Session.DOOR_TIME, 200 )) return;
//            try {
              rt_pos  = Math.max( 0, Math.min( timelineLen, rt_pos ));
// EEE
//              edit  = new EditSetTimelinePosition( this, doc, rt_pos );
//              doc.getUndoManager().addEdit( edit );
//            }
//            finally {
//              doc.bird.releaseExclusive( Session.DOOR_TIME );
//            }
            sync.notifyAll();
            break;
           
          case CMD_PLAY:
          case CMD_POSITION:
// EEE
//            if( rt_command == CMD_PLAY ) {
//              dispatchPlay( rt_startFrame );
//            } else {
//              dispatchPosition( rt_startFrame );
//            }
            // THRU
            targetRate    = rt_context.getSourceRate();
            // e.g. bufSizeH == 512 --> 0x1FF . Maske fuer frameCount
            productionMask  = rt_producer.source.bufSizeH - 1;
            // 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.
            deadline    = (long) (500 * rt_producer.source.bufSizeH / targetRate);
            startTime    = System.currentTimeMillis() - 1;   // division by zero vermeiden
            frameCount    = 0;
            rt_pos      = rt_startFrame;
            sync.notifyAll();
            break commandLp;
           
          case CMD_QUIT:
// EEE
//            dispatchQuit();
            sync.notifyAll();
            return;
           
          default:
            assert rt_command == CMD_IGNORE : rt_command;
            break;
          }
          // sleep until next rt_command arrives
          try {
            sync.wait();
          }
          catch( InterruptedException e1 ) {}
        } while( true );
      } // synchronized( sync )
//      // warten um division by zero zu vermeiden
//      while( System.currentTimeMillis() == startTime ) {
//        yield();
//      }
rt_loop:  while( threadRunning ) {
        frameCount += rt_notifyTickStep;
        rt_pos     += rt_notifyTickStep;
        sysTime    = System.currentTimeMillis();
        currentRate = (int) (1000 * frameCount / (sysTime - startTime));
        while( currentRate > targetRate ) { // wir sind der zeit voraus
          yield();
          sysTime    = System.currentTimeMillis();
          currentRate = (int) (1000 * frameCount / (sysTime - startTime));
        }

        // handle stop + loop
        if( rt_pos >= rt_stopFrame ) {
// EEE
//          if( isLooping() ) {
//            rt_startFrame   = loopStart;
//            if( rt_startFrame >= rt_stopFrame ) {
//              goStop();
//              break rt_loop;
//            }
//// EEE
////            dispatchPosition( rt_startFrame );
//            rt_pos    = rt_startFrame;
//            startTime  = System.currentTimeMillis() - 1;
//            frameCount  = 0;
//            rt_producer.requestProduction(
//              new Span( rt_startFrame, rt_startFrame + rt_producer.source.bufSizeH ),
//              true, sysTime + deadline );
//            rt_producer.requestProduction(
//              new Span( rt_startFrame + rt_producer.source.bufSizeH,
//                    rt_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, rt_producer.source, rt_pos );
          }
        }

        // this has to be called after the consumer's business
        // because the rt thread might be yielded and the
        // the producer will be faster so that the last frame
        // of each buffer half gets read in a wrong way
          if( (frameCount & productionMask) == 0 ) {
          // (frameCount & rt_producer.source.bufSizeH) != 0 entspricht
          // ((frameCount+rt_producer.source.bufSizeH) & rt_producer.source.bufSizeH) == 0
          // d.h. dem zukuenftigen evenOdd!
          rt_producer.requestProduction( new Span( rt_pos + rt_producer.source.bufSizeH,
                               rt_pos + rt_producer.source.bufSize ),
                           (frameCount & rt_producer.source.bufSizeH) != 0,
                           sysTime + deadline );
        }
       
//        yield();
        try {
          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.
   */
    private void goPlay( long position )
    {
    Span prodSpan;
 
        synchronized( sync ) {
      if( isRunning() ) return;
     
      if( rt_context == null ) {
        createContext();
      }
      // full buffer precalc
      rt_startFrame  = doc.timeline.getPosition();   // XXX sync?
// EEE
//      rt_stopFrame  = isLooping() && loopStop > rt_startFrame ? loopStop : doc.timeline.getLength();
      rt_stopFrame  = doc.timeline.getLength();
      prodSpan    = new Span( rt_startFrame, rt_startFrame + rt_producer.source.bufSizeH );
      rt_producer.produceNow( prodSpan, true );
      invokeRealtimeBlock( true );
      prodSpan    = new Span( rt_startFrame + rt_producer.source.bufSizeH,
                      rt_startFrame + rt_producer.source.bufSize );
      rt_producer.produceNow( prodSpan, false );
      invokeRealtimeBlock( false );
      rt_command    = CMD_PLAY;
      threadRunning   = true;
      if( isAlive() ) {
        sync.notifyAll();
      } else {
        System.err.println( "!! TRANSPORT DIED !!" );
//        start();
      }
        }
    }
   
//  /**
//   *  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 )
//  {
//        synchronized( sync ) {
//      if( loopSpan != null ) {
//        loopStart   = loopSpan.getStart();
//        loopStop  = loopSpan.getStop();
//        looping    = true;
//        if( isRunning() && rt_pos < loopStop ) {
//          rt_stopFrame  = loopStop;
//        }
//      } else {
//        looping    = false;
//        if( isRunning() ) {
//          rt_stopFrame  = rt_context.getTimeSpan().getLength();
//        }
//      }
//    }
//  }
//
//  /**
//   *  Returns whether looping
//   *  is active or not
//   *
//   *  @return  <code>true</code> if looping is used
//   */
//  public boolean isLooping()
//  {
//    return looping;
//  }
 
  /*
   *  Requests the thread to stop
   *  playing. TransportListeners
   *  are informed when the
   *  playing really stops.
   */
    private void goStop()
    {
        synchronized( sync ) {
      if( !isRunning() ) return;
     
      rt_command    = CMD_STOP;
            threadRunning   = false;
            sync.notifyAll();
        }
    }

  /*
   *  Requests the thread to stop
   *  playing. Waits until transport
   *  has really stopped.
   */
    private void stopAndWait()
    {
    try {
      synchronized( sync ) {
        rt_command    = CMD_STOP;
        threadRunning   = false;
        sync.notifyAll();
        if( isAlive() ) sync.wait();
      }
    }
    catch( InterruptedException e1 ) {}
    }

  /*
   *  Sends quit rt_command to the transport
   *  returns only after the transport thread
   *  stopped!
   */
    private void quit()
    {
    try {
      synchronized( sync ) {
        rt_command    = CMD_QUIT;
        threadRunning   = false;
        sync.notifyAll();
        if( isAlive() ) sync.wait();
//System.err.println( "transport stopped" );
      }
    }
    catch( InterruptedException e1 ) {}
    }

  // invoke in event thread!
  private void invokeRealtimeBlock( boolean even )
  {
    for( int i = 0; i < rt_numConsumers; i++ ) {
      if( rt_requests[ i ].notifyBlocks ) {
        rt_consumers[ i ].realtimeBlock( rt_context, rt_producer.source, even );
      }
    }
  }
 
  // call in event thread
  private void activateConsumers( java.util.List requests )
  {
    int i, j;
    RealtimeConsumerRequest  rcr;
   
    for( j = 0; j < requests.size(); j++ ) {
      rcr = (RealtimeConsumerRequest) requests.get( j );
ourLp:    for( i = 0; i < rt_numConsumers; i++ ) {
        if( rt_requests[ i ] == rcr ) {
          rt_requests[ i ].active = true;
          break ourLp;
        }
      }
    }
  }
 
// ---------------- TimelineListener interface ----------------

  public void timelineChanged( TimelineEvent e )
  {
        synchronized( sync ) {
          timelineLen = doc.timeline.getLength();
      calcSenseBufSize();
      createContext();
    }
  }

  public void timelinePositioned( TimelineEvent e )
  {
    synchronized( sync ) {
      if( isRunning() ) {
        rt_command    = CMD_CONFIG_PAUSE;
        threadRunning   = false;
        sync.notifyAll();
        if( isAlive() ) {
          try {
            sync.wait();
          }
          catch( InterruptedException e1 ) {}
        }
        // full buffer precalc
        rt_startFrame  = doc.timeline.getPosition();   // XXX sync?
// EEE
//        rt_stopFrame  = isLooping() && loopStop > rt_startFrame ? loopStop : doc.timeline.getLength();
        rt_stopFrame  = doc.timeline.getLength();
        rt_producer.produceNow( new Span( rt_startFrame,
                          rt_startFrame + rt_producer.source.bufSizeH ), true );
        rt_producer.produceNow( new Span( rt_startFrame + rt_producer.source.bufSizeH,
                          rt_startFrame + rt_producer.source.bufSize ), false );
        rt_command    = CMD_POSITION;
        threadRunning   = true;
        if( isAlive() ) {
          sync.notifyAll();
        } else {
          System.err.println( "!! TRANSPORT DIED !!" );
//          start();
        }
      } else {
        if( rt_context == null ) {
          createContext()// will invoke offhandProduction!
// EEE
//        } else {
//          offhandProduction();
        }
      }
    } // synchronized( sync )
  }

  public void timelineSelected( TimelineEvent e ) {}
    public void timelineScrolled( TimelineEvent e ) {}

// ------------------ TransportListener interface ------------------
   
  public void transportStop( Transport transport, long pos )
  {
    stopAndWait();
  }
 
  public void transportPosition( Transport transport, long pos, double rate )
  {
    if( rate != 1.0 ) {
      System.out.println( "WARNING: RealtimeTransport only plays at normal speed!" );
    }
    stopAndWait();
    goPlay( pos );
  }
 
  public void transportPlay( Transport transport, long pos, double rate )
  {
    if( rate != 1.0 ) {
      System.out.println( "WARNING: RealtimeTransport only plays at normal speed!" );
    }
    goPlay( pos );
  }
 
  public void transportQuit( Transport transport )
  {
    quit();
  }
 
  public void transportReadjust( Transport transport, long pos, double rate )
  {
    if( rate != 1.0 ) {
      System.out.println( "WARNING: RealtimeTransport only plays at normal speed!" );
    }
    // XXX WHAT TO DO ???
  }

// ------------------ PreferenceChangeListener interface ------------------

  // called when sensebufsize has changed
  /**
   *  Invoked through preference changes
   */
  public void preferenceChange( PreferenceChangeEvent pce )
  {
        synchronized( sync ) {
      calcSenseBufSize();
      createContext();
    }
  }

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

  public void notifyConsumed( RealtimeProducer.Request r )
  {
    switch( r.type ) {
    case RealtimeProducer.TYPE_PRODUCE:    // XXX sync ?
      invokeRealtimeBlock( r.even );
      break;
    case RealtimeProducer.TYPE_ADDCONFIG:
      activateConsumers( r.requests );
      break;
    case RealtimeProducer.TYPE_REMOVECONFIG:
      // nothing to do
      break;
    default:
      break;
    }
  }

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

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

  public void  showMessage( int type, String text )
  {
    ((ProgressComponent) AbstractApplication.getApplication().getComponent( Main.COMP_MAIN )).showMessage( type, text );
  }
}
TOP

Related Classes of de.sciss.meloncillo.realtime.RealtimeTransport

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.