In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

音に反応する球面 - Stage3D

再生中のサウンドに合わせて、ジャケット画像で構成した球体の拡大縮小や回転を行っています。
スクリーンサイズに合わせて拡大縮小していますが、ミップマップを使用しているので画像も奇麗にレンダリングされていると思います。
[関連記事]
http://dev.yuichiroharai.com/post/sphericial-beat-sample-stage3d/

[追記]
フルスクリーン時にウィンドウサイズを変更するとブラウザがクラッシュするバグがありましたが修正しました。
/**
 * Copyright yuichiroharai ( http://wonderfl.net/user/yuichiroharai )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/b1YR
 */

package {

// ----------------------------------------------------------------------------------------------------
// インポート
//
    import com.adobe.utils.AGALMiniAssembler;
    import com.adobe.utils.PerspectiveMatrix3D;
    
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Graphics;
    import flash.display.Shape;
    import flash.display.SimpleButton;
    import flash.display.Sprite;
    import flash.display.Stage3D;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.display3D.Context3D;
    import flash.display3D.Context3DCompareMode;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Context3DRenderMode;
    import flash.display3D.Context3DTextureFormat;
    import flash.display3D.Context3DVertexBufferFormat;
    import flash.display3D.IndexBuffer3D;
    import flash.display3D.Program3D;
    import flash.display3D.VertexBuffer3D;
    import flash.display3D.textures.Texture;
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.external.ExternalInterface;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Matrix3D;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.geom.Vector3D;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.media.SoundLoaderContext;
    import flash.media.SoundMixer;
    import flash.net.URLRequest;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    import net.hires.debug.Stats;

    [SWF(width="465", height="465", backgroundColor="0x000000")]
    public class SphericialBeatSample extends Sprite{

// ----------------------------------------------------------------------------------------------------
// メイン処理
//
        private const FPS:uint = 30; // フレームレート
        private const STAGE_SIZE:uint = 465; // ステージサイズ
        private const BP_RGB:uint = 0xa0d626; // Beatportカラー
        
        // トラックのサンプル
        private var TRACK_ID_LIST:Vector.<String> = Vector.<String>(["3052630", "3095724", "3087094", "2450900", "1657853", "2077856"]);
        private var _trackIdIndex:int=0;
        private var _track:Track;
        //private var _screenShot:Bitmap;
        
        public function SphericialBeatSample() {
            Wonderfl.disable_capture();
            //addChild(_screenShot = new Bitmap());
            
            stage.frameRate = FPS;
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            //_initStats();
            _initBack();
            
            // 埋め込みフォントを取得
            FontLoader.loadFont(complete);
            
            function complete():void {
                _initMessage();
                _initInfo();
                _initSelect();
                _initControl();
                stage.addEventListener(Event.RESIZE, _onResize);
                stage.addEventListener(Event.FULLSCREEN, _onResize);

                // Stage3Dの初期化
                _init3D(cleated3D, error);
            }

            function cleated3D():void {
                _resizeBack();
                _moveControl();
                // トラックのロード
                _loadTrack(TRACK_ID_LIST[_trackIdIndex]); 
            }
            function error():void {
                _changeMessage("STAGE3D IS NOT ENABLED ON THIS PC...");
                _textFieldMessage.visible = true;
            }
            
        }

        /**
         * トラックIDを指定してトラックをロード
         * 
         * @param    id        BeatportのトラックID
         */
        private function _loadTrack(id:String):void {
            _context3D.clear(0, 0, 0);
            _context3D.present();

            _changeMessage("NOW LODING");
            _showMessage();
            _textFieldInfo.visible = false;
            _hideSelect();
            _hideControl();
            BeatportLoader.load(id, step1, error);

            function step1():void {
                _track = BeatportLoader.track;
                _hideMessage();
                _change3D();
                _changeInfo();
                _changeSelect();
                _loadAndPlaySound(step2);
            }
            function step2():void {
                _textFieldInfo.visible = true;
                _showSelect();
                _buttonPlay = true;
                _showControl();
            }
            function error():void {
                _hideMessage();
                _changeMessage("LOADING ERROR...");
                _textFieldMessage.visible = true;
            }
        }
        
        /**
         * 前or次のトラックをロード
         * 
         * @param    prev    前のトラックをロードするかどうか。デフォルト(false)は次のトラックをロード。
         */
        private function _loadTrackOrder(prev:Boolean=false):void {
            if (prev) {
                if (--_trackIdIndex < 0) _trackIdIndex = TRACK_ID_LIST.length-1;
                _loadTrack(TRACK_ID_LIST[_trackIdIndex]);
            } else {
                if (++_trackIdIndex > TRACK_ID_LIST.length-1) _trackIdIndex = 0;
                _loadTrack(TRACK_ID_LIST[_trackIdIndex]);
            }
        }
        
        /**
         * 任意のトラックをロード
         * 
         * @param    index    トラックリストのインデックス
         */
        private function _loadTrackSelect(index:uint):void {
            _trackIdIndex = (index > TRACK_ID_LIST.length-1) ? TRACK_ID_LIST.length : index;
            _loadTrack(TRACK_ID_LIST[_trackIdIndex]);
        }

        /**
         * ステージリサイズ時の処理
         */
        private function _onResize(e:Event):void {
            _moveMessage();
            _moveSelect();
            _moveInfo();
            _resize3DBackBuffer(); // 先に実行
            _resizeBack(); // 後に実行
            _moveControl(); // 後に実行
            _changePerspective();
        }


// ----------------------------------------------------------------------------------------------------
// Stage3D関連
//    
        private const ANTI_ALIAS:uint = 4; // アンチエイリアス 0, 2, 4, 16
        
        private var _stage3D:Stage3D;
        private var _context3D:Context3D;
        private var _perspectiveMatrix3D:PerspectiveMatrix3D;
        private var _sphericalShader:SphericalShader;
        private var _vertexList:Vector.<Number>;
        private var _indexList:Vector.<uint>;

        /**
         * Stage3Dの初期化を行います。
         * 
         * @param        callbackComplete    Stage3D初期化の成功時のコールバック関数
         * @param        callBackError        Stage3D初期化のエラー時のコールバック関数
         */
        private function _init3D(callbackComplete:Function, callbackError:Function):void {
            if ((_stage3D = stage.stage3Ds[0]) == null) {
                error();
                return;
            }

            _stage3D.addEventListener(Event.CONTEXT3D_CREATE, context3dCreate);
            //_stage3D.addEventListener(ErrorEvent.ERROR, error);
            _stage3D.requestContext3D(Context3DRenderMode.AUTO);
            
            function context3dCreate(e:Event=null):void {
                var program3D:Program3D, vertexAssembler:AGALMiniAssembler, fragmentAssembler:AGALMiniAssembler,
                    vertexNum:uint, vertexBuffer:VertexBuffer3D;

                _context3D = Stage3D(e.target).context3D;
                //_context3D.enableErrorChecking = true;
                _resize3DBackBuffer();
                _context3D.clear(0, 0, 0);
                _context3D.present();
                _context3D.setDepthTest(true, Context3DCompareMode.LESS); // 深度管理

                // AGALのアセンブルとアップロード
                vertexAssembler = new AGALMiniAssembler();
                vertexAssembler.assemble(Context3DProgramType.VERTEX, SphericalShader.AGAL_VERTEX);
                fragmentAssembler = new AGALMiniAssembler();
                fragmentAssembler.assemble(Context3DProgramType.FRAGMENT, SphericalShader.AGAL_FRAGMENT);
                program3D = _context3D.createProgram();
                program3D.upload(vertexAssembler.agalcode, fragmentAssembler.agalcode);
                _context3D.setProgram(program3D);

                // 透視投影変換用のMatrix3Dの生成
                _changePerspective();
                
                // 球面の頂点リストとインデックスリストの生成
                _sphericalShader = new SphericalShader(0.6, Math.PI/3, Math.PI/3, 64, 64);
                _sphericalShader.setVertex(0, 0);
                _sphericalShader.setVertex(Math.PI/2, 0);
                _sphericalShader.setVertex(Math.PI, 0);
                _sphericalShader.setVertex(3*Math.PI/2, 0);
                _sphericalShader.setVertex(0, Math.PI/2);
                _sphericalShader.setVertex(0, -Math.PI/2);
                _vertexList = _sphericalShader.vertexList;
                _indexList = _sphericalShader.indexList;
                
                // 頂点リストのアップロード
                vertexNum = _vertexList.length/5;
                vertexBuffer = _context3D.createVertexBuffer(vertexNum, 5);
                vertexBuffer.uploadFromVector(_vertexList, 0, vertexNum);
                _context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
                _context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
                
                if (callbackComplete is Function) callbackComplete.apply();
            }
            function error(e:ErrorEvent=null):void {
                if (callbackError is Function) callbackError.apply();
            }
        }
        
        private var _3DRotationX:uint = 0;
        private var _3DRotationY:uint = 0;
        private var _3DScale:Number = 1;
        private var _3DCurrentZ:Number = 1000;
        private var _3DZoomIn:Boolean = true;
        private var _3DTimer:uint=0;
        
        /**
         * 音の強さに合わせて球面を変化させます。
         *
         * @param        amp        音の強さ
         */
        private function _transform3D(amp:Number):void {
            var matrix3D:Matrix3D, indexNum:uint, indexBuffer:IndexBuffer3D, dz:Number, currentTimer:uint, timer:uint;

            currentTimer = getTimer();
            timer = currentTimer - _3DTimer;
            _3DTimer = currentTimer;

            _3DRotationX += int((amp*12 + 2)*timer*0.03);
            _3DRotationY += int((amp*9 + 2)*timer*0.03);
            if (_3DRotationX >= 360) _3DRotationX -= 360;
            if (_3DRotationY >= 360) _3DRotationY -= 360;
            _3DScale = amp*2 + 1;
            
            // 表示直後はZ座標を奥から手前にズームイン
            if (_3DZoomIn) {
                dz = 5 - _3DCurrentZ;
                if (dz < -0.1) {
                    _3DCurrentZ += dz*0.75;
                } else {
                    _3DZoomIn = false;
                    _3DCurrentZ = 5;
                }
            }

            // matrix3Dを設定
            matrix3D = new Matrix3D();
            matrix3D.appendRotation(_3DRotationY, Vector3D.Y_AXIS);
            matrix3D.appendRotation(_3DRotationX, Vector3D.X_AXIS);
            matrix3D.appendScale(_3DScale, _3DScale, _3DScale);
            matrix3D.appendTranslation(0, 0, _3DCurrentZ);
            matrix3D.append(_perspectiveMatrix3D);
            _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix3D, true);

            // 描画
            _context3D.clear(0, 0, 0);
            indexNum = _indexList.length/3;
            indexBuffer = _context3D.createIndexBuffer(_indexList.length);
            indexBuffer.uploadFromVector(_indexList, 0, _indexList.length);
            _context3D.drawTriangles(indexBuffer, 0, indexNum);
            
            //_context3D.drawToBitmapData(_screenShot.bitmapData);
            _context3D.present();
        }
        
        // テクスチャの画像変更など、初期化処理
        private function _change3D():void {
            var size:uint, level:uint, texture:Texture, matrix:Matrix, bmpd:BitmapData;
            
            _3DTimer = getTimer();
            _3DZoomIn = true;
            _3DCurrentZ = 1000;
            
            // テクスチャにミップマップを使用
            size = _track.image.width;
            texture = _context3D.createTexture(size, size, Context3DTextureFormat.BGRA, false);
            matrix = new Matrix();
            level = 0;
            bmpd = new BitmapData(size, size, true, 0);
            while (size >= 1) { 
                bmpd.draw(_track.image, matrix, null, null, null, true); 
                texture.uploadFromBitmapData(bmpd, level++);
                matrix.scale(0.5, 0.5);
                size >>= 1;
                if (size) {
                    bmpd.dispose();
                    bmpd = new BitmapData(size, size, true, 0);
                }
            }
            bmpd.dispose();

            _context3D.setTextureAt(0, texture);
        }
        
        private var _stage3DWidth:uint;
        private var _stage3DHeight:uint;

        // ステージサイズに合わせてStage3Dのサイズを変更
        private function _resize3DBackBuffer():void {
            if (stage.stageWidth < stage.stageHeight) {
                _stage3DWidth = _stage3DHeight = int(stage.stageWidth/4)*2;
            } else {
                _stage3DWidth = _stage3DHeight = int(stage.stageHeight/4)*2;
            }
            if (_stage3DWidth < 50) _stage3DWidth = _stage3DHeight = 50;

            _stage3D.x = int((stage.stageWidth - _stage3DWidth)/2);
            _stage3D.y = int((stage.stageHeight - _stage3DHeight)/2);
            _context3D.configureBackBuffer(_stage3DWidth, _stage3DHeight, ANTI_ALIAS, true);
           
            /*if (_screenShot.bitmapData != null) _screenShot.bitmapData.dispose(); 
            _screenShot.bitmapData = new BitmapData(_stage3DWidth, _stage3DHeight, false, 0);   
            _screenShot.x = _stage3D.x;
            _screenShot.y = _stage3D.y;*/
        }
        
        private function _changePerspective():void {
            _perspectiveMatrix3D = new PerspectiveMatrix3D();
            _perspectiveMatrix3D.perspectiveFieldOfViewLH(45*Math.PI/180, _stage3DWidth/_stage3DHeight, 0.1, 1000);
        }


// ----------------------------------------------------------------------------------------------------
// サウンドのロード、再生、停止など
//
        private var _sound:Vector.<Sound>;
        private var _soundChannel:SoundChannel;
        private var _soundPosition:Number=0;
        
        /**
         * サウンドをロードしてから再生
         * 
         * @param    callback    サウンドのオープン(≒再生)時のコールバック
         */
        private function _loadAndPlaySound(callback:Function):void {
            if (_soundChannel != null) {
                _soundChannel.stop();
            }
            if (_sound != null && _sound[0] != null) {
                _sound[0].removeEventListener(Event.COMPLETE, _onCompleteSound);
                try { _sound[0].close(); } catch (e:Error) {}
                delete _sound[0];
                _sound = null;
                removeEventListener(Event.ENTER_FRAME, _onPlayingSound);
            }
            
            _resetSpectrum();
            _soundPosition = 0;
            _sound = Vector.<Sound>([new Sound()]);
            _sound[0].addEventListener(Event.OPEN, open);
            _sound[0].load(new URLRequest(_track.urlSound), new SoundLoaderContext(1000, true));
            
            function open(e:Event):void {
                if (callback is Function) callback.apply(); // 先に実行
                _soundChannel = _sound[0].play(_soundPosition);
                _soundChannel.addEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
                addEventListener(Event.ENTER_FRAME, _onPlayingSound);
                _sound[0].removeEventListener(Event.OPEN, open);
            }
        }
        /**
         * サウンドを再生
         */
        private function _playSound():void {
            if (_sound == null || _sound[0] == null) return;
            _soundChannel = _sound[0].play(_soundPosition);
            _soundChannel.addEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
            addEventListener(Event.ENTER_FRAME, _onPlayingSound);
        }
        /**
         * サウンドをポーズ
         */
        private function _pauseSound():void {
            if (_soundChannel == null || _sound == null || _sound[0] == null) return;
            _soundPosition = _soundChannel.position;
            _soundChannel.stop();
            _soundChannel.removeEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
            _soundChannel = null;
            removeEventListener(Event.ENTER_FRAME, _onPlayingSound);
        }
        /**
         * サウンドを停止
         */
        private function _stopSound():void {
            if (_soundChannel == null || _sound == null || _sound[0] == null) return;
            _soundChannel.stop();
            _soundChannel.removeEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
            _soundChannel = null;
            removeEventListener(Event.ENTER_FRAME, _onPlayingSound);
        }
        /**
         * サウンドの再生完了時の処理。次のトラックを自動再生。
         */
        private function _onCompleteSound(e:Event):void {
            if (_soundChannel == null || _sound == null || _sound[0] == null) return;
            _soundPosition = 0;
            _soundChannel.removeEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
            _soundChannel = null;
            removeEventListener(Event.ENTER_FRAME, _onPlayingSound);
            _loadTrackOrder();
        }
        
        
// ----------------------------------------------------------------------------------------------------
// スペクトラム
//        
        private const POINT_0:Point = new Point(0, 0);
        private const SPECTRUM_MAX:Number = 1.4142136;
        private var _spectrumCurrent:Number;
        private var _spectrumTarget:Number;
        private var _spectrumMax:Number;
        private var _phaseCurrent:Number;
        
        /**
         * スペクトラムのリセット
         */
        private function _resetSpectrum():void {
            _spectrumCurrent = _spectrumTarget = _spectrumMax = _phaseCurrent = 0;
        }
        
        /**
         * サウンドに合わせて球面を変化
         */
        private function _onPlayingSound(e:Event):void {
            var spectrum:Number, phase:Number, scale:Number, tmp1:Number, tmp2:Number;
            
            // 周波数帯の低い方の1/4を使用。0-1の範囲に変換。
            spectrum = _analyzeSound(2)[0]/SPECTRUM_MAX;
            // トラック中の最大値を取得し、その中での現在値を0-1の範囲に変換。
            if (_spectrumMax < spectrum) _spectrumMax = spectrum;
            if (_spectrumMax > 0) spectrum = spectrum/_spectrumMax;

            // Cubic.easeInOut
            tmp1 = spectrum*2;
            tmp2 = 2 - tmp1;
            _spectrumTarget = (spectrum < 0.5) ? tmp1*tmp1*tmp1*0.5 : 1 - tmp2*tmp2*tmp2*0.5;

            // 前の値から次の値へ75%ずつ変位
            _spectrumCurrent += (_spectrumTarget - _spectrumCurrent)*0.75;

            _transform3D(_spectrumCurrent);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// サウンド解析
//    
        private const STRETCH_FACTOR_LIST:Vector.<Number> = Vector.<Number>([257, 129, 65, 33, 17, 9, 5, 3, 2, 1]);
        private const SPECTRUM_LENGTH_LIST:Vector.<Number> = Vector.<Number>([1, 2, 4, 8, 16, 32, 64, 128, 256, 256]);
        
        // 全周波数帯を2^divに分割し、左右のチャンネルの大きい方のスペクトラムデータを取得します。
        private function _analyzeSound(div:int=0):Vector.<Number> {
            var i:uint, len:uint, size:uint, position:uint, l:Number, r:Number, 
            stretchFactor:uint, bytes:ByteArray, vector:Vector.<Number>;
            
            if (div > 9) div = 9;
            stretchFactor = STRETCH_FACTOR_LIST[div];
            len = SPECTRUM_LENGTH_LIST[div];
            size = 1024/len;
            
            bytes = new ByteArray();
            SoundMixer.computeSpectrum(bytes, true, stretchFactor);

            vector = new Vector.<Number>(len, true);
            for (i=0; i<len; i++) {
                position = i*size;
                bytes.position = position;
                l = bytes.readFloat();
                bytes.position = position + 1024;
                r = bytes.readFloat();
                vector[i] = (l > r) ? l : r;
            }
            return vector;
        }

        
// ----------------------------------------------------------------------------------------------------
// トラックを再生/停止するコントローラ 
//
        private const CONTROL_SIZE:uint = 50;
        private var _bmpControlPlay:Bitmap;
        private var _bmpControlPause:Bitmap;
        private var _buttonControl:SimpleButton;
        private var _buttonPlay:Boolean;
        
        /**
         * コントローラを初期化
         */
        private function _initControl():void {
            var s:Shape, g:Graphics, bmp:Bitmap, bmpd:BitmapData;
            
            s = new Shape();
            g = s.graphics;
            
            // 再生
            g.clear();
            g.lineStyle(1, 0, 0.25);
            g.beginFill(0, 0.75);
            g.drawCircle(25, 25, 20);
            g.endFill();
            g.lineStyle(0, 0, 0);
            g.beginFill(BP_RGB, 0.9);
            g.moveTo(19, 15);
            g.lineTo(34, 25);
            g.lineTo(19, 35);
            g.lineTo(19, 15);
            g.endFill();
            bmpd = new BitmapData(CONTROL_SIZE, CONTROL_SIZE, true, 0);
            bmpd.draw(s);
            _bmpControlPlay = new Bitmap(bmpd);
            _bmpControlPlay.visible = false;
            addChild(_bmpControlPlay);
            
            // 停止
            g.clear();
            g.lineStyle(1, 0, 0.25);
            g.beginFill(0, 0.75);
            g.drawCircle(25, 25, 20);
            g.endFill();
            g.lineStyle(0, 0, 0);
            g.beginFill(BP_RGB, 0.9);
            g.drawRect(18, 17, 4, 16);
            g.drawRect(28, 17, 4, 16);
            g.endFill();
            bmpd = new BitmapData(CONTROL_SIZE, CONTROL_SIZE, true, 0);
            bmpd.draw(s);
            _bmpControlPause = new Bitmap(bmpd);
            _bmpControlPause.visible = false;
            addChild(_bmpControlPause);
            
            // 透明ボタン
            g.clear();
            g.beginFill(0, 1);
            g.drawCircle(0, 0, 100);
            g.endFill();
            _buttonControl = new SimpleButton(null, null, null, s);
            _buttonControl.addEventListener(MouseEvent.ROLL_OVER, onRollOver);
            _buttonControl.addEventListener(MouseEvent.ROLL_OUT, onRollOut);
            _buttonControl.addEventListener(MouseEvent.CLICK, onClick);
            addChild(_buttonControl);
            _hideControl();
            
            function onClick(e:MouseEvent):void {
                if (_buttonPlay) {
                    _pauseSound();
                } else {
                    _playSound();    
                }
                _switchControl();
            }
            
            function onRollOver(e:MouseEvent):void {
                if (_buttonPlay) {
                    _bmpControlPause.visible = true;
                } else {
                    _bmpControlPlay.visible = true;
                }
            }
            function onRollOut(e:MouseEvent):void {
                if (_buttonPlay) {
                    _bmpControlPause.visible = false;
                } else {
                    _bmpControlPlay.visible = false;
                }
            }
        }
        /**
         * コントローラの再生/停止を切り替え
         */
        private function _switchControl():void {
            if (_bmpControlPlay.visible || _bmpControlPause.visible) {
                if (_buttonPlay) {
                    _bmpControlPlay.visible = true;
                    _bmpControlPause.visible = false;
                    _buttonPlay = false;
                } else {
                    _bmpControlPlay.visible = false;
                    _bmpControlPause.visible = true;
                    _buttonPlay = true;
                }
            } else {
                _buttonPlay = !_buttonPlay;
            }
        }
        /**
         * コントローラの配置を更新
         */
        private function _moveControl():void {
            var s:Shape;
            
            s = Shape(_buttonControl.hitTestState);
            s.width = _stage3DWidth;
            s.height = _stage3DHeight;
            _bmpControlPlay.x = int((stage.stageWidth - CONTROL_SIZE)/2);
            _bmpControlPlay.y = int((stage.stageHeight - CONTROL_SIZE)/2);
            _bmpControlPause.x = int((stage.stageWidth - CONTROL_SIZE)/2);
            _bmpControlPause.y = int((stage.stageHeight - CONTROL_SIZE)/2);
            _buttonControl.x = int(stage.stageWidth/2);
            _buttonControl.y = int(stage.stageHeight/2);
        }
        /**
         * コントローラを表示
         */
        private function _showControl():void {
            _buttonControl.visible = true;
            _buttonControl.mouseEnabled = true;
        }
        /**
         * コントローラを非表示
         */
        private function _hideControl():void {
            _buttonControl.visible = false;
            _buttonControl.mouseEnabled = false;
        }

        
// ----------------------------------------------------------------------------------------------------
// トラックの選択と現在位置表示のナビゲーション
//
        private const SELECT_SIZE:uint = 12;
        private var _spSelect:Sprite;
        private var _bmpdSelect:BitmapData;
        private var _buttonSelectList:Vector.<SimpleButton>;
        
        /**
         * 選択ナビゲーションを初期化
         */
        private function _initSelect():void {
            var i:uint, len:uint, s:Shape, g:Graphics, bmp:Bitmap, button:SimpleButton;
            
            _spSelect = new Sprite();
            addChild(_spSelect);
            _buttonSelectList = new Vector.<SimpleButton>();
            s = new Shape();
            g = s.graphics;
            
            // ○
            g.beginFill(0x404040, 1);
            g.drawCircle(6, 6, 6);
            g.endFill();
            _bmpdSelect = new BitmapData(SELECT_SIZE, SELECT_SIZE, true, 0);
            _bmpdSelect.draw(s);
            
            len = TRACK_ID_LIST.length;
            for (i=0;i<len;i++) {
                bmp = new Bitmap(_bmpdSelect);
                button = new SimpleButton(bmp, bmp, bmp, bmp);
                button.x = i*20;
                _spSelect.addChild(button);
                _buttonSelectList[i] = button;
                button.addEventListener(MouseEvent.CLICK, _onClickSelect);
                button.addEventListener(MouseEvent.ROLL_OVER, _onRollOverSelect);
                button.addEventListener(MouseEvent.ROLL_OUT, _onRollOutSelect);
            }
            _changeSelect();
            _moveSelect();
            _hideSelect();
        }
        private function _onClickSelect(e:MouseEvent):void {
            _stopSound();
            _loadTrackSelect(_buttonSelectList.indexOf(e.target));
        }
        private function _onRollOverSelect(e:MouseEvent):void {
            _buttonSelectList[_buttonSelectList.indexOf(e.target)].transform.colorTransform = new ColorTransform(0, 0, 0, 1, 0x80, 0x80, 0x80, 0);
        }
        private function _onRollOutSelect(e:MouseEvent):void {
            _buttonSelectList[_buttonSelectList.indexOf(e.target)].transform.colorTransform = new ColorTransform();
        }
        
        /**
         * 選択ナビゲーションの現在位置を変更
         */
        private function _changeSelect():void {
            var i:uint, len:uint;
            
            len = _buttonSelectList.length;
            for (i=0;i<len;i++) {
                if (i == _trackIdIndex) {
                    _buttonSelectList[i].mouseEnabled = false;
                    _buttonSelectList[i].transform.colorTransform = new ColorTransform(0, 0, 0, 1, ((BP_RGB >> 16) & 255)*0.8, ((BP_RGB >> 8) & 255)*0.8, (BP_RGB & 255)*0.8, 0);
                } else {
                    _buttonSelectList[i].mouseEnabled = true;
                    _buttonSelectList[i].transform.colorTransform = new ColorTransform()
                }
            }
        }
        /**
         * 選択ナビゲーションの配置を更新
         */
        private function _moveSelect():void {
            _spSelect.x = int((stage.stageWidth - _spSelect.width)/2);
            _spSelect.y = int(stage.stageHeight/8*7 - _spSelect.height);
        }
        /**
         * 選択ナビゲーションを表示
         */
        private function _showSelect():void {
            _spSelect.visible = true;
            _spSelect.mouseChildren = true;
        }
        /**
         * 選択ナビゲーションを表示
         */
        private function _hideSelect():void {
            _spSelect.visible = false;
            _spSelect.mouseChildren = false;
        }

            
// ----------------------------------------------------------------------------------------------------
// トラック情報(タイトル / アーティスト / レーベル)の表示
//
        private var _textFieldInfo:TextField;        
        
        /**
         * トラック情報を初期化
         */
        private function _initInfo():void {
            _textFieldInfo = new TextField();
            _textFieldInfo.defaultTextFormat = new TextFormat(FontLoader.FONT_NAME_KROEGER_0655, 8, 0xffffff);
            _textFieldInfo.embedFonts = true;
            _textFieldInfo.autoSize = TextFieldAutoSize.LEFT;
            _textFieldInfo.multiline = false;
            _textFieldInfo.selectable = false;
            addChild(_textFieldInfo);
        }
        /**
         * トラック情報のテキストを変更
         */
        private function _changeInfo():void {
            _textFieldInfo.text = _track.info;
            _moveInfo();
        }
        /**
         * トラック情報の配置を更新
         */
        private function _moveInfo():void {
            _textFieldInfo.x = int((stage.stageWidth - _textFieldInfo.width)/2);
            _textFieldInfo.y = int(stage.stageHeight/8*7 + 10);
        }


// ----------------------------------------------------------------------------------------------------
// 画面の中心にメッセージを表示
//
        private var _textFieldMessage:TextField;
        private var _messageTime:uint;

        /**
         * メッセージ表示を初期化
         */
        private function _initMessage():void {
            _textFieldMessage = new TextField;
            _textFieldMessage.defaultTextFormat = new TextFormat(FontLoader.FONT_NAME_KROEGER_0655, 8, 0xffffff);
            _textFieldMessage.embedFonts = true;
            _textFieldMessage.autoSize = TextFieldAutoSize.LEFT;
            _textFieldMessage.multiline = false;
            _textFieldMessage.selectable = false;
            addChild(_textFieldMessage);
        }
        /**
         * メッセージ表示のテキストを変更
         * 
         * @param    text    表示メッセージ
         */
        private function _changeMessage(text:String):void {
            _textFieldMessage.text = text;
            _moveMessage();
        }
        /**
         * メッセージ表示の配置を更新
         */
        private function _moveMessage():void {
            _textFieldMessage.x = int((stage.stageWidth - _textFieldMessage.width)/2);
            _textFieldMessage.y = int((stage.stageHeight - _textFieldMessage.height)/2);
        }
        /**
         * メッセージ表示の点滅を開始
         */
        private function _showMessage():void {
            addEventListener(Event.ENTER_FRAME, _blinkMessage);
            _textFieldMessage.visible = true;
        }
        /**
         * メッセージ表示の点滅を終了
         */
        private function _hideMessage():void {
            removeEventListener(Event.ENTER_FRAME, _blinkMessage);
            _textFieldMessage.visible = false;
        }
        /**
         * メッセージ表示を一定間隔で点滅
         * 
         * @param    e    イベント
         */
        private function _blinkMessage(e:Event):void {
            var timer:uint;
            
            if ((timer = getTimer()) - _messageTime > 25) {
                _messageTime = timer;
                _textFieldMessage.visible = !_textFieldMessage.visible;
            }
        }


// ----------------------------------------------------------------------------------------------------
// 背景色
//
        private var _bmpBack:Bitmap;
        /**
         * 背景用のBitmapを作成
         */
        private function _initBack():void {
            _bmpBack = new Bitmap(new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xff000000));
            addChildAt(_bmpBack, 0);
        }
        /**
         * 背景用のBitmapをリサイズ。Stage3Dの描画部分のみ透明化。
         */
        private function _resizeBack():void {
            var i:uint, j:uint;
            
            _bmpBack.bitmapData.dispose();
            _bmpBack.bitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xff000000);
            for (i=0;i<_stage3DWidth;i++) {
                for (j=0;j<_stage3DHeight;j++) {
                    _bmpBack.bitmapData.setPixel32(_stage3D.x + i, _stage3D.y + j, 0);
                }
            }
        }


