Beautifl - Flash Gallery

Thumbnail : RGB Ensemble using Webcam
RGB Ensemble using Webcam
alumican_net 2009-08-27 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

/* この前のてら子でSaqooshaさんが作ってたやつのカラーバージョン
 * 
 * 入力はwebカメラ、カメラがない人はサンプル静止画を読み込んで、
 * RGB分解した画像を解析してリアルタイムで音を鳴らすおもちゃ。
 * RGBの順に音域が低くなってる。
 * 
 * いい感じの音を鳴らすために
 * http://wonderfl.kayac.com/code/1cafdfd8a0f008107c8e42c33043107a73cb52e8
 * の波形生成アルゴリズムを使わせてもらってます。
 * 
 * 単純にRGB分解すると白い部分とかが全チャネルに反応してしまうので
 * 下の方のRGBExtractorクラスを使ってる。
 * 
 * ソースは汚い。
 */
package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.IBitmapDrawable;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.SampleDataEvent;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.Video;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;

    public class FlashTest extends Sprite {
        Wonderfl.disable_capture();
        //Wonderfl.capture_delay(10);
        
        //1つの画像のサイズ
        private const W:uint = uint(465 / 2);
        private const H:uint = uint(465 / 2);
        
        //よく使うので
        private const PI:Number  = Math.PI;
        private const PI2:Number = Math.PI * 2;
        
        //鳴らす音の周波数
        private const KEYS:Array = [130.8, 146.8, 164.8, 174.6, 196.0, 220.0, 246.9, 261.6, 293.7, 329.6, 349.2, 392.0, 440, 493.9, 523.3];
        private const KEYCount:uint = KEYS.length;
        
        //入力
        private var _camera:Camera;
        private var _video:Video;
        private var _picture:Bitmap;
        private var _loader:Loader;
        
        //RGB抽出
        private var _extractor:RGBExtractor = new RGBExtractor(W, H);
        private var _transFunctions:Array = [_extractor.transBlue2Red, _extractor.transBlue2Green, null];
        
        //音
        private var _sound:Sound;
        private var _soundChannel:SoundChannel;
        private var _phase:Array = [0, 0, 0];
        private var _power:Array = [0, 0, 0];
        private var _band:Array  = [0, 0, 0];
        private var _freq:Array  = [0, 0, 0];
        private var _pitch:Array = [0, 0, 0];
        private var _position:uint = 0;
        
        //描画用
        private var _target:IBitmapDrawable;
        private var _canvas:BitmapData;
        private var _source:BitmapData;
        private var _mirror:Matrix = new Matrix(-1, 0, 0, 1, W, 0);
        private var _offset:Matrix = new Matrix();
        private var _offsetX:Array = [W, 0, W];
        private var _offsetY:Array = [0, H, H];
        private var _line:BitmapData;
        private var _dot:BitmapData;
        
        public function FlashTest() {
            // don't take a capture
            Wonderfl.disable_capture();
            // take a capture after 10 sec
            //Wonderfl.capture_delay( 10 );
            
            _source = new BitmapData(H    , H    , false, 0x0);
            _canvas = new BitmapData(W * 2, H * 2, false, 0x0);
            addChild(new Bitmap(_canvas));
            
            _sound = new Sound();
            _sound.addEventListener(SampleDataEvent.SAMPLE_DATA, _sampleDataHandler);
            
            _line = new BitmapData(1, H, false, 0xffffff);
            _dot  = new BitmapData(10, 10, false , 0xffffff);
            
            _camera = Camera.getCamera();
            
            if (_camera) {
                
                //webカメラがあるとき
                _video = new Video(W, H);
                _video.attachCamera(_camera);
                _start(_video);
            } else {
                
                //webカメラがないとき
                var completeHandler:Function = function(e:Event):void {
                    _loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, completeHandler);
                    _picture = _loader.content as Bitmap;
                    _start(_picture);
                };
                _loader = new Loader();
                _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
                _loader.load(new URLRequest("http://lab.alumican.net/wonderfl/rgb_ensemble_input.png"), new LoaderContext(true));
            }
        }
        
        /**
         * 開始
         */
        private function _start(target:IBitmapDrawable):void
        {
            _target = target;
            _soundChannel = _sound.play();
            addEventListener(Event.ENTER_FRAME, _update);
        }
        
        /**
         * その時のpositionに記録されている音を鳴らす
         */
        private function _sampleDataHandler(e:SampleDataEvent):void 
        {
            _power[0] = (_pitch[0] == 0) ? 0 : 0.2;
            _power[1] = (_pitch[1] == 0) ? 0 : 1.0;
            _power[2] = (_pitch[2] == 0) ? 0 : 0.5;
            
            _freq[0] = KEYS[uint(_pitch[0] * (KEYCount - 1))] * 4;
            _freq[1] = KEYS[uint(_pitch[1] * (KEYCount - 1))] * 2;
            _freq[2] = KEYS[uint(_pitch[2] * (KEYCount - 1))] * 1;
            
            _band[0] = PI2 * _freq[0] / 44100;
            _band[1] = PI2 * _freq[1] / 44100;
            _band[2] = PI2 * _freq[2] / 44100;
            
            var s:Number;
            var count:uint;
            var i:uint;
            var j:uint;
            
            for (i = 0; i < 2048; ++i) {
                
                count = 0;
                s = 0;
                for (j = 0; j < 3; ++j) {
                    if (_power[j] == 0) continue;
                    _phase[j] += _band[j];
                    if (_phase[j] > PI2) _phase[j] -= PI2;
                    ++count;
                    
                    //三角波(FC風)
                    //http://wonderfl.kayac.com/code/1cafdfd8a0f008107c8e42c33043107a73cb52e8
                    var temp:Number = 1 / 16;
                    s += (_phase[j] <= PI) ? (int((-2 / PI * _phase[j] + 1) / temp) * temp) : 
                                             (int(( 2 / PI * _phase[j] - 3) / temp) * temp) ;
                }
                s /= count;
                
                //波形データを書き込む
                e.data.writeFloat(s);
                e.data.writeFloat(s);
            }
        }
        
        /**
         * 毎フレーム音の高さを調べてpositionに入れておく
         */
        private function _update(e:Event):void {
            //キャプチャ
            _source.draw(_target, _mirror);
            
            var channels:Array = _extractor.separate(_source);
            
            _canvas.lock();
            _canvas.draw(_source);
            
            var channel:BitmapData;
            for (var i:uint = 0; i < 3; ++i) {
                _offset.identity();
                _offset.tx = _offsetX[i];
                _offset.ty = _offsetY[i];
                
                //チャネル別に鳴らす音の高さを取得
                channel = BitmapData(channels[i]);
                var pitch:Number = _seekPitch(channel, _position, 0x44);
                _pitch[i] = 1 - pitch;
                
                if (_transFunctions[i] != null) channel = _transFunctions[i](channel);
                _canvas.draw(channel, _offset);
                
                _offset.translate(_position, 0);
                _canvas.draw(_line, _offset);
                
                _offset.translate(-4, pitch * H - 4);
                _canvas.draw(_dot, _offset);
            }
            _canvas.unlock();
            
            //シーク位置を更新する
            _position += 2;
            if (_position >= W) _position = 0;
        }
        
        /**
         * 縦方向に上から走査して色の大きい点にきたらその場所を返す
         */
        private function _seekPitch(data:BitmapData, position:uint, threshhold:uint = 0x7f):Number {
            var i:uint;
            var n:uint = data.height;
            var bytes:ByteArray = data.getPixels(new Rectangle(position, 0, 1, n));
            for (i = 0; i < n; ++i)
                if (bytes[i * 4 + 3] > threshhold) break;
            return i / n;
        }
    }
}


