KORG NTS-1 の audio out でデジタル通信して内部をのぞく方法 :: オシレータ編


# はじめに

Logue SDK を使うと KORG NTS-1 上で動くカスタム・モジュールを作ることができます。しかし、デバッグ環境などがまだそれほど充実していないので、実機上の動作を手探りで見つける必要に迫られる場合もあります。

このポストでは、KORG NTS-1 からの唯一の出力端子 (*1) である audio out を利用して、実機上のカスタム・オシレータからデジタル・データを読み出せるようにする方法を解説します。具体的には、カスタム・オシレータ内でマンチェスタ符号化したデータを音声信号の HIGH/LOW として出力し、それを Arduino で受信、デコードすることでデータを読み出します。

これを使うことで、`shape_lfo` の値や実機上に予め用意された波形など、Github で公開されている Logue SDK のコードやドキュメントだけでは追いきれない NTS-1 の実際の挙動を数字で確かめることが可能となります。

(*1 : 箱から出した状態では唯一ですが、カスタムボードを使ったり、標準にこっそり用意されているっぽい SWD を使ったりしたらその限りではないです）

## できること

- 通信用にコンパイルしたカスタム・オシレータがアクセスできるデータをデジタルデータとして読み出せます
 - 通信用カスタム・オシレータに渡ってくる値
 - 通信用カスタム・オシレータから呼ぶことのできる関数の戻り値
 - 通信用カスタム・オシレータからアクセスできる NTS-1 上のメモリの内容

## できないこと

- 他のモジュールの内容や挙動を取得する
- 演奏中のデータを取得する
 - audio out を利用するので、通信中の NTS-1 は実際の演奏には使えません
- 48,000 Hz のデータをリアルタイムにやりとりする（通信速度は遥かに遅いです）
- 通信エラーのない信頼性の高い通信

## 必要なもの

- KORG NTS-1 x1
- Arduino Uno x1
- 10kΩ 抵抗 x1
- 不要な 3.5mm ステレオミニプラグとそれ用の配線 x1
- ブレッドボードなど上記を接続するためのもの x1
- Arduino の `Serial.print` の結果を表示する何らかの手段（ラップトップなど） x1

## 動作環境

以下の環境で動作を確認しています。

### MacOS
```sh
$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.2
BuildVersion:   19C57
```

### KORG NTS-1 / logue-cli
```sh
$ ./logue-cli probe -i 0 -o 0
> Device: nutekt digital
> System version: 1.03
> Logue API version: 1.01-0
> Available modules:
```

## 余談 : 通信速度、通信方式など

ここは内部の挙動などの話なので、ひとまず手っ取り早く通信モジュールをセットアップしたい方は読み飛ばして構いません。

KORG NTS-1 のサンプリング周波数は 48,000 Hz なので、原理上は 48 kbps の通信に使うことができるはずです。量子化ビット数はわかりませんが、HIGH と LOW の間の値を使うことで 1 クロックにさらに詰め込むことも可能なはずです。しかし、本来音声出力用の端子にデジタル信号を載せることによる制約であったり、Arduino 側でのデコード処理および Arduino と表示端末との間の通信などの要因で、今回の通信モジュールは遥かに遅い 557 bps 程度となりました。

このポストで解説する通信モジュールの信号レベルは、シンプルに HIGH と LOW だけを使っています。カスタム・オシレータ内ではそれぞれ +0.99 と -0.99 を割り当てています。

内部的には、NTS-1 が一秒間に扱う 48,000 個のフレームを 40 ずつまとめ、それを通信における 1 クロックとして扱っています。よってまずは 48,000 / 40 = 1,200 クロック となります。さらに NTS-1 の audio out は HIGH や LOW が連続すると信号を通さなくなるようなので、これを避けるためにマンチェスタ符号を利用しています。マンチェスタ符号は 1 bit の表現に 2 クロック必要になるので 1,200 / 2 = 600 bit/s。それに加えて、1 ワード（今回は 32bit としました）ごとに NTS-1 と Arduino で同期をとるための信号を 5 クロック分加えているため、その分を除くとおおよそ 557 bps となりました。

# 通信環境の構築

KORG NTS-1 と Arduino とでデジタル通信を行うには以下のステップが必要となります。

- デバイスの接続
- 受信モジュールを Arduino にデプロイする
- 送信モジュールを NTS-1 にデプロイする

NTS-1 と Arduino で動かすモジュールのコードはこちらで公開しています。

- [logue-sdk-custom-modules/serial_comm](https://github.com/miz884/logue-sdk-custom-modules/tree/master/serial_comm)

## デバイスの接続 

用意したものを以下のように、Sleeve を Arduino の GND、Tip か Ring のいずれかを 10kΩ 抵抗を通して Arduino の A5 に接続します。

![nts-1_serial_ comm_bb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118471/f60b1ed7-b43f-365c-d9ab-32bed64db33d.png)


回路図では 3.5mm のオーディオ・ジャックとして表現していますが、実際には 3.5mm ステレオミニプラグの配線を剥いて直接接続する形で接続しました。このやり方だとあまりにも雑すぎて回路図を描くツールが対応していなかったため、図ではオーディオ・ジャックを経由する表現となっています。直接接続でも実質は変わりません。

## 受信モジュールを Arduino にデプロイする

Arduino 側の受信モジュールは以下の場所にあります。

- [受信モジュールのソースコード](https://github.com/miz884/logue-sdk-custom-modules/blob/master/serial_comm/arduino_nts-1_serial_comm/arduino_nts-1_serial_comm.ino)

これをコンパイルし Arduino にインストールします。

## 送信モジュールを NTS-1 にデプロイする

KORG NTS-1 側の送信モジュールのコア部分は以下のディレクトリに置いてある[`serial_comm.cpp`](https://github.com/miz884/logue-sdk-custom-modules/blob/master/serial_comm/logue_nts-1_serial_comm/osc/serial_comm.cpp) です。

- [送信モジュールのソースコード](https://github.com/miz884/logue-sdk-custom-modules/tree/master/serial_comm/logue_nts-1_serial_comm/osc)

これはデータのエンコードや同期の処理などの通信処理のみを行うコードで、実際に送信するデータを準備する部分は用途に合わせて準備する必要があります。例えば、ノブを操作した際にカスタム・オシレータにどんな値が渡るのかを調べたときの送信データは [`serial_comm_knobs.cpp`](https://github.com/miz884/logue-sdk-custom-modules/blob/master/serial_comm/logue_nts-1_serial_comm/osc/serial_comm_knobs.cpp) で扱っています。

```cpp
#include "serial_comm.h"

typedef struct State {
  Message message;
} State;

static State state;

void init_message() {
  state.message.ui32 = 0L;
}

void update_message(const user_osc_param_t *params,
                   int32_t *yn,
                   const uint32_t buf_len) {  
  (void) params;
}

Message get_next_message() {
  return state.message;
}

void OSC_PARAM(uint16_t index, uint16_t value) {
  state.message.ui32 = value;
}
```

このように、何らかのイベント（ここでは `OSC_PARAM`）が発生した際にその値をとっておいて（`state.message`）、必要に応じて加工し、次の通信データを要求する `get_next_message()` が呼ばれた際にそれを返すようにします。それ以降の通信に関する処理は [`serial_comm.cpp`](https://github.com/miz884/logue-sdk-custom-modules/blob/master/serial_comm/logue_nts-1_serial_comm/osc/serial_comm.cpp) が扱います。ひとつのメッセージを送信し終えると再度 `get_next_message()` が呼ばれるので、次に送りたいデータを返します。

このカスタム・オシレータは以下のようにコンパイルできます。

```sh
$ make PROJECT_MK=project_knobs.mk 
```

問題なくコンパイルできたら、次のように NTS-1 実機にインストールします。

```sh
./logue-cli load -m osc -o 0 -i 0 -s 1 -u ~/path/to/serial_comm.ntkdigunit
```

`-o 0 -i 0` の部分は環境により異なる場合があるので、必要に応じて調整してください。適切な設定値がわからない場合はこちらを参考にしてください。

- [logue-cli が KORG NTS-1 (v1.03) に接続失敗する場合の対処法](https://qiita.com/mizba/items/a11b8c280e2b84e442e6)


# 動かしてみる

ここまでで通信の準備が完了しました。実際に動かしてみましょう。

## 受信側の起動

Arduino はホスト側のマシンに接続し、Arduino の開発環境から Serial Monitor を起動しておきます。

<img width="582" alt="Screen Shot 2020-01-30 at 0.09.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118471/6c1f2bda-d966-23df-eaad-d32ac5300a40.png">


## 送信側の起動

送信側の NTS-1 もいくつかの下準備が必要になります。

1. TRS プラグを audio out に接続する。Arduino 側にオーディオ・ジャックを使用している場合は TSR のオスオスで NTS-1 の audio out とArduino を接続します。
1. OSC で通信用のカスタムモジュールを選択する。
1. FILTER, MOD, DELAY, REVERB は Off にしておく。
1. EG は Open に設定する。こうすることで note on / off に関係なくオシレータが呼ばれ発音します。
1. 音量を最大にする。これは TRS プラグがきちんと接続されていることを確認してからのほうが良いです。

## 受信データの確認

以上の準備が完了すると、先程起動した Serial Monitor 上に通信されたデータが表示されているはずです。およそ 557 bps なので、一秒あたり 17 行ほどのデータが表示されます。

```
0	0	0	0.0000000000	
2	2	2	0.0000000000	
6	6	6	0.0000000000	
B	11	11	0.0000000000	
10	16	16	0.0000000000	
17	23	23	0.0000000000	
1E	30	30	0.0000000000	
26	38	38	0.0000000000	
2F	47	47	0.0000000000	
36	54	54	0.0000000000	
3B	59	59	0.0000000000	
40	64	64	0.0000000000	
43	67	67	0.0000000000	
...
```

# まとめ 

以上で、通信用にコンパイルされたカスタム・オシレータが NTS-1 から audio out に書き出した値を、Arduino 経由で Serial Monitor 上に表示することができました。残念ながら演奏など通常動作中のモジュールをリアルタイムにモニターする事はできませんが、カスタム・オシレータが実機上でどういった値を渡されて動作する必要があるのかを探るには便利かと思います。

同様に modfx など別タイプのカスタム・モジュールからもデジタル通信を行うことも原理上は可能なはずです。今後そういったあたりも実装したらまた解説します。