JavaのSound sampledについて

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

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

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

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

https://kensukeinage.com/synth_structure/

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

http://k-hiura.cocolog-nifty.com/blog/2011/07/java-6e01.html

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

http://muimi.com/j/game/sound/

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

# ライブラリの勉強　Java Sound

https://docs.oracle.com/javase/jp/1.5.0/guide/sound/programmer_guide/contents.html

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

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

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/8f13c0e1-d4a5-15df-b1ea-cb3a7370dca5.png)

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

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

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/caa3d9da-fac5-e47f-115b-54a1b5757488.gif)
![3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/ff353c78-f70c-ea93-eb77-50b13902b7fc.gif)

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

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

## Lineオブジェクト

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

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/Control.html

```Java
Control(Control.Type type)
```
で生成したControlインスタンスをメンバ変数として持ちます。

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

https://javadoc.scijava.org/Java6/javax/sound/sampled/Control.Type.html

```java
Control.Type("Gain")
Control.Type("Balance")
```
のように宣言して、Lineオブジェクトに機能を追加します。

詳しくはこちら

https://docs.oracle.com/javase/jp/1.5.0/guide/sound/programmer_guide/chapter6.html

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

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

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/LineEvent.html

サンプルコード

https://www.tabnine.com/code/java/methods/javax.sound.sampled.Clip/addLineListener

https://github.com/jcgay/maven-notifier/blob/master/src/main/java/fr/jcgay/maven/notifier/sound/EndListener.java


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

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/Line.Info.html

詳しくは不明


# Portオブジェクト

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/Port.html


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

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/Port.Info.html

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

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

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/SourceDataLine.html

```java
open(AudioFormat format)
or
open(AudioFormat format, int bufferSize)
```
でスーパークラスのLineインスタンスをopen()します（Lineインスタンスのオープンはメモリの生成？）。

```java
int write(byte[] b,
          int off,
          int len)
```
bがバイトデータ
offはバッファの書き込み開始点
lenはbのサイズ

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

https://docs.oracle.com/javase/jp/1.5.0/guide/sound/programmer_guide/chapter4.html

```java
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のメソッドです。

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/DataLine.html#start--


# 再生システムの構築
![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/caa3d9da-fac5-e47f-115b-54a1b5757488.gif)
このようにインスタンスを繋げることで再生システムを構築できるはずです。
(追記)実際にはつなげることはしない。後述のAudioSystemクラス内のメソッドでDataLineをgetする。このDataLine内にMixerは含まれているのか？Mixerクラス内にsynchronizeメソッドがあり、Lineオブジェクトを繋げることができそうだが。

https://docs.oracle.com/javase/jp/1.5.0/guide/sound/programmer_guide/chapter3.html

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

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

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

https://mocha-java.com/recording-5/

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/AudioSystem.html

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

# AudioSystemクラスについて

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

```java
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形式がほかに可能そうです。

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/AudioFileFormat.Type.html

### AudioFileFormat

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/AudioFileFormat.html

```java
        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()

```java
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に関してはこちら

https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/sampled/Mixer.Info.html

```java
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を使っています。

```java
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インスタンスによる再生

https://nompor.com/2017/12/14/post-128/

が参考になります。

AudioInputStreamを取得して、

```java
clip.open(AudioInputStream);
```
で再生していますね。

最初に挙げた

http://k-hiura.cocolog-nifty.com/blog/2011/07/java-6e01.html

こちらでも

```java
clip.open(AudioFormat format, byte[] data, int offset, int bufferSize);
```
を使って再生しています。


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

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

```java
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情報の編集、音声の編集、ミキサー、エフェクタを持つソフト