vanilla_如何使用Vanilla JavaScript构建钢琴键盘

vanilla

Making a playable piano keyboard can be a great way to learn a programming language (besides being heaps of fun). This tutorial shows you how to code one using vanilla JavaScript without the need for any external libraries or frameworks.

制作可弹奏的钢琴键盘可能是学习编程语言的一种好方法(除了很有趣之外)。 本教程向您展示如何使用Vanilla JavaScript编写代码,而无需任何外部库或框架。

Here is the JavaScript piano keyboard I made if you want to check out the end product first.

如果您想首先检出最终产品,这是我制作的JavaScript钢琴键盘

This tutorial assumes you have a basic understanding of JavaScript such as functions and event handling, as well as familiarity with HTML and CSS. Otherwise, it is totally beginner friendly and geared toward those who want to improve their JavaScript skills through project-based learning (or just want to make a cool project!).

本教程假定您对JavaScript具有基本的了解,例如功能和事件处理,以及对HTML和CSS的熟悉。 否则,它完全是初学者友好的产品,适合那些希望通过基于项目的学习提高JavaScript技能的人(或者只是想做一个很棒的项目!)。

The piano keyboard we are making for this project is based on the dynamically generated synthetic keyboard made by Keith William Horwood. We will extend the number of keys available to 4 octaves and set new key bindings.

我们为该项目制作的钢琴键盘基于Keith William Horwood生产的动态生成的合成键盘 。 我们将可用的键数扩展到4个八度,并设置新的键绑定。

Although his keyboard can play sounds from other instruments, we will keep things simple and just stick with piano.

尽管他的键盘可以演奏其他乐器的声音,但我们将使事情变得简单,只需要坚持使用钢琴即可。

Here are the steps we will take to tackle this project:

这是我们将要解决的项目的步骤:

1.      Get working files

1. 获取工作文件

2.      Set up key bindings

2. 设置按键绑定

3.      Generate keyboard

3. 生成键盘

4.      Handle key presses

4. 处理按键

Let’s get started!

让我们开始吧!

1.获取工作文件 (1. Get Working Files)

This tutorial will use the following files:

本教程将使用以下文件:

·        audiosynth.js

· audiosynth.js

·        playKeyboard.js

· playKeyboard.js

As mentioned, we will base our piano keyboard off the one made by Keith. Naturally, we will also borrow some of his code which he has kindly given permission with audiosynth.js.

如前所述,我们将基于Keith制造的钢琴键盘。 当然,我们还将借用他的一些代码,这些代码已得到了audiosynth.js的许可。

We incorporate audiosynth.js in playKeyboard.js (my modified version of some of Keith’s code) which handles all our JavaScript. This tutorial gives a detailed explanation in the following sections on the major points of how the code in this file creates a fully working piano keyboard.

我们在处理所有JavaScript的playKeyboard.js(我对Keith的某些代码的修改版本)中合并了audiosynth.js。 本教程在以下各节中详细说明了此文件中的代码如何创建可以正常使用的钢琴键盘的要点。

We leave the file audiosynth.js untouched as it is solely responsible for sound generation.

我们将文件audiosynth.js保持不变,因为它仅负责生成声音。

The code in this file distinguishes this piano keyboard from others found online by using Javascript to dynamically generate the appropriate sound when the user presses a key. Thus, the code does not have to load any external audio files.

该文件中的代码通过在用户按下琴键时使用Javascript动态生成适当的声音,从而将该钢琴键盘与其他在线钢琴键盘区分开来。 因此,该代码不必加载任何外部音频文件。

Keith already provides an explanation of how the sound generation works on his website so we will not get into the details here.

Keith已经在他的网站上提供了声音生成方式的解释,因此我们在这里不再赘述。

In a nutshell, it involves using the Math.sin() function in JS to create sinusoidal waveforms and transforming them so they sound more like real instruments through some fancy math.

简而言之,它涉及使用JS中的Math.sin()函数创建正弦波形并进行转换,以便通过一些精美的数学运算听起来更像是真实的乐器。

Create an index HTML file, and let’s link to the JS files in the header:

创建一个索引HTML文件,然后在标题中链接到JS文件:

