midi.zip/midi.cpp
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <memory>
#include <vector>
#include <boost/format.hpp>
#include <boost/range/irange.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/optional.hpp>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <Windows.h>
#include <Shlwapi.h>
#include "midi.h"
#pragma comment (lib, "winmm.lib")
#pragma comment (lib, "Shlwapi.lib")
#define TEMPO 500000
void SetAppDir(void) {
char str[MAX_PATH];
::GetModuleFileNameA(nullptr, str, _countof(str));
::PathRemoveFileSpecA(str);
::SetCurrentDirectoryA(str);
}
//デフォルト指定可能版atoi
int atoi2(char *str,int def) {
int i;
if(!isdigit(str[0]))return def;
i = atoi(str);
if(i == 0 && str[0] != '0')return def;
return i;
}
void rev(void *a,int len) { //バイト反転(バイトオーダー問題)
char t,*s,*b;
s = (char *)a;
b = s+len-1;
while(s < b){
t = *s;
*s = *b;
*b = t;
s++;
b--;
}
}
void err(int num) { //errメッセージ出力、個別に書いているからもはや存在意義はない
if(num == ERR_NON){
printf("正常な処理に失敗しました\n");
getchar();
exit(1);
}
if(num == ERR_FORMAT)printf("未対応のフォーマットです\n読み込みに失敗するか何らかの情報が欠落する可能性があります\n");
}
volatile bool PlayStatus = false;
void MonitorPlayStatus() {
const unsigned int interval = 16;
while (true) {
if (::GetKeyState(VK_F7) >= 0) {
::Sleep(interval);
continue;
}
PlayStatus = false;
unsigned int status = 0;
while (true) {
if (status == 0 && ::GetKeyState(VK_F7) >= 0) {
status = 1;
} else if (status == 1 && ::GetKeyState(VK_F7) < 0) {
status = 2;
} else if (status == 2 && ::GetKeyState(VK_F7) >= 0) {
break;
}
::Sleep(interval);
}
PlayStatus = true;
}
}
void MidiPlay(const Midi &midi, const double start, const int deviceId, const unsigned int volume, const double speed) {
if (midi.head->format != 0) {
std::cout << "このプログラムはフォーマット0以外のmidi再生は未対応です。\n";
return;
}
::timeBeginPeriod(1);
std::cout << "midiの再生を開始します、F7で一時停止\n";
HMIDIOUT hmo;
if(MMSYSERR_NOERROR != ::midiOutOpen(&hmo, deviceId, 0, 0, CALLBACK_NULL)){
std::cout << "デバイスのオープンに失敗しました\n";
err(ERR_NON);
}
::midiOutSetVolume(hmo, volume * 0xFFFF / 100);
MIDIHDR mh;
::memset(&mh, 0, sizeof(mh));
unsigned int t1 = ::timeGetTime();
::midiOutReset(hmo);
int tempo = TEMPO;
const int delta = midi.head->time;
double time = 0.0;
const double totalTime = midi.track[0].time / speed;
const std::string totalTimeString = (
boost::format("%u:%02u:%02u")
% (static_cast<unsigned int>(::floor(totalTime / 60 / 60)))
% (static_cast<unsigned int>(::floor(totalTime / 60)) % 60)
% (static_cast<unsigned int>(::floor(totalTime)) % 60)
).str();
bool noteStatus[16][256] = {};
PlayStatus = true;
boost::thread keyMonitorThread(MonitorPlayStatus);
for (const MidiData &a : midi.track[0].dataList) {
const unsigned int deltaTime = static_cast<unsigned int>(a.time / speed);
double t = deltaTime * (tempo / 1000.0) / delta;
const unsigned int t2 = timeGetTime()-t1;
t1 += static_cast<unsigned int>(t);
if(time >= start){
const auto printTime = [&totalTimeString](const double time) {
std::cout << boost::format("\r%d:%02d:%02d/%s") % (static_cast<int>(floor(time / 60 / 60))) % (static_cast<int>(floor(time / 60)) % 60) % (static_cast<int>(floor(time)) % 60) % totalTimeString;
};
const int temp = static_cast<int>(t - (t2 <= 0 ? 0 : t2));
const unsigned int sleepTime = (temp <= 0 ? 0 : temp);
const unsigned int nextRefreshTime = 1000 - static_cast<unsigned int>(::floor(time*1000)) % 1000;
const unsigned int safe = 4;
const unsigned int safeSleepTime = (sleepTime <= safe ? 0 : sleepTime - safe);
if (safeSleepTime <= nextRefreshTime) {
::Sleep(safeSleepTime);
} else {
::Sleep(nextRefreshTime <= safe ? 0 : nextRefreshTime - safe);
for (const unsigned int i : boost::irange(0U, (safeSleepTime - nextRefreshTime) / 1000)) {
printTime(time + static_cast<double>(nextRefreshTime) / 1000 + i);
::Sleep(1000 - safe);
}
printTime(time + static_cast<double>(nextRefreshTime) / 1000 + static_cast<double>((safeSleepTime - nextRefreshTime) / 1000));
const int temp = t1 - timeGetTime() - safe;
::Sleep(temp <= 0 ? 0 : temp);
}
while(t1 > timeGetTime()){}
printTime(time + static_cast<double>(sleepTime) / 1000);
}
time += t / 1000;
if (a.stat == 0xFF) { //メタイベント
if(a.param1 == 0x2F){ //エンドオブトラック
break;
} else if(a.param1 == 0x51){ // セットテンポ
tempo = a.param3[2] + (a.param3[1] << 8) + (a.param3[0] << 16);
}
} else if (a.stat >= 0xF0) { //SysExイベント
std::vector<char> str;
unsigned int len = 0;
switch(a.stat){
case 0xF0:
len = a.param1+1;
str.resize(len);
str[0] = a.stat;
if (!a.param3.empty()) {
::memcpy(&str[1], &a.param3.front(), a.param1);
}
break;
case 0xF7:
len = a.param1;
str.resize(len);
if (!a.param3.empty()) {
::memcpy(&str.front(), &a.param3.front(), a.param1);
}
break;
default:
printf("未対応のSysExイベントに遭遇しました\n");
err(ERR_NON);
break;
}
mh.lpData = (str.empty() ? nullptr : &str.front());
mh.dwFlags = 0;
mh.dwBufferLength = len;
mh.dwBytesRecorded = len;
if(MMSYSERR_NOERROR != midiOutPrepareHeader(hmo, &mh, sizeof(MIDIHDR))){
printf("\n排他バッファを取得できませんでした。\nSysExイベントを実行できません。\n");
err(ERR_NON);
} else {
::midiOutLongMsg(hmo,&mh,sizeof(MIDIHDR));
while ((mh.dwFlags & MHDR_DONE) == 0);
const MMRESULT result = ::midiOutUnprepareHeader(hmo, &mh, sizeof(MIDIHDR));
while(MMSYSERR_NOERROR != result){
if(result != MIDIERR_STILLPLAYING){
printf("SysEx命令実行中にエラーが発生しました。\n");
err(ERR_NON);
}
}
}
} else { //MIDIイベント
noteStatus[a.stat & 0x0F][a.param1] = ((a.stat & 0x90) != 0);
const unsigned char type = a.stat&0xF0; //Bx Cx Dx系のイベントは通しておかないと楽器情報がおかしくなる
if(type ==0xB0 || type ==0xC0 || type ==0xE0 ||time >= start){
const unsigned int msg = a.stat + (a.param1 << 8) | (a.param2 << 16);
::midiOutShortMsg(hmo, msg);
}
}
if (!PlayStatus) {
std::cout << "\n一時停止中、F7で復帰\n";
for (const unsigned int channel : boost::irange(0U, 16U)) {
for (const unsigned int note : boost::irange(0U, 256U)) {
if (!noteStatus[channel][note]) {
continue;
}
const unsigned int msg = 0x80 + channel + (note << 8) | (0xFF << 16);
::midiOutShortMsg(hmo, msg);
}
}
unsigned int status = 0;
while (true) {
if (PlayStatus) {
break;
}
::Sleep(100);
}
std::cout << "復帰します\n";
t1 = ::timeGetTime();
}
}
keyMonitorThread.interrupt();
::midiOutReset(hmo); //念のため
::midiOutClose(hmo);
printf("\n再生を終了します\n");
::timeEndPeriod(1);
}
unsigned int track2bin(const std::vector<MidiData> &dataList, std::ostream &os) { //loadtrackの逆、トラックデータを下にファイル出力する
unsigned int t,temp,count;
const auto start = os.tellp();//書き込み文字数計測用
for (const MidiData &a : dataList) {
t = 0xFE00000; //デルタタイム書き込み
count = 21;
while(1){
if(a.time&t || count == 0){
while(1){
temp = ((a.time&t)>>count);
if(count)temp += 128;
os.write(reinterpret_cast<const char *>(&temp), 1);
if(!count)break;
t = t >> 7;
count -= 7;
}
break;
}
t = t >> 7;
count -= 7;
}
os.write(reinterpret_cast<const char *>(&a.stat), 1);//イベント書き込み
os.write(reinterpret_cast<const char *>(&a.param1), 1);
switch(a.stat & 0xF0){
case 0x80:
case 0x90:
case 0xA0:
case 0xB0:
case 0xE0:
os.write(reinterpret_cast<const char *>(&a.param2), 1);
break;
case 0xC0:
case 0xD0:
break;
case 0xF0:
switch (a.stat) {
case 0xF0:
case 0xF7:
if (!a.param3.empty()) {
os.write(reinterpret_cast<const char *>(&a.param3.front()), a.param1);
}
break;
case 0xFF:
os.write(reinterpret_cast<const char *>(&a.param2), 1);
if (!a.param3.empty()) {
os.write(reinterpret_cast<const char *>(&a.param3.front()), a.param2);
}
break;
default:
printf("未対応のイベントです(ステータス0x%02x)\n", a.stat);
err(ERR_NON);
break;
}
break;
default:
printf("不正なイベントです(ステータス0x%02x)\n", a.stat);
err(ERR_NON);
break;
}
}
return static_cast<unsigned int>(os.tellp() - start);
}
void savemidi(const boost::filesystem::path &path, const Midi &midi) { //loadmidiの逆、midiデータをファイル出力する
int temp;
boost::filesystem::ofstream ofs(path, std::ios::binary);
printf("ヘッダー書き込み中\n");
ofs.write("MThd", 4); //ファイルヘッダ書き込み
temp = 0x06000000; //バイトオーダーが逆なので
ofs.write(reinterpret_cast<const char *>(&temp), 4);
if(midi.head->format == 0 && midi.head->num > 1){
std::cout << boost::format("\nトラックデータ数異常、フォーマット0では%d個のトラックは扱えません\n") % midi.head->num;
err(ERR_NON);
}
if(midi.head->format < 3){
temp = midi.head->num + midi.head->format*0x10000;
rev(&temp, 4);
ofs.write(reinterpret_cast<const char *>(&temp), 4);
} else {
std::cout << "未対応のフォーマット番号です、midiファイル化できません\n";
err(ERR_NON);
}
temp = midi.head->time;
rev(&temp,2);
ofs.write(reinterpret_cast<const char *>(&temp), 2);
std::cout << "ヘッダー書き込み完了\n";
std::cout << "トラックデータ書き込みを開始します\n";
for (const unsigned int i : boost::irange(0U, static_cast<unsigned int>(midi.head->num))) {
std::cout << boost::format("トラック%dを書き込んでいます...\n") % i;
ofs.write("MTrk", 4);
temp = 0;
rev(&temp, 4);
ofs.write(reinterpret_cast<const char *>(&temp), 4);
const unsigned int len = track2bin(midi.track[i].dataList, ofs);
temp = len;
rev(&temp, 4);
ofs.seekp(-static_cast<int>(len + 4), std::ios::cur);
ofs.write(reinterpret_cast<const char *>(&temp), 4);
ofs.seekp(len, std::ios::cur);
std::cout << boost::format("トラック%d(サイズ%d)の書き込みが完了しました\n") % i % len;
}
std::cout << boost::format("midiファイル%sの書き込みが完了しました\n") % path.string();
}
const std::vector<MidiData> &loadtrack(MidiTrack *track, unsigned char *data, int len, int time){ //トラックデータ読み込み
int temp,i,count,tempo;
unsigned char *p,prev = 0;
double stime=0.0;
p = data;
count = 0;
tempo = TEMPO;
while(p < data+len){
MidiData b;
b.param3.clear();
b.time = 0; //デルタタイム読み込み
do{
temp = *p;
b.time = (b.time<<7)+(temp&0x7F);
p++;
}while(temp&0x80);
b.stat = *p; //ステータスコード読み込み
p++;
if(b.stat&0x80){ //ランニングステータス用の分岐
b.param1 = *p;
p++;
prev = b.stat;
} else {
b.param1 = b.stat;
b.stat = prev;
}
switch(b.stat & 0xF0){
case 0x80:
case 0x90:
case 0xA0:
case 0xB0:
case 0xE0:
b.param2 = *p;
p++;
break;
case 0xC0:
case 0xD0:
b.param2 = 0;
break;
case 0xF0:
switch(b.stat){
case 0xF0:
case 0xF7:
b.param2 = 0;
b.param3.resize(b.param1);
if (!b.param3.empty()) {
::memcpy(&b.param3.front(), p, b.param1);
}
p+= b.param1;
break;
case 0xFF:
b.param2 = *p;
p++;
b.param3.resize(b.param2);
if (!b.param3.empty()) {
::memcpy(&b.param3.front(), p, b.param2);
}
p+= b.param2;
if(b.param1 == 0x51){ //セットテンポ
tempo = b.param3[2] + (b.param3[1] << 8) + (b.param3[0] << 16);
}
break;
default:
break;
}
break;
default:
printf("不正なトラックです(ステータス%02x:アドレス%d)\n", b.stat, reinterpret_cast<unsigned int>(p));
p-=10;
i = 0;
printf("以下デバッグ用出力:");
while(i < 10){
printf("%02x ",*p++);
i++;
}
err(ERR_NON);
break;
}
stime += b.time*(tempo/1000.0)/time;
count++;
track->dataList.push_back(b);
if(b.stat == 0xFF && b.param1 == 0x2F)break;
}
if(p < data+len)printf("正常に読み込めましたがトラックの長さが設定値と違います(設定値%d:実際%d)\n",len,p-data);
else if(p > data+len)printf("トラックが正常に終了していません(設定値%d:実際%d)\n",len,p-data);
printf("イベント数:%d\n演奏時間:%lf秒\n",count,stime/1000.0);
track->time = stime/1000.0;
return track->dataList;
}
std::unique_ptr<Midi> loadmidi(const char * const fname) { //midi読み込み
FILE *fp;
int i,temp;
std::unique_ptr<Midi> midi = std::make_unique<Midi>();
midi->head = std::make_unique<MidiHead>();
fopen_s(&fp, fname,"rb");
printf("ヘッダー解析中");
fread(midi->head->type,1,4,fp); //ファイルヘッダ読み込み
if(strncmp(midi->head->type,"MThd",4)){
printf("\n識別子MThdを認識できませんでした(%s)\n",midi->head->type);
err(ERR_NON);
}
printf(".");
fread(&(midi->head->len),4,1,fp);
rev(&(midi->head->len),4);
if(midi->head->len != 6){
printf("\nヘッダサイズ異常(%dbyte)\n",midi->head->len);
err(ERR_NON);
}
printf(".");
fread(&(midi->head->format),2,1,fp);
rev(&(midi->head->format),2);
if(midi->head->format > 2){
printf("\nフォーマット異常(type%d未対応)\n",midi->head->format);
err(ERR_NON);
}
printf(".");
fread(&(midi->head->num),2,1,fp);
rev(&(midi->head->num),2);
if(midi->head->format == 0 && midi->head->num > 1){
printf("\nトラックデータ数異常、フォーマット0では%d個のトラックは扱えません\n",midi->head->num);
err(ERR_NON);
}
printf(".");
fread(&(midi->head->time),2,1,fp);
rev(&(midi->head->time),2);
printf("\nトラックデータ取得中...\n");
midi->track = std::make_unique<MidiTrack[]>(midi->head->num);
if(!midi->track){
printf("メモリー確保に失敗しました、トラック数が多すぎるか使用可能メモリーが足りません\n");
err(ERR_NON);
}
i = 0;
while(i < midi->head->num){
printf("トラック%dを読み込んでいます...\n",i);
fread(midi->track[i].type,1,4,fp);
if(strncmp(midi->track[i].type,"MTrk",4)){
printf("識別子MTrkを認識できませんでした(%s)\n",midi->track[i].type);
err(ERR_NON);
}
fread(&(midi->track[i].len),4,1,fp);
rev(&(midi->track[i].len),4);
if(midi->track[i].len == 0){
printf("トラック%dには音響データが存在しません\n",i);
midi->track[i].dataList.clear();
} else {
std::vector<unsigned char> data(midi->track[i].len);
::fread(&data.front(), 1, midi->track[i].len, fp);
::loadtrack(&midi->track[i], &data.front(), midi->track[i].len, midi->head->time);
}
i++;
}
temp = ftell(fp);
fseek(fp,0,SEEK_END);
if(temp != ftell(fp))printf("ファイル末尾に使用されていないバイト列が存在します(サイズ%d)\n",ftell(fp)-temp);
fclose(fp);
printf("midiファイル%sの読み込みが完了しました\n\n",fname);
printf("フォーマット:%d\nトラック数:%d\n",midi->head->format,midi->head->num);
i = 0;
while(i < midi->head->num){
printf("トラック%d:%dbyte\n",i,midi->track[i].len);
i++;
}
printf("\n");
return midi;
}
//デバイス情報列挙
boost::optional<std::string> getDeviceInfoString(const unsigned int deviceId, const unsigned int indentCount) {
const char * const typeList[] = {
"MIDI hardware port",
"synthesizer",
"square wave synthesizer",
"FM synthesizer",
"Microsoft MIDI mapper",
"hardware wavetable synthesizer",
"software synthesizer",
};
MIDIOUTCAPS info;
const MMRESULT result = ::midiOutGetDevCaps(deviceId, &info, sizeof(info));
if (MMSYSERR_NOERROR != result) {
return{};
}
std::stringstream out;
std::string indent;
for (const unsigned int i : boost::irange(0U, indentCount)) {
indent += " ";
}
out << indent << boost::format("Manufacturer identifier : %d\n") % info.wMid;
out << indent << boost::format("Product identifier : %d\n") % info.wPid;
out << indent << boost::format("Version number : %d\n") % info.vDriverVersion;
out << indent << boost::format("Technology : %s\n") % typeList[info.wTechnology - 1];
out << indent << boost::format("Number of voices : %d\n") % info.wVoices;
out << indent << boost::format("Maximum number of simultaneous notes : %d\n") % info.wNotes;
out << indent << boost::format("Channels : %d\n") % info.wChannelMask;
if (info.dwSupport & MIDICAPS_VOLUME) {
out << indent << boost::format("Supports volume control\n");
}
if (info.dwSupport & MIDICAPS_LRVOLUME) {
out << indent << boost::format("Supports separate left and right volume control\n");
}
if (info.dwSupport & MIDICAPS_CACHE) {
out << indent << boost::format("Supports patch caching\n");
}
if (info.dwSupport & MIDICAPS_STREAM) {
out << indent << boost::format("Provides direct support for the midiStreamOut function\n");
}
if(info.wTechnology-1 == 0) {
out << indent << boost::format("このデバイスでは再生途中に終了できなくなる恐れがあります\n");
}
return out.str();
}
void showMidiDevice() {
const unsigned int deviceCount = ::midiOutGetNumDevs();
std::cout << boost::format("%d個のデバイスを検出しました\n") % deviceCount;
if (deviceCount == 0) {
return;
}
for (const unsigned int deviceId : boost::irange(0U, deviceCount)|boost::adaptors::reversed) {
std::cout << boost::format("デバイス%d:\n") % deviceId;
const boost::optional<std::string> info = ::getDeviceInfoString(deviceId, 3);
if (!info) {
std::cout << "存在しないデバイス番号です\n";
continue;
}
std::cout << *info;
}
}
bool isMidiDeviceNone() {
return ::midiOutGetNumDevs() == 0;
}
boost::optional<unsigned int> getDeviceId() {
const unsigned int deviceCount = ::midiOutGetNumDevs();
if (::isMidiDeviceNone()) {
return{};
}
if (deviceCount == 1) {
return 0;
}
while (true) {
char buffer[256];
std::cout << "使用するデバイス番号を入力してください。\nデバイス番号:";
std::cin.getline(buffer, _countof(buffer));
const unsigned int result = ::atoi2(buffer, -1);
if (result < 0 || result >= deviceCount) {
continue;
}
return result;
}
}
/*
・既知のバグ
midiイベント以外のイベント直後データが破損していてもランニングステータスと誤認識して読み込んでしまう可能性があるバグ
SysExイベントのデータ長が127バイト以上であったときに正常に読み書きできないバグ
midiヘッダの時間単位が実時間指定であった場合に正常に読み書き再生できないバグ
*/