LoginSignup
0
0

More than 1 year has passed since last update.

JavaのSound sampledについて

Last updated at Posted at 2022-07-30

概要

Javaでシンセサイザーを作ることを目標にします。
付ける機能は

  • キーボードとドレミファソラシドを紐づけて、ピアノのようにする
  • 波形(サイン・のこぎり・矩形)
  • エンベローブ(減衰)
  • フィルタ(音色)
  • モジュレーション(周期的な変調)
  • リズムの定型を複数用意して繰り返し流す(ドラム)
  • 和音・コード入力
  • 録音+繰り返し
  • ハイゲイン(ギター)
  • リバーブ
  • 波形・FFT表示
  • 繰り返し等を使う場合、楽譜と現在位置の表示

を目標にします。私自身、まったく音楽をやったことも詳しくもないので、そこも知っていきたいです。

シンセサイザーの機能の解説はこちら

JavaでPCM(パルス符号変調)で記述した音データを再生する方法は

こちらを参考にしました。

はAPIの概略がわかります(この後の話を見た後、見てみてください)。

ライブラリの勉強 Java Sound

が全体像の解説をしてくれています。
但しサンプルプログラムが一切ないです。

ライブラリの継承関係は次のようにLineをもとにして派生しています。

1.png

  • 音声ファイルをロードして再生する場合Clipインスタンスを事前に生成することで可能
  • SourceDataLineインスタンス逐次データを追加してリアルタイムで再生する
  • TargetDataLineインスタンス録音や音声データを取得することができる

Port,Mixerインスタンスを使用することで次のようなシステムを構築し、再生・録音をします。

2.gif
3.gif

ミキサー内の要素

ミキサーは複数のオーディオストリームを合成します。
ここでミキサー内の要素について説明します。

リバーブ = 残響
パン = 左右の強度差
ゲイン = 音の大きさ

Lineオブジェクト

メンバ変数1:Controlインスタンス

メンバ変数としてControlインスタンスを持ちます。

Control(Control.Type type)

で生成したControlインスタンスをメンバ変数として持ちます。

ControlインスタンスはControl.Typeクラスのインスタンスを持ちます。Control.Typeクラスは

Control.Type("Gain")
Control.Type("Balance")

のように宣言して、Lineオブジェクトに機能を追加します。

詳しくはこちら

メンバ変数2:LineListenerインスタンス

事前生成したLineListenerインスタンスを登録することで、Lineインスタンスのステータスが変化したときにイベントを受信することができます。

変化するたびに、LineListenerインスタンス内のupdate()メソッドが呼び出されます。この際LineEventオブジェクトが渡されるので、イベントの情報・再生停止位置などを取得することができます。

サンプルコード

メンバ変数3:Line.Infoオブジェクト

詳しくは不明

Portオブジェクト

メンバとしてPort.Infoオブジェクトを持ちます。

イヤホンやマイクなどの指定ができます。

SourceDataLine

オーディオデータをバイトデータで設定することができます。

open(AudioFormat format)
or
open(AudioFormat format, int bufferSize)

でスーパークラスのLineインスタンスをopen()します(Lineインスタンスのオープンはメモリの生成?)。

int write(byte[] b,
          int off,
          int len)

bがバイトデータ
offはバッファの書き込み開始点
lenはbのサイズ

バッファは常に満たされている必要はなく、バッファが空の場合は音が出ない状態になります。
バッファサイズ以上のデータをwriteすると、再生しながらバッファの送り出しとデータの書き込みが逐次行われます。すべてのデータがバッファに入った段階で処理が帰ります。書き込みデータがバッファサイズ以下の場合はすぐ戻ります。

line.start();
while (total < totalToRead && !stopped)}
    numBytesRead = stream.read(myData, 0, numBytesToRead);
    if (numBytesRead == -1) break;
    total += numBytesRead; 
    line.write(myData, 0, numBytesRead);
}

がサンプルです。
start(),stop()メソッドはDataLineのメソッドです。

再生システムの構築

2.gif
このようにインスタンスを繋げることで再生システムを構築できるはずです。
(追記)実際にはつなげることはしない。後述のAudioSystemクラス内のメソッドでDataLineをgetする。このDataLine内にMixerは含まれているのか?Mixerクラス内にsynchronizeメソッドがあり、Lineオブジェクトを繋げることができそうだが。

こちらを読むとわかるように、これまでMixerをjava内で生成したインスタンスと考えていましたが、上のリンクに書いてあるように

"
システムには複数のミキサーがインストールされている。通常は、オーディオ入力用のミキサーとオーディオ出力用 のミキサーが、少なくとも 1 つずつある。
"

とあり、windows内にあるソフトウェアを指している可能性がある。

によると、AudioSystemクラスにより具体的にwindows内のソフトとつなげるということだろう。

AudioSystemクラスについて

フォーマット取得:ファイル名指定より

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.File;
import java.io.IOException;

