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

loadPCMFromByteArrayでサイン波の再生

【音量にご注意!】
Flash Player 11のloadPCMFromByteArrayでサイン波を再生してみました。
キーボードで音程とオクターブを変える事ができます。
http://dev.yuichiroharai.com/post/play-sine-wave-with-loadpcmfrombytearray/
/**
 * Copyright yuichiroharai ( http://wonderfl.net/user/yuichiroharai )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/j6no
 */

package {
    
// ----------------------------------------------------------------------------------------------------
// インポート
//
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Graphics;
    import flash.display.Loader;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.external.ExternalInterface;
    import flash.geom.Rectangle;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.media.SoundMixer;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.Capabilities;
    import flash.system.LoaderContext;
    import flash.system.SecurityDomain;
    import flash.text.Font;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.ByteArray;
    
    import net.hires.debug.Stats;
    
    
    /**
     * loadPCMFromByteArrayでサイン波の再生【音量にご注意!】
     * 参考記事: 
     * http://dev.yuichiroharai.com/post/play-sine-wave-with-loadpcmfrombytearray/
     *
     * @author Yuichiroh Arai
     */
    public class LoadPCMFromByteArray extends Sprite {
        
// ----------------------------------------------------------------------------------------------------
// メイン処理
//            
        private const FPS:uint = 60; // フレームレート
        private const STAGE_SIZE:uint = 465; // ステージサイズ
        private const SAMPLE_RATE:uint = 44100;
        private const LENGTH:uint = 1;
        private const SAMPLE_LENGTH:uint = SAMPLE_RATE*(LENGTH+0.2); // 少し長めにデータを生成
        private const HERTZ_LIST:Vector.<Number> = Vector.<Number>([16.3516, 17.3239, 18.3540, 19.4454, 20.6017, 21.8268, 23.1247, 24.4997, 25.9565, 27.5000, 29.1352, 30.8677, 32.7032]);
        private const KEY_CODE_LIST:Vector.<uint> = Vector.<uint>([90, 83, 88, 68, 67, 86, 71, 66, 72, 78, 74, 77, 188]);
        
        private var _sound:Vector.<Sound>;
        private var _soundChannel:SoundChannel;
        private var _byteArrayList:Vector.<ByteArray>;
        private var _currentHertz:int = -1;
        private var _currentOctave:int = 4;
        
        public function LoadPCMFromByteArray() {
            stage.frameRate = FPS;
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            
            _initBack();
            stage.addEventListener(Event.RESIZE, _onResize);
            stage.addEventListener(Event.FULLSCREEN, _onResize);
            
            _loadFont(step1);
            
            function step1():void {
                //_initStats();
                _initKeyboard();
                _initSpectrum();
                _initHertzOctave();
                _initSlideBar();
                _initCaution();
                
                _byteArrayList = new Vector.<ByteArray>(HERTZ_LIST.length, true);
                stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyBoardDown);
            }
            
            function onKeyBoardDown(e:KeyboardEvent):void {
                if (e.keyCode == 38) {
                    if (_currentOctave < 9) {
                        ++_currentOctave;
                        _byteArrayList = new Vector.<ByteArray>(HERTZ_LIST.length, true);
                        _textOctave.text = _currentOctave.toString();
                    }
                } else if (e.keyCode == 40) {
                    if (_currentOctave > 0) {
                        --_currentOctave;
                        _byteArrayList = new Vector.<ByteArray>(HERTZ_LIST.length, true);
                        _textOctave.text = _currentOctave.toString();
                    }
                } else {
                    _currentHertz = KEY_CODE_LIST.indexOf(e.keyCode);
                    if (_currentHertz == -1) return;
                    
                    _destroySound();
                    _createByteArray();
                    _loadAndPlaySound();
                    
                    _textHertz.text = (HERTZ_LIST[_currentHertz]*Math.pow(2, _currentOctave)).toString();
                }
            }
        }
        
        /**
         * ステージリサイズ時の処理
         */
        private function _onResize(e:Event):void {
            _moveSpectrum();
            _moveKeyboard();
            _moveOctave();
            _moveSlideBar();
            _moveCaution();
            _resizeBack();
        }
        
        
// ----------------------------------------------------------------------------------------------------
// サウンドの生成、再生など
//
        /**
         * 現在の音程のByteArrayからサウンドを再生
         */
        private function _loadAndPlaySound():void {
            _sound = Vector.<Sound>([new Sound()]);
            _byteArrayList[_currentHertz].position = 0;
            _sound[0].loadPCMFromByteArray(_byteArrayList[_currentHertz], SAMPLE_LENGTH, "float", true, SAMPLE_RATE);
            _soundChannel = _sound[0].play(0);
            stage.addEventListener(Event.ENTER_FRAME, _onPlayingSound);
        }
        
        /**
         * 現在のサウンドを破棄
         */
        private function _destroySound():void {
            if (_soundChannel != null) {
                _soundChannel.stop();
                _soundChannel = null;
            }
            
            if (_sound != null && _sound[0] != null) {
                try { _sound[0].close(); } catch (e:Error) {}
                delete _sound[0];
                _sound = null;
            }
            stage.removeEventListener(Event.ENTER_FRAME, _onPlayingSound);
        }
        
        /**
         * 現在の音程のByteArrayデータが無ければ生成
         */
        private function _createByteArray():void {
            var i:uint, byteArray:ByteArray, unit:Number, amp:Number;
            
            if (_byteArrayList[_currentHertz] != null) return;
            
            byteArray = new ByteArray();
            unit = Math.PI*2/SAMPLE_RATE*(HERTZ_LIST[_currentHertz]*Math.pow(2, _currentOctave));
            for (i=0;i<SAMPLE_LENGTH;i++) {
                amp =  Math.round(Math.sin(i*unit) * 1000000000000000) / 1000000000000000;
                byteArray.writeFloat(amp);
                byteArray.writeFloat(amp);
            }
            _byteArrayList[_currentHertz] = byteArray;
        }
        
        
// ----------------------------------------------------------------------------------------------------
// スペクトラム表示
//
        private const SPECTRUM_SIZE_WIDTH:uint = 256;
        private const SPECTRUM_SIZE_HEIGHT:uint = 200;
        private var _spSpectrum:Sprite;
        private var _bmpSpectrumBorder:Bitmap;
        private var _bmpSpectrumMeter:Bitmap;
        
        /**
         * スペクトラム表示を初期化
         */
        private function _initSpectrum():void {
            _spSpectrum = new Sprite();
            addChild(_spSpectrum);
            
            _bmpSpectrumMeter = new Bitmap();
            _bmpSpectrumMeter.x = _bmpSpectrumMeter.y = 1;
            _spSpectrum.addChild(_bmpSpectrumMeter);
            
            _bmpSpectrumBorder = new Bitmap(makeBorderBitmapData(SPECTRUM_SIZE_WIDTH+2, SPECTRUM_SIZE_HEIGHT+2, 0xff404040));
            _spSpectrum.addChild(_bmpSpectrumBorder);
            
            _moveSpectrum();
        }
        
        /**
         * スペクトラム表示の配置を更新
         */
        private function _moveSpectrum():void {
            if (_spSpectrum == null) return;
            _spSpectrum.x = int((stage.stageWidth - SPECTRUM_SIZE_WIDTH+2)/2);
            _spSpectrum.y = int((stage.stageHeight - SPECTRUM_SIZE_HEIGHT+2)/2);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// スペクトラム
//        
        private const SPECTRUM_MAX:Number = 1.4142136;
        private const STRETCH_FACTOR_LIST:Vector.<Number> = Vector.<Number>([0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257]);
        
        private var _spectrumDiv:uint=0;
        
        /**
         * サウンドに合わせてスペクトラムを描画
         */
        private function _onPlayingSound(e:Event):void {
            var i:uint, bytes:ByteArray, l:Number, r:Number, value:Number, h:uint, stretchFactor:uint, bmpd:BitmapData;
            
            if (_soundChannel.position > LENGTH*1000) {
                _bmpSpectrumMeter.bitmapData = null;
                _destroySound();
                return;
            }
            
            stretchFactor = STRETCH_FACTOR_LIST[_spectrumDiv];
            bytes = new ByteArray();
            SoundMixer.computeSpectrum(bytes, true, stretchFactor);
            
            bmpd = new BitmapData(SPECTRUM_SIZE_WIDTH, SPECTRUM_SIZE_HEIGHT, true, 0);
            bmpd.lock();
            for (i=0;i<256;i++) {
                bytes.position = i*4;
                l = bytes.readFloat();
                bytes.position = i*4 + 1024;
                r = bytes.readFloat();
                value = (l > r) ? l : r;
                h = int(value/SPECTRUM_MAX*200+0.5);
                bmpd.fillRect(new Rectangle(i, SPECTRUM_SIZE_HEIGHT - h, 1, h), 0xff808080);
            }
            bmpd.unlock();
            
            if (_bmpSpectrumMeter.bitmapData != null) _bmpSpectrumMeter.bitmapData.dispose();
            _bmpSpectrumMeter.bitmapData = bmpd;
        }
        
        
// ----------------------------------------------------------------------------------------------------
// 鍵盤表示
//
        private const KEYBOARD_WHITE_WIDTH:uint = 20;
        private const KEYBOARD_WHITE_HEIGHT:uint = 50;
        private const KEYBOARD_BLACK_WIDTH:uint = 15;
        private const KEYBOARD_BLACK_HEIGHT:uint = 30;
        private const KEY_WHITE_LIST:Vector.<String> = Vector.<String>(["Z", "X", "C", "V", "B", "N", "M", ","]);
        private const KEY_BLACK_LIST:Vector.<String> = Vector.<String>(["S", "D", "", "G", "H", "J"]);
        
        private var _spKeyboard:Sprite;
        
        /**
         * 鍵盤表示を初期化
         */
        private function _initKeyboard():void {
            var i:uint, text:TextField, bmp:Bitmap, bmpd:BitmapData, s:Shape, g:Graphics;
            
            _spKeyboard = new Sprite();
            addChild(_spKeyboard);
            
            // 白鍵盤
            bmpd = new BitmapData(KEYBOARD_WHITE_WIDTH, KEYBOARD_WHITE_HEIGHT, true, 0xffffffff);
            for (i=0;i<8;i++) {
                bmp = new Bitmap(bmpd);
                bmp.x = i*(KEYBOARD_WHITE_WIDTH+1);
                _spKeyboard.addChild(bmp);
                
                text = new TextField();
                text.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0655, 8, 0x000000);
                text.embedFonts = true;
                text.autoSize = TextFieldAutoSize.LEFT;
                text.multiline = false;
                text.selectable = false;
                text.text = KEY_WHITE_LIST[i];
                text.x = int(i*(KEYBOARD_WHITE_WIDTH+1) + (KEYBOARD_WHITE_WIDTH-text.width)/2);
                text.y = KEYBOARD_WHITE_HEIGHT - 15;
                _spKeyboard.addChild(text);
            }
            
            // 黒鍵盤
            bmpd = new BitmapData(KEYBOARD_BLACK_WIDTH, KEYBOARD_BLACK_HEIGHT, true, 0xff000000);
            for (i=0;i<6;i++) {
                if (i == 2) continue;
                bmp = new Bitmap(bmpd);
                bmp.x = (i+1)*(KEYBOARD_WHITE_WIDTH+1) - KEYBOARD_BLACK_WIDTH/2 - 1;
                _spKeyboard.addChild(bmp);
                
                text = new TextField();
                text.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0655, 8, 0xffffff);
                text.embedFonts = true;
                text.autoSize = TextFieldAutoSize.LEFT;
                text.multiline = false;
                text.selectable = false;
                text.text = KEY_BLACK_LIST[i];
                text.x = int((i+1)*(KEYBOARD_WHITE_WIDTH+1) - text.width/2);
                text.y = KEYBOARD_BLACK_HEIGHT - 15;
                _spKeyboard.addChild(text);
            }
            
            // オクターブ
            s = new Shape();
            g = s.graphics;
            
            g.clear();
            g.beginFill(0xffffff, 1);
            g.moveTo(4, 0);
            g.lineTo(8, 8);
            g.lineTo(0, 8);
            g.lineTo(4, 0);
            g.endFill();
            bmpd = new BitmapData(8, 8, true, 0);
            bmpd.draw(s);
            bmp = new Bitmap(bmpd);
            bmp.x = (KEYBOARD_WHITE_WIDTH+1)*8 + 20;
            bmp.y = KEYBOARD_WHITE_HEIGHT/2 - 15;
            _spKeyboard.addChild(bmp);
            
            g.clear();
            g.beginFill(0xffffff, 1);
            g.moveTo(0, 0);
            g.lineTo(8, 0);
            g.lineTo(4, 8);
            g.lineTo(0, 0);
            g.endFill();
            bmpd = new BitmapData(8, 8, true, 0);
            bmpd.draw(s);
            bmp = new Bitmap(bmpd);
            bmp.x = (KEYBOARD_WHITE_WIDTH+1)*8 + 20;
            bmp.y = KEYBOARD_WHITE_HEIGHT/2 + 5;
            _spKeyboard.addChild(bmp);
            
            _moveKeyboard();
        }
        
        /**
         * 鍵盤表示の配置を更新
         */
        private function _moveKeyboard():void {
            if (_spKeyboard == null) return;
            _spKeyboard.x = int((stage.stageWidth - _spKeyboard.width)/2);
            _spKeyboard.y = int((stage.stageHeight + SPECTRUM_SIZE_HEIGHT+2)/2 + 40);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// 周波数とオクターブの表示
//
        private var _spHertzOctave:Sprite;
        private var _textHertz:TextField;
        private var _textOctave:TextField;
        
        /**
         * 周波数とオクターブの表示を初期化
         */
        private function _initHertzOctave():void {
            var text:TextField;
            
            _spHertzOctave = new Sprite();
            addChild(_spHertzOctave);
            
            // 周波数
            text = new TextField();
            text.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0555, 8, 0x808080);
            text.embedFonts = true;
            text.autoSize = TextFieldAutoSize.LEFT;
            text.multiline = false;
            text.selectable = false;
            text.text = "HERTZ:";
            _spHertzOctave.addChild(text);
            
            _textHertz = new TextField();
            _textHertz.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0655, 8, 0xffffff);
            _textHertz.embedFonts = true;
            _textHertz.autoSize = TextFieldAutoSize.LEFT;
            _textHertz.multiline = false;
            _textHertz.selectable = false;
            _textHertz.text = "---";
            _textHertz.x = text.width;
            _textHertz.y = -2;
            _spHertzOctave.addChild(_textHertz);
            
            // オクターブ
            text = new TextField();
            text.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0555, 8, 0x808080);
            text.embedFonts = true;
            text.autoSize = TextFieldAutoSize.LEFT;
            text.multiline = false;
            text.selectable = false;
            text.text = "OCTAVE:";
            text.x = (KEYBOARD_WHITE_WIDTH+1)*5;
            _spHertzOctave.addChild(text);
            
            _textOctave = new TextField();
            _textOctave.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0655, 8, 0xffffff);
            _textOctave.embedFonts = true;
            _textHertz.autoSize = TextFieldAutoSize.LEFT;
            _textOctave.multiline = false;
            _textOctave.selectable = false;
            _textOctave.text = _currentOctave.toString();
            _textOctave.x = text.x + text.width;
            _textOctave.y = -2;
            _spHertzOctave.addChild(_textOctave);
            
            _moveOctave();
        }
        
        /**
         * 周波数とオクターブの配置を更新
         */
        private function _moveOctave():void {
            if (_spHertzOctave == null) return;
            _spHertzOctave.x = int((stage.stageWidth - _spKeyboard.width)/2 - 2);
            _spHertzOctave.y = int((stage.stageHeight + SPECTRUM_SIZE_HEIGHT+2)/2 + 20);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// stretchFactorを変更するためのスライドバー
//
        private const SLIDE_BAR_WIDTH:uint = 120;
        private var _spSlideBar:Sprite;
        private var _slideBar:SlideBar;
        private var _textSlideBar:TextField;
        
        /**
         * スライドバーを初期化
         */
        private function _initSlideBar():void {
            var text:TextField;
            
            _spSlideBar = new Sprite();
            addChild(_spSlideBar);
            
            text = new TextField();
            text.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0555, 8, 0x808080);
            text.embedFonts = true;
            text.autoSize = TextFieldAutoSize.LEFT;
            text.multiline = false;
            text.selectable = false;
            text.text = "STRETCH FACTOR:";
            text.x = -5;
            _spSlideBar.addChild(text);
            
            _textSlideBar = new TextField();
            _textSlideBar.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0655, 8, 0xffffff);
            _textSlideBar.embedFonts = true;
            _textSlideBar.autoSize = TextFieldAutoSize.LEFT;
            _textSlideBar.multiline = false;
            _textSlideBar.selectable = false;
            _textSlideBar.text = STRETCH_FACTOR_LIST[_spectrumDiv].toString();
            _textSlideBar.x = text.width;
            
            _textSlideBar.y = -2;
            _spSlideBar.addChild(_textSlideBar);
            
            _slideBar = new SlideBar(SLIDE_BAR_WIDTH, STRETCH_FACTOR_LIST.length-1, SlideBar.createBar(7, 15, 0xffffffff), SlideBar.createRail(SLIDE_BAR_WIDTH, 1, 0xff404040));
            _slideBar.value = _spectrumDiv;
            _slideBar.onChange = onChange;
            _slideBar.y = 25;
            _spSlideBar.addChild(_slideBar);
            
            _moveSlideBar();
            
            function onChange(value:uint):void {
                _spectrumDiv = value;
                _textSlideBar.text = STRETCH_FACTOR_LIST[_spectrumDiv].toString();
            }
        }
        
        /**
         * スライドバーの配置を更新
         */
        private function _moveSlideBar():void {
            if (_spSlideBar == null) return;
            _spSlideBar.x = int((stage.stageWidth - _spSlideBar.width)/2 + 5);
            _spSlideBar.y = int((stage.stageHeight - SPECTRUM_SIZE_HEIGHT+2)/2 - 48);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// 警告テキスト
//
        private var _textCaution:TextField;
        
        /**
         * 警告テキストを初期化
         */
        private function _initCaution():void {
            _textCaution = new TextField();
            _textCaution.defaultTextFormat = new TextFormat(FONT_NAME_KROEGER_0655, 8, 0xCC0000);
            _textCaution.embedFonts = true;
            _textCaution.autoSize = TextFieldAutoSize.LEFT;
            _textCaution.multiline = false;
            _textCaution.selectable = false;
            _textCaution.text = "CAUTION! HIGH VOLUME SOUND!";
            addChild(_textCaution);
            
            _moveCaution();
        }
        
        /**
         * 警告テキストの配置を更新
         */
        private function _moveCaution():void {
            if (_textCaution == null) return;
            _textCaution.x = int((stage.stageWidth - _textCaution.width)/2);
            _textCaution.y = int((stage.stageHeight - SPECTRUM_SIZE_HEIGHT+2)/2 - 80);
        }
        
        
        
        // ----------------------------------------------------------------------------------------------------
        // フォント内蔵のSWFをロード、登録
        //        
        private const FONT_CLASS_KROEGER_0555:String = "Kroeger0555";
        private const FONT_CLASS_KROEGER_0655:String = "Kroeger0655";
        private const FONT_NAME_KROEGER_0555:String = "kroeger 05_55";
        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_0555) as Class);
                    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 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 {
            if (_bmpBack == null || _bmpBack.bitmapData == null) return;
            _bmpBack.bitmapData.dispose();
            _bmpBack.bitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0);
        }
        
        
