查看: 1563|回复: 3

基于Python和Arduino的MIDI架子鼓

[复制链接]


从小我就想要一个架子鼓。那时的音乐设备没有我们今天这些丰富的数字应用。最近我决定从eBay购买最便宜的架子鼓,最低要求是能够拆下来并将我自己软硬件附加到设备上。

202008316450..png
202008316221..png

这次入手令人相当满意:便携式可折卷的架子鼓套件,带9个不同的音垫(sound pad),两个脚踏开关,一个击鼓,一个击铙(hi-hat),还有一个Micro USB电源插座。真正令人兴奋的是输出声音(将架子鼓套件连接音箱来欣赏)。我决定将这玩意儿转换为自己的可编程设备,通过USB连接基于Arduino的MIDI架子鼓,用户界面使用Python来编写。这样可以方便使用和轻松修改,如音量、音符及选择通道。

202008318789..png

译注:hi-hat在wiki百科中的解释就是两片钹加一个踏板,就是下图这个东西。不知道翻译成“铙”对不对。原文的鼓组图片不是很清楚,从wikipedia上找了张图,看图一目了然:

设备亮点:

  • 价格亲民
  • 可以从任何数字输入 - 甚至是Arduino的按钮阵列来创建架子鼓组合
  • 仅通过USB接口提供通信支持和电源 - 集成了USB至UART转换器和Arduino设备
  • 仅需最少的部件即可正常操作
  • 基于Python的易于使用的用户界面
  • 带可调节力度、音符及Arduino引脚的完整的MIDI支持
  • 保存并加载存储在设备内存中的自定义架子鼓配置

步骤1 操作原理

项目框图

如图,我们将项目结构划分若干部分:

202008313487..png

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或任何其他设备。

202008317133..png

202008317173..png

步骤2 零部件和工具

零部件

202008313306..png

202008314435..png

工具

  • 电烙铁/焊台
  • 焊锡
  • 细径单芯线
  • 镊子
  • 切刀
  • 老虎钳
  • 螺丝刀
  • 3D打印机(可选-用于制作踏板平台)

软件

步骤3 焊接和组装

202008317356..png

焊接和组装过程非常简单,只需将三个模块组合在一起:

  • 将Arduino Pro-Micro与FTDI设备连接在一起:
    • VBUS-VBUS
    • GND-GND
    • DTR-DTR
    • RXD-TXD
    • TXD-RXD

202008315854..png

  • 卸下架子鼓塑料外壳上的所有螺钉,集中注意力连接鼓垫和板子以及上拉电阻

202008316933..png

  • 用细线焊接上一步接好的Arduino-FTDI模块:
    • 数字输入: [2:11]
    • VBUS
    • D+
    • D-
    • GND

202008319395..png

  • 将模块插入电池盒内,使导线与鼓垫的上拉电阻浮在同一侧

202008315161..png

  • 如图所示,将所有数字输入焊接到鼓垫端子。

202008311810..png

  • 将Micro-USB总线(VBUS,D +,D-,GND)焊接到FTDI设备,确保没有错误。

  • 将带有热胶的Arduino-FTDI模块连接到电池盒

  • 重新用螺钉组装设备

我们已经完成了组装,下一步开始编写代码。

步骤4:编程A:Arduino

202008311908..png

下面我们逐段说明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启动同时激活的情况下,初始化变量和编程模式。 截屏2020-08-31 10.28.34.png

  • MIDI通信处理程序,延迟1ms的音符保持时间 /* Play MIDI note function */
    截屏2020-08-31 10.27.49.png

  • 设备循环操作的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和用户界面

202008311150..png

乍一看Python用户界面有点复杂,下面我们试着解释基本知识、使用方法、每个按钮具有什么功能以及如何正确编程Arduino设备。

用户界面-应用程序

202008314527..png
202008312796..png

UI用户界面是架子鼓的图形表示形式,它使得在任何时候对Arduino设备的编程既易用又方便。用户界面由几个图形模块组成,这些图形模块与它们的建议操作相关。我们一个个来看:

  1. 鼓组图片: Python UI使用不同鼓的图片的XY坐标来确定选择了哪种鼓型。如果选择了有效的鼓区,则会显示辅助IO消息,包括音符、力度和鼓垫的Arduino终端。这些参数由用户验证并确认后,会直接传输到Arduino设备。

  2. 外部控制器图像:为了能够在VST/音乐创建环境中使用MIDI鼓组,需要运行Serial-To-MIDI解释器。我使用过Hairless的,免费,只需按压其图像即可直接从UI运行。

  3. COM端口列表:为了与Arduino通信,需要指定其连接的COM端口。按下刷新按钮可以刷新端口列表。

  4. 加载/保存配置:代码中定义了默认的MIDI值,用户可以通过与UI交互进行修改。在config.txt文件中配置以特定格式定义,可以由用户保存或加载。

  5. 程序设备按钮:为了将所有修改后的MIDI值存储在Arduino EEPROM中,需要踩下两个脚踏板(鼓和铙),等待数据传输完成。如果出现任何通讯问题,将弹出相关的窗口。如果传输成功,UI显示成功消息。

  6. 退出按钮:仅在用户许可下退出应用程序。

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运行应用程序。我计划在将来的版本导出该项目的可执行文件。

简要代码说明

从函数和类的角度看代码会更容易理解:

  1. main 函数 - 代码从这里开始
    if __name__ == '__main__':<br>    drumkit_gui()

  2. 鼓组常数、坐标和默认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]
  3. 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)

  4. 串行通讯
    def get_serial_ports()
    def communicate_with_arduino(port)

  5. 使用文件:从txt文件存储/加载设置
    def save_config()
    def load_config()

  6. 使用Python线程功能运行外部应用程序hairless.exe
    class ExternalExecutableThread(threading.Thread)
    def run_hairless_executable()

为了运行代码,必须在项目文件夹中附加一个文件列表:

  • config.txt:设置文件
  • hairless.exe:Hairless MIDI转换器

202008318533..png

  • drumkit.png:定义了我们UI上所有可击打的鼓垫图像(下图)

  • drumgui.py:项目代码

要让架子鼓正常工作,以上这些就是我们需要强调的所有内容。将文件添加到项目中非常重要:鼓组图片、hairless.exe可执行文件和设置文件config.txt。请在附件中下载。

现在...大功告成啦!:)

感谢阅读!:)

原文链接:https://www.instructables.com/id/MIDI-Drum-Kit-on-Python-and-Arduino/
作者:Faransky
翻译:szjuliet
202008317691..png

Debouncer-master.zip

10.07 KB, 下载次数: 12

debouncer库

config文件和教程.zip

220 Bytes, 下载次数: 12

配置文件和教程

Arduino and Python.zip

5.93 KB, 下载次数: 11

代码文件

20060606  高级技匠

发表于 2020-9-3 20:47:00

好创意,赞一个
回复

使用道具 举报

佛系唐法官  中级技师

发表于 2020-9-5 13:30:04

回复

使用道具 举报

breaker_MAG  见习技师

发表于 2020-9-20 18:37:18


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2020 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail