Home - Flash - Flash : MIDI機器とFlashを連携(その1)

Flash : MIDI機器とFlashを連携(その1)

  • posted on 2009年1月22日 16:19 / update 2011年8月26日 19:14 / by hoehoe3

081224_midi2as1.gif

KORGのnanoPADnanoKONTROLが安価でかわいかったので衝動買い。でも音モノは全然やらないんで、MIDI音源やMIDI機器をFlashを連携させて、インスタレーション用の入力機器にして遊んでみました。というか、前もやったネタなんだけど詳しく解説しときます。

構成は下の図な感じ。MIDIと繋ぐには、AIRなVJアプリ「Visp」のライブラリを流用します。VJアプリとしてはもひとつな感じだけど、MIDI機器を入力にしてムービーをコントロールできる機能があります。例のごとくFlash(AIR)自身では、他の機器にアクセスできません。なもんでVispにはMIDI機器にアクセスするためのゲートウェイ「MIDI Proxy」が用意されています。MIDI ProxyはJavaアプリです。JavaのMIDIライブラリから、MIDI機器から送られるMIDIメッセージ受け取り、XMLに変換します。FlashとはXMLSocketで通信させます。そしてASのライブラリでデータをパースして、各MIDIイベントに対応したイベントを通知します。あとはそのイベントを拾って対応させたデータを反映させるという感じ。

081224_midi2as2

準備と実装するよ

VispのサイトからソースとMIDI Proxyのバイナリをダウンロードしてきます。ライブラリはflex_src/com/以下のファイルをコピーしてきます。あとMIDI機器をPC上で使えるようにセッティング。今回はnanoPAD、nanoKONTROLでやってきます。OS Xの場合、そのままだとMIDI機器をJavaが認識できないんで、Mandolane MIDI SPImmjを、Javaの拡張ディレクトリ(/Library/Java/Extensions/)にインストールしときます。

インプットマネージャーを実装するよ

元がAIRなだけに設定保存とかFlashには余分なとこは省いていきます。インプットマネージャー(com/visp/input/InputManager.as)あたりをいじっていけば実装できるはず。MIDIマネージャーを生成→MIDIイベントに対応したイベントを登録→入力があったらイベントを発行/取得→さらにMIDIイベントによって動作別のイベント(RANGE、BANGとか)をチェーン→動作別のイベントを通知したコントロールナンバーやノートを取得→データを反映。となるように実装します。

package {
 /** 
  * MIDI2AS00.as
  *
  * nanoKONTROL から入力して箱を動かすよ。
  *
  **/
   
 import com.visp.events.RangeEvent;
 import com.visp.midi.ControllerEvent;
 import com.visp.midi.MidiManager;
 import flash.display.Sprite;

 public class MIDI2AS00 extends Sprite
 { 

  private var _midiMgr:MidiManager;
  private var _rect:Sprite;
  
  public function MIDI2AS00()
  {
   
   // 動かすものを用意
   this._rect = new Sprite();
   this._rect.graphics.beginFill(0xFF0000);
   this._rect.graphics.drawRect(0, 0, 100, 100);
   this._rect.graphics.endFill();
   this.addChild(this._rect);
   
   // MIDIマネージャーを作成
   this._midiMgr = MidiManager.getInstance();
   this._midiMgr.initialize();
   
   // コントールチェンジのイベントを登録
   this._midiMgr.addEventListener(ControllerEvent.CONTROLLER, _handleController);
   // レンジイベントを登録
   this.addEventListener(RangeEvent.RANGE, _handleRange);
   
  }
  
  // コントロールチェンジ(MIDIイベント)発生したら
  private function _handleController(e:ControllerEvent) : void
  {

    // id:コントローラーナンバー(スライダーとか)
    // value:その値

   if(e.id) {
    // レンジイベントを発行して数字の範囲を丸める
    dispatchEvent(new RangeEvent(RangeEvent.RANGE, e.id, e.value, true));
   }   

  }
  
  // レンジイベントが発生したら
  private function _handleRange(e:RangeEvent):void
  {

   // コントロールナンバーをキーにして動きをつける
   switch(e.id) {
    case 2:
     this._rect.x = 255 * e.value + 100;
    break;
    
    case 3:
     this._rect.y = 255 * e.value + 100;
    break;
    
    case 14:
     this._rect.rotation = 360 * e.value;
    break;
   }   
   //trace(e.id, e.value);

  }
 }
}

nanoPAD、nanoKONTROLのスライダーやパッドのコントロールナンバー、ノートナンバー、MIDIイベント等は、KORGから配布されているnanoシリーズ用の「KORG KONTROL Editor」で確認と変更ができます。