// ----------------------------------------------------------------------------------------------------
// ユーティリティメソッド
//
        /**
         * ボーダーBitmapDataを作成
         * 
         * @param    width        横幅
         * @param    height        高さ
         * @param    argb        ARGBカラー
         * @return                ボーダーBitmapData
         */
        public function makeBorderBitmapData(width:uint, height:uint, argb:uint):BitmapData {
            var bmpd:BitmapData;
            
            bmpd = new BitmapData(width, height, true, 0);
            addBorderBitmapData(bmpd, argb);
            
            return bmpd;
        }
        
        /**
         * BitmapDataにボーダーを上書き
         * 
         * @param    bmpd        対象のBitmapData
         * @param    argb        ARGBカラー
         */
        public function addBorderBitmapData(bmpd:BitmapData, argb:uint):void {
            var width:uint, height:uint, i:uint;
            
            width = bmpd.width;
            height = bmpd.height;
            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();
        }
        
        
// ----------------------------------------------------------------------------------------------------
// スタッツ
//
        /**
         * スタッツを表示
         */
        private function _initStats():void {
            var stats:Stats;
            stats = new Stats();
            addChild(stats);
        }
        
        
    }
}






// ----------------------------------------------------------------------------------------------------
// インポート
//
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;

/**
 * スライドバー(X軸)を構成するクラスです。
 */
