ASIO SDKで音を出す

# 概要
ASIO規格に対応しているサウンドカードを使うために、Steinbergから[ASIO SDK](https://www.steinberg.net/en/company/developers.html)が配布されています。
Windows上でこのSDKを使って、sin波を出力します。

# 環境構築

### Visual Studio

1. Visual Studio 2019をインストールしておいてください
2. [公式チュートリアル](https://docs.microsoft.com/ja-jp/cpp/windows/walkthrough-creating-windows-desktop-applications-cpp?view=vs-2019)を参考にしてC++でWindowsデスクトップアプリケーションを開発するための設定をしておいてください

### SDKのダウンロード

[配布ページ](https://www.steinberg.net/en/company/developers.html)からASIO SDKをダウンロードします。

### サンプルソースコードのビルド

1. sdkのフォルダを開いて、
host->sampleの中にあるhostsample.dspをVisualStudioで開きます。プロジェクトのアップデートを促されるのでOKを押してアップデートします。
2. VisualStudioの上にある「ローカルWindowsデバッガ－」ボタンを押して実行します。

### ドライバのインストール

今の時点では、実行しても何も表示されず処理が終了します。これは、正しいASIOドライバが選択されていないからです。テスト用にASIO4ALLというドライバをインストールします。
[ダウンロード](http://www.asio4all.org/)してインストールしてください。

# サンプルプログラムの改造

### ASIOドライバの列挙

hostsample.cppを以下のように編集します。

```diff:hostsample.cpp-10行目付近
     #include <string.h>
+    #include <iostream>
     #include "asiosys.h"
```

```diff:hostsample.cpp-450行目付近
 int main(int argc, char* argv[])
 {
+    AsioDrivers ads;
+    char buffer[10][32] = {};
+    char* buf[10];
+    for (int i = 0; i < 10; ++i) {
+        buf[i] = buffer[i];
+    }
+    ads.getDriverNames(buf, 10);
+    for (int i = 0; i < 10; ++i) {
+        std::cout << buf[i] << std::endl;
+    }
     // load the driver, this will setup all the necessary internal data structures
     if (loadAsioDriver (ASIO_DRIVER_NAME))
```

この状態でプログラムを実行すると、PCにインストールされているASIOドライバの一覧が表示されます。
例えば、以下のように表示されます。

```:出力
ASIO4ALL v2
Realtek ASIO
Yamaha Steinberg USB ASIO
```

「ASIO4ALL v2」ドライバが正しくインストールされていることが確認できます。

### ASIOドライバの選択

hostsample.cppを以下のように編集します。

```diff:hostsample.cpp-15行目付近
 // name of the ASIO device to be used
 #if WINDOWS
 //	#define ASIO_DRIVER_NAME    "ASIO Multimedia Driver"
-	#define ASIO_DRIVER_NAME    "ASIO Sample"
+	#define ASIO_DRIVER_NAME    "ASIO4ALL v2"
 #elif MAC
```

プログラムを実行すると、選択されたASIOドライバが起動します、同時に、対応するASIOデバイスの詳細が取得され、表示されます。例えば以下のように表示されます。

```:出力
asioVersion:   0
driverVersion: 2
Name:          ASIO4ALL v2
ErrorMessage:  No ASIO Driver Error
ASIOGetChannels (inputs: 2, outputs: 2);
ASIOGetBufferSize (min: 64, max: 2048, preferred: 512, granularity: 8);
ASIOGetSampleRate (sampleRate: 44100.000000);
ASIOOutputReady(); - Not supported
ASIOGetLatencies (input: 0, output: 997);

ASIO Driver started succefully.

 23600645 ms / 223600645 ms / 883200 samples / TC: 00:00:00:00000
```

### サンプリングビットの確認

今の段階では、ASIOドライバは起動したものの、音声データがドライバに渡されていないので、音が鳴りません。そこで、sin波の音声データを渡す処理を書こうと思います。しかし、サンプリングビットによって音声データの渡し方が少し変わるので、今のドライバが利用しているサンプリングビットを調べます。

```diff:hostsample.cpp-220行目付近
 // perform the processing
 	for (int i = 0; i < asioDriverInfo.inputBuffers + asioDriverInfo.outputBuffers; i++)
 	{
 		if (asioDriverInfo.bufferInfos[i].isInput == false)
 		{
+			std::cout << asioDriverInfo.channelInfos[i].type << std::endl;
			// OK do processing for the outputs only
			switch (asioDriverInfo.channelInfos[i].type)
```

`asioDriverInfo.channelInfos[i].type`の中の値がサンプリングビット数に対応しています。
その数値と、サンプリングビットやサンプリングビットのデータ型の対応は以下のようになります。

| 数値       |  サンプリングビット |     サンプリングビットのデータ型 |
|:-----------------|------------------:|:------------------:|
|16                |           16ビット|     ASIOSTInt16LSB|
|17                |           24ビット|     ASIOSTInt24LSB|
|18                |           32ビット|     ASIOSTInt32LSB|

この状態で実装すると、手元の環境では以下のようになりました。

```:出力
...
ASIO Driver started succefully.

18ms / 0 ms / 0 samples / TC: 00:00:00:00000
18
18
18
18
...
...
```

18という数値が出力されたことから、僕の環境では32ビットのサンプリングビット数であることが分かりました。

次の作業に移る前に、数値の出力の処理はもう使わないのでコメントアウトしておきます。

```diff:hostsample.cpp-218行目
 // perform the processing
 	for (int i = 0; i < asioDriverInfo.inputBuffers + asioDriverInfo.outputBuffers; i++)
 	{
 		if (asioDriverInfo.bufferInfos[i].isInput == false)
 		{
-			std::cout << asioDriverInfo.channelInfos[i].type << std::endl;
+			//std::cout << asioDriverInfo.channelInfos[i].type << std::endl;
			// OK do processing for the outputs only
			switch (asioDriverInfo.channelInfos[i].type)
```

### sin波を出力

以下のように編集します。

```diff:hostsample.cpp-10行目付近
 #include <string.h>
 #include <iostream>
+#include <cmath>
+#include <limits>
 #include "asiosys.h"
```

```diff:hostsample.cpp-170行目付近
 #endif
+
+ int t = 0;

 ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow)
```

```diff:hostsample.cpp-220行目付近
 // buffer size in samples
	long buffSize = asioDriverInfo.preferredSize;
+	t += buffSize;
+	int tempT = t;
	// perform the processing
	for (int i = 0; i < asioDriverInfo.inputBuffers + asioDriverInfo.outputBuffers; i++)
	{
		if (asioDriverInfo.bufferInfos[i].isInput == false)
		{
			//std::cout << asioDriverInfo.channelInfos[i].type << std::endl;
+			int32_t* dist = static_cast<int32_t*>(asioDriverInfo.bufferInfos[i].buffers[index]);
			// OK do processing for the outputs only
			switch (asioDriverInfo.channelInfos[i].type)
			{
			case ASIOSTInt16LSB:
				memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 2);
				break;
			case ASIOSTInt24LSB:		// used for 20 bits as well
				memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 3);
				break;
			case ASIOSTInt32LSB:
				memset (asioDriverInfo.bufferInfos[i].buffers[index], 0, buffSize * 4);
+				for (int i = 0; i < buffSize; ++i) {
+				*(dist++) = (INT32_MAX/2) * std::sin((float)tempT++ / 10.0f);
+				}
				break;
			case ASIOSTFloat32LSB:		// IEEE 754 32 bit float, as found on Intel x86 architecture
```

`asioDriverInfo.bufferInfos[i].buffers[index]`にはi番目の出力チャンネルに渡す音声データへのポインタが入っています。`buffSize`は一度に書き込むデータの個数です。

# 結論

ASIO SDKを使ってsin波を出すことが出来ました。
気が向いたらWAVファイルの出力もやってみたいです。

# 参照

[金澤ソフト設計 : ASIOを利用してWAVファイルを再生する](https://kana-soft.com/tech/sample_0004.htm)