使ってみる

大阪てら子19でちょっとだけデモったやつを置いときますよっと。

nanoKONTROLを使ったサンプル

スライダーの1〜6ではちゅねがグルグル回る。だけ。カメラをぐるぐる回すはずが、よくわからんかったので放置。ミキサー卓ごっこができそう。

package {
 /** 
  * MIDI2AS02.as
  *
  * nanoKONTROLから入力してはちゅねを動かすよ。
  *
  **/
  
 import com.visp.events.RangeEvent;
 import com.visp.midi.ControllerEvent;
 import com.visp.midi.MidiManager;
 import org.papervision3d.view.stats.StatsView;
 import flash.display.Sprite;
 
 [SWF(width='800',height='600',backgroundColor='0x000000',frameRate='60')]

 public class MIDI2AS02 extends Sprite
 {
  private var _midiMgr:MidiManager;
  private var _3dObj:MikuPv3dObject;
  
  public function MIDI2AS02()
  {
   
   // 3Dオブジェクトを配置
   this._3dObj = new MikuPv3dObject();
   this.addChild(_3dObj);
   var _stats:StatsView = new StatsView(this._3dObj.renderer);
   this.addChild(_stats);
   
   // MIDIマネージャーを作成
   this._midiMgr = MidiManager.getInstance();
   this._midiMgr.initialize();
   // コントロールチェンジのイベントを登録
   this._midiMgr.addEventListener(ControllerEvent.CONTROLLER, _handleController);
   // RANGEイベントを登録
   this.addEventListener(RangeEvent.RANGE, _handleRange);
   
  }
  
  // コントロールチェンジ(MIDIイベント)発生したら
  private function _handleController(e:ControllerEvent) : void
  {
   
   /**
    *  id:コントローラーナンバー(スライダーとか)
    *  value:その値値
    **/
    
   if(e.id) {
    // レンジイベントを発行して数字の範囲を丸める
    dispatchEvent(new RangeEvent(RangeEvent.RANGE, e.id, e.value, true));
   }
   
  }
  
  private function _handleRange(e:RangeEvent):void
  {
   
   // コントロールナンバーをキーにして動きをつける
   switch(e.id) {
    case 2:
     this._3dObj.setTracking(e.value, 0, 0, 0, 0, 0);
    break;
    
    case 3:
     this._3dObj.setTracking(0, e.value, 0, 0, 0, 0);
    break;
    
    case 4:
     this._3dObj.setTracking(0, 0, e.value, 0, 0, 0);
    break;
    
    case 5:
     this._3dObj.setTracking(0, 0, 0, e.value, 0, 0);
    break;
    
    case 6:
     this._3dObj.setTracking(0, 0, 0, 0, e.value, 0);
    break;
    
    case 8:
     this._3dObj.setTracking(0, 0, 0, 0, 0, e.value);
    break;
   }
   
   //trace(e.id, e.value);
   
  }
 }
}


// 3Dオブジェクトのクラス

import flash.display.*;
import flash.events.*;

import org.papervision3d.objects.parsers.Collada;
import org.papervision3d.view.BasicView;
import org.papervision3d.view.stats.StatsView;

class MikuPv3dObject extends BasicView
{
 private var _rotationX:Number;
 private var _rotationY:Number;
 private var _rotationZ:Number;
 private var _x:Number;
 private var _y:Number;
 private var _z:Number;
 private var _cmodel:Collada;
 
 private var rot:Number;
 
 public function MikuPv3dObject()
 {
  //viewportの定義とカメラタイプ定義
  //super (0,0,true,false,"CAMERA3D");
  super (0,0,true,false,"Target");
  init();
  init3D();
 }
 
 public function init():void 
 {
  this._rotationX = 0;
  this._rotationY = 0;
  this._rotationZ = 0;
  this._x = 0;
  this._y = 0;
  this._z = 0;
 }
 
 public function init3D():void
    {
  //カメラ設定
  camera.z = -300;
  camera.focus = 570;
  camera.zoom = 2;
  
  // はちゅね召還
  this._cmodel = new Collada("negimiku.dae");
  this._cmodel.scale = 0.05;
  this.scene.addChild(_cmodel);

  //レンダリング開始
  startRendering();
    }
    