<script src="audiosynth.js"></script>
<script src="playKeyboard.js"></script>

In the body, we can create an empty <div> element to serve as our keyboard “container”:

在主体中,我们可以创建一个空的<div>元素用作键盘的“容器”:

<div id= “keyboard”></div>

We give it an id name so that we can reference it later when we create the keyboard using JS. We can run our JS code by calling it in the body as well:

我们给它指定一个id名称,以便以后在使用JS创建键盘时可以引用它。 我们也可以通过在主体中调用JS代码来运行它:

<script type="text/javascript">playKeyboard()</script>

We use playKeyboard.js as one big function. It will run as soon as the browser gets to that line of code and generate a fully working keyboard in the <div> element with id = “keyboard”.

我们使用playKeyboard.js作为一项重要功能。 一旦浏览器到达该行代码,它就会运行,并在<div>元素中生成一个完全正常工作的键盘,其id = “keyboard”

The first few lines of playKeyboard.js sets up for mobile device functionality (optional) and creates a new AudioSynth() object. We use this object to call the methods of audiosynth.js which we linked to earlier. We use one of these methods in the beginning to set a volume for the sound.

playKeyboard.js的前几行设置为移动设备功能(可选),并创建一个新的AudioSynth()对象。 我们使用此对象来调用我们先前链接的audiosynth.js的方法。 我们在一开始使用这些方法之一来设置声音的音量。

On line 11, we set position of middle C to the 4th octave.

在第11行,我们将中间C的位置设置为第4个八度。

2.设置键绑定 (2. Set Up Key Bindings)

Before we generate the keyboard, we should set up our key bindings as they determine how many keys should be generated.

在生成键盘之前,我们应该设置按键绑定,因为它们确定应该生成多少个按键。

I originally wanted to try to play the opening notes of ‘Für Elise’ so I chose a range of 4 octaves for a total of 48 black and white keys. This required nearly every key on my (PC) keyboard and you can feel free to include fewer.

我本来想尝试演奏“FürElise”的开头音符,所以我选择了4个八度的范围,总共48个黑白键。 这几乎需要(PC)键盘上的每个键,您可以随意添加更少的键。

A note of warning: I do not have the best key bindings so they may feel unintuitive when you actually try to play. Maybe this is the price of trying to create a 4-octave keyboard.

警告提示:我没有最好的按键绑定,因此当您实际尝试演奏时,它们可能会感觉不直观。 也许这就是尝试创建4个八度音阶键盘的代价。

To set up the key bindings, first create an object that will use keycode as its keys and the note to be played as its key values (starting line 15):

要设置按键绑定,首先创建一个对象,该对象将使用按键代码作为其按键,并使用要播放的音符作为其按键值(第15行开始):

var keyboard = {
	/* ~ */
	192: 'C,-2',
	/* 1 */
	49: 'C#,-2',
	/* 2 */
	50: 'D,-2',
	/* 3 */
	51: 'D#,-2',
    //...and the rest of the keys
}

The comments denote the keys that a user may press on a computer keyboard. If a user presses the tilde key, then the corresponding keycode is 192. You may get the keycode using a tool such as keycode.info.

注释表示用户可以在计算机键盘上按下的键。 如果用户按下波浪号键,则对应的键码为192。您可以使用诸如keycode.info之类的工具来获取键码。

The key value is the note to be played and written in the format of ‘note, octave modifier’ where the octave modifier represents the relative octave position from the octave containing middle C. For example, ‘C, -2’ is the C note 2 octaves below middle C.

关键值是以“ note,octave修饰符”的格式播放和编写的音符,其中octave修饰符表示从包含中间C的八度起的相对八度位置。例如,“ C,-2”是C音符在中间C下方2个八度音阶

Note that there are no ‘flat’ keys. Every note is represented by a ‘sharp’.

请注意,没有“扁平”键。 每个音符都由“尖锐”表示。

To make our piano keyboard functional, we have to prepare a reverse lookup table where we switch the key: value pairs such that the note to be played becomes the key and the keycode becomes the value.

为了使我们的钢琴键盘正常工作,我们必须准备一个反向查询表,在其中切换key: value对,以便要弹奏的音符成为键,而键码成为值。

