HTML
JavaScript
jQuery
Web
vue.js

【初音ミク】Innocence【3DPV】に出てくる「あの楽器」のWeb版

あの楽器 Web版 [仕様]

  • ちゃんと音が鳴ります。
  • 鍵盤はVue.jsで描画
  • 画面表示はSVG手打ちで描画
  • 1.5秒でフェードアウト
  • 直線、マル、シカクが表示されます。

asdfsdfasdfggggg

作成したもの

あの楽器とは

以下、ニコニコ大百科参照のこと

あの楽器

「あの楽器」とは 【初音ミク】Innocence【3DPV】 に登場する、初音ミクが弾いている楽器のことである。

特徴

形状はショルダーキーボードに近い。鍵盤にあたる部分が一面ディスプレイになっており、タッチして演奏する。タッチすることでディスプレイ上に直線、三角形、四角形などの幾何学模様があらわれる。 PV中では楽器全体が宙に浮き、演奏者の手元へ飛んでくる。音色はPVからうかがい知ることはできない。

ニコニコ技術部による実現プロジェクト

2008年12月24日にニコニコ技術部宛に「これって作れる?」「挑戦者求む!」という内容の動画が投稿される。ニコニコ技術部もこれにすぐに応え、「あの楽器」を再現した動画が複数投稿される。2009年3月現在、複数の技術部員が「あの楽器」を実現すべく活動中である。
なお、非常に類似しているデザインの楽器として、試作までされたが発売されなかった立奏鍵盤の「YAMAHA KX3」があり、類似技術研究として、Cymis(pdfファイル)のようなものがある。
また、液晶のサイズは650mm*150mm程度であるらしい。
2月20日、芸者東京エンターテイメントによるiPhone版がAppStoreより発売(関連記事)。iPhoneアプリ自体はすでに別の作者が無料で公開していたが、突然の企業による有料化に「開発に水を差すのでは・・・」と心配する声が沸いた。騒動は落ち着きを見せたものの、ニコ技部員による開発競争はますます過熱しており今後の展開に目が離せない。

以前、私が作ったときはニコニコ技術部でiアプリ版を公開していました。
最初に私が作ったのは2010年の夏休み・・・2011年に開発終了したため

その間・・・約8年

そんなに経過したのか!!!((((;゚Д゚))))ガクガクブルブル

実装にあたって

完成形はこちら:GitHub

単純に音を鳴らしたトコロにラインを出せばいいんでしょう?って思う方もいると思います。そうです。そのとおりです。単純にdivなりaタグなりで実装して、音を鳴らしていきます。
Canvas等で図形を鍵盤の上に配置していきたいのですが、これは危険。図形が描画されているとそのエリア下にあるコンポーネントは叩けません。

そこで3層構造にしていきます。

  • 背景に鍵盤の色、図形などを表示
  • 図形を描画するエリア
  • 背景色、枠等なし。タッチ/クリックでイベントを実装

では実装していきましょう。

鍵盤を配置

というわけで鍵盤を配置していきます。
鍵盤のスタイルはVue.jsにて実装しています。
同じ鍵盤を大量に並べ、同じ関数を叩き、同じ処理をしていく場合・・・一つ一つ書くのはめんどくさいです。(大量に書くのがめんどくさかった)

// Vue.jsで使用する鍵盤の配列を作成。これを使いまわしていきます。
// key: 音を定義します。
// ken: 黒鍵盤・白鍵盤の定義。
var app = new Vue({
    el: '#waku',
    data: {
        lists: [
            { key: "F3", ken: false },
            { key: "F#3", ken: true },
            { key: "G3", ken: false },
            { key: "G#3", ken: true },
            { key: "A3", ken: false },
            { key: "A#3", ken: true },
            { key: "B3", ken: false },
            { key: "C4", ken: false },
            { key: "C#4", ken: true },
            { key: "D4", ken: false },
            { key: "D#4", ken: true },
            { key: "E4", ken: false },
            { key: "F4", ken: false },
            { key: "F#4", ken: true },
            { key: "G4", ken: false },
            { key: "G#4", ken: true },
            { key: "A4", ken: false },
            { key: "A#4", ken: true },
            { key: "B4", ken: false },
            { key: "C5", ken: false },
            { key: "C#5", ken: true },
            { key: "D5", ken: false },
            { key: "D#5", ken: true },
            { key: "E5", ken: false },
            { key: "F5", ken: false }
        ],
    }
});
<!-- V-forでループを書きます。 -->
<div id="waku">
    <!-- 再背面の鍵盤を描画 -->
    <div class="waku">
        <template v-for="list in lists">
            <div class="kenNormal" v-bind:class="{ken :list.ken}">
                <span>{{list.key}}</span>
            </div>
        </template>
    </div>
</div>
#waku {
    height: 100%;
    margin: 0;
    padding: 0;
}

.waku {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: 100%;
}

.kenNormal {
    display: inline-block;
    width: 4%;
    height: 100%;
    color: #444;
    background-color: #000000;
    border: none;
}

