前回からの続きで複数の音を鳴らすwavファイルを作っています。複数の音源のサンプルを足し合わすことでミックスすることができます。SoundReadableインタフェースの実装を続けます。
チャンネル数を返す
このクラスは最初からステレオ2チャンネルで実装しているのでチャンネル数として返す場合も固定で2になります。
@Override public int getChannel() { return 2; }
音源とそこから読み込めるのこりのサンプル数を保持するクラス
タイムラインに登録されている音源のうち読み込むを開始するのは以下の条件のときです。
音源の開始位置 = 現在の読み込み位置
このときにデータの長さを残りのデータ数として覚えておいて読み込むたびにデクリメント(1ずつ減らす)していきます。そのためにSoundReadableと残りのデータ数を保持するクラスを定義します。
class ReadableCounter { long rest; SoundReadable readable; ReadableCounter(SoundReadable readable){ this.readable = readable; rest = readable.length(); System.out.println(rest); } }
フィールドとコンストラクタ
long index; ArrayList<ReadableCounter> reading; ArrayList<Double> buffer; public TimeLine(){ index = 0; reading = new ArrayList<>(); buffer = new ArrayList<>(); }
indexは現在の読み込み位置、readingは現在読み込み対象の音源です。bufferは2チャンネル分読み込んでバッファするためのものです。
readメソッドの実装
一番最後の行から説明します。バッファの先頭から抜き出した値を返します。
@Override public double read() { if(buffer.isEmpty()){ //読み込み処理 return buffer.remove(0); }
バッファになにもないときに読み込みの処理を行います。
まず現在の読み込み位置に音源が登録されている場合には読み込みリストにこれを足します。リストには先ほど定義したReadableCounterクラスで登録するので残りのデータ数を中で保持することになります。
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 valueL = 0; double valueR = 0; for(ReadableCounter counter:reading){ valueL += counter.readable.read(); valueR += counter.readable.read(); counter.rest -=2; } buffer.add(valueL); buffer.add(valueR);
ソース
最後に出来上がったソースを載せます。
package mocha.sound; import java.util.ArrayList; import java.util.TreeMap; public class TimeLine extends TreeMap<Long, ArrayList<SoundReadable>> implements SoundReadable { long index; ArrayList<ReadableCounter> reading; ArrayList<Double> buffer; public TimeLine() { index = 0; reading = new ArrayList<>(); buffer = new ArrayList<>(); } public void addReadable(double second, SoundReadable readable) { long key = (long) (second * SAMPLE_RATE); if (!containsKey(key)) { put(key, new ArrayList<>()); } 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()); } } @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; } @Override public int getChannel() { return 2; } @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 valueL = 0; double valueR = 0; for (ReadableCounter counter : reading) { valueL += counter.readable.read(); valueR += counter.readable.read(); counter.rest -= 2; } buffer.add(valueL); buffer.add(valueR); } return buffer.remove(0); } class ReadableCounter { long rest; SoundReadable readable; ReadableCounter(SoundReadable readable) { this.readable = readable; rest = readable.length(); System.out.println(rest); } } }
次回は実行処理を書いてwavファイルを作成します。