【Follow me第二季第1期】创意任务一:可以跟随MIDI音符律动的音乐摆件
[复制链接]
本帖最后由 MioChan 于 2024-7-29 08:48 编辑
创意任务一:有创意的可穿戴装饰——可结合多种传感器和灯光效果展示
假期不在实验室,焊台、3D打印、电池啥都没有,想了想就做一个可以跟着MIDI音符律动或显示电平的指示器吧,用个绳子就可以挂到显示器上,算是个给桌面的装饰吧。主要用到的就是MIDI USB这个库。
要实现这个,最主要的是能读取到USB的 MIDI音符信息,通过以下几个代码就能做到。首先通过rx = MidiUSB.read() 接受USB MIDI信息,rx.header != 0 表示有收到信息,if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) rx.byte1 是接收到的 MIDI 消息的第一个字节(状态字节),MIDI 状态字节的前四位表示消息类型,后四位表示通道号,通过位掩码操作,将 rx.byte1 的后四位清零,只保留前四位消息类型部分。如果前四位等于 0x90,表示这是一个 “NOTE ON”消息。rx.byte3 是接收到的 MIDI 消息的第三个字节音符力度velocity,判断其不为0即可。后面只是针对这个消息做处理就好了,并不复杂。
第一个功能是针对单音符旋律的电平指示,当开发版接受到音符信号后根据力度大小用一个从绿到红的颜色环指示出力度大小,力度大小被映射为灯珠编号,灯珠会从0-对应位置逐渐增加,仿照lv表一样。有新音符进来时会及时打断上一个音符的电平显示,以保证同步。颜色就用自带的色环来定义,0就是红色。
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
第二个功能是跟随音符律动,简单来说当开发板接受到midi音符信号后随机亮起板子上0-9号彩色灯珠,并显示随机的颜色。亮度则由音符力度控制,除了旋律也可以处理和弦(毕竟一个音符只占用一个灯珠,最多可以同时显示10个)
本来是为这两个功能写了两个程序,但突然想到板子上的滑动开关还没用过,刚好可以用这个来切换两个程序。最后直接写了一个二合一程序,开关在左边为电平指示,在右为律动,可以在运行中实时切换。顺便把板子上那两个按钮也用到了,左边的可以减小灯珠的亮度,右边则是增加。
再简单介绍一下怎么用,其实只要能输出midi信号的软件都行,这里以Logic DAW为例,创建一个工程,然后在图中空白处右键创建外部MIDI轨
选中这个轨道,在侧栏的MIDI Output中选择我们的开发板
接下来,写好音符就可以在板子上看见效果了。顺便一提,如果在上面的MIDI Input中选择了MDI 键盘也可以实现键盘对开发板的直接控制,键盘的按压力度也能直观的展现。
下面是演示视频:
midi演示
第一段是电平计演示,使用的是人声轨旋律的音符
第二段是律动演示,使用的是电吉他的音符
代码如下:
#include <Adafruit_CircuitPlayground.h>
#include <MIDIUSB.h>
bool slideSwitch;
int brightness = 100;
// 定义颜色数组
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
randomSeed(analogRead(0)); // 随机数生成器
}
void loop() {
slideSwitch = CircuitPlayground.slideSwitch();
bool noteHandled = false; // 标记是否处理过音符
midiEventPacket_t rx;
if (CircuitPlayground.leftButton()) {
brightness = max(0, brightness - 10); // 减少亮度
CircuitPlayground.setBrightness(brightness);
delay(100);
}
if (CircuitPlayground.rightButton()) {
brightness = min(255, brightness + 10); // 增加亮度
CircuitPlayground.setBrightness(brightness);
delay(100);
}
if (slideSwitch) {
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
// 检查是否是 NOTE ON 消息
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int velocity = rx.byte3; // 获取音符力度
updateLEDs(velocity);
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
// 处理 NOTE OFF 消息或者 NOTE ON 力度为 0
turnOffAllLEDs();
}
}
} while (rx.header != 0);
delay(5);
} else {
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
if (!noteHandled) {
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int velocity = rx.byte3;
lightRandomLED(velocity);
noteHandled = true;
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
turnOffAllLEDs();
}
}
}
} while (rx.header != 0 && !noteHandled);
delay(5);
}
}
void updateLEDs(int velocity) {
// 计算亮灯的数量
int ledCountToLight = map(velocity, 0, 127, 0, 10);
Serial.print("Velocity: ");
Serial.print(velocity);
Serial.print(", LED Count: ");
Serial.println(ledCountToLight);
// 更新 LED
for (int i = 0; i < 10; i++) {
if (MidiUSB.read().header != 0) {
turnOffAllLEDs();
return; // 检查是否有新的 MIDI 输入
}
delay(20);
if (i < ledCountToLight) {
CircuitPlayground.setPixelColor(i, gradientColors[i]);
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
for (int i = ledCountToLight; i >= 0; i--) {
if (MidiUSB.read().header != 0) {
turnOffAllLEDs();
return; // 检查是否有新的 MIDI 输入
}
delay(20);
CircuitPlayground.setPixelColor(i, 0);
}
}
void lightRandomLED(int velocity) {
// 计算亮度
int brightness = map(velocity, 0, 127, 0, 150);
// 生成随机 LED 和颜色
int randomLED = random(0, 10);
uint32_t randomColor = random(0, 254);
CircuitPlayground.setBrightness(brightness);
// 点亮随机 LED
CircuitPlayground.setPixelColor(randomLED, CircuitPlayground.colorWheel( randomColor));
Serial.print("Random LED: ");
Serial.print(randomLED);
Serial.print(", Color: ");
Serial.print((randomColor >> 16) & 0xFF);
Serial.print(",");
Serial.print((randomColor >> 8) & 0xFF);
Serial.print(",");
Serial.println(randomColor & 0xFF);
}
void turnOffAllLEDs() {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0); // 关闭每个 LED
}
}
|