What OSC_PARAM receives on KORG NTS-1

# What this note is for?
--> [Japanese version](https://qiita.com/mizba/items/e99b22e31fc1b17ea6ed)

Logue SDK custom oscillators can take up to 6 parameters which is controlled by B knob on KORG NTS-1. The custom oscillators can receive the value on `OSC_PARAM` function. It is documented on the [official doc](https://github.com/korginc/logue-sdk/tree/master/platform/nutekt-digital), but there looks some different behavior on the actual KORG NTS-1 devices.

In this note, I'll explain the actual values which your custom oscillator will receive on KORG NTS-1, including the un-documented part as well :) Please note that the undocumented part should be the subject to change.

## NTS-1 device info
I used the following device to check the behavior below.

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


## Values for Developers and Users
The parameter value on B knob will be passed to `OSC_PARAM`. Developers will see this value on their custom oscillators.
OTOH, there is 7-segment display on KORG NTS-1 as well. Users will see the values here.
There are mismatches on both numbers in some cases. In this note, I'm using the following format to explain the both values for developers and users.

| | min | zero | max |
|---|---|---|---|
| config | minimum value in `manifest.json` | - | maximum value in `manifest.json` |
| for devs | min value passed to `OSC_PARAM` | value passed to `OSC_PARAM` when we expect "0" | max value passed to `OSC_PARAM` |
| for users | min value on the 7-segment display | value on the display when we expect "0" | maximum value on the display |

The *italic value* means that there will be some mismatches on the expected values or any offsets on them.

# typeless values
It will be a typeless value if the 4th value of parameter definition is "" (empty) in `manifest.json`.

## Explanation in the public doc
 - 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.

## Actual behavior on NTS-1

### Case: minimum value = 0
This is not allowed based on the public doc, but it's possible to set. In this case, there will be a 1 offset. The `OSC_PARAM` will receive [0, 9], and users will see [1, 10] as documented.

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

### Case: minimum value > 0
This is a legit case based on the public doc. But surprisingly the offset, which is documented, is not applied in this case.

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

### Case: minimum value < 0
This is against the public doc though, it is possible to configure as well.
In this case, when the B knob points to the negative range, `OSC_PARAM` will receive the negative value. But **please note that you will need to cast the 2nd arg to `int16_t` since it is `uint16_t` in the `OSC_PARAM` template.**

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

In addition, it looks that the 7-segment display can't handle negative values correctly at this point. It shows `-999` for all negative values.

| | 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
It will be a perecntage value if the 4th value of parameter definition is "%" in `manifest.json`.


## Explanation in the public doc
There are no specific explanations about percentage values in the public doc. So it is assumed that the following general restriction will be applied:

 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.


## Actual behavior on NTS-1

### Case: minimum value = 0
In case of percentage values, there are no offsets like typeless values, and the raw values will be shown for users as well. Easy to understand :)

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


### Case: minimum value > 0
No offsets as well. Simple enough.

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

### Case: minimum value < 0
It is a legit configuration, but there looks some undocumented behavior on it.
The `OSC_PARAM` will receive values with a 100 offset. As the result, its value range starts from 0 and your module will receive positive values only. You will need to subtract 100 from the raw value to have the expected number.

In addition, the 7-segment display shows weird numbers as well. If the B knob points to any negative range (it means `OSC_PARAM` is receiving [0, 99]), the display always shows `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 |


On top of that, if the maximum value is a negative value, there'll be more weird behavior that the NTS-1 just ignore the maximum value and it will be considered to be set to 0. In the real use cases, I suppose it is not so usual to set negative values on both min and max values, so it won't be a big problem though.

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


# Appendix：k_user_osc_param_shape and  k_user_osc_param_shiftshape
It is documented that they will be passed as a 10-bit value. More particularly, it means `uint16_t` value in a range of 0-1023. In the sample code in the official repository, it converts the `int16_t` into `float` by `param_val_to_f32()`. It applies a fixed factor internally to covert 0-1024 integer into 0.0-1.0 floating point value. If you don't need to have 0.0-1.0 value in your custom module, it is not always needed to apply `param_val_to_f32()` it looks.

# Conclution
This note explains the actual values which your custom oscillator will receive on KORG NTS-1, including the un-documented part as well. Again, please note that the undocumented part should be the subject to change,.