TinyASIOを使ったリアルタイム・オーディオ処理

# はじめに
DTMではオーディオ・インターフェースという音響機器を使って，リアルタイムにDTMソフトと楽器間の通信を行っています．通信はASIOという規格が使われ，ASIOを使うと音が遅れて聴こえるという現象がなくなります．

さて，このASIOですが，オーディオ・インターフェースを持っていないとできないのかと言うとそうでもなく，[ASIO4ALL](http://www.forest.impress.co.jp/library/software/asio4all/)というドライバを入れれば使えるようになります．ただし，あくまで仮想的な振る舞いをするだけですので，そんなに速くならないかもしれません．あしからず．


# TinyASIO
[TinyASIO](https://github.com/GRGSIBERIA/TinyASIO)は，ASIOを操作するためのラッパーライブラリです．普通はCOMインターフェースを直接叩かないとASIOが使えないのですが，その辺りをカプセル化して使い易くしてあります．

## 最も簡単なサンプル
そういうわけで，雰囲気をつかむために簡単なサンプルを例示します．

```cpp:test.cpp
#include <Siv3D.hpp>
#include <TinyASIO.hpp>

void Main() {
  // 入力音声を直接出力するだけのコントローラ
  asio::InputBackController controller("AudioBox");  // 接続されているAudioBoxを使います
  
  controller.Start();   // バッファリング開始，うっかり忘れずに

  while (System::Update())
  {
    // StreamPtrはstd::shared_ptr<std::vector<float>>のtypedef
    StreamPtr stream = controller.Fetch();  // ここでストリームに蓄積された音声を取得できる
  }

  controller.Stop();    // バッファリング停止

  // 自動的に開放されます．Stopが呼ばれていない場合，勝手にStopします．
  // 稀に解放した時にオーディオ・インターフェースが原因で落ちることがあります．
}
```

たったの4～5行程度でオーディオ・インターフェースが扱えるようになります．

TinyASIOで一番重要なクラスがコントローラです．コントローラとは，音声データを送ったり受け取ったりするためのクラスです．
TinyASIOでは，おまけでInputBackControllerとInputOnlyControllerを提供しています．
コントローラの仕組みは後ほど詳しく説明します．

## オーディオ・インターフェースの一覧を取得
まずは，登録されているオーディオ・インターフェースの一覧を取得します．

```cpp:drivers.cpp
#include <Siv3D.hpp>
#include "TinyASIO.hpp"
 
void Main() {
  
  auto pathes = asio::Registory::GetAsioDriverPathes(); // ASIOドライバのレジストリ値を取得する
  
  std::string driverName;
  for (const auto& subkey : pathes.Items()) {
    driverName = subkey.driverName;   // ドライバの名前を適当に取得
  }
  
  asio::InputBackController controller(driverName); // ドライバ名で初期化する

  // 中略
} 
```

登録されているオーディオ・インターフェースは，asio::Registroy::GetAsioDriverPathes関数で取得することができます．
コントローラのコンストラクタにsubkey.driverNameを渡すと，自動的にそのドライバを取得して，ASIOの初期化を行います．

## コントローラを自作する
コントローラの自作は少し難しいかもしれません．

```cpp:original.cpp
#include "TinyASIO.hpp"
using namespace asio;

// ControllerBaseを継承することで，オリジナルのコントローラを作れます
class OriginalController : public ControllerBase
{
  // コールバック関数から呼び出せるようにstaticで宣言する
  static InputBuffer* input;
  static OutputBuffer* output;
  
  // バッファリング用のコールバック関数
  // バッファが満杯になるとASIOから呼び出される
  static void BufferSwitch(long index, long)
  {
    void* outBuf = output->GetBuffer(index);  // ASIO側の出力バッファのアドレス
    void* inBuf = input->GetBuffer(index);    // ASIO側の入力バッファのアドレス
    
    // 入力から出力に流す，BufferSize()でバッファの容量を返す
    memcpy(outBuf, inBuf, BufferSize());    // 入力バッファ0 => 出力バッファ0
    
    // 入力ストリームに蓄積する，BufferLength()でバッファの個数を返す
    input->Store(inBuf, BufferLength());    // 入力バッファ0 => 入力ストリーム0
    
    // note: 重い処理をかけると音が途切れるので注意
    // note: フーリエなどの処理は一度ストリームに蓄積して，コールバック関数外で処理するのがベタです
  }
  
public:
  OriginalController(const std::string& driverName) : ControllerBase(driverName)
  {
    // CreateBufferでバッファの初期化とコールバック関数の登録をする
    // 1番の入力チャンネルと0番の出力チャンネルからバッファを生成する
    CreateBuffer({channelManager->Inputs(1), channelManager->Outputs(0)}, &BufferSwitch);
    input = &bufferManager->Inputs(0);    // 入力チャンネル1 => 入力バッファ0
    output = &bufferManager->Outputs(0);  // 出力チャンネル0 => 出力バッファ0
  }
  
  StreamPtr Fetch()
  {
    // 入力ストリームの内容を取り出す
    return input->Fetch();
  }
};

InputBuffer* OriginalController::input = nullptr;
OutputBuffer* OriginalController::output = nullptr;
```

とりあえず，ソースコード中のコメントに必要な情報は書いてあります．
InputBufferもしくはOutputBufferのポインタをstatic宣言することと，CreateBuffer関数でチャンネルとバッファを対応付けることを忘れなければ，オールオーケーじゃないかなと．

バッファとストリームという言葉が出てきますが，バッファというのはASIO側で確保されるメモリのことを指します．ストリームの方は，バッファから受け取ったデータを退避させるためのメモリです．
バッファのデータはリアルタイムにやり取りさせますが，ストリームの方は逐次的にデータをやり取りするためのものです．

# おわりに
ちょっと急ぎ足になってしまいましたが，これでリアルタイムに音声処理ができるようになります．
音が遅れて聴こえなくなるだけなんですが，普通にOSのサウンドドライバを通すと，0.2～0.5秒ぐらい遅れて聞こえてきます．
楽器とコンピュータのインタラクションをするのに，この遅れはかなり致命的です．
ASIOはそこをミリ秒以下の遅れにしてくれる画期的な規格なのですが，いかんせん開発が難しいです．
開発を楽にしたいという理由でTinyASIOを作りましたが，今ではSiv3DとTinyASIOがないと研究にならないレベルで依存しています．
他の人も，ぜひぜひASIOで楽器とコンピュータのインタラクションを実現してみてください．

そういうわけで筆を置きます．
Siv3Dアドカレの7日担当は@Pctg-x8さんです．