<h1>ピアノの鍵盤</h1>
	
	<div class="piano" id="pf">
		<div class="absoslm">
			<div class="basetone"><input type="text" v-model="basehz" @change="bshzChange"/></div>
			<div class="keyname">{{tonename}}</div>
		</div>
		<div v-for="range in ranges" v-bind:class="range.clsnm">
			<button v-for="key in range.keys" v-on:mousedown="keydown" v-on:mouseup="keyup" v-bind:hz="key.hz" v-bind:rangerate="range.rangerate" v-bind:range="range.range" v-bind:tone="key.tone" v-bind:class="key.clsnm"></button>
		</div>
	</div>
@charset "utf-8";
/*
各種色
黒鍵:#1a1c1b
黒鍵影:#121413

白鍵:#ffffff
白鍵影:#b2b2b2

背景黒:#060707

*/


:root{
	--white_key_width:30px;
	--black_key_width:20px;
	--black_key_pad:5px;
}

.piano{
	background:#060707;
	height:250px;
	display:flex;
	padding:50px;
	box-sizing:border-box;
}


.piano .scale{
	display:flex;
	height:100%;
	position:relative;
}

#pf{
	position:relative;
}
#pf .absoslm{
	position:absolute;
	left:10px; top:10px;
	color:#fff;
	display:flex;
}

/* 重なりが順番になるように */
.scale.lowlow{ z-index:80; }
.scale.low{ z-index:70; }
.scale.midi1{ z-index:60; }
.scale.midi2{ z-index:50; }
.scale.hi{ z-index:40; }
.scale.hihi{ z-index:30; }
.scale.hihihi{ z-index:20; }
.scale.hihihihi{ z-index:10; }

.piano .key{
	width:var(--white_key_width);
	border:none;
	border-radius:3px;
	background:#b2b2b2;
	height:100%;
	border:#b2b2b2 solid 1px;
	padding:0;
	transition:all 0.2s ease-out;
}

.piano .key:before{
	content:"";
	height:calc(100% - 10px);
	width:100%;
	position:relative;
	top:-5px;
	display:block;
	background:#fff;
}

.piano .key.bl:before{
	background:#1a1c1b;
}
.piano .key.bl{
	width:var(--black_key_width);
	background:#121413;
	height:50%;
	position:absolute;
	z-index:10
}

.piano .key.bl.aSh{ left:calc(var(--white_key_width) / 2 + var(--black_key_pad));}
.piano .key.bl.cSh{ left:calc(var(--white_key_width) *2 + var(--white_key_width) / 2 + var(--black_key_pad)); }
.piano .key.bl.dSh{ left:calc(var(--white_key_width) *3 + var(--white_key_width) / 2 + var(--black_key_pad)); }
.piano .key.bl.fSh{ left:calc(var(--white_key_width) *5 + var(--white_key_width) / 2 + var(--black_key_pad)); }
.piano .key.bl.gSh{ left:calc(var(--white_key_width) *6 + var(--white_key_width) / 2 + var(--black_key_pad)); }


.piano .key:active:before{
	height:calc(100% - 2px);
	top:1px;
}

.piano .key.wh:active:before{
	background:#ffafba;
}

.piano .key.bl:active:before{
	background:#7f4b63;
}

