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

imported snd2 api (based on openal)

ncannasse 8 жил өмнө
parent
commit
0d053337f0

+ 11 - 0
hxd/res/Sound.hx

@@ -46,6 +46,15 @@ class Sound extends Resource {
 		}
 	}
 
+	#if hl
+
+	public function play( ?loop = false, volume = 1. ) {
+		throw "TODO";
+		return null;
+	}
+
+	#else
+
 	public function play( ?loop = false, volume = 1. ) {
 		lastPlay = haxe.Timer.stamp();
 		return channel = getWorker().play(this, loop, volume);
@@ -72,4 +81,6 @@ class Sound extends Resource {
 		return defaultWorker.start();
 	}
 
+	#end
+
 }

+ 4 - 1
hxd/snd/Worker.hx

@@ -1,4 +1,5 @@
 package hxd.snd;
+#if !hl
 
 private enum Message {
 	Play( path : String, loop : Bool, volume : Float, time : Float );
@@ -345,4 +346,6 @@ class Worker extends hxd.Worker<Message> {
 		return w.start() ? null : w;
 	}
 
-}
+}
+
+#end

+ 67 - 0
hxd/snd2/hxd/snd/Channel.hx

@@ -0,0 +1,67 @@
+package hxd.snd;
+
+@:allow(hxd.snd.System)
+class Channel extends ChannelBase {
+	static var ID = 0;
+
+	@:noCompletion public var next     : Channel;
+	@:noCompletion public var nextFree : Channel;
+
+	public var id           (default, null) : Int;
+	public var soundRes     (default, null) : hxd.res.Sound;
+	public var soundData    (default, null) : hxd.snd.Data;
+	public var soundGroup   (default, null) : SoundGroup;
+	public var channelGroup (default, null) : ChannelGroup;
+	public var duration     (default, null) : Float;
+	public var position     (default, set)  : Float;
+
+	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;
+
+
+	private function new() {
+		super();
+		id = ++ID;
+	}
+
+	function set_position(v : Float) {
+		lastStamp = haxe.Timer.stamp();
+		positionChanged = true;
+		return position = v;
+	}
+
+	function set_pause(v : Bool) {
+		if (!v) lastStamp = haxe.Timer.stamp();
+		return pause = v;
+	}
+
+	function init(sound : hxd.res.Sound) {
+		reset();
+		pause     = false;
+		isVirtual = false;
+		loop      = false;
+		lastStamp = haxe.Timer.stamp();
+
+		soundRes  = sound;
+		soundData = sound.getData();
+		duration  = soundData.samples / 44100;
+		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;
+	}
+
+	public function stop() {
+		@:privateAccess System.instance.releaseChannel(this);
+	}
+}

+ 39 - 0
hxd/snd2/hxd/snd/ChannelBase.hx

@@ -0,0 +1,39 @@
+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 function new() {
+		reset();
+	}
+
+	public function getEffect<T:Effect>( etype : Class<T> ) : T {
+		for (e in effects) {
+			var e = Std.instance(e, etype);
+			if (e != null) return e;
+		}
+		return null;
+	}
+
+	public function addEffect<T:Effect>( e : T ) : T {
+		if( e == null ) throw "Can't add null effect";
+		effects.push(e);
+		return e;
+	}
+
+	public function removeEffect( e : Effect ) {
+		effects.remove(e);
+	}
+
+	public function reset() {
+		effects  = [];
+		priority = 0.0;
+		volume   = 1.0;
+		pan      = 0.0;
+		mute     = false;
+	}
+}

+ 10 - 0
hxd/snd2/hxd/snd/ChannelGroup.hx

@@ -0,0 +1,10 @@
+package hxd.snd;
+
+@:allow(hxd.snd.System)
+class ChannelGroup extends ChannelBase {
+	public var name (default, null) : String;
+	private function new(name : String) {
+		super();
+		this.name = name;
+	}
+}

+ 20 - 0
hxd/snd2/hxd/snd/Effect.hx

@@ -0,0 +1,20 @@
+package hxd.snd;
+
+@:allow(audiov2.System)
+class Effect {
+	public var gain (get, set) : Float;
+
+	function get_gain() return 1.0;
+	function set_gain(v) {
+		throw "cannot set the gain on this effect";
+		return v;
+	}
+
+	private function new() { }
+
+	#if hl
+	function apply(channel : Channel, source : openal.AL.Source) {
+	}
+	#end
+
+}

+ 20 - 0
hxd/snd2/hxd/snd/SoundGroup.hx

