Преглед на файлове

added channel streaming support

Nicolas Cannasse преди 8 години
родител
ревизия
0ea5569394
променени са 5 файла, в които са добавени 302 реда и са изтрити 96 реда
  1. 45 28
      hxd/snd/ALEmulator.hx
  2. 15 2
      hxd/snd/Channel.hx
  3. 40 27
      hxd/snd/Data.hx
  4. 185 37
      hxd/snd/Driver.hx
  5. 17 2
      samples/Sound.hx

+ 45 - 28
hxd/snd/ALEmulator.hx

@@ -67,6 +67,7 @@ private class ALChannel extends NativeChannel {
 				curSample += scount;
 			}
 			source.currentSample = baseSample + curSample;
+			if( source.currentSample < 0 ) throw baseSample+"/" + curSample;
 		}
 
 		for( i in 0...count<<1 )
@@ -94,6 +95,7 @@ class ALSource {
 	public var volume : F32 = 1.;
 	public var playing(get, never) : Bool;
 	public var duration : Float;
+	public var frequency : Int;
 
 	public function new() {
 		id = ++ID;
@@ -101,6 +103,7 @@ class ALSource {
 	}
 
 	public function updateDuration() {
+		frequency = buffers.length == 0 ? 1 : buffers[0].frequency;
 		duration = 0.;
 		for( b in buffers )
 			duration += b.samples / b.frequency;
@@ -109,13 +112,15 @@ class ALSource {
 	inline function get_playing() return chan != null;
 
 	public function play() {
-		if( chan == null )
+		if( chan == null ) {
+			playedTime = haxe.Timer.stamp() - currentSample / frequency;
 			chan = new ALChannel(this, 4096 /* 100 ms latency @44.1Khz */);
+		}
 	}
 
-	public function stop() {
+	public function stop( immediate = false ) {
 		if( chan != null ) {
-			if( STOP_DELAY == 0 )
+			if( STOP_DELAY == 0 || immediate )
 				chan.stop();
 			else
 				haxe.Timer.delay(chan.stop, STOP_DELAY);
@@ -140,8 +145,8 @@ class ALBuffer {
 
 	public var id : Int;
 	public var data : haxe.ds.Vector<F32>;
-	public var frequency : Int;
-	public var samples : Int;
+	public var frequency : Int = 1;
+	public var samples : Int = 0;
 
 	public function new() {
 		id = ++ID;
@@ -154,6 +159,12 @@ class ALBuffer {
 		id = 0;
 	}
 
+	public function alloc(size) {
+		if( data == null || data.length != size )
+			data = new haxe.ds.Vector(size);
+		return data;
+	}
+
 	public inline function toInt() return id;
 	public static inline function ofInt(i) return all.get(i);
 
@@ -273,10 +284,14 @@ class ALEmulator {
 	}
 
 	// Set Source parameters
-	public static function sourcef(source : Source, param : Int, value  : F32) {
+	public static function sourcef(source : Source, param : Int, value : F32) {
 		switch( param ) {
 		case SEC_OFFSET:
-			source.currentSample = source.buffers.length == 0 ? 0 : Std.int(value * source.buffers[0].frequency);
+			source.currentSample = source.buffers.length == 0 ? 0 : Std.int(value * source.frequency);
+			if( source.playing ) {
+				source.stop(true);
+				source.play();
+			}
 		case GAIN:
 			source.volume = value;
 		default:
@@ -390,12 +405,12 @@ class ALEmulator {
 	}
 
 	public static function sourcePlay(source : Source) {
-		source.playedTime = haxe.Timer.stamp();
 		source.play();
 	}
 
 	public static function sourceStop(source : Source) {
 		source.stop();
+		source.currentSample = 0;
 	}
 
 	public static function sourceRewind(source : Source) {
@@ -407,24 +422,26 @@ class ALEmulator {
 
 	// Queue buffers onto a source
 	public static function sourceQueueBuffers(source : Source, nb : Int, buffers : Bytes) {
-		var queue = [];
 		for( i in 0...nb ) {
 			var b = Buffer.ofInt(buffers.getInt32(i * 4));
-			queue.push(b);
+			if( b == null ) throw "assert";
+			source.buffers.push(b);
 		}
-		source.buffers = queue;
 		source.updateDuration();
 	}
 
 	public static function sourceUnqueueBuffers(source : Source, nb : Int, buffers : Bytes) {
 		for( i in 0...nb ) {
 			var b = Buffer.ofInt(buffers.getInt32(i * 4));
-			if( b == source.buffers[0] ) {
+			if( b != source.buffers[0] ) throw "assert";
+			if( source.playing ) {
+				if( source.currentSample < b.samples ) throw "assert";
 				source.buffers.shift();
 				source.currentSample -= b.samples;
 				source.playedTime += b.samples / b.frequency;
-				source.updateDuration();
-			}
+			} else
+				source.buffers.shift();
+			source.updateDuration();
 		}
 	}
 
@@ -450,40 +467,40 @@ class ALEmulator {
 		}
 		switch( format ) {
 		case FORMAT_MONO8:
-			buffer.data = new haxe.ds.Vector(size*2);
+			var bdata = buffer.alloc(size*2);
 			for( i in 0...size ) {
 				var v = data.get(i) / 0xFF;
-				buffer.data[i << 1] = v;
-				buffer.data[(i<<1) | 1] = v;
+				bdata[i << 1] = v;
+				bdata[(i<<1) | 1] = v;
 			}
 		case FORMAT_STEREO8:
-			buffer.data = new haxe.ds.Vector(size);
+			var bdata = buffer.alloc(size);
 			for( i in 0...size ) {
 				var v = data.get(i) / 0xFF;
-				buffer.data[i] = v;
+				bdata[i] = v;
 			}
 		case FORMAT_MONO16:
-			buffer.data = new haxe.ds.Vector(size);
+			var bdata = buffer.alloc(size);
 			for( i in 0...size>>1 ) {
 				var v = sext16(data.getUInt16(i << 1)) / 0x8000;
-				buffer.data[i << 1] = v;
-				buffer.data[(i<<1) | 1] = v;
+				bdata[i << 1] = v;
+				bdata[(i<<1) | 1] = v;
 			}
 		case FORMAT_STEREO16:
-			buffer.data = new haxe.ds.Vector(size >> 1);
+			var bdata = buffer.alloc(size >> 1);
 			for( i in 0...size>>1 ) {
 				var v = sext16(data.getUInt16(i << 1)) / 0x8000;
-				buffer.data[i] = v;
+				bdata[i] = v;
 			}
 		case FORMAT_MONOF32:
-			buffer.data = new haxe.ds.Vector(size >> 1);
+			var bdata = buffer.alloc(size >> 1);
 			for( i in 0...size >> 1 ) {
 				var f = data.getFloat(i << 2);
-				buffer.data[i << 1] = f;
-				buffer.data[(i<<1) | 1] = f;
+				bdata[i << 1] = f;
+				bdata[(i<<1) | 1] = f;
 			}
 		case FORMAT_STEREOF32:
-			buffer.data = new haxe.ds.Vector(size >> 2);
+			var bdata = buffer.alloc(size >> 2);
 			#if flash
 			flash.Memory.select(data.getData());
 			#end

+ 15 - 2
hxd/snd/Channel.hx

@@ -14,8 +14,16 @@ class Channel extends ChannelBase {
 	public var duration     (default, null) : Float;
 	public var position     (default, set)  : Float;
 
-	public var pause (default, set) : Bool;
-	public var loop  : Bool;
+	public var pause(default, set) : Bool;
+	public var loop : Bool;
+
+	/**
+		Instead of being decoded at once and cached for reuse, the sound will be progressively
+		decoded as it plays and will not be cached. It is automatically set to true if the sound
+		duration is longer than hxd.snd.Driver.STREAM_DURATION (default to 5 seconds.)
+	**/
+	public var streaming(default,set) : Bool;
+
 	var audibleGain : Float;
 	var lastStamp   : Float;
 	var isVirtual   : Bool;
@@ -51,6 +59,11 @@ class Channel extends ChannelBase {
 		return pause = v;
 	}
 
+	function set_streaming(v:Bool) {
+		if( source != null ) throw "Can't change streaming mode while playing";
+		return streaming = v;
+	}
+
 	public function calcAudibleGain() {
 		audibleGain = volume * channelGroup.volume * soundGroup.volume;
 		for (e in effects) audibleGain *= e.gain;

+ 40 - 27
hxd/snd/Data.hx

@@ -31,7 +31,6 @@ class Data {
 		decodeBuffer(out, outPos, sampleStart, sampleCount);
 	}
 
-	@:noDebug
 	public function resample( rate : Int, format : SampleFormat, channels : Int ) : Data {
 		if( sampleFormat == format && samplingRate == rate && this.channels == channels )
 			return this;
@@ -39,10 +38,25 @@ class Data {
 		var newSamples = Math.ceil(samples * (rate / samplingRate));
 		var bpp = getBytesPerSample();
 		var data = haxe.io.Bytes.alloc(bpp * samples);
-		var resample = samples != newSamples;
 		decodeBuffer(data, 0, 0, samples);
 
-		var out = new haxe.io.BytesBuffer();
+		var out = haxe.io.Bytes.alloc(channels * newSamples * formatBytes(format));
+		resampleBuffer(out, 0, data, 0, rate, format, channels, samples);
+
+		var data = new WavData(null);
+		data.channels = channels;
+		data.samples = newSamples;
+		data.sampleFormat = format;
+		data.samplingRate = rate;
+		@:privateAccess data.rawData = out;
+		return data;
+	}
+
+	@:noDebug
+	public function resampleBuffer( out : haxe.io.Bytes, outPos : Int, input : haxe.io.Bytes, inPos : Int, rate : Int, format : SampleFormat, channels : Int, samples : Int ) {
+		var bpp = getBytesPerSample();
+		var newSamples = Math.ceil(samples * (rate / samplingRate));
+		var resample = samples != newSamples;
 		var srcChannels = this.channels;
 		var commonChannels = channels < srcChannels ? channels : srcChannels;
 		var extraChannels = (channels > srcChannels ? channels : srcChannels) - commonChannels;
@@ -51,7 +65,7 @@ class Data {
 			var targetSample = (i / (newSamples - 1)) * (samples - 1);
 			var isample = Std.int(targetSample);
 			var offset = targetSample - isample;
-			var srcPos = isample * bpp;
+			var srcPos = inPos + isample * bpp;
 			if( isample == samples - 1 ) resample = false;
 			for( k in 0...commonChannels ) {
 				var sval1, sval2 = 0.;
@@ -62,16 +76,16 @@ class Data {
 
 				switch( sampleFormat ) {
 				case UI8:
-					sval1 = data.get(srcPos) / 0xFF;
-					if( resample ) sval2 = data.get(srcPos + bpp) / 0xFF;
+					sval1 = input.get(srcPos) / 0xFF;
+					if( resample ) sval2 = input.get(srcPos + bpp) / 0xFF;
 					srcPos++;
 				case I16:
-					sval1 = sext16(data.getUInt16(srcPos)) / 0x8000;
-					if( resample ) sval2 = sext16(data.getUInt16(srcPos + bpp)) / 0x8000;
+					sval1 = sext16(input.getUInt16(srcPos)) / 0x8000;
+					if( resample ) sval2 = sext16(input.getUInt16(srcPos + bpp)) / 0x8000;
 					srcPos += 2;
 				case F32:
-					sval1 = data.getFloat(srcPos);
-					if( resample ) sval2 = data.getFloat(srcPos + bpp);
+					sval1 = input.getFloat(srcPos);
+					if( resample ) sval2 = input.getFloat(srcPos + bpp);
 					srcPos += 4;
 				}
 
@@ -80,43 +94,42 @@ class Data {
 				case UI8:
 					ival = Std.int((sval + 1) * 128);
 					if( ival > 255 ) ival = 255;
-					out.addByte(ival);
+					out.set(outPos++, ival);
 				case I16:
 					ival = Std.int(sval * 0x8000);
 					if( ival > 0x7FFF ) ival = 0x7FFF;
-					out.addByte(ival & 0xFF);
-					out.addByte((ival>>>8) & 0xFF);
+					out.set(outPos++, ival & 0xFF);
+					out.set(outPos++, (ival>>>8) & 0xFF);
 				case F32:
-					out.addFloat(sval);
+					out.setFloat(outPos, sval);
+					outPos += 4;
 				}
 			}
 			for( i in 0...extraChannels )
 				switch( format ) {
 				case UI8:
-					out.addByte(ival);
+					out.set(outPos++,ival);
 				case I16:
-					out.addByte(ival & 0xFF);
-					out.addByte((ival>>>8) & 0xFF);
+					out.set(outPos++,ival & 0xFF);
+					out.set(outPos++,(ival>>>8) & 0xFF);
 				case F32:
-					out.addFloat(sval);
+					out.setFloat(outPos, sval);
+					outPos += 4;
 				}
 		}
-
-		var data = new WavData(null);
-		data.channels = channels;
-		data.samples = newSamples;
-		data.sampleFormat = format;
-		data.samplingRate = rate;
-		@:privateAccess data.rawData = out.getBytes();
-		return data;
 	}
 
+
 	function decodeBuffer( out : haxe.io.Bytes, outPos : Int, sampleStart : Int, sampleCount : Int ) : Void {
 		throw "Not implemented";
 	}
 
 	public function getBytesPerSample() {
-		return channels * switch( sampleFormat ) {
+		return channels * formatBytes(sampleFormat);
+	}
+
+	public static inline function formatBytes(format:SampleFormat) {
+		return switch( format ) {
 		case UI8: 1;
 		case I16: 2;
 		case F32: 4;

+ 185 - 37
hxd/snd/Driver.hx

@@ -24,11 +24,13 @@ class Source {
 	public var loop = false;
 	public var volume = 1.;
 	public var playing = false;
-
-	public var nextSound : hxd.res.Sound;
-	public var nextBuffer : Buffer;
 	public var hasQueue = false;
 
+	public var streamData : hxd.snd.Data;
+	public var streamSample : Int;
+	public var streamPosition : Float;
+	public var streamPositionNext : Float;
+
 	public function new(inst) {
 		this.inst = inst;
 		buffers = [];
@@ -46,14 +48,31 @@ class Buffer {
 	}
 
 	public function unref() {
-		playCount--;
-		if( playCount == 0 ) lastStop = haxe.Timer.stamp();
+		if( sound == null ) {
+			var tmp = haxe.io.Bytes.alloc(4);
+			tmp.setInt32(0, inst.toInt());
+			AL.deleteBuffers(1, tmp);
+		} else {
+			playCount--;
+			if( playCount == 0 ) lastStop = haxe.Timer.stamp();
+		}
 	}
 }
 
 @:access(hxd.snd.Channel)
 @:access(hxd.snd.Effect)
 class Driver {
+
+	/**
+		When a channel is streaming, how much data should be bufferize.
+	**/
+	public static var STREAM_BUFSIZE = 1 << 19;
+
+	/**
+		Automatically set the channel to streaming mode if its duration exceed this value.
+	**/
+	public static var STREAM_DURATION = 5.;
+
 	static var instance : Driver;
 
 	public var masterVolume	: Float;
@@ -71,6 +90,7 @@ class Driver {
 	static inline var AL_NUM_SOURCES = 16;
 
 	var cachedBytes : haxe.io.Bytes;
+	var resampleBytes : haxe.io.Bytes;
 
 	var alDevice      : ALDevice;
 	var alContext     : ALContext;
@@ -160,6 +180,7 @@ class Driver {
 		c.soundGroup   = soundGroup;
 		c.channelGroup = channelGroup;
 		c.next = channels;
+		c.streaming = c.duration > STREAM_DURATION;
 		channels = c;
 		return c;
 	}
@@ -177,7 +198,45 @@ class Driver {
 				releaseChannel(c);
 				c.onEnd();
 			case AL.PLAYING:
-				if (!c.positionChanged) {
+				if( c.streaming ) {
+					if( c.positionChanged ) {
+						// force full resync
+						releaseSource(s);
+						c.source = null;
+						continue;
+					}
+					var count = AL.getSourcei(s.inst, AL.BUFFERS_PROCESSED);
+					if( count > 0 ) {
+						// swap buffers
+						var b0 = s.buffers[0];
+						var b1 = s.buffers[1];
+						var tmp = getTmp(8);
+						tmp.setInt32(0, b0.inst.toInt());
+						AL.sourceUnqueueBuffers(s.inst, 1, tmp);
+						s.streamPosition = s.streamPositionNext;
+						updateStreaming(s, b0);
+						tmp.setInt32(0, b0.inst.toInt());
+						AL.sourceQueueBuffers(s.inst, 1, tmp);
+						s.buffers[0] = b1;
+						s.buffers[1] = b0;
+					}
+					var position = AL.getSourcef(s.inst, AL.SEC_OFFSET);
+					var prev = c.position;
+					c.position = position + s.streamPosition;
+					c.lastStamp = now;
+					if( c.position > c.duration ) {
+						if( c.queue.length > 0 ) {
+							s.streamPosition -= c.duration;
+							queueNext(c);
+							c.onEnd();
+						} else if( c.loop ) {
+							c.position -= c.duration;
+							s.streamPosition -= c.duration;
+							c.onEnd();
+						}
+					}
+					c.positionChanged = false;
+				} else if( !c.positionChanged ) {
 					var position = AL.getSourcef(s.inst, AL.SEC_OFFSET);
 					var prev = c.position;
 					c.position = position;
@@ -308,10 +367,13 @@ class Driver {
 		var c = s.channel;
 		if( c == null ) return;
 		if( c.positionChanged ) {
-			AL.sourcef(s.inst, AL.SEC_OFFSET, c.position);
+			if( !c.streaming ) {
+				AL.sourcef(s.inst, AL.SEC_OFFSET, c.position);
+				c.position = AL.getSourcef(s.inst, AL.SEC_OFFSET); // prevent rounding
+			}
 			c.positionChanged = false;
 		}
-		var loopFlag = c.loop && c.queue.length == 0;
+		var loopFlag = c.loop && c.queue.length == 0 && !c.streaming;
 		if( s.loop != loopFlag ) {
 			s.loop = loopFlag;
 			AL.sourcei(s.inst, AL.LOOPING, loopFlag ? AL.TRUE : AL.FALSE);
@@ -369,7 +431,31 @@ class Driver {
 			for( b in s.buffers )
 				b.unref();
 			s.buffers = [];
+			s.streamData = null;
 			s.hasQueue = false;
+
+		} else if( c.streaming ) {
+
+			if( !s.hasQueue ) {
+				if( s.buffers.length != 0 ) throw "assert";
+				s.hasQueue = true;
+				var tmpBytes = getTmp(8);
+				AL.genBuffers(2, tmpBytes);
+				s.buffers = [new Buffer(ALBuffer.ofInt(tmpBytes.getInt32(0))), new Buffer(ALBuffer.ofInt(tmpBytes.getInt32(4)))];
+				s.streamData = c.sound.getData();
+				s.streamSample = Std.int(c.position * s.streamData.samplingRate);
+				// fill first two buffers
+				updateStreaming(s, s.buffers[0]);
+				s.streamPosition = s.streamPositionNext;
+				updateStreaming(s, s.buffers[1]);
+				tmpBytes.setInt32(0, s.buffers[0].inst.toInt());
+				tmpBytes.setInt32(4, s.buffers[1].inst.toInt());
+				AL.sourceQueueBuffers(s.inst, 2, tmpBytes);
+				var error = AL.getError();
+				if( error != 0 )
+					throw "Failed to queue streaming buffers 0x"+StringTools.hex(error);
+			}
+
 		} else if( s.hasQueue || c.queue.length > 0 ) {
 
 			if( !s.hasQueue && s.buffers.length > 0 )
@@ -378,16 +464,21 @@ class Driver {
 			var buffers = [getBuffer(c.sound, c.soundGroup)];
 			for( snd in c.queue )
 				buffers.push(getBuffer(snd, c.soundGroup));
+
+			// only append new ones
+			for( i in 0...s.buffers.length )
+				if( buffers.shift() != s.buffers[i] )
+					throw "assert";
+
 			var tmpBytes = getTmp(buffers.length * 4);
 			for( i in 0...buffers.length ) {
 				var b = buffers[i];
 				b.playCount++;
 				tmpBytes.setInt32(i << 2, b.inst.toInt());
 			}
-			for( b in s.buffers )
-				b.unref();
 			AL.sourceQueueBuffers(s.inst, buffers.length, tmpBytes);
-			s.buffers = buffers;
+			for( b in buffers )
+				s.buffers.push(b);
 			if( AL.getError() != 0 )
 				throw "Failed to queue buffers : format differs";
 
@@ -401,6 +492,85 @@ class Driver {
 		}
 	}
 
+	var targetRate : Int;
+	var targetFormat : Data.SampleFormat;
+	var targetChannels : Int;
+	var alFormat : Int;
+
+	function checkTargetFormat( dat : hxd.snd.Data, forceMono = false ) {
+		targetRate = dat.samplingRate;
+		#if !hl
+		// perform resampling to nativechannel frequency
+		targetRate = AL.NATIVE_FREQ;
+		#end
+		targetChannels = forceMono || dat.channels == 1 ? 1 : 2;
+		targetFormat = switch( dat.sampleFormat ) {
+		case UI8:
+			alFormat = targetChannels == 1 ? AL.FORMAT_MONO8 : AL.FORMAT_STEREO8;
+			UI8;
+		case I16:
+			alFormat = targetChannels == 1 ? AL.FORMAT_MONO16 : AL.FORMAT_STEREO16;
+			I16;
+		case F32:
+			#if hl
+			alFormat = targetChannels == 1 ? AL.FORMAT_MONO16 : AL.FORMAT_STEREO16;
+			I16;
+			#else
+			alFormat = targetChannels == 1 ? AL.FORMAT_MONOF32 : AL.FORMAT_STEREOF32;
+			F32;
+			#end
+		}
+		return targetChannels == dat.channels && targetFormat == dat.sampleFormat && targetRate == dat.samplingRate;
+	}
+
+	function updateStreaming( s : Source, buf : Buffer ) {
+		// decode
+		var tmpBytes = getTmp(STREAM_BUFSIZE >> 1);
+		var bpp = s.streamData.getBytesPerSample();
+		var reqSamples = Std.int((STREAM_BUFSIZE >> 1) / bpp);
+		var samples = reqSamples;
+		var outPos = 0;
+		var qPos = 0;
+
+		while( samples > 0 ) {
+			var avail = s.streamData.samples - s.streamSample;
+			if( avail <= 0 ) {
+				var next = s.channel.queue[qPos++];
+				if( next != null ) {
+					s.streamSample -= s.streamData.samples;
+					s.streamData = next.getData();
+				} else if( !s.channel.loop || s.streamData.samples == 0 )
+					break;
+				else
+					s.streamSample -= s.streamData.samples;
+			} else {
+				var count = samples < avail ? samples : avail;
+				if( outPos == 0 )
+					s.streamPositionNext = s.streamSample / s.streamData.samplingRate;
+				s.streamData.decode(tmpBytes, outPos, s.streamSample, count);
+				s.streamSample += count;
+				outPos += count * bpp;
+				samples -= count;
+			}
+		}
+
+		if( !checkTargetFormat(s.streamData) ) {
+			reqSamples -= samples;
+			var bytes = resampleBytes;
+			var reqBytes = targetChannels * reqSamples * Data.formatBytes(targetFormat);
+			if( bytes == null || bytes.length < reqBytes ) {
+				bytes = haxe.io.Bytes.alloc(reqBytes);
+				resampleBytes = bytes;
+			}
+			s.streamData.resampleBuffer(resampleBytes, 0, tmpBytes, 0, targetRate, targetFormat, targetChannels, reqSamples);
+			AL.bufferData(buf.inst, alFormat, resampleBytes, reqBytes, targetRate);
+		} else {
+			AL.bufferData(buf.inst, alFormat, tmpBytes, outPos, s.streamData.samplingRate);
+			if( AL.getError() != 0 )
+				throw "Failed to upload buffer data";
+		}
+	}
+
 	function getBuffer( snd : hxd.res.Sound, grp : SoundGroup ) : Buffer {
 		var b = bufferMap.get(snd);
 		if( b != null )
@@ -467,35 +637,13 @@ class Driver {
 	}
 
 	function fillBuffer(buf : Buffer, dat : hxd.snd.Data, forceMono = false) {
-		var targetRate = dat.samplingRate;
-
-		#if !hl
-		// perform resampling to nativechannel frequency
-		targetRate = AL.NATIVE_FREQ;
-		#end
-
-		var targetChannels = forceMono || dat.channels == 1 ? 1 : 2;
-		var alFormat;
-		var targetFormat : hxd.snd.Data.SampleFormat = switch( dat.sampleFormat ) {
-		case UI8:
-			alFormat = targetChannels == 1 ? AL.FORMAT_MONO8 : AL.FORMAT_STEREO8;
-			UI8;
-		case I16:
-			alFormat = targetChannels == 1 ? AL.FORMAT_MONO16 : AL.FORMAT_STEREO16;
-			I16;
-		case F32:
-			#if hl
-			alFormat = targetChannels == 1 ? AL.FORMAT_MONO16 : AL.FORMAT_STEREO16;
-			I16;
-			#else
-			alFormat = targetChannels == 1 ? AL.FORMAT_MONOF32 : AL.FORMAT_STEREOF32;
-			F32;
-			#end
-		}
-		if( targetChannels != dat.channels || targetFormat != dat.sampleFormat || targetRate != dat.samplingRate )
+		if( !checkTargetFormat(dat, forceMono) )
 			dat = dat.resample(targetRate, targetFormat, targetChannels);
 		var dataBytes = haxe.io.Bytes.alloc(dat.samples * dat.getBytesPerSample());
 		dat.decode(dataBytes, 0, 0, dat.samples);
 		AL.bufferData(buf.inst, alFormat, dataBytes, dataBytes.length, dat.samplingRate);
+		if( AL.getError() != 0 )
+			throw "Failed to upload buffer data";
 	}
+
 }

+ 17 - 2
samples/Sound.hx

@@ -17,6 +17,8 @@ class NoiseChannel extends hxd.snd.NativeChannel {
 class Sound extends hxd.App {
 
 	var time = 0.;
+	var slider : h2d.Slider;
+	var music : hxd.snd.Channel;
 
 	override function init() {
 		var res = if( hxd.res.Sound.supportedFormat(Mp3) )
@@ -27,9 +29,18 @@ class Sound extends hxd.App {
 			null;
 		if( res != null ) {
 			trace("Playing "+res);
-			var c = res.play(true);
-			c.onEnd = function() trace("LOOP");
+			music = res.play(true);
+			//music.queueSound(...);
+			music.onEnd = function() trace("LOOP");
 		}
+
+		slider = new h2d.Slider(300, 10, s2d);
+		slider.x = 150;
+		slider.y = 80;
+		if( music == null ) slider.remove();
+		slider.onChange = function() {
+			music.position = slider.value * music.duration;
+		};
 	}
 
 	override function update(dt:Float) {
@@ -41,6 +52,10 @@ class Sound extends hxd.App {
 		} else
 			engine.backgroundColor = 0;
 
+		if( music != null ) {
+			slider.value = music.position / music.duration;
+		}
+
 		if( hxd.Key.isPressed(hxd.Key.SPACE) ) {
 			var c = new NoiseChannel();
 			haxe.Timer.delay(c.stop, 1000);