KORG NTS-1 で OSC_PARAM に渡ってくる値の実際


# はじめに
--> [English version](https://qiita.com/mizba/items/3b5990389350828433f2)

KORG NTS-1 上で動く Logue SDK カスタム・オシレータは、Bノブでコントロールできるパラメータを6つまで取ることができます。このパラメータ値は `OSC_PARAM` 関数を経由してカスタム・オシレータに渡されます。この挙動は[公式のドキュメント](https://github.com/korginc/logue-sdk/tree/master/platform/nutekt-digital)で説明されていますが、実機上の動きとは若干異なるようです。

この文書ではドキュメントされていない範囲も含めて NTS-1 実機上のパラメータの挙動を説明します。ドキュメントされていない挙動に関しては今後変更される可能性がある点はご留意ください。

## NTS-1 検証環境
以下の環境を使用して検証しました。

```sh
$ ./logue-cli probe
> Device: nutekt digital
> System version: 1.02
> Logue API version: 1.01-0
> Available modules:
...
```


## 開発者から見える値とユーザから見える値
NTS-1 実機のBノブで指定されたパラメータ値は `OSC_PARAM` に渡されます。開発者が見るのはこの数値です。
一方、実機上の7セグメントディスプレイにもBノブの設定値が表示されます。ユーザはこの数値を見ることになります。
残念ながらこの２つの数字は必ずしも一致しないので、以下ではこの２つを分けて以下のような表で説明します。

| | min | zero | max |
|---|---|---|---|
| config | `manifest.json` 内の min 値 | - | 同じく max 値 |
| for devs | `OSC_PARAM` に渡る最小値 | 設定上 0 を期待するとき`OSC_PARAM` に渡る値 | `OSC_PARAM` に渡る最大値 |
| for users | 7セグに表示される最小値 | 設定上 0 を期待するときにユーザーが見る値 | 7セグに表示される最大値 |

また、オフセットが適用されたり内部表現と違う値が表示される場合などは *斜体で表示しています*。

# typeless values
`manifest.json` 内のパラメータ定義の第四引数が "" （空文字）の場合は typeless values として扱われます。

## ドキュメントでの説明
 - In the case of typeless values, the minimum and maximum values should be positive.
 - Also the displayed value will be offset by 1 such that a 0-9 range will be shown as 1-10 to the minilogue xd user.

## NTS-1 実機上の挙動

### minimum value が 0 の場合
いきなりドキュメントの説明に反するケースですが、minimum value を 0 に設定することができます。この場合はドキュメントにある通り `OSC_PARAM` には [0, 9] が渡り、ユーザには1つオフセットされた [1, 10] が表示されます。

| | min | zero | max |
|---|---|---|---|
| config | 0 | - | 9 |
| for devs | 0 | 0 | 9 |
| for users | *1* | *1* | *10* |

### minimum value が正値の場合
ドキュメントに則った正しいケースですが、なんとこの場合はオフセットが適用されません。なんと。

| | min | zero | max |
|---|---|---|---|
| config | 20 | - | 30 |
| for devs | 20 | - | 30 |
| for users | 20 | - | 30 |

### minimum value が負値の場合
ドキュメントに反するケースですが、実際には minimum value に負値を設定することができます。
この場合、Bノブの位置が負値に相当する場合は `OSC_PARAM` にも負値が渡りますが **テンプレートでは第二引数の型が `uint16_t` となっているため内部で `int16_t` にキャストする必要があります**。

```cpp
void OSC_PARAM(uint16_t index, uint16_t value) {
  const int16_t value_i = (int16_t) value;
```

もう一つ、現状では7セグは負値をうまく扱えていないようで、typeless values の負値はすべて `-999` で表示されます。

| | min | zero | max |
|---|---|---|---|
| config | -100 | - | 100 |
| for devs | -100 | 0 | 100 |
| for users | *-999* | 0 | 100 |

| | min | zero | max |
|---|---|---|---|
| config | -30 | - | 20 |
| for devs | -30 | 0 | 20 |
| for users | *-999* | 0 | 20 |


# percentage values
`manifest.json` 内のパラメータ定義の第四引数が "%" の場合は percentage values として扱われます。

## ドキュメントでの説明
ドキュメントでは percentage values だけに適用される具体的な説明はありません。
以下の一般的な制約が適用されるはずです。

 1. minimum value (int) : Value should be in -100,100 range.
 1. maximum value (int) : Value should be in -100,100 range and greater than minimum value.


## NTS-1 実機上の挙動

### minimum value が 0 の場合
percentage values の場合は typeless values のようなオフセットは適用されず、生の値がそのまま表示されます。
わかりやすい。

| | min | zero | max |
|---|---|---|---|
| config | 0 | - | 9 |
| for devs | 0 | 0 | 9 |
| for users | 0 | 0 | 9 |


### minimum value が正値の場合
こちらも特にオフセットは適用されません。
こちらもわかりやすい。

| | min | zero | max |
|---|---|---|---|
| config | 20 | - | 30 |
| for devs | 20 | - | 30 |
| for users | 20 | - | 30 |

### minimum value が負値の場合
ドキュメント上も許された設定ですが、ドキュメントには書かれていない挙動が現れます。
まず `OSC_PARAM` に渡る値に100オフセットが適用されます。これにより、負値が渡ってくることはなくなりますが、負値を期待している場合はこの100を内部で差し引いて扱う必要があります。

さらに、7セグの表示もおかしくなります。Bノブが負値に相当する場合、`OSC_PARAM` では [0, 99] が渡ってくる場合に、7セグの表示がすべて `999` になります。

| | min | zero | max |
|---|---|---|---|
| config | -100 | - | 100 |
| for devs | *0* | *100* | *200* |
| for users | *999* | 0 | 100 |

| | min | zero | max |
|---|---|---|---|
| config | -30 | - | 20 |
| for devs | *70* | *100* | *120* |
| for users | *999* | 0 | 20 |

さらに maximum value も負値の場合はさらに不思議な挙動になります。maximum value が無視されて、0 を指定した場合と同様な振る舞いになります。実用上 min と max に負値を設定するケースはあまりないと思うのであまり問題にはならないとは思いますが……。

| | min | zero | max |
|---|---|---|---|
| config | -30 | - | -20 |
| for devs | *70* | - | *100* |
| for users | *999* | - | *0* |


# おまけ：k_user_osc_param_shape と k_user_osc_param_shiftshape
こちらは 10-bit の値が渡ってくるとされています。これは  0-1023 の `uint16_t` 値が渡ってくるようです。サンプルコードでは `param_val_to_f32()` で `float` に変換していますが、これは 0-1023 を 0.0-1.0 の `float` 値に変換するための係数を掛けているようです。実装によっては必ずしも `param_val_to_f32()` を呼ぶ必要はなくて、0-1023 をそのまま使っても良さそうです。

# 最後に
発音中にカスタム・オシレータをコントロールする際に重要なBノブのユーザ・パラメータの挙動を、ドキュメントされていない範囲も含めてまとめました。繰り返しになりますが、ドキュメントされていない挙動に関しては今後変更される可能性がある点はご留意ください。