1
4

More than 5 years have passed since last update.

openFrameworksでMIDIファイル再生

Posted at

準備

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でも)動作するofxMidijdksmidiを組み合わせたアドオンである。指定したMIDIのポート番号でMIDIファイルの再生を行うことができる。しかし、本家ではBPMの設定を行うことができない(自分の場合は動画とともに再生していたため、音と動画のズレが顕著にわかった)。
再生速度の問題

ofxThreadedMidiPlayer(daitomanabe ver.)

https://github.com/daitomanabe/ofxThreadedMidiPlayer
Rhizomatiksの真鍋さんがofxThreadedMidiPlayerをフォークしたファイルである。このファイルでは、上記の呼び出しと再生だけでなく、BPMの設定を呼び出し、使用することができる。しかし、exampleを動かすだけでも、CPU稼働率が100%付近に達してしまい、長時間の再生と実践での起動は危険だと思われる。
スクリーンショット 2019-03-14 0.40.47.png

プログラム

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

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を軽減させる。

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もぐんと下げることができた。
スクリーンショット 2019-03-14 0.41.13.png

Example

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;
};

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();
    }
}

おわりに

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

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4