MIDIをopenFrameworksで使ってみる。

僕のTwitter タイムライン界隈では、Kinectハックの話題でもちきりですが、現在僕は、MIDIを再勉強中。
MIDIって、それ自体は過去の遺産の様な規格なのだけど。パラメーターで表現できる範囲が0~127って、…8bitサウンドにも届かないわ…いつの時代だよっ!! と思うけれど。


MIDIの一番の強みは、DTMソフトと連携できる」これに尽きる。


以前は、各種MIDIコントローラーの強みもあったけれど、今では、タッチパネル+OSCの方が柔軟だな。
しかし、DTMソフトの連携を考える際の最有力はやはりMIDI

MIDIの復習

MIDIとか古すぎて忘れちゃった。といった方は、
詳細MIDI規格 http://www.pluto.dti.ne.jp/~daiki/Midi/Midi.html
が最強。



さて、今回考えていることは、
DTMソフトのタイムライン上でエディットしたMIDIデータを、MIDIファイルに書きだして、音楽をつくるように、oFのパラメーターを制御する。
例えば、Arduinoと連携して、照明を音楽の用にDTMソフトで編集できたら面白い。
(詳しくは知らないけど、照明の現場でつかってるのはMIDI規格みたいなもんらしい。)


MIDIの入出力

兎にも角かくにも、ライブラリを選定。
MIDIの通信系には、openFrameworksで ofxMIDIがよさそう。


http://addons.openframeworks.cc/projects/show/ofxmidiin
で見れるはずなのだけれど、サイトが落ちてるのかな?


http://www.addons.openframeworks.cc/files/090812030923_ofxMidi_002.zip
ソースの直接ダウンロードはこちらから。


Mac + XCode環境の場合は、フレームワークにCoreMidiを追加するのを忘れずに。
その他の環境での諸々は、内部的に使っているRtMIDI*1のサイトに対策が載っている。


クラスは基本的には、ofxMidiIn と ofxMidiOut の2つで事足りる。oFらしいシンプル設計。今回、ProggramChangeを扱いたかったので、


ofxMidiOut.h に

void			sendProgramChange(int channel, int id);

ofxMidiOut.cpp に

// --------------------------------------------------------------------------------------
void ofxMidiOut::sendProgramChange(int channel, int id) {
	message.clear();
	message.push_back( MIDI_PROGRAM_CHANGE+(channel-1) );
	message.push_back( id );
	sendMessage();
}

を追加。

OS側での設定も忘れずに。

Macの場合、Audio MIDI設定というアプリを開いて、
f:id:asus4:20101121174341p:image
IAC DriverでのMIDIポートをオンにすることで、内部的なmidi通信が出来るようになる。

さらにネットワークの部分を設定すれば、Mac同士で、MIDIケーブルをひかなくても、LANネットワークでMIDI通信が出来てしまう。




ここまで来れば、岩井俊雄坂本龍一メディアアートの教科書に載せるべき作品、
機械仕掛けのピアノのための嬉遊曲

のような作品もoFで可能。




MIDIファイルの読み込み。モデル保持。

今回は、音楽をつくるように時間軸上で音楽以外のパラメーターを編集したかったので、midiファイルの読み込み、再生の機構が必要。
openFrameworks用のライブラリはなさそうだけれど、c++のライブラリがあった。
構造とか、あまりoF向けではないけれど、オープンソースのライブラリにはいつも感謝。