We need such a table because we want to iterate over the musical notes to easily generate our keyboard.

我们需要这样一个表,因为我们想遍历音符以轻松生成键盘。

Now here’s where things may get tricky: we actually need 2 reverse lookup tables.

现在这可能会变得棘手:实际上,我们需要2个反向查找表。

We use one table to look up the label we want to display for the computer key we press to play a note (declared as reverseLookupText on line 164) and a second to look up the actual key that was pressed (declared as reverseLookup on line 165).

我们使用一个表来查找我们要显示的标签,以便我们按下该键来播放音符(在第164行上声明为reverseLookupText ),在第二个表中查找所按下的实际键(在第165行上声明为reverseLookup )。

The astute may realize that both lookup tables have keycodes as the values, so what is the difference between them?

精明的人可能会意识到两个查找表都将键码作为值,所以它们之间有什么区别?

It turns out that (for reasons unknown to me) when you get a keycode that corresponds to a key and you try to use String.fromCharCode() method on that keycode, you don’t always get back the same string representing the pressed key.

事实证明(出于我未知的原因),当您获得与某个键相对应的键码,并尝试对该键码使用String.fromCharCode()方法时,您并不总是获取代表所按下键的相同字符串。

For example, pressing left open bracket yields keycode 219 but when you actually try to convert the keycode back to a string using String.fromCharCode(219) it returns "Û". To get "[", you have to use key code 91. We replace the incorrect codes starting on line 168.