// ----------------------------------------------------------------------------------------------------
// スタッツ
//
        /**
         * スタッツを表示
         */
        public function _initStats():void {
            addChild(new Stats());
        }

    }
}



// ----------------------------------------------------------------------------------------------------
// Stage3Dの球面用のデータを生成
//
class SphericalShader {

// ----------------------------------------------------------------------------------------------------
// AGAL定数
//        
    public static const AGAL_VERTEX:String = "m44 op, va0, vc0\n" + "mov v0, va1";
    public static const AGAL_FRAGMENT:String = "tex ft1, v0, fs0 <2d,linear,mipnearest>\n" + "mov oc, ft1"; // テクスチャ用
    private static const _PI2:Number = Math.PI*2;
        
// ----------------------------------------------------------------------------------------------------
// 変数
//    
    private var _radius:Number;
    private var _width:Number;
    private var _height:Number;
    private var _segmentX:uint = 2;
    private var _segmentY:uint = 2;
    private var _segmentXP:uint;
    private var _segmentYP:uint;
    private var _vertexLength:uint;
    private var _surfaceNum:uint=0;
    
    public function get indexList():Vector.<uint> { return _indexList.slice(); }
    private var _indexList:Vector.<uint>;
    public function get vertexList():Vector.<Number> { return _vertexList.slice(); }
    private var _vertexList:Vector.<Number>;

// ----------------------------------------------------------------------------------------------------
// コンストラクタ
//
    /**
     * 球体の半径、球面のX軸とY軸方向の長さ、断面の数を指定してインスタンスを生成します。
     * 
     * @param    radius        球面の半径 - 球座標の半径
     * @param    width        球面のX軸方向(Y軸周り)の長さを表すラジアン(2PIで一周分)
     * @param    height        球面のY軸方向(X軸周り)の長さを表すラジアン(2PIで一周分)
     * @param    segmentX    球面上のX軸方向(Y軸周り)の断面(四角形)の数
     * @param    segmentY    球面上のY軸方向(X軸周り)の断面(四角形)の数
     */
    public function SphericalShader(radius:Number, width:Number, height:Number, segmentX:uint=2, segmentY:uint=2) {
        _radius = (radius > 0 && radius < 1) ? radius : 1;
        _width =  (width > 0) ? width : SphericalShader._PI2;
        _height =  (height > 0) ? height : SphericalShader._PI2;
        if (segmentX > 1) _segmentX = segmentX;
        if (segmentY > 1) _segmentY = segmentY;
        _segmentXP = _segmentX + 1;
        _segmentYP = _segmentY + 1;
        
        _vertexLength = _segmentXP*_segmentYP;
        _indexList = new Vector.<uint>();
        _vertexList = new Vector.<Number>();
    }
    
