LoginSignup
8
4

More than 3 years have passed since last update.

LINE BOT + ObnizでIoTアルペジエーターを作ってみた

Last updated at Posted at 2019-06-26

これはなに?

Botに対して、LINEメッセージでChord名を送信すると、そのChordの構成音を返してくれます。
同時に、そのChordをObnizがアルペジオで演奏してくれます。

動作の流れ

  1. ユーザはChord名を送信する(ここではC Minor・Seventh) image.png
  2. BotはChordの構成音を返す image.png
  3. ObnizがChordをアルペジオする image.png

動作の様子

構成図

image.png

使用したもの

ソースコード

  • node.jsでローカル起動し、ngrokでトンネリングします。
  • LINE BotのWebhook URLにngrokのURLを指定してください。
  • LINE BotのToken, SecretおよびObniz IDはjsファイルと同じ階層に置いたsecret.jsファイルに保持しています。
server.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const fs = require('fs');
const { promisify } = require('util');
const Obniz = require('obniz');
const PORT = 3000;
const SECRET_PATH = "secret.json";

const keyHzMapping = {
  'C': 261.626,
  'C#': 277.183,
  'Db': 277.183,
  'D': 293.665,
  'D#': 311.127,
  'Eb': 311.127,
  'E': 329.628,
  'F': 349.228,
  'F#': 369.994,
  'Gb': 369.994,
  'G': 391.995,
  'G#': 415.305,
  'Ab': 415.305,
  'A': 440,
  'A#': 466.164,
  'Bb': 466.164,
  'B': 493.883
};

async function main() {
  const { channelSecret, channelAccessToken, obnizId } = JSON.parse(await promisify(fs.readFile)(SECRET_PATH, 'utf-8'));
  const config = {
    channelSecret: channelSecret,
    channelAccessToken: channelAccessToken
  };
  let speaker;
  const obniz = new Obniz(obnizId);
  obniz.onconnect = async function () {
    speaker = obniz.wired("Speaker", { signal: 0, gnd: 1 })
    const id = setInterval(() => {
      const tones = queue.shift();
      if (tones) {
        playArpeggio(tones);
      }
    }, 3200);
  }
  const queue = [];
  async function playArpeggio(tones) {
    let hrzs = tones.map(tone => keyHzMapping[tone]);
    const duration = 600 / hrzs.length;
    hrzs.sort();
    await playSpeaker(hrzs[hrzs.length - 1], duration);
    for (let i = 0; i < 5; i++) {
      for (let hrz of hrzs) {
        await playSpeaker(hrz, duration);
      }
    }
    speaker.stop();
  }
  async function queueArpeggio(tones) {
    queue.push(tones);
  }
  async function playSpeaker(hertz, duration) {
    speaker.play(hertz);
    await obniz.wait(duration);
  }

  const app = express();
  app.get('/', (req, res) => res.send('Hello LINE BOT!'));
  app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    // 接続確認用
    if (req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
      res.send('Hello LINE BOT!(POST)');
      console.log('疎通確認用');
      return;
    }
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
  });

  const client = new line.Client(config);
  const waitingMessage = "調べています…";
  const chordDoesNotExistMessage = "そのコードは存在しません";
  async function handleEvent(event) {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return Promise.resolve(null);
    }
    // 非同期処理に入る前にプッシュメッセージ
    pushMessage(event.source.userId, waitingMessage);
    const tones = await getComposition(event.message.text);
    if (tones) {
      queueArpeggio(tones.split(','));
    }
    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: tones || chordDoesNotExistMessage
    });
  }

  const pushMessage = async (userId, message) => {
    await client.pushMessage(userId, {
      type: 'text',
      text: message,
    });
  }

  const chordApiUrl = 'https://api.uberchord.com/v1/chords/';
  const getComposition = async (chordName) => {
    const res = await axios.get(chordApiUrl + encodeURIComponent(chordName));
    return res.data.length ? res.data[0].tones : "";
  }

  app.listen(PORT);
  console.log(`Server running at ${PORT}`);
}

main();

工夫したところ

Chordが鳴っている最中にLINEで新たなChordを送信しても、割り込みが起きないようにしています。前の音が最後まで鳴ってから、次の音が鳴り始めます。
キューを用意してあり、送信されたChordは構成音に分解後、エンキューされます。
スピーカーを操作するfunctionは、3200msに1度Chordの構成音をデキューします。次の音を取り出すまでの3200msの間、そのChordをアルペジオし続けます。
これにより、Chordが途切れないようメッセージを送信し続けることで、曲を演奏しているかのように音を再生し続けることが可能になります。

8
4
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
8
4