    // トラッキング情報を設定
    public function setTracking(vYaw:Number, vRoll:Number, vPitch:Number, vx:Number, vy:Number, vz:Number):void 
    {
     if(vYaw > 0) {
      this._rotationY = int(vYaw * 360);
     }
     if(vRoll > 0) {
      this._rotationZ = int(vRoll * 360);
     }
     if(vPitch > 0) {
      this._rotationX = int(vPitch * 360);
     }
     if(vx > 0) {
      this._x = int(vx * 360) - 180;
      trace(this._x);
     }
     if(vy > 0) {
      this._y = int(vy * 360) - 180;
     }
     if(vz > 0) {
      this._z = int(vz * 360) - 480;
     }
    }
    
    // オブジェクトの位置を更新
    override protected function onRenderTick(event:Event=null):void
    {
     this._cmodel.rotationX = this._rotationX;
        this._cmodel.rotationY = this._rotationY;
        this._cmodel.rotationZ = this._rotationZ;
        this.camera.x = this._x;
        this.camera.y = this._y;
        this.camera.z = this._z;
        super.onRenderTick(event);
    }
}

nanoPADを使ったサンプル

BOX2D触ってみたかったんで、パッドを叩いたらボールがはねるってのを作るはずが、ただ落ちるだけに。。1〜4のパッドを叩くと丸、三角、四角、棒、星の形のオブジェクトが落ちてきます。叩く強さによって大きさが変わります。太鼓の達人とかできそう。

package {
 /** 
  * MIDI2AS01.as
  *
  * nanoPADから入力していろいろを降らすよ。
  *
  **/
  
 import com.visp.events.BangEvent;
 import com.visp.midi.MidiManager;
 import com.visp.midi.NoteEvent;
 import flash.display.Sprite;
 
 [SWF(width='800',height='600',backgroundColor='0x000000',frameRate='60')]
 
 public class MIDI2AS01 extends Sprite
 {
  
  private var _midiMgr:MidiManager;
  private var _b2Set:b2Set;
  
  public function MIDI2AS01()
  {

   
   _b2Set = new b2Set();
   this.addChild(_b2Set);
   
   this._midiMgr = MidiManager.getInstance();
   this._midiMgr.initialize();
   
   this._midiMgr.addEventListener(NoteEvent.NOTE_ON, _handleNoteOn);
   this._midiMgr.addEventListener(NoteEvent.NOTE_OFF, _handleNoteOff);
   
   this.addEventListener(BangEvent.BANG_ON, _handleBangOn);
  }
  
  
  private function _handleNoteOn(e:NoteEvent) : void
  {
   if(e.pitch) {
    var bangOn : BangEvent = new BangEvent(BangEvent.BANG_ON, e.pitch, true);
    bangOn.velocity = e.velocity;
    dispatchEvent(bangOn);
   }
  }
  
  private function _handleNoteOff(e : NoteEvent) : void
  {
   if(e.pitch){
    var bangOff : BangEvent = new BangEvent(BangEvent.BANG_OFF, e.pitch, true);
    dispatchEvent(bangOff);
   }
  }
  
  private function _handleBangOn(e:BangEvent):void
  { 
   switch(e.id) {
    case 39:
     _b2Set.createBoxObject(0, e.velocity);
    break;
    
    case 48:
     _b2Set.createBoxObject(1, e.velocity);
    break;
    
    case 45:
     _b2Set.createBoxObject(2, e.velocity);
    break;
    
    case 43:
     _b2Set.createBoxObject(3, e.velocity);
    break;
    
    case 51:
     _b2Set.createBoxObject(4, e.velocity);
    break;
   }
  }
 }
}


// Box2Dのセット
import Box2D.Collision.Shapes.b2CircleDef;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Collision.b2AABB;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2DebugDraw;
import Box2D.Dynamics.b2World;

import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;

class b2Set extends Sprite
{
 
 // 物理エンジンの管理クラス
 private var world:b2World;
 // 円の中心
 private var circleCenter:Point = new Point();
 // 物理エンジン内の1mを表すためのピクセル数
 private static const DRAW_SCALE:Number = 100;
 
