wavファイルを書き出すクラスをもう少し改良します。今まではこのクラスのなかでオシレータや周波数マップをもとに自前で書き出す音量を計算していましたがこれは別のクラスに任せて指定された長さ分読むだけの処理を行います。
wavファイル書き出しクラスの修正
このクラスではオシレーターとか周波数とかは関知せずひたすらデータを読み込んでファイルに書き出すようにします。データの読み込む先をインタフェースで定義しておきます。
package mocha.sound;
public interface SoundReadable {
public long length();
public int getChannel();
public double read();
}
読み込むときにその読み込み元について知るべきことはこのメソッドで定義した3点です。
- 読み込み元のデータの長さ
- チャンネル数
- 読み込むデータ
書き出しクラスはこれを受けてシンプルになります。
package mocha.sound;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
public class WavFileWriter extends InputStream {
public static final float SAMPLE_RATE = 48000;
boolean signed = true;
boolean big_endian = true;
int sample_size_byte = 2;
ArrayList list;
double volume;
SoundReadable readable;
public WavFileWriter(SoundReadable readable) {
this.readable = readable;
list = new ArrayList<>();
volume = Math.pow(2, sample_size_byte * 8 - 1) - 1;
}
@Override
public int read() throws IOException {
if (list.isEmpty()) {
double value = readable.read() * volume;
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.putLong((long) value);
byte[] array = buffer.array();
for (int i = 8 - sample_size_byte; i < 8; i++) {
list.add(array[i]);
}
}
int ret = Byte.toUnsignedInt(list.remove(0));
return ret;
}
public long length() {
return readable.length();
}
public AudioFormat getFormat() {
return new AudioFormat(SAMPLE_RATE, sample_size_byte * 8, readable.getChannel(), signed, big_endian);
}
}
オシレーターの読み込み元クラスを作る
先ほどのインタフェースを実装したオシレータと周波数を元にデータを返すクラスを作ります。
package mocha.sound;
public class OscillatorReader implements SoundReadable {
public static final int CHANNELS = 1;
Oscillatable oscillatable;
FrequencyMap freqMap;
double seconds;
long index;
public OscillatorReader(Oscillatable oscillatable, FrequencyMap freqMap, double seconds) {;
this.oscillatable = oscillatable;
this.freqMap = freqMap;
this.seconds = seconds;
}
@Override
public long length() {
return (long) (WavFileWriter.SAMPLE_RATE * CHANNELS * seconds);
}
@Override
public int getChannel() {
return CHANNELS;
}
@Override
public double read() {
return oscillatable.read(freqMap.getFreq(index++), 1);
}
}
チャンネルはモノラルなので1固定です。そのうちステレオにするときにはこれを2にする予定です。
処理のロジックを書く
mainメソッドに以下のような記述をします。処理する内容は前回と同じですが多少書き方が変わりました。多少すっきりしたと思います。周波数や秒数やオシレーターの種類を変更するとまた別の音ができるでしょう。
FrequencyMap freqMap = new FrequencyMap(440.0);
freqMap.putFreq(2.0, 880.0);
WavFileWriter ss = new WavFileWriter(new OscillatorReader(new SineOscillator(), freqMap, 3));
AudioSystem.write(
new AudioInputStream(ss, ss.getFormat(), ss.length()),
AudioFileFormat.Type.WAVE, new File("/Users/minaberger/mywork/sine_440_880.wav"));
書き出されるwavファイルは前回と全く同じですが中の実装が整理されたのでまた発展的な音を作ることができそうです。