安装ALSA库和软件合成器
sudo apt-get install libasound2-dev timidity fluid-soundfont-gm
安装Python MIDI库
pip install python-rtmidi
python代码:
import rtmidi
import time
import threading
class Scale:
Rest = 0
C8 = 108
B7 = 107
A7s = 106
A7 = 105
G7s = 104
G7 = 103
F7s = 102
F7 = 101
E7 = 100
D7s = 99
D7 = 98
C7s = 97
C7 = 96
B6 = 95
A6s = 94
A6 = 93
G6s = 92
G6 = 91
F6s = 90
F6 = 89
E6 = 88
D6s = 87
D6 = 86
C6s = 85
C6 = 84
B5 = 83
A5s = 82
A5 = 81
G5s = 80
G5 = 79
F5s = 78
F5 = 77
E5 = 76
D5s = 75
D5 = 74
C5s = 73
C5 = 72
B4 = 71
A4s = 70
A4 = 69
G4s = 68
G4 = 67
F4s = 66
F4 = 65
E4 = 64
D4s = 63
D4 = 62
C4s = 61
C4 = 60
B3 = 59
A3s = 58
A3 = 57
G3s = 56
G3 = 55
F3s = 54
F3 = 53
E3 = 52
D3s = 51
D3 = 50
C3s = 49
C3 = 48
B2 = 47
A2s = 46
A2 = 45
G2s = 44
G2 = 43
F2s = 42
F2 = 41
E2 = 40
D2s = 39
D2 = 38
C2s = 37
C2 = 36
B1 = 35
A1s = 34
A1 = 33
G1s = 32
G1 = 31
F1s = 30
F1 = 29
E1 = 28
D1s = 27
D1 = 26
C1s = 25
C1 = 24
B0 = 23
A0s = 22
A0 = 21
class Voice:
X1 = Scale.C2
X2 = Scale.D2
X3 = Scale.E2
X4 = Scale.F2
X5 = Scale.G2
X6 = Scale.A2
X7 = Scale.B2
L1 = Scale.C3
L2 = Scale.D3
L3 = Scale.E3
L4 = Scale.F3
L5 = Scale.G3
L6 = Scale.A3
L7 = Scale.B3
M1 = Scale.C4
M2 = Scale.D4
M3 = Scale.E4
M4 = Scale.F4
M5 = Scale.G4
M6 = Scale.A4
M7 = Scale.B4
H1 = Scale.C5
H2 = Scale.D5
H3 = Scale.E5
H4 = Scale.F5
H5 = Scale.G5
H6 = Scale.A5
H7 = Scale.B5
LOW_SPEED = 500
MIDDLE_SPEED = 400
HIGH_SPEED = 300
_ = 0 # 用0表示休止符而不是0xFF
def playNote(midiout, channel, instrument, note, velocity, duration=0):
"""设置乐器音色"""
# 确保channel和instrument值在合法范围内
channel = max(0, min(15, channel)) # MIDI通道范围是0-15
instrument = max(0, min(127, instrument)) # MIDI音色范围是0-127
# Program Change消息: 0xC0 + channel, instrument
midiout.send_message([0xC0 + channel, instrument])
"""发送Note On消息"""
# 确保note和velocity值在合法范围内
note = max(0, min(127, note)) # MIDI音符范围是0-127
velocity = max(0, min(127, velocity)) # MIDI力度范围是0-127
# Note On消息: 0x90 + channel, note, velocity
midiout.send_message([0x90 + channel, note, velocity])
if duration:
# 暂停指定时长
time.sleep(duration)
"""发送Note Off消息"""
# Note Off消息: 0x80 + channel, note, 0
midiout.send_message([0x80 + channel, note, 0])
return f"Channel: {channel}, Instrument: {instrument}, Note: {note}, Velocity: {velocity}"
def open_midi_output():
"""打开MIDI输出端口"""
midiout = rtmidi.MidiOut()
# 获取可用的MIDI端口
available_ports = midiout.get_ports()
print("可用的MIDI端口:")
for i, port in enumerate(available_ports):
print(f"{i}: {port}")
# 查找常见的软件合成器端口
synth_ports = [
"TiMidity",
"FluidSynth",
"Synth",
"synthesizer"
]
selected_port = None
for port in available_ports:
for synth in synth_ports:
if synth.lower() in port.lower():
selected_port = port
break
if selected_port:
break
if selected_port:
port_index = available_ports.index(selected_port)
midiout.open_port(port_index)
print(f"已连接到软件合成器端口: {selected_port}")
elif available_ports:
# 通常第一个端口是系统默认MIDI合成器
midiout.open_port(0)
print(f"已连接到MIDI端口: {available_ports[0]}")
else:
# 没有系统端口时创建虚拟端口
midiout.open_virtual_port("起风了合成器")
print("已创建虚拟MIDI端口")
return midiout
def go(midiout, channel, instrument, velocity):
"""演奏《起风了》主旋律"""
wind = [400, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L3, 0, Voice.M5, Voice.M3, 300, Voice.L2, Voice.L5, 2, Voice._, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L2, 0, Voice.M5, Voice.M3, Voice.M2, Voice.M3, Voice.M1, Voice.M2, Voice.L7, Voice.M1, 300, Voice.L5, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L3, 0, Voice.M5, Voice.M3, 300, Voice.L2, Voice.L5, 2, Voice._, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L2, 0, Voice.M5, Voice.M3, Voice.M2, Voice.M3, Voice.M1, Voice.M2, Voice.L7, Voice.M1, 300, Voice.L5,
0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L3, 0, Voice.M5, Voice.M3, 300, Voice.L2, Voice.L5, 2, Voice._, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L2, 0, Voice.M5, Voice.M3, Voice.M2, Voice.M3, Voice.M1, Voice.M2, Voice.L7, Voice.M1, 300, Voice.L5, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L3, 0, Voice.M5, Voice.M3, 300, Voice.L2, Voice.L5, 2, Voice._,
0, Voice.M6, Voice.M3, Voice.M2, Voice.L6, Voice.M3, Voice.L6, Voice.M2, Voice.M3, Voice.L6, Voice._, Voice._, Voice._,
Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, Voice.M3, Voice.M5, 0, Voice.M3, 700, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, Voice.M2, Voice.M3, Voice.M2, Voice.M1, 300, Voice.L5, Voice._,
Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, Voice.M3, Voice.M5, 0, Voice.M3, 700, 300, Voice.M2, 700, 0, Voice.M3, 300, Voice.M2, 0, Voice.M1, 700, 300, Voice.M2, Voice._, Voice._, Voice._,
Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, Voice.M3, Voice.M5, 0, Voice.M3, 700, 300, Voice.M2, 700, 0, Voice.M3, 300, Voice.M2, 0, Voice.M1, 700, 300, Voice.L6, Voice._,
0, Voice.M3, Voice.M2, Voice.M1, Voice.M2, 300, Voice.M1, Voice._, 0, Voice.M3, Voice.M2, Voice.M1, Voice.M2, 300, Voice.M1, 700, 0, Voice.L5, Voice.M3, Voice.M2, Voice.M1, Voice.M2, 300, Voice.M1, Voice._, Voice._, Voice._,
Voice.M1, Voice.M2, Voice.M3, Voice.M1, Voice.M6, 0, Voice.M5, Voice.M6, 300, Voice._, 700, 0, Voice.M1, 300, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice._, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice.M3, 0, Voice.H1, Voice.H2, Voice.H1, Voice.M7, 300, Voice.M6, Voice.M5, Voice.M6, 0, Voice.M5, Voice.M6, Voice._, Voice.M5, Voice.M6, Voice.M5, 300, Voice.M6, 0, Voice.M5, Voice.M2, 300, Voice._, 0, Voice.M5, 700, 300, Voice.M3, Voice._, Voice._, Voice._,
Voice.M1, Voice.M2, Voice.M3, Voice.M1, Voice.M6, 0, Voice.M5, Voice.M6, 300, Voice._, 700, 0, Voice.M1, 300, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice._, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice.M3, 0, Voice.H1, Voice.H2, Voice.H1, Voice.M7, 300, Voice.M6, Voice.M5, Voice.M6, 0, Voice.H3, Voice.H3, 300, Voice._, Voice.M5, Voice.M6, 0, Voice.H3, Voice.H3, 300, Voice._, 0, Voice.M5, 700, 300, Voice.M6, Voice._, Voice._, Voice._, Voice._, Voice._,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H2, 0, Voice.H1, Voice.M6, 300, Voice._, 0, Voice.H1, Voice.H1, 300, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, 0, Voice._, 300, Voice.H1, 700, Voice.H3, Voice._, 0, Voice.H3, Voice.H4, Voice.H3, Voice.H2, Voice.H3, 300, Voice.H2, 700,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 300, Voice._, Voice.H3, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 700, 300, Voice.H3, 700, Voice.H2, 0, Voice.H1, Voice.M6, 700, 300,
Voice.H3, 700, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, Voice.H1, Voice.H1, Voice._, Voice._, Voice._, Voice._, Voice._,
0, Voice.M6, 300, Voice.H3, 700, Voice.H2, 0, Voice.H1, Voice.M6, 700, 300, Voice.H3, Voice.H2, 700, 300, 0, Voice.H1, Voice.M6, 300, 700, Voice.H1, Voice.H1, Voice._, Voice._,
0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L3, 0, Voice.M5, Voice.M3, 300, Voice.L2, Voice.L5, 2, Voice._, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L2, 0, Voice.M5, Voice.M3, Voice.M2, Voice.M3, Voice.M1, Voice.M2, Voice.L7, Voice.M1, 300, Voice.L5, 0, Voice.L7, Voice.M1, Voice.M2, Voice.M3, 300, Voice.L3, 0, Voice.M5, Voice.M3, 300, Voice.L2, Voice.L5, 2, Voice._,
0, Voice.M6, Voice.M3, Voice.M2, Voice.L6, Voice.M3, Voice.L6, Voice.M2, Voice.M3, Voice.L6, Voice._, Voice._, Voice._,
Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, Voice.M3, Voice.M5, 0, Voice.M3, 700, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, Voice.M2, Voice.M3, Voice.M2, Voice.M1, 300, Voice.L5, Voice._,
Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, Voice.M3, Voice.M5, 0, Voice.M3, 700, 300, Voice.M2, 700, 0, Voice.M3, 300, Voice.M2, 0, Voice.M1, 700, 300, Voice.M2, Voice._, Voice._, Voice._,
Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, 700, 0, Voice.M1, 300, Voice.M2, Voice.M3, Voice.M5, 0, Voice.M3, 700, 300, Voice.M2, 700, 0, Voice.M3, 300, Voice.M2, 0, Voice.M1, 700, 300, Voice.L6, Voice._,
0, Voice.M3, Voice.M2, Voice.M1, Voice.M2, 300, Voice.M1, Voice._, 0, Voice.M3, Voice.M2, Voice.M1, Voice.M2, 300, Voice.M1, 700, 0, Voice.L5, Voice.M3, Voice.M2, Voice.M1, Voice.M2, 300, Voice.M1, Voice._, Voice._, Voice._,
Voice.M1, Voice.M2, Voice.M3, Voice.M1, Voice.M6, 0, Voice.M5, Voice.M6, 300, Voice._, 700, 0, Voice.M1, 300, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice._, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice.M3, 0, Voice.H1, Voice.H2, Voice.H1, Voice.M7, 300, Voice.M6, Voice.M5, Voice.M6, 0, Voice.M5, Voice.M6, Voice._, Voice.M5, Voice.M6, Voice.M5, 300, Voice.M6, 0, Voice.M5, Voice.M2, 300, Voice._, 0, Voice.M5, 700, 300, Voice.M3, Voice._, Voice._, Voice._,
Voice.M1, Voice.M2, Voice.M3, Voice.M1, Voice.M6, 0, Voice.M5, Voice.M6, 300, Voice._, 700, 0, Voice.M1, 300, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice._, Voice.M7, 0, Voice.M6, Voice.M7, 300, Voice._, Voice.M3, 0, Voice.H1, Voice.H2, Voice.H1, Voice.M7, 300, Voice.M6, Voice.M5, Voice.M6, 0, Voice.H3, Voice.H3, 300, Voice._, Voice.M5, Voice.M6, 0, Voice.H3, Voice.H3, 300, Voice._, 0, Voice.M5, 700, 300, Voice.M6, Voice._, Voice._, Voice._, Voice._, Voice._,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H2, 0, Voice.H1, Voice.M6, 300, Voice._, 0, Voice.H1, Voice.H1, 300, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, 0, Voice._, 300, Voice.H1, 700, Voice.H3, Voice._, 0, Voice.H3, Voice.H4, Voice.H3, Voice.H2, Voice.H3, 300, Voice.H2, 700,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 300, Voice._, Voice.H3, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 700, 300, Voice.H3, 700, Voice.H2, 0, Voice.H1, Voice.M6, 700, 300,
Voice.H3, 700, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, Voice.H1, Voice.H1, Voice._, Voice._, Voice._, Voice._, Voice._,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H2, 0, Voice.H1, Voice.M6, 300, Voice._, 0, Voice.H1, Voice.H1, 300, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, 0, Voice._, 300, Voice.H1, 700, Voice.H3, Voice._, 0, Voice.H3, Voice.H4, Voice.H3, Voice.H2, Voice.H3, 300, Voice.H2, 700,
Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 300, Voice._, Voice.H3, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 700, 300, Voice.H3, 700, Voice.H2, 0, Voice.H1, Voice.M6, 700, 300,
Voice.H3, 700, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, Voice.H1, Voice.H1, Voice._, Voice._, Voice._, Voice._, Voice._,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H3, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H6, Voice.H5, 300, Voice._, 0, Voice.H2, Voice.H3, 300, Voice.H2, 0, Voice.H1, Voice.M6, 300, Voice._, 0, Voice.H1, Voice.H1, 300, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, 0, Voice._, 300, Voice.H1, 700, Voice.H3, Voice._, 0, Voice.H3, Voice.H4, Voice.H3, Voice.H2, Voice.H3, 300, Voice.H2, 700,
Voice.H1, Voice.H2, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 300, Voice._, Voice.H3, Voice.H3, 0, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, Voice._, Voice.H6, Voice.H5, 700, 300, Voice.H3, 700, Voice.H2, 0, Voice.H1, Voice.M6, 700, 300,
Voice.H3, 700, Voice.H2, 0, Voice.H1, 300, Voice.M6, 700, Voice.H1, Voice.H1, Voice._, Voice._, Voice._, Voice._, Voice._,
0, Voice.M6, 300, Voice.H3, 700, Voice.H2, 0, Voice.H1, Voice.M6, 700, 300, Voice.H3, Voice.H2, 700, 300, 0, Voice.H1, Voice.M6, 300, 700, Voice.H1, Voice.H1, Voice._, Voice._, Voice._, Voice._, Voice._, Voice._, Voice._, -1
]
sleep = 0.35
for i in wind:
if i == -1:
break
if i == 0:
sleep = 0.172
continue
if i == 700:
time.sleep(0.172)
continue
if i == 300:
sleep = 0.35
continue
if i == Voice._: # 休止符处理
time.sleep(0.25)
continue
# 只有当i不是休止符时才播放音符
back = playNote(midiout, channel, instrument, i, velocity, sleep)
print(back)
# time.sleep(sleep)
def main():
"""主函数:初始化MIDI并启动双轨演奏"""
try:
print("正在查找MIDI端口...")
print("可用的音频合成器:")
print("1. TiMidity: 运行 'timidity -iA' 命令启动")
print("2. FluidSynth: 运行 'fluidsynth -a alsa -m alsa_seq -i soundfont.sf2' 命令启动")
print("(如果没有安装,可以使用 'sudo apt install timidity' 或 'sudo apt install fluidsynth' 安装)")
print("")
# 打开MIDI输出
midiout1 = open_midi_output()
midiout2 = open_midi_output() # 第二个通道需要独立的MIDI输出对象
print("\n开始演奏《起风了》...")
print("如果听不到声音,请确保已启动音频合成器(如TiMidity)")
# 创建两个线程,分别演奏不同乐器
# 注意:在Linux/ALSA系统中,端口名称包含客户端名称和ALSA客户端及端口号 [[8]]
thread1 = threading.Thread(target=go, args=(midiout1, 0, 78, 90)) # 78号乐器:Recorder (哨)
thread2 = threading.Thread(target=go, args=(midiout2, 1, 0, 127)) # 0号乐器:Acoustic Grand Piano
# 启动线程
thread1.start()
thread2.start()
# 等待线程完成
thread1.join()
thread2.join()
print("演奏完成!")
except Exception as e:
print(f"发生错误: {e}")
import traceback
traceback.print_exc()
finally:
# 清理资源
if 'midiout1' in locals():
del midiout1
if 'midiout2' in locals():
del midiout2
print("MIDI资源已释放")
if __name__ == "__main__":
main()
运行:
运行软件合成器(前面已安装)
timidity -iA &
运行python代码,听听效果吧。
乐谱来源:https://blog.csdn.net/2401_86461228/article/details/142004441