實時顯示 MIDI 訊號

要在銀幕上顯示小鍵盤來視覺化彈奏訊號其實有蠻多解決方案的,比如說打開 Logic Pro X 就可以用他的鍵盤(其他 DAW 基本上都做得到),但為了這個打開整個 DAW 就很拖沓。而另外嘗試過用 MuseScore 的小鍵盤,但 OBS 就是死都抓不到鍵盤的視窗。

也罷,反正我們有好和弦創辦人——官大為大大。這邊就直接採用他提供的解決方案。

官大為的解方

官大為使用了 Processing 搭配上 MidiBus 的函式庫自幹了一個鋼琴訊號顯示 App PianoVisualizer,並直接開源給大家,我還不用爆?

然而人生總是事與願違,直接複製人家現成的東西來用,還是他媽給我報錯。

好啊報錯

哎,算了,循著報錯資訊,到 Libraries 安裝 JavaFX,另外再加上一行 import processing.javafx.*;,總算成功執行。

我就懶得追究詳細報錯原因了,可能跟源碼是基於 Processing 3 寫的有關,畢竟我使用的當下已經是最新版本的 Processing 4 了。

成果展示

而我自己稍微自定義了一下顏色、背景、圓角,成果如下:

界面

直播實例

源碼

在這邊直接附上我的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import processing.javafx.*;

import themidibus.*; //Import the midibus library
MidiBus myBus; // The MidiBus

boolean nowPedaling = false; // is it pedaling?(不要動)
int[] isKeyOn = new int[128]; // what notes are being pressed (1 or 0)(不要動)
float[] isFilled = new float[128]; // not used (yet)(不要動)
int[] isPedaled = new int[128]; // what notes are pedaled (1 or 0)(不要動)
color keyOnColor; // set it in setup()
color pedaledColor; // set it in setup()

int[] isBlack = {0, 11, 0, 13, 0, 0, 11, 0, 12, 0, 13, 0}; // 是黑鍵嗎?是的話,相對左方的白鍵位移多少?(default: {0, 11, 0, 13, 0, 0, 11, 0, 12, 0, 13, 0})
int border = 3; // 左方留空幾個畫素?(default: 3)
int whiteKeyWidth = 20; // 白鍵多寬?(default: 20)
int whiteKeySpace = 1; // 白鍵間的縫隙多寬?(default: 1)
int blackKeyWidth = 17; // 黑鍵多寬?(default: 17)
int blackKeyHeight = 45; // 黑鍵多高?(default: 45)
int radius = 4; // 白鍵圓角(default: 5)
int bRadius = 3; // 黑鍵圓角(default: 4)
int keyAreaY = 3; // 白鍵從 Y 軸座標多少開始?(default: 3)
int keyAreaHeight = 70; // 白鍵多高?(default: 70)
boolean rainbowMode = false; // 彩虹模式 (default: false)

void setup() {
int winWidth = (whiteKeyWidth+whiteKeySpace)*52 + (border*2); // recommended window width
int winHeight = keyAreaHeight + (keyAreaY*2); // recommended window width
println("Recommended window size: ", winWidth, winHeight); // 建議的視窗大小(顯示於 Console)
size(1098, 76, FX2D); // 視窗大小 (default: 1098, 76)
colorMode(HSB, 360, 100, 100, 100);
keyOnColor = color(230, 65, 100, 99); // <---- 編輯這裡換「按下時」的顏色![HSB Color Mode]
pedaledColor = color(230, 50, 50, 90); // <---- 編輯這裡換「踏板踩住」的顏色![HSB Color Mode]
smooth(2);
frameRate(60);
initKeys();
MidiBus.list(); // 啟動時會列出 MIDI 輸入/輸出 設備,記下你想用的 MIDI 輸入編號,設定在下一行。
myBus = new MidiBus(this, 2, -1); // 編輯「this 後面那個數字」選擇 MIDI 輸入設備。
}

void draw() {
background(0, 0, 0, 0);
drawWhiteKeys();
drawBlackKeys();
}

void initKeys() {
for (int i = 0; i<128; i++) {
isKeyOn[i] = 0;
isFilled[i] = 0.0;
isPedaled[i] = 0;
}
}

void drawWhiteKeys() {
int wIndex = 0; // white key index
stroke(0, 0, 0);
strokeWeight(1);
for (int i = 21; i < 109; i++) {
if (isBlack[i % 12] == 0) {
// it's a white key
if (isKeyOn[i] == 1 && !rainbowMode) {
fill(keyOnColor); // keypressed
} else if (isKeyOn[i] == 1 && rainbowMode) {
fill(map(i, 21, 108, 0, 1080)%360, 100, 100, 100); // rainbowMode
} else if (isPedaled[i] == 1 && !rainbowMode) {
fill(pedaledColor); // pedaled
} else if (isPedaled[i] == 1 && rainbowMode) {
fill(map(i, 21, 108, 0, 1080)%360, 100, 70, 100); // pedaled rainbowMode
} else {
fill(0, 0, 100); // white key
}
int thisX = border + wIndex*(whiteKeyWidth+whiteKeySpace);
rect(thisX, keyAreaY, whiteKeyWidth, keyAreaHeight, radius);
// println(wIndex);
wIndex++;
}
}
}

void drawBlackKeys() {
int wIndex = 0; // white key index
stroke(0, 0, 0);

strokeWeight(1.5);

for (int i = 21; i < 109; i++) {
if (isBlack[i % 12] == 0) {
// it's a white key
wIndex++;
}

if (isBlack[i % 12] > 0) {
// it's a black key
if (isKeyOn[i] == 1 && !rainbowMode) {
fill(keyOnColor); // keypressed
} else if (isKeyOn[i] == 1 && rainbowMode) {
fill(map(i, 21, 108, 0, 1080)%360, 100, 100, 100); // rainbowMode
} else if (isPedaled[i] == 1 && !rainbowMode) {
fill(pedaledColor); // pedaled
} else if (isPedaled[i] == 1 && rainbowMode) {
fill(map(i, 21, 108, 0, 1080)%360, 100, 70, 100); // pedaled rainbowMode
} else {
fill(0, 0, 0); // white key
}


int thisX = border + (wIndex-1)*(whiteKeyWidth+whiteKeySpace) + isBlack[i % 12];
rect(thisX, keyAreaY-1, blackKeyWidth, blackKeyHeight, bRadius);
}
}
}

void noteOn(int channel, int pitch, int velocity) {
// Receive a noteOn
isKeyOn[pitch] = 1;
isFilled[pitch] = 100;
if (nowPedaling) {
isPedaled[pitch] = 1;
}
}

void noteOff(int channel, int pitch, int velocity) {
// Receive a noteOff
isKeyOn[pitch] = 0;
isFilled[pitch] = 0;
}

void controllerChange(int channel, int number, int value) {
// Receive a controllerChange
if (number == 64 && value >= 64) {
nowPedaling = true;
for (int i = 0; i<128; i++) {
// copy key on to pedal
isPedaled[i] = isKeyOn[i];
}
}

if (number == 64 && value < 64) {
nowPedaling = false;
for (int i = 0; i<128; i++) {
// reset isPedaledlin
isPedaled[i] = 0;
}
}
}