タイムラインのオプティマイズ(2)

スポンサーリンク

前回TimeLineクラスを分離してモノラルでも利用できうる抽象クラスを作りましたのでそれを利用して楽器で「演奏された音」クラス、Playedクラスを作ります。

タイムラインのオプティマイズ(1)

演奏された音は音源から発せられているはずなのでステレオのような音の広がりは必要ありません。これは通常の楽器の話です。シンセサイザーの中にはステレオでの音の広がりを含めた音作りをしているものがありますがここではモノラルのままとして、音をミックスするときに考慮したいと思います。

Playedクラスの実装

実装するところはそれほど多くありません。継承元クラスにモノラルである1を渡してフォーマットではモノラルの音源のみそのまま登録するようにするだけです。

package mocha.sound;

public class Played extends AbstractTimeLine {

  public Played() {
    super(1);
  }

  @Override
  protected SoundReadable formatReadable(SoundReadable readable) {
    int channel = readable.getChannel();
    switch (channel) {
      case 1:
        return readable;
      default:
        return null;
    }
  }
}

楽器インタフェースの定義

個々の音色を作り出す楽器クラスを作る前にその仕様をインタフェースにまとめて置きたいと思います。

MIDIの仕様を参考にして楽器は以下の三つの情報を元に音を作り出すことにします。

変数名 意味
note 音の高さ
duration 音の長さ
velocity 音の速さ

「音の長さ」はピアノでいうとどのくらいの時間鍵盤を押しているかと言う意味になります。これは秒数で指定するようにします。lengthはこのシステムでデータ量を意味しているので別な名前にするのは混同を避けるためにも有効です。

「音の速さ」とはピアノにたとえて言うとどれぐらい早く鍵盤を叩いたかという意味です。早く叩くと音は大きくなりますが音色も変わりますので「大きさ」と「音色」の両方を操作する値になります。

インタフェースInstrumentalは以下の一つのメソッドだけ定義するようにします。これだけ実装されていればあとは楽器は自由にデザインして良いことになります。

package mocha.sound;

public interface Instrumental {
  public Played play(double note, double duration, double velocity);
}

今度は楽器の実装をしますがこれは次回行います。

楽器の音色を決める(1)

抽象クラスAbstractTimeLineのソース

前回ソース全体を載せていなかったので以下抽象クラスのソースになります。

package mocha.sound;

import java.util.ArrayList;
import java.util.TreeMap;

public abstract class AbstractTimeLine extends TreeMap<Long, ArrayList> implements SoundReadable {

  protected long index;
  protected ArrayList reading;
  protected int channel;
  ArrayList buffer;

  public AbstractTimeLine(int channel) {
    this.channel = channel;
    index = 0;
    reading = new ArrayList<>();
    buffer = new ArrayList<>();
  }

  protected abstract SoundReadable formatReadable(SoundReadable readable);

  public void addReadable(double second, SoundReadable readable) {
    long key = (long) (second * SAMPLE_RATE);
    if (!containsKey(key)) {
      put(key, new ArrayList<>());
    }
    SoundReadable formatted = formatReadable(readable);
    if (formatted == null || formatted.getChannel() != channel) {
      throw new IllegalArgumentException("unexpected channel=" + readable.getChannel());
    }
    get(key).add(formatted);
  }

  @Override
  public long length() {
    long length = 0;
    for (long key : keySet()) {
      for (SoundReadable readable : get(key)) {
        length = Math.max(length, key * channel + readable.length());
      }
    }
    return length;
  }

  @Override
  public int getChannel() {
    return channel;
  }

  @Override
  public double read() {
    if (buffer.isEmpty()) {
      if (containsKey(index)) {
        for (SoundReadable readable : get(index)) {
          reading.add(new ReadableCounter(readable));
        }
      }
      index++;
      for (int i = reading.size() - 1; i >= 0; i--) {
        if (reading.get(i).rest <= 0) {
          reading.remove(i);
        }
      }
      double[] values = new double[channel];
      for (ReadableCounter counter : reading) {
        for (int i = 0; i < channel; i++) {
          values[i] += counter.readable.read();
        }
        counter.rest -= channel;
      }
      for (int i = 0; i < channel; i++) {
        buffer.add(values[i]);
      }
    }
    return buffer.remove(0);
  }

  class ReadableCounter {

    long rest;
    SoundReadable readable;

    ReadableCounter(SoundReadable readable) {
      this.readable = readable;
      rest = readable.length();
    }
  }

}
スポンサーリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

%d人のブロガーが「いいね」をつけました。