@@ -0,0 +1,20 @@
+package hxd.snd;
+
+@:allow(hxd.snd.System)
+class SoundGroup {
+	public var name (default, null) : String;
+	public var volume               : Float;
+	public var minDelay             : Float;
+	public var maxAudible           : Int;
+	public var muteFadeSpeed        : Float;
+	public var mono					: Bool;
+
+	private function new(name : String) {
+		this.name  = name;
+		maxAudible = -1;
+		muteFadeSpeed = 0;
+		minDelay = 0.0;
+		volume = 1;
+		mono = false;
+	}
+}

+ 403 - 0
hxd/snd2/hxd/snd/System.hx

@@ -0,0 +1,403 @@
+package hxd.snd;
+
+import openal.AL;
+import openal.ALC;
+
+class System {
+	public static var instance : System;
+
+	public var masterVolume	: Float;
+	public var masterSoundGroup   (default, null) : SoundGroup;
+	public var masterChannelGroup (default, null) : ChannelGroup;
+
+	var soundGroups   : Array<SoundGroup>;
+	var channelGroups : Array<ChannelGroup>;
+	var effects       : Array<Effect>;
+
+	public var listenerPos : h3d.Vector;
+	public var listenerVel : h3d.Vector;
+	public var listenerDir : h3d.Vector;
+	public var listenerUp  : h3d.Vector;
+
+	var usedChannels : Channel;
+	var freeChannels : Channel;
+
+	// ------------------------------------------------------------------------
+	// AL SHIT
+	// ------------------------------------------------------------------------
+
+	var AL_NUM_SOURCES = 16;
+	var AL_NUM_BUFFERS = 256;
+
+	var alListenerOriBytes : haxe.io.Bytes;
+
+	var alDevice      : Device;
+	var alContext     : Context;
+
+	var alBuffers     : Array<Buffer>;
+	var alBufRefs     : Array<Int>;
+	var alFreeBufs    : Array<Int>;
+	var soundToBuf    : Map<hxd.res.Sound, Int>;
+	var bufToSound    : Map<Int, hxd.res.Sound>;
+
+	var alSources     : Array<Source>;
+	var chanToSource  : Map<Channel, Int>;
+	var sourceToChan  : Array<Channel>;
+	var alFreeSources : Array<Int>;
+
+	// ------------------------------------------------------------------------
+
+	private function new() {
+		masterVolume       = 1.0;
+		masterSoundGroup   = new SoundGroup  ("master");
+		masterChannelGroup = new ChannelGroup("master");
+
+		soundGroups   = [masterSoundGroup];
+		channelGroups = [masterChannelGroup];
+		effects       = [];
+
+		listenerPos = new h3d.Vector();
+		listenerVel = new h3d.Vector();
+		listenerDir = new h3d.Vector(0,  0, -1);
+		listenerUp  = new h3d.Vector(0,  1,  0);
+
+		{
+			// al init
+			alDevice  = ALC.openDevice(null);
+			alContext = ALC.createContext(alDevice, null);
+			ALC.makeContextCurrent(alContext);
+
+			var bytes = haxe.io.Bytes.alloc(4 * AL_NUM_SOURCES);
+			AL.genSources(AL_NUM_SOURCES, bytes);
+			alSources = [];
+			for (i in 0...AL_NUM_SOURCES) alSources.push(cast bytes.getInt32(i * 4));
+
+			var bytes = haxe.io.Bytes.alloc(4 * AL_NUM_BUFFERS);
+			AL.genBuffers(AL_NUM_BUFFERS, bytes);
+			alBuffers = [];
+			for (i in 0...AL_NUM_BUFFERS) alBuffers.push(cast bytes.getInt32(i * 4));
+		}
+
+		alBufRefs  = [];
+		alFreeBufs = [];
+
+		for (i in 0...alBuffers.length) {
+			alBufRefs.push(0);
+			alFreeBufs.push(i);
+		}
+
+		sourceToChan  = [];
+		alFreeSources = [];
+		for (i in 0...alSources.length) {
+			alFreeSources.push(i);
+			sourceToChan.push(null);
+		}
+
+		soundToBuf = new Map();
+		bufToSound = new Map();
+		chanToSource = new Map();
+		alListenerOriBytes = haxe.io.Bytes.alloc(4 * 3 * 2);
+	}
+
+	public static function init() {
+		instance = new System();
+	}
+
+	public function shutdown() {
+		for (s in alSources) {
+			var buf : Int = AL.NONE;
+			AL.getSourcei(s, AL.BUFFER, new hl.Ref(buf));
+			if (buf != AL.NONE) AL.sourcei(s, AL.BUFFER, AL.NONE);
+		}
+
+		AL.deleteSources(AL_NUM_SOURCES, hl.Bytes.getArray(alSources));
+		AL.deleteBuffers(AL_NUM_BUFFERS, hl.Bytes.getArray(alBuffers));
+
+		ALC.makeContextCurrent(null);
+		ALC.destroyContext(alContext);
+		ALC.closeDevice(alDevice);
+	}
+
+	public function createSoundGroup(name : String) {
+		var sg = new SoundGroup(name);
+		soundGroups.push(sg);
+		return sg;
+	}
+
+	public function createChannelGroup(name : String) {
+		var cg = new ChannelGroup(name);
+		channelGroups.push(cg);
+		return cg;
+	}
+
+	public function releaseSoundGroup(sg : SoundGroup) {
+		soundGroups.remove(sg);
+	}
+
+	public function releaseChannelGroup(cg : ChannelGroup) {
+		channelGroups.remove(cg);
+	}
+
+	public function createEffect<T:Effect>(etype : Class<T>, ?args : Array<Dynamic>) : T {
+		if (args == null) args = [];
+		var e = Type.createInstance(etype, args);
+		return e;
+	}
+
+	public function releaseEffect(e : Effect) {
+		effects.remove(e);
+	}
+
+	public function playSound(sound : hxd.res.Sound, ?soundGroup : SoundGroup, ?channelGroup : ChannelGroup) {
+		if (soundGroup   == null) soundGroup   = masterSoundGroup;
+		if (channelGroup == null) channelGroup = masterChannelGroup;
+		var c = acquireChannel(sound, soundGroup, channelGroup);
+
+		var index = soundToBuf.get(sound);
+		if (index == null) {
+			index = alFreeBufs.pop();
+			if (index == null) throw "too many buffers";
+			soundToBuf.set(sound, index);
+			fillBuffer(alBuffers[index], c.soundData, soundGroup.mono);
+		} else alFreeBufs.remove(index);
+
+		var oldSound = bufToSound.get(index);
+		if (oldSound != null && oldSound != sound) {
+			soundToBuf.remove(oldSound);
+			alBufRefs[index] = 0;
+		}
+
+		bufToSound.set(index, sound);
+		alBufRefs[index] = alBufRefs[index] + 1;
+
+		return c;
+	}
+
+	public function update() {
+		// update playing channels from sources & release stopped channels
+		for (i in 0...alSources.length) {
+			var c = sourceToChan[i];
+			if (c == null) continue;
+			var state  = 0;
+			var source = alSources[i];
+			AL.getSourcei(source, AL.SOURCE_STATE, new hl.Ref(state));
+			switch (state) {
+				case AL.STOPPED :
+					releaseChannel(c);
+				case AL.PLAYING :
+					if (!c.positionChanged) {
+						var position : hl.F32 = 0.0;
+						AL.getSourcef(source, AL.SEC_OFFSET, new hl.Ref(position));
+						c.position = position;
+						c.positionChanged = false;
+					}
+				default :
+			}
+		}
+
+		// calc audible gain & virtualize inaudible channels
+		var c = usedChannels;
+		while (c != null) {
+			c.isVirtual = false;
+			c.calcAudibleGain();
+			if (c.pause || c.mute || c.channelGroup.mute || c.audibleGain == 0) c.isVirtual = true;
+			c = c.next;
+		}
+
+		// sort channels by priority
+		usedChannels = haxe.ds.ListSort.sortSingleLinked(usedChannels, sortChannel);
+
+		{	// virtualize sounds that puts the put the audible count over the maximum number of sources
+			var sgroupRefs = new Map<SoundGroup, Int>();
+
+			var audibleCount = 0;
+			var c = usedChannels;
+			while (c != null && !c.isVirtual) {
+				if (++audibleCount > alSources.length) c.isVirtual = true;
+				else if (c.soundGroup.maxAudible >= 0) {
+					var sgRefs = sgroupRefs.get(c.soundGroup);
+					if (sgRefs == null) sgRefs = 0;
+					if (++sgRefs > c.soundGroup.maxAudible) {
+						c.isVirtual = true;
+						--audibleCount;
+					}
+					sgroupRefs.set(c.soundGroup, sgRefs);
+				}
+				c = c.next;
+			}
+		}
+
+
+		// free sources that points to virtualized channels
+		for (i in 0...alSources.length) {
+			if (sourceToChan[i] == null || !sourceToChan[i].isVirtual) continue;
+			releaseSource(i);
+		}
+
+		// bind sources to non virtual channels
+		var c = usedChannels;
+		while (c != null) {
+			var srcIndex = chanToSource.get(c);
+			if (srcIndex != null || c.isVirtual) {
+				c = c.next;
+				continue;
+			}
+
+			srcIndex = alFreeSources.pop();
+			if (srcIndex == null) throw "woups " + c.id;
+			chanToSource.set(c, srcIndex);
+			sourceToChan[srcIndex] = c;
+
+			// bind buf & play source
+			var source = alSources[srcIndex];
+			var buffer = alBuffers[soundToBuf.get(c.soundRes)];
+			AL.sourcei(source, AL.BUFFER, buffer);
+			AL.sourcePlay(source);
+			AL.sourcef(source, AL.SEC_OFFSET, c.position);
+			c.positionChanged = false;
+			c = c.next;
+		}
+
+		{	// update listener parameters
+			AL.listenerf(AL.GAIN, masterVolume);
+			AL.listener3f(AL.POSITION, listenerPos.x, listenerPos.y, listenerPos.z);
+
+			alListenerOriBytes.setFloat(0,  listenerDir.x);
+			alListenerOriBytes.setFloat(4,  listenerDir.y);
+			alListenerOriBytes.setFloat(8,  listenerDir.z);
+
+			alListenerOriBytes.setFloat(12, listenerUp.x);
+			alListenerOriBytes.setFloat(16, listenerUp.y);
+			alListenerOriBytes.setFloat(20, listenerUp.z);
+
+			AL.listenerfv(AL.ORIENTATION, alListenerOriBytes);
+		}
+
+		// update source paramaters
+		for (i in 0...alSources.length) {
+			var c = sourceToChan[i];
+			var source = alSources[i];
+			if (c == null) continue;
+
+			if (c.positionChanged) {
+				AL.sourcef(source, AL.SEC_OFFSET, c.position);
+				c.positionChanged = false;
+			}
+
+			AL.sourcei(source, AL.LOOPING, c.loop ? AL.TRUE : AL.FALSE);
+			AL.sourcef(source, AL.GAIN, c.volume * c.channelGroup.volume * c.soundGroup.volume);
+
+			for (e in c.effects) @:privateAccess e.apply(c, source);
+		}
+
+		// update virtual channels
+		var c = usedChannels;
+		while (c != null) {
+			if (!c.pause && c.isVirtual) {
+				c.position += haxe.Timer.stamp() - c.lastStamp;
+				if (!c.loop && c.position >= c.duration)
+					releaseChannel(c);
+			}
+			c = c.next;
+		}
+	}
+
+	// ------------------------------------------------------------------------
+	// internals
+	// ------------------------------------------------------------------------
+
+	function releaseSource(i : Int) {
+		chanToSource.remove(sourceToChan[i]);
+		sourceToChan[i] = null;
+		alFreeSources.push(i);
+
+		var source = alSources[i];
+		AL.sourceStop(source);
+		AL.sourcei(source, AL.BUFFER, AL.NONE);
+	}
+
+	function sortChannel(a : Channel, b : Channel) {
+		if (a.isVirtual != b.isVirtual)
+			return (a.isVirtual && !b.isVirtual) ? 1 : -1;
+
+		if (a.channelGroup.priority != b.channelGroup.priority)
+			return a.channelGroup.priority < b.channelGroup.priority ? 1 : -1;
+
+		if (a.priority != b.priority)
+			return a.priority < b.priority ? 1 : -1;
+
+		if (a.audibleGain != b.audibleGain)
+			return a.audibleGain < b.audibleGain ? 1 : -1;
+
+		return a.initStamp < b.initStamp ? 1 : -1;
+	}
+
+	function acquireChannel(sound : hxd.res.Sound, soundGroup : SoundGroup, channelGroup : ChannelGroup) {
+		var c : Channel = null;
+
+		if (freeChannels == null) {
+			c = new Channel();
+		} else {
+			c = freeChannels;
+			freeChannels = c.nextFree;
+			c.nextFree   = null;
+		}
+
+		c.init(sound);
+
+		c.soundGroup   = soundGroup;
+		c.channelGroup = channelGroup;
+
+		c.next = usedChannels;
+		usedChannels = c;
+
+		return c;
+	}
+
+	function releaseChannel(c : Channel) {
+		if (usedChannels == c) {
+			usedChannels = c.next;
+		} else {
+			var prev = usedChannels;
+			while (prev.next != c)
+				prev = prev.next;
+			prev.next = c.next;
+		}
+
+		var bi = soundToBuf.get(c.soundRes);
+		alBufRefs[bi] = alBufRefs[bi] - 1;
+		if (alBufRefs[bi] == 0) alFreeBufs.unshift(bi);
+
+		c.next       = null;
+		c.nextFree   = freeChannels;
+		freeChannels = c;
+
+		var isrc = chanToSource.get(c);
+		if (isrc != null) releaseSource(isrc);
+	}
+
+	function fillBuffer(buf : Buffer, dat : hxd.snd.Data, ?mono = false) {
+		var nsamples  = dat.samples;
+		var nchannels = mono ? 1 : 2;
+
+		var floatBytes = haxe.io.Bytes.alloc(nsamples * 2 * 4);
+		var shortBytes = haxe.io.Bytes.alloc(nsamples * nchannels * 2);
+
+		dat.decode(floatBytes, 0, 0, nsamples);
+		if (nchannels == 2) for (i in 0...nsamples) {
+			shortBytes.setUInt16(i * 4 + 0, Std.int(floatBytes.getFloat(i * 8 + 0) * 32767));
+			shortBytes.setUInt16(i * 4 + 2, Std.int(floatBytes.getFloat(i * 8 + 4) * 32767));
+		} else for (i in 0...nsamples) {
+			var valL = Std.int(floatBytes.getFloat(i * 8 + 0) * 32767);
+			var valR = Std.int(floatBytes.getFloat(i * 8 + 4) * 32767);
+			shortBytes.setUInt16(i * 2, (valL + valR) >> 1);
+		}
+
+		var format = switch(nchannels) {
+			case 1 : AL.FORMAT_MONO16;
+			case 2 : AL.FORMAT_STEREO16;
+			default : throw "unsupported sound format";
+		}
+		AL.bufferData(buf, format, shortBytes, shortBytes.length, 44100);
+	}
+}

