本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

基本的 Arduino MIDI 的和弦工具——琶音器

发布时间:2021-06-28
分享到:

基本的 Arduino MIDI 的和弦工具——琶音器

发布时间:2021-06-28
分享到:

在本教程中,我将向您展示如何使用 Arduino Uno 制作基本的 MIDI 乐器。本乐器连续演奏三个音符(三和弦)。使用不同的电位器,您可以上下移动音符以及上下移动音阶。您还可以控制每个音符之间的延迟。我觉得我从这个项目中学到了一些关于 MIDI 控制器的有趣的东西,我希望你也会。

这个乐器是琶音器的一个非常基本的版本。这是一个花哨的动作视频。琶音器利用和弦的自然音乐性,不费吹灰之力就发出许多美妙的声音。它们通常作为合成器的一部分出现,您只需按住想要琶音的和弦即可。在这个例子中,我们只有一个触发琶音的按钮和三个控制起始音高及其在音阶上的位置的按钮。

对于 arduino Uno,使用 MIDI 协议的最简单方法是通过串行端口。它需要一些配置才能在 Windows 上运行,我将介绍一下。我使用了FortySevenEffects 的 Ardunio MIDI 库。我还遵循了这些优秀的 youtube 教程:Arduino的 MIDI,如何使用 Arduino构建 MIDI 控制器。我根据第二个视频系列的代码编写了用于控制按钮和电位器的代码。在制作 MIDI 控制器的整个免费课程的一部分中,它确实有很好的记录。

在这个项目中,我将 Grove Starter Kit Plus 用于 RGB 背光 LCD 屏幕。这不是绝对必要的,但很高兴看到您正在演奏的音符。

补给品

  • 4个电位器(我用了我身边的任何东西)
  • 1 个按钮
  • 1 个 1k 电阻器用于按钮
  • 1 Arduino Uno
  • 1 Grove 入门套件/RGB 背光 LCD(可选)
  • 连接件用电线

电脑相关工具

  • DAW
  • Hairless MIDI(串行桥接器——将串行输出转换为 MIDI 输入的东西)
  • Loop MIDI(为您的计算机创建虚拟环回端口。)
  • Arduino IDE

第 1 步:设置电路板

下面是我在没有 LCD 的情况下使用的布局图。该按钮将用作启动琶音的触发器。其中三个电位器将控制琶音的不同方面,最后一个电位器将用作 MIDI 控制。

在我的实际电路布局中,我使用了 Grove 板(连接在 Arduino Uno 的顶部)。它带有一个按钮和一个不错的电位器,您可以在上图中看到它们已插入各自的端口。LCD 屏幕与此连接非常容易,您只需将 Grove 套件随附的一根电缆从 LCD 屏幕连接到 I2C 端口。

名称 数量 组件
R5    1 220 Ω 电阻
U9   1   1 Arduino Uno R3
Rpot9
Rpot10
Rpot11
Rpot12
4 250 kΩ 电位器
R8 1 10 kΩ 电阻
S1 1 按钮

第 2 步:熟悉 MIDI

MIDI 是一种专门为 80 年代初创建的乐器而构建的通信协议。通过我们拥有的软件设置,我们可以使用 MIDI 进行输入和输出到我们的 arduino,但为了简单起见,我们将专注于输出。具体我们将使用两个不同频道的语音消息:注意和控制器更改。Note On 允许我们告诉 DAW 或我们用来打开或关闭音符的任何东西,以及以什么速度(MIDI 说音量的奇怪方式)。控制器更改告诉 DAW 控制器的值已更改。您可以将其想象为在放大器上扭转刻度盘,无论是为了音量还是某些效果,如失真或混响。还有很多其他的消息类型,而且事情会变得非常复杂,非常快。我们使用的 MIDI 库有很多函数来支持这些不同的消息类型。

有兴趣了解更多吗?我首先会向您介绍我放在教程开头的 youtube 链接。然后我会四处搜索,看看人们用 MIDI 做了什么,以了解可能性。

