钢琴 一共88个键,52个白键,36个黑键。
从左到右按音区分,可分为
低音区:不完整的大二字组、大字一组、大字组;
中音区:小字组、小字一组、小字二组;
高音区:小字三组、小字四组和不完整的小字五组。
除大字二组只有A、#A、B三个音,小字五组只有c一个音外
其它各组分别有c、#c、d、#d、e、f、#f、g、#g、a、#a、b十二个音。
网上有个flash小游戏,使用键盘弹钢琴,将键盘上的按键和钢琴对应起来,如下图:
游戏网址:http://www.hao352.com/tangangqin.htm
这个游戏出发点就是将钢琴大众化,简单化,原理如下: ////////////////////////////////// // Low Section // 1_ 2_ 3_ 4_ 5_ 6_ 7_ // A B C D E F G // a b c d e f g // Middle Section // 1- 2- 3- 4- 5- 6- 7- // H I J K L M N // h i j k l m n // High Section // 1` 2` 3` 4` 5` 6` 7` // O P Q R S T U // o p q r s t u //////////////////////////////////
上面转换表中,数字后缀有三种:
基于这种编码的一个乐谱如下:
《青花瓷》Flash键盘钢琴谱 LLJ IJF IJLJ I LLJ IJE IJLI H HIJLMLJ LJJI I HIH IHI IJLJ J LLJ IJF IJLJI LLJ IJE IJLIH HIJ LMLJ LJJII EJIIH
玩家可以根据这种编码,按相应的按键,就能够弹奏一曲~
首先,要制作这么一个游戏,首先需要一个MiDi发声引擎:最终在codeproject上面找到了一个不错的开源MIDI引擎:MIDIWrapper
网址:http://www.codeproject.com/KB/audio-video/midiwrapper.aspx。作者实现了一个标准的钢琴键盘。
然后就是根据乐谱转化为相应的音符。乐谱就是一串字符,根据输入的字符,对照上面的转换表,转化为低音,中音,高音的音符。代码中如何实现呢?
CMIDIWrapper中定义了钢琴中基本的十二个音,
enum NOTE_ID { C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B };
SHARP是表示高半音,但是我们常用的七音符: 1,2,3,4,5,6,7 <==> C, D, E, F, G, A B.
NOTE_PER_OCTAVE = 12, //一个组十二个音
LOW_NOTE = 36 //低音符开始值
NoteId = (octave) * NOTE_PER_OCTAVE + LOW_NOTE;
octave表示第几个音组,0-4 一共五个组,从低音到最高音五个档次,NOTE_PER_OCTAVE是一个偏移量。我们关心的是每个音组中的C,D,E,F,G,A,B的提取。
#include "stdafx.h" #include <iostream> #include <sstream> #include <fstream> #include <string> #include <vector> #include <map> #include <ctime> #include <xutility> #include <algorithm> #include <windows.h> #include "midi.h" #include "MIDIOutDevice.h" #include "ShortMsg.h" using namespace std; using midi::CMIDIOutDevice; using midi::CShortMsg; // Some useful constants const unsigned char CHANNEL = 0; const unsigned char NOTE_ID = 64; const unsigned char VELOCITY = 127; class MusicPlayer { public: MusicPlayer(midi::CMIDIOutDevice* device) : _device(device) { m_KeyMap[0] = C; // m_KeyMap[1] = D; // m_KeyMap[2] = E; // m_KeyMap[3] = F; // m_KeyMap[4] = G; // m_KeyMap[5] = A; // m_KeyMap[6] = B; // } ~MusicPlayer() { _device = 0; } enum NOTE_INFORMATION { CHANNEL = 0, NOTE_ID = 64, VELOCITY = 127, NOTE_PER_OCTAVE = 12, //一个音节 LOW_NOTE = 36 }; enum NOTE_ID { C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B }; void PlayLatterRhythm(const unsigned char& letter, float time) { unsigned char NoteId = LetterToNote(letter); OnNoteOn(NoteId); { // Wait a bit ::Sleep(700*time); }OnNoteOff(NoteId); } // postfix : _ , - void PlayNumberRhythm(int number, unsigned char postfix, float time) { unsigned char NoteId = numberToNote(number, postfix); OnNoteOn(NoteId); { // Wait a bit //::Sleep(700*time); clock_t current = clock(); while (clock()-current < 700*time) ; }OnNoteOff(NoteId); } protected: // Note-on event notification bool OnNoteOn(unsigned char NoteId) { midi::CShortMsg ShortMsg(midi::NOTE_ON, CHANNEL, NoteId, VELOCITY, 0); ShortMsg.SendMsg(*_device); return true; } // Note-off event notification bool OnNoteOff(unsigned char NoteId) { midi::CShortMsg ShortMsg(midi::NOTE_OFF, CHANNEL, NoteId, VELOCITY, 0); ShortMsg.SendMsg(*_device); return true; } ////////////////////////////////// // Low Section // 1_ 2_ 3_ 4_ 5_ 6_ 7_ // A B C D E F G // a b c d e f g // Middle Section // 1- 2- 3- 4- 5- 6- 7- // H I J K L M N // h i j k l m n // High Section // 1` 2` 3` 4` 5` 6` 7` // O P Q R S T U // o p q r s t u ////////////////////////////////// unsigned char LetterToNote(unsigned char letter) { bool result; result = (letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'); if (!result) return result; int octave(0); int idx(-1); if (letter >= 97) { octave = (letter-'a') / 7; idx = (letter-'a') % 7; } else { octave = (letter-'A') / 7; idx = (letter-'A') % 7; } int NoteId(-1); NoteId = m_KeyMap[idx]; NoteId += (octave+1) * NOTE_PER_OCTAVE + LOW_NOTE; return NoteId; } // postfix: '_' '-' '`' // low middle high // number: 1-7 unsigned char numberToNote(int number, unsigned char postfix) { int octave(-1); int NoteId(-1); if ('_'==postfix) octave=0; else if ('-'==postfix) octave=1; else if ('`'==postfix) octave=2; NoteId = m_KeyMap[number-1]; NoteId += (octave+1) * NOTE_PER_OCTAVE + LOW_NOTE; return NoteId; } private: midi::CMIDIOutDevice* _device; int m_KeyMap[7]; }; int main(void) { try { // Make sure there is an output device present if(CMIDIOutDevice::GetNumDevs() > 0) { CMIDIOutDevice OutDevice; // Use the first device OutDevice.Open(0); MusicPlayer player(&OutDevice); //ifstream inFile("BJ Welcom U-letter.txt"); //vector<string> musicList; //copy(istream_iterator<string>(inFile), istream_iterator<string>(), back_inserter(musicList)); //copy(musicList.begin(), musicList.end(), ostream_iterator<string>(cout,"/n")); // //for (size_t i(0); i<musicList.size(); i++) //{ // std::istringstream iss(musicList[i]); // unsigned char letter; // unsigned char u; // float value; // iss >> letter >> u >> value; // std::cout << letter << "/t" << value << std::endl; // player.PlayLatterRhythm(letter, value); //} //ifstream inFile("BJ Welcom U-number.txt"); ifstream inFile("tian kong zhi cheng.txt"); vector<string> musicList; copy(istream_iterator<string>(inFile), istream_iterator<string>(), back_inserter(musicList)); copy(musicList.begin(), musicList.end(), ostream_iterator<string>(cout,"/n")); for (size_t i(0); i<musicList.size(); i++) { if ('#'==musicList[i][0]) continue; std::istringstream iss(musicList[i]); int number; unsigned char postfix; float value; iss >> number >> postfix >> value; std::cout << number << "/t" << value << std::endl; player.PlayNumberRhythm(number, postfix, value); } // Close device OutDevice.Close(); } else { std::cout << "No MIDI output devices present!/n"; } } catch(const std::exception &Err) { std::cout << Err.what(); } system("PAUSE"); return 0; }
要运行代码,需要下载CMIDIWrapper:
这个库中使用MFC下的CWinThread,所以需要在stdafx中增加:
#define _AFXDLL
#include <afxwin.h>
而且设置工程使用多字节字符集;C++代码生成中,运行库:多线程DLL/MD;添加输入库:winmm.lib
程序说明
PlayLatterRhythm 函数可以播放字母音符,后面float表示停顿时间,
PlayNumberRhythm 播放数字音符,后缀标志音高,中,低;float表示停顿时间。
最后根据flash中的字母音节,制作了自己的钢琴简谱,
字母简谱的格式为: Letter,playTime
数字简谱的格式,低音,中音,高音分别为:
number_playTime number-playTime number`playTime
BJ Welcom U-letter.txt内容,只是北京欢饮你第一句:
J,0.5 L,0.5 J,0.5 I,0.5 J,0.5 I,0.5 J,1 J,0.75 I,0.25 F,0.5 H,0.5 J,0.5 I,1.5
BJ Welcom U-number.txt内容,北京欢饮你的整个简谱,数字版:
#sentence1 3-0.5 5-0.5 3-0.5 2-0.5 3-0.5 2-0.5 3-1 3-0.75 2-0.25 6_0.5 1-0.5 3-0.5 2-1.5 #sentence2 2-0.5 1-0.5 6_0.5 1-0.5 2-0.5 3-0.5 5-0.5 2-0.5 3-0.5 6-0.5 5-0.5 5_0.5 2-0.5 1-1.5 #sentence3 2-0.5 1-0.5 6_0.5 1-0.5 2-0.5 3-0.5 5-0.5 2-0.5 3-0.5 6-0.5 5-0.5 5-0.5 3-2 #sentence4 2-0.5 3-0.5 2-0.5 1-0.5 5-0.75 6-0.25 3-1 6_0.5 3-0.5 2-0.5 2-0.5 1-1.5 #sentence5 3-0.5 5-0.5 1`0.5 5-0.5 6-1.5 5-0.5 6-0.5 5-0.5 3-0.5 3-0.5 5-0.5 5-1.5 #sentence6 3-0.5 5-0.5 6-0.5 1`0.5 2`0.5 1`0.5 5-0.5 3-0.5 2-0.5 5-1.5 3-2
运行程序,就会自动播放制作好的简谱咯~ O(∩_∩)O哈哈~