note.x

SiON使って、UIにSEを付けたりしてみるテスト。

(要:FlashPlayer10)

マウスイベントなんかに連動して音を鳴らす場合、今まではmp3ファイル読み込んで再生してたけど、SiONを使うことで、PSG / FM / WaveTable / PCMなどの多彩な音源で、しかもラクしてサウンド制御ができる。個人的にはOPMが鳴るってだけで抱かれてもいいと思ったけど、トータルに音の制御ができるという側面はコンテンツ制作する上でとっても有用。

例えば、下記のようなコードで音の鳴るボタンが作れる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package
{
    import flash.display.*;
    import flash.events.*;
 
    import org.si.sion.*;
 
    [SWF(backgroundColor=0x000000)]
    public class main extends Sprite
    {
        private var driver:SiONDriver;
        private var sinWave:SiONVoice;
 
        public function main()
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        public function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
 
            stage.frameRate = 60;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.quality = StageQuality.BEST;
 
            initSiON();
        }
 
        private function initSiON():void
        {
            //SiONDriverをスタンバイ
            driver = new SiONDriver();
            driver.play();
 
            //音色定義(%5, @0 = sin波)
            sinWave = new SiONVoice(5,0);
 
            //UIを定義
            var soundButton:Sprite = new Sprite();
            soundButton.graphics.beginFill(0x0099cc);
            soundButton.graphics.drawRoundRect(0, 0, 120, 120, 4, 4);
            soundButton.graphics.endFill();
            addChild(soundButton);
            soundButton.x = 20, soundButton.y = 20;
 
            soundButton.addEventListener(MouseEvent.ROLL_OVER, onrollover);
            soundButton.useHandCursor = true;
        }
 
        public function onrollover(e:MouseEvent):void
        {
            //ノート番号90番(オクターブ7の「ファ」)の音を
            //事前に定義した音色(sinWave)で鳴らす。
            //第3引数:音長(1 = 16分音符), 第4引数:発音までのディレイ
            driver.noteOn(90, sinWave, 1, 0);
        }
    }
}

音色定義については、SiON MMLリファレンス「音色関連 – ‘%’,'@’の第1引数」の項を参照。

音色定義を

var sawWave:SiONVoice = new SiONVoice(5,1);    // ノコギリ波
var triWave:SiONVoice = new SiONVoice(5,4);    // 三角波
var squareWave:SiONVoice = new SiONVoice(5,5); // 矩形波

などと差し替えることで違う音色にできる。PSG / FM / WaveTable 音源については、使いこなせれば、別途音源データを用意せずに、コーディングだけで多彩な音を鳴らすことができる反面、音色定義自体がそれなりの知識と慣れを要求するので、UIサウンドとしてお手軽にSiONを活用することを考えた場合、swfに埋め込んだ、もしくは外部から読み込んだmp3サウンドをSiONで制御するのが良いんじゃないかと思う。例えば以下のような感じで。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package
{
    import flash.display.*;
    import flash.events.*;
    import flash.media.*;
 
    import org.si.sion.*;
 
    [SWF(backgroundColor=0x000000)]
    public class main extends Sprite
    {
        private var driver:SiONDriver;
        private var pcmsound:SiONVoice;
 
        [Embed(source='hoge.mp3')]
        private var hogeSound:Class;
 
        public function main()
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        public function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
 
            stage.frameRate = 60;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.quality = StageQuality.BEST;
 
            initSiON();
        }
 
        private function initSiON():void
        {
            //SiONDriverをスタンバイ
            driver = new SiONDriver();
            driver.play();
 
            //音色定義(PCMサウンド)
            var hoge:Sound = new hogeSound();
            driver.setSamplerSound(90, hoge);  //ノート番号90番に、Embedしたmp3サウンドを割り当て
            pcmsound = new SiONVoice(10);
 
            //UIを定義
            var soundButton:Sprite = new Sprite();
            soundButton.graphics.beginFill(0x0099cc);
            soundButton.graphics.drawRoundRect(0, 0, 120, 120, 4, 4);
            soundButton.graphics.endFill();
            addChild(soundButton);
            soundButton.x = 20, soundButton.y = 20;
 
            soundButton.addEventListener(MouseEvent.ROLL_OVER, onrollover);
            soundButton.useHandCursor = true;
        }
 
        public function onrollover(e:MouseEvent):void
        {
            //ノート番号90番(mp3サウンドを割り当てた場所)の音を鳴らす。
            driver.noteOn(90, pcmsound, 1, 0);
        }
    }
}

setSamplerSoundは、通常0-127までのノート番号に任意のSoundを割り当て可能。さらにバンク切り替えで128-255まで定義しておくことができる。

また、driver.noteOn()の代わりに driver.sequenceOn() を使うことで、事前に定義したMMLを再生することも可能。

driver = new SiONDriver();
var mml:SiONData = driver.compile("l16 o6 ffffre-rgrf2;l16 o6 c>bb-argrb-ra2;");
driver.play();

