Spresenseでつくるテルミンゆる楽器

# はじめに
みなさんは、テルミンという楽器をご存知でしょうか? 空間に手をかざすと
音階と音量がかわる不思議な楽器です。
これをSpresenseで作ってみようと思いました。

# Spresenseについて
Spresenseはスマートセンシングプロセッサ(CXD5602)が搭載されている
マイコンボードで、Spresense SDKに加えてArduino SDKでも開発が
できます。
私は、ソフトウェアは詳しい方ではないのでArduino SDKで開発を
しています。
詳しくはSpresenseのWebページをご覧ください
https://developer.sony.com/ja/develop/spresense/

# VL53L0Xについて
Stmicro社が開発した、Time-of-Flight方式の測距センサです。
目には見えませんが光をだしてその反射を測定する方式なので
スマホでこの写真を取ると、紫の光が撮影できます。
SPRESENSEとはI2Cで通信します。
![vl53l0x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3002095/141d9973-de54-6d1a-b617-a3393e99d33c.jpeg)


# Neopixelについて
シリアルコマンドにてLEDの光を変化させることができるデバイスで
R,G,Bの小さなLEDがデバイスに実装されて、それらの輝度を変える
ことで様々な色に変化させることができます。
SPRESENSEとはGPIOから制御します。
ライブラリは[ライゾマティクス](URL:https://rhizomatiks.com/work/)さんのエンジニアの方が
提供されており、ArduinoのSDKから簡単にダウンロード可能です。
![spresenseneopixel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3002095/8517b4eb-aff0-fb76-e598-77d9c860e0c1.png)


# Sound Signal Processing Library について
Spresenseで簡単に楽器を開発することが出来るライブラリで
MIDI規格をベースとしたAPIを実装しています。
詳しくは下記をご覧ください。
https://github.com/SonySemiconductorSolutions/ssih-music

# 構成概要図
SPRESENSEのメインボードと拡張ボードを使用します。
VL53L0XとはメインボードのI2Cと接続し
Neopixelとは今回は制御端子はD22(SEN_IRQ_IN)と接続します。
また、Neopixelは今回は別の用途で作成した5x5のマトリクス状に配置した
25個のNeopixelを使いました。
3.3Vの電源とGNDはメインボードと拡張ボードのそれぞれのピンヘッダから
供給しています。
![spresense-pinout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3002095/894d479e-eca0-b991-b077-67a9afd0378f.png)



# ソースコード
ベースはライブラリのサンプルファイルの[ButtonDrum.ino](URL:https://github.com/SonySemiconductorSolutions/ssih-music/blob/develop/examples/ButtonDrum/ButtonDrum.ino)が
スイッチに応じて音階が変わるプログラムなので、スイッチの部分を
測距センサの入力に応じたデータに変更をしました。

```c:spresense_yuru.ino
/*
 * SPDX-License-Identifier: (Apache-2.0 OR LGPL-2.1-or-later)
 *
 * Copyright 2022 Sony Semiconductor Solutions Corporation
 */

#ifndef ARDUINO_ARCH_SPRESENSE
#error "Board selection is wrong!!"
#endif
#ifdef SUBCORE
#error "Core selection is wrong!!"
#endif

#include <MemoryUtil.h>

#include <SDSink.h>

//sensor
#include <Wire.h>
#include <VL53L0X.h>

VL53L0X sensor;

//neopixel
#include <SpresenseNeoPixel.h>

// const uint16_t PIN = 6;
//const uint16_t PIN = 24;
const uint16_t PIN = 22; // 
const uint16_t NUM_PIXELS = 25;
SpresenseNeoPixel<PIN, NUM_PIXELS> neopixel;



// this file names are deifned middle C (60) as C4
const SDSink::Item table[12] = {
    {60, "SawLpf/60_C4.wav"},   // C4
    {61, "SawLpf/61_C#4.wav"},  // C#4
    {62, "SawLpf/62_D4.wav"},   // D4
    {63, "SawLpf/63_D#4.wav"},  // D#4
    {64, "SawLpf/64_E4.wav"},   // E4
    {65, "SawLpf/65_F4.wav"},   // F4
    {66, "SawLpf/66_F#4.wav"},  // F#4
    {67, "SawLpf/67_G4.wav"},   // G4
    {68, "SawLpf/68_G#4.wav"},  // G#4
    {69, "SawLpf/69_A4.wav"},   // A4
    {70, "SawLpf/70_A#4.wav"},  // A#4
    {71, "SawLpf/71_B4.wav"}    // B4
};
SDSink inst(table, 12);

int selector = 0;

int button4 = HIGH;
int button5 = HIGH;
int button6 = HIGH;

void setup() {
    // init built-in I/O
    Serial.begin(115200);
    pinMode(LED0, OUTPUT);
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    pinMode(LED3, OUTPUT);

    // init buttons
    pinMode(PIN_D04, INPUT_PULLUP);
    pinMode(PIN_D05, INPUT_PULLUP);
    pinMode(PIN_D06, INPUT_PULLUP);

    // initialize memory pool
//    initMemoryPools();
//    createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);

    // setup instrument
    if (!inst.begin()) {
        Serial.println("ERROR: init error.");
        while (true) {
            delay(1000);
        }
    }

    Serial.println("Ready to play DistDrum");

// VL53L0X setup
//  Serial.begin(9600);
   Wire.begin();

    sensor.setTimeout(500);
   if (!sensor.init())
   {
    Serial.println("Failed to detect and initialize sensor!");
    while (1) {}
    }

  // Start continuous back-to-back mode (take readings as
  // fast as possible).  To use continuous timed mode
  // instead, provide a desired inter-measurement period in
  // ms (e.g. sensor.startContinuous(100)).
    sensor.startContinuous();


//      inst.sendNoteOn(61, DEFAULT_VELOCITY, DEFAULT_CHANNEL);
//      inst.update();
//      delay(1000);
    neopixel.clear();
    neopixel.framerate(40); // default framerate is 40[fps]
    delay(1000);
    neopixel.set(0, 0, 0); //R,G,B

}

int note1 = INVALID_NOTE_NUMBER;
int pre_note1 = INVALID_NOTE_NUMBER;

void loop() {

//  Serial.print(sensor.readRangeContinuousMillimeters());
  if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }
//  Serial.println();

  int distance_input = sensor.readRangeContinuousMillimeters();

//測定している距離が一定の距離に入ったら音を出すようにする。以下は 300mm未満 
  if (distance_input < 300 ) {
    //距離を12等分したものを割って、小数点が切り捨てられるようにし60に加算
    //値が72になるとエラーし暴走してしまうので注意
    note1 = 60 + int(float(distance_input / (300 / 12.0)-0.5));
      
    Serial.println(note1);
    if(note1 != pre_note1){
      inst.sendNoteOff(pre_note1, DEFAULT_VELOCITY, DEFAULT_CHANNEL);
      inst.sendNoteOn(note1, DEFAULT_VELOCITY, DEFAULT_CHANNEL);
//      neopixel.set(1,50, 0, 0); //R,G,B
      
      switch (note1)
      {
        case 60:
            neopixel.set(50, 0, 0); //R,G,B
            break;
        case 61: 
            neopixel.set(0, 50, 0); //R,G,B
            break;
        case 62: 
            neopixel.set(0,  0, 50); //R,G,B
            break;
        case 63: 
            neopixel.set(50, 50, 0); //R,G,B
            break;
        case 64: 
            neopixel.set(0, 50, 50); //R,G,B
            break;
        case 65: 
            neopixel.set(50, 0, 50); //R,G,B
            break;
        case 66: 
            neopixel.set(0, 50, 50); //R,G,B
            break;
        case 67: 
            neopixel.set(50, 50, 50); //R,G,B
            break;
        case 68: 
            neopixel.set(25, 50, 0); //R,G,B
            break;
        case 69: 
            neopixel.set(0, 25, 50); //R,G,B
            break;
        case 70: 
            neopixel.set(25, 0, 50); //R,G,B
            break;
        case 71: 
            neopixel.set(50, 25, 0); //R,G,B
            break;
        default:
            neopixel.set(0, 0, 0); //R,G,B
            break;
      }

    neopixel.show();
    }

    pre_note1 = note1;
  }

    inst.update();
}
```



# 動作動画
<iframe width="640" height="360" src="https://www.youtube.com/embed/wz4HOAKyohU" title="Spresenseでつくるテルミンゆる楽器" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 最後に
Spresenseは小型ながらマルチコアのCPU搭載されており、それを使った楽器を作る
ライブラリが揃っているので、Arduinoで簡単に楽器が出来ると思います。
ライブラリ自体は和音の機能をもっているので、和音を出すことに挑戦してみたいと
思います。