見出し画像

シンセサイザーのつくりかた

はじめに

 わたしは、スマホやタブレットで弾けるシンセサイザーを作っています。ここでは、シンセサイザーを作っていくプロセスで学んだことを、アウトプットしていきたいと思います。

音声処理はJavaScript、画像処理はSVG、これらをHTML上でまとめて、なるべくたくさんのOSやブラウザで動作するものを目指します。

現状は こんな感じ です。

画像1

外部ファイルには依存せずに、この index.htm だけで完結しています。

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=10">
<title>Web Audio API for iPhone</title>
</head>

<!--None touch-action-->
<body style="touch-action: none;" oncontextmenu='return false;' oncopy='return false;'>

<!--Main GUI-->
<div id="main_gui">
 <svg width="100%" height="100%" viewBox="0 0 536 232">
   <rect id="master_back_ground" x="0" y="0" width="100%" height="100%" opacity="1"
     fill="#9E4B2D" rx="3" ry="3" 
     stroke="#000000" stroke-width="3" />

   <!--Piano key GUI-->
   <svg width="100%" height="100%" viewBox="0 0 536 232">
     <rect id="piano_key" x="0" y="0" width="100%" height="100%" opacity="0" />

     <!-- 白鍵(white key) -->
     <rect id="w0" x="-12.14%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w1" x="0" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w2" x="12.14%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w3" x="24.28%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w4" x="36.42%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w5" x="48.56%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w6" x="60.7%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w7" x="72.84%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w8" x="84.98%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="w9" x="97.12%" y="0" width="12.14%" height="100%" opacity="1"
       fill="#FFFFFF" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />

     <!-- 黒鍵(black key) -->
     <rect id="b1" x="8.5%" y="0" width="6%" height="64%"  opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="b2" x="21.7%" y="0" width="6%" height="64%" opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="b3" x="44.3%" y="0" width="6%" height="64%" opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="b4" x="57.7%" y="0" width="6%" height="64%" opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="b5" x="70.9%" y="0" width="6%" height="64%" opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="b6" x="93.48%" y="0" width="6%" height="64%" opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <rect id="b7" x="106.68%" y="0" width="6%" height="64%" opacity="1"
       fill="#000000" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
   </svg>
   <!--Piano key GUI-->

   <!--Control bar GUI-->
   <svg width="100%" height="100%" viewBox="0 0 1072 464">
     <rect id="control_bar" x="0" y="0" width="100%" height="96" opacity="1"
       fill="#DDDDDD" rx="3" ry="3" 
       stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
     <!--Waveform-->
     <svg width="40%" height="40%" viewBox="-20 -48 536 232">
       <!-- Saw / Square Switch -->
       <!-- Area -->
       <rect id="saw_square_waveform_area" x="36" y="-35" width="63" height="30" fill="#000000" />
         <!-- Switch -->
         <g id="saw_square_waveform_switch" transform="translate(0,0)">
           <rect x="36" y="-35" width="32" height="30" fill="#303030" />
           <path d="
             M40,-35 
             L40,-35 40,-5" 
             fill="none" 
             stroke="#808080" stroke-width="1" stroke-opacity="0.5" stroke-linejoin="round"/>
           <path d="
             M44,-35 
             L44,-35 44,-5" 
             fill="none" 
             stroke="rgb(0, 0, 0)" stroke-width="4" stroke-opacity="0.5" stroke-linejoin="round"/>
           <path d="
             M48,-35 
             L48,-35 48,-5" 
             fill="none" 
             stroke="#808080" stroke-width="1" stroke-opacity="0.5" stroke-linejoin="round"/>
           <path d="
             M52,-35 
             L52,-35 52,-5" 
             fill="none" 
             stroke="rgb(0, 0, 0)" stroke-width="4" stroke-opacity="0.5" stroke-linejoin="round"/>
           <path d="
             M56,-35 
             L56,-35 56,-5" 
             fill="none" 
             stroke="#808080" stroke-width="1" stroke-opacity="0.5" stroke-linejoin="round"/>
           <path d="
             M60,-35 
             L60,-35 60,-5" 
             fill="none" 
             stroke="rgb(0, 0, 0)" stroke-width="4" stroke-opacity="0.5" stroke-linejoin="round"/>
           <path d="
             M64,-35 
             L64,-35 64,-5" 
             fill="none" 
             stroke="#808080" stroke-width="1" stroke-opacity="0.5" stroke-linejoin="round"/>
         </g>
       <!-- Triangle fill SVG geometry -->
       <path d="
         M56,27 
         L56,27 67,7 
         L67,7 78,27 
         Z" 
         fill="#000000" 
         stroke="rgb(0, 0, 0)" stroke-width="2" stroke-opacity="0.8" stroke-linejoin="round"/>
       <!-- Saw wave SVG geometry -->
       <path d="
         M8,27 
         L8,27 8,7 
         L8,7 28,27 
         L28,27 28,7 
         L28,7 48,27" 
         fill="none" 
         stroke="rgb(0, 0, 0)" stroke-width="4" stroke-opacity="0.8" stroke-linejoin="round"/>
       <!-- Square wave SVG geometry -->
       <path d="
         M88,26 
         L88,26 88,7 
         L88,7 100,7 
         L100,7 100,25 
         L100,25 112,25 
         L112,25 112,7 
         L112,7 124,7 
         L124,7 124,25 
         L124,25 132,25" 
         fill="none" 
         stroke="rgb(0, 0, 0)" stroke-width="4" stroke-opacity="0.8" stroke-linejoin="round"/>
       <text id="waveform" x="6" y="50" font-size="20" font-weight="bold">WAVEFORM</text>
     </svg>
     <!--Silent mode GUI-->
     <g id="silent_mode_svg">
       <rect id="tap_to_unmute_white_back" x="20" y="93" width="240" height="61" 
         fill="#FFFFFF" rx="3" ry="3" 
         stroke="#000000" stroke-width="1" stroke-opacity="0.8" />
       <g stroke-linecap="round" transform="translate(36, 107)">
         <path d="
           M7.36,20.07 
           L7.36,20.07 0.25,20.07 
           L0.25,20.07 0.25,10.42 
           L0.25,10.42 8.88,10.42 
           L8.88,10.42 0.64,2.06 
           L0.64,2.06 2.91,0.28 
           L2.91,0.28 30.66,28.31 
           L30.66,28.31 28.29,29.99 
           L28.62,29.99 15.22,16.58 
           L15.22,16.58 15.22,28.20 
           L15.22,28.20 7.30,20.07 
           Z" 
           fill="#000000" 
           stroke="rgb(0, 0, 0)" stroke-width="1" stroke-opacity="0.8" stroke-linejoin="round"/>
         <path d="
           M19.03,26.68 
           C20.59,26.08 21.89,25.35 22.89,24.26 
           L19.03,26.68 19.03,29.73 
           C21.42,29.21 23.47,28.26 25.43,26.80 
           L25.43,26.80 22.89,24.26 
           Z " 
           fill="#000000" 
           stroke="rgb(0, 0, 0)" stroke-width="1" stroke-opacity="0.8" stroke-linejoin="round"/>
         <path d="
           M15.48,1.78 
           L15.48,1.78 15.48,8.39 
           L15.42,8.47 12.35,5.40 
           L15.34,2.12 12.38,5.08 
           Z" 
           fill="#000000" 
           stroke="rgb(0, 0, 0)" stroke-width="1" stroke-opacity="0.8" stroke-linejoin="round"/>
         <path d="
           M18.97,8.43 
           C21.48,9.71 22.96,12.09 23.42,15.59 
           L19.03,8.89 19.03,11.43 
           L23.26,16.07 18.97,8.43 
           Z" 
           fill="#000000" 
           stroke="rgb(0, 0, 0)" stroke-width="1" stroke-opacity="0.8" stroke-linejoin="round"/>
         <path d="
           M26.49,19.39 
           C26.49,19.39 32,8 19.03,3.81 
           L19.03,3.81 19.03,0.76 
           C29.65,4.28 32.99,11.22 28.98,21.89 
           L28.98,21.89 26.49,19.39 
           Z" 
           fill="#000000" 
           stroke="rgb(0, 0, 0)" stroke-width="1" stroke-opacity="0.8" stroke-linejoin="round"/>
       </g>
       <text id="tap_to_unmute_text" x="84" y="130" font-size="19" font-weight="bold">TAP TO UNMUTE</text>
     </g>
     <!--Silent mode GUI-->

   </svg>
   <!--Control bar GUI-->

 </svg>