    /**
     * 球面の中心点を指定して頂点の位置を計算し、内部の頂点リストとインデックスリストにデータを追加します。
     * 
     * @param    rotationX    球面の中心点X軸方向(Y軸周り)の回転角度ラジアン
     * @param    rotationY    球面のY軸方向(X軸周り)の回転角度ラジアン
     */
    public function setVertex(rotationX:Number, rotationY:Number):void {
        var i:uint, j:uint, w:uint, h:uint, angleX:Number, angleY:Number, pi:Number, offset:uint,
        x1:Number, y1:Number, z1:Number, x2:Number, y2:Number, z2:Number, verticies:Vector.<Number>;
        
        w = _segmentX+1;
        h = _segmentY+1;
        rotationY *= -1;
        
        // 頂点リストにデータを追加
        for (j=0;j<_segmentYP;j++) {
            angleY = _height*j/_segmentY - _height/2;
            
            for (i=0;i<_segmentXP;i++) {
                angleX = _width*i/_segmentX - _width/2;
                
                // 以下、Matrix3Dで実装できそう?
                
                // 正面(x=0, y=0, z=-1)を中心に球面の頂点を生成
                x1 = _radius * _sin(angleX);
                y1 = _radius * -_cos(angleX) * _sin(angleY);
                z1 = _radius * -_cos(angleX) * _cos(angleY);
                
                // 頂点を全体にX軸方向(Y軸周り)とY軸方向(X軸周り)に回転
                x2 = _cos(rotationX) * x1 - _sin(rotationX) * (-1 * _sin(rotationY) * y1 + _cos(rotationY) * z1);
                y2 = _cos(rotationY) * y1 + _sin(rotationY) * z1;
                z2 = _sin(rotationX) * x1 + _cos(rotationX) * (-1 * _sin(rotationY) * y1 + _cos(rotationY) * z1);
                
                _vertexList.push(x2, y2, z2, i/_segmentXP, j/_segmentYP);
            }
        }
        
        // インデックスリストにデータを追加
        offset = _surfaceNum*_vertexLength;
        for (j=0;j<_segmentY;j++) {
            for (i=0;i<_segmentX;i++) {            
                _indexList.push(j*_segmentYP + i + offset, (j+1)*_segmentYP + i+1 + offset, (j+1)*_segmentYP + i + offset);
                _indexList.push(j*_segmentYP + i + offset, j*_segmentYP + i+1 + offset, (j+1)*_segmentYP + i+1 + offset);
            }
        }
        // 面の数をインクリメント
        _surfaceNum++;
    }
    
