Forráskód Böngészése

added music worker

ncannasse 11 éve
szülő
commit
21aab5e07d
2 módosított fájl, 342 hozzáadás és 0 törlés
  1. 112 0
      hxd/Worker.hx
  2. 230 0
      hxd/res/MusicWorker.hx

+ 112 - 0
hxd/Worker.hx

@@ -0,0 +1,112 @@
+package hxd;
+
+class Worker<T:EnumValue> {
+
+	public static var ENABLE = true;
+	var sendChan : flash.system.MessageChannel;
+	var recvChan : flash.system.MessageChannel;
+	var enumValue : Enum<T>;
+	var isWorker : Bool;
+	var queue : Array<Dynamic>;
+	var curMessage : { code : Int, count : Int, args : Array<Dynamic> };
+	var debugPeer : Worker<T>;
+
+	public function new( e : Enum<T> ) {
+		this.enumValue = e;
+	}
+
+	function clone() : Worker<T> {
+		throw "Not implemented";
+		return null;
+	}
+
+	public function send( msg : T ) {
+		if( !ENABLE ) {
+			haxe.Timer.delay(debugPeer.handleMessage.bind(msg), 1);
+			return;
+		}
+		var args = Type.enumParameters(msg);
+		sendRaw( { code : Type.enumIndex(msg), count : args.length } );
+		// send args as separate messages or else bytearrays will get copied
+		for( a in args )
+			sendRaw(a);
+	}
+
+	function sendRaw( v : Dynamic ) {
+		if( queue != null )
+			queue.push(v);
+		else
+			sendChan.send(v);
+	}
+
+	function readMessage() : T {
+		if( curMessage == null ) {
+			curMessage = recvChan.receive();
+			curMessage.args = [];
+			if( curMessage.count > 0 )
+				return null;
+		} else {
+			curMessage.args.push(recvChan.receive());
+			if( --curMessage.count > 0 )
+				return null;
+		}
+		var m = Type.createEnumIndex(enumValue, curMessage.code, curMessage.args);
+		curMessage = null;
+		return m;
+	}
+
+	function handleMessage( msg : T ) {
+		throw "TODO";
+	}
+
+	function setupWorker() {
+	}
+
+	public function start() {
+		if( !ENABLE ) {
+			isWorker = false;
+			debugPeer = clone();
+			debugPeer.isWorker = true;
+			debugPeer.setupWorker();
+			debugPeer.debugPeer = this;
+			return false;
+		}
+		var cur = flash.system.Worker.current;
+		if( cur.isPrimordial ) {
+			var wait = true;
+			var bgWorker = flash.system.WorkerDomain.current.createWorker(flash.Lib.current.loaderInfo.bytes);
+			sendChan = cur.createMessageChannel(bgWorker);
+			recvChan = bgWorker.createMessageChannel(cur);
+			recvChan.addEventListener(flash.events.Event.CHANNEL_MESSAGE, function(_) {
+				if( queue != null ) {
+					recvChan.receive(true); // ignore
+					for( m in queue )
+						sendChan.send(m);
+					queue = null;
+					return;
+				}
+				var msg = readMessage();
+				if( msg != null ) handleMessage(msg);
+			});
+			bgWorker.setSharedProperty("send", sendChan);
+			bgWorker.setSharedProperty("recv", recvChan);
+			bgWorker.start();
+			isWorker = false;
+			queue = [];
+			return false;
+		} else {
+			// inverse
+			sendChan = cur.getSharedProperty("recv");
+			recvChan = cur.getSharedProperty("send");
+			recvChan.addEventListener(flash.events.Event.CHANNEL_MESSAGE, function(e) {
+				var msg = readMessage();
+				if( msg != null ) handleMessage(msg);
+			});
+			isWorker = true;
+			setupWorker();
+			sendChan.send(0);
+			return true;
+		}
+	}
+
+}

+ 230 - 0
hxd/res/MusicWorker.hx