</div>
<!--Main GUI-->

<script>

// ----------
// HTML Audio
// ----------
let silent_mode_flag = 1 ; // サイレントモードフラグ(Silent mode flag)
// 無音wav生成コードの関数を定義(Definition of silent wav)
function silent_wav(){
 // 448 Byteの空のバッファを生成(Create empty buffer of 448 byte)
 let bf = new ArrayBuffer(448) ; // バッファ(Buffer)
 let dv = new DataView(bf) ;     // データを設定する(They set the data)
 // ファイルのヘッダ44バイト(Header of file 44 Byte)
  dv.setUint8(0, parseInt("52", 16)) ;  dv.setUint8(1, parseInt("49", 16)) ;
  dv.setUint8(2, parseInt("46", 16)) ;  dv.setUint8(3, parseInt("46", 16)) ;
  dv.setUint8(4, parseInt("10", 16)) ;  dv.setUint8(5, parseInt("02", 16)) ;
  dv.setUint8(8, parseInt("57", 16)) ;  dv.setUint8(9, parseInt("41", 16)) ;
 dv.setUint8(10, parseInt("56", 16)) ; dv.setUint8(11, parseInt("45", 16)) ;
 dv.setUint8(12, parseInt("66", 16)) ; dv.setUint8(13, parseInt("6D", 16)) ;
 dv.setUint8(14, parseInt("74", 16)) ; dv.setUint8(15, parseInt("20", 16)) ;
 dv.setUint8(16, parseInt("10", 16)) ; dv.setUint8(20, parseInt("01", 16)) ;
 dv.setUint8(22, parseInt("02", 16)) ; dv.setUint8(24, parseInt("44", 16)) ;
 dv.setUint8(25, parseInt("AC", 16)) ; dv.setUint8(28, parseInt("10", 16)) ;
 dv.setUint8(29, parseInt("B1", 16)) ; dv.setUint8(30, parseInt("02", 16)) ;
 dv.setUint8(32, parseInt("04", 16)) ; dv.setUint8(34, parseInt("10", 16)) ;
 dv.setUint8(36, parseInt("64", 16)) ; dv.setUint8(37, parseInt("61", 16)) ;
 dv.setUint8(38, parseInt("74", 16)) ; dv.setUint8(39, parseInt("61", 16)) ;
 dv.setUint8(40, parseInt("94", 16)) ; dv.setUint8(41, parseInt("01", 16)) ;
 //ArrayBufferをBlobに変換(From buffer to Blob)
 let blob = new Blob([bf], {type: "audio/wav"}),
 // BlobURLを取得(Get BiobURL)
 bloburl = window.URL.createObjectURL(blob) ;
 // 無音wav生成コードの関数silent_wav()の戻り値としてBlobURLを設定(Return is BlobURL)
 return bloburl ;
} ;
// 44.1kHz、16bit、2chの無音wavを作成
// Create of silent wav(44.1kHz, 16bit, 2ch)
let url = silent_wav() ;
let silent_audio = new Audio() ;
silent_audio.src = url ;

