linux + python 演奏《起风了》

安装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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值