对于 Note On,这是指向音高列表及其分配的 MIDI 值的链接。例如,C4 的 midi 值为 60。每个数字都映射到一个半音,因此使用模除法来确定您有什么音符非常容易。鉴于 C4=60,我知道 C5 = 72,C6 = 84,无需查看表格,因为我知道八度音程相隔 12 个半音。当我们将 MIDI 值转换为音符以显示在 LCD 屏幕上时,这将派上用场。此外,如果我们将 Cmaj 音阶 MIDI 值保存到列表中,我们可以使用 mod7 除法来移动音阶中的值。

第 3 步:代码

如果您不需要/不需要 LCD,我附上了一个版本,其中我注释掉了 LCD 组件。我在代码中进行了大量注释,但在这里我将指出一些事情,从顶部开始。

初始化变量:

  • 按钮的变量“debounce_delay”以及变量“Timeout”和“var_threshold”可用于减少来自按钮和电位计的一些噪声和错误读数。如果您遇到问题,那将是一个很好的起点。由于我们将连续范围转换为离散整数,因此电位器可能会在边界之间变得有点不稳定。如果电位器在状态之间闪烁过多,有几种方法可以解决。我在此未实施的一种解决方案是,在进行任何进一步处理之前,先读取几次读数并取平均值。
  • “音符”列表是我们要在琶音中演奏的音符序列。我将它设置为播放起始音符,并以大调的方式记录 2 个音符和 4 个音符。这将形成基本的大调、小调和减和弦。如果你想要一些更有趣的东西,你可以在“笔记”列表中添加更多的笔记。只是不要忘记更改常量“CHORD_LENGTH”。例如,您可以这样做:
const int CHORD_LENGTH = 6; //number of notes in chord
int midi_vals[CHORD_LENGTH] = {}; //array to store midi-output to play
int notes[CHORD_LENGTH] = {0,2,4,2, 5,7}; //1st 3rd, and 5th note on scale, plus the 7th. This is more jazzy
  • 通过创建列表“Cmaj_scale”,我创建了一个模板,我可以上下移动来创建和弦。如果我想更改键,我需要做的就是使用来自第三个电位器的输入更改“midi_c_state[2]”。如果我只想从给定的起始音符移动大调,我只需从第一个电位计更改“midi_c_state[0]”。

设置:

  • 您需要为MIDI 设置正确的波特率才能正确读取您的串行输入。否则你会在无毛 MIDI 中得到错误,比如“得到了意外的数据字节”
  • LCD 屏幕带有 16 列数字和 2 行。

循环和相关功能

  • 函数“pause_length”控制正在播放的音符之间的延迟。
  • “starting_scale_position”允许您在不改变键的情况下上下移动大调。“起始音符”允许您将所有内容向上或向下移动一个半色调值,从而允许您更改调。这两个函数都调用“move_chord”,将您选择的音符重新映射到 MIDI 值。
  • 更改延迟、起始音阶位置、MIDI 控制或音符将导致 LCD 刷新。

基本琶音器.ino

//Basic MIDI arpeggiator instrument for the Arduino UNO by Joseph Thompson. 
//This plays triads that can be shifted in pitch and position on the scale
//Inspired by and uses the button/potentiometer checking from https://github.com/silveirago/DIY-Midi-Controller/blob/master/Code%20-%20c%C3%B3digo/en-DIY_midi_controller/en-DIY_midi_controller.ino
// uses Grove rgb_lcd, from grove starter kit. https://wiki.seeedstudio.com/Grove-LCD_RGB_Backlight/ This is not essential and can be removed
#include<rgb_lcd.h>
#include<MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE(); //Creates MIDI object to interact with Serial
// BUTTONS
//Only have 1 button to trigger the arpeggio
constint N_BUTTONS = 1; //*  total numbers of buttons
constint BUTTON_ARDUINO_PIN[N_BUTTONS] = {3}; //* pins of each button connected straight to the Arduino
int button_c_state[N_BUTTONS] = {};        // stores the button current value
int button_p_state[N_BUTTONS] = {};        // stores the button previous value
// debounce
unsignedlong last_debounce_time[N_BUTTONS] = {0};  // the last time the output pin was toggled
unsignedlong debounce_delay = 50;    //* the debounce time; increase if the output flickers
//Potentiometers
constint N_POTS=4;
constint POT_ARDUINO_PIN[N_POTS] = { A0, A1, A2, A3}; //* pins of each pot connected straight to the Arduino
int pot_c_state[N_POTS] = {0,0,0,0}; // Current state of the pot
int pot_p_state[N_POTS] = {0,0,0,0}; // Previous state of the pot
int pot_var = 0; // Difference between the current and previous state of the pot
/* midi_c_state[0] --> Position on scale
 *  midi_c_state[1] -->pause length
 *  midi_c_state[2] --> adjusts position on MIDI note map. i.e. C5 = 60, C5# = 61...
 *  midi_c_state[3] --> MIDI control
*/
int midi_c_state[N_POTS] = {0,0,0,0}; // Current state of the midi value
int midi_p_state[N_POTS] = {0,0,0,0}; // Previous state of the midi value
constint TIMEOUT = 300; //* Amount of time the potentiometer will be read after it exceeds the var_threshold
constint var_threshold = 10; //* Threshold for the potentiometer signal variation
boolean pot_moving= true; // If the potentiometer is moving
unsignedlong PTime[N_POTS] = {0,0,0,0}; // Previously stored time
unsignedlong timer[N_POTS] = {0,0,0,0}; // Stores the time that has elapsed since the timer was reset
// MIDI
byte midi_ch = 1; //* MIDI channel to be used
byte cc = 1; //* Lowest MIDI CC to be used
//Scale info
String note_names[] = {"C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B "}; //for display on the LCD
int Cmaj_scale[] = {60,62,64,65,67,69,71,72}; //MIDI code for C Major scale. In midi, each number is a semi-tone
int scale_start = 0; //starting position in scale
constint CHORD_LENGTH = 3; //number of notes in chord
int midi_vals[CHORD_LENGTH] = {}; //array to store midi-output to play
int notes[CHORD_LENGTH] = {0,2,4}; //1st 3rd, and 5th note on scale, makes basic triad
int note_delay = 100;
//LCD
rgb_lcd lcd; //create LCD instance
constint colorR = 255; //set background colors
constint colorG = 0;
constint colorB = 100;
voidsetup() {
// put your setup code here, to run once:
  Serial.begin(115200);
for (int i = 0; i < N_BUTTONS; i++) {
pinMode(BUTTON_ARDUINO_PIN[i], INPUT_PULLUP);
//LCD
    lcd.begin(16,2); //rows and columns of LCD
    lcd.setRGB(colorR, colorG, colorB); //starts background color
    lcd.print("Get Ready to Jam!");
  }
}
voidloop() {
//buttons
arp_trigger(); //scale_starts the arpegio
//pots
starting_note(); //adjusts starting note
starting_scale_position(); //adjusts scale position
pause_length(); //adjust time between notes in arppegio
pot_midi(); //adjusts MIDI control pots
}
// BUTTONS
voidarp_trigger() {
    button_c_state[0] = digitalRead(BUTTON_ARDUINO_PIN[0]);  // read pin from arduino
if ((millis() - last_debounce_time[0]) > debounce_delay) {//Stops multiple triggers in too short of a timespan(debouncing)
if (button_p_state[0] != button_c_state[0]) { //Checks if something changed
        last_debounce_time[0] = millis();
if (button_c_state[0] == LOW) { //Plays the arpegio
for( int i =0; i
            MIDI.sendNoteOn(midi_vals[i], 127, midi_ch);// note, velocity, channel
delay(note_delay); //delay controlled by potentiometer 1
          }
        }
else { //stops playing arpegio
for( int i =0; i
            MIDI.sendNoteOn(midi_vals[i], 0, midi_ch);
          }
        }
        button_p_state[0] = button_c_state[0]; //saves current state as past state
      }
    }
}
voidmove_chord(){
/* helper function to shift chord around the scale. midi_c_state[0] is controlled by potentiometer 0 */
int note;
    scale_start = midi_c_state[0];
for (int i =0; i< CHORD_LENGTH ; i++){ //Go through each note in arpeggio. Here it is the basic triads in major scale
      note = notes[i];
if(scale_start+ note >7){//This allows us to play notes in next octave
        midi_vals[i] = Cmaj_scale[(scale_start+note)%7] + 12 + midi_c_state[2]; //converts note to default scale, adds twelve semitones to move it up an octave
    }else{
       midi_vals[i] = Cmaj_scale[scale_start+note]+ midi_c_state[2]; //note is in current octave, we can store it normally
    }
    }
}
// POTENTIOMETERS
voidstarting_scale_position() {
/* This function determines where on scale you start. This is controlled by potentiometer 0 (0 is start of scale, 7 is the octave) */
   pot_c_state[0] = analogRead(POT_ARDUINO_PIN[0]); // reads the pins from arduino
    midi_c_state[0] = map(pot_c_state[0], 0, 1023, 0, 7); // Maps the reading of thepot_c_state to a value that we will add to the starting midi value
    pot_var = abs(pot_c_state[0] - pot_p_state[0]); // Calculates the absolute value between the difference between the current and previous state of the pot
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold
      PTime[0] = millis(); // Stores the previous time
    }
    timer[0] = millis() - PTime[0]; // Resets the timer 11000 - 11000 = 0ms