// -------------
// Web Audio API
// -------------
// AudioContextを作成(Create AudioContext)
acx = new (window.AudioContext || window.webkitAudioContext)() ;
vol = acx.createGain() ;       // 音量を作成(Create volume)
vol.connect(acx.destination) ; // 音量を出力と接続(Connect gain to destination)
let channels = 2 ;                                     // ステレオ(Stereo)
let frq = 440 ;                                        // 周波数(Frequency)
let frameCount = ( acx.sampleRate / frq ) ;            //フレーム数(Frame count)
let myArrayBuffer ;
// 鋸歯状波(Sawtooth wave)
function sawtooth(){
 // AudioContextのサンプルレートで440Hzの空のステレオバッファを生成する
 // Create an empty 440Hz stereo buffer at the sample rate of the AudioContext
 myArrayBuffer = acx.createBuffer(channels, frameCount, acx.sampleRate) ;
 // Fill the buffer with sawtooth wave down from +1.0 to -1.0
 // バッファに+1.0から-1.0へ下降する単純な鋸歯状波を書き込む準備
 for (let channel = 0; channel < channels; channel++) {
   let nowBuffering = myArrayBuffer.getChannelData(channel) ;
   // データをバッファに格納する配列を取得する
   // This gives us the actual array that contains the data
   for (let i = 0 ; i < frameCount ; i++) {
     // 鋸歯状波の生成を開始
     // Start generating sawtooth wave
     let sw ;
     for (sw = 1 ; sw > -1.0 ; sw = sw - ( 2 / frameCount )) {
       nowBuffering[i] = ( sw ) ;
       i++ ;
     } ;
     // 鋸歯状波の生成を終了
     // Finished generating sawtooth wave
   } ;
 } ;
 return myArrayBuffer ;
} ;
// 矩形波(Square wave)
function square(){
 // AudioContextのサンプルレートで440Hzの空のステレオバッファを生成する
 // Create an empty 440Hz stereo buffer at the sample rate of the AudioContext
 myArrayBuffer = acx.createBuffer(channels, frameCount, acx.sampleRate) ;
 // Fill the buffer with square wave loop of values between -1.0 and 1.0
 // バッファに-1.0と+1.0のループである単純な矩形波を書き込む準備
 for (let channel = 0; channel < channels; channel++) {
   let nowBuffering = myArrayBuffer.getChannelData(channel) ;
   // データをバッファに格納する配列を取得する
   // This gives us the actual array that contains the data
   for (let i = 0 ; i < frameCount ; i++) {
     // 矩形波の生成を開始(前半)
     // Start generating square wave(First half)
     let sq_1 ;
     for (sq_1 = 0 ; sq_1 < ( frameCount / 2 ) ; sq_1++) {
       nowBuffering[i] = ( +1.0 ) ;
       i++ ;
     } ;
     // 矩形波の生成を開始(後半)
     // Start generating square wave(Latter half)
     let sq_2 ;
     for (sq_2 = 0 ; sq_2 < ( frameCount / 2 ) ; sq_2++) {
       nowBuffering[i] = ( -1.0 ) ;
       i++ ;
     } ;
     // 矩形波の生成を終了
     // Finished generating square wave
   } ;
 } ;
 return myArrayBuffer ;
} ;
// 波形生成(Wave generate)
function waveGenerate(){
 if ( waveform_flag === 0 ){
   sawtooth() ;
 } else if ( waveform_flag === 1 ){
   square() ;
 } ;
};
// 再生用ノードのAudioBufferSourceNodeを制御
// Control an AudioBufferSourceNode
class myAudioBufferSourceNode{
 constructor(source){
   // 再生用ノードのAudioBufferSourceNodeを生成
   // Create an AudioBufferSourceNode
   this.source = acx.createBufferSource() ;
 }
 start(){
   // バッファのデータをAudioBufferSourceNodeに渡す
   // Set the buffer in the AudioBufferSourceNode
   this.source.buffer = myArrayBuffer ;
   this.source.connect(vol) ; // バッファを音量に接続(Connect buffer to gain)
   vol.gain.value = 0 ;  // 音量 0(volume 0)
   this.source.loop = true ;                  // ループ再生に設定(Set of loop play)
   this.source.loopStart = 0 ;                // ループ始点を設定(Set of start position)
   this.source.loopEnd = ( frameCount - 1 ) ; // ループ終点を設定(Set of end position)
   this.source.start() ; // 音源の再生を始める(start the source playing)
   vol.gain.value = 1 ;  // 音量 1(volume 1)
 }
 stop(){
   vol.gain.value = 0 ; // 音量 0(volume 0)
   this.source.stop() ; // 音源の再生を止める(stop the source playing)
 }
}
// コントロールバーの状態を初期化(Control bar GUI)
let waveform_flag = 0 ;
// 波形生成(Wave generate)
waveGenerate() ;
// 再生用ノードのAudioBufferSourceNodeを制御
// Control an AudioBufferSourceNode
let acx_buf_node ;