例如,按左方括号将产生键代码219,但是当您实际尝试使用String.fromCharCode(219)将键代码转换回字符串时,它将返回“Û”。 要获得“ [”,您必须使用键代码91。我们替换了从第168行开始的错误代码。

Getting the right keycode initially involved a bit of trial and error, but later I realized you can just use another function (getDispStr() on line 318) to force the correct string to be displayed.

获取正确的键码最初需要反复尝试,但是后来我意识到您可以使用另一个函数(第318行的getDispStr() )来强制显示正确的字符串。

The majority of the keys do behave properly but you can choose to start with a smaller keyboard so you don’t have to deal with incorrect keycodes.

大多数键的行为都正常,但是您可以选择从较小的键盘开始,这样就不必处理错误的键码。

3.生成键盘 (3. Generate Keyboard)

We start the keyboard generation process by selecting our <div> element keyboard container with document.getElementById(‘keyboard’) on line 209.

我们通过在第209行上选择带有document.getElementById('keyboard') <div>元素键盘容器来开始键盘生成过程。

On the next line, we declare the selectSound object and set the value property to zero to have audioSynth.js load the sound profile for piano. You may wish to enter a different value (can be 0-3) if you want to try out other instruments. See line 233 of audioSynth.js with Synth.loadSoundProfile for more details.

在下一行,我们声明selectSound对象并将value属性设置为零,以使audioSynth.js加载钢琴的声音配置文件。 如果您想尝试其他乐器,则可能希望输入其他值(可以为0-3)。 有关更多详细信息,请参见带有Synth.loadSoundProfile的audioSynth.js的第233行。

On line 216 with var notes, we retrieve the available notes for one octave (C, C#, D…B) from audioSynth.js.

在带有var notes第216行,我们从audioSynth.js检索了一个八度音阶(C,C#,D…B)的可用音符。

We generate our keyboard by looping through each octave and then each note in that octave. For each note, we create a <div> element to represent the appropriate key using document.createElement(‘div’).

我们通过遍历每个八度,然后遍历该八度中的每个音符来生成键盘。 对于每个音符,我们使用document.createElement('div')创建一个<div>元素来表示相应的键。

To distinguish whether we need to create a black or white key, we look at the length of the note name. Adding a sharp sign makes the length of the string greater than one (ex. ‘C#’) which indicates a black key and vice versa for white.

为了区分是否需要创建黑键或白键,我们查看音符名称的长度。 添加尖锐的符号会使字符串的长度大于一(例如“ C#”),这表示黑键,反之亦然。

For each key we can set a width, height, and an offset from the left based on key position. We can also set appropriate classes for use with CSS later.

对于每个按键,我们可以根据按键位置设置宽度,高度和与左侧的偏移量。 稍后,我们还可以设置适当的类以供CSS使用。

Next, we label the key with the computer key we need to press to play its note and store it in another <div> element. This is where reverseLookupText comes in handy. Inside the same <div>, we also display the note name. We accomplish all of this by setting the label’s innerHTML property and appending the label to the key (lines 240-242).

接下来,我们用需要按下的计算机键来标记该键以播放其音符并将其存储在另一个<div>元素中。 这是reverseLookupText派上用场的地方。 在同一<div> ,我们还显示注释名称。 我们通过设置标签的innerHTML属性并将标签附加到键上来完成所有这些操作(第240-242行)。

label.innerHTML = '<b class="keyLabel">' + s + '</b>' + '<br /><br />' + n.substr(0,1) + 
'<span name="OCTAVE_LABEL" value="' + i + '">' + (__octave + parseInt(i)) + '</span>' + 
(n.substr(1,1)?n.substr(1,1):'');

Similarly, we add an event listener to the key to handle mouse clicks (line 244):

同样,我们向按键添加事件侦听器以处理鼠标单击(第244行):

thisKey.addEventListener(evtListener[0], (function(_temp) { return function() { fnPlayKeyboard({keyCode:_temp}); } })(reverseLookup[n + ',' + i]));

The first parameter evtListener[0] is a mousedown event declared much earlier on line 7. The second parameter is a function that returns a function. We need reverseLookup to get us the correct keycode and we pass that value as a parameter _temp to the inner function. We will not need reverseLookup to handle actual keydown events.

第一个参数evtListener[0]是在第7行的更早处声明的mousedown事件。第二个参数是返回函数的函数。 我们需要reverseLookup来获取正确的键码,然后将该值作为参数_temp传递给内部函数。 我们将不需要reverseLookup处理实际的keydown事件。

This code is pre-ES2015 (aka ES6) and the updated, hopefully clearer equivalent is:

该代码是ES2015之前的版本(又名ES6),并且更新的,更清晰的等效代码是:

const keyCode = reverseLookup[n + ',' + i];
thisKey.addEventListener('mousedown', () => {
  fnPlayKeyboard({ keyCode });
});

After creating and appending all necessary keys to our keyboard, we will need to handle the actual playing of a note.

在将所有必需的键创建并添加到键盘后,我们将需要处理音符的实际演奏。

4.处理按键 (4. Handle Key Presses)

We handle key presses the same way whether the user clicks the key or presses the corresponding computer key through use of the function fnPlayKeyboard on line 260. The only difference is the type of event we use in addEventListener to detect the key press.

无论是用户单击键还是通过使用260行上的fnPlayKeyboard函数来按下相应的计算机键,我们都以相同的方式处理按键。唯一的区别是,在addEventListener用于检测按键按下的事件类型。

We set up an array called keysPressed in line 206 to detect what keys are being pressed/clicked. For simplicity, we will assume that a key being pressed can include it being clicked as well.

我们在第206行中建立了一个名为keysPressed的数组,以检测正在按下/单击的键。 为简单起见,我们将假定按下的键也可以包括被单击的键。

We can divide the process of handling key presses into 3 steps: adding the keycode of the pressed key to keysPressed, playing the appropriate note, and removing the keycode from keysPressed.

我们可以将处理按键的过程分为3个步骤:将按键的键码添加到keysPressed ,播放适当的音符以及从keysPressed删除键码。

The first step of adding a keycode is easy:

添加键码的第一步很简单:

keysPressed.push(e.keyCode);

where e is the event detected by addEventListener.

其中eaddEventListener检测到的事件。

If the added keycode is one of the key bindings we assigned, then we call fnPlayNote() on line 304 to play the note associated with that key.

如果添加的键码是我们分配的键绑定之一,则我们在第304行调用fnPlayNote()来播放与该键关联的音符。

In fnPlayNote(), we first create a new Audio() element container for our note using the generate() method from audiosynth.js. When the audio loads, we can then play the note.

fnPlayNote() ,我们首先使用audiosynth.js中的generate()方法为笔记创建一个新的Audio()元素container 。 加载音频后,我们便可以播放音符。

Lines 308-313 are legacy code and seem they can just be replaced by container.play(), though I have not done any extensive testing to see what the difference is.

第308-313行是旧代码,似乎可以用container.play()代替,尽管我没有做任何广泛的测试来了解它们之间的区别。

Removing a key press is also quite straightforward, as you can just remove the key from the keysPressed array with the splice method on line 298. For more details, see the function called fnRemoveKeyBinding().

删除按键也非常简单,因为您可以使用第298行的splice方法从keysPressed数组中删除按键。有关更多详细信息,请参见函数fnRemoveKeyBinding()

The only thing we have to watch out for is when the user holds down a key or multiple keys. We have to make sure that the note only plays once while a key is held down (lines 262-267):

我们唯一需要注意的是用户按住一个或多个键时的情况。 我们必须确保在按住某个键时,该音符仅播放一次(第262-267行):

var i = keysPressed.length;
while(i--) {
	if(keysPressed[i]==e.keyCode) {
		return false;	
    }
}

Returning false prevents the rest of fnPlayKeyboard() from executing.

返回false阻止fnPlayKeyboard()的其余部分执行。

摘要 (Summary)

We have created a fully functioning piano keyboard using vanilla JavaScript!

我们使用香草JavaScript创建了功能齐全的钢琴键盘!

To recap, here are the steps we took:

回顾一下,这是我们采取的步骤:

  1. We set up our index HTML file to load the appropriate JS files and execute playKeyboard() in <body> to generate and make the keyboard functional. We have a <div> element with id= "keyboard" where the keyboard will be displayed on the page.

    我们设置索引HTML文件以加载适当的JS文件,并在<body>执行playKeyboard()以生成并使键盘起作用。 我们有一个id= "keyboard"<div>元素,键盘将显示在页面上。

  2. In our JavaScript file playKeyboard.js, we set up our key bindings with keycodes as keys and musical notes as values. We also create two reverse lookup tables in which one is responsible for looking up the appropriate key label based on the note and the other for looking up the correct keycode.

    在我们JavaScript文件playKeyboard.js中,我们设置了键绑定,键码为键,音符为值。 我们还创建了两个反向查找表,其中一个负责根据注释查找适当的密钥标签,另一个负责查找正确的密钥代码。

  3. We dynamically generate the keyboard by looping through every note in each octave range. Each key is created as its own <div> element. We use the reverse lookup tables to generate the key label and correct keycode. Then an event listener on mousedown uses it to call fnPlayKeyboard() to play the note. The keydown event calls the same function but does not need a reverse lookup table to get the keycode.

    我们通过遍历每个八度音阶中的每个音符来动态生成键盘。 每个键都创建为自己的<div>元素。 我们使用反向查找表来生成密钥标签和正确的密钥代码。 然后,在mousedown上的事件侦听器使用它来调用fnPlayKeyboard()来播放音符。 keydown事件调用相同的函数,但不需要反向查找表即可获取键码。

  4. We handle key presses resulting from either mouse clicks or computer key presses in 3 steps: add keycode of the pressed key to an array, play the appropriate note, and remove keycode from that array. We must be careful not to repeatedly play a note (from the beginning) while the user continuously holds down a key.

    我们通过3个步骤处理由鼠标单击或计算机按键产生的按键:将按键的键码添加到数组中,播放适当的音符,以及从该数组中删除键码。 当用户连续按住一个键时,我们必须小心不要重复播放音符(从头开始)。

The keyboard is now fully functional but it may look a bit dull. I will leave the CSS part to you 😊

键盘现在可以正常使用,但看起来有些沉闷。 我将把CSS部分留给您😊

Again, here is the JavaScript piano keyboard I made for reference.

同样,这是我参考的JavaScript钢琴键盘

If you want to learn more about web development and check out some other neat projects, visit my blog at 1000 Mile World.

如果您想了解有关Web开发的更多信息并查看其他一些出色的项目,请访问我的博客1000 Mile World

Thanks for reading and happy coding!

感谢您的阅读和愉快的编码!

翻译自: https://www.freecodecamp.org/news/javascript-piano-keyboard/

vanilla

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值