<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/blog_02.css">
<script src="./js/vue.min.js"></script>
<script src="./js/vue.piano.js"></script>
</head>
<body>
	<h1>ピアノの鍵盤</h1>
	
	<div class="piano" id="pf"  v-on:keydown="keyborddown" v-on:keydown.up="keyborddown_oct8hi" v-on:keydown.down="keyborddown_oct8low" v-on:keyup="keybordup">
		<div class="absoslm">
			<div class="basetone"><input type="text" v-model="basehz" @change="bshzChange"/></div>
			<div class="keyctl"><label><input type="checkbox" v-model="keyplay" />キーボードを使用する</label></div>
			<div class="oct">オクターブ{{nowrange}}</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:ref="range.range + '_' + key.tone" 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>
	<div>
		A:ラ
		W:ラ#(シ♭)
		S:シ
		D:ド
		R:ド#(レ♭)
		F:レ
		T:レ#(ミ♭)
		G:ミ
		H:ファ
		U:ファ#(ソ♭)
		J:ソ
		I:ソ#(ラ♭)<br />
		↑キー:1オクターブ上げる ↓キー:1オクターブ下げる
	</div>
	
	
</body>
</html>


@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;
}

#pf .absoslm > div{
	margin:0 15px;
}

/* 重なりが順番になるように */
.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;
}

/* キーボードで押したときの色 */
.piano .key.wh[note_play]:before{
	background:#4169E1;
}

.piano .key.bl[note_play]:before{
	background:#1F346D;
}


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_keybord = {
		'0':'KeyA','1':'KeyW','2':'KeyS','3':'KeyD','4':'KeyR','5':'KeyF','6':'KeyT',
		'7':'KeyG','8':'KeyH','9':'KeyU','10':'KeyJ','11':'KeyI',
	};
	
	var scale = [];
	var keycode4note = [];
	//var scale_num = Object.keys(scale_names).length; //音程の種類数
	
	scale = makeScale(scale_names, _baseHz, bairitu);
	keycode4note = makeKeycode4note(scale_names, scale_keybord);
	
	
	/*
	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_snum = {
		30:'low',
		40:'midi1',
		50:'midi2',
		60:'hi'
	};
	//音程を定義する 本当はもう少したくさんあるけどとりあえずこんだけ
	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,
			'range_snum':range_snum,
			'nowrange':40,
			'baserange':40,
			'keybord':keycode4note,
			'keyplay':false,
			'pressing':false,
			'pressing_ref':null
		},
		mounted:function(){
			//var l = Object.keys(this.$refs);
			//console.log(l);
			//console.log(this);
			//console.log(this.$refs.midi1_a[0].getAttribute('hz'));
			//this.$refs.midi1_a[0].forcus();
		},
		methods:{
			'bshzChange':function(e){ //カポ
				//console.log('カポ');
				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);
				}
			},
			'keydown':function(e){
				
				// 音の発音をこちらに集約
				this.playNote(e.target);
				
			},
			'keyborddown':function(e){
				//console.log(this.keyplay)
				if(this.keyplay){
					//ArrowRight
					var Kcode = keybord4notename(this.keybord, e.code);
					if(Kcode != null){
						//音を鳴らす
						if(this.range_snum[this.nowrange]){
							var ref_name = this.range_snum[this.nowrange] + '_' + Kcode;
							
							//attrを追加する
							this.pressing_ref = ref_name;
							this.$refs[ref_name][0].setAttribute('note_play', 'play');
							this.playNote(this.$refs[ref_name][0]);
						}
					}
				}
			},
			'keyborddown_oct8hi':function(e){ //オクターブ上げる
				
				if(this.keyplay){
					var range_max = Math.max.apply(null, Object.keys(this.range_snum));
					var next_range = this.nowrange + 10;
					if(range_max < next_range){
						next_range = range_max;
					}
					this.nowrange = next_range;
				}
			},
			'keyborddown_oct8low':function(e){ //オクターブ下げる
				if(this.keyplay){
					var range_min = Math.min.apply(null, Object.keys(this.range_snum));
					var next_range = this.nowrange - 10;
					if(range_min > next_range){
						next_range = range_min;
					}
					this.nowrange = next_range;
				}
			},
			'keybordup':function(e){
				this.stopNote();
				//console.log(this.keybord[e.code]);
				//console.log(e);
			},
			'keyup':function(e){
				this.stopNote();
				//this.tonename = ''; //音程名をリセットする
			},
			'playNote':function(note){
				if(!this.pressing){
					this.pressing = true;
					//console.log('p');
					var nm = '';
					var hz = note.getAttribute('hz') * note.getAttribute('rangerate');
					
					nm += note.getAttribute('range');
					nm += ' ';
					nm += note.getAttribute('tone');
					nm += ' : ';
					nm += hz
					this.tonename = nm;
					
					//発音処理
					//actx = new AudioContext();
					osciillatorNode = actx.createOscillator();
					osciillatorNode.frequency.value = hz; //発音周波数を指定する
					osciillatorNode.connect(actx.destination);
					osciillatorNode.start(); //実際に音を鳴らす
				}
			},
			'stopNote':function(){
				if(this.pressing){
					osciillatorNode.stop(); //音をストップする
					
					if(this.pressing_ref != null){
						this.$refs[this.pressing_ref][0].removeAttribute('note_play');
					}
					this.pressing_ref = null;
					this.pressing = false;
					//console.log('s');
				}
			}
		}
	});
	
	document.getElementById("pf").focus();
	//piano.focus();


}

/*
keybord4notename
*/
function keybord4notename(keybord, keycode){
	var name = null;
	if(keybord[keycode]){
		name = keybord[keycode];
	}
	
	return name;
}

/*
音を出す

*/


/* 
キーボードに対応する音名を設定していく
 */
function makeKeycode4note(scl_nms, scl_kb){
	var k4n = {};
	
	var l = Object.keys(scl_nms).length;
	for(var i = 0; i < l; i++){
		k4n[scl_kb[i]] = scl_nms[i];
	}
	
	return k4n;
}

/*
鍵盤の音階データを作成する
引数の説明
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