Package com.groovemanager.spi.asio

Source Code of com.groovemanager.spi.asio.ASIODataLine

/*
* Created on 22.04.2004
*
*/
package com.groovemanager.spi.asio;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Control;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.Control.Type;


/**
* This class implements SourceDataLine as well as TargetDataLine
* because input and output processing is similar in ASIO. All lines
* provided by the ASIOMixer will be instances of this class.
* @author Manu Robledo
*/
public class ASIODataLine implements SourceDataLine, TargetDataLine {
  /**
   * A RingBuffer for each channel wrapped around the two buffer halfs
   */
  DoubleHalfRingBuffer[] ringBuffers;
  /**
   * The real asioBuffers. They are not used by the line itself,
   * but by the ASIOMixer. It copies the content from/to the
   * internalBuffers when bufferSwitchtimeInfo() is called
   */
  ByteBuffer[][] asioBuffers;
  /**
   * List of the registered LineListeners
   */
  private ArrayList<LineListener> listeners = new ArrayList<LineListener>();
  /**
   * The ASIOMixer instance which created this line
   */
  private ASIOMixer mixer;
  /**
   * THE ASIOLineInfo object corresponding to this line
   */
  private ASIOLineInfo info;
  /**
   * The buffersize in bytes specified to open(AudioFormat, int)
   */
  private int desiredBufferSize = AudioSystem.NOT_SPECIFIED;
  /**
   * Indicates if this line is open
   */
  private boolean open;
  /**
   * The format of this line when its running
   */
  private AudioFormat format;
  /**
   * Number of channels associated with this line right now
   */
  int channels = 1;
  /**
   * Sequence number used when opening this line. This number should
   * be used for ASIOMixer.ASIOExit(long seq)
   */
  private long openSeq = 0;
  /**
   * frameSize of the current format
   */
  private int frameSize;
  /**
   * sample size of the current format
   */
  private int sampleSize;
  /**
   * indicates whether this line has been started or not
   */
  private boolean started = false;
  /**
   * indicates whether flus() has been called lately on this line
   */
  private boolean flushed = false;
  /**
   * the Controls for this line
   */
  private Control[] controls;
  /**
   * an empty array for flushing
   */
  private byte[] emptyArray;
 
  static{
    System.loadLibrary(ASIOMixerProvider.getLibName());
  }
 
  /**
   * Get the ASIOMixer to which this Line belongs
   * @return
   *     The ASIOMixer to which this Line belongs
   */
  ASIOMixer getMixer(){
    return mixer;
  }
 
  ASIODataLine(ASIOMixer mixer, ASIOLineInfo info){
    this.mixer = mixer;
    this.info = info;
  }

  public void drain() {
    if(isInput() || !isOpen()) return;
    while(available() < getBufferSize())
      try {
        Thread.sleep(0, 1);
      } catch (InterruptedException e) {
      }
  }

  public void flush() {
    if(!isOpen()) return;
   
    //int length = internalBuffers[0][0].capacity();
    int length = ringBuffers[0].size();
    if(emptyArray == null || emptyArray.length < length) emptyArray = new byte[length];
    synchronized(mixer){
      for(int i = 0; i < channels; i++){
        ringBuffers[0].rewind();
        ringBuffers[0].write(emptyArray, 0, length);
        ringBuffers[0].rewind();
        /*
        internalBuffers[i][0].rewind();
        internalBuffers[i][0].put(emptyArray, 0, length);
        internalBuffers[i][0].rewind();
        internalBuffers[i][1].rewind();
        internalBuffers[i][1].put(emptyArray, 0, length);
        internalBuffers[i][1].rewind();
        */
      }
      flushed = true;
    }
  }
 
  public void start() {
    if(isActive()) return;
    mixer.startLine(this);
  }
 
  public void stop() {
    if(!isActive()) return;
    mixer.stopLine(this);
  }

  public boolean isRunning() {
    return isActive() && !flushed;
  }

  public boolean isActive() {
    return open && mixer.getStatus() == ASIOMixer.RUNNING && started;
  }

  public AudioFormat getFormat() {
    return format;
  }

  public int getBufferSize() {
    //return internalBuffers[0][0].capacity() * channels;
    return ringBuffers[0].size() * channels;
  }

  public int available() {
    //return internalBuffers[0][mixer.bufferIndex].remaining() * channels;
    if(!isOpen()) return 0;
    if(isInput()) return ringBuffers[0].readAvailable() * channels;
    else return ringBuffers[0].writeAvailable() * channels;
  }

