CRUD Lab. Tech Blog

ギークス株式会社の沖縄開発拠点CRUDLab. (クラッドラボ)のIT技術(テック)ブログ。kintoneやAngularJSによる開発を行っています。

kintoneでステップシーケンサーを作ってみた

0. 目次


1. 概要

ギークス株式会社CRUD Lab.の、ATSUSHIです。 今回はkintoneとJavaScriptカスタマイズを組み合わせて楽器をつくってしまおうと思います。

業務効率化とかそういった仕事に使える仕組みではないですが、 kintoneでどこまでできるか試していきたいので、今後もこういった記事を定期的にUPしていければと思います。

2. ステップシーケンサーの仕組み

ステップシーケンサーとは一般的に、一定間隔(ステップ)で音符を配置してそれらを元に音源を自動再生する音楽機器のことを指します。

この仕組みでドラムやベースを実装しリズムパートとして利用することが多いです。

f:id:crud_lab_editor:20160310001133p:plain

今回はシンプルに以下の仕様で実装を行います。

  • loop機能なし
  • 音源はピアノ、1オクターブのみ
  • BPM変更可能

<参考>

3. html5でのサウンドプログラミング

html5の仕様でaudio/videoタグが追加されました。 それまではembedタグを利用して外部プレーヤー(ex. flashプレイヤー,Quicktimeプレーヤー)で音源を再生させていました。

現在では、ブラウザ互換の課題はあるものの、JavaScript APIである、「Web Audio API」「Web MIDI API」などをタグと併用してかなり高レベルの音楽表現ができるように なっています。

音楽関連のJavaScriptライブラリとして有名なのが timbre.jsですが、 今回はシンプルに音源を読み込み、kintoneのUIで設定されたステップ情報をよみ、対応した音源を再生させる仕組みをつくっていきます。

3. 実装

それでは、実装します。 音声が流れないので、イメージしずらいかも知れませんが以下のようなプレイヤーを構築します。

f:id:crud_lab_editor:20160310001024g:plain

アプリの設定

スペースを利用して再生ボタンと、再生される音源のaudioタグを貼り付ける領域を定義します。
ステップの部分はラジオボタンを縦レイアウトで8個作成します。(2小節分のステップ情報)
BPMの設定はドロップダウンを利用します。

フィールド名 フィールドコード 初期値 フィールドコードタイプ/その他
タイトル title -- 文字列(1行) /
BPM bpm 120 ドロップダウン /60,80,120,150,240を設定
- play_button -- スペース / 再生ボタンを配置するためのスペース
- samples -- スペース / audioタグを配置するためのスペース
1〜8 seq_1〜seq_2 C ラジオボタン(縦) / 1オクターブの情報を保持

JavaScriptのコーディング

アプリ設定「詳細設定」タブを開き、「JavaScript / CSSでカスタマイズ」>「スマートフォン用のJavaScriptファイル」に以下のファイルをリンクするようにしてください。

'use strict';

// 必要なjsライブラリをインクルード
document.write("<script type='text/JavaScript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>");

// レコードの新規作成、編集時に処理を実行する
kintone.events.on(['app.record.create.show','app.record.edit.show','app.record.detail.show'], function(event){

    // 音階のサウンドファイルをインクルード
    var samples = document.createElement('div');
    samples.id   = "samples";
    kintone.app.record.getSpaceElement("samples").appendChild(samples);
    
    var base_url = "https://kintone-dev-test-xxxxxx.io/"; // ファイルパス
    var audio_tags = '';
    audio_tags += '<audio src="'+base_url+'do.mp3" preload="auto" id="key_C" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'re.mp3" preload="auto" id="key_D" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'mi.mp3" preload="auto" id="key_E" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'fa.mp3" preload="auto" id="key_F" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'so.mp3" preload="auto" id="key_G" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'ra.mp3" preload="auto" id="key_A" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'si.mp3" preload="auto" id="key_B" class="note"></audio>';
    audio_tags += '<audio src="'+base_url+'do.mp3" preload="auto" id="key_C" class="note"></audio>';
    $(audio_tags).appendTo('#samples');
    $('#samples').css({visiblity:'hidden'});
    
    // ボタン
    var play_button = document.createElement('div');
    play_button.id   = "play_button";
    kintone.app.record.getSpaceElement("play_button").appendChild(play_button);
    var button_tags = '<button id="play_button">再生</button>';
    $(button_tags).appendTo('#play_button');
    $('#play_button').css({margin:'16px'});

    $('#play_button').on('click',function() {
        
        var  record = kintone.app.record.get().record;
        
        var bpm = parseInt(record["bpm"].value,10); // 入力値
        var tempo_ms = (60/bpm)*1000;
        console.log(tempo_ms);
        
        var sequence  = [];
        
        for(var i=1; i<=8; i++){
            sequence.push(record["seq_"+i].value);
        }
        console.log(sequence);
        
        var sound_obj = document.getElementsByClassName('note');
        var tmp_sound_obj = null;
        var interval_id = setInterval(function(){
    
            var note = sequence[0];
            var sound_obj = document.getElementById('key_'+note);
            sound_obj.currentTime=0;
            sound_obj.play();
            tmp_sound_obj = sound_obj;
            
            sequence.shift();
            console.log(sequence);
            
        },tempo_ms);
        
        setTimeout(function () {
            clearInterval(interval_id);
            console.log('loop has stopped after ' + (tempo_ms) + ' msec!');
        }, tempo_ms*sequence.length);
    
    });
});
    

補足説明

アプリの詳細表示/新規作成/編集のタイミングで再生が可能です。 audioタグはpreloadを設定しており、JavaScriptが読み込まれた際に音源のプリロードが発生します。UI上は非表示にしてあります。

BPMの計算とシーケンサーの仕組み

BPM(Beat Per Minuite)は分間に4分音符がいくつおけるかで示すテンポを表す単位です。setInterval()を利用して音源を順々に再生させていきますので以下の計算で音と音の間隔を設定しています。

setIntervalの間隔 = 60(sec) / bpm * 1000

4. まとめ

今回作成したステップシーケンサーの仕組みはとてもシンプルなものです。 前述のとおりWeb Audio APIやtimbre.jsを利用することでさらに複雑な処理が実装できます。kintoneを活用してこういった面白い仕組みを構築することができるのは楽しいですね。

kintone x {something} の可能性を感じて頂ければ嬉しいです。