C++ 使用MIDI库演奏《晴天》-一、开始工作

时间:2025-02-14 08:36:03

1. 引入MIDI库和相关控制类

CMakeLists.txt中:

target_link_libraries(SunnyDays winmm)

MIDIHelper.h中:

#include <windows.h>
#pragma comment(lib,"winmm.lib")

定义Scale(音阶), Instrument(乐器, 仅包括部分)等枚举。我把Drum单独提了出来。

enum Scale
{
    X1 = 36, X2 = 38, X3 = 40, X4 = 41, X5 = 43, X6 = 45, X7 = 47,
    L1 = 48, L2 = 50, L3 = 52, L4 = 53, L5 = 55, L6 = 57, L7 = 59,
    M1 = 60, M2 = 62, M3 = 64, M4 = 65, M5 = 67, M6 = 69, M7 = 71,
    H1 = 72, H2 = 74, H3 = 76, H4 = 77, H5 = 79, H6 = 81, H7 = 83,
    LOW_SPEED = 500, MIDDLE_SPEED = 400, HIGH_SPEED = 300,
    _ = 0XFF
};
enum Drum{
    BassDrum = 36, SnareDrum = 38, ClosedHiHat = 42, OpenHiHat = 46
};
enum Instrument{
    AcousticGrandPiano = 0, BrightAcousticPiano = 1,
    ElectricGrandPiano = 2, HonkyTonkPiano = 3,
    ElectricPiano1 = 4, ElectricPiano2 = 5
};

一些基础方法,包括初始化/关闭设备、设置参数、播放单个音符和播放和弦等。

void initDevice();
void closeDevice();
void setInstrument(int channel, int instrument);
void setVolume(int channel, int volume);

void PlayNote(HMIDIOUT handle, UINT channel, UINT note, UINT velocity);

void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT note4, UINT velocity);

void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT velocity);

MIDIHelper.cpp中:

void initDevice(){
    midiOutOpen(&hMidiOut, 0, 0, 0, CALLBACK_NULL);
}

void closeDevice(){
    midiOutClose(hMidiOut);
}

void setInstrument(int channel,int instrument){
    if (channel > 15 || instrument > 127) return;
    DWORD message = 0xC0 | channel | (instrument << 8);
    midiOutShortMsg(hMidiOut, message);
}

void setVolume(int channel,int volume){
    if (channel > 15 || volume > 127) return;
    DWORD message = 0xB0 | channel | (7 << 8) | (volume << 16);
    midiOutShortMsg(hMidiOut, message);
}

//播放单个音符,note是音符,velocity是力度
void PlayNote(HMIDIOUT handle, UINT channel, UINT note, UINT velocity) {
    if (channel > 15 || note > 127 || velocity > 127) return;
    DWORD message = 0x90 | channel | (note << 8) | (velocity << 16);
    midiOutShortMsg(handle, message);
}

//四指和弦
void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT note4, UINT velocity){
    if (channel > 15 || note1 > 127 || note2 > 127 || note3 > 127 || note4 > 127 || velocity > 127) return;
    DWORD message1 = 0x90 | channel | (note1 << 8) | (velocity << 16);
    DWORD message2 = 0x90 | channel | (note2 << 8) | (velocity << 16);
    DWORD message3 = 0x90 | channel | (note3 << 8) | (velocity << 16);
    DWORD message4 = 0x90 | channel | (note4 << 8) | (velocity << 16);
    midiOutShortMsg(handle, message1);
    midiOutShortMsg(handle, message2);
    midiOutShortMsg(handle, message3);
    midiOutShortMsg(handle, message4);
}

//三指和弦
void playChord(HMIDIOUT handle, UINT channel, UINT note1, UINT note2, UINT note3, UINT velocity) {
    if (channel > 15 || note1 > 127 || note2 > 127 || note3 > 127 || velocity > 127) return;
    DWORD message1 = 0x90 | channel | (note1 << 8) | (velocity << 16);
    DWORD message2 = 0x90 | channel | (note2 << 8) | (velocity << 16);
    DWORD message3 = 0x90 | channel | (note3 << 8) | (velocity << 16);
    midiOutShortMsg(handle, message1);
    midiOutShortMsg(handle, message2);
    midiOutShortMsg(handle, message3);
}

2. 初始化和结束

先在头文件中定义一个全局MIDI句柄:

extern HMIDIOUT hMidiOut;

在入口处初始化MIDI设备并在结束时关闭:

HMIDIOUT hMidiOut;
int main() {
    initDevice();
    //...
    closeDevice();
    return 0;
}

初始化MIDI设备之后,为每一个乐器分配一个通道channel(0~15,通常9分配给打击类乐器,例如鼓组),控制音量volume,然后就可以开始演奏了。