MOTION-FLUID-SOUND (forked from: Instrument)
Motion controlled fluid dynamics with a bit o' sound. Press space bar to start it. A webcam is required.
/**
* Copyright devon_o ( http://wonderfl.net/user/devon_o )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/mdHR
*/
// forked from Instrument
package {
import com.bit101.components.Label;
import com.bit101.components.Style;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
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.StatusEvent;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.media.Camera;
import flash.media.Video;
import flash.utils.setTimeout;
import net.hires.debug.Stats;
/**
* __ __ ___ _____ ___ ___ _ _ _____ _ _ _ ___ ____ ____ ___ _ _ _ _ ____
* | \/ |/ _ \_ _|_ _/ _ \| \ | | | ___| | | | | |_ _| _ \ / ___| / _ \| | | | \ | | _ \
* | |\/| | | | || | | | | | | \| | _____ | |_ | | | | | || || | | |_____ \___ \| | | | | | | \| | | | |
* | | | | |_| || | | | |_| | |\ | |_____| | _| | |___| |_| || || |_| |_____| ___) | |_| | |_| | |\ | |_| |
* |_| |_|\___/ |_| |___\___/|_| \_| |_| |_____|\___/|___|____/ |____/ \___/ \___/|_| \_|____/
*
*
* Fluid dynamics based on this processing script: http://www.processing.org/learning/topics/fluid.html
* There are other Wonderfl projects based on the same Processing code ( such as this one: http://wonderfl.net/c/7ZnV ) and, though they may
* actually perform better, I wanted to go back to the source and do it my own way.
*
*
* @author Devon O.
* 22/10/2010 10:52
*/
[SWF(width='465', height='465', backgroundColor='#000000', frameRate='31')]
public class Main extends Sprite {
public static const NUM_PARTICLES:int = 3000;
public static const RESOLUTION:int = 15;
public static const PEN_SIZE:int = 30;
public static var cols:int;
public static var rows:int;
public static var w:Number;
public static var h:Number;
public static var v:Vector.<Vector.<VSquare>>;
public static var vbuf:Vector.<Vector.<VBuffer>>;
private var _particles:Vector.<Particle> = new Vector.<Particle>(NUM_PARTICLES, true);
private var _pcount:int = 0;
private var _mouseXvel:int = 0;
private var _mouseYvel:int = 0;
private var _prevGuideX:Number = 0.0;
private var _prevGuideY:Number = 0.0;
private var _pHolder:Shape = new Shape();
private var _display:BitmapData;
private var _blur:BlurFilter = new BlurFilter(4, 4, 2);
private var _pt:Point = new Point();
private var _redden:ColorTransform = new ColorTransform(1, .50, .50, 1);
private var _video:Video;
private var _tracker:Tracker;
private var _motionGuide:Shape;
private var _motionHolder:Sprite;
private var _previewMatrix:Matrix = new Matrix();
private var _previewTransform:ColorTransform = new ColorTransform(.3, .3, .3, .5);
private var _toneholder:Sprite;
private var _tones:Vector.<ToneCircle> = new Vector.<ToneCircle>(16, true);
private var _draw:Boolean = false;
private var _showCircles:Boolean = false;
private var _showVideo:Boolean = false;
private var _inited:Boolean = false;
private var _t:Label;
public function Main() {
if (stage) showText();
else addEventListener(Event.ADDED_TO_STAGE, showText);
}
private function showText(event:Event = null):void {
Style.LABEL_TEXT = 0xFFFFFF;
_t = new Label(this, 0, 0, "Move around in front of your webcam to stir up the liquid and create music.\n\nPress A to show/hide webcam image.\nPress S to show/hide guides.\n\nPress SPACE to begin or stop.");
_t.x = (stage.stageWidth - _t.width) >> 1;
_t.y = 175;
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
}
private function init():void {
w = stage.stageWidth;
h = stage.stageHeight;
cols = w / RESOLUTION;
rows = h / RESOLUTION;
_prevGuideX = stage.stageWidth >> 1;
_prevGuideY = stage.stageHeight >> 1;
if (initVideo()) {
initDisplay();
initFluid();
initToneGrid();
//addChild(new Stats());
setTimeout(begin, 1000);
addEventListener(Event.ENTER_FRAME, frameHandler);
}
}
private function begin():void {
_draw = true;
}
private function initDisplay():void {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.showDefaultContextMenu = false;
_display = new BitmapData(w, h, false, 0x000000);
addChild(new Bitmap(_display));
}
private function onKey(event:KeyboardEvent):void {
// 65=a 83=s 32=space
if (event.keyCode == 65) _showVideo = !_showVideo;
if (event.keyCode == 83) _showCircles = !_showCircles;
if (event.keyCode == 32) {
if (!_inited) {
_inited = true;
removeChild(_t);
init();
} else {
if (!willTrigger(Event.ENTER_FRAME)) {
addEventListener(Event.ENTER_FRAME, frameHandler);
} else {
removeEventListener(Event.ENTER_FRAME, frameHandler);
}
}
}
}
private function initVideo():Boolean {
_previewMatrix.scale(.25, .25);
_motionGuide = new Shape();
_motionGuide.graphics.lineStyle(0, 0xFFFFFF, .25);
_motionGuide.graphics.drawCircle(0, 0, 10);
_motionHolder = new Sprite();
_motionHolder.addChild(_motionGuide);
_video = new Video(stage.stageWidth, stage.stageHeight);
var camIndex:int = 0;
for ( var i:int = 0 ; i < Camera.names.length ; i++ ) {
if ( Camera.names[ i ] == "USB Video Class Video" ) {
camIndex = i;
break;
}
}
var cam:Camera = Camera.getCamera(String(camIndex));
if (cam != null) {
cam.setMode(stage.stageWidth, stage.stageHeight, 25);
_video.attachCamera(cam);
_tracker = new Tracker(_video);
return true;
} else {
_t.text = "No webcam detected.";
addChild(_t);
return false;
}
}
private function initFluid():void {
v = new Vector.<Vector.<VSquare>>();
vbuf = new Vector.<Vector.<VBuffer>>();
for (var i:int = 0; i < NUM_PARTICLES; i++) {
_particles[i] = new Particle(Math.random() * w, Math.random() * h);
}
for (var col:int = 0; col <= cols; col++) {
v[col] = new Vector.<VSquare>();
vbuf[col] = new Vector.<VBuffer>();
for (var row:int = 0; row <= rows; row++) {
v[col][row] = new VSquare(col * RESOLUTION, row * RESOLUTION);
vbuf[col][row] = new VBuffer(col * RESOLUTION, row * RESOLUTION);
}
}
v.fixed = true;
vbuf.fixed = true;
}
private function initToneGrid():void {
_toneholder = new Sprite();
var ctr:int = 0;
var hStep:int = int(w / 4);
var vStep:int = int(h / 4);
var xpos:int = hStep * .5;
var ypos:int = vStep * .5;
for (var i:int = 0; i < 4; i++) {
for (var j:int = 0; j < 4; j++) {
var tc:ToneCircle = new ToneCircle(4000 - (ctr * 200 + 500));
tc.x = xpos;
tc.y = ypos;
xpos += hStep;
_toneholder.addChild(tc);
_tones[ctr] = tc;
ctr++;
}
xpos = hStep * .5;
ypos += vStep;
}
}
private function frameHandler(event:Event):void {
updateTracker();
updateParticles();
checkForTone();
if (_draw) {
if (_showVideo) {
_display.draw(_tracker._previous, _previewMatrix, _previewTransform);
}
if (_showCircles) {
_display.draw(_toneholder);
_display.draw(_motionHolder);
}
_display.applyFilter(_display, _display.rect, _pt, _blur);
_display.draw(_pHolder, null, _redden, BlendMode.ADD);
}
}
private function checkForTone():void {
var i:int = _tones.length;
while (i--) {
var tc:ToneCircle = _tones[i];
var dx:Number = _motionGuide.x - tc.x;
var dy:Number = _motionGuide.y - tc.y;
var d:Number = dx * dx + dy * dy;
// 55 is radius of motionguide + radius of tc
if (d < 55 * 55) {
if (!tc.played) tc.playTone();
} else if (tc.played) {
tc.played = false;
}
}
}
private function updateTracker():void {
_tracker.track();
_motionGuide.x += (_tracker.x - _motionGuide.x) / 10;
_motionGuide.y += (_tracker.y - _motionGuide.y) / 10;
}
private function updateParticles():void {
var axvel:int = _motionGuide.x - _prevGuideX;
var ayvel:int = _motionGuide.y - _prevGuideY;
_mouseXvel = (axvel != _mouseXvel) ? axvel : 0;
_mouseYvel = (ayvel != _mouseYvel) ? ayvel : 0;
for (var col:int = 1; col < cols; col++) {
for (var row:int = 1; row < rows; row++) {
vbuf[col][row].update(col, row);
v[col][row].addBuffer(col, row);
var adj:Number = v[col][row].x - _motionGuide.x;
var opp:Number = v[col][row].y - _motionGuide.y;
var d:Number = Math.sqrt(opp * opp + adj * adj);
if (d < PEN_SIZE) {
if (d < 4) d = PEN_SIZE;
var mod:Number = PEN_SIZE / d;
v[col][row].xvel += _mouseXvel * mod;
v[col][row].yvel += _mouseYvel * mod;
}
v[col][row].xvel *= 0.98;
v[col][row].yvel *= 0.98;
}
}
_pHolder.graphics.clear();
var i:int = NUM_PARTICLES;
while(i--) {
var p:Particle = _particles[i];
p.update();
var dx:Number = p.tx - p.x;
var dy:Number = p.ty - p.y;
d = (dx * dx + dy * dy);
var lim:Number = Math.random() ;
if (d < lim) {
p.tx = p.x + lim;
p.ty = p.y + lim;
}
p.color = d * 50 << 16 | d * 10 << 8 | 0xFF;
_pHolder.graphics.lineStyle(0, p.color);
_pHolder.graphics.moveTo(p.x, p.y);
_pHolder.graphics.lineTo(p.tx, p.ty);
p.tx = p.x;
p.ty = p.y;
}
_prevGuideX = _motionGuide.x;
_prevGuideY = _motionGuide.y;
}
}
}
/*
____ _ _ _
| _ \ __ _ _ __| |_(_) ___| | ___
| |_) / _` | '__| __| |/ __| |/ _ \
| __/ (_| | | | |_| | (__| | __/
|_| \__,_|_| \__|_|\___|_|\___|
*/
class Particle {
public var x:Number;
public var y:Number
public var xvel:Number;
public var yvel:Number;
public var pos:int;
public var tx:Number;
public var ty:Number;
public var color:uint = 0xFFFFFF;
public function Particle(xIn:Number, yIn:Number) {
x = tx = xIn;
y = ty = yIn;
}
public function update():void {
if (x > 0 && x < Main.w && y > 0 && y < Main.h) {
var vi:int = x / Main.RESOLUTION;
var vu:int = y / Main.RESOLUTION;
var ax:Number = (x % Main.RESOLUTION) / Main.RESOLUTION;
var ay:Number = (y % Main.RESOLUTION) / Main.RESOLUTION;
xvel += (1 - ax) * Main.v[vi][vu].xvel * 0.05;
yvel += (1 - ay) * Main.v[vi][vu].yvel * 0.05;
try {
xvel += ax * Main.v[vi+1][vu].xvel * 0.05;
yvel += ax * Main.v[vi+1][vu].yvel * 0.05;
xvel += ay * Main.v[vi][vu + 1].xvel * 0.05;
yvel += ay * Main.v[vi][vu + 1].yvel * 0.05;
} catch (e:Error) { };
x += xvel;
y += yvel;
} else {
x = tx = Math.random() * Main.w;
y = ty = Math.random() * Main.h;
xvel = 0;
yvel = 0;
}
xvel *= 0.5;
yvel *= 0.5;
}
}
/*
__ ______ __ __
\ \ / / __ ) _ _ / _|/ _| ___ _ __
\ \ / /| _ \| | | | |_| |_ / _ \ '__|
\ V / | |_) | |_| | _| _| __/ |
\_/ |____/ \__,_|_| |_| \___|_|
*/
class VBuffer {
public var x:int;
public var y:int;
public var xvel:Number;
public var yvel:Number;
public var pressurex:Number = 0.0;
public var pressurey:Number = 0.0;
public var pressure:Number = 0.0;
public function VBuffer(xIn:int, yIn:int) {
x = xIn;
y = yIn;
}
public function update(i:int, u:int):void {
pressurex = (Main.v[i - 1][u - 1].xvel * 0.5 + Main.v[i - 1][u].xvel + Main.v[i - 1][u + 1].xvel * 0.5 - Main.v[i + 1][u - 1].xvel * 0.5 - Main.v[i + 1][u].xvel - Main.v[i + 1][u + 1].xvel * 0.5);
pressurey = (Main.v[i - 1][u - 1].yvel * 0.5 + Main.v[i][u - 1].yvel + Main.v[i + 1][u - 1].yvel * 0.5 - Main.v[i - 1][u + 1].yvel * 0.5 - Main.v[i][u + 1].yvel - Main.v[i + 1][u + 1].yvel * 0.5);
pressure = (pressurex + pressurey) * 0.25;
}
}
/*
__ ______
\ \ / / ___| __ _ _ _ __ _ _ __ ___
\ \ / /\___ \ / _` | | | |/ _` | '__/ _ \
\ V / ___) | (_| | |_| | (_| | | | __/
\_/ |____/ \__, |\__,_|\__,_|_| \___|
|_|
*/
class VSquare {
public var x:int;
public var y:int;
public var xvel:Number = 0.0;
public var yvel:Number = 0.0;
public var col:Number;
public function VSquare(xIn:int, yIn:int) {
x = xIn;
y = yIn;
}
public function addBuffer(i:int, u:int):void {
if (i > 0 && i < Main.cols && u > 0 && u < Main.rows) {
xvel += (Main.vbuf[i-1][u-1].pressure * 0.5
+ Main.vbuf[i-1][u].pressure
+ Main.vbuf[i-1][u+1].pressure * 0.5
- Main.vbuf[i+1][u-1].pressure * 0.5
- Main.vbuf[i+1][u].pressure
- Main.vbuf[i+1][u+1].pressure * 0.5
) * 0.25;
yvel += (Main.vbuf[i-1][u-1].pressure * 0.5
+ Main.vbuf[i][u-1].pressure
+ Main.vbuf[i+1][u-1].pressure * 0.5
- Main.vbuf[i-1][u+1].pressure * 0.5
- Main.vbuf[i][u+1].pressure
- Main.vbuf[i+1][u+1].pressure * 0.5
) * 0.25;
}
}
}
/*
_____ _
|_ _|_ __ __ _ ___| | __ ___ _ __
| | | '__/ _` |/ __| |/ // _ \ '__|
| | | | | (_| | (__| <| __/ |
|_| |_| \__,_|\___|_|\_\\___|_|
Basic motion tracker - very roughly based on code by Soulwire ( http://blog.soulwire.co.uk/ )
*/
import flash.display.BlendMode;
import flash.filters.BlurFilter;
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.media.Video;
class Tracker {
public var _previous:BitmapData;
public var _current:BitmapData;
public var x:Number;
public var y:Number;
private var _blur:BlurFilter = new BlurFilter(32, 32);
private var _vid:Video;
private var _mirror:Matrix;
private var _point:Point = new Point();
private var _area:Rectangle;
private var _isMotion:Boolean = false;
public function Tracker(vid:Video) {
_vid = vid;
_mirror = new Matrix();
_mirror.scale( -1, 1);
_mirror.translate(_vid.width, 0);
_current = new BitmapData(_vid.width, _vid.height, false, 0x000000);
_previous = _current.clone();
}
public function track():void {
_current.draw(_vid, _mirror);
_current.draw(_previous, null, null, BlendMode.DIFFERENCE);
_current.applyFilter(_current, _current.rect, _point, _blur);
_current.threshold(_current, _current.rect, _point, ">", 0xFF333333, 0xFFFFFFFF);
_previous.draw(_vid, _mirror);
_area = _current.getColorBoundsRect(0xFFFFFFFF, 0xFFFFFFFF, true);
_isMotion = ( _area.width > ( _vid.width / 100) * 10 || _area.height > (_vid.height / 100) * 10 );
if ( _isMotion ) {
x = _area.x + (_area.width * .5);
y = _area.y + (_area.width * .5);
}
}
}
/*
_____ ____ _ _
|_ _|___ _ __ ___ / ___(_)_ __ ___| | ___
| | / _ \| '_ \ / _ \ | | | '__/ __| |/ _ \
| || (_) | | | | __/ |___| | | | (__| | __/
|_| \___/|_| |_|\___|\____|_|_| \___|_|\___|
*/
import flash.display.Shape;
class ToneCircle extends Shape {
private var _tone:Tone;
public var played:Boolean = false;
public function ToneCircle(freq:Number) {
_tone = new Tone(freq);
graphics.lineStyle(0, 0xFFFFFF, .25);
graphics.drawCircle(0, 0, 50);
}
public function playTone():void {
_tone.play();
played = true;
}
}
/*
_____
|_ _|___ _ __ ___
| | / _ \| '_ \ / _ \
| || (_) | | | | __/
|_| \___/|_| |_|\___|
from Keith Peters (aka Bit-101 ) ( http://www.bit-101.com/blog/?p=2681 )
*/
import flash.media.Sound;
import flash.events.SampleDataEvent;
import flash.events.Event;
class Tone {
protected const RATE:Number = 44100;
protected const PI2:Number = Math.PI * 2;
protected var _position:int = 0;
protected var _sound:Sound;
protected var _numSamples:int = 2048;
protected var _samples:Vector.<Number>;
protected var _isPlaying:Boolean = false;
protected var _frequency:Number;
public function Tone(frequency:Number) {
_frequency = frequency;
_sound = new Sound();
_sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
_samples = new Vector.<Number>();
createSamples();
}
protected function createSamples():void {
var amp:Number = 1.0;
var i:int = 0;
var mult:Number = frequency / RATE * PI2;
while(amp > 0.001) {
_samples[i] = Math.sin(i * mult) * amp;
amp *= 0.99993;
i++;
}
_samples.length = i;
}
public function play():void {
if(!_isPlaying){
_position = 0;
_sound.play();
_isPlaying = true;
}
}
protected function onSampleData(event:SampleDataEvent):void {
for (var i:int = 0; i < _numSamples; i++){
if(_position >= _samples.length){
_isPlaying = false;
return;
}
event.data.writeFloat(_samples[_position]);
event.data.writeFloat(_samples[_position]);
_position++;
}
}
public function set frequency(value:Number):void{
_frequency = value;
createSamples();
}
public function get frequency():Number{
return _frequency;
}
}