class SlideBar extends Sprite {
    
// ----------------------------------------------------------------------------------------------------
// 変数
//
    // スライドバーのボタン
    private var _bar:SimpleButton;
    // スライドバーのレール
    private var _rail:Sprite;
    
    // スライドバーの横幅
    private var _width:uint;
    // スライドバーの取り得る値の範囲
    private var _range:uint;
    
    /**
     * バーの値です。
     */
    public function get value():uint {
        return _value;
    }
    public function set value(v:uint):void {
        _value = (v > _range) ? _range : v;
        _barMoveJust();
        if (onChange is Function) onChange.apply(null, [_value]);
    }
    private var _value:uint = 0;
    
    /**
     * バーの移動係数です。
     */
    public function get moveFactor():Number {
        return _moveFactor;
    }
    public function set moveFactor(v:Number):void {
        _moveFactor = (v >= 0 && v <= 1) ? v : 0.5;
    }
    private var _moveFactor:Number = 0.5;
    
    // valueに対応するX座標
    private var _x:Number=0;
    
    // スライドバーが移動中かどうか
    private var _moving:Boolean=false;
    
    /**
     * SlideBarの値が変更された時に呼び出されるコールバック関数です。
     */
    public var onChange:Function;
    
    
// ----------------------------------------------------------------------------------------------------
// コンストラクタ
//
    /**
     * スライドバーを作成します。
     *
     * @param    width    スライドバーの幅
     * @param    range    スライドバーの値の数
     * @param    bar        スライドバーのバーを表すSimpleButtonです。
     * @param    rail    スライドバーのレールを表すSpriteです。
     */
    public function SlideBar(width:uint, range:uint, bar:SimpleButton, rail:Sprite) {
        _width = (width < 2) ? 2 : width;
        _range = (range < 2 || range > width) ? width : range;
        addChildAt(_rail = rail, 0);
        addChild(_bar = bar);
        
        _bar.addEventListener(MouseEvent.MOUSE_DOWN, _barMouseDown, false, 0, true);
    }
    
    
// ----------------------------------------------------------------------------------------------------
// スライドバーの操作
//        
    // バーのドラッグ開始
    private function _barMouseDown(e:Event):void {
        if (stage == null) return;
        stage.addEventListener(MouseEvent.MOUSE_MOVE, _mouseMove);
        stage.addEventListener(MouseEvent.MOUSE_UP, _mouseUp);
    }
    