  public int getFramePosition() {
    return (int)mixer.getSamplePosition();
  }

  public long getMicrosecondPosition() {
    return mixer.getMicrosecondPosition();
  }

  /**
   * ASIO doesn't support this. <code>AudioSystem.NOT_SPECIFIED</code> will be returned.
   * @see javax.sound.sampled.DataLine#getLevel()
   */
  public float getLevel() {
    return AudioSystem.NOT_SPECIFIED;
  }

  public ASIOLineInfo getLineInfo() {
    return info;
  }

  /**
   * If no arguments are specified to <code>open()</code>, the preferred buffer
   * size will be used and the Format will be Mono with the preferred Sample Rate
   * @see javax.sound.sampled.Line#open()
   */
  public void open() throws LineUnavailableException {
    if(open) return;
    float rate;
    if(format == null)
      rate = (float)mixer.getSampleRate();
    else
      rate = format.getSampleRate();
    format = new ASIOAudioFormat(rate, info.getChannelInfo().type(), channels);
    if(desiredBufferSize > 0) mixer.openLine(this, desiredBufferSize / format.getFrameSize());
    else mixer.openLine(this);
  }

  public void close() {
    if(!open) return;
    mixer.closeLine(this, openSeq);
  }

  public boolean isOpen() {
    return open;
  }

  /**
   * Will return a Clock Source Control for Selection of the Clock Source
   * @see javax.sound.sampled.Line#getControls()
   */
  public Control[] getControls() {
    if(controls != null) return controls;
    long seq = 0;
    try {
      seq = mixer.ASIOInit();
      ASIOClockSource[] sources = ASIOStaticFunctions.ASIOGetClockSources(15);
      mixer.ASIOExit(seq);
      if(sources.length > 0){
        controls = new Control[]{new ASIOClockSourceControl(this, sources)};
        return controls;
      }
    } catch (ASIOError e) {
      e.printStackTrace();
      mixer.ASIOExit(seq);
    }
    return new Control[]{new ASIOClockSourceControl(this)};
  }

  /**
   * Only a Clock Source Control for Selection of the Clock Source is supported
   * @see javax.sound.sampled.Line#isControlSupported(javax.sound.sampled.Control.Type)
   */
  public boolean isControlSupported(Type control) {
    return control == ASIOClockSourceControl.Type.CLOCK_SOURCE;
  }

  /**
   * Will return a Clock Source Control for Selection of the Clock Source
   * @see javax.sound.sampled.Line#getControl(javax.sound.sampled.Control.Type)
   */
  public Control getControl(Type control) {
    if(control == ASIOClockSourceControl.Type.CLOCK_SOURCE){
      Control[] c = getControls();
      return c[0];
    }
    return null;
  }

  public void addLineListener(LineListener listener) {
    listeners.add(listener);
  }

  public void removeLineListener(LineListener listener) {
    listeners.remove(listener);
  }

  public void open(AudioFormat format, int buffersize) throws LineUnavailableException {
    if(open) return;
    if(ASIOMixerProvider.isFullCheck())
      if(!info.isFormatSupported(format))
        throw new LineUnavailableException("Audio Format not supported.");
   
    this.format = format;
    channels = format.getChannels();
    desiredBufferSize = buffersize;
    open();
  }

  public void open(AudioFormat format) throws LineUnavailableException {
    if(open) return;
    if(ASIOMixerProvider.isFullCheck())
      if(!info.isFormatSupported(format))
        throw new LineUnavailableException("Audio Format not supported.");
    this.format = format;
    channels = format.getChannels();
    open();
  }
 
  public int read(byte[] b, int off, int len){
    // when reading starts, the last flush call is forgotten
    flushed = false;
    synchronized(mixer){
      // Don't read more than available...
      len = Math.min(len, available());
      len -= len % frameSize;
     
      // Mono reading is more efficient
      if(channels == 1){
        //internalBuffers[0][mixer.bufferIndex].get(b, off, len);
        ringBuffers[0].read(b, off, len);
      }
      else{
        // Multiplex
        int frames = len / frameSize;
        for(int i = 0; i < frames; i++){
          for(int j = 0; j < channels; j++){
            //internalBuffers[j][mixer.bufferIndex].get(b, off + i * frameSize + j * sampleSize, sampleSize);
            ringBuffers[0].read(b, off + i * frameSize + j * sampleSize, sampleSize);
          }
        }
      }
    }
    return len;
  }
 