 public function b2Set()
 {
  
  var worldAABB:b2AABB = new b2AABB();
  worldAABB.lowerBound.Set(-100, -100);
  worldAABB.upperBound.Set(100, 100);
  
  // 重力を定義する
  var gravity:b2Vec2 = new b2Vec2(0, 10);
  
  // 物理エンジン全体のセットアップ
  world = new b2World(worldAABB, gravity, true);
  
  // 床の場所を定義する
  var floorBodyDef:b2BodyDef = new b2BodyDef();
  floorBodyDef.position.Set(4, 5);
  
  // 床の形を定義する
  var floorShapeDef:b2PolygonDef = new b2PolygonDef();
  floorShapeDef.SetAsBox(3.5, 0.1);
  
  // 床を設置する
  var floor:b2Body = world.CreateBody(floorBodyDef);
  floor.CreateShape(floorShapeDef);
  
  // 描画設定
  var debugDraw:b2DebugDraw = new b2DebugDraw();
  debugDraw.m_sprite = this;
  debugDraw.m_drawScale = 100; // 1mを100ピクセルにする
  debugDraw.m_fillAlpha = 0.3; // 不透明度
  debugDraw.m_lineThickness = 1; // 線の太さ
  debugDraw.m_drawFlags = b2DebugDraw.e_shapeBit;
  world.SetDebugDraw(debugDraw);
  
  addEventListener(Event.ENTER_FRAME, onEnterframe);
  
 }
 
 public function createBoxObject(shape:int, velocity:Number):void 
 {
  
  var x:Number = (Math.random() * stage.stageWidth) / DRAW_SCALE;
  var y:Number = 50 / DRAW_SCALE;
  
  
  switch(shape) {
   case 0:
    var radius:Number =  0.9 * velocity;
    var _ball:Ball =  new Ball(x, y, radius, 0.8, 0.5);
    _ball.setObject(world);
    break;
   case 1:
    var hy:Number = 1.5 * velocity;
    var _box1:Box = new Box(x, y, 0.1, hy, 0.8, 0.5);
    _box1.setObject(world);
    break;
   case 2:
    var len:Number = 1.2 * velocity;
    var _triangle1:Triangle = new Triangle(x, y, len, len, 0.5);
    _triangle1.setObject(world);
    break;
   case 3:
    var _star1:Star = new Star(x, y, 0.8, 0.8, 0.5, world);
    _star1.setObject(world);
    break;
   case 4:
    var bsize:Number = 0.8 * velocity;
    var _box2:Box = new Box(x, y, bsize, bsize, 0.8, 0.5);
    _box2.setObject(world);
    break;
  }
  
 }
 
 private function onEnterframe(e:Event):void 
 {
  if (world == null) {
   return;
  }
  // Flashはデフォルトで秒間24フレームなので、
  // 物理シミュレーションを1/24秒進める
  world.Step(1 / 30, 10);
 }
}


import Box2D.Dynamics.b2BodyDef;
import Box2D.Collision.Shapes.b2CircleDef;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2World;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Common.Math.b2Vec2;
import flash.geom.Point;

// いろんな形
class Ball 
{
 public var bodyDef:b2BodyDef;
 public var shapeDef:b2CircleDef
 public var body:b2Body;
 
 public function Ball(x:Number, y:Number, radius:Number, density:Number, restitution:Number)
 {
  // 円の場所を設定する
  bodyDef = new b2BodyDef;
  bodyDef.position.Set(x, y);
   
  // 円の大きさなどを設定する
  shapeDef = new b2CircleDef();
  shapeDef.radius = radius;
  shapeDef.density = 0.8;     // 密度 [kg/m^2]
  shapeDef.restitution = 0.8;  // 反発係数、通常は0〜1
 }
 
 public function setObject(target:b2World):void 
 {
  body = target.CreateBody(this.bodyDef);
  body.CreateShape(this.shapeDef);
  body.SetMassFromShapes();
 }
}

class Box 
{
 private var boxBodyDef:b2BodyDef;
 private var boxShapeDef:b2PolygonDef;
 private var boxBody:b2Body;
 
 public function Box(x:Number, y:Number, hx:Number, hy:Number, density:Number, restitution:Number)
 {
  // 箱の場所を設定する
  boxBodyDef = new b2BodyDef();
  boxBodyDef.position.Set(x, y);
  
  // 箱の大きさなどを設定する
  boxShapeDef = new b2PolygonDef();
  boxShapeDef.SetAsOrientedBox(hx, hy, new b2Vec2(0, 0), 0.8);
  boxShapeDef.density = density;        // 密度 [kg/m^2] 通常は1
  boxShapeDef.restitution = restitution;  // 反発係数、通常は0〜1
 }
 
 public function setObject(target:b2World):void {
  boxBody = target.CreateBody(boxBodyDef);
  boxBody.CreateShape(boxShapeDef);
  boxBody.SetMassFromShapes();
 }
}

class Triangle {
 public var triangleBodyDef:b2BodyDef;
 public var triangleShapeDef:b2PolygonDef;
 public var triangleBody:b2Body;
 