import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.filters.ColorMatrixFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

/**
 * RGBExtractor
 * カラーのBitmapDataをRGBに分解するクラス
 * @author alumican.net<Yukiya Okuda>
 */
internal class RGBExtractor {
    private const ZEROS:Point = new Point(0, 0);
    
    private var _width:uint;
    private var _height:uint;
    private var _base:BitmapData;
    private var _refB:BitmapData;
    private var _refR:BitmapData;
    private var _refG:BitmapData;
    private var _channelR:BitmapData;
    private var _channelG:BitmapData;
    private var _channelB:BitmapData;
    private var _rect:Rectangle;
    private var _cmfR2B:ColorMatrixFilter;
    private var _cmfG2B:ColorMatrixFilter;
    private var _cmfB2B:ColorMatrixFilter;
    private var _cmfB2R:ColorMatrixFilter;
    private var _cmfB2G:ColorMatrixFilter;
    private var _scaleTrans:ColorTransform;
    
    public function RGBExtractor(width:uint = 0, height:uint = 0):void {
        _width  = width;
        _height = height;
        
        if (_width > 0 && _height > 0)  _createBitmapData(_width, _height);
        
        //R成分のみ取り出してB成分に置き換えるフィルタ
        _cmfR2B = new ColorMatrixFilter([
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            1, 0, 0, 0, 0,
            0, 0, 0, 1, 0
        ]);
        
        //G成分のみ取り出してB成分に置き換えるフィルタ
        _cmfG2B = new ColorMatrixFilter([
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            0, 1, 0, 0, 0,
            0, 0, 0, 1, 0
        ]);
        
        //B成分のみ取り出してB成分に置き換えるフィルタ
        _cmfB2B = new ColorMatrixFilter([
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 1, 0, 0,
            0, 0, 0, 1, 0
        ]);
        
        //B成分のみ取り出してR成分に置き換えるフィルタ
        _cmfB2R = new ColorMatrixFilter([
            0, 0, 1, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 0, 1, 0
        ]);
        
        //B成分のみ取り出してG成分に置き換えるフィルタ
        _cmfB2G = new ColorMatrixFilter([
            0, 0, 0, 0, 0,
            0, 0, 1, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 0, 1, 0
        ]);
        
        //出力
        _scaleTrans = new ColorTransform();
    }
    