    // サインとコサインの修正版
    private static function _sin(v:Number):Number { return Math.round(Math.sin(v) * 1000000000000000) / 1000000000000000; }
    private static function _cos(v:Number):Number { return Math.round(Math.cos(v) * 1000000000000000) / 1000000000000000; }
}



// ----------------------------------------------------------------------------------------------------
// Beatport API
//
import flash.display.Bitmap;
import flash.display.Loader;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.external.ExternalInterface;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.text.Font;

class BeatportLoader {

    private static const TEXTURE_SIZE:uint=256;
    public static const TRACK_URL_XML:String = "http://api.beatport.com/catalog/tracks?format=xml&v=1.0";

    private static var _urlLoader:URLLoader;
    private static var _loader:Loader;
    public static var track:Track;
    
    /**
     * Beatport APIを通してトラック情報とジャケット画像をロードします。
     * 
     * @param        id                    BeatportのトラックID
     * @param        callbackComplete    ロード成功時のコールバック関数
     * @param        callBackError        エラー時のコールバック関数
     */
    public static function load(id:String, callbackComplete:Function, callbackError:Function):void {
        if (track != null) track.destroy();
        track = new Track();

        _urlLoader = new URLLoader();
        _urlLoader.addEventListener(Event.COMPLETE, onSuccessInfo);
        _urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
        _urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
        _urlLoader.load(new URLRequest(TRACK_URL_XML + "&id=" + id));
        
        // トラック情報のロード成功時
        function onSuccessInfo(e:Event):void {
            var resultList:XMLList;
            
            _urlLoader.removeEventListener(Event.COMPLETE, onSuccessInfo);
            _urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
            _urlLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
            _urlLoader = null;

            resultList = XMLList(new XML(e.target.data).result);
            if (resultList.length() > 0) {
                _parseXml(resultList[0]);
            } else {
                if (callbackError is Function) callbackError.apply();
                return;
            }

            // 続いてジャケット画像のロード
            _loader = new Loader();
            _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onSuccessImage);
            _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
            _loader.load(new URLRequest(track.urlImage), new LoaderContext(true));
        }
        