  public int write(byte[] b, int off, int len){
    if(len % frameSize > 0) throw new IllegalArgumentException(len + " does not represent an integral number of sample frames.");
    flushed = false;
    int written = 0;
    // As long as we didnエt write all the data...
    while(len > 0){
      // Wait until there is some space inside the buffer
      while(available() == 0 && isRunning()){
        try { Thread.sleep(0, 1); catch (InterruptedException e) {}
      }
      // alyways look, if this method should return because of
      // a call to close(), stop() or flush()
      if(!isRunning()) return written;
       
      synchronized(mixer){
        int toWrite = Math.min(len, available());
        // Mono is more efficient
        if(channels == 1){
          //internalBuffers[0][mixer.bufferIndex].put(b, off, toWrite);
          ringBuffers[0].write(b, off, toWrite);
          written += toWrite;
        }
        else{
          // Demultiplex
          int frames = toWrite / frameSize;
          for(int i = 0; i < frames; i++){
            for(int j = 0; j < channels; j++){
              //internalBuffers[j][mixer.bufferIndex].put(b, off + written, sampleSize);
              ringBuffers[j].write(b, off + written, sampleSize);
              written += sampleSize;
            }
          }
        }
        len -= toWrite;
      }
    }
    return written;
  }
 
  /**
   * Get the channel index of the first channel that belongs to this line
   * @return The zero-based index of the lowest channel that belongs to
   * this line
   */
  int getChannel(){
    return info.getChannelInfo().channel();
  }

  /**
   * Tells if this Line is used as In- or Output
   * @return
   *     true if this Line is used as Input
   *     false otherwise
   */
  public boolean isInput() {
    return info.getChannelInfo().isInput();
  }

  /**
   * called from the Mixer to indicate that this line has been opened
   * @param seq The sequence number that should be used for closing
   * @param buffers The asioBuffers
   */
  void opened(long seq, ByteBuffer[][] buffers) {
    openSeq = seq;
    frameSize = format.getFrameSize();
    sampleSize = frameSize / channels;
   
    // Store the asioBuffers with this line and create internal ones too
    asioBuffers = buffers;
    ByteBuffer[][] internalBuffers = new ByteBuffer[channels][2];
    ringBuffers = new DoubleHalfRingBuffer[channels];
    int bsize = buffers[0][0].capacity();
    for(int i = 0; i < channels; i++){
      internalBuffers[i][0] = ByteBuffer.allocateDirect(bsize);
      internalBuffers[i][1] = ByteBuffer.allocateDirect(bsize);
      ringBuffers[i] = new DoubleHalfRingBuffer(new DoubleHalfBuffer(internalBuffers[i][0], internalBuffers[i][1]), format.getFrameSize() / format.getChannels());
      ringBuffers[i].open();
    }
   
    open = true;
    notifyListeners(new LineEvent(this, LineEvent.Type.OPEN, getFramePosition()));
  }

  /**
   * called from the Mixer to indicate that this line has been started
   */
  void started() {
    started = true;
    flushed = false;
    notifyListeners(new LineEvent(this, LineEvent.Type.START, getFramePosition()));
  }
 
  /**
   * Notify the line listeners
   * @param e The LineEvent
   */
  private void notifyListeners(LineEvent e){
    for (Iterator<LineListener> iter = listeners.iterator(); iter.hasNext();) {
      LineListener listener = iter.next();
      listener.update(e);
    }
  }
 
  /**
   * called from the Mixer to indicate that this line has been stopped
   */
  void stopped(){
    if(!started) return;
    started = false;
    notifyListeners(new LineEvent(this, LineEvent.Type.STOP, getFramePosition()));
  }
 
  /**
   * called from the Mixer to indicate that this line has been closed
   */
  void closed(){
    if(!open) return;
    stopped();
    desiredBufferSize = AudioSystem.NOT_SPECIFIED;
    channels = 1;
    //internalBuffers = null;
    ringBuffers = null;
    open = false;
    notifyListeners(new LineEvent(this, LineEvent.Type.CLOSE, getFramePosition()));
  }
 
  public void finalize(){
    started = false;
    open = false;
  }

  public long getLongFramePosition() {
    return mixer.getSamplePosition();
  }
}
TOP

Related Classes of com.groovemanager.spi.asio.ASIODataLine

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.