Package de.sciss.meloncillo.realtime

Source Code of de.sciss.meloncillo.realtime.RealtimeProducer$Source

/*
*  RealtimeProducer.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   created
*    28-Jul-04   fixed even/odd to work exactly as supercollider realtime phasor
*    01-Sep-04  commented
*/

package de.sciss.meloncillo.realtime;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import de.sciss.io.Span;
import de.sciss.meloncillo.receiver.Receiver;
import de.sciss.meloncillo.session.Session;
import de.sciss.meloncillo.transmitter.TrajectoryGenerator;
import de.sciss.meloncillo.transmitter.Transmitter;

/**
*  The RealtimeProducer is the "factory" of
*  the realtime engine. Requests for stream data
*  production can be made synchronously and
*  asynchronously. The data is kept in a public
*  instance variable <code>source</code>. Data
*  is produced for alternating buffer halfs
*  (first half or second half) such that only
*  one buffer field is needed but future data
*  can be produced in advance. The requests are
*  usually made from the transport running thread.
*  The transport also requests adding and removal
*  of consumers. The RealtimeProducer creates an
*  internal representation of the union of all
*  consumer requests such that the minimum frame
*  step is determinated and only necessary data is
*  produced. Whenever a request is completed,
*  the host's (transport's) <code>notifyConsumed</code>
*  method is called.
*
@author    Hanns Holger Rutz
@version  0.75, 15-Jul-08
*
@see  Transport
@see  RealtimeProducer#RealtimeProducer( Main, Session, RealtimeHost )
*/
public class RealtimeProducer
{
  /**
   *  Request type: stream data generation
   */
  public static final int TYPE_PRODUCE    = 0;
  /**
   *  Request type: adding one or several consumers
   */
  public static final int TYPE_ADDCONFIG    = 1;
  /**
   *  Request type: removing one or several consumers
   */
  public static final int TYPE_REMOVECONFIG  = 2;
  /**
   *  Request type: add a trajectory replacement
   */
  public static final int TYPE_ADDREPLACEMENT    = 3;
  /**
   *  Request type: remove a trajectory replacement
   */
  public static final int TYPE_REMOVEREPLACEMENT  = 4;

  /**
   *  The source contains the stream data
   *  buffer and the curr
   */
  public RealtimeProducer.Source source;
 
  private final List  collInfos      = new ArrayList()// synced because always in event thread
  private final List  collReplacements  = new ArrayList()// synced because always in event thread
 
//  private final Session    doc;
//  private final RealtimeHost  host;
 
//  private final float[][] offhandTempBuf  = new float[2][1];
//  private final float[]  offhandTempBuf2 = new float[1];
 
  /**
   *  Creates a new RealtimeProducer. This is only
   *  done once when initializing the transport.
   *
   *  @param  root  Application root
   *  @param  doc    Session document
   *  @param  host  usually the Transport
   */
  public RealtimeProducer( /* Session doc, RealtimeHost host */ )
  {
//    this.doc  = doc;
//    this.host   = host;
  }
 
  /**
   *  Requests are fulfilled here
   */
  public void process( Request r )
  {
//    Request r      = (Request) o;
    long  now      = System.currentTimeMillis();
    long  patience  = (now - r.deadline) >> 1;
   
    switch( r.type ) {
    case TYPE_PRODUCE:
      if( patience <= 0 ) {
        System.err.println( "drop!" );
        return;
      }
      produce( r.blockSpan, r.even, patience );
      break;

    case TYPE_ADDCONFIG:
      collInfos.addAll( r.requests );
      reConfig();
      break;

    case TYPE_REMOVECONFIG:
      boolean result = collInfos.removeAll( r.requests );
      if( result ) {
        reConfig();
      } else {
        System.err.println( "obsolete rt consumer requests found:" );
        for( int i = 0; i < r.requests.size(); i++ ) {
          System.err.println( ((RealtimeConsumerRequest) r.requests.get( i )).consumer.getClass().getName() );
        }
      }
      break;

    case TYPE_ADDREPLACEMENT:
      collReplacements.add( (TrajectoryReplacement) r.obj );
      reConfigReplacements( source );
      break;
   
    case TYPE_REMOVEREPLACEMENT:
      collReplacements.remove( (TrajectoryReplacement) r.obj );
      reConfigReplacements( source );
      break;
     
    default:
      assert false : r.type;
      break;
    }
   
//    if( host != null ) host.notifyConsumed( r );
  }
 