window.onload = function(){
	
	//vueを使ったピアノ
	
	//web Audio API
	var actx = new AudioContext();
	var osciillatorNode = null;
	
	
	var bairitu = Math.pow(2,1/12); //半音の差をあらかじめ計算しておく
	var _baseHz = 440; //A(ラ)の音が基準
	
	//音程の名前を配列であらかじめ定義する
	var scale_names = {
		'0':'a', '1':'aSh', '2':'b', '3':'c', '4':'cSh', '5':'d', '6':'dSh',
		'7':'e', '8':'f', '9':'fSh', '10':'g', '11': 'gSh'
	}
	
	var scale = [];
	//var scale_num = Object.keys(scale_names).length; //音程の種類数
	
	scale = makeScale(scale_names, _baseHz, bairitu);
	
	
	/*
	var scale = [
		{'tone':'a', 'clsnm':'key a wh', 'hz':baseHz},
		{'tone':'aSh', 'clsnm':'key aSh bl', 'hz':baseHz * bairitu},
		{'tone':'b', 'clsnm':'key b wh', 'hz':baseHz * Math.pow(bairitu, 2)},
		{'tone':'c', 'clsnm':'key c wh', 'hz':baseHz * Math.pow(bairitu, 3)},
		{'tone':'cSh', 'clsnm':'key cSh bl', 'hz':baseHz * Math.pow(bairitu, 4)},
		{'tone':'d', 'clsnm':'key d wh', 'hz':baseHz * Math.pow(bairitu, 5)},
		{'tone':'dSh', 'clsnm':'key dSh bl', 'hz':baseHz * Math.pow(bairitu, 6)},
		{'tone':'e', 'clsnm':'key e wh', 'hz':baseHz * Math.pow(bairitu, 7)},
		{'tone':'f', 'clsnm':'key f wh', 'hz':baseHz * Math.pow(bairitu, 8)},
		{'tone':'fSh', 'clsnm':'key fSh bl', 'hz':baseHz * Math.pow(bairitu, 9)},
		{'tone':'g', 'clsnm':'key g wh', 'hz':baseHz * Math.pow(bairitu, 10)},
		{'tone':'gSh', 'clsnm':'key gSh bl', 'hz':baseHz * Math.pow(bairitu, 11)},
	];
	*/
	
	//音程を定義する 本当はもう少したくさんあるけどとりあえずこんだけ
	var range = [
		{'range':'low', 'clsnm':'scale low', 'keys':scale, 'rangerate':0.5},
		{'range':'midi1', 'clsnm':'scale midi1', 'keys':scale, 'rangerate':1},
		{'range':'midi2', 'clsnm':'scale midi2', 'keys':scale, 'rangerate':2},
		{'range':'hi', 'clsnm':'scale hi', 'keys':scale, 'rangerate':4},
	];
	
	
	//	{'range':'hihi', 'clsnm':'scale hihi', 'keys':scale, 'rangerate':8}
	var piano = new Vue({
		el:'#pf',
		data:{
			'tonename':'',
			'ranges':range,
			'basehz':_baseHz
		},
		methods:{
			'bshzChange':function(e){
				console.log('change base');
				var ojbk = Object.keys(this.ranges);
				var l = ojbk.length;
				for(var k = 0; k < l; k++){
					var range_key = ojbk[k];
					
					this.ranges[range_key]['keys'] = makeScale(scale_names, this.basehz, bairitu);
					/*var keyskeys = Object.key(range[range_key]['keys']);
					var kl = keyskeys.length;
					for(var kk = 0; kk < kl; kk++){
						range[range_key]['keys']
					}*/
					
				}
			},
			'keydown':function(e){
				var nm = '';
				var hz = e.target.getAttribute('hz') * e.target.getAttribute('rangerate');
				
				nm += e.target.getAttribute('range');
				nm += ' ';
				nm += e.target.getAttribute('tone');
				nm += ' : ';
				nm += hz
				this.tonename = nm;
				
				//発音処理
				osciillatorNode = actx.createOscillator();
				osciillatorNode.frequency.value = hz; //発音周波数を指定する
				osciillatorNode.connect(actx.destination);
				osciillatorNode.start(); //実際に音を鳴らす
				
			},
			'keyup':function(e){
				
				osciillatorNode.stop(); //音をストップする
				//this.tonename = ''; //音程名をリセットする
			}
		}
	});


}

/*
引数の説明
scl_nms:音階の名前
bhz    :基準の周波数(通常440)
brt    :半音の倍率(12√2)

*/
function makeScale(scl_nms, bhz, brt){
	var scl = [];
	var scl_num = Object.keys(scl_nms).length; //音程の種類数
	
	//キーボードを描画するためのオブジェクトを動的に生成する
	for(var i = 0; i < scl_num; i++){
		//割り振られた音の周波数を計算する
		var xhz = bhz;
		if(i > 1){
			xhz = bhz * Math.pow(brt, i);
		} else if(i == 1){
			xhz = bhz * brt;
		}
		
		//表示する鍵盤ボタンのクラス名を生成する
		var keycls = 'key';
		keycls += ' ';
		keycls += scl_nms[i];
		keycls += ' ';
		if(scl_nms[i].match(/Sh$/)){ //末尾が「Sh」の場合は黒鍵にする
			keycls += 'bl';
		} else {
			keycls += 'wh';
		}
		
		scl.push({'tone':scl_nms[i], 'clsnm':keycls, 'hz':xhz});
	}
	return scl;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://inglow.jp/files/js/code-pen/vue.min.js