RGB Ensemble using Webcam
この前のてら子でSaqooshaさんが作ってたやつのカラーバージョン
*
* 入力はwebカメラ、カメラがない人はサンプル静止画を読み込んで、
* RGB分解した画像を解析してリアルタイムで音を鳴らすおもちゃ。
* RGBの順に音域が低くなってる。
*
* いい感じの音を鳴らすために
* http://wonderfl.kayac.com/code/1cafdfd8a0f008107c8e42c33043107a73cb52e8
* の波形生成アルゴリズムを使わせてもらってます。
*
* 単純にRGB分解すると白い部分とかが全チャネルに反応してしまうので
* 下の方のRGBExtractorクラスを使ってる。
*
* ソースは汚い。
/**
* Copyright alumican_net ( http://wonderfl.net/user/alumican_net )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/d14b
*/
/* この前のてら子で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;
}
}