M5StackでM5Piano

#M5Piano
　AliExpressで購入しました。
　とてもかわいいM5Stack公式のピアノボードです。公式サイトでの名前は「Acrylic Piano Board with RGB LED」と書かれています。M5Pianoが正しい名前なのかはわかりませんが、本体裏面には「M5.Piano」と大きく印字されています。

　M5Stack(私はM5Stack Basicを使用)の側面にピンを差し込んで使用します。

　2018年に発売されたもののようですが、日本では未発売のお品のようです。
![DSC_4599.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308578/9877c752-86d3-106a-3285-37e83685595a.jpeg)
　すべての鍵盤（白鍵も黒鍵も）にタッチセンサーが付いており、またすべてのキーの上部にLEDが付いています。
　LEDは均等に配置されているので黒鍵が無い場所（ミとファの間、シとドの間）にも余分にLEDが付いています。

#プログラム
　サンプルコードをネット検索で下記1件だけ見つけました。
　https://github.com/m5stack/Applications-M5PIANO
　起動時にLEDをすべて順に点灯させたあと消灯。鍵盤をタッチすると音が鳴り、タッチした鍵盤の上のLEDランプが光るプログラムです。

　ただしかしこれ爆音で音が割れるうえ、音が止まらなくなって暴走することしばしば。
　ということで上記ソースをベースに修正しました。

【修正／改造点】

* 音をボリューム指定できるように修正（プログラムでは3に設定しています）
下記サイト参考にさせていただきました。ありがとうございます。
  http://nuneno.cocolog-nifty.com/blog/2020/01/post-b9cd2c.html
* 黒鍵の音（半音）が白鍵の音と同じ音に設定されていたので修正
* キーを押したときに光るLEDの位置が最初のほうがおかしい点の修正
* 一部関数化