// -------------
// Pointer Event
// -------------
// 現役のポインタオブジェクト配列(Active pointer object array)
let active_pnt_obj_arr = [] ;
let defference_date = 0 ; // ミリ秒単位の差分(Pointer move difference date)
let defference_X = 0 ;    // 横スクロール差分(Pointer move difference clientX)
// 全鍵盤をノートオフで初期化(Trigger flag)
let keyW1_trigger_flag = 0 ;
let keyW2_trigger_flag = 0 ;
let keyW3_trigger_flag = 0 ;
let keyW4_trigger_flag = 0 ;
let keyW5_trigger_flag = 0 ;
let keyW6_trigger_flag = 0 ;
let keyW7_trigger_flag = 0 ;
let keyW8_trigger_flag = 0 ;
let keyW9_trigger_flag = 0 ;
let keyW0_trigger_flag = 0 ;
let keyB1_trigger_flag = 0 ;
let keyB2_trigger_flag = 0 ;
let keyB3_trigger_flag = 0 ;
let keyB4_trigger_flag = 0 ;
let keyB5_trigger_flag = 0 ;
let keyB6_trigger_flag = 0 ;
let keyB7_trigger_flag = 0 ;
// ポインタイベントを登録(addEventListenerPointerEvent)
// Main GUI
let silent_mode_pel = document.getElementById("main_gui") ;
silent_mode_pel.addEventListener("pointerdown", silent_mode_pntDown, false) ;     // PointerDown
function silent_mode_pntDown(){
 if ( silent_mode_flag === 1 ) {
   silent_audio.play() ;            // 無音wavを再生(Clear silent mode)
 } ;
};
silent_mode_pel.addEventListener("pointermove", silent_mode_pntMove, false) ;     // PointerMove
function silent_mode_pntMove(){
};
silent_mode_pel.addEventListener("pointerup", silent_mode_pntUp, false) ;         // PointerUp
function silent_mode_pntUp(){
 if ( silent_mode_flag === 1 ) {
   silent_mode_svg.innerHTML = "" ; // サイレントモードのSVGを削除(Delete silent mode svg)
   silent_mode_flag = 0 ;           // サイレントモードフラグをオフ(Silent mode flag off)
 } ;
};
silent_mode_pel.addEventListener("pointercancel", silent_mode_pntCancel, false) ; // PointerCancel
function silent_mode_pntCancel(){
 if ( silent_mode_flag === 1 ) {
   silent_mode_svg.innerHTML = "" ; // サイレントモードのSVGを削除(Delete silent mode svg)
   silent_mode_flag = 0 ;           // サイレントモードフラグをオフ(Silent mode flag off)
 } ;
};
// 白鍵(White key)
let keyW1 = document.getElementById("w1") ;
keyW1.addEventListener("pointerdown", keyW1_pntDown, false) ;     // PointerDown
function keyW1_pntDown(){
 if(keyW1_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW1_trigger_flag = 0;
 }
 keyW1.setAttribute("fill","#BBBBBB");
 keyW1_trigger_flag = 1;
 let noteNo = active_key_id_arr[0].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW1) ;
};
keyW1.addEventListener("pointermove", keyW1_pntMove, false) ;     // PointerMove
function keyW1_pntMove(){
 pntMove(keyW1) ;
};
keyW1.addEventListener("pointerup", keyW1_pntUp, false) ;         // PointerUp
function keyW1_pntUp(){
 keyW1.setAttribute("fill","#FFFFFF");
 keyW1_trigger_flag = 0;
 pntUp(keyW1) ;
};
keyW1.addEventListener("pointercancel", keyW1_pntCancel, false) ; // PointerCancel
function keyW1_pntCancel(){
 keyW1.setAttribute("fill","#FFFFFF");
 keyW1_trigger_flag = 0;
 pntCancel(keyW1) ;
};
let keyW2 = document.getElementById("w2") ;
keyW2.addEventListener("pointerdown", keyW2_pntDown, false) ;     // PointerDown
function keyW2_pntDown(){
 if(keyW2_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW2_trigger_flag = 0;
 }
 keyW2.setAttribute("fill","#BBBBBB");
 keyW2_trigger_flag = 1;
 let noteNo = active_key_id_arr[2].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW2) ;
};
keyW2.addEventListener("pointermove", keyW2_pntMove, false) ;     // PointerMove
function keyW2_pntMove(){
 pntMove(keyW2) ;
};
keyW2.addEventListener("pointerup", keyW2_pntUp, false) ;         // PointerUp
function keyW2_pntUp(){
 keyW2.setAttribute("fill","#FFFFFF");
 keyW2_trigger_flag = 0;
 pntUp(keyW2) ;
};
keyW2.addEventListener("pointercancel", keyW2_pntCancel, false) ; // PointerCancel
function keyW2_pntCancel(){
 keyW2.setAttribute("fill","#FFFFFF");
 keyW2_trigger_flag = 0;
 pntCancel(keyW2) ;
};
let keyW3 = document.getElementById("w3") ;
keyW3.addEventListener("pointerdown", keyW3_pntDown, false) ;     // PointerDown
function keyW3_pntDown(){
 if(keyW3_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW3_trigger_flag = 0;
 }
 keyW3.setAttribute("fill","#BBBBBB");
 keyW3_trigger_flag = 1;
 let noteNo = active_key_id_arr[4].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW3) ;
};
keyW3.addEventListener("pointermove", keyW3_pntMove, false) ;     // PointerMove
function keyW3_pntMove(){
 pntMove(keyW3) ;
};
keyW3.addEventListener("pointerup", keyW3_pntUp, false) ;         // PointerUp
function keyW3_pntUp(){
 keyW3.setAttribute("fill","#FFFFFF");
 keyW3_trigger_flag = 0;
 pntUp(keyW3) ;
};
keyW3.addEventListener("pointercancel", keyW3_pntCancel, false) ; // PointerCancel
function keyW3_pntCancel(){
 keyW3.setAttribute("fill","#FFFFFF");
 keyW3_trigger_flag = 0;
 pntCancel(keyW3) ;
};
let keyW4 = document.getElementById("w4") ;
keyW4.addEventListener("pointerdown", keyW4_pntDown, false) ;     // PointerDown
function keyW4_pntDown(){
 if(keyW4_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW4_trigger_flag = 0;
 }
 keyW4.setAttribute("fill","#BBBBBB");
 keyW4_trigger_flag = 1;
 let noteNo = active_key_id_arr[5].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW4) ;
};
keyW4.addEventListener("pointermove", keyW4_pntMove, false) ;     // PointerMove
function keyW4_pntMove(){
 pntMove(keyW4) ;
};
keyW4.addEventListener("pointerup", keyW4_pntUp, false) ;         // PointerUp
function keyW4_pntUp(){
 keyW4.setAttribute("fill","#FFFFFF");
 keyW4_trigger_flag = 0;
 pntUp(keyW4) ;
};
keyW4.addEventListener("pointercancel", keyW4_pntCancel, false) ; // PointerCancel
function keyW4_pntCancel(){
 keyW4.setAttribute("fill","#FFFFFF");
 keyW4_trigger_flag = 0;
 pntCancel(keyW4) ;
};
let keyW5 = document.getElementById("w5") ;
keyW5.addEventListener("pointerdown", keyW5_pntDown, false) ;     // PointerDown
function keyW5_pntDown(){
 if(keyW5_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW5_trigger_flag = 0;
 }
 keyW5.setAttribute("fill","#BBBBBB");
 keyW5_trigger_flag = 1;
 let noteNo = active_key_id_arr[7].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW5) ;
};
keyW5.addEventListener("pointermove", keyW5_pntMove, false) ;     // PointerMove
function keyW5_pntMove(){
 pntMove(keyW5) ;
};
keyW5.addEventListener("pointerup", keyW5_pntUp, false) ;         // PointerUp
function keyW5_pntUp(){
 keyW5.setAttribute("fill","#FFFFFF");
 keyW5_trigger_flag = 0;
 pntUp(keyW5) ;
};
keyW5.addEventListener("pointercancel", keyW5_pntCancel, false) ; // PointerCancel
function keyW5_pntCancel(){
 keyW5.setAttribute("fill","#FFFFFF");
 keyW5_trigger_flag = 0;
 pntCancel(keyW5) ;
};
let keyW6 = document.getElementById("w6") ;
keyW6.addEventListener("pointerdown", keyW6_pntDown, false) ;     // PointerDown
function keyW6_pntDown(){
 if(keyW6_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW6_trigger_flag = 0;
 }
 keyW6.setAttribute("fill","#BBBBBB");
 keyW6_trigger_flag = 1;
 let noteNo = active_key_id_arr[9].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW6) ;
};
keyW6.addEventListener("pointermove", keyW6_pntMove, false) ;     // PointerMove
function keyW6_pntMove(){
 pntMove(keyW6) ;
};
keyW6.addEventListener("pointerup", keyW6_pntUp, false) ;         // PointerUp
function keyW6_pntUp(){
 keyW6.setAttribute("fill","#FFFFFF");
 keyW6_trigger_flag = 0;
 pntUp(keyW6) ;
};
keyW6.addEventListener("pointercancel", keyW6_pntCancel, false) ; // PointerCancel
function keyW6_pntCancel(){
 keyW6.setAttribute("fill","#FFFFFF");
 keyW6_trigger_flag = 0;
 pntCancel(keyW6) ;
};
let keyW7 = document.getElementById("w7") ;
keyW7.addEventListener("pointerdown", keyW7_pntDown, false) ;     // PointerDown
function keyW7_pntDown(){
 if(keyW7_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW7_trigger_flag = 0;
 }
 keyW7.setAttribute("fill","#BBBBBB");
 keyW7_trigger_flag = 1;
 let noteNo = active_key_id_arr[11].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW7) ;
};
keyW7.addEventListener("pointermove", keyW7_pntMove, false) ;     // PointerMove
function keyW7_pntMove(){
 pntMove(keyW7) ;
};
keyW7.addEventListener("pointerup", keyW7_pntUp, false) ;         // PointerUp
function keyW7_pntUp(){
 keyW7.setAttribute("fill","#FFFFFF");
 keyW7_trigger_flag = 0;
 pntUp(keyW7) ;
};
keyW7.addEventListener("pointercancel", keyW7_pntCancel, false) ; // PointerCancel
function keyW7_pntCancel(){
 keyW7.setAttribute("fill","#FFFFFF");
 keyW7_trigger_flag = 0;
 pntCancel(keyW7) ;
};
let keyW8 = document.getElementById("w8") ;
keyW8.addEventListener("pointerdown", keyW8_pntDown, false) ;     // PointerDown
function keyW8_pntDown(){
 if(keyW8_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW8_trigger_flag = 0;
 }
 keyW8.setAttribute("fill","#BBBBBB");
 keyW8_trigger_flag = 1;
 let noteNo = active_key_id_arr[12].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW8) ;
};
keyW8.addEventListener("pointermove", keyW8_pntMove, false) ;     // PointerMove
function keyW8_pntMove(){
 pntMove(keyW8) ;
};
keyW8.addEventListener("pointerup", keyW8_pntUp, false) ;         // PointerUp
function keyW8_pntUp(){
 keyW8.setAttribute("fill","#FFFFFF");
 keyW8_trigger_flag = 0;
 pntUp(keyW8) ;
};
keyW8.addEventListener("pointercancel", keyW8_pntCancel, false) ; // PointerCancel
function keyW8_pntCancel(){
 keyW8.setAttribute("fill","#FFFFFF");
 keyW8_trigger_flag = 0;
 pntCancel(keyW8) ;
};
let keyW9 = document.getElementById("w9") ;
keyW9.addEventListener("pointerdown", keyW9_pntDown, false) ;     // PointerDown
function keyW9_pntDown(){
 if(keyW9_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW9_trigger_flag = 0;
 }
 keyW9.setAttribute("fill","#BBBBBB");
 keyW9_trigger_flag = 1;
 let noteNo = active_key_id_arr[14].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW9) ;
};
keyW9.addEventListener("pointermove", keyW9_pntMove, false) ;     // PointerMove
function keyW9_pntMove(){
 pntMove(keyW9) ;
};
keyW9.addEventListener("pointerup", keyW9_pntUp, false) ;         // PointerUp
function keyW9_pntUp(){
 keyW9.setAttribute("fill","#FFFFFF");
 keyW9_trigger_flag = 0;
 pntUp(keyW9) ;
};
keyW9.addEventListener("pointercancel", keyW9_pntCancel, false) ; // PointerCancel
function keyW9_pntCancel(){
 keyW9.setAttribute("fill","#FFFFFF");
 keyW9_trigger_flag = 0;
 pntCancel(keyW9) ;
};
// データ的な最左端
let keyW0 = document.getElementById("w0") ;
keyW0.addEventListener("pointerdown", keyW0_pntDown, false) ;     // PointerDown
function keyW0_pntDown(){
 if(keyW0_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyW0_trigger_flag = 0;
 }
 keyW0.setAttribute("fill","#BBBBBB");
 keyW0_trigger_flag = 1;
 let noteNo = active_key_id_arr[16].note_No ;
 frq = 440 * Math.pow ( 2 , ( ( noteNo - 17 ) - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyW0) ;
};
keyW0.addEventListener("pointermove", keyW0_pntMove, false) ;     // PointerMove
function keyW0_pntMove(){
 pntMove(keyW0) ;
};
keyW0.addEventListener("pointerup", keyW0_pntUp, false) ;         // PointerUp
function keyW0_pntUp(){
 keyW0.setAttribute("fill","#FFFFFF");
 keyW0_trigger_flag = 0;
 pntUp(keyW0) ;
};
keyW0.addEventListener("pointercancel", keyW0_pntCancel, false) ; // PointerCancel
function keyW0_pntCancel(){
 keyW0.setAttribute("fill","#FFFFFF");
 keyW0_trigger_flag = 0;
 pntCancel(keyW0) ;
};
// 黒鍵(Black key)
let keyB1 = document.getElementById("b1") ;
keyB1.addEventListener("pointerdown", keyB1_pntDown, false) ;     // PointerDown
function keyB1_pntDown(){
 if(keyB1_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB1_trigger_flag = 0;
 }
 keyB1.setAttribute("fill","#444444");
 keyB1_trigger_flag = 1;
 let noteNo = active_key_id_arr[1].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB1) ;
};
keyB1.addEventListener("pointermove", keyB1_pntMove, false) ;     // PointerMove
function keyB1_pntMove(){
 pntMove(keyB1) ;
};
keyB1.addEventListener("pointerup", keyB1_pntUp, false) ;         // PointerUp
function keyB1_pntUp(){
 keyB1.setAttribute("fill","#000000");
 keyB1_trigger_flag = 0;
 pntUp(keyB1) ;
};
keyB1.addEventListener("pointercancel", keyB1_pntCancel, false) ; // PointerCancel
function keyB1_pntCancel(){
 keyB1.setAttribute("fill","#000000");
 keyB1_trigger_flag = 0;
 pntCancel(keyB1) ;
};
let keyB2 = document.getElementById("b2") ;
keyB2.addEventListener("pointerdown", keyB2_pntDown, false) ;     // PointerDown
function keyB2_pntDown(){
 if(keyB2_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB2_trigger_flag = 0;
 }
 keyB2.setAttribute("fill","#444444");
 keyB2_trigger_flag = 1;
 let noteNo = active_key_id_arr[3].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB2) ;
};
keyB2.addEventListener("pointermove", keyB2_pntMove, false) ;     // PointerMove
function keyB2_pntMove(){
 pntMove(keyB2) ;
};
keyB2.addEventListener("pointerup", keyB2_pntUp, false) ;         // PointerUp
function keyB2_pntUp(){
 keyB2.setAttribute("fill","#000000");
 keyB2_trigger_flag = 0;
 pntUp(keyB2) ;
};
keyB2.addEventListener("pointercancel", keyB2_pntCancel, false) ; // PointerCancel
function keyB2_pntCancel(){
 keyB2.setAttribute("fill","#000000");
 keyB2_trigger_flag = 0;
 pntCancel(keyB2) ;
};
let keyB3 = document.getElementById("b3") ;
keyB3.addEventListener("pointerdown", keyB3_pntDown, false) ;     // PointerDown
function keyB3_pntDown(){
 if(keyB3_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB1_trigger_flag = 0;
 }
 keyB3.setAttribute("fill","#444444");
 keyB3_trigger_flag = 1;
 let noteNo = active_key_id_arr[6].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB3) ;
};
keyB3.addEventListener("pointermove", keyB3_pntMove, false) ;     // PointerMove
function keyB3_pntMove(){
 pntMove(keyB3) ;
};
keyB3.addEventListener("pointerup", keyB3_pntUp, false) ;         // PointerUp
function keyB3_pntUp(){
 keyB3.setAttribute("fill","#000000");
 keyB3_trigger_flag = 0;
 pntUp(keyB3) ;
};
keyB3.addEventListener("pointercancel", keyB3_pntCancel, false) ; // PointerCancel
function keyB3_pntCancel(){
 keyB3.setAttribute("fill","#000000");
 keyB3_trigger_flag = 0;
 pntCancel(keyB3) ;
};
let keyB4 = document.getElementById("b4") ;
keyB4.addEventListener("pointerdown", keyB4_pntDown, false) ;     // PointerDown
function keyB4_pntDown(){
 if(keyB4_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB4_trigger_flag = 0;
 }
 keyB4.setAttribute("fill","#444444");
 keyB4_trigger_flag = 1;
 let noteNo = active_key_id_arr[8].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB4) ;
};
keyB4.addEventListener("pointermove", keyB4_pntMove, false) ;     // PointerMove
function keyB4_pntMove(){
 pntMove(keyB4) ;
};
keyB4.addEventListener("pointerup", keyB4_pntUp, false) ;         // PointerUp
function keyB4_pntUp(){
 keyB4.setAttribute("fill","#000000");
 keyB4_trigger_flag = 0;
 pntUp(keyB4) ;
};
keyB4.addEventListener("pointercancel", keyB4_pntCancel, false) ; // PointerCancel
function keyB4_pntCancel(){
 keyB4.setAttribute("fill","#000000");
 keyB4_trigger_flag = 0;
 pntCancel(keyB4) ;
};
let keyB5 = document.getElementById("b5") ;
keyB5.addEventListener("pointerdown", keyB5_pntDown, false) ;     // PointerDown
function keyB5_pntDown(){
 if(keyB5_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB5_trigger_flag = 0;
 }
 keyB5.setAttribute("fill","#444444");
 keyB5_trigger_flag = 1;
 let noteNo = active_key_id_arr[10].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB5) ;
};
keyB5.addEventListener("pointermove", keyB5_pntMove, false) ;     // PointerMove
function keyB5_pntMove(){
 pntMove(keyB5) ;
};
keyB5.addEventListener("pointerup", keyB5_pntUp, false) ;         // PointerUp
function keyB5_pntUp(){
 keyB5.setAttribute("fill","#000000");
 keyB5_trigger_flag = 0;
 pntUp(keyB5) ;
};
keyB5.addEventListener("pointercancel", keyB5_pntCancel, false) ; // PointerCancel
function keyB5_pntCancel(){
 keyB5.setAttribute("fill","#000000");
 keyB5_trigger_flag = 0;
 pntCancel(keyB5) ;
};
let keyB6 = document.getElementById("b6") ;
keyB6.addEventListener("pointerdown", keyB6_pntDown, false) ;     // PointerDown
function keyB6_pntDown(){
 if(keyB6_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB6_trigger_flag = 0;
 }
 keyB6.setAttribute("fill","#444444");
 keyB6_trigger_flag = 1;
 let noteNo = active_key_id_arr[13].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB6) ;
};
keyB6.addEventListener("pointermove", keyB6_pntMove, false) ;     // PointerMove
function keyB6_pntMove(){
 pntMove(keyB6) ;
};
keyB6.addEventListener("pointerup", keyB6_pntUp, false) ;         // PointerUp
function keyB6_pntUp(){
 keyB6.setAttribute("fill","#000000");
 keyB6_trigger_flag = 0;
 pntUp(keyB6) ;
};
keyB6.addEventListener("pointercancel", keyB6_pntCancel, false) ; // PointerCancel
function keyB6_pntCancel(){
 keyB6.setAttribute("fill","#000000");
 keyB6_trigger_flag = 0;
 pntCancel(keyB6) ;
};
let keyB7 = document.getElementById("b7") ;
keyB7.addEventListener("pointerdown", keyB7_pntDown, false) ;     // PointerDown
function keyB7_pntDown(){
 if(keyB7_trigger_flag === 1){
   acx_buf_node.stop() ;
   keyB7_trigger_flag = 0;
 }
 keyB7.setAttribute("fill","#444444");
 keyB7_trigger_flag = 1;
 let noteNo = active_key_id_arr[15].note_No ;
 frq = 440 * Math.pow ( 2 , ( noteNo - 69 ) / 12 ) ; // MIDIノート番号から周波数を算出
 frameCount = ( acx.sampleRate / frq ) ;             //フレーム数(Frame count)
 waveGenerate() ; // 波形生成(Wave generate)
 pntDown(keyB7) ;
};
keyB7.addEventListener("pointermove", keyB7_pntMove, false) ;     // PointerMove
function keyB7_pntMove(){
 pntMove(keyB7) ;
};
keyB7.addEventListener("pointerup", keyB7_pntUp, false) ;         // PointerUp
function keyB7_pntUp(){
 keyB7.setAttribute("fill","#000000");
 keyB7_trigger_flag = 0;
 pntUp(keyB7) ;
};
keyB7.addEventListener("pointercancel", keyB7_pntCancel, false) ; // PointerCancel
function keyB7_pntCancel(){
 keyB7.setAttribute("fill","#000000");
 keyB7_trigger_flag = 0;
 pntCancel(keyB7) ;
};
let SawSquareWaveformArea = document.getElementById("saw_square_waveform_area") ;
let SawSquareWaveformSwitch = document.getElementById("saw_square_waveform_switch") ;
SawSquareWaveformArea.addEventListener("pointerdown", SawSquareSwitch_pntDown, false) ; // PointerDown
SawSquareWaveformSwitch.addEventListener("pointerdown", SawSquareSwitch_pntDown, false) ; // PointerDown
function SawSquareSwitch_pntDown(){
 if ( waveform_flag === 0 ){
   SawSquareWaveformSwitch.setAttribute("transform","translate(32,0)") ;
   waveform_flag = 1 ;
 } else if ( waveform_flag === 1 ){
   SawSquareWaveformSwitch.setAttribute("transform","translate(0,0)") ;
   waveform_flag = 0 ;
 } ;
};
// ポインタダウン(PointerDown)タッチ開始時
function pntDown(pev){
 pev = pev || window.pev ;                       // IE
 i = active_pnt_obj_arr.length ;                 // PointerEventArrayIndex
 active_pnt_obj_arr[i] = {
   pointerId          : pev.pointerId ,          // PointerEvent.pointerId
   date               : Date.now() ,             // ms
   width              : pev.width ,              // PointerEvent.width
   height             : pev.height ,             // PointerEvent.height
   clientX            : pev.clientX ,            //  Touch.clientX or MouseEvent.clientX
   clientY            : pev.clientY              //  Touch.clientY or MouseEvent.clientY
 } ;
 pevdiv.innerHTML = "<b>PointerDown</b>                                                   <br>" + 
                    "length             : " + active_pnt_obj_arr.length                + " - "  +
                    "ID                 : " + i                                        + " - "  +
                    "pointerId          : " + active_pnt_obj_arr[i].pointerId          + "<br>" + 
                    "date               : " + active_pnt_obj_arr[i].date               + "<br>" + 
                    "width              : " + active_pnt_obj_arr[i].width              + " - "  +
                    "height             : " + active_pnt_obj_arr[i].height             + "<br>" + 
                    "clientX            : " + active_pnt_obj_arr[i].clientX            + " - "  +
                    "clientY            : " + active_pnt_obj_arr[i].clientY            + "<br>"
 ;
 acx_buf_node = new myAudioBufferSourceNode() ;
 acx_buf_node.start() ;
} ;
// ポインタムーブ(PointerMove)タッチ移動時
function pntMove(pev){
 pev = pev || window.pev ;                       // IE
 i = active_pnt_obj_arr.length ;                 // PointerEventArrayIndex
 // active_pnt_obj_arr.lengthでforループをまわす
 for (let i = 0; i < active_pnt_obj_arr.length; i++) {
   // active_pnt_obj_arr[i].pointerIdとpev.pointerIdが一致したら
   if ( active_pnt_obj_arr[i].pointerId === pev.pointerId ) {
     defference_date = Date.now() - active_pnt_obj_arr[i].date ;  // Pointer move difference date
     defference_X = pev.clientX - active_pnt_obj_arr[i].clientX ; // Pointer move difference clientX
     // 現役のポインタオブジェクト配列を更新(Update the active pointer object array)
     active_pnt_obj_arr[i] = {
       pointerId          : pev.pointerId ,          // PointerEvent.pointerId
       date               : Date.now() ,             // ms
       width              : pev.width ,              // PointerEvent.width
       height             : pev.height ,             // PointerEvent.height
       clientX            : pev.clientX ,            //  Touch.clientX or MouseEvent.clientX
       clientY            : pev.clientY ,            //  Touch.clientY or MouseEvent.clientY
       defferenceDate     : defference_date ,        // Pointer move difference date
       defferenceX        : defference_X             // Pointer move difference clientX
     } ;
     pevdiv.innerHTML = "<b>PointerMove</b>                                                   <br>" + 
                        "length             : " + active_pnt_obj_arr.length                + " - "  + 
                        "ID                 : " + i                                        + " - "  + 
                        "pointerId          : " + active_pnt_obj_arr[i].pointerId          + "<br>" + 
                        "date               : " + active_pnt_obj_arr[i].date               + "<br>" + 
                        "width              : " + active_pnt_obj_arr[i].width              + " - "  +
                        "height             : " + active_pnt_obj_arr[i].height             + "<br>" + 
                        "clientX            : " + active_pnt_obj_arr[i].clientX            + " - "  +
                        "clientY            : " + active_pnt_obj_arr[i].clientY            + "<br>" + 
                        "defferenceDate     : " + active_pnt_obj_arr[i].defferenceDate     + " - "  +
                        "defferenceX        : " + active_pnt_obj_arr[i].defferenceX        + "<br>"
     ;
   } ;
 } ;
} ;
// ポインタアップ(PointerUp)タッチ離脱時
function pntUp(pev){
 pev = pev || window.pev ;                       // IE
 i = active_pnt_obj_arr.length ;                 // PointerEventArrayIndex
 pevdiv.innerHTML = "<b>PointerUp</b><br>"
 ;
 // active_pnt_obj_arr.lengthでforループをまわす
 for ( let i = 0 ; i < active_pnt_obj_arr.length ; i++ ) {
   // active_pnt_obj_arr[i].pointerIdとpev.pointerIdが一致したら
   if ( active_pnt_obj_arr[i].pointerId === pev.pointerId ) {
     // slice()でi番目の要素を1個だけ削除する
     active_pnt_obj_arr.splice( i , 1 ) ;
     pevdiv.innerHTML = "<b>PointerUp</b><br>" + 
                        "length : " + active_pnt_obj_arr.length + "<br>" + 
                        "ID : " + i + "<br>"
     ;
   } ;
 } ;
 acx_buf_node.stop() ;
} ;
// ポインタキャンセル(PointerCancel)タッチ無効時
function pntCancel(pev){
 pev = pev || window.pev ;                       // IE
 i = active_pnt_obj_arr.length ;                 // PointerEventArrayIndex
 // active_pnt_obj_arr.lengthでforループをまわす
 for ( let i = 0 ; i < active_pnt_obj_arr.length ; i++ ) {
   // active_pnt_obj_arr[i].pointerIdとpev.pointerIdが一致したら
   if ( active_pnt_obj_arr[i].pointerId === pev.pointerId ) {
     // slice()でi番目の要素を1個だけ削除する
     active_pnt_obj_arr.splice( i , 1 ) ;
     pevdiv.innerHTML = "<b>PointerCancel</b><br>" + 
                        "length : " + active_pnt_obj_arr.length + "<br>" + 
                        "ID : " + i + "<br>"
     ;
   } ;
 } ;
 acx_buf_node.stop() ;
} ;

