Skip to content

Instantly share code, notes, and snippets.

@ixsiid
Last active May 20, 2018 14:46
Show Gist options
  • Save ixsiid/4d54b20e8a0f3eb155204ed948a33b4a to your computer and use it in GitHub Desktop.
Save ixsiid/4d54b20e8a0f3eb155204ed948a33b4a to your computer and use it in GitHub Desktop.
SMF (Standard Midi Format)を解析し、MIDIインターフェースに流し込む(プロトタイプ編) ref: https://qiita.com/ixsiid/items/e3633bc0a594dc5fb972
typedef struct _Smf {
std::string filepath;
Note * notes;
Note * endpoint;
Note * current;
} Smf;
typedef struct _Note {
int time;
unsigned char message[3];
} Note;
#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