搬运 JS在线钢琴模拟器代码

源码之家
MDN AudioContext
MDN MouseEvent.buttons
MDN BaseAudioContext.createOscillator()
MDN OscillatorNode
MDN AudioNode.connect()
MDN BaseAudioContext.createGain()
MDN AudioContext.destination
MDN GainNode
MDN AudioParam.linearRampToValueAtTime()
MDN AudioContext.currentTime
MDN OscillatorNode.start()

AudioContext

表示由链接在一起的音频模块构建的音频处理图,每个模块由一个AudioNode表示

MouseEvent.buttons

返回值

  • 0 : 没有按键或者是没有初始化
  • 1 : 鼠标左键
  • 2 : 鼠标右键
  • 4 : 鼠标滚轮或者是中键
  • 8 : 第四按键 (通常是“浏览器后退”按键)
  • 16 : 第五按键 (通常是“浏览器前进”)

eval()函数

把一个字符串当作一个JavaScript表达式一样去执行它

直接输入id => 可以获取DOM节点

BaseAudioContext.createOscillator()

创建一个OscillatorNode,它是一个表示周期性波形的源。 它基本上产生一个不变的音调。

OscillatorNode

表示一个振荡器,它产生一个周期的波形信号(如正弦波)。
它是一个 AudioScheduledSourceNode 音频处理模块,
这个模块会生成一个指定频率的波形信号(即一个固定的音调)

AudioNode.connect()

方法使你能将一个节点的输出连接到一个指定目标,
这个指定的目标可能是另一个 AudioNode(从而将音频数据引导到下一个指定节点)或一个AudioParam,
以便上一个节点的输出数据随着时间流逝能自动地对下一个参数值进行改变。

OscillatorNode.frequency

一个 a-rate AudioParam 对象的属性代表了振动的频率(单位为赫兹hertz)
(虽然返回的AudioParam 是只读的,但是它所表示的值是可以修改的)。
默认值是 440 Hz (基本的中A音高).

BaseAudioContext.createGain()

创建一个GainNode, 可用于控制音频图的总体增益(或音量)。

AudioContext.destination

返回一个AudioDestinationNode表示context中所有音频(节点)的最终目标节点,
一般是音频渲染设备,比如扬声器。

GainNode

表示音量的变化。
它是一个AudioNode音频处理模块,在输出前使用给定增益应用到输入。
一个 GainNode 总是只有一个输入和一个输出,两者拥有同样数量的声道。

增益是一个无单位的值,会对所有输入声道的音频进行相应的增加。
如果进行了修改,则会立即应用新增益,
从而在结果音频中产生奇怪的“咔嗒”声。
为了防止这种情况发生,请不要直接更改值,
而应在AudioParam接口上使用指数插值方法。

AudioParam.linearRampToValueAtTime()

方法可调度AudioParam值的逐渐线性变化。
更改从为上一个事件指定的时间开始,
然后线性增加到value参数中给定的新值,
并在endTime参数中给定的时间达到新值。

AudioContext.currentTime

一个read-only属性,返回double秒(从0开始)表示一个只增不减的硬件时间戳,
可以用来控制音频回放,实现可视化时间轴等等。

OscillatorNode.start()

方法指定开始播放音调的确切时间。
它的参数是可选的,默认为0。

<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>JS在线钢琴模拟器代码 - 源码之家</title>

<link rel="stylesheet" href="css/style.css">

</head>
<body>

<span id="K"></span>
八度音 <input type=number id=O value=0 min=-3 max=3 onkeydown='return false'> 
Key <input type=number id=Y value=0 min=-12 max=12 onkeydown='return false'><br>

<script  src="js/script.js"></script>
<div style="text-align:center;">
<p>更多源码:<a href="http://www.mycodes.net/" target="_blank">源码之家</a></p>
</div>

</body>
</html>
body
{
  background:#112;
  color:#fff;
  user-select:none;
  text-align:center;
  style=font-size:30px;
  font-family:arial;
}
input
{
  width:40;
}
///////////////////////////////////////////////////////////////////////////////
/* 1Keys - A 1 Kilobyte JavaScript Piano - CodePen Version

By Frank Force 2020
GNU General Public License v3.0
https://github.com/KilledByAPixel/1Keys
Orignally made for JS1024

1k Features...
- Three different instrument sounds
- Keyboard and mouse support
- Multiple notes can be played independently
- Notes can be held for any length of time
- Keys light up red when played
- Sounds stop if focus is lost
- Sounds ramp on and off to eliminate pops
- Pure vanilla JS with no external files

Extra CodePen Features...
- Octave and Key control
- Show keyboard keys on piano
- Sine instrument

*/

