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

vanilla_如何使用Vanilla JavaScript构建钢琴键盘相关推荐

  1. vanilla_如何使用Vanilla JavaScript构建简单的全屏幻灯片

    vanilla 在本教程中,您将学习如何使用纯JavaScript创建响应式全屏幻灯片. 要构建它,我们将经历几个不同的前端技巧. 另外,当我们将鼠标悬停在幻灯片上时,我们将更进一步,自定义光标的外观 ...

  2. 使用 Vanilla JavaScript 构建自定义 SPA 路由器

    介绍 在本文中,我将解释如何使用 Vanilla JavaScript 构建自定义 SPA 路由器.我必须在没有任何使用框架的情况下构建一个 UI 项目,并且必须弄清楚如何处理路由,并发现您可以轻松地 ...

  3. vanilla_如何在Vanilla JavaScript中操作DOM

    vanilla by carlos da costa 通过卡洛斯·达·科斯塔 如何在Vanilla JavaScript中操作DOM (How to manipulate the DOM in Van ...

  4. vanilla_使用Vanilla JavaScript构建Cookie库

    vanilla If you're like me, you are always on the lookout for jQuery libraries to use, and there are ...

  5. 使用 Vanilla JavaScript 创建 Web 组件

    Web 应用程序开发是一个非常拥挤的技术领域.有不同类型的框架.库和工具.在开发 Web 应用程序时,主要目标是提供带有封装组件的高质量用户界面 (UI). 因此,当您使用 React.Vue.Ang ...

  6. vanilla_使用Vanilla JavaScript即时搜索

    vanilla Originally posted on www.florin-pop.com 最初发布在www.florin-pop.com The theme for week #15 of th ...

  7. vanilla_包装Vanilla JavaScript软件包以在React中使用

    vanilla Complex web projects often require the use of 3rd party widgets. But what if you're using a ...

  8. electron 桌面程序_如何使用Electron使用JavaScript构建您的第一个桌面应用程序

    electron 桌面程序 by Carol-Theodor Pelu 通过Carol-Theodor Pelu 如何使用Electron使用JavaScript构建您的第一个桌面应用程序 (How ...

  9. 使用钢琴键盘作为电脑键盘[关闭]

    我有RSI问题,并尝试了30种不同的电脑键盘,这些都给我带来了痛苦. 弹钢琴不会让我感到痛苦. 我已经弹钢琴大约20年没有任何疼痛问题. 我想知道是否有办法从MIDI键盘捕捉MIDI并输出键盘敲击. ...

最新文章

  1. 电机贴上锡纸到底有没有作用?
  2. Windows下部署最新版青龙、诺兰、傻妞入门保姆级教程
  3. 【Android RTMP】NV21 图像旋转处理 ( 图像旋转算法 | 后置摄像头顺时针旋转 90 度 | 前置摄像头顺时针旋转 90 度 )
  4. internal error:failed to get path of 64-bit Program Files directory
  5. 「雅礼集训 2017 Day5」珠宝
  6. SQL Server修改表结构后批量更新所有视图
  7. 删除临时表并且插入数据
  8. vue内引入语音播报功能
  9. android 支付宝月账单 统计图_@三明人 支付宝年度账单来了!今天的你晒账单了吗?...
  10. 黑马程序员—银行调度系统
  11. ppt中插入html格式图表
  12. 呼叫中心点击拨打接口升级代码
  13. mac air 2019安装双系统
  14. “老赖”罗永浩:“首席忽悠官”,发布黑科技鲨纹技术
  15. win7计算机盘共享,win7电脑如何共享文件夹 win7电脑共享文件夹操作方法
  16. 关于柔性显示器工作原理及发展前景的探究
  17. java方法 判断今天是工作日/周末/节假日
  18. 我如何在GitHub Project上获得1,000个星星,以及在此过程中学到的教训
  19. [Linux 基础] -- Linux input 子系统要点总结
  20. 玩vr游戏的计算机配置要求,玩虚拟现实游戏,电脑配置必须强大如此

热门文章

  1. 汉白玉产地在哪里_汉白玉产地是在哪里的
  2. 笔记:时间序列相关问题
  3. 第二证券|昨日涨停,今日1分钟闪崩跌停,超1亿资金排队“出逃”!
  4. 三种中间件的刷盘(持久化)策略
  5. escapexml java_JSTL fn:escapeXml()函数 | 菜鸟教程
  6. matlab能给图像编组么,Matlab如何画箱线图群组
  7. Android 如何应用ttf图标字体库
  8. 把数字金额转换成人民币大写[原创]
  9. 自学Java最起码要学到什么程度?
  10. 天逸100装不了linux,联想天逸100重装系统后不能开机