+ 64 - 0
hxd/snd2/hxd/snd/effect/Spatialization.hx

@@ -0,0 +1,64 @@
+package audiov2.effect;
+
+import openal.AL;
+
+@:keep
+class Spatialization extends Effect {
+	public var position  : h3d.Vector;
+	public var velocity  : h3d.Vector;
+	public var direction : h3d.Vector;
+
+	public var referenceDistance : Float;
+	public var maxDistance  : Null<Float>;
+	public var fadeDistance : Null<Float>;
+	public var rollOffFactor : Float;
+
+	private function new() {
+		super();
+		position  = new h3d.Vector();
+		velocity  = new h3d.Vector();
+		direction = new h3d.Vector();
+
+		referenceDistance = 1.0;
+		rollOffFactor =  1.0;
+	}
+
+	function getFadeGain() {
+		var dist = audiov2.System.instance.listenerPos.distance(position);
+		if (maxDistance != null) dist -= maxDistance;
+		else dist -= referenceDistance;
+		var gain = 1 - dist / fadeDistance;
+		if (gain > 1) gain = 1;
+		if (gain < 0) gain = 0;
+		return gain;
+	}
+
+	override function apply(channel : Channel, source : Source) {
+		AL.source3f(source, AL.POSITION,  position.x,  position.y,  position.z);
+		AL.source3f(source, AL.VELOCITY,  velocity.x,  velocity.y,  velocity.z);
+		AL.source3f(source, AL.DIRECTION, direction.x, direction.y, direction.z);
+
+		AL.sourcef(source, AL.REFERENCE_DISTANCE, referenceDistance);
+		AL.sourcef(source, AL.ROLLOFF_FACTOR, rollOffFactor);
+		AL.sourcef(source, AL.MIN_GAIN, 0);
+
+		if (maxDistance != null) {
+			var md : Float = maxDistance;
+			AL.sourcef(source, AL.MAX_DISTANCE, md);
+		}
+
+		if (fadeDistance != null) {
+			var volume = channel.volume * channel.soundGroup.volume * channel.channelGroup.volume;
+			AL.sourcef(source, AL.GAIN, getFadeGain() * volume);
+		}
+	}
+
+	override function get_gain() {
+		var dist = audiov2.System.instance.listenerPos.distance(position);
+		dist = Math.max(dist, referenceDistance);
+		if (maxDistance != null) dist = Math.min(dist, maxDistance);
+		var gain = referenceDistance/(referenceDistance + rollOffFactor * (dist - referenceDistance));
+		if (fadeDistance != null) gain *= getFadeGain();
+		return gain;
+	}
+}