        // ジャケット画像のロード成功時
        function onSuccessImage(e:Event):void {
            var bmpd:BitmapData, bmpdTemp:BitmapData;

            // BitmapDataをテクスチャのサイズに変換
            bmpd = Bitmap(e.target.content).bitmapData;
            if (bmpd.width < bmpd.height) {
                bmpdTemp = new BitmapData(TEXTURE_SIZE, TEXTURE_SIZE, true, 0);
                bmpdTemp.draw(bmpd, new Matrix(TEXTURE_SIZE/bmpd.height, 0, 0, TEXTURE_SIZE/bmpd.height, 0, 0), null, null, null, true);
            } else {
                bmpdTemp = new BitmapData(TEXTURE_SIZE, TEXTURE_SIZE, true, 0);
                bmpdTemp.draw(bmpd, new Matrix(TEXTURE_SIZE/bmpd.width, 0, 0, TEXTURE_SIZE/bmpd.width, 0, 0), null, null, null, true);
            }
            bmpd.dispose();
            track.image = bmpdTemp;
    
            _loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onSuccessImage);
            _loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onError);
            _loader = null;
            if (callbackComplete is Function) callbackComplete.apply();
        }
        
        // ロード失敗時のリスナー
        function onError(e:ErrorEvent):void {
            if (callbackError is Function) callbackError.apply();
        }
    }
    
    /**
     * Beatport APIで取得したXMLデータを解析します。
     * 
     * @param    xml    トラック情報の入ったXMLオブジェクト
     */
    private static function _parseXml(xml:XML):void {
        var mixName:String, artist:String, artistList:Array, performer:Object;
        
        track.urlSound = xml.document.track.@url;

        // 画像のURL(大きい画像)
        if (xml.document.track.image.(@ref=='release'&&@width=='500') != undefined) {
            track.urlImage = xml.document.track.image.(@ref=='release'&&@width=='500').@url // 500×500
        // 画像のURL(小さい画像)
        } else if (xml.document.track.image.(@ref=='release'&&@width=='60') != undefined) {
            track.small = true;
            track.urlImage = xml.document.track.image.(@ref=='release'&&@width=='60').@url; // 60×60 リリース画像
        } else if (xml.document.track.image.(@ref=='label'&&@width=='60') != undefined) {
            track.small = true;
            track.urlImage = xml.document.track.image.(@ref=='label'&&@width=='60').@url; // 60×60 レーベル画像
        } else {
            track.small = true;
            track.urlImage = xml.document.track.image.(@ref=='defalut'&&@width=='60').@url; // 60×60 デフォルト画像
        }

        mixName = (xml.document.track.mixName == "" || xml.document.track.mixName == "Original Mix") ? "" : " (" + xml.document.track.mixName + ")";
        artistList = [];
        for each (performer in xml.document.track.performer) {
            if (performer.@ref == "Artist") {
                artistList.push(performer.name);
            }
        }
        artist = artistList.join(", ");
        track.info = String(xml.document.track.name).toUpperCase() + String(mixName).toUpperCase() + " / " + String(artist).toUpperCase() + " / " + String(xml.document.track.label.name).toUpperCase();
    }
}



