Browse Source

Merged in rcoaxil/love/minor (pull request #63)

TYPE_QUEUE Source implemented, ported to minor branch.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
7cd24de402

+ 1 - 0
src/modules/audio/Audio.h

@@ -75,6 +75,7 @@ public:
 
 
 	virtual Source *newSource(love::sound::Decoder *decoder) = 0;
 	virtual Source *newSource(love::sound::Decoder *decoder) = 0;
 	virtual Source *newSource(love::sound::SoundData *soundData) = 0;
 	virtual Source *newSource(love::sound::SoundData *soundData) = 0;
+	virtual Source *newSource(int sampleRate, int bitDepth, int channels) = 0;
 
 
 	/**
 	/**
 	 * Gets the current number of simultaneous playing sources.
 	 * Gets the current number of simultaneous playing sources.

+ 1 - 0
src/modules/audio/Source.cpp

@@ -63,6 +63,7 @@ StringMap<Source::Type, Source::TYPE_MAX_ENUM>::Entry Source::typeEntries[] =
 {
 {
 	{"static", Source::TYPE_STATIC},
 	{"static", Source::TYPE_STATIC},
 	{"stream", Source::TYPE_STREAM},
 	{"stream", Source::TYPE_STREAM},
+	{"queue",  Source::TYPE_QUEUE},
 };
 };
 
 
 StringMap<Source::Type, Source::TYPE_MAX_ENUM> Source::types(Source::typeEntries, sizeof(Source::typeEntries));
 StringMap<Source::Type, Source::TYPE_MAX_ENUM> Source::types(Source::typeEntries, sizeof(Source::typeEntries));

+ 5 - 0
src/modules/audio/Source.h

@@ -38,6 +38,7 @@ public:
 	{
 	{
 		TYPE_STATIC,
 		TYPE_STATIC,
 		TYPE_STREAM,
 		TYPE_STREAM,
+		TYPE_QUEUE,
 		TYPE_MAX_ENUM
 		TYPE_MAX_ENUM
 	};
 	};
 
 
@@ -100,6 +101,10 @@ public:
 	virtual float getMaxDistance() const = 0;
 	virtual float getMaxDistance() const = 0;
 
 
 	virtual int getChannels() const = 0;
 	virtual int getChannels() const = 0;
+
+	virtual int getFreeBufferCount() const = 0;
+	virtual bool queue(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels) = 0;
+
 	virtual Type getType() const;
 	virtual Type getType() const;
 
 
 	static bool getConstant(const char *in, Type &out);
 	static bool getConstant(const char *in, Type &out);

+ 5 - 0
src/modules/audio/null/Audio.cpp

@@ -51,6 +51,11 @@ love::audio::Source *Audio::newSource(love::sound::SoundData *)
 	return new Source();
 	return new Source();
 }
 }
 
 
+love::audio::Source *Audio::newSource(int, int, int)
+{
+	return new Source();
+}
+
 int Audio::getSourceCount() const
 int Audio::getSourceCount() const
 {
 {
 	return 0;
 	return 0;

+ 1 - 0
src/modules/audio/null/Audio.h

@@ -46,6 +46,7 @@ public:
 	// Implements Audio.
 	// Implements Audio.
 	love::audio::Source *newSource(love::sound::Decoder *decoder);
 	love::audio::Source *newSource(love::sound::Decoder *decoder);
 	love::audio::Source *newSource(love::sound::SoundData *soundData);
 	love::audio::Source *newSource(love::sound::SoundData *soundData);
+	love::audio::Source *newSource(int sampleRate, int bitDepth, int channels);
 	int getSourceCount() const;
 	int getSourceCount() const;
 	int getMaxSources() const;
 	int getMaxSources() const;
 	bool play(love::audio::Source *source);
 	bool play(love::audio::Source *source);

+ 10 - 0
src/modules/audio/null/Source.cpp

@@ -217,6 +217,16 @@ int Source::getChannels() const
 	return 2;
 	return 2;
 }
 }
 
 
+int Source::getFreeBufferCount() const
+{
+	return 0;
+}
+
+bool Source::queue(void *, int, int, int, int)
+{
+	return false;
+}
+
 } // null
 } // null
 } // audio
 } // audio
 } // love
 } // love

+ 3 - 0
src/modules/audio/null/Source.h

@@ -76,6 +76,9 @@ public:
 	virtual float getMaxDistance() const;
 	virtual float getMaxDistance() const;
 	virtual int getChannels() const;
 	virtual int getChannels() const;
 
 
+	virtual int getFreeBufferCount() const;
+	virtual bool queue(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels);
+
 private:
 private:
 
 
 	float pitch;
 	float pitch;

+ 5 - 0
src/modules/audio/openal/Audio.cpp

@@ -157,6 +157,11 @@ love::audio::Source *Audio::newSource(love::sound::SoundData *soundData)
 	return new Source(pool, soundData);
 	return new Source(pool, soundData);
 }
 }
 
 
+love::audio::Source *Audio::newSource(int sampleRate, int bitDepth, int channels)
+{
+	return new Source(pool, sampleRate, bitDepth, channels);
+}
+
 int Audio::getSourceCount() const
 int Audio::getSourceCount() const
 {
 {
 	return pool->getSourceCount();
 	return pool->getSourceCount();

+ 1 - 0
src/modules/audio/openal/Audio.h

@@ -71,6 +71,7 @@ public:
 	// Implements Audio.
 	// Implements Audio.
 	love::audio::Source *newSource(love::sound::Decoder *decoder);
 	love::audio::Source *newSource(love::sound::Decoder *decoder);
 	love::audio::Source *newSource(love::sound::SoundData *soundData);
 	love::audio::Source *newSource(love::sound::SoundData *soundData);
+	love::audio::Source *newSource(int sampleRate, int bitDepth, int channels);
 	int getSourceCount() const;
 	int getSourceCount() const;
 	int getMaxSources() const;
 	int getMaxSources() const;
 	bool play(love::audio::Source *source);
 	bool play(love::audio::Source *source);

+ 6 - 0
src/modules/audio/openal/Pool.cpp

@@ -283,6 +283,12 @@ double Pool::getDuration(Source *source, void *unit)
 	return source->getDurationAtomic(unit);
 	return source->getDurationAtomic(unit);
 }
 }
 
 
+bool Pool::queue(Source *source, void *data, ALsizei length)
+{
+	thread::Lock lock(mutex);
+	return source->queueAtomic(data, length);
+}
+
 ALuint Pool::findi(const Source *source) const
 ALuint Pool::findi(const Source *source) const
 {
 {
 	std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);
 	std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);

+ 1 - 0
src/modules/audio/openal/Pool.h

@@ -91,6 +91,7 @@ public:
 	void seek(Source *source, float offset, void *unit);
 	void seek(Source *source, float offset, void *unit);
 	float tell(Source *source, void *unit);
 	float tell(Source *source, void *unit);
 	double getDuration(Source *source, void *unit);
 	double getDuration(Source *source, void *unit);
+	bool queue(Source *source, void *data, ALsizei length);
 
 
 	bool play(const std::vector<love::audio::Source*> &sources);
 	bool play(const std::vector<love::audio::Source*> &sources);
 	void stop(const std::vector<love::audio::Source*> &sources);
 	void stop(const std::vector<love::audio::Source*> &sources);

+ 396 - 102
src/modules/audio/openal/Source.cpp

@@ -64,6 +64,50 @@ Ensure the Source is not multi-channel before calling this function.")
 
 
 };
 };
 
 
+class QueueFormatMismatchException : public love::Exception
+{
+public:
+
+	QueueFormatMismatchException()
+		: Exception("Queued sound data must have same format as sound Source.")
+	{
+	}
+
+};
+
+class QueueTypeMismatchException : public love::Exception
+{
+public:
+
+	QueueTypeMismatchException()
+		: Exception("Only queueable Sources can be queued with sound data.")
+	{
+	}
+
+};
+
+class QueueMalformedLengthException : public love::Exception
+{
+public:
+
+	QueueMalformedLengthException(int bytes)
+		: Exception("Data length must be a multiple of sample size (%d bytes).", bytes)
+	{
+	}
+
+};
+
+class QueueLoopingException : public love::Exception
+{
+public:
+
+	QueueLoopingException()
+		: Exception("Queueable Sources can not be looped.")
+	{
+	}
+
+};
+
 StaticDataBuffer::StaticDataBuffer(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
 StaticDataBuffer::StaticDataBuffer(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
 	: size(size)
 	: size(size)
 {
 {
@@ -134,11 +178,54 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder)
 	, bitDepth(decoder->getBitDepth())
 	, bitDepth(decoder->getBitDepth())
 	, decoder(decoder)
 	, decoder(decoder)
 	, toLoop(0)
 	, toLoop(0)
+	, unusedBufferTop(MAX_BUFFERS - 1)
 {
 {
 	if (getFormat(decoder->getChannels(), decoder->getBitDepth()) == 0)
 	if (getFormat(decoder->getChannels(), decoder->getBitDepth()) == 0)
 		throw InvalidFormatException(decoder->getChannels(), decoder->getBitDepth());
 		throw InvalidFormatException(decoder->getChannels(), decoder->getBitDepth());
 
 
 	alGenBuffers(MAX_BUFFERS, streamBuffers);
 	alGenBuffers(MAX_BUFFERS, streamBuffers);
+	for (unsigned int i = 0; i < MAX_BUFFERS; i++)
+		unusedBuffers[i] = streamBuffers[i];
+
+	float z[3] = {0, 0, 0};
+
+	setFloatv(position, z);
+	setFloatv(velocity, z);
+	setFloatv(direction, z);
+}
+
+Source::Source(Pool *pool, int sampleRate, int bitDepth, int channels)
+	: love::audio::Source(Source::TYPE_QUEUE)
+	, pool(pool)
+	, valid(false)
+	, staticBuffer(nullptr)
+	, pitch(1.0f)
+	, volume(1.0f)
+	, relative(false)
+	, looping(false)
+	, minVolume(0.0f)
+	, maxVolume(1.0f)
+	, referenceDistance(1.0f)
+	, rolloffFactor(1.0f)
+	, maxDistance(MAX_ATTENUATION_DISTANCE)
+	, cone()
+	, offsetSamples(0)
+	, offsetSeconds(0)
+	, sampleRate(sampleRate)
+	, channels(channels)
+	, bitDepth(bitDepth)
+	, decoder(nullptr)
+	, toLoop(0)
+	, unusedBufferTop(-1)
+	, bufferedBytes(0)
+{
+	ALenum fmt = getFormat(channels, bitDepth);
+	if (fmt == 0)
+		throw InvalidFormatException(channels, bitDepth);
+
+	alGenBuffers(MAX_BUFFERS, streamBuffers);
+	for (unsigned int i = 0; i < MAX_BUFFERS; i++)
+		unusedBuffers[i] = streamBuffers[i];
 
 
 	float z[3] = {0, 0, 0};
 	float z[3] = {0, 0, 0};
 
 
@@ -169,13 +256,18 @@ Source::Source(const Source &s)
 	, bitDepth(s.bitDepth)
 	, bitDepth(s.bitDepth)
 	, decoder(nullptr)
 	, decoder(nullptr)
 	, toLoop(0)
 	, toLoop(0)
+	, unusedBufferTop(-1)
 {
 {
 	if (type == TYPE_STREAM)
 	if (type == TYPE_STREAM)
 	{
 	{
 		if (s.decoder.get())
 		if (s.decoder.get())
 			decoder.set(s.decoder->clone(), Acquire::NORETAIN);
 			decoder.set(s.decoder->clone(), Acquire::NORETAIN);
-
+	}
+	if (type != TYPE_STATIC)
+	{
 		alGenBuffers(MAX_BUFFERS, streamBuffers);
 		alGenBuffers(MAX_BUFFERS, streamBuffers);
+		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
+			unusedBuffers[i] = streamBuffers[i];
 	}
 	}
 
 
 	setFloatv(position, s.position);
 	setFloatv(position, s.position);
@@ -188,7 +280,7 @@ Source::~Source()
 	if (valid)
 	if (valid)
 		pool->stop(this);
 		pool->stop(this);
 
 
-	if (type == TYPE_STREAM)
+	if (type != TYPE_STATIC)
 		alDeleteBuffers(MAX_BUFFERS, streamBuffers);
 		alDeleteBuffers(MAX_BUFFERS, streamBuffers);
 }
 }
 
 
@@ -242,49 +334,66 @@ bool Source::update()
 	if (!valid)
 	if (!valid)
 		return false;
 		return false;
 
 
-	if (type == TYPE_STATIC)
-	{
-		// Looping mode could have changed.
-		alSourcei(source, AL_LOOPING, isLooping() ? AL_TRUE : AL_FALSE);
-		return !isFinished();
-	}
-	else if (type == TYPE_STREAM && (isLooping() || !isFinished()))
+	switch (type)
 	{
 	{
-		// Number of processed buffers.
-		ALint processed = 0;
-
-		alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
-
-		while (processed--)
+		case TYPE_STATIC:
+			// Looping mode could have changed.
+			// FIXME: make looping mode not change without you noticing so that this is not needed
+			alSourcei(source, AL_LOOPING, isLooping() ? AL_TRUE : AL_FALSE);
+			return !isFinished();
+		case TYPE_STREAM:
+			if (!isFinished())
+			{
+				ALint processed;
+				ALuint buffers[MAX_BUFFERS];
+				float curOffsetSamples, curOffsetSecs, newOffsetSamples, newOffsetSecs;
+				int freq = decoder->getSampleRate();
+
+				alGetSourcef(source, AL_SAMPLE_OFFSET, &curOffsetSamples);
+				curOffsetSecs = curOffsetSamples / freq;
+
+				alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
+				alSourceUnqueueBuffers(source, processed, buffers);
+
+				alGetSourcef(source, AL_SAMPLE_OFFSET, &newOffsetSamples);
+				newOffsetSecs = newOffsetSamples / freq;
+
+				offsetSamples += (curOffsetSamples - newOffsetSamples);
+				offsetSeconds += (curOffsetSecs - newOffsetSecs);
+
+				for (unsigned int i = 0; i < (unsigned int)processed; i++)
+					unusedBufferPush(buffers[i]);
+
+				while (unusedBufferPeek() != AL_NONE)
+				{
+					if(streamAtomic(unusedBufferPeek(), decoder.get()) > 0)
+						alSourceQueueBuffers(source, 1, unusedBufferPop());
+					else
+						break;
+				}
+
+				return true;
+			}
+			return false;
+		case TYPE_QUEUE: 
 		{
 		{
-			ALuint buffer;
-
-			float curOffsetSamples, curOffsetSecs;
-
-			alGetSourcef(source, AL_SAMPLE_OFFSET, &curOffsetSamples);
-
-			int freq = decoder->getSampleRate();
-			curOffsetSecs = curOffsetSamples / freq;
-
-			// Get a free buffer.
-			alSourceUnqueueBuffers(source, 1, &buffer);
-
-			float newOffsetSamples, newOffsetSecs;
-
-			alGetSourcef(source, AL_SAMPLE_OFFSET, &newOffsetSamples);
-			newOffsetSecs = newOffsetSamples / freq;
-
-			offsetSamples += (curOffsetSamples - newOffsetSamples);
-			offsetSeconds += (curOffsetSecs - newOffsetSecs);
-
-			// FIXME: We should put freed buffers into a list that we later
-			// consume here, so we can keep track of all free buffers even if we
-			// tried to stream data to one but the decoder didn't have data for it.
-			if (streamAtomic(buffer, decoder.get()) > 0)
-				alSourceQueueBuffers(source, 1, &buffer);
+			ALint processed;
+			ALuint buffers[MAX_BUFFERS];
+
+			alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
+			alSourceUnqueueBuffers(source, processed, buffers);
+
+			for (unsigned int i = 0; i < (unsigned int)processed; i++)
+			{
+				ALint size;
+				alGetBufferi(buffers[i], AL_SIZE, &size);
+				bufferedBytes -= size;
+				unusedBufferPush(buffers[i]);
+			}
+			return !isFinished();
 		}
 		}
-
-		return true;
+		case TYPE_MAX_ENUM:
+			break;
 	}
 	}
 
 
 	return false;
 	return false;
@@ -334,11 +443,7 @@ float Source::getVolume() const
 
 
 void Source::seekAtomic(float offset, void *unit)
 void Source::seekAtomic(float offset, void *unit)
 {
 {
-	bool wasPlaying = isPlaying();
-
-	// To drain all buffers
-	if (valid && type == TYPE_STREAM)
-		stopAtomic();
+	float offsetSamples, offsetSeconds;
 
 
 	switch (*((Source::Unit *) unit))
 	switch (*((Source::Unit *) unit))
 	{
 	{
@@ -353,16 +458,61 @@ void Source::seekAtomic(float offset, void *unit)
 		break;
 		break;
 	}
 	}
 
 
-	if (type == TYPE_STREAM)
-		decoder->seek(offsetSeconds);
-	else if (valid) // Playing static source
+	switch (type)
 	{
 	{
-		alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
-		offsetSamples = offsetSeconds = 0;
-	}
+		case TYPE_STATIC:
+			alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
+			offsetSamples = offsetSeconds = 0;
+			break;
+		case TYPE_STREAM:
+		{
+			bool wasPlaying = isPlaying();
+
+			// To drain all buffers
+			if (valid)
+				stopAtomic();
 
 
-	if (wasPlaying && type == TYPE_STREAM)
-		playAtomic(source);
+			decoder->seek(offsetSeconds);
+
+			if (wasPlaying)
+				playAtomic(source);
+
+			break;
+		}
+		case TYPE_QUEUE:
+			if (valid)
+			{
+				alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
+				offsetSamples = offsetSeconds = 0;
+			}
+			else
+			{
+				ALint size;
+				ALuint buffer = unusedBufferPeek();
+
+				//emulate AL behavior, discarding buffer once playback head is past one
+				while (buffer != AL_NONE)
+				{
+					alGetBufferi(buffer, AL_SIZE, &size);
+
+					if (offsetSamples < size / (bitDepth / 8 * channels))
+						break;
+
+					unusedBufferPop();
+					buffer = unusedBufferPeek();
+					bufferedBytes -= size;
+					offsetSamples -= size / (bitDepth / 8 * channels);
+				}
+				if (buffer == AL_NONE)
+					offsetSamples = 0;
+				offsetSeconds = offsetSamples / sampleRate;
+			}
+			break;
+		case TYPE_MAX_ENUM:
+			break;
+	}
+	this->offsetSamples = offsetSamples;
+	this->offsetSeconds = offsetSeconds;
 }
 }
 
 
 void Source::seek(float offset, Source::Unit unit)
 void Source::seek(float offset, Source::Unit unit)
@@ -401,25 +551,40 @@ double Source::getDurationAtomic(void *vunit)
 {
 {
 	Unit unit = *(Unit *) vunit;
 	Unit unit = *(Unit *) vunit;
 
 
-	if (type == TYPE_STREAM)
+	switch (type)
 	{
 	{
-		double seconds = decoder->getDuration();
+		case TYPE_STATIC:
+		{
+			ALsizei size = staticBuffer->getSize();
+			ALsizei samples = (size / channels) / (bitDepth / 8);
 
 
-		if (unit == UNIT_SECONDS)
-			return seconds;
-		else
-			return seconds * decoder->getSampleRate();
-	}
-	else
-	{
-		ALsizei size = staticBuffer->getSize();
-		ALsizei samples = (size / channels) / (bitDepth / 8);
+			if (unit == UNIT_SAMPLES)
+				return (double) samples;
+			else
+				return (double) samples / (double) sampleRate;
+		}
+		case TYPE_STREAM:
+		{
+			double seconds = decoder->getDuration();
 
 
-		if (unit == UNIT_SAMPLES)
-			return (double) samples;
-		else
-			return (double) samples / (double) sampleRate;
+			if (unit == UNIT_SECONDS)
+				return seconds;
+			else
+				return seconds * decoder->getSampleRate();
+		}
+		case TYPE_QUEUE:
+		{
+			ALsizei samples = (bufferedBytes / channels) / (bitDepth / 8);
+
+			if (unit == UNIT_SAMPLES)
+				return (double)samples;
+			else
+				return (double)samples / (double)sampleRate;
+		}
+		case TYPE_MAX_ENUM:
+			return 0.0;
 	}
 	}
+	return 0.0;
 }
 }
 
 
 double Source::getDuration(Unit unit)
 double Source::getDuration(Unit unit)
@@ -541,6 +706,9 @@ bool Source::isRelative() const
 
 
 void Source::setLooping(bool enable)
 void Source::setLooping(bool enable)
 {
 {
+	if (type == TYPE_QUEUE)
+		throw QueueLoopingException();
+
 	if (valid && type == TYPE_STATIC)
 	if (valid && type == TYPE_STATIC)
 		alSourcei(source, AL_LOOPING, enable ? AL_TRUE : AL_FALSE);
 		alSourcei(source, AL_LOOPING, enable ? AL_TRUE : AL_FALSE);
 
 
@@ -552,6 +720,66 @@ bool Source::isLooping() const
 	return looping;
 	return looping;
 }
 }
 
 
+bool Source::queue(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels)
+{
+	if (type != TYPE_QUEUE)
+		throw QueueTypeMismatchException();
+
+	if (dataSampleRate != sampleRate || dataBitDepth != bitDepth || dataChannels != channels )
+		throw QueueFormatMismatchException();
+
+	if (length % (bitDepth / 8 * channels) != 0)
+		throw QueueMalformedLengthException(bitDepth / 8 * channels);
+
+	if (length == 0)
+		return true;
+
+	return pool->queue(this, data, (ALsizei)length);
+}
+
+bool Source::queueAtomic(void *data, ALsizei length)
+{
+	if (valid)
+	{
+		ALuint buffer = unusedBufferPeek();
+		if (buffer == AL_NONE)
+			return false;
+
+		alBufferData(buffer, getFormat(channels, bitDepth), data, length, sampleRate);
+		alSourceQueueBuffers(source, 1, &buffer);
+		unusedBufferPop();
+	}
+	else
+	{
+		ALuint buffer = unusedBufferPeekNext();
+		if (buffer == AL_NONE)
+			return false;
+
+		//stack acts as queue while stopped
+		alBufferData(buffer, getFormat(channels, bitDepth), data, length, sampleRate);
+		unusedBufferQueue(buffer);
+	}
+	bufferedBytes += length;
+
+	return true;
+}
+
+int Source::getFreeBufferCount() const
+{
+	switch (type) //why not :^)
+	{
+		case TYPE_STATIC: 
+			return 0;
+		case TYPE_STREAM:
+			return unusedBufferTop + 1;
+		case TYPE_QUEUE:
+			return valid ? unusedBufferTop + 1 : (int)MAX_BUFFERS - unusedBufferTop - 1;
+		case TYPE_MAX_ENUM:
+			return 0; 
+	}
+	return 0;
+}
+
 void Source::prepareAtomic()
 void Source::prepareAtomic()
 {
 {
 	// This Source may now be associated with an OpenAL source that still has
 	// This Source may now be associated with an OpenAL source that still has
@@ -559,50 +787,89 @@ void Source::prepareAtomic()
 	// of the new one.
 	// of the new one.
 	reset();
 	reset();
 
 
-	if (type == TYPE_STATIC)
-	{
-		alSourcei(source, AL_BUFFER, staticBuffer->getBuffer());
-		if (offsetSamples >= 0)
-			alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
-	}
-	else if (type == TYPE_STREAM)
+	switch (type)
 	{
 	{
-		int usedBuffers = 0;
-
-		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
+		case TYPE_STATIC:
+			alSourcei(source, AL_BUFFER, staticBuffer->getBuffer());
+			//source can be seeked while not valid
+			if (offsetSamples >= 0) 
+				alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
+			break;
+		case TYPE_STREAM:
+			while (unusedBufferPeek() != AL_NONE)
+			{
+				if(streamAtomic(unusedBufferPeek(), decoder.get()) == 0)
+					break;
+
+				alSourceQueueBuffers(source, 1, unusedBufferPop());
+
+				if (decoder->isFinished())
+					break;
+			}
+			break;
+		case TYPE_QUEUE:
 		{
 		{
-			if (streamAtomic(streamBuffers[i], decoder.get()) == 0)
-				break;
-
-			++usedBuffers;
-
-			if (decoder->isFinished())
-				break;
+			int top = unusedBufferTop;
+			//when queue source is stopped, loaded buffers are stored in unused buffers stack
+			while (unusedBufferPeek() != AL_NONE)
+				alSourceQueueBuffers(source, 1, unusedBufferPop());
+			//construct a stack of unused buffers (beyond the end of stack)
+			for (unsigned int i = top + 1; i < MAX_BUFFERS; i++)
+				unusedBufferPush(unusedBuffers[i]);
+
+			if (offsetSamples >= 0) 
+				alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
+			break;
 		}
 		}
-
-		if (usedBuffers > 0)
-			alSourceQueueBuffers(source, usedBuffers, streamBuffers);
+		case TYPE_MAX_ENUM:
+			break;
 	}
 	}
 }
 }
 
 
 void Source::teardownAtomic()
 void Source::teardownAtomic()
 {
 {
-	if (type == TYPE_STATIC)
+	switch (type)
 	{
 	{
-		alSourcef(source, AL_SAMPLE_OFFSET, 0);
-	}
-	else if (type == TYPE_STREAM)
-	{
-		decoder->seek(0);
+		case TYPE_STATIC:
+			alSourcef(source, AL_SAMPLE_OFFSET, 0);
+			break;
+		case TYPE_STREAM:
+		{
+			ALint queued;
+			ALuint buffer;
 
 
-		int queued = 0;
-		alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
+			decoder->seek(0);
+			// drain buffers
+			//since we only unqueue 1 buffer, it's OK to use singular variable pointer instead of array
+			alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
+			for (unsigned int i = 0; i < (unsigned int)queued; i++)
+				alSourceUnqueueBuffers(source, 1, &buffer);
 
 
-		while (queued--)
+			// generate unused buffers list
+			for (unsigned int i = 0; i < MAX_BUFFERS; i++)
+				unusedBuffers[i] = streamBuffers[i];
+
+			unusedBufferTop = MAX_BUFFERS - 1;
+			break;
+		}
+		case TYPE_QUEUE:
 		{
 		{
+			ALint queued;
 			ALuint buffer;
 			ALuint buffer;
-			alSourceUnqueueBuffers(source, 1, &buffer);
+
+			alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
+			for (unsigned int i = (unsigned int)queued; i > 0; i--)
+				alSourceUnqueueBuffers(source, 1, &buffer);
+
+			// generate unused buffers list
+			for (unsigned int i = 0; i < MAX_BUFFERS; i++)
+				unusedBuffers[i] = streamBuffers[i];
+
+			unusedBufferTop = -1;
+			break;
 		}
 		}
+		case TYPE_MAX_ENUM:
+			break;
 	}
 	}
 
 
 	alSourcei(source, AL_BUFFER, AL_NONE);
 	alSourcei(source, AL_BUFFER, AL_NONE);
@@ -629,7 +896,7 @@ bool Source::playAtomic(ALuint source)
 	valid = true; //if it fails it will be set to false again
 	valid = true; //if it fails it will be set to false again
 	//but this prevents a horrible, horrible bug
 	//but this prevents a horrible, horrible bug
 
 
-	if (type == TYPE_STATIC)
+	if (type != TYPE_STREAM)
 		offsetSamples = offsetSeconds = 0;
 		offsetSamples = offsetSeconds = 0;
 
 
 	return success;
 	return success;
@@ -681,7 +948,7 @@ bool Source::playAtomic(const std::vector<love::audio::Source*> &sources, const
 		Source *source = (Source*) _source;
 		Source *source = (Source*) _source;
 		source->valid = source->valid || success;
 		source->valid = source->valid || success;
 
 
-		if (success && source->type == TYPE_STATIC)
+		if (success && source->type != TYPE_STREAM)
 			source->offsetSamples = source->offsetSeconds = 0;
 			source->offsetSamples = source->offsetSeconds = 0;
 	}
 	}
 
 
@@ -784,6 +1051,33 @@ ALenum Source::getFormat(int channels, int bitDepth) const
 	return 0;
 	return 0;
 }
 }
 
 
+ALuint Source::unusedBufferPeek()
+{
+	return (unusedBufferTop < 0) ? AL_NONE : unusedBuffers[unusedBufferTop];
+}
+
+ALuint Source::unusedBufferPeekNext()
+{
+	return (unusedBufferTop >= (int)MAX_BUFFERS - 1) ? AL_NONE : unusedBuffers[unusedBufferTop + 1];
+}
+
+ALuint *Source::unusedBufferPop()
+{
+	return &unusedBuffers[unusedBufferTop--];
+}
+
+void Source::unusedBufferPush(ALuint buffer)
+{
+	unusedBuffers[++unusedBufferTop] = buffer;
+}
+
+void Source::unusedBufferQueue(ALuint buffer)
+{
+	for (unsigned int i = ++unusedBufferTop; i > 0; i--)
+		unusedBuffers[i] = unusedBuffers[i - 1];
+	unusedBuffers[0] = buffer;
+}
+
 int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d)
 int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d)
 {
 {
 	// Get more sound data.
 	// Get more sound data.

+ 14 - 0
src/modules/audio/openal/Source.h

@@ -86,6 +86,7 @@ public:
 
 
 	Source(Pool *pool, love::sound::SoundData *soundData);
 	Source(Pool *pool, love::sound::SoundData *soundData);
 	Source(Pool *pool, love::sound::Decoder *decoder);
 	Source(Pool *pool, love::sound::Decoder *decoder);
+	Source(Pool *pool, int sampleRate, int bitDepth, int channels);
 	Source(const Source &s);
 	Source(const Source &s);
 	virtual ~Source();
 	virtual ~Source();
 
 
@@ -130,6 +131,10 @@ public:
 	virtual float getMaxDistance() const;
 	virtual float getMaxDistance() const;
 	virtual int getChannels() const;
 	virtual int getChannels() const;
 
 
+	virtual int getFreeBufferCount() const;
+	virtual bool queue(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels);
+	virtual bool queueAtomic(void *data, ALsizei length);
+
 	void prepareAtomic();
 	void prepareAtomic();
 	void teardownAtomic();
 	void teardownAtomic();
 
 
@@ -159,12 +164,19 @@ private:
 
 
 	int streamAtomic(ALuint buffer, love::sound::Decoder *d);
 	int streamAtomic(ALuint buffer, love::sound::Decoder *d);
 
 
+	ALuint unusedBufferPeek();
+	ALuint unusedBufferPeekNext();
+	ALuint *unusedBufferPop();
+	void unusedBufferPush(ALuint buffer);
+	void unusedBufferQueue(ALuint buffer);
+	
 	Pool *pool;
 	Pool *pool;
 	ALuint source;
 	ALuint source;
 	bool valid;
 	bool valid;
 
 
 	static const unsigned int MAX_BUFFERS = 8;
 	static const unsigned int MAX_BUFFERS = 8;
 	ALuint streamBuffers[MAX_BUFFERS];
 	ALuint streamBuffers[MAX_BUFFERS];
+	ALuint unusedBuffers[MAX_BUFFERS];
 
 
 	StrongRef<StaticDataBuffer> staticBuffer;
 	StrongRef<StaticDataBuffer> staticBuffer;
 
 
@@ -198,6 +210,8 @@ private:
 	StrongRef<love::sound::Decoder> decoder;
 	StrongRef<love::sound::Decoder> decoder;
 
 
 	unsigned int toLoop;
 	unsigned int toLoop;
+	int unusedBufferTop;
+	ALsizei bufferedBytes;
 }; // Source
 }; // Source
 
 
 } // openal
 } // openal

+ 19 - 0
src/modules/audio/wrap_Audio.cpp

@@ -78,6 +78,24 @@ int w_newSource(lua_State *L)
 		return luax_typerror(L, 1, "Decoder or SoundData");
 		return luax_typerror(L, 1, "Decoder or SoundData");
 }
 }
 
 
+int w_newQueueableSource(lua_State *L)
+{
+	Source *t = nullptr;
+
+	luax_catchexcept(L, [&]() {
+		t = instance()->newSource((int)luaL_checknumber(L, 1), (int)luaL_checknumber(L, 2), (int)luaL_checknumber(L, 3));
+	});
+
+	if (t != nullptr)
+	{
+		luax_pushtype(L, AUDIO_SOURCE_ID, t);
+		t->release();
+		return 1;
+	}
+	else
+		return 0; //all argument type errors are checked in above constructor
+}
+
 static std::vector<Source*> readSourceList(lua_State *L, int n)
 static std::vector<Source*> readSourceList(lua_State *L, int n)
 {
 {
 	if (n < 0)
 	if (n < 0)
@@ -307,6 +325,7 @@ static const luaL_Reg functions[] =
 {
 {
 	{ "getSourceCount", w_getSourceCount },
 	{ "getSourceCount", w_getSourceCount },
 	{ "newSource", w_newSource },
 	{ "newSource", w_newSource },
+	{ "newQueueableSource", w_newQueueableSource },
 	{ "play", w_play },
 	{ "play", w_play },
 	{ "stop", w_stop },
 	{ "stop", w_stop },
 	{ "pause", w_pause },
 	{ "pause", w_pause },

+ 62 - 0
src/modules/audio/wrap_Source.cpp

@@ -20,6 +20,7 @@
 
 
 #include <limits>
 #include <limits>
 
 
+#include "sound/SoundData.h"
 #include "wrap_Source.h"
 #include "wrap_Source.h"
 
 
 namespace love
 namespace love
@@ -329,6 +330,63 @@ int w_Source_getChannels(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_Source_getFreeBufferCount(lua_State *L)
+{
+	Source *t = luax_checksource(L, 1);
+	lua_pushinteger(L, t->getFreeBufferCount());
+	return 1;
+}
+
+int w_Source_queue(lua_State *L)
+{
+	Source *t = luax_checksource(L, 1);
+	bool success;
+
+	if (luax_istype(L, 2, SOUND_SOUND_DATA_ID))
+	{
+		love::sound::SoundData *s = luax_totype<love::sound::SoundData>(L, 2, SOUND_SOUND_DATA_ID);
+
+		int offset = 0;
+		int length = s->getSize();
+
+		if (lua_gettop(L) == 4)
+		{
+			offset = luaL_checknumber(L, 3);
+			length = luaL_checknumber(L, 4);
+		}
+		else if (lua_gettop(L) == 3)
+			length = luaL_checknumber(L, 3);
+
+		if (length > (int)s->getSize() - offset || offset < 0 || length < 0)
+			return luaL_error(L, "Data region out of bounds.");
+
+		luax_catchexcept(L, [&]() {
+			success = t->queue((void*)((uintptr_t)s->getData() + (uintptr_t)offset), 
+				length, s->getSampleRate(), s->getBitDepth(), s->getChannels());
+		});
+	}
+	else if (lua_islightuserdata(L, 2))
+	{
+		int offset = luaL_checknumber(L, 3);
+		int length = luaL_checknumber(L, 4);
+		int sampleRate = luaL_checknumber(L, 5);
+		int bitDepth = luaL_checknumber(L, 6);
+		int channels = luaL_checknumber(L, 7);
+
+		if (length < 0 || offset < 0)
+			return luaL_error(L, "Data region out of bounds.");
+
+		luax_catchexcept(L, [&]() {
+			success = t->queue((void*)((uintptr_t)lua_touserdata(L, 2) + (uintptr_t)offset), length, sampleRate, bitDepth, channels);
+		});
+	}
+	else
+		return luax_typerror(L, 1, "Sound Data or lightuserdata");
+
+	luax_pushboolean(L, success);
+	return 1;
+}
+
 int w_Source_getType(lua_State *L)
 int w_Source_getType(lua_State *L)
 {
 {
 	Source *t = luax_checksource(L, 1);
 	Source *t = luax_checksource(L, 1);
@@ -381,6 +439,10 @@ static const luaL_Reg w_Source_functions[] =
 	{ "getRolloff", w_Source_getRolloff},
 	{ "getRolloff", w_Source_getRolloff},
 
 
 	{ "getChannels", w_Source_getChannels },
 	{ "getChannels", w_Source_getChannels },
+
+	{ "getFreeBufferCount", w_Source_getFreeBufferCount },
+	{ "queue", w_Source_queue },
+
 	{ "getType", w_Source_getType },
 	{ "getType", w_Source_getType },
 
 
 	{ 0, 0 }
 	{ 0, 0 }