  /**
   *  Tells the realtime producer that the realtime context
   *  changed. Note that calling this method will result
   *  in a removal of all current request, hence they have to
   *  be readded after this method returns.
   *
   *  @param  c  the new context from which the source
   *        is re-constructed
   *
   *  @see  #addConsumerRequestsNow( java.util.List )
   *
   *  @synchronization  call only in the event thread!
   */
  public void changeContext( RealtimeContext c )
  {
    java.util.List  coll;
    Source      s  = new Source();
    int        i;
 
    collInfos.clear();
 
    s.numTrns    = c.getTransmitters().size();
    s.numRcv    = c.getReceivers().size();
    coll      = c.getTransmitters();
    s.transmitters  = new Transmitter[ coll.size() ];
    for( i = 0; i < coll.size(); i++ ) {
      s.transmitters[ i ] = (Transmitter) coll.get( i );
    }
    coll      = c.getReceivers();
    s.receivers    = new Receiver[ coll.size() ];
    for( i = 0; i < coll.size(); i++ ) {
      s.receivers[ i ] = (Receiver) coll.get( i );
    }
    s.bufSize    = c.getSourceBlockSize();
    s.bufSizeH    = s.bufSize >> 1;
    s.senseRequest  = new boolean[ s.numTrns ][ s.numRcv ];
    s.senseBlockBuf = new float[ s.numTrns ][ s.numRcv ][ s.bufSize ];
    s.senseOffhand  = new float[ s.numTrns ][ s.numRcv ];
    s.trajRequest   = new boolean[ s.numTrns ];
    s.trajRplc    = new int[ s.numTrns ];
    s.trajBlockBuf  = new float[ s.numTrns ][ 2 ][ s.bufSize ];
    s.trajOffhand   = new float[ s.numTrns ][ 2 ];
    s.trnsRequest   = new boolean[ s.numTrns ];
    s.minSenseStep  = s.bufSizeH;   // max allowed

    reConfigReplacements( s );
    this.source    = s;
  }
 