    /**
     * 作業領域の確保
     */
    private function _createBitmapData(width:uint, height:uint):void {
        _rect = new Rectangle(0, 0, width, height);
        
        _base = new BitmapData(width, height, false, 0x0);
        
        _refB = _base.clone();
        _refR = _base.clone();
        _refG = _base.clone();
        
        _channelR = _base.clone();
        _channelG = _base.clone();
        _channelB = _base.clone();
    }
    
    /**
     * RGB抽出
     */
    public function separate(source:BitmapData, scale:Number = 5):Array {
        if (source.width != _width || source.height != _height) _createBitmapData(source.width, source.height);
        
        var channelR:BitmapData = _channelR;
        var channelG:BitmapData = _channelG;
        var channelB:BitmapData = _channelB;
        
        var refB:BitmapData = _refB;
        var refR:BitmapData = _refR;
        var refG:BitmapData = _refG;
        
        var rect:Rectangle = _rect;
        
        channelR.applyFilter(source, rect, ZEROS, _cmfR2B);
        channelG.applyFilter(source, rect, ZEROS, _cmfG2B);
        channelB.applyFilter(source, rect, ZEROS, _cmfB2B);
        
        refB = channelR.clone();
        refR = channelG.clone();
        refG = channelB.clone();
        
        refB.draw(channelG, null, null, BlendMode.LIGHTEN);
        refR.draw(channelB, null, null, BlendMode.LIGHTEN);
        refG.draw(channelR, null, null, BlendMode.LIGHTEN);
        
        channelR.draw(refR, null, null, BlendMode.SUBTRACT);
        channelG.draw(refG, null, null, BlendMode.SUBTRACT);
        channelB.draw(refB, null, null, BlendMode.SUBTRACT);
        
        if (scale != 1) {
            _scaleTrans.redMultiplier = _scaleTrans.greenMultiplier = _scaleTrans.blueMultiplier = scale;
            channelR.draw(channelR, null, _scaleTrans);
            channelG.draw(channelG, null, _scaleTrans);
            channelB.draw(channelB, null, _scaleTrans);
        }
        
        return [channelR, channelG, channelB];
    }
    
    /**
     * 青チャネルを赤に移動させる
     */
    public function transBlue2Red(bitmapData:BitmapData):BitmapData {
        bitmapData.applyFilter(bitmapData, bitmapData.rect, ZEROS, _cmfB2R);
        return bitmapData;
    }
    
    /**
     * 青チャネルを緑に移動させる
     */
    public function transBlue2Green(bitmapData:BitmapData):BitmapData {
        bitmapData.applyFilter(bitmapData, bitmapData.rect, ZEROS, _cmfB2G);
        return bitmapData;
    }
}