Simple Synthesizer with Vue and Web Audio API
by bc_rikko
HTML
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<div class="keyboard">
<ul class="key-list">
<li
v-for="(note, i) in notes"
:key="i"
class="key"
:class="{ '-sharp': isSharp(i) }"
@mousedown="stroke(note)"
@mouseup="release"
@mouseleave="release"
></li>
</ul>
</div>
</div>
SCSS
.keyboard {
padding: 10px 0;
background-color: rgba(6,6,6,.3);
}
.key-list {
height: 250px;
background-color: #666666;
/* 改行させないため */
overflow-x: scroll;
overflow-y: hidden;
white-space:nowrap;
> .key {
cursor: pointer;
display: inline-block;
width: 60px;
height: calc(100% - 6px); /* scrollbar分 */
background-color: #ffffff;
margin: 0 3px;
border-radius: 0 0 5px 5px;
&.-sharp {
position: relative;
width: 0;
margin: 0;
&::after {
display: block;
content: '';
position: absolute;
top: 0;
left: calc(-50px / 2);
width: 50px;
height: 50%;
border-radius: 0 0 5px 5px;
background-color: #000000;
}
}
}
}
JavaScript
// import vue@2.5.3
class VOscillator {
constructor() {
this.ctx = new AudioContext();
this.osc;
}
/**
* 音を鳴らす
* @param note Hz
*/
sound(note) {
// Failed to execute 'start' on 'AudioScheduledSourceNode': cannot call start more than once.
// というエラーがでるので、音を鳴らす度にオシレーターを再生成する
this.osc = this.ctx.createOscillator();
this.osc.frequency.value = note;
this.osc.connect(this.ctx.destination);
this.osc.start();
}
/**
* 音を止める
*/
stop() {
this.osc.stop();
}
}
new Vue({
el: '#app',
data () {
return {
KEY_A: 440, // 基音: 440Hz
osc: null // オシレーター
}
},
created () {
this.osc = new VOscillator();
},
mounted () {
// 基音付近にスクロール
const keyList = document.querySelector('.key-list');
keyList.scrollLeft = (keyList.scrollWidth / 2) - (keyList.offsetWidth / 2);
},
computed: {
notes () {
// 88鍵 -48は基音A4→A0までの距離
return [...Array(88)].map((_, i) => this.KEY_A * Math.pow(2, (1 / 12) * (-48 + i)));
}
},
methods: {
isSharp (i) {
// A#, C#, D#, F#, G#
return [1, 4, 6, 9, 11].includes(i % 12)
},
stroke (note) {
this.osc.sound(note);
},
release () {
this.osc.stop();
}
}
});