jdksmidi ( https://github.com/jdkoftinoff/jdksmidi


未完成かつ、c++も勉強中の身だけれど、oFラッパーを作成してみたので、折角なので公開。
ofxMidiSequencer.openMidiFileで、midiファイルを読み込む、
ofxMidiSequencer.start() でシーケンサー再生開始。
今のところ、1ms精度で作っているので、音楽再生には、若干難有り。
その他、いろいろするには、manageMIDIEventsや、sendMidiMessageをオーバーライドしてね。といった不親切設計。追々直していきます。


ofxMidiSequencer.h

#ifndef OFX_MIDI_SEQUENCER
#define OFX_MIDI_SEQUENCER

#include "ofMain.h"
#include "ofxThread.h"
#include "ofxMidi.h"

#include "jdksmidi/msg.h"
#include "jdksmidi/parser.h"
#include "jdksmidi/fileread.h"
#include "jdksmidi/fileshow.h"
#include "jdksmidi/sequencer.h"
#include "jdksmidi/track.h"
#include "jdksmidi/multitrack.h"
#include "jdksmidi/filereadmultitrack.h"

using namespace jdksmidi;

class ofxMidiSequencer : protected ofxThread {
public:
	
	ofxMidiSequencer();
	virtual ~ofxMidiSequencer();
	
	// load MIDI file
	bool openMidiFile(string _filepath);
	
	void start();
	void stop();
	
	void restart();
	
	void sendMidiMessage(MIDIMessage& msg);
	void setTempo(float tempo);
	float getTempo();
	
protected:
	ofxMidiOut _midiOut;
	
	MIDIClockTime _startedTime;
	MIDIClockTime _currentTime;
	MIDIClockTime _nextEventTime;
	
	MIDISequencer * _sequencer;	
	MIDIMultiTrack * _tracks;
	
	virtual void threadedFunction();
	inline void manageMIDIEvents();
	
private:
	
	
};

#endif


ofxMidiSequencer.cpp

#include "ofxMidiSequencer.h"



using namespace jdksmidi;

ofxMidiSequencer::ofxMidiSequencer() {
	_currentTime = 0;
	_midiOut.openPort();
	
	_tracks = 0;
	_sequencer = 0;
}

ofxMidiSequencer::~ofxMidiSequencer() {
	_midiOut.sendControlChange(1, 123, 0);
	_midiOut.closePort();
	if(_sequencer) delete _sequencer;
	if(_tracks) delete _tracks;
}

bool ofxMidiSequencer::openMidiFile(string _filepath) {
	// clear data
	if(_sequencer) delete _sequencer;
	if (_tracks) delete _tracks;
	
	// absolute path
	string filepath = ofToDataPath(_filepath, true);
	
	_tracks = new MIDIMultiTrack();
	
	MIDIFileReadStreamFile rs( filepath.c_str() );
	MIDIFileReadMultiTrack track_loader( _tracks );
	MIDIFileRead reader( &rs, &track_loader );
	
	// set amount of tracks equal to midifile
	_tracks->ClearAndResize( reader.ReadNumTracks() );
	
	// load the midifile into the multitrack object
	if ( !reader.Parse() )
	{
		cout << "\nError parse file " << filepath << endl;
		return false;
	}
	
	// init sequenser
	_sequencer = new MIDISequencer( _tracks );
	
	return true;
}

void ofxMidiSequencer::start() {
	_startedTime = ofGetSystemTime();
	_currentTime = 0;
	_nextEventTime = 0;
	startThread(true, false);
	_sequencer->GoToTimeMs(0.0f);
}

void ofxMidiSequencer::stop() {
	_currentTime = 0;
	_nextEventTime = 0;
	stopThread();
}

void ofxMidiSequencer::restart() {
	_startedTime = ofGetSystemTime();
	_currentTime = 0;
	_nextEventTime = 0;
	_sequencer->GoToTimeMs(0.0f);
}

void ofxMidiSequencer::sendMidiMessage(MIDIMessage& msg) {
	if(msg.IsNoteOn()) {
		_midiOut.sendNoteOn(1, msg.GetNote(), msg.GetVelocity());
	}
	else if(msg.IsNoteOff()) {
		_midiOut.sendNoteOff(1, msg.GetNote(), msg.GetVelocity());
	}
	else if(msg.IsControlChange()) {
		_midiOut.sendControlChange(1, msg.GetController(), msg.GetControllerValue());
	}
	else if( msg.IsProgramChange()) {
		_midiOut.sendProgramChange(1, msg.GetPGValue());
	}
}

void ofxMidiSequencer::setTempo(float tempo) {
	float currentTempo = _sequencer->GetCurrentTempo();
	float currentTempoScale = _sequencer->GetCurrentTempoScale();
	float tempoScase = tempo*currentTempoScale/currentTempo;
	cout << "tempoScase" << tempoScase << "\n";
	_sequencer->SetCurrentTempoScale(tempoScase);
}

float ofxMidiSequencer::getTempo() {
	return _sequencer->GetCurrentTempo();
}

#pragma mark -
#pragma mark protected methods

void ofxMidiSequencer::threadedFunction() {
	while (isThreadRunning() != 0) {
		if( lock() ) {
			
			manageMIDIEvents();
			
			unlock();			
			ofSleepMillis(1);
		}
	}
}


void ofxMidiSequencer::manageMIDIEvents() {
	_currentTime = ofGetSystemTime() - _startedTime;
	
	while (uint(_nextEventTime) <= uint(_currentTime)) {
		int track;
		MIDITimedBigMessage msg;
		
		if( _sequencer->GetNextEvent( &track, &msg ) ){
			sendMidiMessage(msg);
			if( !_sequencer->GetNextEventTime( &_nextEventTime) ){
				restart();
				break;
			}
		}
		else {
			break;
		}
	}
}

まだ、できたものが見せられないのですが、出来たら報告します。

*1:http://www.music.mcgill.ca/~gary/rtmidi/