sinWave = new SiONVoice(5,0);
driver.sequenceOn(mml, sinWave, 0, 0, 1, 3);

driver.sequenceOn()は、別途演奏中のMMLシーケンスとタイミングを合わせて再生を乗せることができたりするあたりの仕様にニヤリとさせられた。

加えて、delay, reverb, chorus 等のエフェクトや、low/high pass filter等のフィルタも備えているため、かなり自由度の高い音作りをすることもできる(このエントリ冒頭のデモでは、reverbをon/offできるようにした)
SiONで使えるエフェクトについては、SiON MMLリファレンス「エフェクタ接続」の項を参照。

ということで、SiONはオッサンの懐古主義を満足させるためだけのものではなく(オレにとってはここも超重要なのだけど)、Flashによるコンテンツ制作におけるサウンド面の実装を容易に、表現力豊かにしてくれる、とても有り難いライブラリなのですな。


週末はSiONの日。

pcmとFMを同時に鳴らすテスト。
最終的には、自作の.mdxを全部SiONに移植したいので、.pdxからpcmを取り出してmp3化する方法も併せて模索。

pdxをバラすのに、dos用mxdrvのpdexを使用。取り出したpcmをpcm2wavで、wavファイルに変換後mp3化した。この辺はwindows環境でないとツールが全然無い。マカーにとっては寂しい限り。

ということで出来た。FM8ch + PCM3ch構成の「Palace」米光アレンジモドキ。

(要:FlashPlayer10)

次はFM8ch+PCM8chなネタで、mxdrv+pcm8をFlashで復元だー。


tkinjoさんが、wonderflに投稿されたコードを見て。

sceneの原点(0, 0, 0)にターゲット指定したカメラを x=0, y=1000, z=0 のように、ワールド座標のY軸線上に配置すると何も表示されなくなる。
回避策として、tkinjoさんも採用している x, y座標を0にしないってのが一番ラクチン。
x=0, y=1000, z=-0.1 とか x=0.01, y=1000, z=-0.05 とかを指定する方法。
ただし、x軸、z軸のどっちを使ってY軸からずらすかによって結果が異なる。また数値の±によっても結果が異なる点に注意。大抵はz軸にマイナスの値を入れるといいんじゃないかと。

別の方法として、カメラのターゲット指定をせずに

camera.target = null;
camera.x = 0;
camera.y = 1000;
camera.z = 0;
camera.rotationX = 90;

という設定で同じ状況にした場合も、問題を回避できる。

にしても、なんでなんだぜ?
今まで何が原因なのかをちゃんと調べてなかったので、この機会に調べてみた。
多分、随分前に遭遇したこのトラブルとも関連がありそう。

カメラにターゲット指定しなけりゃ問題ねーってことなんで、lookAt メソッドが怪しいと睨んで DisplayObject3D.lookAt の該当部分を見てみると、

[1]まず、目標にカメラを向けた時のz軸ベクトルを、ターゲットの位置ベクトルからカメラの位置ベクトルから減算して正規化。

_zAxis.copyFrom(_lookatTarget);
_zAxis.minusEq(_position);
_zAxis.normalize();

[2]1で求めたカメラの正規化されたz軸ベクトルと、カメラのupperベクトル(通常は Number3D(0, 1, 0) )との外積をとって、x軸ベクトルを求める。

