view source

JavaScript

document.title = 'ピアノ ドレミファソラシド音再生';


//音の計算はよくわからんw
//https://qiita.com/APO/items/883f7eb250f17c36473a

//他にもシンセとか音関係ライブラリあるっっぽい
//https://qiita.com/comorebi_notes/items/56e41f3ddc228597450d

var audio;

//音階を再生
//node ノード(ド=48,ド#=49…)
//sec 再生秒数
function play(node,sec){
    stop();
    //if(node>=96)return;
    if(node<1)return;

    Hz=11025;//サンプリングレート

    //Waveデータ
    var bytes=new Uint8Array(Math.floor(Hz*sec)+100);

    //Waveヘッダー作成
    var header="524946460000000057415645666D74201000000001000100112B0000112B0000010008006461746100000000";
    for(fp = 0; fp < header.length/2; fp++){
        bytes[fp]=parseInt(header.substr(fp*2,2),16);
    }

    //音階(ドド#レ…シ)の周波数(1オクターブ下がると1/2倍)
    var freqs=[4180, 4428, 4708, 4968, 5264, 5592, 5884, 6300, 6676, 6988, 7476, 7848];

    //1サンプルあたりの位相計算
    octave = Math.floor(node/12); //オクターブ
    freq = freqs[node%12] / (1<<(7-octave)); //周波数
    phase = 6.28 / (Hz / freq);

    //波形作成
    for (t = 0; t < Math.floor(Hz*sec); t++){
        bytes[fp++] = Math.floor(Math.sin(phase*t)*127)+128;
    }

    //データ補正
    setLittleEndian(bytes,4,fp-8);  //ファイルサイズ
    setLittleEndian(bytes,24,Hz);   //サンプリングレート
    setLittleEndian(bytes,40,fp-44);//波形サイズ

    //BASE64変換してオーディオ作成
    str="";
    for (i=0;i<fp;i++){str+=String.fromCharCode(bytes[i]);}
    audio=new Audio("data:audio/wav;base64,"+btoa(str));
    audio.play();
}

function stop(){
    if (audio&&!audio.ended){audio.pause();audio.currentTime=0;}
}

window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();
function playHz(hz){
  var osc = audioCtx.createOscillator();
  osc.frequency.value = hz;
  var audDes = audioCtx.destination;
  osc.connect(audDes);
  osc.start = osc.start || osc.noteOn;
  osc.start();
  setTimeout(function() { osc.stop(0);}, 200);
}


function setLittleEndian(bytes,p,data)
{
    bytes[p] = (data & 0xFF);
    bytes[p+1] = ((data >> 8) & 0xFF);
    bytes[p+2] = ((data >> 16) & 0xFF);
    bytes[p+3] = ((data >> 24) & 0xFF);
}


var doremiAry = [
  {
    text:'ド',
    node:48,
    keycode:90,//Z
    hz:261,
  },
  {
    text:'レ',
    node:50,
    keycode:88,//X
    hz:293,
  },
  {
    text:'ミ',
    node:52,
    keycode:67,//C
    hz:329,
  },
  {
    text:'ファ',
    node:53,
    keycode:86,//V
    hz:349,
  },
  {
    text:'ソ',
    node:55,
    keycode:66,//B
    hz:392,
  },
  {
    text:'ラ',
    node:57,
    keycode:78,//N
    hz:440,
  },
  {
    text:'シ',
    node:59,
    keycode:77,//M
    hz:493,
  },
  {
    text:'ド',
    node:60,
    keycode:188,//<
    hz:523,
  },
];

var doremiBlackAry = [
  {
    text:'ド#',
    node:49,
    keycode:83,//S
  },
  {
    text:'レ#',
    node:51,
    keycode:68,//D
  },
  {
    text:'',
    node:0,
    keycode:70,//F
  },
  {
    text:'ファ#',
    node:54,
    keycode:71,//G
  },
  {
    text:'ソ#',
    node:56,
    keycode:72,//H
  },
  {
    text:'ラ#',
    node:58,
    keycode:74,//J
  },
];
$(function(){
  $piano = $('<div>',{id:'piano'});
  $pianoBlack = $('<div>',{id:'pianoBlack'});
  $pianoOscillator = $('<div>',{id:'oscillator'});
  $('#demo').append($piano);
  $('#demo').append($pianoBlack);
  $('#demo').append($pianoOscillator);
  doremiAry.forEach(function(v){
    $keyboard = $('<p>',{title:v.text});
    $keyboard.on('click', function(){
      play(v.node,0.2);
    });
    $keyboard.on('mouseup', function(){
      audio.pause();
    });
    $piano.append($keyboard);
  });
  doremiBlackAry.forEach(function(v){
    $keyboard = $('<p>',{title:v.text});
    if(v.node===0){$keyboard.addClass('none');}
    $keyboard.on('click', function(){
      play(v.node,0.2);
    });
    $keyboard.on('mouseup', function(){
      audio.pause();
    });
    $pianoBlack.append($keyboard);
  });

  doremiAry.forEach(function(v){
    $keyboard = $('<p>',{title:v.text}).text(v.text);
    if(v.node===0){$keyboard.addClass('none');}
    $keyboard.on('click', function(){
      playHz(v.hz);
    });
    $pianoOscillator.append($keyboard);
  });

  $(document).on('keydown', function(e){
    doremiAry.forEach(function(v){
      if(v.keycode === e.keyCode){
        $('[title="'+v.text+'"]').trigger('click');
        //play(v.node,0.2);
      }
    });
    doremiBlackAry.forEach(function(v){
      if(v.keycode === e.keyCode){
        $('[title="'+v.text+'"]').trigger('click');
        //play(v.node,0.2);
      }
    });
  });


  $input = $('<input>',{type:'number',min:0,max:96}).val(48);
  $input.on('change', function(e){
    var v = $(this).val()
    play(v,0.2);
  });
  $('#demo').after($input);
});

CSS

*{
  box-sizing:border-box;
}
html,body{
  overflow-x:hidden;
}
#demo{
  position:relative;
  top:0;
  left:0;
  user-select:none;
}
#piano{
  display:flex;
  width:100%;
}
#piano p{
  width:100%;
  height:100vh;
  border:1px solid #000;
}
#piano p:hover{
  background-color:rgba(0,0,0,.1);
}
#pianoBlack{
  position:absolute;
  top:0;
  left:calc(100% / 8 * 3 / 6);
  display:flex;
  width:100%;
}
#pianoBlack p{
  width:calc(100% / 8 * 1/ 2);
  height:50vh;
  margin:0 calc(100% / 8 * 1 / 4);
  border:1px solid #000;
  background-color:#000;
}
#pianoBlack p.none{
  opacity:0;
}

#oscillator{
  display:flex;
  width:100%;
  overflow:hidden
}
#oscillator p{
  width:100%;
  height:20vh;
  line-height:20vh;
  border:1px solid #000;
  text-align:center;
  font-weight:bold;
  font-size:24px;
  
}
#oscillator p:hover{
  background-color:rgba(0,0,0,.1);
}

input{
  width:99%;
  font-size:32px;
}

HTML

ページのソースを表示 : Ctrl+U , DevTools : F12

view-source:https://hi0a.com/demo/-js/js-audio-piano/

ABOUT

hi0a.com 「ひまアプリ」は無料で遊べるミニゲームや便利ツールを公開しています。

プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。

必要な機能の関数をコピペ利用したり勉強に活用できます。

プログラムの動作サンプル結果は画面上部に出力表示されています。

環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。

画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。

動く便利なものが好きなだけで技術自体に興味はないのでコードは汚いです。

途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。