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

Ripple Beat - 音に反応する波紋

再生中のサウンドに反応してジャケット画像に波紋エフェクトをかけ、スピーカーの振動(っぽいもの)を表現してみました。 
曲と画像はBeatport APIから取得しています。
下記サイトで使用しています。
http://www.yuichiroharai.com/ripplebeat/
キーボードキーを押すとDisplacementMapFilterで使用している画像を表示できます。
/**
 * Copyright yuichiroharai ( http://wonderfl.net/user/yuichiroharai )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/fymi
 */

package {

// ----------------------------------------------------------------------------------------------------
// インポート
//
    import com.adobe.serialization.json.*;
    
    import flash.display.*;
    import flash.events.*;
    import flash.filters.DisplacementMapFilter;
    import flash.filters.DisplacementMapFilterMode;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.media.*;
    import flash.net.*;
    import flash.system.ApplicationDomain;
    import flash.system.SecurityDomain;
    import flash.system.LoaderContext;
    import flash.system.System;
    import flash.text.*;
    import flash.text.Font;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    
    import net.hires.debug.Stats;

    /**
     * Ripple Beatの波紋エフェクトのサンプル
     * http://www.yuichiroharai.com/ripplebeat/
     * 
     * @author Yuichiroh Arai
     */
    public class RippleBeatSample extends Sprite {

// ----------------------------------------------------------------------------------------------------
// メイン処理
//
        
        private const FPS:uint = 60; // フレームレート
        private const STAGE_SIZE:uint = 465; // ステージサイズ
        
        // トラックのサンプル
        // From Within / Johannes Heil / Break New Soil Recordings 
        // Singa / Kaiserdisco / MBF
        // Braumstig (Rodriguez Jr. Remix) / Joachim Pastor / Mistakes Music
        // Lifetimes (Pan-Pot Tribute To Life Remix) / Slam / Soma Records
        // Callisto / Marc Romboy, Stephan Bodzin / Systematic Recordings
        // Agent Minimal / Louie Cut / Piso Records
        private var TRACK_ID_LIST:Vector.<String> = Vector.<String>(["2739989", "2436514", "3017670", "2077856", "844498", "2815309"]);
        private var _trackIdIndex:int=0;
        
        /**
         * コンストラクタ
         */
        public function RippleBeatSample():void {
            stage.frameRate = FPS;
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            _initBack();
            
            //_initStats();
            
            // 埋め込みフォントを取得
            _loadFont(complete);
            
            function complete():void {
                _initMessage();
                _initInfo();
                _initImage();
                _initSelect();
                _initControl();
                stage.addEventListener(Event.RESIZE, _onResize);
                stage.addEventListener(Event.FULLSCREEN, _onResize);
                stage.addEventListener(KeyboardEvent.KEY_DOWN, function(e:Event):void{_changeImageRipple()});
                // トラックをロード
                _loadTrack(TRACK_ID_LIST[_trackIdIndex]); 
            }
        }
        
        /**
         * トラックIDを指定してトラックをロード
         * 
         * @param    id        BeatportのトラックID
         */
        private function _loadTrack(id:String):void {
            _textFieldInfo.visible = false;
            _hideImage();
            _hideSelect();
            _hideControl();
            _changeMessage("NOW LODING");
            _showMessage();
            _loadBeatport(id, step1, error);

            function step1():void {
                _makeRipple(step2);
            }
            function step2():void {
                _hideMessage();
                _changeInfo();
                _textFieldInfo.visible = true;
                _changeImage();
                _changeSelect();
                _changeControl();
                _showImage();
                _loadAndPlaySound(step3);
            }
            function step3():void {
                _showSelect();
                _showControl();
            }
            function error():void {
                _hideMessage();
                _changeMessage("LOADING ERROR...");
                _textFieldMessage.visible = true;
                _showSelect();
            }
        }

        /**
         * 前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();
            _moveInfo();
            _moveImage();
            _moveSelect();
            _moveControl();
            _resizeBack();
        }
        
        
// ----------------------------------------------------------------------------------------------------
// ジャケット画像、ボーダーの表示
//
        private const IMAGE_SIZE_MAX:uint = 212;
        private var _bmpImage:Bitmap;
        private var _bmpBorder:Bitmap;
        private var _bmpRipple:Bitmap;
        private var _rippleImage:Boolean=false;
        
        /**
         * 表示画像の初期化
         */
        private function _initImage():void {
            _bmpRipple = new Bitmap();
            addChild(_bmpRipple);
            _bmpImage = new Bitmap();
            addChild(_bmpImage);
            _bmpBorder = new Bitmap();
            addChild(_bmpBorder);
        }
        /**
         * 表示画像の変更
         */
        private function _changeImage():void {
            _bmpImage.bitmapData = _track.image;
            _bmpBorder.bitmapData = _track.border;
            _moveImage();
        }
        /**
         * 表示画像の配置を更新
         */
        private function _moveImage():void {
            _bmpImage.x = _bmpRipple.x = int((stage.stageWidth - _bmpImage.width)/2);
            _bmpImage.y = _bmpRipple.y = int((stage.stageHeight - _bmpImage.height)/2);

            _bmpBorder.x = _bmpImage.x - 1;
            _bmpBorder.y = _bmpImage.y - 1;
        }
        /**
         * 表示画像の表示
         */
        private function _showImage():void {
            if (_rippleImage) {
                _bmpRipple.visible = true;
            } else {
                _bmpImage.visible = true;
            }            
            _bmpBorder.visible = true;
        }
        /**
         * 表示画像の非表示
         */
        private function _hideImage():void {
            _bmpImage.visible = false;
            _bmpRipple.visible = false;
            _bmpBorder.visible = false;
        }
        /**
         * ジャケット画像と波紋画像の表示を切り替え
         */
        private function _changeImageRipple():void {
            if (_rippleImage) {
                _rippleImage = false;
                _bmpImage.visible = true;
                _bmpRipple.visible = false;
                _bmpRipple.bitmapData = null;
            } else {
                _rippleImage = true;
                _bmpImage.visible = false;
                _bmpRipple.visible = true;
                _bmpImage.filters = null;
            }
        }


// ----------------------------------------------------------------------------------------------------
// DisplacementMapFilterで使用する波紋エフェクトのBitmapDataを作成
//
        // 波紋の波長
        private const RIPPLE_WAVELENGTH:uint = 4;
        private var _rippleList:Vector.<BitmapData>;
        
        /**
         * 波紋エフェクトのBitmapDataのリストを作成
         * 
         * @param    callback    コールバック関数
         */
        private function _makeRipple(callback:Function):void {
            var num:uint, width:uint, height:uint, widthHarf:uint, heightHarf:uint, lengthList:Vector.<Number>;

            if (_rippleList != null) {
                for (num=0;num<_rippleList.length;num++) {
                    if (_rippleList[num] != null) _rippleList[num].dispose();
                }
            }
            
            _rippleList = new Vector.<BitmapData>(RIPPLE_WAVELENGTH, true);
            
            width = _track.image.width;
            height = _track.image.height;
            widthHarf = uint(width/2);
            heightHarf = uint(height/2);
            
            makeData();

            // 処理に時間がかかるため、1フレームごとに実行。
            num = 0;
            addEventListener(Event.ENTER_FRAME, enterframe);
            function enterframe(e:Event):void {
                if (num < RIPPLE_WAVELENGTH) {
                    // 位相をずらしながら、BitmapDataを生成
                    _rippleList[num] = makeBitmapData(num);
                } else {
                    removeEventListener(Event.ENTER_FRAME, enterframe);
                    if (callback is Function) callback.apply();
                }
                ++num;
            }

            // 事前に各ピクセルの中心からの距離を計算
            function makeData():void {
                var area:uint, d:Number, diagonal:Number, 
                i:uint, j:uint, ii:uint, ij:uint, x:int, y:int, xx:uint, yy:uint, ll:Number;

                area = width * height;
                d = Math.sqrt(widthHarf*widthHarf + heightHarf*heightHarf);
                diagonal = (d == uint(d)) ? d : uint(d+1);
                ++diagonal;
                
                lengthList = new Vector.<Number>(area, true);
                for (j=0;j<height;j++) {
                    ij = j * height;
                    y = j - heightHarf;
                    yy = y*y;
                    for (i=0;i<width;i++) {
                        ii = ij + i;
                        x = i - widthHarf;
                        xx = x*x;
                        ll = xx + yy;
                        lengthList[ii] = Math.sqrt(ll);
                    }
                }
            }
            // 位相を指定してBitmapDataを作成
            function makeBitmapData(phase:uint=0):BitmapData {
                var i:uint, j:uint, ij:uint, ii:uint, x:int, y:int, len:Number, unit:Number, amp:Number, sx:Number, sy:Number, c:uint, bitmapData:BitmapData;
                
                unit = 2*Math.PI/RIPPLE_WAVELENGTH;
                bitmapData = new BitmapData(width, height, false, 0x008080);
                bitmapData.lock();
                for (j=0;j<height;j++) {
                    ij = j * height;
                    y = j - heightHarf;
                    for (i=0;i<width;i++) {
                        ii = ij + i;
                        x = i - widthHarf;
                        len = lengthList[ii];
                        amp = Math.round(Math.sin(((len-phase) % RIPPLE_WAVELENGTH)*unit) * 1000000000000000) / 1000000000000000;
                        sx = x*0x80/len*amp;
                        sy = y*0x80/len*amp;
                        c = (0x80 - int(sx + 0.5))*0x100 + 0x80 - int(sy + 0.5);
                        bitmapData.setPixel(i, j, c);
                    }
                }
                bitmapData.unlock();
                return bitmapData;
            }
        }


// ----------------------------------------------------------------------------------------------------
// サウンドのロード、再生、停止など
//
        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 {
                _soundChannel = _sound[0].play(_soundPosition);
                _soundChannel.addEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
                addEventListener(Event.ENTER_FRAME, _onPlayingSound);
                _sound[0].removeEventListener(Event.OPEN, open);
                if (callback is Function) callback.apply();
            }
        }
        /**
         * サウンドを再生
         */
        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);
            _bmpImage.filters = null;
        }
        /**
         * サウンドの再生完了時の処理。次のトラックを自動再生。
         */
        private function _onCompleteSound(e:Event):void {
            if (_soundChannel == null || _sound == null || _sound[0] == null) return;
            _switchControl();
            _soundPosition = 0;
            _soundChannel.removeEventListener(Event.SOUND_COMPLETE, _onCompleteSound);
            _soundChannel = null;
            removeEventListener(Event.ENTER_FRAME, _onPlayingSound);
            _bmpImage.filters = null;
            
            _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, dmf:DisplacementMapFilter;
            
            // 周波数帯の低い方の1/4を使用。0-1の範囲に変換。
            spectrum = _analyzeSound(2)[0]/SPECTRUM_MAX;
            // トラック中の最大値を取得し、その中での現在値を0-1の範囲に変換。
            if (_spectrumMax < spectrum) _spectrumMax = spectrum;
            if (_spectrumMax > 0) spectrum = spectrum/_spectrumMax;
            
            // スペクトラムの値が0%
            if (spectrum == 0) {
                _spectrumTarget = spectrum;
                phase = 0;
            // スペクトラムの値が80%より上
            } else if (spectrum > 0.8) {
                _spectrumTarget = spectrum;
                phase = (0.5+spectrum)*30/FPS;
            // スペクトラムの値が80%以下、30%より上
            } else if (spectrum > 0.3) {
                _spectrumTarget = spectrum/3;
                phase = (0.2+spectrum)*30/FPS;
            // スペクトラムの値が30%以下、0%より上
            } else {
                _spectrumTarget = 0.1;
                phase = 0.5*30/FPS;
            }
            // 前の値から次の値へ75%ずつ変位
            _spectrumCurrent += (_spectrumTarget - _spectrumCurrent)*0.75;

            // 位相の現在値を計算
            if ((_phaseCurrent += phase) >= 4) _phaseCurrent -= 4;
        
            scale = _spectrumCurrent*12;
            if (_rippleImage) {
                _bmpRipple.bitmapData = _rippleList[uint(_phaseCurrent)];
            } else {
                dmf = new DisplacementMapFilter(_rippleList[uint(_phaseCurrent)], POINT_0, 2, 4, scale, scale, DisplacementMapFilterMode.CLAMP, 0, 0);
                _bmpImage.filters = [dmf];
            }
        }


