如何用 Express.js & Socket.io 构建协同 MIDI App
如何用 Express.js & Socket.io 构建协同 MIDI App
在本教程中,我将向你展示如何构建一个应用程序,让你可以使用 midi 键盘与朋友实时制作音乐!这是我们正在建造的:
Key light up pink for you, blue for other users.
如你所见,弹奏一个音符会点亮一个粉红色的键,并在列表中显示输入数据。如果另一个用户加入该会话,他们的输入将显示为蓝色,他们的数据将在列表中显示为蓝色条目。
我们将该过程分为 5 个部分:
- 使用 Express.js 创建应用程序
- 连接 Midi 控制器
- 为协作添加 Socket.io
- 式样
- 部署到 Heroku
第一步:创建一个 Express.js 应用
我们将首先创建一个目录,并使用以下三个终端命令初始化项目:
mkdir midi-collaboration
cd midi-collaboration
npm init
NPM 实用程序将要求提供一些信息来设置 package.json 文件。请随意提供这些信息,或者按 enter 键暂时将这些字段留空。
接下来,添加 Express:
npm install express --save
在 package.json 中,您现在会看到 Express 作为一个依赖项包含在内。
在根文件夹中,使用以下内容创建 server.js:
touch server.js
在 server.js 中,添加以下内容:
var express = require('express');
var app = express();
const port = process.env.PORT || 8080;
var server = app.listen(port);app.use(express.static('public'));
这将创建一个 Express 实例,并将端口默认设置为 8080。app.use 指示 express 提供“公共”文件夹中的文件。如果你现在运行服务器,你会得到一个错误。那是因为我们还没有公共文件夹!
公共文件夹
让我们创建公共文件夹:
mkdir public
cd public
在里面,我们将创建 index.html、index.js 和一个包含 style.css 的 CSS 文件夹:
touch index.html
touch index.js
mkdir css
cd css
touch style.css
“公共”文件夹应该如下所示:
public
--> index.html
--> index.js
css
--> style.css
index.js 应该链接到 style.css 和 index.js。所以让我们添加以下内容:
<!-- /public/index.html --><html>
<head>
<title>Midi Collaboration</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div>
<h1>MIDI Collaboration</h1>
<ul id="midi-data">
</ul>
</div>
</body>
<script src="index.js" type="text/javascript"></script>
</html>
这就产生了一个简单的头,后面跟着一个空的无序列表——这是我们记录 midi 数据的地方。在主体之后,它引用 index.js。现在,让我们向 index.js 添加一个简单的 console.log,这样我们就可以确保它正常工作:
// public/index.jsconsole.log('index.js is connected!');
更新 package.json
最后,我们希望更新 package.json,这样我们就可以用终端命令' npm start '运行我们的服务器。将下面一行添加到“脚本”中:
"start": "node server.js"
您的 package.json 应该如下所示:
{
"name": "remaking-midi-maker",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.15.3"
}
}
确定您在 midi-collaboration 文件夹中,并启动您的应用程序:
npm start
太好了!它现在位于 http://localhost:8080/
步骤 2:连接您的 Midi 控制器
我们将使用 Web Midi API 将 USB midi 控制器连接到应用程序。如果你看到“index.js 已连接!”在您的控制台中,让我们将 console.log 替换为:
// public/javascript/index.jsvar context = new AudioContext();
var oscillators = {};
var midi, data;if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess({
sysex: false
}).then(onMIDISuccess, onMIDIFailure);
} else {
console.warn("No MIDI support in your browser");
}function onMIDISuccess(midiData) {
console.log(midiData);
midi = midiData;
var allInputs = midi.inputs.values();
for (var input = allInputs.next(); input && !input.done; input = allInputs.next()) {
input.value.onmidimessage = onMIDImessage;
}
}function onMIDIFailure() {
console.warn("Not finding a MIDI controller");
}
如果您的 midi 控制器已连接,您应该会看到 MIDIAccess 对象的 console.log:

在 onMIDISuccess()中,for 循环监听 midi 消息。现在它应该在控制台中导致一个错误。为什么?因为我们还没有定义当它接收到一个 midi 信息时要做什么。
让我们创建循环中引用的 onMIDImessage 函数:
function onMIDImessage(messageData) {
var newItem = document.createElement('li');
newItem.appendChild(document.createTextNode(messageData.data));
newItem.className = 'user-midi';
document.getElementById('midi-data').prepend(newItem);
}
这个函数创建一个新的
- 元素。它用我们的 midi 数据附加一个文本节点。它添加了一个 user-midi 的 css 类(这在后面会很重要)。最后,它用 id“midi-data”将新的列表项添加到无序列表中。