```C++
#include <M5Stack.h>
#include <Wire.h>

#include <FastLED.h>
// #include <Adafruit_NeoPixel.h>
#define LED_PIN            2

#define NUM_LEDS_PER_STRIP     29
CRGB Leds[NUM_LEDS_PER_STRIP];
// Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

#define LF        0x0A //New Line
#define CR        0x0D //Carriage  return
#define SP        0x20 //Spcae

#define Sensitivity_PWM1  0x00 //ch2,ch1
#define Sensitivity_PWM2  0x01 //ch4,ch3
#define Sensitivity_PWM3  0x02 //ch6,ch5
#define Sensitivity_PWM4  0x03 //ch21,ch7
#define Sensitivity_PWM5  0x04 //ch9,ch8
#define Sensitivity_PWM6  0x05 //ch11,ch10
#define Sensitivity_PWM7  0x06 //ch13,ch12
#define Sensitivity_PWM8  0x07 //ch15,ch14
#define Sensitivity_PWM9  0x08 //ch17,ch16
#define Sensitivity_PWM10 0x09 //ch19,ch18
#define Sensitivity_PWM11   0x0A //ch20

#define GTRL1     0x0B
#define GTRL2     0x0C

#define Cal_CTRL    0x0D

#define Port_CTRL1    0x0E
#define Port_CTRL2    0x0F
#define Port_CTRL3    0x10
#define Port_CTRL4    0x11
#define Port_CTRL5    0x12
#define Port_CTRL6    0x13

#define Cal_HOLD1   0x14
#define Cal_HOLD2   0x15
#define Cal_HOLD3   0x16

#define Err_CTRL    0x17

#define Output1     0x20
#define Output2     0x21
#define Output3     0x22

#define Ref_wr_H    0x23
#define Ref_wr_L    0x24
#define Ref_wr_CH1    0x25
#define Ref_wr_CH2    0x26
#define Ref_wr_CH3    0x27

#define Sen_RD_CTRL   0x28
#define Sensitivity_RD  0x29

#define Rd_CH1      0x30
#define Rd_CH2      0x31
#define Rd_CH3      0x32
#define Sen_H     0x33
#define Sen_L     0x34
#define Ref_H     0x35
#define Ref_L     0x36
#define Rd_CH1      0x37
#define Rd_CH2      0x38
#define Rd_CH3      0x39

#define TS20_SLAVE_GND  0x6A //0xD4<<1 //ADD Pin = GND
#define TS20_SLAVE_VDD  0x7A //0xF4<<1 //ADD Pin = VDD

void  Init_TS20(void); //Initialize TS20

#define RESET_PIN 7 //Reset pin
#define EN_PIN    6 //I2C Enable Pin
#define Set_Bit(val, bitn)    (val |=(1<<(bitn)))
#define Clr_Bit(val, bitn)     (val&=~(1<<(bitn)))
#define Get_Bit(val, bitn)    (val &(1<<(bitn)) )

byte key[4];
// #define pRed pixels.Color(5,0,0)
// #define pBlack pixels.Color(0,0,0)

uint16_t CurrentVolume = 3;    // 音量

void setup() {

  M5.begin();
  Wire.begin(26, 5);       // join i2c bus (address optional for master)
  Serial.begin(115200);  // start serial for output (Spped)
  delay(100); //wait for 100[msec]
  Init_TS20(); //Initialize TS20
  delay(100); //wait for 100[msec]
  key[0] = 0;
  key[1] = 0;
  key[2] = 0;
  key[3] = 0;
  
  // 一番最初に光る緑の電源LED
  pinMode(2, OUTPUT);  
  digitalWrite(2, HIGH);
  delay(500);
  digitalWrite(2, LOW);  // これが点いているとランプの位置がずれるのでOFFっておく

  // キーボードの上のLED
  FastLED.addLeds<NEOPIXEL, LED_PIN>(Leds, NUM_LEDS_PER_STRIP);
  FastLED.setBrightness(10);
  // pixels.begin();

  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(1, 10);
  M5.Lcd.setTextColor(TFT_YELLOW, TFT_BLACK);
  M5.Lcd.setTextSize(3);
  M5.Lcd.printf("M5PIANO");

  for (int i = 0; i < 29; i++)
  {
    Leds[i] = CRGB::Red;
    // pixels.setPixelColor(i, pRed);
    delay(20);
    FastLED.show();
  }
  delay(1000);
  for (int i = 0; i < 29; i++)
  {
    Leds[i] = CRGB::Black;
    // FastLED.setPixelColor(i, pBlack);
    delay(10);
    FastLED.show();
  }

  // Speaker初期化
  ledcSetup(TONE_PIN_CHANNEL, 0, 10);
  ledcAttachPin(SPEAKER_PIN, TONE_PIN_CHANNEL);
}

// toneEx
//  引数 
//   frequency (Hz)
//   duration 鳴動時間
void toneEx(uint16_t frequency, uint16_t duration){
  toneOn(frequency, CurrentVolume);
  delay(duration);
  toneOff();
}

// toneOn
//  引数 
//   frequency (Hz)
//   vol (0 ～ 9、0:無音 9:最大)
void toneOn(uint16_t frequency, uint16_t vol) {
  ledcSetup(TONE_PIN_CHANNEL, frequency, 10);
  ledcWrite(TONE_PIN_CHANNEL,0x1FF>>(9-vol));
}

// toneOff
void toneOff() {
  ledcWriteTone(TONE_PIN_CHANNEL, 0);
  digitalWrite(SPEAKER_PIN, 0);
}

#define NOTE_D0 -1      

#define NOTE_DL1 261    // frequency (Hz) ド
#define NOTE_DL1_ 277   // ド#
#define NOTE_DL2 293    // レ
#define NOTE_DL2_ 311   // レ#
#define NOTE_DL3 329    // ミ
#define NOTE_DL4 349    // ファ
#define NOTE_DL4_ 370   // ファ#
#define NOTE_DL5 392    // ソ
#define NOTE_DL5_ 415   // ソ#
#define NOTE_DL6 440    // ラ
#define NOTE_DL6_ 466   // ラ#
#define NOTE_DL7 494    // シ

#define NOTE_D1 523     // ド
#define NOTE_D1_ 554     // ド#
#define NOTE_D2 587     // レ
#define NOTE_D2_ 622     // レ#
#define NOTE_D3 659     // ミ
#define NOTE_D4 698     // ファ
#define NOTE_D4_ 740     // ファ#
#define NOTE_D5 784     // ソ
#define NOTE_D5_ 830     // ソ#
#define NOTE_D6 880     // ラ
#define NOTE_D6_ 932     // ラ#
#define NOTE_D7 988     // シ

#define NOTE_DH1 1046   // ド
#define NOTE_DH1_ 1108   // ド#
#define NOTE_DH2 1174   // レ
#define NOTE_DH2_ 1244   // レ#
#define NOTE_DH3 1318   // ミ
#define NOTE_DH4 1397   // ファ
#define NOTE_DH4_ 1480   // ファ#
#define NOTE_DH5 1568   // ソ
#define NOTE_DH5_ 1661   // ソ#
#define NOTE_DH6 1760   // ラ
#define NOTE_DH6_ 1864   // ラ#
#define NOTE_DH7 1975   // シ   

// キーのタッチ判定処理：LED・音
void setKeySound(int ledNo, int data, byte b, int keyCnt, byte setValue, uint16_t frequency){
  if ((data & b) == b) {
    Set_Bit(key[keyCnt], setValue);
    Leds[ledNo] = CRGB::Red;
    
    // 音の出し方を変更
    //M5.Speaker.tone(frequency, 20);
    toneEx(frequency, 20);  // 20ミリ秒ならす
    // 音ON
    //toneOn(frequency, CurrentVolume);
    
  } else {
    Leds[ledNo] = CRGB::Black;
    Clr_Bit(key[keyCnt], setValue);
  }
}

void loop() {

  int data0, data1, data2, data3, data4, data5;
  delay(5);

  Wire.beginTransmission(TS20_SLAVE_GND); // sned ic slave address
  Wire.write(byte(Output1)); // sends register address
  Wire.endTransmission(); // stop transmitting
  Wire.requestFrom(TS20_SLAVE_GND, 3); // key data read (3 byte)
  data0 = Wire.read();
  data1 = Wire.read();
  data2 = Wire.read();
  Wire.beginTransmission(TS20_SLAVE_VDD); // sned ic slave address
  Wire.write(byte(Output1)); // sends register address
  Wire.endTransmission(); // stop transmitting
  Wire.requestFrom(TS20_SLAVE_VDD, 3); // key data read (3 byte)
  data3 = Wire.read();
  data4 = Wire.read();
  data5 = Wire.read();


  M5.Lcd.setCursor(1, 50);
  M5.Lcd.printf("%d,%d,%d,%d,%d,%d   ", data0, data1, data2, data3, data4, data5);

  for (int i = 0; i < 29; i++)
  {
    Leds[i] = CRGB::Black;
    // pixels.setPixelColor(i, pBlack);
  }

  // // 最初にOFFる
  // toneOff();

  // キータッチ判定、LED/音
  setKeySound(0, data0, 0x10, 0, 0, NOTE_DL1); 
  setKeySound(1, data0, 0x20, 0, 1, NOTE_DL1_);
  setKeySound(2, data0, 0x40, 0, 2, NOTE_DL2);
  setKeySound(3, data1, 0x01, 0, 3, NOTE_DL2_);
  setKeySound(4, data1, 0x02, 0, 4, NOTE_DL3);
  setKeySound(6, data2, 0x04, 0, 5, NOTE_DL4);
  setKeySound(7, data2, 0x08, 0, 6, NOTE_DL4_);
  setKeySound(8, data2, 0x10, 0, 7, NOTE_DL5);
  setKeySound(9, data0, 0x01, 1, 0, NOTE_DL5_);
  setKeySound(10, data0, 0x02, 1, 1, NOTE_DL6);
  setKeySound(11, data0, 0x04, 1, 2, NOTE_DL6_);
  setKeySound(12, data0, 0x08, 1, 3, NOTE_DL7);
  
  setKeySound(14, data3, 0x10, 1, 4, NOTE_D1);
  setKeySound(15, data3, 0x20, 1, 5, NOTE_D1_);
  setKeySound(16, data3, 0x40, 1, 6, NOTE_D2);
  setKeySound(17, data4, 0x01, 1, 7, NOTE_D2_);
  setKeySound(18, data4, 0x02, 2, 0, NOTE_D3);
  setKeySound(20, data5, 0x02, 2, 1, NOTE_D4);
  setKeySound(21, data5, 0x04, 2, 2, NOTE_D4_); 
  setKeySound(22, data5, 0x08, 2, 3, NOTE_D5);
  setKeySound(23, data5, 0x10, 2, 4, NOTE_D5_);
  setKeySound(24, data3, 0x01, 2, 5, NOTE_D6);
  setKeySound(25, data3, 0x02, 2, 6, NOTE_D6_);
  setKeySound(26, data3, 0x04, 2, 7, NOTE_D7);
  
  setKeySound(28, data3, 0x08, 3, 0, NOTE_DH1);

  // 音をStartStopで鳴らすときはここをコメントに
  //delay(20);

  FastLED.show();

  M5.update();

}

void  Init_TS20(void)
{
  //------------------ Software Reset Enable (Set)----------------------
  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(GTRL2)); // sends register address
  Wire.write(byte(0x1A)); // sends register data
  Wire.endTransmission(); // stop transmitting

  //----------------- Port Control ------------------------------------
  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Port_CTRL1)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Port_CTRL2)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Port_CTRL3)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Port_CTRL4)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Port_CTRL5)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Port_CTRL6)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  //--------------- Sensitivty control,(threshold level)-----------
  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM1)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM2)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM3)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM4)); // sends register address
  Wire.write(byte(0xF5)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM5)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM6)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM7)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM8)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); /// send ic slave address
  Wire.write(byte(Sensitivity_PWM9)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Sensitivity_PWM10)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // sned ic slave address
  Wire.write(byte(Sensitivity_PWM11)); // sends register address
  Wire.write(byte(0x55)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(GTRL1)); // sends register address
  Wire.write(byte(0x4B)); // sends register data
  Wire.endTransmission(); // stop transmitting
  //------------------ Calibration On ---------------------------
  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Cal_HOLD1)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Cal_HOLD2)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Cal_HOLD3)); // sends register address
  Wire.write(byte(0x00)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Err_CTRL)); // sends register address
  Wire.write(byte(0x0D)); // sends register data
  Wire.endTransmission(); // stop transmitting

  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(Cal_CTRL)); // sends register address
  Wire.write(byte(0xFA)); // sends register data
  Wire.endTransmission(); // stop transmitting

  //------------------ Software Reset Disable (Clear) -----------------------
  Wire.beginTransmission(TS20_SLAVE_GND); // send ic slave address
  Wire.write(byte(GTRL2)); // sends register address
  Wire.write(byte(0x12)); // sends register data
  Wire.endTransmission(); // stop transmitting
}
```

　サンプルプログラムからそうだったのですが、音がピピピピと少し途切れ途切れになって連続した音になりません。
　これはloop()のときに200ミリ秒の音を再生して、次またループに入ったときに200ミリ秒の音を再生して…ってやってるので、ループごとに途切れてしまうためです。

　本当はドのキーを押したときにドを出し続けて、手を離したときにドの音を止めたいですが、内部では「音を全部止める」しかできない感じになっているので、例えば和音ドミソ出しててドだけ止めようと思ったら、全部止めてからミソを出す、という風にしか書けないのでどうしても一瞬切れてしまう瞬間があるんです。

　それでもその切れ目をなるべくなくそうと思い、音を止めるタイミングをループをまたがないようできないかとか色々工夫してみたんですが、そうすると和音が出せなくなってしまったりと色々問題が発生してしまったので、音が連続しない件はそのままとしました。