Python-Sounddevice ASIOで使える音響信号処理モジュール[基本編]

#PythonモジュールのSounddeviceってなあに？
Pythonで使える音響信号処理モジュールといえばPyaudioがあると思いますが、個人的になんか使いにくい！また一度Windowsで開発中に使えなくなったことでPyaudioには軽いトラウマがあり、ASIOベースでオーディオインターフェースの制御ができるSounddeviceを紹介したいと思います。

###sounddeviceのいいところ
・ASIOデバイスをコントロールできる
・入出力のチャンネルマッピング（マイク、スピーカの振り分け）も簡単
・WASAPI、core audioの制御も簡単
・とにかく書きやすい使いやすい
・同時録音再生が超簡単にできる（音響工学やってる人的にはインパルス応答の計測とかめっちゃ簡単にできて嬉しい）

#この記事で扱う項目
この記事ではsounddeviceを使う前の準備から使う上での初期設定の方法、そしてsounddeviceでの基本的な再生方法、録音方法、同時録音再生方法、ストリーミングの方法までを書いていきます。

チャンネルマッピングの方法（マルチチャンネル環境で好きなマイクから入力、好きなスピーカから出力する方法）やASIO、WASAPI、Core Audioの細かい設定方法などについては後編の[Python Sounddevice ASIOで使える音響信号処理モジュール[応用編]](https://qiita.com/MikuMaekawa/items/29030ff5b3bf329050d9)で書いていきたいと思います。

#インストール
###①Linux、macOS、Anaconda環境ではconda-forgeからインストールできます。
```conda install -c conda-forge python-sounddevice
```

###②pipを使ってPyPlからインストールできます。
```python3 -m pip install sounddevice
```
macOSあるいはWindowsにpipを使ってSounddeviceをインストールした場合、自動的にPortAudioライブラリーがダウンロードされます。他のプラットフォームでインストールする場合には各々ライブラリーをインストールしてください。

#使い方
##インポート
```
import sounddevice as sd
import numpy as np #NumPyは基本必須です。インポートしてください。
import wave #オーディオファイルを扱いたい場合インポートします
```
sounddeviceでは基本NumPy配列でデータを扱うのでNumPyのインポートが必要です。
しかしNumPyを使わずにデータを扱う方法もあるのでそれについては後述します。
##初期設定
sounddeviceではデフォルトで使えるサンプルレートやチャンネル数、使用するオーディオインターフェースをあらかじめ設定しておくこともできます。もちろん設定を行わずその都度設定することもできます。今回の記事ではデフォルトの設定を行う場合と行わない場合について両方記述していきますが、とりあえずはじめにデフォルトの設定の仕方を書いておきます。
###サンプルレートの設定
```default_fs.py
fs = 44100 #普通41000Hzか48000Hz
sd.default.samplerate = fs
```
###チャンネル数の設定
出力する際のチャンネル数は関数に入力されたデータから自動で設定されますが、入力チャンネル数については自分で指定する必要があります。

```default_ch.py
channels = 2
sd.default.channels = channels
#sd.default.channels = 1, 2 としてチャンネル数をそれぞれ設定することもできます
```
###使用機材の設定
まず自分の環境に搭載あるいは接続されているオーディオシステムのデバイスIDを取得します。

```default_system.py
sd.query_devices()
```
コマンドプロンプトに入力してみると私の環境では次のように出力されました。

```
> 0 Built-in Microphone, Core Audio (2 in, 0 out)
< 1 Built-in Output, Core Audio (0 in, 2 out)
  2 Pro Tools 機器セット, Core Audio (2 in, 2 out)
```
現在入力はIDの0、出力はIDの1に設定されています。ASIOデバイスにマシンを接続している場合

```
3 〇〇Audio system, ASIO (24 in, 24 out)
```

のように出力されます。使いたいデバイスIDを確認できたら次のように設定してください。

```default_system.py
sd.default.device = 3
# sd.default.device = [4, 5]として入出力をそれぞれ設定することもできます。
```
###記録するデータ型の設定
録音する際、デフォルトでは'float32'で記録されますが、異なるデータ型で記録したい場合には次のように設定してください。

```default_dtype.py
sd.default.dtype = 'float64'
```

###全ての初期設定をリセット
設定した初期設定を解除します

```default_reset.py
sd.default.reset()
```

##再生(Playback)
実行すると関数はすぐに戻りますがバックグラウンドで再生をし続けます。


```playback.py
sd.play(myarray,fs) 
# myarray : オーディオデータを保持するNumPy配列
# fs : サンプルレート
```
サンプルレートの初期設定を行った場合引数を削減できます。

```playback.py
sd.play(myarray)
```

その他オプションについてはこのようになっています。

```playback.py
sd.play(data,
        samplerate=None,
        mapping=None, #チャンネルマッピングの設定（後編で説明します）
        blocking=False, #Trueで関数が戻らないようにします（再生中は待機します）
        loop=False, #Trueでループ再生します
        **kwargs) 
```
##再生停止(stop)
音の再生を停止する。

```stop.py
sd.stop()
```
##録音(Recording)
取得したオーディオデータをNumPy配列で記録します。デフォルトでは配列データは'float32'で記録されます。実行すると関数はすぐに戻りますが、バックグラウンドで録音を続けます。

```rec.py
duration = 10.5 #再生時間[秒]
recdata = sd.rec(int(duration * fs), samplerate = fs,channels = 2)
#タップ数、サンプルレート、チャンネル数
```
初期設定を行った場合引数を削減できます。

```rec.py
recdata = sd.rec(int(duration * fs))
```
録音が完了したか知りたい場合次の関数を使います。
この関数では録音が完了している場合はすぐに戻り、未完了の場合には録音が終了してから戻ります。録音が終了してから次のフェーズに進みたい時にも使えます。

```rec.py
sd.wait()
```
その他オプションについては次のようになっています

```rec.py
recdata = sd.rec(frames=None,#オプション　録音されているフレーム数 outが指定されている場合は必要無し
                 samplerate=None,
                 channels=None,#チャンネル数　mappingまたはoutが指定されている場合必要無し
                 dtype=None,#データ型の設定
                 out=None,#任意の配列に録音データを書き込みます
                 mapping=None,#チャンネルマッピングの設定（後編で説明します）
                 blocking=False,#Trueで録音中は待機します
                 **kwargs)
```

##同時録音再生(Simultaneous Playback and Recording)
Arrayの再生と録音を同時に実行できます。
出力チャンネル数については与えられたデータから自動で設定が行われますが、入力チャンネル数については自分で設定することができます。

```playrec.py
recdata = sd.playrec(myarray,fs,channels = 2)
```
初期設定でデフォルトのサンプルレート、チャンネル数を決めている場合は例によって記述を省略できます。

```playrec.py
recdata = sd.playrec(myarray)
```
その他オプションについてはこちら

```playrec.py
recdata = sd.playrec(data,
                     samplerate=None,
                     channels=None,#出力チャンネル数の設定　無ければ自動的にデータから決定
                     dtype=None,#データ型の設定
                     out=None,#任意の配列に録音データを書き込みます
                     input_mapping=None,#入力のチャンネルマッピングの設定
                     output_mapping=None,#出力のチャンネルマッピングの設定
                     blocking=False,#Trueで録音中は待機
                     **kwargs)
```
##ストリーミング(Callback stream)
マイクで取ってきた音をそのままスピーカに流します

```stream.py
duration = 5.5  # seconds

def callback(indata, outdata, frames, time, status):
    if status:
        print(status)
    outdata[:] = indata

with sd.Stream(channels=2, callback=callback):
    sd.sleep(int(duration * 1000))   
```

またここでのsd.sleepは任意の時間次の呼び出しを凍結できますが、あんまり正確じゃないので注意

```sleep.py
sd.sleep(msec)
```

#とりあえずここまで
簡単な使い方についてはこのあたりで十分だと思います。
チャンネルマッピングの方法（マルチチャンネル環境で好きなマイクから入力、好きなスピーカから出力する方法）やASIO、WASAPI、Core Audioの細かい設定方法などについては後編の[Python Sounddevice ASIOで使える音響信号処理モジュール[応用編]](https://qiita.com/MikuMaekawa/items/29030ff5b3bf329050d9)で書いていきたいと思います。

#リファレンス
[Python sounddevice公式](https://python-sounddevice.readthedocs.io/en/0.3.7/)
[Python Sounddevice ASIOで使える音響信号処理モジュール[応用編]](https://qiita.com/MikuMaekawa/items/29030ff5b3bf329050d9)