  private void reConfig()
  {
    int            trnsIdx, rcvIdx, cfIdx;
    RealtimeConsumerRequest  cf;
    boolean          makeSense, makeTraj;

    // reset requests
    for( trnsIdx = 0; trnsIdx < source.numTrns; trnsIdx++ ) {
      source.trnsRequest[ trnsIdx ] = false;
      source.trajRequest[ trnsIdx ] = false;
      for( rcvIdx = 0; rcvIdx < source.numRcv; rcvIdx++ ) {
        source.senseRequest[ trnsIdx ][ rcvIdx ] = false;
      }
    }
    source.doors    = 0;
    source.minSenseStep = source.bufSizeH;   // max allowed
   
    // sum individual requests
    for( cfIdx = 0; cfIdx < collInfos.size(); cfIdx++ ) {
      cf    = (RealtimeConsumerRequest) collInfos.get( cfIdx );

      // cf.frameStep must be a power of 2 and greater or equal 1
      assert (cf.frameStep > 0) && (cf.frameStep <= source.bufSizeH) &&
           ((cf.frameStep | (cf.frameStep - 1)) == (cf.frameStep + cf.frameStep - 1)) : cf.frameStep;
          
      for( trnsIdx = 0, makeSense = false, makeTraj = false; trnsIdx < source.numTrns; trnsIdx++ ) {
        if( cf.trajRequest[ trnsIdx ]) {
          source.trnsRequest[ trnsIdx ]   = true;
          source.trajRequest[ trnsIdx ]   = true;
          makeTraj            = true;
        }
        for( rcvIdx = 0; rcvIdx < source.numRcv; rcvIdx++ ) {
          if( cf.senseRequest[ trnsIdx ][ rcvIdx ]) {
            source.trnsRequest[ trnsIdx ]        = true;
            source.senseRequest[ trnsIdx ][ rcvIdx = true;
            makeSense                  = true;
          }
        }
      }
      if( makeSense ) {
        source.minSenseStep = Math.min( source.minSenseStep, cf.frameStep );
        source.doors |= Session.DOOR_TRNSMTE | Session.DOOR_RCV;
      }
      if( makeTraj ) {
        source.doors |= Session.DOOR_TRNSMTE;
      }
    }
   
    // shall we produce now?
  }

  // sync: call in event thread
  private void reConfigReplacements( Source s )
  {
    if( s == null ) return;
   
    TrajectoryReplacement tr;
    Transmitter trns;

    // reset requests
trnsLp:  for( int trnsIdx = 0; trnsIdx < s.numTrns; trnsIdx++ ) {
      trns = s.transmitters[ trnsIdx ];
      for( int i = 0; i < collReplacements.size(); i++ ) {
        tr = (TrajectoryReplacement) collReplacements.get( i );
        if( tr.collTransmitters.contains( trns )) {
          s.trajRplc[ trnsIdx ] = i;
          continue trnsLp;
        }
      }
      s.trajRplc[ trnsIdx ] = -1;
    }
  }
 
  private void produce( Span blockSpan, boolean even, long patience )
  {

//System.out.println( "produce: blockSpan = " + blockSpan + "; even = " + even + "; patience = " + patience );

    int trnsIdx, rcvIdx, offStart, offStop;
   
    if( even ) {
      offStart      = 0;
      offStop        = (int) blockSpan.getLength();
      source.firstHalf  = blockSpan;
    } else {
      offStart      = source.bufSizeH;
      offStop        = offStart + (int) blockSpan.getLength();
      source.secondHalf  = blockSpan;
    }
   
//    if( !doc.bird.attemptShared( source.doors, patience )) {  // XXX MTE can't be shared
//      System.err.println( "busy!" );
//      return;
//    }
    try {
      for( trnsIdx = 0; trnsIdx < source.numTrns; trnsIdx++ ) {
        if( !source.trnsRequest[ trnsIdx ]) continue;
       
        // --- read transmitter trajectory data ---
        if( source.trajRplc[ trnsIdx ] == -1 ) {
//          source.transmitters[ trnsIdx ].getAudioTrail().read(
//                                                  blockSpan, source.trajBlockBuf[ trnsIdx ], offStart );
          source.transmitters[ trnsIdx ].getAudioTrail().readFrames(
            source.trajBlockBuf[ trnsIdx ], offStart, blockSpan );
        } else {
          TrajectoryReplacement  tr;
          long          truncStart, truncStop, delta;
          Span          subSpan;
         
          tr      = (TrajectoryReplacement) collReplacements.get( source.trajRplc[ trnsIdx ]);
          truncStart  = Math.max( blockSpan.getStart(), tr.span.getStart() );
          truncStop  = Math.min( blockSpan.getStop(), tr.span.getStop() );
         
          if( truncStart >= truncStop ) {  // no intersection
//            source.transmitters[ trnsIdx ].getAudioTrail().read(
//                                                  blockSpan, source.trajBlockBuf[ trnsIdx ], offStart );
            source.transmitters[ trnsIdx ].getAudioTrail().readFrames(
              source.trajBlockBuf[ trnsIdx ], offStart, blockSpan );
          } else {            // ok, we have to split it up
            delta = truncStart - blockSpan.getStart();
            if( delta > 0 ) {  // beginning not replaced
              subSpan = new Span( blockSpan.getStart(), truncStart );
//              source.transmitters[ trnsIdx ].getAudioTrail().read(
//                                                  subSpan, source.trajBlockBuf[ trnsIdx ], offStart );
              source.transmitters[ trnsIdx ].getAudioTrail().readFrames(
                source.trajBlockBuf[ trnsIdx ], offStart, subSpan );
            }
            subSpan = new Span( truncStart, truncStop );
            tr.tg.read( subSpan, source.trajBlockBuf[ trnsIdx ], (int) (offStart + delta) );
            delta = blockSpan.getStop() - truncStop;
            if( delta > 0 ) {
              subSpan = new Span( truncStop, blockSpan.getStop() );
//              source.transmitters[ trnsIdx ].getAudioTrail().read( subSpan,
//                                                   source.trajBlockBuf[ trnsIdx ], (int) (offStart + truncStop - blockSpan.getStart()) );
              source.transmitters[ trnsIdx ].getAudioTrail().readFrames(
                source.trajBlockBuf[ trnsIdx ], (int) (offStart + truncStop - blockSpan.getStart()),
                subSpan );
            }
          }
        }
        // --- satisfy sensibilities requests ---
        for( rcvIdx = 0; rcvIdx < source.numRcv; rcvIdx++ ) {
          if( !source.senseRequest[ trnsIdx ][ rcvIdx ]) continue;

          source.receivers[ rcvIdx ].getSensitivities(
            source.trajBlockBuf[ trnsIdx ], source.senseBlockBuf[ trnsIdx ][ rcvIdx ],
            offStart, offStop, source.minSenseStep );
        } // for( rcvIdx = 0; rcvIdx < numRcv; rcvIdx++ )

      } // for( trnsIdx = 0; trnsIdx < numTrns; trnsIdx++ )
    }
    catch( IOException e1 ) {
      System.err.println( e1 );
    }
//    finally {
//      doc.bird.releaseShared( source.doors );
//    }
  }
 
  /**
   *  Asks the realtime producer to produce some time
   *  span of stream data. The actual production is
   *  deferred to the event thread.
   *
   *  @param  blockSpan  the time span to produce
   *  @param  even    <code>false</code> means the first
   *            buffer half will be overwritten,
   *            <code>true</code> means the second
   *            buffer half will be overwritten.
   *  @param  deadline  the request must be fulfilled before
   *            the current system time reaches this
   *            deadline which is an absolute time value.
   *            if the deadline is missed, a warning text
   *            will be printed to the console.
   */
  public void requestProduction( Span blockSpan, boolean even, long deadline )
  {
    Request r   = new Request( TYPE_PRODUCE );
    r.blockSpan = blockSpan;
    r.even    = even;
   
//    lim.queue( r );
    process( r );
  }

  /**
   *  Asks the realtime producer to produce some time
   *  span of stream data immediately. When this method
   *  returns, the data has been produced.
   *
   *  @param  blockSpan  the time span to produce
   *  @param  even    <code>false</code> means the first
   *            buffer half will be overwritten,
   *            <code>true</code> means the second
   *            buffer half will be overwritten.
   *
   *  @synchronization  call only in the event thread!
   */
  public void produceNow( Span blockSpan, boolean even )
  {
    produce( blockSpan, even, 250 );
  }

  /**
   *  Asks the realtime producer to produce one frame of
   *  stream data for a certain time position. The data
   *  will be stored in the <code>trajOffhand</code> and
   *  <code>senseOffhand</code> fields of the source.
   *  When the method eturns, the data has been produced.
   *
   *  @param  currentPos  the time position frame to produce
   *
   *  @synchronization  call only in the event thread!
   */
  public void produceOffhand( long currentPos )
  {
/* EEE
    int trnsIdx, rcvIdx;
    Span miniSpan = new Span( currentPos, currentPos + 1 );
   
    if( !doc.bird.attemptShared( source.doors, 250 )) {  // XXX MTE can't be shared
      System.err.println( "busy!" );
      return;
    }
    try {
      for( trnsIdx = 0; trnsIdx < source.numTrns; trnsIdx++ ) {
        if( !source.trnsRequest[ trnsIdx ]) continue;

        // --- read transmitter trajectory data ---
        if( source.trajRplc[ trnsIdx ] == -1 ) {
          source.transmitters[ trnsIdx ].getAudioTrail().read( miniSpan, offhandTempBuf, 0 );
        } else {
          TrajectoryReplacement tr = (TrajectoryReplacement) collReplacements.get( source.trajRplc[ trnsIdx ]);

          if( tr.span.getStart() > currentPos || tr.span.getStop() < currentPos ) {
            source.transmitters[ trnsIdx ].getAudioTrail().read( miniSpan, offhandTempBuf, 0 );
          } else {
            tr.tg.read( miniSpan, offhandTempBuf, 0 );
          }
        }
       
        // --- satisfy trajectory requests ---
        if( source.trajRequest[ trnsIdx ]) {
          source.trajOffhand[ trnsIdx ][0]  = offhandTempBuf[0][0];
          source.trajOffhand[ trnsIdx ][1]  = offhandTempBuf[1][0];
        }

        // --- satisfy sensibilities requests ---
        for( rcvIdx = 0; rcvIdx < source.numRcv; rcvIdx++ ) {
          if( !source.senseRequest[ trnsIdx ][ rcvIdx ]) continue;

          source.receivers[ rcvIdx ].getSensitivities(
            offhandTempBuf, offhandTempBuf2, 0, 1, 1 );
          source.senseOffhand[ trnsIdx ][ rcvIdx ] = offhandTempBuf2[ 0 ];
        } // for( rcvIdx = 0; rcvIdx < numRcv; rcvIdx++ )
      } // for( trnsIdx = 0; trnsIdx < numTrns; trnsIdx++ )
    }
    catch( IOException e1 ) {
      System.err.println( e1 );
    }
    finally {
      doc.bird.releaseShared( source.doors );
    }
*/
  }

  /**
   *  Asks the realtime producer to integrate a new consumer's
   *  request into the future production. The actual request is
   *  deferred to the event thread.
   *
   *  @param  request    the request to integrate in terms
   *            of data production and frame step.
   */
  public void requestAddConsumerRequest( RealtimeConsumerRequest request )
  {
    Request r   = new Request( TYPE_ADDCONFIG );
    r.requests  = new ArrayList( 1 );
    r.requests.add( request );
//    lim.queue( r );
    process( r );
  }

  /**
   *  Asks the realtime producer to integrate new consumer
   *  requests into the future production. The actual request is
   *  deferred to the event thread.
   *
   *  @param  requests  a list of <code>RealtimeConsumerRequest</code>s
   *            to integrate in terms of data production and frame step.
   */
  public void requestAddConsumerRequests( java.util.List requests )
  {
    Request r   = new Request( TYPE_ADDCONFIG );
    r.requests  = requests;
//    lim.queue( r );
    process( r );
  }

  public void requestAddTrajectoryReplacement( TrajectoryReplacement tr )
  {
    Request r   = new Request( TYPE_ADDREPLACEMENT );
    r.obj    = tr;
//    lim.queue( r );
    process( r );
  }

  public void requestRemoveTrajectoryReplacement( TrajectoryReplacement tr )
  {
    Request r   = new Request( TYPE_REMOVEREPLACEMENT );
    r.obj    = tr;
//    lim.queue( r );
    process( r );
  }
 
  /**
   *  Asks the realtime producer to integrate new consumer
   *  requests into the future production. The method performs
   *  immediately and returns when the requests have been integrated.
   *
   *  @param  requests  a list of <code>RealtimeConsumerRequest</code>s
   *            to integrate in terms of data production and frame step.
   *
   *  @synchronization  call only in the event thread!
   */
  public void addConsumerRequestsNow( java.util.List requests )
  {
    collInfos.addAll( requests );
    reConfig();
  }

  /**
   *  Asks the realtime producer to disintegrate a consumer's
   *  request from the future production. The actual request is
   *  deferred to the event thread.
   *
   *  @param  request    the request to disintegrate in terms
   *            of data production and frame step.
   */
  public void requestRemoveConsumerRequest( RealtimeConsumerRequest request )
  {
    Request r   = new Request( TYPE_REMOVECONFIG );
    r.requests  = new ArrayList( 1 );
    r.requests.add( request );
//    lim.queue( r );
    process( r );
  }

  /**
   *  Struct class describing
   *  a request made to the producer
   */
  protected static class Request
  {
    /**
     *  The deadline for the requests.
     *  Requests arriving too late will
     *  produce a warning message
     */
    private long        deadline;
    /**
     *  What was requested, such as TYPE_REMOVECONFIG
     */
    protected int        type;
    /**
     *  For production requests: the span to produce
     */
    private Span        blockSpan;
    /**
     *  For production requests: whether to
     *  overwrite the first buffer half (false)
     *  or the second buffer half (true)
     */
    protected boolean      even;
    /**
     *  For adding/removing consumer requests:
     *  a list of requests to be integrated
     *  or disintegrated ;
     */
    protected java.util.List  requests;

    /*
     *  for adding/removing trajectory replacements
     *  a t.r. instance
     */
    private Object obj;
    
    private Request( int type )
    {
      this.type   = type;
    }
  }

  /**
   *  Struct class featuring
   *  the stream data and
   *  (privately) the requests
   */
  public class Source
  {
    /**
     *  Time span describing the
     *  current first half of the stream buffers
     */
    public Span        firstHalf    = new Span();
    /**
     *  Time span describing the
     *  current second half of the stream buffers
     */
    public Span        secondHalf    = new Span();
    /**
     *  Number of transmitters in the realtime context
     */
    public int        numTrns;
    /**
     *  The transmitters in the realtime context
     */
    public Transmitter[]  transmitters;
    /**
     *  Number of receivers in the realtime context
     */
    public int        numRcv;
    /**
     *  The receivers in the realtime context
     */
    public Receiver[]    receivers;
    /**
     *  Sense data covering the two time
     *  spans as given by <code>firstHalf</code>
     *  and <code>secondHalf</code>. Consumers
     *  may only read indices which are a multiple
     *  of their personal frameStep and only
     *  array fields which they have requested!
     *  Array indices are [numTrns][numRcv][bufSize]
     */
    public float[][][]    senseBlockBuf;
    /**
     *  Sense data covering the current offline
     *  timeline position. Consumers
     *  may only read
     *  array fields which they have requested!
     *  Array indices are [numTrns][numRcv]
     */
    public float[][]    senseOffhand;
    /**
     *  Trajectory data covering the two time
     *  spans as given by <code>firstHalf</code>
     *  and <code>secondHalf</code>. Consumers
     *  may only read indices which are a multiple
     *  of their personal frameStep and only
     *  array fields which they have requested!
     *  Array indices are [numTrns][ch][bufSize],
     *  whery channel 0 is x and channel 1 is y coordinates.
     */
    public float[][][]    trajBlockBuf;
    /**
     *  Trajectory data covering  the current offline
     *  timeline position. Consumers
     *  may only read
     *  array fields which they have requested!
     *  Array indices are [numTrns][ch],
     *  whery channel 0 is x and channel 1 is y coordinates.
     */
    public float[][]    trajOffhand;
    /**
     *  Length of stream buffers
     */
    public int        bufSize;
    /**
     *  Length of each half of the stream buffers.
     *  I.e. bufSize/2
     */
    public int        bufSizeH;

    private boolean[][]    senseRequest;
    private boolean[]    trajRequest;
    private int[]      trajRplc;

    private boolean[]    trnsRequest;
    private int        minSenseStep;
    private int        doors      = 0;
  } // class Source

  public static class TrajectoryReplacement
  {
    private final java.util.List    collTransmitters;
    private final Span          span;
    private final TrajectoryGenerator  tg;
 
    public TrajectoryReplacement( TrajectoryGenerator tg, Span span, java.util.List collTransmitters )
    {
      this.collTransmitters  = collTransmitters;
      this.span        = span;
      this.tg          = tg;
    }
  }
}
TOP

Related Classes of de.sciss.meloncillo.realtime.RealtimeProducer$Source

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.