从小我就想要一个架子鼓。那时的音乐设备没有我们今天这些丰富的数字应用。最近我决定从eBay购买最便宜的架子鼓,最低要求是能够拆下来并将我自己软硬件附加到设备上。
这次入手令人相当满意:便携式可折卷的架子鼓套件,带9个不同的音垫(sound pad),两个脚踏开关,一个击鼓,一个击铙(hi-hat),还有一个Micro USB电源插座。真正令人兴奋的是输出声音(将架子鼓套件连接音箱来欣赏)。我决定将这玩意儿转换为自己的可编程设备,通过USB连接基于Arduino的MIDI架子鼓,用户界面使用Python来编写。这样可以方便使用和轻松修改,如音量、音符及选择通道。
译注:hi-hat在wiki百科中的解释就是两片钹加一个踏板,就是下图这个东西。不知道翻译成“铙”对不对。原文的鼓组图片不是很清楚,从wikipedia上找了张图,看图一目了然:
设备亮点:
- 价格亲民
- 可以从任何数字输入 - 甚至是Arduino的按钮阵列来创建架子鼓组合
- 仅通过USB接口提供通信支持和电源 - 集成了USB至UART转换器和Arduino设备
- 仅需最少的部件即可正常操作
- 基于Python的易于使用的用户界面
- 带可调节力度、音符及Arduino引脚的完整的MIDI支持
- 保存并加载存储在设备内存中的自定义架子鼓配置
步骤1 操作原理
项目框图
如图,我们将项目结构划分若干部分:
1. 可折卷架子鼓
这部分是项目的主体。它由9个单独的击打鼓垫组成,每个打击垫都是一个按钮阵列,可在击打时改变逻辑状态。它的结构可以让任何按钮来构造特定的鼓组。每个鼓垫都连接到主板上的上拉电阻,当反复击打鼓垫时,特定开关连接到电路的接地,鼓垫线路上呈现逻辑低电平。如果没有施加压力,则鼓垫开关断开,由于接了上拉电阻,鼓垫线路上呈现逻辑高电平。由于该项目是创建一个完整的数字MIDI设备,因此可以忽略主PCB上的所有模拟部件。请务必注意,架子鼓有两个踏板,用于击鼓和铙 - 它们同样也接了上拉电阻并和其它鼓垫共享逻辑操作(我们稍后讨论)。
2. Arduino Pro-Micro
这部分是架子鼓的大脑。它的作用是检测鼓垫是否发出信号,并提供所有必要参数的MIDI输出:音符、力度和信号的持续时间。由于鼓垫的数字特性,可以将它们简单地连接到arduino的数字输入(共10个引脚)。为了存储所需的设置和MIDI信息,我们将使用其内存-EEPROM,每次打开设备电源都会从EEPROM加载MIDI信息,从而使其可重新编程和重新配置。另外,Arduino Pro-Micro的尺寸非常小,可以很容易地在架子鼓内部的盒子中放置配。
3. FTDI USB转串行转换器
由于Arduino Pro-Micro没有USB接口,为了能在电脑中对设备功能进行编程和定义,需要将USB接口转换为串行接口。由于设备之间的通信基于UART,而FTDI设备简便易用,因此在本项目中使用FTDI USB转换器。
4. 电脑端应用程序 - Python
涉及到用户界面和快速构建项目的开发时,Python是一个极好的解决方案。UI应用程序可以更加方便的为架子鼓重定义MIDI属性、存储信息、编程设备以及在系统之间进行通信,而无需一遍又一遍地编译代码。我们使用串口与架子鼓进行通信,网上有很多免费模块支持任何类型的串行通信。另外正如稍后要讨论的,UART接口总共由三个引脚组成:RXD,TXD和DTR。DTR用于在Arduino模块上执行重置,因此当我们运行MIDI应用程序或将用户界面连接到程序设备时,完全不需要重新连接USB或任何其他设备。
步骤2 零部件和工具
零部件
工具
- 电烙铁/焊台
- 焊锡
- 细径单芯线
- 镊子
- 切刀
- 老虎钳
- 刀
- 螺丝刀
- 3D打印机(可选-用于制作踏板平台)
软件
步骤3 焊接和组装
焊接和组装过程非常简单,只需将三个模块组合在一起:
- 将Arduino Pro-Micro与FTDI设备连接在一起:
- VBUS-VBUS
- GND-GND
- DTR-DTR
- RXD-TXD
- TXD-RXD
- 卸下架子鼓塑料外壳上的所有螺钉,集中注意力连接鼓垫和板子以及上拉电阻
- 用细线焊接上一步接好的Arduino-FTDI模块:
- 数字输入: [2:11]
- VBUS
- D+
- D-
- GND
- 将模块插入电池盒内,使导线与鼓垫的上拉电阻浮在同一侧
我们已经完成了组装,下一步开始编写代码。
步骤4:编程A:Arduino
下面我们逐段说明Arduino代码:
-
为了正常操作架子鼓,需要包括两个必要的库。EPROM已预先安装在Arduino IDE中,但用于击鼓的去抖模块(debouncer module)必须单独安装。
#include <Debouncer.h><br>#include <EEPROM.h>
-
下面这些开关主要用于调试。如果你想尝试鼓垫与Arduino引脚的连接,并确定所有的数字输入,那么需要定义这些开关。
/* Developer Switches: Uncomment desired mode for debugging or initializing */<br>//#define LOAD_DEFAULT_VALUES // Load constant values instead of EEPROM //#define PRINT_PADS_PIN_NUMBERS // Print pin number that is connected to a pad that was hit via serial port
-
常数字段代表所有默认值,包括鼓垫的枚举。在首次运行设备时,需要了解踏板和铙的确切连接(引脚)。
/* Drum type enumeration */
enum DRUM_POSITION { KICK = 0, SNARE, HIHAT, RIDE, CYMBAL1, CYMBAL2, TOM_HIGH, TOM_MID, TOM_LO, HIHAT_PEDAL };``/* Default values */
const uint8_t DRUM_NOTES[10] = { 36, 40, 42, 51, 49, 55, 47, 45, 43, 48};
const uint8_t DRUM_VELOCITIES[10] = { 110, 100, 100, 110, 110,110,110,110,110,110};
const uint8_t DRUM_PINS[10] = { 8, 6, 4, 3, 11, 9, 5, 10, 2, 7 };`
`/* Kick drum debounce duration */
const uint8_t KICK_DB_DURATION = 30;
-
EEPROM用于存储/加载来自PC应用程序的所有数据。上述地址范围显示了每个鼓垫的MIDI信息的确切位置
/* EEPROM Addresses mapping <br>
Notes: |0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09|
Pins: |0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13|
Velocities |0x14,0x15,0x16,0x17,0x18,0x19,0x20,0x21,0x22,0x23| */
const uint8_t NOTES_ADDR = 0x00;
const uint8_t VELOCITIES_ADDR = 0x14;
const uint8_t PINS_ADDR = 0x0A;
-
全局变量用于确定每个鼓垫的状态,并相应地执行MIDI通信。
/* Global Variables */<br>
uint8_t drumNotes[10], drumVelocities[10], drumPins[10]; // MIDI Variables
uint8_t uartBuffer[64]; // UART Buffer for collecting and storing MIDI Data
Debouncer kick(DRUM_PINS[KICK], KICK_DB_DURATION); // Debouncer object for kick drum
volatile bool previousState[9] = {0,0,0,0,0,0,0,0,0}; // Drum pad previous logic states
volatile bool currentState[9] = {0,0,0,0,0,0,0,0,0}; // Drum pad current logic states
-
EEPROM函数。
/* Store settings in the EEPROM*/
void storeEEPROM() {
memcpy(drumNotes, uartBuffer, 10);
memcpy(drumPins, uartBuffer + 10, 10);
memcpy(drumVelocities, uartBuffer + 20, 10);
for (uint8_t i = 0; i < 10; i++) EEPROM.write(NOTES_ADDR + i, drumNotes[i]);
for (uint8_t i = 0; i < 10; i++) EEPROM.write(PINS_ADDR + i, drumPins[i]);
for (uint8_t i = 0; i < 10; i++) EEPROM.write(VELOCITIES_ADDR + i, drumVelocities[i]);
}```
/ Load settings from the EEPROM/
void loadEEPROM() {
for (uint8_t i = 0; i < 10; i++) drumNotes[i] = EEPROM.read(NOTES_ADDR + i);
for (uint8_t i = 0; i < 10; i++) drumPins[i] = EEPROM.read(PINS_ADDR + i);
for (uint8_t i = 0; i < 10; i++) drumVelocities[i] = EEPROM.read(VELOCITIES_ADDR + i);
}
-
在踏板和Arduino启动同时激活的情况下,初始化变量和编程模式。
-
MIDI通信处理程序,延迟1ms的音符保持时间
/* Play MIDI note function */
-
设备循环操作的setup()和loop()函数:
void setup() {
Serial.begin(115200);
for (uint8_t i = 0; i < 10; i++) {
pinMode(i + 2,INPUT);
}
#ifdef PRINT_PADS_PIN_NUMBERS
while(true) { // Infinite debug loop
for (uint8_t i = 0; i < 10; i++) {
if (!digitalRead(i + 2)) {
Serial.print("Pin No: D");
Serial.print(i + '0'); // Convert number to ASCII character
}
}
}
#else
initValues();
/* Programming mode: If two pedals are pressed while booting - mode is activated */
if (!digitalRead(drumPins[KICK]) && !digitalRead(drumPins[HIHAT_PEDAL])) enterProgrammingMode();
#endif
}
void loop() {<br> for (uint8_t i = 1; i < 9; i = i + 1) {
currentState = digitalRead(drumPins);
if (!currentState && previousState) midiOut(i); // Compare states and detect falling edge
previousState = currentState;
}
kick.update(); // Kick drum uses custom debounce algorithm
if (kick.edge()) if (kick.falling()) midiOut(KICK);
}
步骤5 编程B:Python和用户界面
乍一看Python用户界面有点复杂,下面我们试着解释基本知识、使用方法、每个按钮具有什么功能以及如何正确编程Arduino设备。
用户界面-应用程序
UI用户界面是架子鼓的图形表示形式,它使得在任何时候对Arduino设备的编程既易用又方便。用户界面由几个图形模块组成,这些图形模块与它们的建议操作相关。我们一个个来看:
-
鼓组图片: Python UI使用不同鼓的图片的XY坐标来确定选择了哪种鼓型。如果选择了有效的鼓区,则会显示辅助IO消息,包括音符、力度和鼓垫的Arduino终端。这些参数由用户验证并确认后,会直接传输到Arduino设备。
-
外部控制器图像:为了能够在VST/音乐创建环境中使用MIDI鼓组,需要运行Serial-To-MIDI解释器。我使用过Hairless的,免费,只需按压其图像即可直接从UI运行。
-
COM端口列表:为了与Arduino通信,需要指定其连接的COM端口。按下刷新按钮可以刷新端口列表。
-
加载/保存配置:代码中定义了默认的MIDI值,用户可以通过与UI交互进行修改。在config.txt文件中配置以特定格式定义,可以由用户保存或加载。
-
程序设备按钮:为了将所有修改后的MIDI值存储在Arduino EEPROM中,需要踩下两个脚踏板(鼓和铙),等待数据传输完成。如果出现任何通讯问题,将弹出相关的窗口。如果传输成功,UI显示成功消息。
-
退出按钮:仅在用户许可下退出应用程序。
Python代码重点
代码完成了很多工作,我们下面逐段说明。
为了使用UI,需要先下载几个模块来使代码正常工作:
import os<br>import threading
import tkinter as tk
from tkinter import messagebox
from tkinter import *
from PIL import ImageTk, Image
import numpy as np
import serial
import glob
一些模块包含在Python默认软件包中。可以通过PIP工具安装几个模块:
pip install Pillow
pip install numpy
pip install ScreenInfo
强烈建议通过PyCharm运行应用程序。我计划在将来的版本导出该项目的可执行文件。
简要代码说明
从函数和类的角度看代码会更容易理解:
-
main 函数 - 代码从这里开始
if __name__ == '__main__':<br> drumkit_gui()
-
鼓组常数、坐标和默认MIDI信息
class Drums:<br> DRUM_TYPES = ["Kick", "Hihat", "Snare", "Crash 1", "Crash 2", "Tom High", "Tom Mid", "Tom Low", "Ride",
"Hihat Pedal", "Controller"]
COORDINATES_X = [323, 117, 205, 173, 565, 271, 386, 488, 487, 135, 79]
COORDINATES_Y = [268, 115, 192, 40, 29, 107, 104, 190, 71, 408, 208]
DIMS_WIDTH = [60, 145, 130, 120, 120, 70, 70, 130, 120, 70, 145]
DIMS_LENGTH = [60, 60, 80, 35, 35, 40, 40, 70, 35, 100, 50]
DRUM_ENUM = ["Kick", "Snare", "Hihat", "Ride", "Crash 1", "Crash 2", "Tom High", "Tom Mid", "Tom Low", "Hihat Pedal"]
DRUM_NOTES = [36, 40, 42, 51, 49, 55, 47, 45, 43, 48]
DRUM_VELOCITIES = [110, 100, 100, 110, 110, 110, 110, 110, 110, 110]
DRUM_PINS = [8, 6, 4, 3, 11, 9, 5, 10, 2, 7]
-
UI函数 - 处理用户界面和图形对象
def set_active(ui)
def secondary_ui(drum_type)
class SelectionUi(tk.Frame)
class Application(tk.Frame)
def drumkit_gui()
def event_ui_clicked(event)
def getorigin(self, event)
-
串行通讯
def get_serial_ports()
def communicate_with_arduino(port)
-
使用文件:从txt文件存储/加载设置
def save_config()
def load_config()
-
使用Python线程功能运行外部应用程序hairless.exe
class ExternalExecutableThread(threading.Thread)
def run_hairless_executable()
为了运行代码,必须在项目文件夹中附加一个文件列表:
- drumkit.png:定义了我们UI上所有可击打的鼓垫图像(下图)
要让架子鼓正常工作,以上这些就是我们需要强调的所有内容。将文件添加到项目中非常重要:鼓组图片、hairless.exe可执行文件和设置文件config.txt。请在附件中下载。
现在...大功告成啦!:)
感谢阅读!:)
原文链接:https://www.instructables.com/id/MIDI-Drum-Kit-on-Python-and-Arduino/
作者:Faransky
翻译:szjuliet
|
|