// ----------------------------------------------------------------------------------------------------
// Beatportのトラックデータを格納するクラス
//
import flash.display.BitmapData;
class Track {    
    public var info:String;                    // トラック名 (ミックス名) / アーティスト / レーベル
    public var urlSound:String;                // サウンドのURL
    public var urlImage:String;                // ジャケット画像のURL
    public var image:BitmapData;            // ジャケット画像
    public var small:Boolean=false;            // 小さい画像のトラックかどうか
    
    public function destroy():void {
        info = null
        urlSound = null;
        urlImage = null;
        if (image != null) image.dispose();
        image = null;
        small = false;
    }
}



// ----------------------------------------------------------------------------------------------------
// フォントのロード用クラス
//    
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.text.Font;
import net.hires.debug.Stats;
import flash.system.SecurityDomain;

class FontLoader {

    //private static const FONT_CLASS_KROEGER_0555:String = "Kroeger0555";
    //private static const FONT_NAME_KROEGER_0555:String = "kroeger 05_55";
    public static const FONT_CLASS_KROEGER_0655:String = "Kroeger0655";
    public static const FONT_NAME_KROEGER_0655:String = "kroeger 06_55";
    //private static const FONT_CLASS_KROEGER_0665:String = "Kroeger0665";
    //private static const FONT_NAME_KROEGER_0665:String = "kroeger 06_65";
    private static const FONT_SWF_URL:String = "http://global.yuichiroharai.com/swf/FontKroeger.swf";
    
    /**
     * フォント内蔵のSWFをロード
     * 
     * @param    callback    ロード完了時のコールバック関数
     */
    public static function loadFont(callBack:Function):void {
        var loader:Loader;

        loader = new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, complete);
        loader.load(new URLRequest(FONT_SWF_URL), new LoaderContext(true, ApplicationDomain.currentDomain, SecurityDomain.currentDomain));

        function complete(e:Event):void {
            try {
                Font.registerFont(loader.contentLoaderInfo.applicationDomain.getDefinition(FONT_CLASS_KROEGER_0655) as Class);
            } catch (e:Error) { return; }
            
            loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, complete);
            loader = null;
            if (callBack is Function) callBack.apply();
        }
    }
}