Last active
May 20, 2018 14:46
-
-
Save ixsiid/4d54b20e8a0f3eb155204ed948a33b4a to your computer and use it in GitHub Desktop.
SMF (Standard Midi Format)を解析し、MIDIインターフェースに流し込む(プロトタイプ編) ref: https://qiita.com/ixsiid/items/e3633bc0a594dc5fb972
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
typedef struct _Smf { | |
std::string filepath; | |
Note * notes; | |
Note * endpoint; | |
Note * current; | |
} Smf; | |
typedef struct _Note { | |
int time; | |
unsigned char message[3]; | |
} Note; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <cstdlib> | |
#include <smf.h> | |
#include <algorithm> | |
#include <chrono> | |
#include <thread> | |
#include <unistd.h> | |
#include "RtMidi.h" | |
#include "SmfParser.hpp" | |
#include "midi.hpp" | |
typedef struct _Note { | |
int time; | |
unsigned char message[3]; | |
} Note; | |
// 解析したNoteを発生タイミング(timeメンバ)順に並び変える比較器 | |
bool compare_event(const ch_event & left, const ch_event & right) { | |
if (left.acumulate_time == right.acumulate_time) { | |
if (left.event_type == right.event_type) { | |
return left.par1 < right.par2; | |
} | |
return left.event_type < right.event_type; | |
} | |
return left.acumulate_time < right.acumulate_time; | |
} | |
// Note構造体のcout出力用(デバッグ用) | |
std::ostream & operator<<(std::ostream & Str, const Note & note) { | |
if (note.message[0] == 144) { | |
printf("timing %d[msec], note %d on, velocity = %d", note.time, note.message[1], note.message[2]); | |
} else if (note.message[0] == 128) { | |
printf("timing %d[msec], note %d off", note.time, note.message[1]); | |
} else { | |
printf("unknown note; %d %d %d %d", note.time, note.message[0], note.message[1], note.message[2]); | |
} | |
return Str; | |
} | |
typedef struct _Smf { | |
std::string filepath; | |
Note * notes; | |
Note * endpoint; | |
Note * current; | |
} Smf; | |
int main() | |
{ | |
MidiInterface * port = new MidiInterface("Multi"); | |
RtMidiOut *midiout = (RtMidiOut *)port->connect("FLUID", MidiDirection::OUT); | |
if (midiout == NULL) { | |
std::cout << "Not found target port name" << std::endl; | |
exit(1); | |
} | |
// 読み込むファイルからSMF構造体を初期化 | |
// 複数のファイルを読み込めるように配列として処理しています。 | |
std::vector<Smf> files = { | |
{"magic_music.mid", NULL, NULL, NULL} | |
}; | |
for (int i=0; i<1; i++) { | |
Smf * x = &(files[i]); | |
std::cout << "parse: " << x->filepath << std::endl; | |
SmfParser smf = SmfParser(x->filepath.c_str()); | |
smf.printcredits(); | |
smf.parse(0); | |
smf.abstract(); | |
std::cout << std::endl; | |
std::cout << "ticks per beat: " << smf.getTicks_per_beat() << std::endl; | |
int noteCount = 0; | |
for (auto y : smf.events) { | |
// Note配列の確保領域を調べるため、イベントのうちtype 8, 9(Midi offとon)をカウントする | |
if (y.event_type == 9 || y.event_type == 8) noteCount++; | |
} | |
// 複数のトラックが混在しているため、すべてのトラックのイベントを発生タイミング順に並び変える | |
std::sort(smf.events.begin(), smf.events.end(), compare_event); | |
x->notes = new Note[noteCount]; | |
int count = 0; | |
int tempo = smf.getBpm(); | |
int tickPerBeat = smf.getTicks_per_beat(); | |
// 発生タイミングを時間(ミリ秒)に変換するための係数(後述) | |
float k = 60000.0f / (tempo * tickPerBeat); | |
std::cout << "tempo " << tempo << ", tpb " << tickPerBeat << ", k=" << k << std::endl; | |
for (auto y : smf.events) { | |
if (y.event_type == 9) { | |
// Midi ONイベント | |
// Midiメッセージはunsigned char[3]になっておりそれぞれの意味は次の通り | |
// 0: イベントタイプを表す。144で固定 | |
// 1: 鍵盤の番号 参考 :: [MIDIノート番号と音名、周波数の対応表](http://www.asahi-net.or.jp/~HB9T-KTD/music/Japan/Research/DTM/freq_map.html) | |
// 2: Velocity, 音の強さ | |
x->notes[count].time = (int)(y.acumulate_time * k); | |
x->notes[count].message[0] = 144; | |
x->notes[count].message[1] = y.par1; | |
x->notes[count].message[2] = y.par2; | |
count++; | |
} else if (y.event_type == 8) { | |
// Midi OFFイベント | |
// Midiメッセージはunsigned char[2]になっておりそれぞれの意味は次の通り | |
// 0: イベントタイプを表す。128で固定 | |
// 1: 鍵盤の番号 参考 :: [MIDIノート番号と音名、周波数の対応表](http://www.asahi-net.or.jp/~HB9T-KTD/music/Japan/Research/DTM/freq_map.html) | |
x->notes[count].time = (int)(y.acumulate_time * k); | |
x->notes[count].message[0] = 128; | |
x->notes[count].message[1] = y.par1; | |
x->notes[count].message[2] = 0; | |
count++; | |
} | |
} | |
x->endpoint = x->notes + count; | |
x->current = x->notes; | |
std::cout << "count: " << count << std::endl; | |
} | |
// 再生は別スレッドで行う(今後のため) | |
auto start = std::chrono::system_clock::now(); | |
// 再生するファイルを切り替える変数 | |
int playIndex = 0; | |
std::thread thr([&files, &playIndex](RtMidiOut * midi) { | |
long j = 0; // 経過時間出力制御用 | |
while(true) { | |
int acumulate = std::chrono::duration_cast<std::chrono::milliseconds>( | |
std::chrono::system_clock::now() - start | |
).count(); | |
if (j++ > 100000) { | |
// たまに状況を出力する | |
std::cout << "\racumlate time: " << acumulate << "[msec]"; | |
fflush(stdout); | |
j = 0; | |
} | |
while(true) { | |
for (unsigned int x=0; x<files.size(); x++) { | |
Smf * play = &files[x]; | |
if (play->current < play->endpoint && play->current->time <= acumulate) { | |
if (playIndex == x) midi->sendMessage(play->current->message, 3); | |
play->current++; | |
// std::cout << "next: " << play -> current << std::endl; | |
} else { | |
break; | |
} | |
} | |
break; | |
} | |
// 処理速度と合わせて、負荷軽減のためのWait処理 | |
// Raspberry Pi3で約3msecくらい遅れる | |
// 違和感ないレベルに調整する | |
usleep(1500); | |
} | |
std::cout << "unknown finish to play" << std::endl; | |
return; | |
}, midiout); | |
thr.detach(); | |
while (true) { | |
std::cout << "\r> "; | |
fflush(stdout); | |
int c = std::cin.get(); | |
if (c >= '0' && c <= '9') { | |
playIndex = c - '0'; | |
} else if (c == 'q' || std::cin.eof()) { | |
break; | |
} | |
} | |
delete midiout; | |
delete port; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment