/* この前のてら子で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; } } RGB Ensemble using Webcam