openFrameworksでMIDIファイル再生

#準備
Xcode 10.1
openFrameworks 0.9.8

#概要
openFrameworksのアドオン(ofxThreadedMidiPlayer)を用いて、openFrameworksからMIDIファイルの再生を行う。
-MIDIファイルの再生
-MIDIファイルのBPM管理(時間管理)
-パソコンのCPU負荷の軽減

#アドオン説明
###ofxMidi
https://github.com/danomatika/ofxMidi 
openFrameworksのMIDI接続の中で最も基礎的なアドオンである。MIDIのポート番号を指定し、MIDIを送信する。ofxMidiはMIDIの生成を行うときに用いる。ofxMidiでは、MIDIファイルの再生を行うことができない。

###jdksmidi
https://github.com/jdkoftinoff/jdksmidi
C++のMIDI Libraryである。これを用いることでMIDIファイルから情報を読み込むことができる。openFrameworksのアドオンでは、ofxMidiFileLoaderやofxMidiFileなどに応用されている。

###ofxThreadedMidiPlayer(本家)
https://github.com/jvcleave/ofxThreadedMidiPlayer
openFrameworks 0.9.3で(0.9.8でも)動作する**ofxMidi**と**jdksmidi**を組み合わせたアドオンである。指定したMIDIのポート番号でMIDIファイルの再生を行うことができる。しかし、本家ではBPMの設定を行うことができない(自分の場合は動画とともに再生していたため、音と動画のズレが顕著にわかった)。
[再生速度の問題](https://github.com/jvcleave/ofxThreadedMidiPlayer/issues/7)

###ofxThreadedMidiPlayer(daitomanabe ver.)
https://github.com/daitomanabe/ofxThreadedMidiPlayer
Rhizomatiksの真鍋さんがofxThreadedMidiPlayerをフォークしたファイルである。このファイルでは、上記の呼び出しと再生だけでなく、BPMの設定を呼び出し、使用することができる。しかし、exampleを動かすだけでも、CPU稼働率が100%付近に達してしまい、長時間の再生と実践での起動は危険だと思われる。
<img width="400" alt="スクリーンショット 2019-03-14 0.40.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/361940/007dc706-5ccb-a710-9eed-bec5eb82c15f.png">


#プログラム
https://github.com/amako0609/ofxThreadedMidiPlayer
今回、ofxThreadedMidiPlayer(daitomanabe ver.)のコードの一部を改変することでCPUへの負担を軽減し、BPMの設定も行うことができるofxThreadedMidiPlayerを作成した。
以下はofxThreadedMidiPlayer.cpp内のコードの一部である。ここを編集し、軽量化を狙う。

```c++:ofxMidiPlayer.cpp
void ofxThreadedMidiPlayer::threadedFunction()
{
    do
    {
        init();
        unsigned startTimeMillis = ofGetElapsedTimeMillis();
        if (sequencer)
        {
            float nextEventMs;
            while (isThreadRunning() && sequencer->GetNextEventTimeMs(&nextEventMs))
            {
                if (ofGetElapsedTimeMillis() - startTimeMillis > nextEventMs)
                {
                    MIDITimedBigMessage bigMessage;
                    int track;
                    if (sequencer->GetNextEvent(&track, &bigMessage))
                    {
                        if (bigMessage.GetLength() > 0)
                        {
                            vector<unsigned char> message;
                            message.push_back(bigMessage.GetStatus());
                            if (bigMessage.GetLength()>0) message.push_back(bigMessage.GetByte1());
                            if (bigMessage.GetLength()>1) message.push_back(bigMessage.GetByte2());
                            if (bigMessage.GetLength()>2) message.push_back(bigMessage.GetByte3());
                            if (bigMessage.GetLength()>3) message.push_back(bigMessage.GetByte4());
                            if (bigMessage.GetLength()>4) message.push_back(bigMessage.GetByte5());
                            message.resize(bigMessage.GetLength());
                            midiout->sendMessage(&message);
                            dispatchMidiEvent(message);
                        }
                    }
                }
            }
        }
    }
    while (doLoop && isThreadRunning());
}
```
あまり詳しいコードは読みきれていないが、時間計算のあたりが重たくなっている原因であることは間違いないだろう。そこで、このコードの中に**sleep(1)**を一つ入れて上げることでCPUを軽減させる。

```c++:ofxMidiPlayer.cpp
void ofxThreadedMidiPlayer::threadedFunction()
{
    do
    {
        init();
        
        unsigned startTimeMillis = ofGetElapsedTimeMillis();
        
        if (sequencer)
        {
            float nextEventMs;
            while (isThreadRunning() && sequencer->GetNextEventTimeMs(&nextEventMs))
            {
                if (ofGetElapsedTimeMillis() - startTimeMillis > nextEventMs)
                {
                    MIDITimedBigMessage bigMessage;
                    int track;
                    if (sequencer->GetNextEvent(&track, &bigMessage))
                    {
                        if (bigMessage.GetLength() > 0)
                        {
                            vector<unsigned char> message;
                            message.push_back(bigMessage.GetStatus());
                            if (bigMessage.GetLength()>0) message.push_back(bigMessage.GetByte1());
                            if (bigMessage.GetLength()>1) message.push_back(bigMessage.GetByte2());
                            if (bigMessage.GetLength()>2) message.push_back(bigMessage.GetByte3());
                            if (bigMessage.GetLength()>3) message.push_back(bigMessage.GetByte4());
                            if (bigMessage.GetLength()>4) message.push_back(bigMessage.GetByte5());
                            message.resize(bigMessage.GetLength());
                            midiout->sendMessage(&message);
                            
                            dispatchMidiEvent(message);
                        }
                    }
                }
                sleep(1);
            }
        }
    }
    while (doLoop && isThreadRunning());
}
```
これを行うことで挙動はほぼ同じであることが確認でき、CPUもぐんと下げることができた。
<img width="400" alt="スクリーンショット 2019-03-14 0.41.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/361940/3192f038-0e1b-7012-6284-746eb5af20c9.png">

#Example

```c++:ofApp.h
#pragma once

#include "ofMain.h"
#include "ofxMidi.h"
#include "ofxThreadedMidiPlayer.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
        void exit();
		void keyPressed(int key);
    
    private:
        ofxThreadedMidiPlayer midiPlayer;
};

```


```c++:ofApp.cpp
#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    ofSetVerticalSync(true);
    
    //setup midi
    ofSetLogLevel(OF_LOG_VERBOSE);
    int midiPortNumber = 0;
    const std::string filename = " .mid";
    midiPlayer.setup(filename, midiPortNumber);
    midiPlayer.setBpm(120);
    
    midiPlayer.start();
}

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){

}

void ofApp::exit(){
    midiPlayer.stop();
    midiPlayer.clean();
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if (key == 'a'){
        midiPlayer.start();
    }else if (key == 's'){
        midiPlayer.stop();
        midiPlayer.clean();
    }
}

```

#おわりに
まだペーペーなので、質問やアドバイス等のコメントお待ちしております。