// ----------------------------------------------------------------------------------------------------
// サウンド解析
//    
        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;
        }





// ----------------------------------------------------------------------------------------------------
// Beatport API
//
        private const BP_RGB:uint = 0xa0d626; // Beatportカラー
        //private const BP_TRACK_JSON = "http://api.beatport.com/catalog/tracks?format=json&v=1.0";
        private const BP_TRACK_URL_XML:String = "http://api.beatport.com/catalog/tracks?format=xml&v=1.0";
        private const BP_TRACK_IMAGE_URL:String = "http://geo-media.beatport.com/image_size/212x212/"; // 212×212のジャケット画像取用
        private const BP_TRACK_BORDER_ARGB:uint = 0xff808080;

        private var _urlLoader:URLLoader;
        private var _loader:Loader;
        private var _track:Track;

        /**
         * Beatport APIを通してトラック情報とジャケット画像をロードします。
         * 
         * @param        id                    BeatportのトラックID
         * @param        callbackComplete    ロード成功時のコールバック関数
         * @param        callBackError        エラー時のコールバック関数
         */
        private function _loadBeatport(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(BP_TRACK_JSON + "&id=" + id));
            _urlLoader.load(new URLRequest(BP_TRACK_URL_XML + "&id=" + id));

            // トラック情報のロード成功時
            function onSuccessInfo(e:Event):void {
                // var resultList:Array;
                var resultList:XMLList;
                
                _urlLoader.removeEventListener(Event.COMPLETE, onSuccessInfo);
                _urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
                _urlLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
                _urlLoader = null;
                
                
                /*resultList = JSON.decode(e.target.data).results;
                if (resultList.length > 0) {
                    _decodeBeatportJson(resultList[0]);
                } else {
                    if (callbackError is Function) callbackError.apply();
                    return;
                }*/
                resultList = new XMLList(e.target.data).result;

                if (resultList.length() > 0) {
                    _parseBeatportXml(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 bmpdTemp:BitmapData;
                
                // BitmapDataを抜き出し
                _track.image = Bitmap(e.target.content).bitmapData;
                
                // 画像が小さい場合は2倍に拡大
                if (_track.small) {
                    bmpdTemp = new BitmapData(_track.image.width*2, _track.image.height*2, false, 0);
                    bmpdTemp.draw(_track.image, new Matrix(2, 0, 0, 2, 0, 0), null, null, null, true);
                    _track.image.dispose();
                    _track.image = bmpdTemp;
                }
                
                // ボーダーの作成
                _track.border = makeBorderBitmapData(_track.image.width+2, _track.image.height+2, BP_TRACK_BORDER_ARGB)
                
                _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で取得したJSONデータをデコードします。
         * 
         * @param    json    トラック情報の入ったJSONオブジェクト
         */
        /*private function _decodeBeatportJson(jsonObject:Object):void {
            var mixName:String, artist:String, artistList:Array, performer:Object;

            _track.urlSound = jsonObject.sampleUrl;

            // 画像のURL(大きい画像)
            if (jsonObject.images.large != undefined) {
                _track.urlImage = BP_TRACK_IMAGE_URL + jsonObject.images.large.url.match(/([^\/]*)?$/)[0]; // 212×212
            // 画像のURL(小さい画像)
            } else {
                _track.small = true;
                _track.urlImage = jsonObject.images.medium.url; // 60×60
            }

            mixName = (jsonObject.mixName == "" || jsonObject.mixName == "Original Mix") ? "" : " (" + jsonObject.mixName + ")";
            artistList = [];
            for each (performer in jsonObject.artists) {
                if (performer.type == "Artist") {
                    artistList.push(performer.name);
                }
            }
            artist = artistList.join(", ");
            _track.info = String(jsonObject.name).toUpperCase() + String(mixName).toUpperCase() + " / " + String(artist).toUpperCase() + " / " + String(jsonObject.label.name).toUpperCase();
        }*/
        
        /**
         * Beatport APIで取得したXMLデータを解析します。
         * 
         * @param    xml    トラック情報の入ったXMLオブジェクト
         */
        private function _parseBeatportXml(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 = BP_TRACK_IMAGE_URL + xml.document.track.image.(@ref=='release'&&@width=='500').@url.match(/([^\/]*)?$/)[0]; // 212×212
            // 画像の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();
        }


// ----------------------------------------------------------------------------------------------------
// トラック情報(タイトル / アーティスト / レーベル)の表示
//
        private var _textFieldInfo:TextField;        

        /**
         * トラック情報を初期化
         */
        private function _initInfo():void {
            _textFieldInfo = new TextField();
            _textFieldInfo.defaultTextFormat = new TextFormat(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 + IMAGE_SIZE_MAX)/2 + 50);
        }


// ----------------------------------------------------------------------------------------------------
// トラックを再生/停止するコントローラ 
//        
        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);
            
            // 透明ボタン
            bmp = new Bitmap();
            _buttonControl = new SimpleButton(bmp, bmp, bmp, bmp);
            _buttonControl.addEventListener(MouseEvent.ROLL_OVER, onRollOver);
            _buttonControl.addEventListener(MouseEvent.ROLL_OUT, onRollOut);
            _buttonControl.addEventListener(MouseEvent.CLICK, onClick);
            
            addChild(_buttonControl);
            
            _moveControl();
            
            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 _changeControl():void {
            var bmp:Bitmap;
            bmp = Bitmap(_buttonControl.hitTestState);
            
            if (bmp.bitmapData != null) bmp.bitmapData.dispose();
            bmp.bitmapData = new BitmapData(_track.image.width, _track.image.height, true, 0);
            _moveControl();
            
            _buttonPlay = true;
        }
        /**
         * コントローラの再生/停止を切り替え
         */
        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 {
            _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 - _buttonControl.width)/2);
            _buttonControl.y = int((stage.stageHeight - _buttonControl.height)/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();
        }
            private function _onClickSelect(e:MouseEvent):void {
                _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 _addSelect():void {
            var i:uint, bmp:Bitmap, bmpd:BitmapData, button:SimpleButton;
            
            i = _buttonSelectList.length;
            bmp = new Bitmap(_bmpdSelect);
            button = new SimpleButton(bmp, bmp, bmp, bmp);
            button.x = i*20;
            _spSelect.addChild(button);
            _buttonSelectList.push(button);
            button.addEventListener(MouseEvent.CLICK, _onClickSelect);
            button.addEventListener(MouseEvent.ROLL_OVER, _onRollOverSelect);
            button.addEventListener(MouseEvent.ROLL_OUT, _onRollOutSelect);
            _moveSelect();
        }
        
        /**
         * 選択ナビゲーションの現在位置を変更
         */
        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 + IMAGE_SIZE_MAX)/2 + 15);
        }
        /**
         * 選択ナビゲーションを表示
         */
        private function _showSelect():void {
            _spSelect.visible = true;
            _spSelect.mouseChildren = true;
        }
        /**
         * 選択ナビゲーションを表示
         */
        private function _hideSelect():void {
            _spSelect.visible = false;
            _spSelect.mouseChildren = false;
        }