    // バーのドラッグ終了
    private function _mouseUp(e:Event):void {
        if (stage == null) return;
        stage.removeEventListener(MouseEvent.MOUSE_MOVE, _mouseMove);
        stage.removeEventListener(MouseEvent.MOUSE_UP, _mouseUp);
    }
    
    // バーのドラッグ
    private function _mouseMove(e:Event):void {
        var temp:uint = _value;
        
        if (stage == null) return;
        
        if (!_moving) {
            stage.addEventListener(Event.ENTER_FRAME, _barMove);
            _moving = true;
        }
        
        if (mouseX < 0) {
            _value = 0;
            _x = 0;
        } else if (mouseX > _width) {
            _value = _range;
            _x = _width;
        } else {
            _value = uint(mouseX/_width*_range + 0.5);
            _x = uint(_width*_value/_range);
        }
        
        if (temp != _value && onChange is Function) onChange.apply(null, [_value]);
    }
    
    // バーの移動
    private function _barMove(e:Event):void {
        var diff:Number, abs:Number;
        
        diff = _x - _bar.x;
        abs = (diff < 0) ? -diff : diff;
        if (abs*_moveFactor < 0.05) {
            _bar.x = _x;
            if (_moving) {
                stage.removeEventListener(Event.ENTER_FRAME, _barMove);
                _moving = false;
            }
        } else {
            _bar.x += diff*_moveFactor;
        }
    }
    
