僕の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設定というアプリを開いて、
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/