// forked from Event's Instrument /* 10月のJAMの課題 「視覚的にも楽しめる楽器アプリをつくってください。」に挑戦。 Sound with footprint. 足跡に合わせて音が鳴ります。 クリック&ドラッグで休符を置けます。 */ package { import flash.display.Loader; import flash.display.Sprite; import gs.*; [SWF(width="465",height="465",backgroundColor="0",frameRate="60")] public class FlashTest extends Sprite { private var loaders:Vector.<Loader>; private var data:Data; public function FlashTest() { loaders = Data.load(); //画像のロード開始 var nowload:NowLoading = new NowLoading(stage,init); //ロード画面の表示。ロード終了後init()を実行させる。 for each(var loader:Loader in loaders){ nowload.addLoader(loader); } //ロード画面と画像のロードの状態をリンクさせる } private function init():void{ stage.removeChildAt(1); addChild( new DanceHole(465,465) ) } } } import flash.filters.BlurFilter; import flash.geom.*; import flash.system.LoaderContext; import flash.events.Event; import flash.display.*; import flash.net.*; import frocessing.color.ColorHSV; import org.si.sound.events.*; import gs.*; import gs.easing.*; class DanceHole extends Bitmap{ private var black:BitmapData; public var stop:Boolean = false; public var count:int; public var footCount:int = 0; public var prints:Vector.<Print> = new Vector.<Print>; public var data:Data; public var footX:int = 0; public var footY:int = 0; public var step:int = 60; //歩幅 public var pace:int = 1; public var color:ColorHSV = new ColorHSV(0,0.9,1,0.1); public var filter:BlurFilter = new BlurFilter(2,2); private var isDown:Boolean = false function DanceHole(w:int,h:int){ this.addEventListener("addedToStage",init); black = new BitmapData(w,h,true,0xFF000000); data = new Data(); super( black.clone() ); } private function init(e:Event):void{ this.removeEventListener("addedToStage",init); this.addEventListener("exitFrame",onFrame); SiON.driver.dm.addEventListener(SoundObjectEvent.ENTER_FRAME ,onBeat); stage.addEventListener("mouseMove",onMove); stage.addEventListener("mouseDown",onDown); stage.addEventListener("mouseUp",onUp); footX = mouseX; footY = mouseY; } private function onFrame(e:Event):void{ if(!stop){ draw(); SiON.driver.bpm = 100 + prints.length*3; } } private function onMove(e:Event):void{ if(!stop){ var dx:Number = mouseX - footX; var dy:Number = mouseY - footY; var r:Number = Math.sqrt(dx*dx+dy*dy); var dir:Number = Math.atan2(dy,dx); while( r > step){ footX += step * Math.cos(dir); footY += step * Math.sin(dir); prints.push( new Print(footX,footY,dir,color,footCount++%2==0?"l":"r", isDown) ); r -= step; } } } private function onUp(e:Event):void{isDown = false} private function onDown(e:Event):void{isDown = true;} private function onBeat(e:Event):void{ if(!stop){ count++; color.h += 10; sequence(); } } private function sequence():void{ var s:Boolean = true if( count > pace ){ count = 0 } if( count < 1 ){ if(s && prints.length > 0){ prints.reverse(); var p:Print = prints.pop(); prints.reverse(); var ct:ColorTransform = p.colorTransform; ct.alphaMultiplier = 1; ct.redOffset += 150; ct.greenOffset += 150; ct.blueOffset += 150; data.draw(bitmapData,"foot",p.name,p.x,p.y,p.angle,p.colorTransform); if(!p.mute){ SiON.ar(p.x/465, p.y/465, p.color, p.name == "l"); }else{ SiON.stop();} }else{ SiON.stop(); } } } private function draw():void{ var b:BitmapData = bitmapData; for each(var p:Print in prints){ if(!p.mute){ data.draw(b,"foot",p.name,p.x,p.y,p.angle,p.colorTransform); } } b.unlock(); b.lock(); b.merge(black,b.rect,new Point,20,20,20,10); b.applyFilter(b,b.rect,new Point,filter); } } class Print{ public var x:Number,y:Number,angle:Number; public var color:uint, colorTransform:ColorTransform, name:String, mute:Boolean; function Print(x:Number,y:Number,dir:Number,color:ColorHSV,name:String,mute:Boolean){ this.x = x; this.y = y; this.angle = ( dir - Math.PI/2) ; this.color = color.value; this.colorTransform= new ColorTransform(1,1,1,0.8,color.r,color.g,color.b,0); this.name = name; this.mute = mute } } class Data{ static public const URL:Object = { "manFoot":"http://assets.wonderfl.net/images/related_images/4/4f/4fd0/4fd0652e356d7e09b373d8de9784a51732284d90" } static public const URL_NAME:Array=["manFoot"]; static public const IMG_NAME:Object = { "foot":["l","r"] } static public const cellWidth:int=27,cellHeight:int=27; //各オブジェクトに使うビットマップデータの設定 public var MAP_SET:Object = { "foot":"manFoot" } //ビットマップを記録したオブジェクト。ロード後に使用可能 static public var imageMap:Object = {}; static public var imageCell:Object = {}; static public var imageRect:Object = {}; static private var loaders:Vector.<Loader> = new Vector.<Loader>(); //画像をロード。ローダーの配列を作る。 static public function load():Vector.<Loader>{ for each(var url:String in URL){ var loader:Loader = new Loader(); loaders.push(loader); } loaders[0].load(new URLRequest(URL[URL_NAME[0]]), new LoaderContext(true)); loaders[0].contentLoaderInfo.addEventListener("complete",onLoad,false,1000); return loaders; } //bitmapdataに画像を描画する public function draw(target:BitmapData,type:String,name:String,x:int,y:int,angle:Number,ct:ColorTransform):void{ var map:String = MAP_SET[type]; var b:BitmapData = imageCell[map][ IMG_NAME[type].indexOf(name) ]; if(-1 < IMG_NAME[type].indexOf(name)){ var mtr:Matrix = new Matrix(1,0,0,-1,-(b.width>>1),-(b.height>>1)); mtr.rotate( angle ); mtr.translate( x, y ); target.draw( b, mtr, ct ); } } static private var loadNum:int = 0; static private function onLoad(e:Event):void{ e.currentTarget.removeEventListener("complete",onLoad); var rect:Rectangle = e.currentTarget.content.getRect(e.currentTarget.content); imageMap[URL_NAME[loadNum]]=new BitmapData(rect.width,rect.height,true,0x000000); imageMap[URL_NAME[loadNum]].draw( e.currentTarget.content ); imageMap[URL_NAME[loadNum]].lock(); setImageRect(URL_NAME[loadNum]); loadNum++; if(URL_NAME.length>loadNum){ loaders[loadNum].load(new URLRequest(URL[URL_NAME[loadNum]]), new LoaderContext(true)); loaders[loadNum].contentLoaderInfo.addEventListener("complete",onLoad,false,1000); } } static private function setImageRect(name:String):void{ imageCell[name] = []; var map:BitmapData = imageMap[name]; var lineColor:uint = map.getPixel32(map.width-1,map.height-1); var x:int = 0; var y:int=0; var height:int=0; var width:int=0; var count:int=0; while(true){ width=0;height=0; if(lineColor != map.getPixel32(x,y) ){ for(var i:int=1;i+x<map.width;i++){ if( lineColor == map.getPixel32(x+i,y) ){break;} } width=i; for(var j:int=1;j+y<map.width;j++){ if( lineColor == map.getPixel32(x,y+j) ){break;} } height=j; var rect:Rectangle = new Rectangle(x,y,width,height); var rect2:Rectangle = new Rectangle(0,0,width,height); var cell:BitmapData = new BitmapData(rect.width,rect.height,true,0x0) cell.setVector( rect2,map.getVector( rect ) ); imageCell[name].push( cell ); } x+=width+1; if(x>=map.width){ y+=height+1;x=0; } if(y>=map.height){ break; } count++; } } } class NowLoading extends Sprite{ static public const COMPLETE:String = "complete"; public var loaders:Vector.<Object> = new Vector.<Object>; public var bytesTotal:uint=0,bytesLoaded:uint=0; private var _loaderNum:uint=0,_completedNum:uint=0,_openNum:uint=0; //ローダーの数 private var text:Bitmap, sprite:ProgressSprite; private var onLoaded:Function; private var LETTER:Object = {//文字 "1":[[0,1,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1]],"2":[[1,1,1],[0,0,1],[0,1,1],[1,0,0],[1,1,1]],"3":[[1,1,1],[0,0,1],[1,1,1],[0,0,1],[1,1,1]],"4":[[1,0,1],[1,0,1],[1,0,1],[1,1,1],[0,0,1]],"5":[[1,1,1],[1,0,0],[1,1,1],[0,0,1],[1,1,1]], "6":[[1,1,1],[1,0,0],[1,1,1],[1,0,1],[1,1,1]],"7":[[1,1,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1]],"8":[[1,1,1],[1,0,1],[1,1,1],[1,0,1],[1,1,1]],"9":[[1,1,1],[1,0,1],[1,1,1],[0,0,1],[0,0,1]],"0":[[1,1,1],[1,0,1],[1,0,1],[1,0,1],[1,1,1]], ".":[[0],[0],[0],[0],[1]]," ":[[0],[0],[0],[0],[0]],"n":[[0,0,0],[0,0,0],[1,1,1],[1,0,1],[1,0,1]],"w":[[0,0,0,0,0],[0,0,0,0,0],[1,0,1,0,1],[1,0,1,0,1],[1,1,1,1,1]],"o":[[0,0,0],[0,0,0],[1,1,1],[1,0,1],[1,1,1]], "a":[[0,0,0],[0,0,1],[1,1,1],[1,0,1],[1,1,1]],"l":[[1],[1],[1],[1],[1]],"i":[[1],[0],[1],[1],[1]],"d":[[0,0,1],[0,0,1],[1,1,1],[1,0,1],[1,1,1]],"g":[[0,0,0],[0,0,0],[1,1,1],[1,0,1],[1,1,1],[0,0,1],[1,1,1]], "C":[[1,1,1],[1,0,0],[1,0,0],[1,0,0],[1,1,1]],"O":[[1,1,1],[1,0,1],[1,0,1],[1,0,1],[1,1,1]],"M":[[1,1,1,1,1],[1,0,1,0,1],[1,0,1,0,1],[1,0,1,0,1],[1,0,1,0,1]],"P":[[1,1,1],[1,0,1],[1,1,1],[1,0,0],[1,0,0]], "T":[[1,1,1],[0,1,0],[0,1,0],[0,1,0],[0,1,0]],"L":[[1,0,0],[1,0,0],[1,0,0],[1,0,0],[1,1,1]],"E":[[1,1,1],[1,0,0],[1,1,1],[1,0,0],[1,1,1]] } //ステージと関数を渡す public function NowLoading(stage:Stage, onLoaded:Function = null){ if(onLoaded == null){ this.onLoaded=nullFunc }else{ this.onLoaded=onLoaded } sprite = new ProgressSprite(stage.stageWidth,stage.stageHeight); text = new Bitmap( new BitmapData(30*4,8,true,0x00000000 ) ); stage.addChild(this); addChild(sprite); addChild(text); with(text){scaleX=scaleY=1; blendMode="invert"; x=stage.stageWidth-text.width; y=stage.stageHeight-text.height;} } //ローダーの追加 public function addLoader(loader:Loader):Loader{ setListener(loader.contentLoaderInfo);_loaderNum++;return loader;} public function addURLLoader(loader:URLLoader):URLLoader{setListener(loader); _loaderNum++; return loader;} private function nullFunc():void{} private function setListener(loader:*):void{ loader.addEventListener("open", onOpen); loader.addEventListener("complete", onComplete); loader.addEventListener("progress", update); } private function update(e:Event=null):void{ bytesLoaded=0; bytesTotal=0; for each(var loadObj:Object in loaders){ bytesLoaded += loadObj.bytesLoaded; bytesTotal += loadObj.bytesTotal; }; sprite.progress(bytesLoaded/bytesTotal * _openNum/_loaderNum); if(bytesTotal!=0){ setText( "now loading... "+(bytesLoaded/bytesTotal* _openNum/_loaderNum*100).toFixed(1) ); } } private function onOpen(e:Event):void{ _openNum++;loaders.push(e.currentTarget); bytesTotal+=e.currentTarget.bytesTotal; } private function onComplete(e:Event):void{ _completedNum++;if(_loaderNum == _completedNum){ setText( "COMPLETE" );onLoaded(); } } private function setText(str:String):void{ var b:BitmapData = text.bitmapData; var l:int = str.length; var position:int = b.width; b.lock();b.fillRect(b.rect,0x000000); for(var i:int=0;i<l;i++){ var letterData:Array = LETTER[str.substr(l-i-1,1)];position-=letterData[0].length+1; for(var n:int=0;n<letterData.length;n++){ for(var m:int=0;m<letterData[n].length;m++){ if(letterData[n][m]==1){b.setPixel32(m+position,n+1,0xFF000000);} } } } b.unlock(); } } //このスプライトを編集することでロード画面を変えることができる。 class ProgressSprite extends Sprite{ private var mapData:BitmapData,sphereData:BitmapData,noizeData:BitmapData; private var bfRate:Number=0; //前の段階での進行度 private var drawRate:Number=0; private var maxLevel:int = 5; private var meter:Array = new Array(); //コンストラクタ public function ProgressSprite(width:int,height:int):void{ mapData = new BitmapData(width,height,true,0x00000000); addChild(new Bitmap(mapData)).blendMode="invert"; for(var i:int=0;i<maxLevel;i++){ meter[i]=0; } addEventListener("enterFrame",onFrame); } //ロードが進行したときに呼び出される。 rateはロードの進行度で0-1 public function progress(rate:Number):void{ bfRate = rate; } //ロードの進行度に合わせて、描画。 private function draw(rate:Number, level:int=0):void{ var thick:int = mapData.height*(0.61803)/1.61803; var floor:int = 0; for(var i:int=1;i<level+1;i++){ thick*=(0.61803)/1.61803; floor+=thick; } mapData.fillRect( new Rectangle(0,mapData.height-floor,mapData.width*rate,thick), 0x1000000*int(0xFF*(maxLevel-level+1)/(maxLevel))); } private function onFrame(e:Event):void{ for(var i:int=0;i<maxLevel;i++){ var n:int = Math.pow(2,i+2); meter[i]=(bfRate+ meter[i]*(n-1))/n; draw(meter[i],i); } } } //ライブラリ //SiON-音声全般 http://www.libspark.org/wiki/keim/SiON //サンプルコード:http://wonderfl.net/search?q=org.si.sion import org.si.sion.*; import org.si.sion.effector.*; import org.si.sound.*; import org.si.sound.synthesizers.*; class MyDriver extends SiONDriver { public var waveTableSynth:WaveTableSynth; public var dm:DrumMachine = new DrumMachine(3, 8, 3, 2, 2, 2); public var ar:Arpeggiator = new Arpeggiator(); public var fill:SiONData; function MyDriver():void{ super(); volume = 1; play(); bpm = 240; dm.volume = 0.0; dm.snareVolume = 0.6; dm.bassVolume = 0.8; dm.play(); ar.volume = 0.8; ar.noteLength = 1; ar.gateTime = 1; ar.effectSend1 = ar.volume * 0.4; ar.effectSend2 = ar.volume * 0.5; ar.effectors = [new SiEffectStereoDelay()]; waveTableSynth = new WaveTableSynth(); waveTableSynth.color = 0x1203acff; waveTableSynth.releaseTime = 0.2; ar.synthesizer = waveTableSynth; ar.play(); } } class SiON{ //SiONDriverは一つしか作れないので、staticで作る。 static public var driver:MyDriver = new MyDriver(); static public function ar(x:Number,y:Number,color:int,left:Boolean):void{ driver.ar.pattern = left ? [0,-8] : [-8, 0]; driver.pan = x*2.4 - 1.2; driver.ar.scaleIndex = - y * 20 + 10; driver.waveTableSynth.color = color >> 4; TweenLite.to(driver.ar, 0, {volume:0.8} ); } static public function stop():void{ TweenLite.to(driver.ar, 4, {volume:0,ease:Expo.easeOut} ); } } Sound with Footprint