前回はステレオの音源を作成しました。今回は複数の音が重ね合わされたwavファイルを作成します。音を重ね合わせることをミックスmixと言います。複数の音をミックスするにはある時間のそれぞれのサンプルの値をたせばよいのです。
また複数の音を同じ時間に慣らせば和音になりますが一つの音が終わった後に次の音を鳴らすようにすればメロディになります。
複数の音をどのタイミングで鳴らすかを管理するクラスを作ります。名前はTimeLineとします。
クラスに必要な機能
まずどのような機能が必要かを書き出します。
- 音源SoundReadable実装クラスとその始まる時間を保持する
- ステレオ2チャンネルの実装
- モノラルの音源の場合はパンは中央にする
機能としてはそれほど複雑ではなさそうです。
どのように実装するか検討する
実際にどのような実装にするかを考えます。
public class TimeLine extends TreeMap<Long, ArrayList<SoundReadable>> implements SoundReadable
クラスはTreeMapを継承して作ります。鍵がLong、値はSoundReadableの可変配列です。
鍵はサンプルの位置になります。一番最初からであれば0になりますし1秒後であればサンプリングレート48000Hzの場合48000になります。
値を可変配列にしているのはなぜかというと、ある場所から鳴り出す音というのは一つとは限らないからです。同時に3つの音で和音を鳴らすのであれば配列の長さは3になるはずです。
またステレオなのである時間でのサンプルは左右の2つにります。これをバッファしておきます。操作はパニングを行うときにやったものと同じです。
音源を追加するメソッド
public void addReadable(double second, SoundReadable readable){
  long key = (long) (second * SAMPLE_RATE);
まず引数の秒数にサンプリングレートをかけて何番目のサンプルかを求めます。その数を鍵としてエントリがあるか調べなければ空の可変配列を設定します。
  if(!containsKey(key)){
    put(key, new ArrayList<>());
  }
音源のチャンネル数によって切り分けます。1チャンネルつまりモノラルの場合はパンを中央に設定して可変配列に追加します。中央値は0.5です。
2チャンネル、つまりステレオの場合はそのまま可変配列に追加します。
それ以外のものは想定外として例外を投げて終了します。
  int channel = readable.getChannel();
  switch (channel) {
    case 1:
      get(key).add(new Panner(readable, new DoubleMap(0.5)));
      break;
    case 2:
      get(key).add(readable);
      break;
    default:
      throw new IllegalArgumentException("unexpected channel=" + readable.getChannel());
  }
}
自分自身の長さを返す
次にSoundReadableで実装するべきメソッドを実装します。まずはデータの長さを返すメソッドを実装します。
2チャンネルなのでサンプル数の2倍になります。自分に登録されている全ての音源を調べて一番最後に終わる位置を返します。
@Override
public long length() {
  long length = 0;
  for(long key:keySet()){
    for(SoundReadable readable:get(key)){
      length = Math.max(length, key * 2 + readable.length());
    }
  }
  return length;
}
登録されている鍵の数分の可変配列を調べ、それぞれの可変配列に含まれる音源を全て調べるためfor分が二重になっています。
keyを2倍しているのはその音源が始まる前に返すデータの数は2チャンネルのためサンプル数の2倍になるからです。
続きます。