if (timer[0] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving
      pot_moving= true;
    }
else {
      pot_moving= false;
    }
if (pot_moving== true) { // If the potentiometer is still moving, send the change control
if (midi_p_state[0] != midi_c_state[0]) {
move_chord(); //Calls move chord function. This shifts the chord to the values mapped to the potentiometers
update_lcd(); //updates LCD to reflect changes
        pot_p_state[0] =pot_c_state[0]; // Stores the current reading of the potentiometer to compare with the next
        midi_p_state[0] = midi_c_state[0]; //Stores current mapping
      }
    }
  }
voidpause_length() {
/* This function determines length of pause. Controlled by potentiometer 1 */
   pot_c_state[1] = analogRead(POT_ARDUINO_PIN[1]); // reads the pins from arduino
    midi_c_state[1] = map(pot_c_state[1], 0, 1023, 0, 1000); // Maps the reading of thepot_c_state to a delay value between 0 and 1 second
    pot_var = abs(pot_c_state[1] - pot_p_state[1]); // Calculates the absolute value between the difference between the current and previous state of the pot
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold
      PTime[1] = millis(); // Stores the previous time
    }
    timer[1] = millis() - PTime[1]; // Resets the timer 11000 - 11000 = 0ms
if (timer[1] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving
      pot_moving= true;
    }
else {
      pot_moving= false;
    }
if (pot_moving== true) { // If the potentiometer is still moving, send the change control
if (midi_p_state[1] != midi_c_state[1]) {
        note_delay = midi_c_state[1];
update_lcd(); //updates LCD to display new info
        pot_p_state[1] =pot_c_state[1]; // Stores the current reading of the potentiometer to compare with the next
        midi_p_state[1] = midi_c_state[1]; //Stores current mapping
      }
    }
  }
voidstarting_note() {
/* This function determines absolute starting location. Controlled by potentiometer 2 */
   pot_c_state[2] = analogRead(POT_ARDUINO_PIN[2]); // reads the pins from arduino
    midi_c_state[2] = map(pot_c_state[2], 0, 1023, -10, 30); // Maps the reading of thepot_c_state to a delay value
    pot_var = abs(pot_c_state[2] - pot_p_state[2]); // Calculates the absolute value between the difference between the current and previous state of the pot
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold
      PTime[2] = millis(); // Stores the previous time
    }
    timer[2] = millis() - PTime[2]; // Resets the timer 11000 - 11000 = 0ms
if (timer[2] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving
      pot_moving= true;
    }
else {
      pot_moving= false;
    }
if (pot_moving== true) { // If the potentiometer is still moving, send the change control
if (pot_p_state[2] != pot_c_state[2]) {
move_chord(); //moves the new chord
update_lcd(); //updates LCD to display new info
        pot_p_state[2] =pot_c_state[2]; // Stores the current reading of the potentiometer to compare with the next
      }
    }
  }