// -----------------
// Piano Keyboad GUI
// -----------------
let key_mode_128 = 0 ;       // 128 key mode flag( 0 : 88 key mode , 1 : 128 key mode )
let left_end = 60 ;          // GUI的に最左端の鍵盤のMIDIノート番号(Left end MIDI note)
let key_ID = 0 ;             // getElementById用の各鍵盤ごとSVGのrect id(SVG rect id of keyboad GUI)
let note_No = 60 ;           // 配列に格納するためのMIDIノート番号(MIDI note number)
let active_key_id_arr = [] ; // 現役で描画されている鍵盤情報格納用の配列(Active keyboad id array)
// 現役の鍵盤配列(Active keyboard array)
function active_keyboad_array(){
 note_No = left_end ;
 for ( let i = 0 ; i < 16 ; i++ ) {
   active_key_id_arr[i] = {
     key_ID  : i ,
     note_No : note_No
   } ;
   note_No++ ;
   ;
 } ;
} ;
// 現役の鍵盤配列(Active keyboard array)
active_keyboad_array() ;

// -----------
// Java Script
// -----------
// window.onload
function onLoad(){
 silent_mode_flag = 1 ;                    // サイレントモードフラグ(Silent mode flag)
 window.setTimeout(setTime4000ms,4*1000) ; // first wait 4000ms
} ;
// first wait 4000ms
function setTime4000ms(){
 if ( silent_mode_flag != 0 ) {
   tap_to_unmute_white_back.setAttribute("width","64") ;
   tap_to_unmute_text.innerHTML = "" ;
 } ;
} ;
window.onload = onLoad() ;
</script>