C = new AudioContext; // audio context
A = [];               // active sounds
I = 0;                // instrument type

// instrument select
[...`∿🎻🎷🎹`].map((i,j)=>                           // instrument icons
    K.innerHTML += i +                                 // icon
    `<input type=radio name=I checked onmousedown=I=${ // radio input
    3 - j}>  `);                                  // instrument select

// piano keys
for(i = 0; i < 36; i++) // 3 x 12 keys

    document.body.innerHTML +=
        `${i%12 ? `` : `<br>`                                       // new row
        }<div id=K${                                                // create key
            k = 24 + i%12 - (i/12|0)*12                             // reorder keys
        } style='display:inline-block;outline:3px solid #000;background:${ // style
        (w = `02579`.indexOf(i%12 - 1) < 0) ?                       // b or w?
        `#fff;color:#000;width:60px;height:140px` :                 // white
        `#000;position:absolute;margin-left:-15px;width:29px;height:79px` // black
        }'onmouseover=event.buttons&&P(${ k                         // mouse over
        }) onmousedown=P(${ k                                       // mouse down
        }) onmouseup=X(${ k                                         // mouse up
        }) onmouseout=X(${ k                                        // mouse out
        })>` + (w ? `<br>` : ``) +                                  // lower white keys
        `ZSXDCVGBHNJMQ2W3ER5T6Y7UI9O0P[=]    `[k];                  // show key

///////////////////////////////////////////////////////////////////////////////
// sound

// play note
P = i=> i < 0 || A[i] || // is valid and note not playing?
(
    k = eval(`K`+i),            // get key
    k.g = k.style.background,   // save original color 
    k.style.transition = ``,    // unset transition
    k.style.background = `red`, // set key color red
    k.innerHTML,                // force reset transition

    A[i] = [         // instruments
        [...`1248`], // 🎹 organ
        [...`3579`], // 🎷 brass
        [...`123`],  // 🎻 strings
        [...`4`],    // ∿ sine
    ][ I ].map(j=>(
        o = C.createOscillator(),       // create oscillator
        o.connect(                      // oscillator to gain1
            g = C.createGain(           // create start gain node
                o.frequency.value =     // set frequency
                    j * 55 *            // A 55 root note
                    2**((i+3)/12        // music scale formula
                        + O.value*1     // octave control (O:DOM节点)
                        + Y.value/12))) // key control (Y:DOM节点)
        .connect(                       // gain1 to gain2
            o.g = C.createGain())       // create end gain node
        .connect(C.destination),        // gain2 to destination
        g.gain.linearRampToValueAtTime( // set start gain off
            g.gain.value = 0,           // set gain to 0
            C.currentTime + .02),       // set start time
        g.gain.linearRampToValueAtTime( // set gain start ramp
            .2/(1+Math.log2(j)),        // scale gain
            C.currentTime + .05),       // ramp on gain
        o.start(),                      // play sound
        o)                              // return sound
    )
);

// cancel note
X = i=> A[i] &&  // is note playing?
(
    k = eval(`K`+i),            // get key
    k.style.transition = `.5s`, // set transition
    k.style.background = k.g,   // reset original color

    A[i].map(j=>
        setTimeout(e=>j.stop(), 350,          // stop sound after delay
            j.g.gain.linearRampToValueAtTime( // set full gain
                1, C.currentTime + .02),      // wait a split second
            j.g.gain.linearRampToValueAtTime( // ramp gain off
                A[i] = 0, C.currentTime + .3) // clear note  
        )
    )
);

// stop all sounds if focus lost
onblur = e=> A.map((e,i)=> X(i));

///////////////////////////////////////////////////////////////////////////////
// keyboard controls

// keyboard key to piano key
T = i=>
    (k = `zsxdcvgbhnjm,l.;/q2w3er5t6y7ui9o0p[=]` // map key to note
    .indexOf(i.key.toLowerCase()),               // find key in string
    k - 5 * (k > 16));                           // remap second row of keys

// play note on key down
onkeydown = i=> P(T(i));

// release note on key up
onkeyup = i=> X(T(i));