2
0
Эх сурвалжийг харах

some refactoring + started queueSound

Nicolas Cannasse 8 жил өмнө
parent
commit
a155634377

+ 23 - 7
hxd/snd/ALEmulator.hx

@@ -58,6 +58,11 @@ private class ALChannel extends NativeChannel {
 }
 
 class ALSource {
+
+	// Necessary to prevent stopping the channel while it's still playing
+	// This seems related to some lag in NativeChannel creation and data delivery
+	static inline var STOP_DELAY = #if js 200 #else 0 #end;
+
 	static var ID = 0;
 	static var all = new Map<Int,ALSource>();
 
@@ -85,7 +90,10 @@ class ALSource {
 
 	public function stop() {
 		if( chan != null ) {
-			chan.stop();
+			if( STOP_DELAY == 0 )
+				chan.stop();
+			else
+				haxe.Timer.delay(chan.stop, STOP_DELAY);
 			chan = null;
 		}
 	}
@@ -134,10 +142,6 @@ class ALEmulator {
 
 	public static var NATIVE_FREQ : Int = #if js @:privateAccess Std.int(NativeChannel.getContext().sampleRate) #else 44100 #end;
 
-	// Necessary to prevent reporting the channel as stopped too early
-	// This seems related to some lag in NativeChannel creation and data delivery
-	static inline var STOP_DELAY = #if js 0.2 #else 0. #end;
-
 	// api
 
 	public static function dopplerFactor(value : F32) {}
@@ -295,7 +299,19 @@ class ALEmulator {
 	public static function getSourcef(source : Source, param : Int) : F32 {
 		switch( param ) {
 		case SEC_OFFSET:
-			return source.buffer == null ? 0 : (haxe.Timer.stamp() - source.playedTime);
+			if( source.buffer == null )
+				return 0;
+			var now = haxe.Timer.stamp();
+			var t = now - source.playedTime;
+			var maxT = source.buffer.samples / source.buffer.frequency;
+			if( source.loop ) {
+				while( t > maxT ) {
+					t -= maxT;
+					source.playedTime += maxT;
+				}
+			} else if( t > maxT )
+				t = maxT;
+			return t;
 		default:
 			throw "Unsupported param 0x" + StringTools.hex(param);
 		}
@@ -303,7 +319,7 @@ class ALEmulator {
 	public static function getSourcei(source : Source, param : Int) : Int {
 		switch( param ) {
 		case SOURCE_STATE:
-			return !source.playing || source.buffer == null || (!source.loop && (haxe.Timer.stamp() - source.playedTime - STOP_DELAY) >= source.buffer.samples/source.buffer.frequency ) ? STOPPED : PLAYING;
+			return !source.playing || source.buffer == null || (!source.loop && (haxe.Timer.stamp() - source.playedTime) >= source.buffer.samples/source.buffer.frequency ) ? STOPPED : PLAYING;
 		default:
 			throw "Unsupported param 0x" + StringTools.hex(param);
 		}

+ 22 - 20
hxd/snd/Channel.hx

@@ -6,8 +6,8 @@ class Channel extends ChannelBase {
 	@:noCompletion public var next     : Channel;
 	var driver : Driver;
 	var source : Driver.Source;
+	var id : Int;
 
-	public var id           (default, null) : Int;
 	public var sound     	(default, null) : hxd.res.Sound;
 	public var soundGroup   (default, null) : SoundGroup;
 	public var channelGroup (default, null) : ChannelGroup;
@@ -17,17 +17,26 @@ class Channel extends ChannelBase {
 	public var pause (default, set) : Bool;
 	public var loop  : Bool;
 	var audibleGain : Float;
-	var initStamp   : Float;
 	var lastStamp   : Float;
 	var isVirtual   : Bool;
 	var positionChanged : Bool;
+	var queue : Array<hxd.res.Sound> = [];
 
-
-	private function new() {
+	function new() {
 		super();
-		id = ++ID;
+		id = ID++;
+		pause     = false;
+		isVirtual = false;
+		loop      = false;
+		position  = 0.0;
+		audibleGain = 1.0;
 	}
 
+	/**
+		onEnd() is called when a sound which does not loop has finished playing
+		or when we switch buffer in a queue
+		or when a sound which is streamed loops.
+	**/
 	public dynamic function onEnd() {
 	}
 
@@ -42,27 +51,20 @@ class Channel extends ChannelBase {
 		return pause = v;
 	}
 
-	function init(driver, sound : hxd.res.Sound) {
-		reset();
-		this.driver = driver;
-		pause     = false;
-		isVirtual = false;
-		loop      = false;
-		lastStamp = haxe.Timer.stamp();
-
-		this.sound  = sound;
-		duration  = sound.getData().duration;
-		position  = 0.0;
-		audibleGain = 1.0;
-		initStamp = haxe.Timer.stamp();
-	}
-
 	public function calcAudibleGain() {
 		audibleGain = volume * channelGroup.volume * soundGroup.volume;
 		for (e in effects) audibleGain *= e.gain;
 		return audibleGain;
 	}
 
+	/**
+		Add a sound to the queue. When the current sound is finished playing, the next one will seamlessly continue.
+		This will also trigger an onEnd() event.
+	**/
+	public function queueSound( sound : hxd.res.Sound ) {
+		queue.push(sound);
+	}
+
 	public function stop() {
 		if( driver != null ) {
 			@:privateAccess driver.releaseChannel(this);

+ 5 - 13
hxd/snd/ChannelBase.hx

@@ -2,14 +2,13 @@ package hxd.snd;
 
 class ChannelBase {
 
-	public var priority : Float;
-	public var volume   : Float;
-	public var pan      : Float;
-	public var mute     : Bool;
-	public var effects  : Array<Effect>;
+	public var priority : Float = 0.;
+	public var volume   : Float = 1.;
+	public var pan      : Float = 0.;
+	public var mute     : Bool = false;
+	public var effects  : Array<Effect> = [];
 
 	function new() {
-		reset();
 	}
 
 	public function getEffect<T:Effect>( etype : Class<T> ) : T {
@@ -30,11 +29,4 @@ class ChannelBase {
 		effects.remove(e);
 	}
 
-	public function reset() {
-		if( effects == null || effects.length > 0 ) effects  = [];
-		priority = 0.0;
-		volume   = 1.0;
-		pan      = 0.0;
-		mute     = false;
-	}
 }

+ 86 - 22
hxd/snd/Driver.hx

@@ -21,6 +21,13 @@ class Source {
 	public var channel : Channel;
 	public var buffer : Buffer;
 
+	public var loop = false;
+	public var volume = 1.;
+	public var playing = false;
+
+	public var nextSound : hxd.res.Sound;
+	public var nextBuffer : Buffer;
+
 	public function new(inst) {
 		this.inst = inst;
 	}
@@ -35,6 +42,11 @@ class Buffer {
 	public function new(inst) {
 		this.inst = inst;
 	}
+
+	public function unref() {
+		playCount--;
+		if( playCount == 0 ) lastStop = haxe.Timer.stamp();
+	}
 }
 
 @:access(hxd.snd.Channel)
@@ -134,7 +146,9 @@ class Driver {
 		if (soundGroup   == null) soundGroup   = masterSoundGroup;
 		if (channelGroup == null) channelGroup = masterChannelGroup;
 		var c = new Channel();
-		c.init(this, sound);
+		c.driver = this;
+		c.sound = sound;
+		c.duration = c.sound.getData().duration;
 		c.soundGroup   = soundGroup;
 		c.channelGroup = channelGroup;
 		c.next = channels;
@@ -144,6 +158,8 @@ class Driver {
 
 	public function update() {
 		// update playing channels from sources & release stopped channels
+		var now = haxe.Timer.stamp();
+
 		for( s in sources ) {
 			var c = s.channel;
 			if( c == null ) continue;
@@ -154,9 +170,12 @@ class Driver {
 				c.onEnd();
 			case AL.PLAYING:
 				if (!c.positionChanged) {
-					var position= AL.getSourcef(s.inst, AL.SEC_OFFSET);
+					var position = AL.getSourcef(s.inst, AL.SEC_OFFSET);
+					var prev = c.position;
 					c.position = position;
+					c.lastStamp = now;
 					c.positionChanged = false;
+					if( position < prev ) c.onEnd();
 				}
 			default:
 			}
@@ -236,12 +255,9 @@ class Driver {
 			s.channel = c;
 			c.source = s;
 
-			// bind buf & play source
-			setBuffer(s, getBuffer(c));
+			// bind buf and force full sync
+			setBuffer(s, getBuffer(c.sound, c.soundGroup));
 			c.positionChanged = true;
-			syncSource(s);
-			AL.sourcePlay(s.inst);
-
 			c = c.next;
 		}
 
@@ -254,13 +270,15 @@ class Driver {
 
 		// update virtual channels
 		var c = channels;
-		var now = haxe.Timer.stamp();
 		while (c != null) {
 			var next = c.next;
 			if (!c.pause && c.isVirtual) {
 				c.position += now - c.lastStamp;
-				if (!c.loop && c.position >= c.duration)
+				c.lastStamp = now;
+				if( c.position >= c.duration && !queueNext(c) && !c.loop ) {
 					releaseChannel(c);
+					c.onEnd();
+				}
 			}
 			c = next;
 		}
@@ -269,14 +287,57 @@ class Driver {
 	function syncSource( s : Source ) {
 		var c = s.channel;
 		if( c == null ) return;
-		if (c.positionChanged) {
+		if( c.positionChanged ) {
 			AL.sourcef(s.inst, AL.SEC_OFFSET, c.position);
 			c.positionChanged = false;
 		}
-		AL.sourcei(s.inst, AL.LOOPING, c.loop ? AL.TRUE : AL.FALSE);
-		AL.sourcef(s.inst, AL.GAIN, c.volume * c.channelGroup.volume * c.soundGroup.volume);
+		if( s.loop != c.loop ) {
+			s.loop = c.loop;
+			AL.sourcei(s.inst, AL.LOOPING, c.loop ? AL.TRUE : AL.FALSE);
+		}
+		var v = c.volume * c.channelGroup.volume * c.soundGroup.volume;
+		if( s.volume != v ) {
+			s.volume = v;
+			AL.sourcef(s.inst, AL.GAIN, v);
+		}
 		for(e in c.effects)
 			e.apply(c, s);
+		if( !s.playing ) {
+			s.playing = true;
+			AL.sourcePlay(s.inst);
+		}
+
+		// sync queuing
+		var nextSound = c.queue[0];
+		if( s.nextSound != nextSound ) {
+			if( s.nextSound != null ) {
+				tmpBytes.setInt32(0, s.nextBuffer.inst.toInt());
+				AL.sourceUnqueueBuffers(s.inst, 1, tmpBytes);
+				s.nextBuffer.unref();
+				s.nextSound = null;
+				s.nextBuffer = null;
+			}
+			if( nextSound != null ) {
+				s.nextSound = nextSound;
+				s.nextBuffer = getBuffer(nextSound, c.soundGroup);
+				s.nextBuffer.playCount++;
+				tmpBytes.setInt32(0, s.nextBuffer.inst.toInt());
+				AL.sourceQueueBuffers(s.inst, 1, tmpBytes);
+				if( AL.getError() != 0 )
+					throw "Failed to queue buffer : format differs between " + c.sound + " and " + nextSound;
+			}
+		}
+	}
+
+	function queueNext( c : Channel ) {
+		var snd = c.queue.shift();
+		if( snd == null )
+			return false;
+		c.sound = snd;
+		c.position -= c.duration;
+		c.duration = snd.getData().duration;
+		c.positionChanged = false;
+		return true;
 	}
 
 	// ------------------------------------------------------------------------
@@ -285,7 +346,10 @@ class Driver {
 
 	function releaseSource( s : Source ) {
 		s.channel = null;
-		AL.sourceStop(s.inst);
+		if( s.playing ) {
+			s.playing = false;
+			AL.sourceStop(s.inst);
+		}
 		setBuffer(s, null);
 	}
 
@@ -294,8 +358,7 @@ class Driver {
 			return;
 		if( b == null ) {
 			AL.sourcei(s.inst, AL.BUFFER, AL.NONE);
-			s.buffer.playCount--;
-			if( s.buffer.playCount == 0 ) s.buffer.lastStop = haxe.Timer.stamp();
+			s.buffer.unref();
 			s.buffer = null;
 		} else {
 			AL.sourcei(s.inst, AL.BUFFER, b.inst.toInt());
@@ -304,8 +367,8 @@ class Driver {
 		}
 	}
 
-	function getBuffer( c : Channel ) : Buffer {
-		var b = bufferMap.get(c.sound);
+	function getBuffer( snd : hxd.res.Sound, grp : SoundGroup ) : Buffer {
+		var b = bufferMap.get(snd);
 		if( b != null )
 			return b;
 		if( buffers.length >= 256 ) {
@@ -317,11 +380,11 @@ class Driver {
 		}
 		AL.genBuffers(1, tmpBytes);
 		var b = new Buffer(ALBuffer.ofInt(tmpBytes.getInt32(0)));
-		b.sound = c.sound;
+		b.sound = snd;
 		buffers.push(b);
-		bufferMap.set(c.sound, b);
-		var data = c.sound.getData();
-		var mono = c.soundGroup.mono;
+		bufferMap.set(snd, b);
+		var data = snd.getData();
+		var mono = grp.mono;
 		data.load(function() fillBuffer(b, data, mono));
 		return b;
 	}
@@ -347,7 +410,7 @@ class Driver {
 		if (a.audibleGain != b.audibleGain)
 			return a.audibleGain < b.audibleGain ? 1 : -1;
 
-		return a.initStamp < b.initStamp ? 1 : -1;
+		return a.id < b.id ? 1 : -1;
 	}
 
 	function releaseChannel(c : Channel) {
@@ -360,6 +423,7 @@ class Driver {
 			prev.next = c.next;
 		}
 		c.next = null;
+		c.driver = null;
 		if( c.source != null ) {
 			releaseSource(c.source);
 			c.source = null;