しかし、電子ドラムで実行すると、模範演奏と演奏者がリアルタイムに叩いた音が表示できません。模範演奏のMIDIデータは読み込んで音は流れます。リアルタイムにドラムからメッセージ0x99 0x2B 0x00といった3バイトが送られてきます。(何も叩かないときは常に0xF8、たまに0xFEが送られてきます。)なんとかして上の画像のように模範演奏の音符と電子ドラムの音符の描画をできるようにしたいです。色々と試みたのですが、自分の知識ではまだまだでわからないことが多く、うまくいきません。なんとかお力を借りれないでしょうか。
#include <windows.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdio.h>
#include <time.h>
#include <process.h>
#include "MIDIIO.h"
#include "MIDIData.h"
#include "MIDIClock.h"
#include <opencv2/opencv.hpp>
SYSTEMTIME stTime; /* 現在時刻獲得用変数 */
static char strTime[128]; /* 現在時刻表示用の文字列 */
double cur_time; /* 現在時刻の,当日午前0時からの秒数 */
#define SHEET_GAP 20 //楽譜の表示位置の初期の値
#define NOTE_GAP 60 //音符の表示位置の初期の値
#define NOTE_H 12 //四分音符の表示する高さの初期の値
#define NOTE_W (NOTE_H * 1.05) //二分音符の表示する高さの初期の値
#define NOTE_ANGLE (-20) //音符の大きさ
#define SHEET_W (SHEET_GAP * 2 + NOTE_GAP * 4 * 4) //楽譜の横の長さ
#define SHEET_H (SHEET_GAP * 2 + NOTE_H * 4 + SHEET_GAP * 2) //楽譜の縦の長さ
#define NOTE_OFFSET (12 * 3) //音符表示できる数
#define USE_KEYBOARD 1 //0ならキーボード不使用、1ならキーボード使用
char* winname = "Sheet";
bool g_bContinue = true;
MIDIIn* pMIDIIn; //MIDIの初期化
MIDIOut* pMIDIOut;
HANDLE hGetNotesMutex; //ミューテックスのハンドル
HANDLE hNoteXMutex;
int note_x;
int note_x_by_midi;
int note_y[11];
int flag[2] = { 1, 1 }; // [0] for midi input, [1] for keyboard input
cv::Mat sheet_base;
cv::Mat sheet_note;
cv::Mat sheet_draw;
cv::Mat sheet_base2[4];
int current_x = SHEET_GAP;
std::vector<int> note_correct_x;
std::vector<int> note_correct_y;
std::vector<int> note_player_x;
std::vector<int> note_player_y;
int hitcount = 0;
double avelen = 0.0;
double avelen2[4] = { 0.0, 0.0, 0.0, 0.0 };
void pseudoColor(double index, unsigned char& B, unsigned char& G, unsigned char& R) { //擬似カラーの設定
// 0.0 <= index <= 1.0
if (0.0 <= index && index <= 0.25) {
B = 255;
R = 0;
G = (int)(256.0 / 64.0 * index * 255.0);
}
else if (0.25 < index && index <= 0.5) {
G = 255;
R = 0;
B = (int)(-256.0 / 64.0 * index * 255.0 + 511.0);
}
else if (0.5 < index && index <= 0.75) {
B = 0;
G = 255;
R = (int)(256.0 / 64.0 * index * 255.0 - 511.0);
}
else if (0.75 < index && index <= 1.0) {
R = 255;
B = 0;
G = (int)(-256.0 / 64.0 * index * 255.0 + 256 * 3 - 1);
}
else {
// Error
}
}
void PutNote(cv::Mat& sheet, unsigned char status, unsigned char data1, unsigned char data2, long duration, cv::Scalar color, int note_x, int& index) { //音符の配置
if ((status & 0xF0) == 0x90 && ((status & 0x0F) == 9 || data2 != 0x00)) {
if (data1 == (0x18 + NOTE_OFFSET)) { // Do C
index = 0;
}
else if (data1 == (0x1A + NOTE_OFFSET)) { // Re D
index = 1;
}
else if (data1 == (0x1C + NOTE_OFFSET)) { // Mi E
index = 2;
}
else if (data1 == (0x1D + NOTE_OFFSET)) { // Fa F
index = 3;
}
else if (data1 == (0x26 + NOTE_OFFSET)) { // So G
index = 4;
}
else if (data1 == (0x21 + NOTE_OFFSET)) { // Ra
index = 5;
}
else if (data1 == (0x23 + NOTE_OFFSET)) { //Shi
index = 6;
}
else if (data1 == (0x24 + NOTE_OFFSET)) { // Do
index = 7;
}
else if (data1 == (0x1F + NOTE_OFFSET)) { // Re
index = 8;
}
else if (data1 == (0x28 + NOTE_OFFSET)) {
index = 9;
}
else if (data1 == (0x19 + NOTE_OFFSET)) {
index = 10;
}
if (index != -1) {
if (duration == 120) {
cv::ellipse(sheet, cv::Point(note_x, note_y[index]), cv::Size(NOTE_W, NOTE_H / 2), NOTE_ANGLE, 0, 360, color, -1);
}
else if (duration == 240) {
cv::ellipse(sheet, cv::Point(note_x, note_y[index]), cv::Size(NOTE_W, NOTE_H / 2), NOTE_ANGLE, 0, 360, color, 2);
}
else {
}
}
}
else if (status == 0x80 || data2 == 0x00) {
}
}
void PutNoteData(int mode, cv::Mat& sheet, unsigned char status, unsigned char data1, unsigned char data2, long duration, cv::Scalar color, int note_x) {
int index = -1;
PutNote(sheet, status, data1, data2, duration, color, note_x, index);
if (mode == 0) { // 楽譜構築モード
note_correct_x.push_back(note_x);
note_correct_y.push_back(index);
}
else if (mode == 1) { // ユーザによる演奏音符反映モード
note_player_x.push_back(note_x);
note_player_y.push_back(index);
}
else {
// mode == -1 // 時刻に対応する音符反映モード
}
}
unsigned __stdcall GetNotesThread(void *p)
{
long lLen;
unsigned char byMessage[256];
/* MIDIメッセージの取得ループ */
while (g_bContinue) {
WaitForSingleObject(hGetNotesMutex, INFINITE); //mutex 間は他のスレッドから変数を変更できない
lLen = MIDIIn_GetMIDIMessage(pMIDIIn, byMessage, 256);
ReleaseMutex(hGetNotesMutex);
/* MIDIメッセージを取得した */
if (lLen > 0) {
/* スレッド */
WaitForSingleObject(hGetNotesMutex, INFINITE);
MIDIOut_PutMIDIMessage(pMIDIOut, byMessage, lLen);
for (int i = 0; i < lLen; i++) {
std::cerr << "0x" << std::setw(2) << std::setfill('0') << std::hex << std::uppercase << (int)byMessage[i] << " ";
}
std::cerr << "/ ";;
ReleaseMutex(hGetNotesMutex);
unsigned char status = byMessage[0];
unsigned char data1 = byMessage[1];
unsigned char data2 = byMessage[2];
if ((status & 0xF0) == 0x99 && data2 != 0x00) {
hitcount++;
WaitForSingleObject(hNoteXMutex, INFINITE); //mutex 間は他のスレッドから変数を変更できない
PutNoteData(1, sheet_base2[0], status, data1, data2, 120, cv::Scalar(127, 127, 127), current_x);
int key_x = note_player_x[note_player_x.size() - 1];
int key_y = note_player_y[note_player_y.size() - 1];
int nearest_index = 0;
int nearest_length = 99999;
for (int i = 0; i < note_correct_x.size(); i++) {
if (abs(note_correct_x[i] - key_x) < nearest_length) {
nearest_index = i;
nearest_length = abs(note_correct_x[i] - key_x);
}
}
int near_x = note_correct_x[nearest_index];
int near_y = note_correct_y[nearest_index];
double diff;
// CalcPseudoColor
unsigned char b, g, r;
//ずれ情報なし
diff = 0.0;
pseudoColor(diff, b, g, r);
PutNoteData(-1, sheet_base2[0], status, data1, data2, 120, cv::Scalar(b, g, r), current_x);
avelen2[0] += diff;
//時間のずれ
diff = abs(key_x - near_x) / 30.0; // 0 ~ 30
if (diff > 1.0) {
diff = 1.0;
}
pseudoColor(diff, b, g, r);
PutNoteData(-1, sheet_base2[1], status, data1, data2, 120, cv::Scalar(b, g, r), current_x);
avelen2[1] += diff;
//音程のずれ
diff = abs((double)key_y - (double)near_y) / 4.0; //高さのindexの差
fprintf(stderr, "diff0 = %f\n", diff);
if (diff > 1.0) {
diff = 1.0;
}
pseudoColor(diff, b, g, r);
PutNoteData(-1, sheet_base2[2], status, data1, data2, 120, cv::Scalar(b, g, r), current_x);
avelen2[2] += diff;
fprintf(stderr, "diff = %f\n", diff);
fprintf(stderr, "hitcount = %d\n", hitcount);
//音程と時間両方のずれ
double diff0 = abs(key_x - near_x) / 30.0;
double diff1 = abs((double)key_y - (double)near_y) / 4.0;
diff = sqrt(diff0 * diff0 + diff1 * diff1) / sqrt(2.0);
if (diff > 1.0) {
diff = 1.0;
}
pseudoColor(diff, b, g, r);
PutNoteData(-1, sheet_base2[3], status, data1, data2, 120, cv::Scalar(b, g, r), current_x);
avelen2[3] += diff;
ReleaseMutex(hNoteXMutex);
}
}
/* MIDIメッセージを取得しなかった */
else {
/* スリープ処理 */
Sleep(1);
}
}
_endthreadex(0);
return 0; //コンパイラの警告を殺す
}
int main(int argc, char* argv[]) {
hGetNotesMutex = CreateMutex(NULL, FALSE, NULL); //ミューテックス生成
hNoteXMutex = CreateMutex(NULL, FALSE, NULL); // note_xのためのミューテックス生成
HANDLE hThread;
char szDeviceName[32];
MIDIData* pMIDIData;
MIDITrack* pMIDITrack;
MIDIEvent* pMIDIEvent;
MIDIClock* pMIDIClock = NULL;
long lMillisec = 0;
long lTickCount = 0;
/* MIDIクロックの生成(TPQNベース, 分解能=120, テンポ:4分音符=100) */
pMIDIClock = MIDIClock_Create(MIDICLOCK_TPQNBASE, 120, 60000000 / 100);
if (pMIDIClock == NULL) {
printf("MIDIクロックの生成に失敗しました。\n");
return 0;
}
/* スタンダードMIDIファイル(*.mid)からMIDIデータの読み込み */
pMIDIData = MIDIData_LoadFromSMF("drum.mid");
if (pMIDIData == NULL) {
/* 異常終了時の処理 */
std::cerr << "MIDIデータの読み込みに失敗しました." << std::endl;
return -1;
}
/* MIDIデータのプロパティを出力する。*/
printf("[MIDIデータ]\n");
printf("フォーマット=%d\n", MIDIData_GetFormat(pMIDIData));
printf("トラック数=%d\n", MIDIData_GetNumTrack(pMIDIData));
printf("タイムモード=%d\n", MIDIData_GetTimeMode(pMIDIData));
printf("タイムレゾリューション=%d\n", MIDIData_GetTimeResolution(pMIDIData));
/* 正常終了時の処理 */
forEachTrack(pMIDIData, pMIDITrack) {
forEachEvent(pMIDITrack, pMIDIEvent) {
if (MIDIEvent_IsNoteOn(pMIDIEvent)) {
MIDIEvent_Combine(pMIDIEvent);
}
}
}
char szBuf[1024];
forEachTrack(pMIDIData, pMIDITrack) {
printf("[MIDIトラック]\n");
printf("小節:拍:ティック 種類 長さ 内容\n");
/* それぞれのイベントについて */
forEachEvent(pMIDITrack, pMIDIEvent) {
printf("%s\n", MIDIEvent_ToString(pMIDIEvent, szBuf, sizeof(szBuf)));
}
}
/* 音符画像(ベース)の表示*/
for (int i = 0; i < 10; i++) {
note_y[i] = (SHEET_GAP * 2 + NOTE_H * 5) - NOTE_H * 0.5 * i;
}
sheet_base.create(cv::Size(SHEET_W, SHEET_H), CV_8UC3);
sheet_base = cv::Scalar(255, 255, 255);
for (int i = 0; i < 5; i++) {
cv::line(sheet_base, cv::Point(SHEET_GAP, SHEET_GAP * 2 + NOTE_H * i), cv::Point(SHEET_W - SHEET_GAP, SHEET_GAP * 2 + NOTE_H * i), cv::Scalar(0, 0, 0));
}
note_x = SHEET_GAP + NOTE_GAP / 2; // 初期化
note_x_by_midi = SHEET_GAP + NOTE_GAP / 2; // 初期化
/***** 楽譜の描画 *****/
forEachTrack(pMIDIData, pMIDITrack) {
forEachEvent(pMIDITrack, pMIDIEvent) {
int lLen = pMIDIEvent->m_lLen;
if (note_x < SHEET_W - SHEET_GAP * 2 && MIDIEvent_IsNoteOn(pMIDIEvent)/*&& counttest < 30*/) {
std::cerr << "Event Size: " << lLen << "(Bytes)" << std::endl;
unsigned char status = pMIDIEvent->m_pData[0];
unsigned char data1 = pMIDIEvent->m_pData[1];
unsigned char data2 = pMIDIEvent->m_pData[2];
long duration = MIDIEvent_GetDuration(pMIDIEvent);
PutNoteData(0, sheet_base, status, data1, data2, duration, cv::Scalar(0, 0, 0), note_x);
if (duration == 120) {
note_x += NOTE_GAP;
}
else if (duration == 240) {
note_x += NOTE_GAP * 2;
}
else {
//Error
}
}
}
}
note_x = SHEET_GAP + NOTE_GAP / 2; // 初期化
note_x_by_midi = SHEET_GAP + NOTE_GAP / 2; // 初期化
cv::imshow(winname, sheet_base);
cv::moveWindow(winname, 0, 0);
int key = cv::waitKey(1);
/* */
long lRet;
#if USE_KEYBOARD
lRet = MIDIIn_GetDeviceNum();
std::cerr << "Connected MIDI Device Num = " << lRet << std::endl;
lRet = MIDIIn_GetDeviceName(0, szDeviceName, 32);
if (lRet == 0) {
printf("利用できるMIDI入力デバイスはありません。\n");
system("PAUSE");
return 0;
}
pMIDIIn = MIDIIn_Open(szDeviceName);
if (pMIDIIn == NULL) {
std::cerr << "MIDI入力デバイス「" << szDeviceName << "」を開けません。" << std::endl;
return 0;
}
std::cerr << "MIDI入力デバイス「" << szDeviceName << "」を開きました。" << std::endl;
#endif
/* MIDI出力デバイス(No.0)の名前を調べる */
lRet = MIDIOut_GetDeviceName(0, szDeviceName, 32);
if (lRet == 0) {
std::cerr << "利用できるMIDI出力デバイスはありません。" << std::endl;
return 0;
}
/* MIDI出力デバイスを開く */
pMIDIOut = MIDIOut_Open(szDeviceName);
if (pMIDIOut == NULL) {
std::cerr << "MIDI出力デバイス「" << szDeviceName << "」を開けません。" << std::endl;
return 0;
}
std::cerr << "MIDI出力デバイス「" << szDeviceName << "」を開きました。" << std::endl;
hThread = (HANDLE)_beginthreadex(NULL, 0, GetNotesThread, "GetNotesThread", 0, NULL);
sheet_base.copyTo(sheet_note);
sheet_base.copyTo(sheet_draw);
sheet_base.copyTo(sheet_base2[0]);
sheet_base.copyTo(sheet_base2[1]);
sheet_base.copyTo(sheet_base2[2]);
sheet_base.copyTo(sheet_base2[3]);
/* MIDIクロックのリセットとスタート */
MIDIClock_Reset(pMIDIClock);
MIDIClock_Start(pMIDIClock);
forEachTrack(pMIDIData, pMIDITrack) {
lTickCount = 0;
printf("トラック名:%s\n", MIDITrack_GetName(pMIDITrack, szBuf, sizeof(szBuf)));
/* 最初のNOTE_ONイベントを獲得 */
pMIDIEvent = pMIDITrack->m_pFirstEvent;
long breakTickCount = 120 / 2;
while (lTickCount < 120 * 4 * 4 && pMIDIEvent != NULL) {
/* 移動する縦線を引く*/
sheet_note.copyTo(sheet_draw);
current_x = SHEET_GAP + (lTickCount * (SHEET_W - SHEET_GAP * 2)) / (480 * 4);
cv::line(sheet_draw, cv::Point(current_x, SHEET_GAP), cv::Point(current_x, SHEET_H - SHEET_GAP), cv::Scalar(0, 0, 255));
cv::imshow(winname, sheet_draw);
key = cv::waitKey(1);
if (breakTickCount < lTickCount) {
while (!MIDIEvent_IsNoteOn(pMIDIEvent)) {
std::cerr << MIDIEvent_ToString(pMIDIEvent, szBuf, sizeof(szBuf)) << std::endl;
pMIDIEvent = pMIDIEvent->m_pNextEvent;
}
long duration = MIDIEvent_GetDuration(pMIDIEvent);
breakTickCount = lTickCount + duration;
unsigned char status = pMIDIEvent->m_pData[0];
unsigned char data1 = pMIDIEvent->m_pData[1];
unsigned char data2 = pMIDIEvent->m_pData[2];
if (status == 0x99) {
int lRet = pMIDIEvent->m_lLen;
/* MIDI出力デバイスからメッセージを送出する */
WaitForSingleObject(hGetNotesMutex, INFINITE);
MIDIOut_PutMIDIMessage(pMIDIOut, pMIDIEvent->m_pData, lRet);
for (int i = 0; i < lRet; i++) {
fprintf(stderr, "0x%02X ", pMIDIEvent->m_pData[i]);
}
printf("/ ");
ReleaseMutex(hGetNotesMutex);
}
sheet_base.copyTo(sheet_note);
PutNoteData(-1, sheet_note, status, data1, data2, duration, cv::Scalar(255, 255, 0), note_x_by_midi);
if (duration == 120) {
note_x_by_midi += NOTE_GAP;
}
else if (duration == 240) {
note_x_by_midi += NOTE_GAP * 2;
}
else {
}
pMIDIEvent = pMIDIEvent->m_pNextEvent;
}
lTickCount = MIDIClock_GetTickCount(pMIDIClock);
}
}
if (hitcount == 0) {
std::cerr << "No Count" << std::endl;
}
else {
fprintf(stderr, "Average Length[0] = %f\n", (double)avelen2[0] / hitcount);
fprintf(stderr, "Average Length[1] = %f\n", (double)avelen2[1] / hitcount);
fprintf(stderr, "Average Length[2] = %f\n", (double)avelen2[2] / hitcount);
fprintf(stderr, "Average Length[3] = %f\n", (double)avelen2[3] / hitcount);
}
key = cv::waitKey(0);
GetLocalTime(&stTime); /* 現在時刻獲得 */
/* 獲得した時刻を文字列に変換 */
for (int i = 0; i < 4; i++) {
sprintf_s(strTime, 128, "%04d%02d%02d_%02d%02d%02d_%1d.png",
stTime.wYear, stTime.wMonth, stTime.wDay,
stTime.wHour, stTime.wMinute, stTime.wSecond, i);
cv::imwrite(strTime, sheet_base2[i]);
}
#if USE_KEYBOARD
WaitForSingleObject(hThread, INFINITE); /* スレッドが終了するまで待つ。 */
CloseHandle(hThread); /* ハンドルを閉じる */
#endif
/* MIDI入力デバイスを閉じる */
MIDIIn_Close(pMIDIIn);
/* MIDI出力デバイスを閉じる */
MIDIOut_Close(pMIDIOut);
return 0;
}