SoundLineSource.java

/*
 * Created on 2010/01/04
 * Copyright (C) 2010 Koga Laboratory. All rights reserved.
 *
 */
package org.mklab.tool.control.system.source;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioFormat.Encoding;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;

import org.mklab.nfc.matrix.ComplexNumericalMatrix;
import org.mklab.nfc.matrix.DoubleComplexMatrix;
import org.mklab.nfc.matrix.DoubleMatrix;
import org.mklab.nfc.matrix.RealNumericalMatrix;
import org.mklab.nfc.ode.SolverStopException;
import org.mklab.nfc.scalar.ComplexNumericalScalar;
import org.mklab.nfc.scalar.DoubleComplexNumber;
import org.mklab.nfc.scalar.DoubleNumber;
import org.mklab.nfc.scalar.RealNumericalScalar;


/**
 * ライン入力のPCM波ソースです。
 * 
 * @author Yuhi Ishikura
 * @version $Revision$, 2010/01/04
 * @param <RS> type of real scalar
 * @param <RM> type of real matrix
 * @param <CS> type of complex scalar
 * @param <CM> type of complex matrix
 */
public final class SoundLineSource<RS extends RealNumericalScalar<RS,RM,CS,CM>, RM extends RealNumericalMatrix<RS,RM,CS,CM>, CS extends ComplexNumericalScalar<RS,RM,CS,CM>, CM extends ComplexNumericalMatrix<RS,RM,CS,CM>> extends FixedRateAsynchronousSource<RS,RM,CS,CM> {

  /** サンプリングレートです。 */
  private static final float DEFAULT_SAMPLE_RATE = 44100.0F / 10;
  /** デフォルトのラインのオーディオフォーマットです。 */
  private static final AudioFormat DEFAULT_AUDIO_FORMAT;

  /** クローズ命令の呼出しを示します。 */
  private boolean closed;
  /** ラインのオーディオフォーマットです。 */
  private AudioFormat audioFormat;

  static {
    final Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
    final float sampleRate = DEFAULT_SAMPLE_RATE;
    final float frameRate = sampleRate;
    final int sampleSizeInBits = 16;
    final int channels = 1;
    final int frameSize = 2;
    final boolean bigEndian = false;

    DEFAULT_AUDIO_FORMAT = new AudioFormat(encoding, sampleRate, sampleSizeInBits, channels, frameSize, frameRate, bigEndian);
  }

  /**
   * 入力を行う時間から、 {@link SoundLineSource}オブジェクトを構築します。 <p> 例えば時間に1000を指定すると、0ms ~ 999msまでの値を記録できるソースを構築します。
   * 
   * @param audioFormat データ保存に使用するフォーマット
   * @param timeMillis 保存する時間(ms)
   * @param sunit unit of scalar
   * @return インスタンス
   */
  public SoundLineSource<RS,RM,CS,CM> createFromTime(AudioFormat audioFormat, long timeMillis, RS sunit) {
    final int size = (int)Math.ceil((double)timeMillis / 1000 * audioFormat.getSampleRate());
    return new SoundLineSource<>(audioFormat, size, sunit);
  }

  /**
   * 入力を行う時間から、 {@link SoundLineSource}オブジェクトを構築します。 <p> 例えば時間に1000を指定すると、0ms ~ 999msまでの値を記録できるソースを構築します。
   * 
   * @param timeMillis 保存する時間(ms)
   * @param sunit unit of scalar
   * @return インスタンス
   */
  public SoundLineSource<RS,RM,CS,CM> createFromTime(long timeMillis, RS sunit) {
    return createFromTime(DEFAULT_AUDIO_FORMAT, timeMillis, sunit);
  }

  /**
   * {@link SoundLineSource}オブジェクトを構築します。
   * 
   * @param size 最大出力保存数
   * @param sunit unit of scalar
   */
  public SoundLineSource(int size, RS sunit) {
    this(DEFAULT_AUDIO_FORMAT, size, sunit);
  }

  /**
   * {@link SoundLineSource}オブジェクトを構築します。
   * 
   * @param audioFormat 出力フォーマット
   * @param size 最大出力保存数
   * @param sunit unit of scalar
   */
  public SoundLineSource(AudioFormat audioFormat, int size, RS sunit) {
    super(size,  sunit.create(audioFormat.getSampleRate()).inverse(), sunit);
    this.audioFormat = audioFormat;
  }

  /**
   * @throws LineUnavailableException ラインが利用不可能だった場合
   * @see org.mklab.tool.control.system.source.AsynchronousSource#processImport()
   */
  @Override
  protected void processImport() throws LineUnavailableException {
    try (final TargetDataLine line = AudioSystem.getTargetDataLine(this.audioFormat)) {
      final byte[] buf = new byte[DEFAULT_AUDIO_FORMAT.getSampleSizeInBits() / 8];
      
      try {
        line.open();
        line.start();
        
        while (isClosed() == false && isOutputDataFilled() == false) {
          final int size = line.read(buf, 0, buf.length);
          if (size < buf.length) {
            break;
          }
          
          RS sample = this.sunit.create((buf[0] & 0xFF) | ((buf[1] & 0xFF) << 8));
          publish(sample);
        }
      } finally {
        line.stop();
        line.close();
      }
    }
  }

  /**
   * クローズ要求がなされたかどうか調べます。
   * 
   * @return closed クローズ要求がなされたらtrue,そうでなければfalse
   */
  private boolean isClosed() {
    return this.closed;
  }

  /**
   * @see org.mklab.tool.control.system.source.Importer#close()
   */
  @Override
  public void close() {
    this.closed = true;
  }

  /**
   * メインメソッドです。
   * 
   * @param args コマンドライン引数
   */
  public static void main(final String[] args) {
    final int recTimeSec = 1;
    SoundLineSource<DoubleNumber,DoubleMatrix,DoubleComplexNumber,DoubleComplexMatrix> s = new SoundLineSource<>(0,new DoubleNumber(1));
    final SoundLineSource<DoubleNumber,DoubleMatrix,DoubleComplexNumber,DoubleComplexMatrix>  source =  s.createFromTime(recTimeSec * 1000, new DoubleNumber(1));
    source.open();

    new Thread() {

      /**
       * @see java.lang.Thread#run()
       */
      @Override
      public void run() {
        for (double i = 0; i < recTimeSec; i += 0.1) {
          try {
            DoubleMatrix m = source.outputEquation(new DoubleNumber(i));
            m.print();
          } catch (SolverStopException e) {
            e.printStackTrace();
          }
        }
      }
    }.start();
  }
}