风物之诗琴自动弹奏

闲言碎语

一觉醒来,发现风物之诗琴居然可以兑换了,自己弹是不可能弹的,交给脚本吧。

键盘就让一块Pro Micro扮演吧,Python 解析midi文件,然后将需要按下的按键发送给Pro Micro。

这里我测试用的midi文件为MidiShow上随便找的一个千本樱。因为原神的风物之诗琴没有长按效果,所以实现起来少了很多工作量。

开干

Pro Micro上运行的代码非常简单,只有20行,需要注意的是并非所有的板子都支持Keyboard.h这个库

genshin.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#include "Keyboard.h"

void setup() {
// 初始化键盘
Keyboard.begin();
// 初始化串口
Serial.begin(115200);
}

void loop() {
// 当串口可用时候,读取串口输入
while (Serial.available()) {
char key = Serial.read();
// 模拟键盘点击执行串口读取的内容
Keyboard.print(key);
// 延迟
// delay(20);
}
}

python记得安装serial这个库

serial_communication.py
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

import serial
class SerialCommunication:
# 初始化串口通信类
# @param port_x 端口号 str
# @param bit_rates 波特率 int
# @param timeout 超时时间(s)
def __init__(self, port_x, bit_rates=9600, timeout=1):
self.port_x = port_x
self.bit_rates = bit_rates
self.timeout = timeout
self.serial = serial.Serial(self.port_x, self.bit_rates, timeout=self.timeout)
self.execute_count = 0
print("Serial work on {} at {}bps".format(self.port_x, self.bit_rates))

# 执行一条命令
# @param key_event 待执行的命令
def execute(self, key_event):
self.execute_count += 1
try:
self.serial.write(key_event.encode())
except Exception as execute:
print('---Error on execute---', execute)

# 日志
def log(self):
print("Write %d commands" % self.execute_count)

python需要安装mido这个库才可以解析midi文件

main.py
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

import time

import mido
from mido import MidiFile

from serial_communication import SerialCommunication

# 由于我不懂音乐,所以下面的表格一定是错误的,如果您知道如何修改出正确的关系,请留言。
# 虽然这个是错的表,但是还是能播放的。

key_dict = {

24: 'Z',
# 25: 'X', #
26: 'X',
# 27: 'C', #
28: 'C',
29: 'V',
# 30: 'B', #
31: 'B',
# 32: 'B',
33: 'M',
# 34: 'N', #
35: 'N',
36: 'Z',
# 37: 'X', #
38: 'X',
# 39: 'C', #
40: 'C',
41: 'V',
# 42: 'B', #
43: 'B',
# 44: 'M', #
45: 'M',
# 46: 'N', #
47: 'N',
48: 'A',
# 49: 'S', #
50: 'S',
# 51: 'D', #
52: 'D',
53: 'F',
# 54: 'G', #
55: 'G',
# 56: 'H', #
57: 'H',
# 58: 'J', #
59: 'J',
60: 'Q',
# 61: 'W', #
62: 'W',
# 63: 'E', #
64: 'E',
65: 'R',
# 66: 'T', #
67: 'T',
# 68: 'Y', #
69: 'Y',
# 70: 'U', #
71: 'U',
72: 'Q',
# 73: 'W', #
74: 'W',
# 75: 'E', #
76: 'E',
77: 'R',
# 78: 'T', #
79: 'T',
# 80: 'Y', #
81: 'Y',
# 82: 'U', #
83: 'U',
84: 'A',
# 85: 'S', #
86: 'S',
88: 'D',
89: 'F',
# 90: 'G', #
91: 'G',
93: 'H',
94: 'J',
# 95: 'J',
}

if __name__ == '__main__':
virtual_key_board = SerialCommunication("COM17", 115200, 1)
mid = MidiFile("input_mid.mid")
tempo = mido.bpm2tempo(131)
time.sleep(3)
unsupport = set()
for i, track in enumerate(mid.tracks):
print('Track {}: {}'.format(i, track.name))
passed_time = 0
for msg in track:
ab_time = mido.tick2second(msg.time, mid.ticks_per_beat, tempo)
real_time = ab_time + passed_time
passed_time += ab_time

if msg.type == "note_on":
if msg.note in key_dict.keys():
virtual_key_board.execute(key_dict[msg.note])
pass
else:
unsupport.add(str(msg.note))
print("skip note:{}".format(msg.note))
time.sleep(msg.time*0.001)
print(unsupport)