    // バーの強制移動
    private function _barMoveJust():void {
        _bar.x = _x = uint(_width*_value/_range);
        if (_moving) {
            stage.removeEventListener(Event.ENTER_FRAME, _barMove);
            _moving = false;
        }
    }
    
    /**
     * バーを作成
     *
     * @param    width    バーの幅
     * @param    range    バーの高さ
     * @param    argb    バーのARGBカラー
     */
    public static function createBar(width:uint, height:uint, argb:uint):SimpleButton {
        var bmp:Bitmap;
        
        bmp = new Bitmap(new BitmapData(width, height, true, argb));
        bmp.x =  - uint(width/2);
        bmp.y =  - uint(height/2);
        return new SimpleButton(bmp, bmp, bmp, bmp);
    }
    
    /**
     * レールを作成
     *
     * @param    width    バーの幅
     * @param    range    バーの高さ
     * @param    argb    バーのARGBカラー
     */
    public static function createRail(width:uint, height:uint, argb:uint):Sprite {
        var bmp:Bitmap, sprite:Sprite;
        
        bmp = new Bitmap(new BitmapData(width, height, true, argb));
        bmp.x =  0;
        bmp.y =  - uint(height/2);
        sprite = new Sprite();
        sprite.addChild(bmp);
        return sprite;
    }
}