<div id="pevdiv"></div><br>

</body>

</html>

 Web Audio API にデフォルトで用意されているオシレータノードを使わずにスクラッチからノコギリ波やスクエア波を書き起こしていますので、このあたりのバッファ処理のフローは、個人的な備忘録としても有用かな、という思いがあります。

ハマりポイント

 つくりはじめて最初にハマったポイントは「 iPhone のマナーモードでは音が鳴らない 」という点です。演奏中は通知音などは要らないのでマナーモードで音を鳴らしたい思いがあり、いろいろ調べてみたところ、

[1]HTML Audio の再生でマナーモードを解除できる

[2]ブラウザのオートプレイポリシーに準拠する

上記で解決することがわかりました。

わたしには「 index.htm だけで完結させたい 」という思いがありましたので「 リンクした外部の波形ファイルを読み込む 」というアイディアではなく「 index.htm の中に埋め込んだバイナリデータで HTML Audio を再生する 」という必要がありました。そのためにバッファを一度BlobURLに変換し、その BlobURL を HTML Audio の src に渡す、というフローで解決しました。

これからの予定

 現在のハマっているポイントは「 正しくポリフォニックする 」です。

いままではシンセサイザーの機能として当たり前だと見過ごしていましたが、明確にポリフォニックなシステムを作らないと、モノでもポリでもない、すごく中途半端なものになってしまう、という発見がありました。

 これからも、すごく初歩的なところで多くのつまづきポイントと遭遇しそうな予感がしていますが、定期的にアウトプットしながらブラッシュアップしていければ、という思いでいます。

 みなさまからいただいたフィードバックは、なるべく反映したいと思います。ご興味のあるかたは、ぜひさわってみてください。

この記事が気に入ったらサポートをしてみませんか?