Midi keyup and keydown events
很酷,是吧?
但是这些数字意味着什么呢?还有:声音在哪里?
MIDI 协议
MIDI 协议本身就是一个兔子洞,但出于我们的目的,您可以通过一个简单的图表来理解这些数字:
开/关→ 144 / 128
螺距→0–127
速度→0–127
在处理这些数据时,我们将第一个数字视为一个开关。144 =开,128 =关。第二个数字是音高的范围。在我们这里的用法中,最终速度输入也可以理解为体积。
如果你想更深入地了解 midi,这里的是开始的好地方。
声音
MIDI 数据不是声音,而是一组方向:开/关、音高、力度。我们需要制造自己的合成器,将这些信息转换成音乐音调。
首先,让我们将数组中的值集转换成一个可以传递给声音播放器的“note”对象。在 onMIDImessage 函数中,添加:
var d = messageData.data; // Example: [144, 60, 100]
var note = {
on: d[0],
pitch: d[1],
velocity: d[2]
}
play(note);
上面,变量' d '被赋予了输入数据:一个由三个数字组成的数组。这三个值通过它们的索引进行访问,并被指定为对象属性的值。然后,音符对象被传递给播放函数。让我们编写这个函数:
function play(note){
switch(note.on) {
case 144:
noteOn(frequency(note.pitch), note.velocity);
break;
case 128:
noteOff(frequency(note.pitch), note.velocity);
break;
} function frequency(note) {
return Math.pow(2, ((note - 69) / 12)) * 440;
} function noteOn(frequency, velocity) {
var osc = oscillators[frequency] = context.createOscillator();
osc.type = 'sawtooth';
osc.frequency.value = frequency;
osc.connect(context.destination);
osc.start(context.currentTime);
} function noteOff(frequency, velocity) {
oscillators[frequency].stop(context.currentTime);
oscillators[frequency].disconnect();
}
}
该功能检查音符是开(144)还是关(128),并触发适当的命令。noteOn 和 noteOff 都引用了我们在 index.js 顶部建立的两个全局变量来处理声音的开始和停止。
frequency 函数用于将 midi 音符数转换为赫兹频率。如果你好奇,你可以在这里了解更多。
您的应用程序现在应该像合成器一样播放。万岁。
步骤 3:为协作添加 Socket.io
现在是有趣的时候了:在一个会话中同步多个用户!Socket.io 将通过实现“实时双向基于事件的通信”来帮助我们做到这一点。这意味着当 midi 信息在任何浏览器上被触发时,我们可以发送和接收它们。将 Socket.io 添加到项目中:
npm install socket.io --save
您还需要将它添加到 index.html 的头部:
<!-- public/index.html --><script src="[https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js](https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js)"></script>
注意:我使用的是 2.0.3 版本——确保你的 Socket.io 版本与你的 package.json 和 html 脚本相匹配。你可能想要这个链接到 CDN 。
对于连接用户的 Socket.io,我们需要确保:
1)用户可以发送和接收笔记
2)服务器监听用户输入,并将其广播给其他用户
用户发送/接收
首先,让我们确保用户每次弹奏音符时都会发出一个信号。这可以通过打开 index.js 并向 onMIDImessage 函数添加一行代码来实现。确保在定义了注释对象后添加此。
// public/index.js (inside onMIDImessage)socket.emit('midi', note);
我们还想播放来自其他用户的任何外部注释。在 index.js 中,添加以下内容:
// public/index.jsvar socket = io();
socket.on('externalMidi', gotExternalMidiMessage);function gotExternalMidiMessage(data) {
var newItem = document.createElement('li');
newItem.appendChild(document.createTextNode('Note: ' + data.pitch + ' Velocity: ' + data.velocity));
newItem.className = "external-midi";
document.getElementById('midi-data').prepend(newItem); playNote(data);
}
当我们从服务器收到“externalMidi”消息时,它会触发 gotExternalMidiMessage。这个函数看起来应该很熟悉——事实上,我们可以在以后重构它,但是为了清楚起见,现在我们将重复代码。它在视图中显示外部音符的方式几乎与我们处理自己键盘上的 midi 输入的方式相同。然而,我们给了
- 一个类名‘external-midi’。当我们添加风格来区分我们的 midi 输入和外部用户的输入时,这一点很重要。
最后,音符被传递给玩家以触发声音。
服务器
现在让我们在用户之间架起一座桥梁。我们希望处理任何传入的信号,并将它们传递给任何其他会话。
在 server.js 中,需要 Socket.io 并添加函数 newConnection。当服务器获得一个新的连接时,这将被触发。
// server.jsvar socket = require('socket.io');
var io = socket(server);io.sockets.on('connection', newConnection);function newConnection(socket) {
console.log('new connection from:' + socket.id); socket.on('midi', midiMsg);
function midiMsg(data) {
socket.broadcast.emit('externalMidi', data);
}
}
每次建立新连接时,newConnection console.log 都会出现在终端中。我们还有 socket.on 来监听来自任何用户的消息,然后触发 midiMsg,它将数据广播给除了原始用户之外的每个用户。
就这样,我们都准备好了!
第三步:造型
如果你只是想看到你的笔记与其他玩家的不同,你可以在这里走捷径,只需将这些类添加到你的 style.css:
// public/css/sytle.css.user-midi {
color: green;
}.external-midi {
color: blue;
}
就是这样!现在,您将看到自己的输入为绿色,任何外部信号为蓝色。请随意跳到下一步,将您的应用部署到 Heroku!
如果你想创建一个键盘界面,让我们继续:
构建键盘
我们将制作的这款键盘基于@baileyparker 的 CodePen 项目。我们将对设计和功能进行一些修改,以适应我们的使用。首先,在一个单独的窗口中打开这支笔:
HTML
带有类“piano”的 div 存放了我们所有的键,这些键标有各种选择器。按照钢笔中的 html 结构,将 piano div 及其所有键粘贴到您的文档(/public/index.html)中。
CSS
这是我们风格的重大更新。我们导入一个谷歌字体,给身体分配一个背景图片,最后给颜色来区分。用户-midi(粉色)和。外部 midi(蓝色)信号。ul 和 li 元素的设计使得它们可以以令人满意的角度向后倾斜。
键盘样式占据了 CSS 的剩余部分。这里值得注意的是“活动”类,如。钢琴-键-自然:主动'或'。'钢琴-键-自然-外部:主动'。当演奏一个音符时,这些由 Javascript 触发。如果它与数据号匹配,CSS 将激活那个键,对于你的笔记是粉红色的,对于任何外部输入是蓝色的。
当您将笔中的 CSS 复制到项目的样式表(/public/style.css)中时,请确保遵循其中包含的注释。最重要的是,您需要更新到背景的路径。
JS
您将对 Javascript 做同样的事情:将它剪切并粘贴到我们编写的代码下面的/public/index.js 中。同样,阅读并遵循代码中的一些注释也很重要。
这段代码将管理 midi 控制器连接。但是您会希望将重点放在两个函数上:onMidiMessage 和 updateKeyboard。前者处理本地输入,并应用适当的类来照亮按键。后者对外部消息做同样的事情(但使用不同的类)。
要让外部音符点亮键盘,我们需要返回函数 gotExternalMidiMessage。在我们调用 play()之后,添加以下代码:
var msg = { }
msg.data = [];
msg.data.push(data.on);
msg.data.push(data.pitch);
msg.data.push(data.velocity);updateKeyboard(data);
键盘需要一种特定的对象数据结构来更新,所以我们创建 msg 并用笔记中的数据填充它。该 msg 对象被传递给 updateKeyboard,它为该外部信号点亮蓝色的按键。
你现在应该看到你的键为你自己的输入亮起粉红色!当我们连接到 Heroku 添加更多用户时,您会看到这些外部 midi 信息以蓝色亮起!
步骤 4:部署到 Heroku
我不会在这里逐一介绍,而是将您引向 Heroku 的综合文档。他们在审查部署选项方面做得很好:
Git 是一个强大的分散版本控制系统,是将应用程序部署到 Heroku 的手段。
devcenter.heroku.com](https://devcenter.heroku.com/articles/git)
摘要
如果一切顺利,你现在应该有一个应用程序,可以将信号从一个用户传递到下一个用户,并播放每个用户的笔记。对于您的输入和外部输入,键盘应该以不同的颜色亮起。

Completed App!
我对这个协作应用程序感到兴奋。把它连接起来并邀请其他州的朋友登录并播放音乐真是令人兴奋!回过头来看,我也看到了数据可以更有效地传递的方法和设计可以改进的地方——例如,它对小屏幕不友好。
我很想听听你如何使用这个应用程序,我希望它能让你更好地理解如何结合 Express.js、Socket.io 和 Web MIDI API!