// ----------------------------------------------------------------------------------------------------
// 画面の中心にメッセージを表示
//
        private var _textFieldMessage:TextField;
        private var _messageTime:uint;
        /**
         * メッセージ表示を初期化
         */
        private function _initMessage():void {
            _textFieldMessage = new TextField;
            _textFieldMessage.defaultTextFormat = new TextFormat(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, false, 0));
            addChildAt(_bmpBack, 0);
        }
        /**
         * 背景用のBitmapをリサイズ
         */
        private function _resizeBack():void {
            _bmpBack.bitmapData.dispose();
            _bmpBack.bitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// フォント内蔵のSWFをロード、登録
//            
        private const FONT_CLASS_KROEGER_0655:String = "Kroeger0655";
        private const FONT_NAME_KROEGER_0655:String = "kroeger 06_55";
        private const FONT_SWF_URL:String = "http://global.yuichiroharai.com/swf/FontKroeger.swf";
        
        /**
         * フォント内蔵のSWFをロード
         * 
         * @param    callback    コールバック関数
         */
        private 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();
            }
        }
        
        
// ----------------------------------------------------------------------------------------------------
// スタッツ
//
        /**
         * スタッツを表示
         */
        private function _initStats():void {
            var stats:Stats;
            stats = new Stats();
            addChild(stats);
        }


// ----------------------------------------------------------------------------------------------------
// ユーティリティメソッド
//
        /**
         * ボーダーBitmapDataを作成
         * 
         * @param    width        横幅
         * @param    height        高さ
         * @param    argb        ARGBカラー
         * @return                ボーダーBitmapData
         */
        public function makeBorderBitmapData(width:uint, height:uint, argb:uint):BitmapData {
            var bmpd:BitmapData, i:uint;
            
            bmpd = new BitmapData(width, height, true, 0);
            bmpd.lock();
            for (i=0;i<height;i++) {
                bmpd.setPixel32(0, i, argb);
                bmpd.setPixel32(width-1, i, argb);
            }
            for (i=1;i<width-1;i++) {
                bmpd.setPixel32(i, 0, argb);
                bmpd.setPixel32(i, height-1, argb);
            }
            bmpd.unlock();
            return bmpd;
        }


    }
}



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