Arduino: analogRead()を指数平滑するライブラリを作り直した

![P_20200702_135811.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61597/f9c3933f-10cc-b1f2-907a-ab686d1341dd.jpeg)


MIDIフェーダーが必要になったので試作したのですが、フェーダーが止っているときの値がどうも安定しません。
[以前作ったライブラリ](https://qiita.com/ttatsf/items/99dd40d137f2659fb27b)が、真面目に使ってみると意外にダメダメなことが露見した次第。
新しいのを作りました。

```c++:Smooth.h
#ifndef SMOOTH_H
#define SMOOTH_H
#include <Arduino.h>

class Smooth{
  private:
    const double RATE;
    double previous;
  public:
    Smooth(double rate);
    double operator()(long current);
};

class SchmittRound{
    private:
        long previous;
    public:
        SchmittRound();
        long operator()(double current);
};

class SchmittCeil{
    private:
        long previous;
    public:
        SchmittCeil();
        long operator()(double current);
};

class SchmittFloor{
    private:
        long previous;
    public:
        SchmittFloor();
        long operator()(double current);
};

class IsCHANGED{
  private:
    long previous;
  public:
    IsCHANGED();
    boolean operator()(long current);
};

class IsINCREASED{
  private:
    long previous;
  public:
    IsINCREASED();
    boolean operator()(long current);
};

class IsDECREASED{
  private:
    long previous;
  public:
    IsDECREASED();
    boolean operator()(long current);
};

#endif
```

```C++:Smooth.cpp
#include "Smooth.h"

Smooth::Smooth(double rate): RATE(rate)
        , previous(0.0) {}

double Smooth::operator()(long current){
  return previous = previous + RATE * ( double(current) - previous ) ;
}

SchmittRound::SchmittRound(): previous(0.0) {
}

long SchmittRound::operator()(double current){
    if( abs( current - previous ) < 0.75 ) return previous;
    return previous = long(round(current));
}

SchmittCeil::SchmittCeil(): previous(0.0){
}

long SchmittCeil::operator()(double current){
    const auto DIFF = current - previous;
    if( -1.0 < DIFF &&  DIFF  < 0.5 ) return previous;
    return previous = long(ceil(current));
}

SchmittFloor::SchmittFloor(): previous(0.0) {
}

long SchmittFloor::operator()(double current){
    const auto DIFF = current - previous;
    if( -0.5 < DIFF &&  DIFF  < 1.0  ) return previous;
    return previous = long(floor(current));
}

IsCHANGED::IsCHANGED(): previous(0) {
}

boolean IsCHANGED::operator()(long current) {
  const boolean IS_CHANGED = current != previous;
  previous = current;
  return IS_CHANGED;
}

IsINCREASED::IsINCREASED(): previous(0) {
}

boolean IsINCREASED::operator()(long current) {
  const boolean IS_INCREASED = current > previous;
  previous = current;
  return IS_INCREASED;
}

IsDECREASED::IsDECREASED(): previous(0) {
}

boolean IsDECREASED::operator()(long current) {
  const boolean IS_DECREASED = current < previous;
  previous = current;
  return IS_DECREASED;
}
```

# 変更したところ
### doubleで計算する
以前のやつは、なるべく処理が軽くなるように平滑の計算を整数の領域でやってました。
midiを出すだけだと、別にdouble使ったから処理が滞るってわけでもないのでそこはいいかなと。
それにともなって、補間のレートも以前の (int)0~100 から (double)0.0~1.0 に変更しました。

### シュミットトリガー的な動作を取り入れる
値が少し大きく変化しないと隣の整数に移行しないようにしてみました。
丸めのしかたによって、round, ceil, floor に対応した関数があります。
それぞれの丸めの範囲を仮に[-0.5, 0.5)(小数点以下四捨五入の場合)のように表わすと：

+ round: [-0.5, 0.5) -> SchmittRound : (-0.75, 0.75) 
+ ceil : (-1, 0] -> SchmittCeil : (-1, 0.5)
+ floor : [0, 1) -> SchmittFloor : (-0.5, 1)

となります。

# 以前のと比較してみた

A0に一個だけVRをつけて、analogReadしてみました。

<img width="866" alt="スクリーンショット 2020-07-03 13.57.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61597/1c1def16-fcec-a9b6-7824-f2479c2d7066.png">

+ 青がanalogReadした生データ、 
+ 緑がSmoothしてschmittRoundしたもの、
+ 紫がExponentialSmoothしたもの

です。
青の変動に紫がときどき動いちゃってるのに対して、緑はまったく動じてません。
いいぞ。

# 作ったもの
大量に50kAのVRがあるのでそれを使います。
MIDIフェーダーはBカーブじゃないとちょっと使いにくいので、Arduino上で変換してBカーブに近付けてます。

```c++:MIDI_6_fader_uno_smooth
// MIDI faders using A-curve VRs to make B-curve outputs.
// input pins : A0 ~ A5
// output pin : 1 (Serial TX) for DIN MIDI OUT, or you can use USB MIDI with MOCO-LUFA.
// output MIDI CC: 25 ~ 30 


#include <Smooth.h>

constexpr byte CHANNEL = 0;
constexpr double REACT_RATE = 0.2;

constexpr auto INS_N = 6;

struct Fader{
  const int PIN;
  const byte CC;
  Smooth smooth;
  SchmittFloor schmittFloor;
  IsCHANGED isCHANGED;
  
  Fader(int pin, byte cc) : PIN(pin)
    , CC(cc)
    , smooth( REACT_RATE )
    , schmittFloor()
    , isCHANGED()
    {}
};

Fader FADERS[ INS_N ] {
  Fader(0, 25)
  , Fader(1, 26)
  , Fader(2, 27)
  , Fader(3, 28)
  , Fader(4, 29)
  , Fader(5, 30)
};

long mapAToB(long v ) {
  if(v <=  14) return map(v,   0,   17,   0,  136);
  if(v <=  39) return map(v,  17,   39, 136,  290);
  if(v <= 188) return map(v,  39,  189, 290,  515);
  if(v <= 479) return map(v, 189,  479, 515,  752);
  if(v <= 991) return map(v, 479,  991, 752,  991);
  return v;
}

int doWhenChanged(Fader& f, const long DATA ) {
  if( f.isCHANGED(DATA) ){
    txMidi( 0xB0|CHANNEL, f.CC, byte(DATA) );
  }
  return DATA;
}

void txMidi(const byte& MESSAGE, const byte& CC_NUM, const byte& VALUE){

    Serial.write( MESSAGE );
    Serial.write( CC_NUM );
    Serial.write( VALUE );
    Serial.flush();
}

void setup() {
  Serial.begin(31250);
}

void loop() {
  for( auto&& f : FADERS ){
    doWhenChanged(f,  
      f.schmittFloor(  
        f.smooth( mapAToB( analogRead(f.PIN) ) ) / 8.0 
      ) 
    );  
  }
  delay(2);
}
```
これで、フェーダーを動かしたときだけCCを出力し、動かしてないときは出力しないようになりました。ちゃんとしたMIDIコントローラーになりそうです。

こういったものの需要があるかは?ですが、よろしければお使いください。
[Smooth](https://github.com/ttatsf/Smooth)