こんにちは。自動運転技術が普及してもマニュアルで運転したい派のイシイです。前回のエントリーで、Node.jsとOSCを使ってミニドローンを任意のタイミングで操作できるようになりましたので、今回は音楽に合わせて任意のイベントを発生させる仕組みを作ってみたいと思います。

ofxTimelineを使ったプロジェクトの準備

音楽に合わせて任意のタイミングでイベントを発生させるアプリケーションの作成にあたって、今回はofxTimelineを使ってみることにしました。ofxTimelineは、openFrameworksでタイムラインエディタインタフェースを使用することができるアドオンです。アドオンは、GitHubのこちらからクローンしてきます。ofxTimelineを使用するにはいくつかのアドオンが必要ですが、クローンしてきたソース内のclone_addons.shを実行することで、必要なアドオンをクローンしてきてくれます。

cd ofxTimeline/
./clone_addons.sh

アドオンのクローンまで完了したら、ProjectGeneratorで必要なアドオンにチェックを入れ、プロジェクトを作成します。自動操縦用の任意イベント取得のため、ofxTimelineに必要なアドオンの他に、ofxOscにもチェックを入れておきます。 Project Generatorで生成

プロジェクトの作成が終わったら、ofxTimeline内に含まれているcopytodata_GUIフォルダを、bin/dataにコピーし、フォルダ名を「GUI」に変更します。 copy_to_data_GUIの設置

続けて、使用したい音楽とフォントをdata内に設置します。フォントはこちらを使用させていただきました。

ofxTimelineを使ったプロジェクトの設定

今回は音の波形を参考にイベント発生場所を指定したかったので、タイムラインにオーディオトラックを使用するためにマクロ定義を追加しておきます。

TIMELINE_AUDIO_INCLUDED=1

また、オーディオを使用するため、OpenAL.frameworkをプロジェクトに追加しておきます。 OpenAL.frameworkの追加

これで下準備ができましたので、あとは必要なソースを記述していきます。ヘッダーファイル内のHOSTやPORTには、実行環境に応じて適宜値を入れてください。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxTimeline.h"
#include "ofxOsc.h"
#include "ofxMSATimer.h"

#define HOST "xxx.xxx.xxx.xxx"
#define PORT xxxx

class ofApp : public ofBaseApp{

    public:
        void setup();
        void update();
        void draw();

        void keyPressed(int key);
        void keyReleased(int key);
        void mouseMoved(int x, int y );
        void mouseDragged(int x, int y, int button);
        void mousePressed(int x, int y, int button);
        void mouseReleased(int x, int y, int button);
        void windowResized(int w, int h);
        void dragEvent(ofDragInfo dragInfo);
        void gotMessage(ofMessage msg);

        //Timeline用の定義
        ofxTimeline timeline; //inside of the class add a timeline
        void bangEvent(ofxTLBangEventArgs& args);

        //OSC用の定義
        ofxOscSender sender;
        int charTimer;

        //タイマー表示用の定義
        string message;
        ofTrueTypeFont TTF,TTF2,TTF3;
        ofxMSATimer timer;
        float message_width,message_height,x,y;
        int commandFlg;
        string commandAction;

};

ofApp.cpp

#include "ofApp.h"

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

    // ofxTimelineをセットアップ
    ofBackground(0);
    ofxTimeline::removeCocoaMenusFromGlut("ControlDrone");
    timeline.setup();

    timeline.addAudioTrack("sound","sound/Unison.aiff");
    timeline.setDurationInSeconds(timeline.getAudioTrack("sound")->getDuration());

    timeline.getAudioTrack("sound")->setVolume(100);
    timeline.addFlags("command");
    timeline.addSwitches("droneBlue");
    timeline.addSwitches("droneRed");

    ofAddListener(timeline.events().bangFired, this, &ofApp::bangEvent);

    // open an outgoing connection to HOST:PORT
    sender.setup(HOST, PORT);

    TTF.loadFont("Let's go Digital Regular.ttf", 200);
    TTF2.loadFont("Let's go Digital Regular.ttf", 50);
    TTF3.loadFont("Let's go Digital Regular.ttf", 80);

    //文字列を画面に表示した時の大きさを調べて、書き出すメッセージの長さに応じた表示位置の調整
    message_width = TTF.stringWidth(ofToString(timeline.getAudioTrack("sound")->getDuration() - timeline.getCurrentTime())),
    message_height = TTF.stringHeight(ofToString(timeline.getAudioTrack("sound")->getDuration() - timeline.getCurrentTime()));
    x = ofGetWidth() / 2 - message_width / 2,
    y = ofGetHeight() / 4 * 3 +  message_height / 4 * 3;

    commandFlg = 0;
}

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

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

    //TimeLineの描画
    timeline.draw();

    //残り時間の描画
    TTF.drawString(ofToString(timeline.getAudioTrack("sound")->getDuration() - timeline.getCurrentTime()), x, y - 80);
    float sec_ms = TTF.stringWidth(ofToString(timeline.getAudioTrack("sound")->getDuration() - timeline.getCurrentTime()));
    TTF2.drawString(" sec", x + sec_ms + 20, y - 80);

    //命令文の描画
    if (commandFlg > 0) {

        float commandString = TTF3.stringWidth(commandAction);

        if(timeline.isSwitchOn("droneBlue")){
            ofSetColor(0, 0, 255);
            TTF3.drawString(commandAction, ofGetWidth() / 4 * 1 -  commandString / 2, y + message_height / 2 - 50);
            ofSetColor(255, 255, 255);
        }

        if(timeline.isSwitchOn("droneRed")){
            ofSetColor(255, 0, 0);
            TTF3.drawString(commandAction, ofGetWidth() / 4 * 3 -  commandString / 2 , y + message_height / 2 - 50);
            ofSetColor(255, 255, 255);
        }

        commandFlg--;
    }

}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
}
//--------------------------------------------------------------
void ofApp::keyReleased(int key){
}
//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){
}
//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){
}
//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){
}

//--------------------------------------------------------------
void ofApp::bangEvent(ofxTLBangEventArgs& args){

    if (args.flag != "") {

        commandAction = args.flag;
        commandFlg = 30;

        ofxOscMessage m;
        string command;
        command = ofToString(args.flag);
        m.setAddress(command);

        if(timeline.isSwitchOn("droneBlue")){
            m.addIntArg(1);
        } else {
            m.addIntArg(0);
        }

        if(timeline.isSwitchOn("droneRed")){
            m.addIntArg(1);
        } else {
            m.addIntArg(0);
        }

        sender.sendMessage(m);
        m.clear();

    }

}

作成した任意イベント発生用のアプリケーション実行画面

これでビルドできるようになりましたので、必要なイベントをマウスやキーを使ってタイムライン上に設置していきます。できあがりイメージはこんな感じです。

次のステップ

今回のエントリーで、音楽にあわせて任意イベントを発生させるための仕組みができあがりましたので、受け手側ではこのイベント発生時に送られてくるOSCの処理を加える事で、音楽にあわせた自動操縦を行うことができるようになりました。次回は実際に手動・自動を組み合わせたコンテンツを作成してみたいと思います。