if( _zAxis.modulo > 0.1 )
    {
        _xAxis = Number3D.cross( _zAxis, upAxis || UP, _xAxis );
        _xAxis.normalize();

[3]1で求めたz軸ベクトルと、2で求めたx軸ベクトルの外積をとって、y軸のベクトルを求める。

        _yAxis = Number3D.cross( _zAxis, _xAxis, _yAxis );
        _yAxis.normalize();

こんな手順でカメラとターゲットとの位置関係を元に、カメラの姿勢を決めてる模様。

この方法だと、x=0, y=1000, z=0 にカメラがある場合、_zAxisが Number3D (0, 1, 0) になる。
こうなると _xAxisを求める際に外積を取るための、カメラのupperベクトル:Number3D (0, 1, 0) と平行になる。平行なベクトルの外積の大きさはゼロなので、_xAxis は Number3D (0, 0, 0)。
最後の_yAxis は _xAxis との外積だから、_yAxisも Number3D (0, 0, 0)。
って感じでz軸以外がゼロベクトルになっちゃって、カメラの姿勢が定まらない。結果、何も表示されないということみたいだ。これっていわゆるジンバルロックなのか?
むー。PV3Dサイドとしては、これは仕様だということにしてるんだろうか?

なんかアホっぽい方法で回避できそうな気がしたのでやってみた。
DisplayObject3Dの1213行目辺りから

            _zAxis.copyFrom(_lookatTarget);
            _zAxis.minusEq(_position);
            _zAxis.normalize();

            if( _zAxis.modulo > 0.1 )
            {
                _xAxis = Number3D.cross( _zAxis, upAxis || UP, _xAxis );
                _xAxis.normalize();

                _yAxis = Number3D.cross( _zAxis, _xAxis, _yAxis );
                _yAxis.normalize();

という部分を以下に書き換え。

            _zAxis.copyFrom(_lookatTarget);
            _zAxis.minusEq(_position);
           _zAxis.normalize();

           if( _zAxis.modulo > 0.1 )
            {
                _xAxis = Number3D.cross( _zAxis, upAxis || UP, _xAxis );
                _xAxis.normalize();

                if(_xAxis.x == 0 && _xAxis.y == 0 && _xAxis.z == 0)
                {
                    _yAxis = Number3D.cross( _zAxis, RIGHT, _yAxis );
                    _yAxis.normalize();

                    var tempYAxis:Number3D = new Number3D(-_yAxis.x, -_yAxis.y, -_yAxis.z);
                    _xAxis = Number3D.cross( _zAxis, tempYAxis, _xAxis );
                    _xAxis.normalize();
                }
                else
                {
                    _yAxis = Number3D.cross( _zAxis, _xAxis, _yAxis );
                    _yAxis.normalize();
                }

特異点でlookAtする場合のみ、_yAxisを先に求めるようにしたので、カメラが x=0, y=1000, z=0に配置された場合もそれっぽく表示されるようになる。強制的にLEFTベクトルとの外積を取るようにしちゃったので、カメラがアニメーションしてる場合、おかしな挙動になるケースもあるかもしれない力業。
それでも何にも表示されないよりはマシかなぁ。もうひとひねりしてクォータニオン使う方法も考えてみたい。

09/06/15追記:tkinjoさんのご指摘を受けてコード修正。
やっつけ気味なのは変わらず;


daeAnimation(要:FlashPlayer9)

[↑] … 前進(move forward)
[←] [→] … 方向変える(turn left/right)

Papervision2.1 Alpha 公開で、鬼門だったDAE周りが改善されたということで使ってみた。
http://techblog.floorplanner.com/2009/05/26/papervision3d-21-alpha/

BlenderでArmature使ったスキンメッシュをColladaでエクスポートしたものがようやく動くようになった。3dsMAXとかMaya使わない限り不可能かと思ってたけど、どうにか使えそう。Away3Dでもなんとか動いてたけど、ボーンのモーションにtranslateが含まれてると破綻したりして不完全な感じだったのが、PV3D2.1のほうが格段にマトモだ。

上のデモは、KYUCON*BLOGさんで紹介されているAnimationClip3Dを使う方法で静止状態と、歩行中のアニメーションを切り替えて再生してる。スタート・エンドポイントの指定がフレーム単位でもできればいいのになぁ…なんか歩行モーションのループ部分がぎこちない。Blender上だと綺麗につながるんだけどな。

Colladaの仕様的には、animationChannelを定義できるはずなので、Blenderでアニメーションセットを複数定義した上で正常に書き出しができればもっと楽ができそうな、ほのかで甘酸っぱい期待を持ったんだけど、Blenderで複数のアニメーションセットを定義する方法がわかんなくて一旦保留。

こうなってくると、アニメーション切り替え時にモーションブレンドしたい。それは自前で実装するしかねーだろうなー。


ファルコム音楽フリー宣言とは、日本ファルコム株式会社(以下、当社)のすべての楽曲を自由にご利用していただくことを目的とした宣言です。
ファルコム音楽フリー宣言

ゲームのサントラって、ゲーム自体をやってない人は絶対買わないだろうしなぁ。権利で囲っておくよりも放出しちゃって、そこいら中で聴ける状態にしたほうが、結果的に認知が上がってうわーいっていう意図なのかしら?今さら?とかそんな大人の事情は置いといて素直に喜んでおく。
ファルコム最高ーっ!!

というわけで、イース2のOP「To Make the End of Battle」をSiONで鳴らしてみる。

(要:FlashPlayer10)

(c) Nihon Falcom Corporation

この頃のfalcomは本当にかっこよかったなぁ。


imitatorIntro(要:FlashPlayer10)※音入り注意

SiON と PV3D を使って Executable Music な Intro モドキで、デモシーンごっこ(いわゆる現実逃避)。
かなりCPUパワー使うので、比較的新しめのマシンでないとツラいかも。
なるべく低域の出る環境で音を出してもらえると少しはマシに聴けます。

NoteOnイベント使って、キックとクラッシュシンバルに同期させて画面に変化つけてるわけですが、こういうのがお気楽にできちゃうSiONはやっぱり偉大ですよ。ノート番号も取得できるので、メロディに同期して歌詞が出るとかもできそう。
トンネルエフェクトは、Pixeleroのコード

しかしまぁ、己のセンスの古さに打ちひしがれる思いですな。
んでも、作っててかなり面白かった。
ActionScriptでscenerのマネゴトするのは趣味としていいかもなー。