.ken {
    background-color: #111;
}

.kenNormal span {
    padding-bottom: 1em;
    display: block;
    width: 4%;
    text-align: center;
    bottom: 0;
    position: absolute;
}

音を鳴らす実装

音を鳴らすのはTone.jsを使用しています。
リファレンスのように書いたらカンタンに実装できましたぁ!!

// Tone.jsをリファレンス通りに実装する。
var synth = new Tone.Synth().toMaster();
var app = new Vue({
    el: '#waku',
    data: {
        //・・・中略・・・
    }
    methods: {
        playsound: function (key) {
            synth.triggerAttackRelease(key, "8n");
        }
    }
});
<div id="waku">
    <!-- 再背面の鍵盤を描画 -->
    <div class="waku">
        <!-- 省略 -->
    </div>
    <!-- タッチで反応するdiv枠を定義 -->
    <div class="waku">
        <template v-for="list in lists">
            <div class="kenWaku" v-on:click="playsound(list.key)">
            </div>
        </template>
    </div>
</div>
#waku {
    /* 省略 */
}

.kenNormal {
    /* 省略 */
}

.kenWaku {
    display: inline-block;
    width: 4%;
    height: 100%;
    border: none;
}

.waku {
    /* 省略 */
}

.ken {
    /* 省略 */
}

.kenNormal span {
    /* 省略 */
}

図形を描画する

とりあえずマルとシカクが表示されるように作っています。
jQueryで実装。divに対してsvgを追加していくように書きます。

$('.kenWaku').click(function(e) {
    // 描画シリーズを変更
    var randType = Math.floor(Math.random() * (3 - 0) + 0);
    var randNum = Math.floor(Math.random() * (360 - 0) + 0);
    var randSize = Math.floor(Math.random() * ($(window).width() / 3 - $(window).width() / 5) + $(window).width() / 5);
    var ObjSize = $(window).width() / 100;
    // 図形毎にSVGを変更
    switch (randType) {
        case 0:
            //マル
            $('#view').append(
                $('<div class="svg" style="position: absolute; margin: 0; padding:0 ; left:' +
                    (e.pageX - randSize / 2) + 'px; top: ' +
                    (e.pageY - randSize / 2) + 'px; width: ' +
                    randSize + 'px; height: ' + randSize + 'px; transform: rotate(' + randNum + 'deg);">')
                .append('<svg width="' + randSize + '" height="' + randSize + '" viewBox="0 0 ' + randSize + ' ' + randSize + '">' +
                    '<circle r="' + randSize / 2.2 + '" cx="' + randSize / 2 + '" cy="' + randSize / 2 + '" stroke="#86cecb" stroke-width="' +
                    ObjSize + '" fill-opacity="0" >' +
                    '</circle></svg>'));
            break;
        case 1:
            //シカク
            $('#view').append(
                $('<div class="svg" style="position: absolute; margin: 0; padding:0 ; left:' +
                    (e.pageX - randSize / 2) + 'px; top: ' +
                    (e.pageY - randSize / 2) + 'px; width: ' +
                    randSize + 'px; height: ' + randSize + 'px; transform: rotate(' + randNum + 'deg);">')
                .append('<svg width="' + randSize + '" height="' + randSize + '" viewBox="0 0 ' + randSize + ' ' + randSize + '">' +
                    '<rect width="' + randSize + '" height="' + randSize + '" x="0" y="0" stroke="#86cecb" stroke-width="' +
                    ObjSize * 2 + '" fill-opacity="0" >' +
                    '</rect></svg>'));
            break;
        case 2:
            //線
            $('#view').append(
                $('<div class="svg" style="position: absolute; margin: 0; padding:0 ; left:' +
                    (e.pageX - 4000 / 2) + 'px; top: ' +
                    (e.pageY - ObjSize / 2) + 'px; width: ' +
                    4000 + 'px; height: ' + ObjSize + 'px; transform: rotate(' + randNum + 'deg);">')
                .append('<svg width="4000" height="' + ObjSize + '" viewBox="0 0 4000 ' + ObjSize + '">' +
                    '<rect width="4000" height="' + ObjSize + '" x="0" y="0" stroke-width="0" fill-opacity="1" fill="#86cecb">' +
                    '</rect></svg>'));
            break;
    }
    // フェードアウトを定義。テストした感じ1.5sぐらいかな
    $('.svg').fadeOut(1500);
});
<div id="waku">
    <!-- 再背面の鍵盤を描画 -->
    <div class="waku">
        <!-- 省略 -->
    </div>
    <!-- 図形描画エリア -->
    <div class="waku" id="view">
    </div>
    <!-- タッチで反応するdiv枠を定義 -->
    <div class="waku">
        <!-- 省略 -->
    </div>
</div>

完成形は、GitHubのコードを見ていただければと思います。

まとめ

2018年のミクの日に間に合わせたかったのですが、間に合わなかった・・・
ミクさんマジ天使

作成協力

Twitter : ぺんぎんぬ