public class AudioSystem_test {
    public static void main(String[] args) throws UnsupportedAudioFileException, IOException {
        System.out.println("start");
        AudioFileFormat format = AudioSystem.getAudioFileFormat(new File("C:/Users/~/test2.wav"));
        System.out.println(format.toString());
    }
}

結果

WAVE (.wav) file, byte length: 178382796, data format: PCM_SIGNED 96000.0 Hz,
 32 bit, stereo, 8 bytes/frame, little-endian, frame length: 22297844

mp3は対応していませんでした。wavファイルはいけます。
AIFF,AU,SND形式がほかに可能そうです。

AudioFileFormat

        System.out.println(format.toString());
        System.out.println(format.getFormat());
        System.out.println(format.getType());
        Map<String,Object> map = format.properties();
        System.out.println(format.getProperty("title"));

こんな感じで見れる。getPropertyはnullだった。原因不明

getMixerInfo()

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;

public class AudioSystem_test {
    public static void main(String[] args) throws UnsupportedAudioFileException, IOException {
        System.out.println("start");
        Mixer.Info[] info = AudioSystem.getMixerInfo();
        for(Mixer.Info i:info){
            System.out.println(i.toString());
        }
    }
}

結果

Port ヘッドホン (WI-C310 Stereo), version 0.0
Port ヘッドホン (Realtek(R) Audio), version 10.0
Port PL2492H (NVIDIA High Definition, version 10.0
~
ヘッドセット (WI-C310 Hands-Free AG A, version Unknown Version

こんな感じで使用している音声機器がすべて得られる。

Mixer.Infoに関してはこちら

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;

public class AudioSystem_test {
    public static void main(String[] args) throws UnsupportedAudioFileException, IOException {
        System.out.println("start");
        Mixer.Info[] info = AudioSystem.getMixerInfo();
        for(Mixer.Info i:info){
            System.out.println("----------------------------");
            System.out.println(i.getName());
            System.out.println(i.getDescription());
            System.out.println(i.getVendor());
            System.out.println(i.getVersion());

        }
    }
}

getMixer

次に先ほど得られたMixer情報からヘッドホンの名前に一致するMixerを取得してみます。
私はワイヤレスイヤホンWI-C310を使っています。

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Line;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;

public class AudioSystem_test {
    public static void main(String[] args) throws UnsupportedAudioFileException, IOException {
        System.out.println("start");
        Mixer.Info[] info = AudioSystem.getMixerInfo();
        Mixer mixer = null;
        for(Mixer.Info i:info){
            System.out.println(i.getName());
            if(i.getName().equals("Port ヘッドホン (WI-C310 Stereo)")){
                mixer = AudioSystem.getMixer(i);
                System.out.println("find");
                break;
            }
        }
        Line[] lines = mixer.getTargetLines();
        System.out.println(lines.length);
        
        for(Line i:lines){
            System.out.println(i.toString());
        }

    }
}

結果、ミキサーはgetできたようだが、mixerには何のDataLineも接続されていない。

Clipインスタンスによる再生

が参考になります。

AudioInputStreamを取得して、

clip.open(AudioInputStream);

で再生していますね。

最初に挙げた

こちらでも

clip.open(AudioFormat format, byte[] data, int offset, int bufferSize);

を使って再生しています。

SourceDataLineによる再生

Clipと同様にopenでメモリ等のリソースを確保し、writeでバッファに書き込むという流れでしょう。

Mixer等に接続する必要はないのか、自動で接続されるのか気になりますね。
まぁいいか。Clipのコードを真似して作ってみましょう。

import javax.sound.sampled.*;

public class SourceDataLine_test {
    public static void main(String[] args) throws InterruptedException, LineUnavailableException {
        byte[] wave_data= new byte[44100*2];                      // @A-sta@
        double l1      = 44100.0/440.0;    // A 440hz
        for(int i=0;i<wave_data.length;i++){
            wave_data[i]= (byte)(110*Math.sin((i/l1)*Math.PI*2));
        }                                                      //@A-end@

        AudioFormat frmt= new AudioFormat(44100,8,1,true,false);
        DataLine.Info info= new DataLine.Info(SourceDataLine.class,frmt);
        SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);
        source.open(frmt);

        System.out.println(source.toString());

        source.flush();
        source.write(wave_data,0,wave_data.length);
        source.start();
        System.out.println(source.getBufferSize());
        System.out.println(source.available());
        System.out.println(source.isRunning());
        Thread.sleep(3000);
        System.out.println(source.isRunning());
    }

}

一応音が鳴ります。

MIDIとは

楽譜、演奏情報、通信規格
通信ケーブルのことではない

MIDI音源=MIDIの情報をもとに音を生成する

DAW(Digital Audio Workstation)はMIDI情報の編集、音声の編集、ミキサー、エフェクタを持つソフト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0