 public function Triangle(x:Number, y:Number, size:Number, density:Number, restitution:Number)
 {
  // 三角形の場所を設定する
  triangleBodyDef = new b2BodyDef();
  triangleBodyDef.position.Set(x, y);
  
  // 三角形の大きさなどを設定する
  triangleShapeDef = new b2PolygonDef();
  triangleShapeDef.vertexCount = 3;
  triangleShapeDef.vertices[0].Set(0, 0);
  triangleShapeDef.vertices[1].Set(size, 0);
  triangleShapeDef.vertices[2].Set(0, size);
  
  triangleShapeDef.density = density;        // 密度 [kg/m^2] 通常は1
  triangleShapeDef.restitution = restitution;  // 反発係数、通常は0〜1
  triangleShapeDef.friction = 0.1; // 摩擦係数
 }
 
 public function setObject(target:b2World):void 
 {
  triangleBody = target.CreateBody(triangleBodyDef);
  triangleBody.CreateShape(triangleShapeDef);
  triangleBody.SetMassFromShapes();
 }
}

class Star 
{
 public var starBodyDef:b2BodyDef;
 public var starShapeDef:b2PolygonDef;
 public var starBody:b2Body;
 
 public function Star(x:Number, y:Number, size:Number, density:Number, restitution:Number, target:b2World)
 {
  
  // 星の場所を設定する
  starBodyDef = new b2BodyDef();
  starBodyDef.position.Set(x, y);
  
  starBody = target.CreateBody(starBodyDef);
  
  // 星の大きさなどを設定する
  starShapeDef = new b2PolygonDef();
  
  starShapeDef.vertexCount = 3;
  starShapeDef.vertices[0].Set(0.45, 0);
  starShapeDef.vertices[1].Set(0.62, 0.50);
  starShapeDef.vertices[2].Set(0.29, 0.50);
  starShapeDef.density = density;
  starShapeDef.friction = 0.5;
  starShapeDef.restitution = restitution;
  starBody.CreateShape(starShapeDef);
   
  starShapeDef = new b2PolygonDef();
  starShapeDef.vertexCount = 3;
  starShapeDef.vertices[0].Set(0.90, 0.34);
  starShapeDef.vertices[1].Set(0.50, 0.66);
  starShapeDef.vertices[2].Set(0.40, 0.34);
  starShapeDef.density = density;
  starShapeDef.friction = 0.5;
  starShapeDef.restitution = restitution;
  starBody.CreateShape(starShapeDef);
  
  starShapeDef = new b2PolygonDef();
  starShapeDef.vertexCount = 3;
  starShapeDef.vertices[0].Set(0.58, 0.40);
  starShapeDef.vertices[1].Set(0.73, 0.90);
  starShapeDef.vertices[2].Set(0.32, 0.60);
  starShapeDef.density = density;
  starShapeDef.friction = 0.5;
  starShapeDef.restitution = restitution;
  starBody.CreateShape(starShapeDef);

  starShapeDef = new b2PolygonDef();
  starShapeDef.vertexCount = 3;
  starShapeDef.vertices[0].Set(0.32, 0.40);
  starShapeDef.vertices[1].Set(0.58, 0.60);
  starShapeDef.vertices[2].Set(0.17, 0.90);
  starShapeDef.density = density;
  starShapeDef.friction = 0.5;
  starShapeDef.restitution = restitution;
  starBody.CreateShape(starShapeDef);

  starShapeDef = new b2PolygonDef();
  starShapeDef.vertexCount = 3;
  starShapeDef.vertices[0].Set(0.50, 0.34);
  starShapeDef.vertices[1].Set(0.40, 0.66);
  starShapeDef.vertices[2].Set(0, 0.34);
  starShapeDef.density = density;
  starShapeDef.friction = 0.5;
  starShapeDef.restitution = restitution;
  starBody.CreateShape(starShapeDef);
 }
 
 public function setObject(target:b2World):void 
 {
  starBody.SetMassFromShapes();
 }
}

とりあえずつながったけど。。

いつもの連携ネタですね。今度はMIDI。入力データにMIDIメッセージが使えるのがミソ。ゲートウェイにJavaを使うんでMac/Winで使えるのねん。でもいろいろと問題が山積み。

・同時に発生したイベントが送れない?→MIDI Proxy側をいじらないとだめぽい。
・1チャンネルしか使えない?→同上
・出力は?→機材がないから試してない。→たぶんむり。
・遅い→TCPが遅い?。UDP使えたら速くなるかなぁ。→Stratus

んー気が向けば手を入れてきます。。

関連エントリ:

参考資料:

COMMENT

Auther
hoehoe: おおさか方面でWebとかやってますよ。
Search
Feeds