前回作ったサインオシレーターを使って音声ファイルを作ります。以前矩形波の音声ファイルを作成したのでこのロジックを流用したいと思います。InputStreamのサブクラスとして作っておけばAudioSystemの書き出しメソッドでファイルに書き出してくれるのでした。
始める前に以下の記事を読んでおいてください。
Javaで矩形波のWavファイルを作成する(1)
Javaで矩形波のWavファイルを作成する(2)
修正箇所を決める
フィールド値
矩形波を作成した時には内部でオシレーターがするべき計算もしていましたが今回は別にオシレーターを作っているのでフィールド値は少なくなります。
final boolean signed = true; final boolean big_endian = true; final float sample_rate = 48000; final int sample_size_byte = 2; final int channels = 1; double freq; double seconds; ArrayList list; double volume;
このうち固定の周波数の変数freqと秒数の変数secondはコンストラクタで取得します。可変配列のlistはバイト情報を保持するバッファです。
コンストラクタ
引数はそのままフィールドに取り込みます。他初期化では以下のことをしています。
- バイトバッファのlistを空の状態で初期化
- サンプルサイズから想定される最大ボリュームを計算する
- サインオシレーターの初期化
public SampleSine(double freq, double seconds){ this.freq = freq; this.seconds = seconds; list = new ArrayList<>(); volume = Math.pow(2, sample_size_byte * 8 - 1) - 1; oscillator = new SineOscillator(sample_rate); }
readメソッド
次にInputStreamで実装されるべきreadメソッドの修正を行います。矩形波の時から少しだけ変更して以下のようにしています。
@Override public int read() throws IOException { if(list.isEmpty()){ double value = oscillator.read(freq, 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; }
このうち変更したところは音量を取得する
double value = oscillator.read(freq, volume);
の部分です。矩形波のときにはオシレーターがするべき計算ロジックが入っていたのですがここでは1行だけになります。今後周波数やボリュームが順次変わっていくようなサイン波を作りたい場合はこのあたりを変更することになります。
mainメソッド
最後に処理を呼び出す側のmainメソッドを作ります。これも矩形波の作成ロジックとほぼ同じです。
public static void main(String[] arg) throws IOException { SampleSine ss = new SampleSine(440, 3); AudioSystem.write( new AudioInputStream(ss, ss.getFormat(), ss.length()), AudioFileFormat.Type.WAVE, new File("/Users/myaccount/mywork/sine_440.wav")); }
出力結果の確認
これを実行します。ファイルはwavフォーマットですがこのサンプルはサイズ節約のためmp3に変換しています。
またこの波形を表示すると以下のように見えました。
コード
最後にコードを載せておきます。
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 SampleSine extends InputStream { final boolean signed = true; final boolean big_endian = true; final float sample_rate = 48000; final int sample_size_byte = 2; final int channels = 1; double freq; double seconds; ArrayList list; double volume; SineOscillator oscillator; long index = 0; public SampleSine(double freq, double seconds) { this.freq = freq; this.seconds = seconds; list = new ArrayList<>(); volume = Math.pow(2, sample_size_byte * 8 - 1) - 1; oscillator = new SineOscillator(sample_rate); } @Override public int read() throws IOException { if (list.isEmpty()) { double value = oscillator.read(freq, 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 (long) (sample_rate * channels * seconds); } public AudioFormat getFormat() { return new AudioFormat(sample_rate, sample_size_byte * 8, channels, signed, big_endian); } public static void main(String[] arg) throws IOException { SampleSine ss = new SampleSine(440, 3); AudioSystem.write( new AudioInputStream(ss, ss.getFormat(), ss.length()), AudioFileFormat.Type.WAVE, new File("/Users/minaberger/mywork/sine_440.wav")); } }