LoginSignup
6
0

More than 1 year has passed since last update.

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

Posted at

はじめに

みなさんは、テルミンという楽器をご存知でしょうか? 空間に手をかざすと
音階と音量がかわる不思議な楽器です。
これを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

Neopixelについて

シリアルコマンドにてLEDの光を変化させることができるデバイスで
R,G,Bの小さなLEDがデバイスに実装されて、それらの輝度を変える
ことで様々な色に変化させることができます。
SPRESENSEとはGPIOから制御します。
ライブラリはライゾマティクスさんのエンジニアの方が
提供されており、ArduinoのSDKから簡単にダウンロード可能です。
spresenseneopixel.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

ソースコード

ベースはライブラリのサンプルファイルのButtonDrum.ino
スイッチに応じて音階が変わるプログラムなので、スイッチの部分を
測距センサの入力に応じたデータに変更をしました。

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();
}

動作動画

最後に

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

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0