C++使用MIDI庫實(shí)現(xiàn)演奏晴天
前言
那些在MIDI庫里徘徊的十六分音符
終究沒能拼成告白的主歌
我把周杰倫的《晴天》寫成C++的類
在每個(gè)midiEvent里埋藏故事的小黃花
調(diào)試器的斷點(diǎn)比初戀更漫長(zhǎng)
而青春不過是一串未導(dǎo)出的cmake工程文件
在堆棧溢出的夜晚
終將明白
有些旋律永遠(yuǎn)停在#pragma once的注釋里
有些人永遠(yuǎn)停在未定義的引用里
或許你我的心跳終歸運(yùn)行在不同的時(shí)鐘頻率
卻愿始終記得如何編譯出一場(chǎng)永不落幕的晴天
--題記
就像在題記里說的一樣,這是一個(gè)從未導(dǎo)出成功的工程文件。
所以如果你也想聽聽,可以在PowerShell里運(yùn)行以下指令:
git clone https://github.com/TwilightLemon/SunnyDays cd SunnyDays mkdir build cd build cmake .. -G "MinGW Makefiles" mingw32-make ./SunnyDays.exe
沒環(huán)境?巧了,她也如是說。
幸運(yùn)的話能得到以下效果:
下面來簡(jiǎn)單講講如何使用C++和MIDI庫作曲吧。
開始工作
1. 引入MIDI庫和相關(guān)控制類
在CMakeLists.txt
中:
target_link_libraries(SunnyDays winmm)
在MIDIHelper.h
中:
#include <windows.h> #pragma comment(lib,"winmm.lib")
定義Scale(音階), Instrument(樂器, 僅包括部分)等枚舉。我把Drum單獨(dú)提了出來。
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 };
一些基礎(chǔ)方法,包括初始化/關(guān)閉設(shè)備、設(shè)置參數(shù)、播放單個(gè)音符和播放和弦等。
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); } //播放單個(gè)音符,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. 初始化和結(jié)束
先在頭文件中定義一個(gè)全局MIDI句柄:
extern HMIDIOUT hMidiOut;
在入口處初始化MIDI設(shè)備并在結(jié)束時(shí)關(guān)閉:
HMIDIOUT hMidiOut; int main() { initDevice(); //... closeDevice(); return 0; }
初始化MIDI設(shè)備之后,為每一個(gè)樂器分配一個(gè)通道channel
(0~15,通常9分配給打擊類樂器,例如鼓組),控制音量volume
,然后就可以開始演奏了。
自制簡(jiǎn)易樂譜
以Voice.cpp
為例,定義一個(gè)數(shù)組為頻譜,控制停頓和音符,遍歷數(shù)組播放:
namespace SunnyDays{ int channelVoice=1; void playVoice(int note, int velocity){ PlayNote(hMidiOut, channelVoice, note, velocity); } void voice(){ Sleep(13100);//等待前奏 int sleep = 390; int data[] = { //故事的小黃花 -90, 300,M5,M5,M1,M1,_,M2,M3,_, //從出生那年就飄著 -90, M5,M5,M1,M1,0,M2,M3,300,M2,M1,_, //童年的蕩秋千 -90, 300,M5,M5,M1,M1,_,M2,M3,_, //隨記憶一直晃到現(xiàn)在 -90, M3,_,500,M2,M3,M4,M3,M2,M4,M3,700,M2,700,_, //... } for (auto i : data) { if(i==-30){logTime("Enter chorus");continue;}//調(diào)試用 if(i==-90){NextLyric(); continue;} if (i == 0) { sleep = 180; continue; } //... if (i == _) { Sleep(390); continue; } playVoice(i, 80); Sleep(sleep); } } }
打個(gè)鼓:
namespace SunnyDays{ int channelBassDrum=9; void playDrum(int note, int velocity, int duration){ PlayNote(hMidiOut, channelBassDrum, note, velocity); if(duration>0) { Sleep(duration); PlayNote(hMidiOut, channelBassDrum, note, 0); } } void bassDrum(){ Sleep(11260); cout<<"Drum Bass Start!"<<endl; playDrum(SnareDrum,100,180); playDrum(SnareDrum,100,210); playDrum(BassDrum, 100, 210); playDrum(SnareDrum,100,190); playDrum(BassDrum, 100, 210); playDrum(SnareDrum,100,200); playDrum(SnareDrum,100,200); playDrum(OpenHiHat,100,-1); Sleep(200); //... } }
簡(jiǎn)易副歌和弦,是從B站一位up主那里學(xué)的(已經(jīng)忘記是哪位了qwq):
namespace SunnyDays { int channelChord=2; void chordLevel(int level,int sleep,int repeat=2,int vel=70){ repeat--; int down=8; if(level==1){ //一級(jí)和弦 加右指 playChord(hMidiOut, channelChord, M1, M3, M5, L1, vel); while(repeat--) { Sleep(sleep); playChord(hMidiOut, channelChord, M1, M3, M5, vel - down); } }else if(level==3){ //三級(jí)和弦 加右指 playChord(hMidiOut, channelChord, M3, M5, M7, L3, vel); while(repeat--) { Sleep(sleep); playChord(hMidiOut, channelChord, M3, M5, M7, vel - down); } } //... } void chord(){ Sleep(63724); int sleep=740; int data[]={ //刮風(fēng)這天 我試過握著你手 1,4, 6,4, //但偏偏 雨漸漸 4,2, 5,2, //大到我看你不見 1,4, //還有多久 我才能 3,4, //↑ 在你身邊 6,4, //↓ 等到放晴的那天 4,4, //↑ 也許我會(huì)比較好一點(diǎn) 5,4, //.. } int count=sizeof(data)/sizeof(int); for(int i=0;i<count;i+=2){ cout<<"chord "<<data[i]<<" x"<<data[i+1]<<endl; chordLevel(data[i],sleep,data[i+1]); Sleep(sleep); } //... } }
合成演奏
我用了一個(gè)笨蛋方法,用多線程單獨(dú)控制每一個(gè)通道,然后在主線程中調(diào)用:
int main(){ //... initDevice(); //設(shè)置音量 setVolume(channelChord,80); setVolume(channelMainLine,80); setVolume(channelVoice,120); setVolume(channelBassDrum,80); //設(shè)置樂器(特定音色) setInstrument(channelChord,ElectricPiano1); setInstrument(channelMainLine,ElectricPiano1); system("pause");//按下回車,就開始啦 beginLogger(); thread t0(voice); thread t1(mainLine); thread t2(bassDrum); thread t3(chord); t0.join(); t1.join(); t2.join(); t3.join(); closeDevice(); //... }
(最后疊個(gè)甲,俺不懂音樂制作,更不會(huì)什么C++)
到此這篇關(guān)于C++使用MIDI庫實(shí)現(xiàn)演奏晴天的文章就介紹到這了,更多相關(guān)C++ MIDI演奏音樂內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Matlab實(shí)現(xiàn)帶豎線散點(diǎn)的核密度圖的繪制
核密度估計(jì)是用于估計(jì)隨機(jī)變量概率密度函數(shù)的一種非參數(shù)方法。核密度圖不失為一種用來觀察連續(xù)型變量分布的有效方法。本文將用Matlab實(shí)現(xiàn)帶豎線散點(diǎn)的核密度圖的繪制,感興趣的可以了解一下2022-08-08C語言中動(dòng)態(tài)內(nèi)存管理圖文詳解
在編寫程序時(shí),通常并不知道需要處理的數(shù)據(jù)量,或者難以評(píng)估所需處理數(shù)據(jù)量的變動(dòng)程度,下面這篇文章主要給大家介紹了關(guān)于C語言中動(dòng)態(tài)內(nèi)存管理的相關(guān)資料,需要的朋友可以參考下2022-06-06淺談C++/C關(guān)于#define的那些奇奇怪怪的用法
本文主要介紹了C++/C關(guān)于#define的那些奇奇怪怪的用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07關(guān)于C++面向?qū)ο笤O(shè)計(jì)的訪問性問題詳解
這篇文章主要給大家介紹了關(guān)于C++面向?qū)ο笤O(shè)計(jì)的訪問性問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09Visual Studio 2019 如何新建 Win32項(xiàng)目的方法步驟
這篇文章主要介紹了Visual Studio 2019 如何新建 Win32項(xiàng)目的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03C++11 <future>中std::promise 介紹
這篇文章主要介紹了C++11 <future>中std::promise 介紹,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02QT使用QML實(shí)現(xiàn)地圖繪制虛線的示例代碼
QML提供了MapPolyline用于在地圖上繪制線段,這篇文章主要為大家詳細(xì)介紹了QT如何使用QML實(shí)現(xiàn)在地圖上繪制虛線,需要的小伙伴可以參考一下2023-07-07