@@ -0,0 +1,230 @@
+package hxd.res;
+
+enum MusicMessage {
+	Play( path : String, volume : Float );
+	SetVolume( id : Int, volume : Float );
+	Fade( id : Int, volume : Float, time : Float );
+	Stop( id : Int );
+	Queue( id : Int, ?next : Int );
+	Loop( id : Int, b : Bool );
+	EndLoop( id : Int );
+}
+
+class Channel {
+	var w : MusicWorker;
+	var id : Int;
+	var snd : flash.media.Sound;
+	var samples : Int;
+	var position : Int;
+	var vol : Float;
+	var volumeTarget : Float;
+	var volumeSpeed : Float;
+	public var loop(default, set) : Bool;
+	public var next(default,set) : Channel;
+	public var volume(default, set) : Float;
+
+	public function new(id, v) {
+		this.vol = volume = v;
+		this.id = id;
+		loop = true;
+		volumeSpeed = 0;
+		w = MusicWorker.inst;
+		if( @:privateAccess w.isWorker ) w = null;
+	}
+
+	function set_next( c : Channel ) {
+		if( w != null ) {
+			if( c != null && c.id <= id ) throw "Must queue a channel created after";
+			w.send(Queue(id, c == null ? null : c.id));
+		}
+		return next = c;
+	}
+
+	public function fadeTo( volume : Float, time : Float = 1. ) {
+		var old = w;
+		w = null;
+		this.volume = volume;
+		w = old;
+		if( w != null ) w.send(Fade(id, volume, time));
+	}
+
+	public function stop() {
+		if( w != null ) {
+			w.send(Stop(id));
+			w.channels.remove(this);
+			w.cmap.remove(id);
+			w = null;
+		}
+	}
+
+	function set_loop(b) {
+		if( w != null ) w.send(Loop(id, b));
+		return loop = b;
+	}
+
+	function set_volume(v) {
+		volume = v;
+		if( w != null ) w.send(SetVolume(id, v));
+		return v;
+	}
+
+	public dynamic function onEnd() {
+	}
+
+}
+
+@:access(hxd.res.Channel)
+@:allow(hxd.res.Channel)
+class MusicWorker extends Worker<MusicMessage> {
+
+	var channelID = 1;
+	var channels : Array<Channel> = [];
+	var cmap : Map<Int,Channel> = new Map();
+	var snd : flash.media.Sound;
+	var channel : flash.media.SoundChannel;
+	var tmpBuf : haxe.io.Bytes;
+	var out : haxe.ds.Vector<Float>;
+	static inline var BUFFER_SIZE = 4096;
+
+	public function new() {
+		super(MusicMessage);
+	}
+
+	override function clone() {
+		return new MusicWorker();
+	}
+
+	function makeChannel( volume : Float ) {
+		var c = new Channel(channelID++, volume);
+		channels.push(c);
+		cmap.set(c.id, c);
+		return c;
+	}
+
+	override function handleMessage( msg : MusicMessage ) {
+		switch( msg ) {
+		case Play(path, volume):
+			var c = makeChannel(volume);
+			var bytes = hxd.Res.loader.load(path).entry.getBytes();
+			c.snd = new flash.media.Sound();
+			c.snd.loadCompressedDataFromByteArray(bytes.getData(), bytes.length);
+
+			var mp = new format.mp3.Reader(new haxe.io.BytesInput(bytes)).read();
+			c.samples = mp.sampleCount;
+			var frame = mp.frames[0].data.toString();
+			// http://gabriel.mp3-tech.org/mp3infotag.html
+			var lame = frame.indexOf("LAME", 32 + 120);
+			if( lame >= 0 ) {
+				var startEnd = (frame.charCodeAt(lame + 21) << 16) | (frame.charCodeAt(lame + 22) << 8) | frame.charCodeAt(lame + 23);
+				var start = startEnd >> 12;
+				var end = startEnd & ((1 << 12) - 1);
+				c.samples -= start + end + 1152; // first frame is empty
+			}
+		case SetVolume(id, volume):
+			var c = cmap.get(id);
+			if( c == null ) return;
+			c.vol = volume;
+			c.volumeSpeed = 0;
+		case Stop(id):
+			var c = cmap.get(id);
+			if( c == null ) return;
+			channels.remove(c);
+			cmap.remove(c.id);
+		case Fade(id, vol, time):
+			var c = cmap.get(id);
+			if( c == null ) return;
+			c.volumeTarget = vol;
+			c.volumeSpeed = (vol - c.vol) / (time * 88200);
+		case Queue(id, tid):
+			var c = cmap.get(id);
+			if( c == null ) return;
+			var c2 = cmap.get(tid);
+			c.next = c2;
+		case Loop(id, b):
+			var c = cmap.get(id);
+			if( c == null ) return;
+			c.loop = b;
+		case EndLoop(id):
+			var c = cmap.get(id);
+			if( c != null ) c.onEnd();
+		}
+	}
+
+	override function setupWorker() {
+		tmpBuf = haxe.io.Bytes.alloc(BUFFER_SIZE * 4 * 2);
+		out = new haxe.ds.Vector(BUFFER_SIZE * 2);
+		snd = new flash.media.Sound();
+		snd.addEventListener(flash.events.SampleDataEvent.SAMPLE_DATA, onSample);
+		channel = snd.play(0, 0x7FFFFFFF);
+	}
+
+	function onSample( e:flash.events.SampleDataEvent ) {
+		for( i in 0...BUFFER_SIZE*2 )
+			out[i] = 0;
+		for( c in channels ) {
+
+			if( c.vol <= 0 && c.volumeSpeed <= 0 ) continue;
+			if( !c.loop && c.position == c.samples ) continue;
+
+			var w = 0;
+			while( true ) {
+				var MAGIC_DELAY = 2257;
+				var size = BUFFER_SIZE - (w >> 1);
+				if( size == 0 ) break;
+				var tmpBytes = tmpBuf.getData();
+				tmpBytes.position = 0;
+				if( c.position + size >= c.samples ) {
+					size = c.samples - c.position;
+					c.snd.extract(tmpBytes, size, c.position + MAGIC_DELAY);
+					c.position = 0;
+				} else {
+					c.snd.extract(tmpBytes, size, c.position + MAGIC_DELAY);
+					c.position += size;
+				}
+				tmpBytes.position = 0;
+				for( i in 0...size * 2 ) {
+					out[w++] += tmpBytes.readFloat() * c.vol;
+					if( c.volumeSpeed != 0 ) {
+						c.vol += c.volumeSpeed;
+						if( (c.volumeSpeed > 0) == (c.vol > c.volumeTarget) ) {
+							c.vol = c.volumeTarget;
+							c.volumeSpeed = 0;
+						}
+					}
+				}
+				if( c.position == 0 ) {
+					send(EndLoop(c.id));
+					if( c.next != null ) {
+						c.next.vol = c.vol;
+						c.next.volumeSpeed = c.volumeSpeed;
+						c.next.volumeTarget = c.volumeTarget;
+						c.volume = 0;
+						c.volumeSpeed = 0;
+						break;
+					}
+					if( !c.loop ) {
+						c.position = c.samples;
+						break;
+					}
+				}
+			}
+		}
+		var bytes = e.data;
+		bytes.position = 0;
+		for( i in 0...BUFFER_SIZE * 2 )
+			bytes.writeFloat(out[i]);
+	}
+
+	public static function play( music : Sound, volume = 1. ) {
+		inst.send(Play(music.entry.path, volume));
+		return inst.makeChannel(volume);
+	}
+
+	static var inst : MusicWorker;
+
+	public static function init() {
+		inst = new MusicWorker();
+		return inst.start();
+	}
+
+}