voidpot_midi() {
//this function sends the MIDI control values from the pot to the computer. You can use this to control effects, volume, etc. in DAW
   pot_c_state[3] = analogRead(POT_ARDUINO_PIN[3]); // reads the pins from arduino
    midi_c_state[3] = map(pot_c_state[3], 0, 1023, 0, 127); // Maps the reading of thepot_c_state to a value usable in midi
    pot_var = abs(pot_c_state[3] - pot_p_state[3]); // Calculates the absolute value between the difference between the current and previous state of the pot
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold
      PTime[3] = millis(); // Stores the previous time
    }
    timer[3] = millis() - PTime[3]; // Resets the timer 11000 - 11000 = 0ms
if (timer[3] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving
      pot_moving= true;
    }
else {
      pot_moving= false;
    }
if (pot_moving== true) { // If the potentiometer is still moving, send the change control
if (midi_p_state[3] != midi_c_state[3]) {
          MIDI.sendControlChange(cc + 3, midi_c_state[3], midi_ch); // cc number, cc value, midi channel
update_lcd();
        pot_p_state[3] =pot_c_state[3]; // Stores the current reading of the potentiometer to compare with the next
        midi_p_state[3] = midi_c_state[3];
      }
    }
}
//LCD
voidupdate_lcd(){
/*This function builds the two lines to display on the LCD screen */
    String first_line = "notes:";
    String second_line = "ms:";
    String note_name;
for(int i = 0; i< CHORD_LENGTH; i++){ //building string to display the notes we play
      note_name = note_names[midi_vals[i]%12]; //Using modulo division to map midi number to name of note
      first_line = first_line + note_name;
      first_line = first_line + "";
    }
    second_line = second_line + note_delay;
    second_line = second_line + " MIDI:";
    second_line = second_line + pot_c_state[3]; //
    lcd.clear(); //Erase what was previously written on LCD screen
    lcd.setCursor(0,0); //Move cursor to begginning of first row
    lcd.print(first_line); 
    lcd.setCursor(0,1); //move cursor to second row
    lcd.noCursor(); //removes cursor so user does not see it
    lcd.print(second_line); 
  }

基本琶音器noLCD.ino

第 4 步:设置 PC

 

为此,您需要下载 Hairless MIDI、DAW 和 Loop MIDI。他们的链接在开头。

首先,打开 LoopMIDI 并单击左下角的加号按钮。这将创建一个名为“loopMIDI 端口”的环回端口。如果您随后打开无毛 MIDI,您应该在“串行端口”和“MIDI 输出”的下拉菜单中看到它作为一个选项。对于此示例,您可以忽略“MIDI In”。注意:如果您有很多来自控制器的输入,例如,如果电位计发出大量噪音,您的计算机将关闭端口,您将在 LoopMIDI 中看到它。您应该尝试降低电位计的噪音,然后使用屏幕左下方的“减号”按钮删除 LoopMIDI 上的端口。然后使用加号按钮创建一个新端口并重试。

在 Hairless MIDI 中,如果操作正确,当您单击“调试 MIDI 消息”并触发 MIDI 控制器时,您应该会看到它是哪种 MIDI 消息,如图所示。如果您的波特率有问题,您将收到类似“警告:有一个状态字节...”之类的错误消息。您只需要进入首选项并选择正确的波特率。我附上了一张对我有用的截图。

现在您需要做的就是选择您最喜欢的 DAW 并进行设置,以便它可以从 LoopMIDI 获取输入。这个视频是我在开头链接的系列之一的一部分,展示了它是如何为 Ableton 完成的,但不同的 DAW 可能会有所不同。

第5步:项目展示

下方是我的项目的演示。在本视频中,我将介绍基本功能。如果您只想知道它可以做什么,我还添加了它可以发出的一些声音的 MP3。

以下是对该项目的一些补充想法:

  • 一种在不同琶音模式之间进行选择的方法
  • LCD 上的屏幕滚动以适应更大的琶音模式
  • 更多MIDI控件/按钮
  • 一个排队系统,这样你就可以安排一个接一个的琶音
加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论