diff --git a/src/modules/audio/Audio.h b/src/modules/audio/Audio.h index 5a823338e..0a114a8ad 100644 --- a/src/modules/audio/Audio.h +++ b/src/modules/audio/Audio.h @@ -75,6 +75,7 @@ public: virtual Source *newSource(love::sound::Decoder *decoder) = 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. diff --git a/src/modules/audio/Source.cpp b/src/modules/audio/Source.cpp index d0356ec09..db3569e61 100644 --- a/src/modules/audio/Source.cpp +++ b/src/modules/audio/Source.cpp @@ -63,6 +63,7 @@ StringMap::Entry Source::typeEntries[] = { {"static", Source::TYPE_STATIC}, {"stream", Source::TYPE_STREAM}, + {"queue", Source::TYPE_QUEUE}, }; StringMap Source::types(Source::typeEntries, sizeof(Source::typeEntries)); diff --git a/src/modules/audio/Source.h b/src/modules/audio/Source.h index 7530b91db..03a1dff75 100644 --- a/src/modules/audio/Source.h +++ b/src/modules/audio/Source.h @@ -38,6 +38,7 @@ public: { TYPE_STATIC, TYPE_STREAM, + TYPE_QUEUE, TYPE_MAX_ENUM }; @@ -100,6 +101,10 @@ public: virtual float getMaxDistance() const = 0; virtual int getChannels() const = 0; + + virtual bool isQueueable() const = 0; + virtual void queueData(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels) = 0; + virtual Type getType() const; static bool getConstant(const char *in, Type &out); diff --git a/src/modules/audio/null/Audio.cpp b/src/modules/audio/null/Audio.cpp index 376bfae00..e70113b17 100644 --- a/src/modules/audio/null/Audio.cpp +++ b/src/modules/audio/null/Audio.cpp @@ -51,6 +51,11 @@ love::audio::Source *Audio::newSource(love::sound::SoundData *) return new Source(); } +love::audio::Source *Audio::newSource(int sampleRate, int bitDepth, int channels) +{ + return new Source(); +} + int Audio::getSourceCount() const { return 0; diff --git a/src/modules/audio/null/Audio.h b/src/modules/audio/null/Audio.h index d186ccff2..e4143d447 100644 --- a/src/modules/audio/null/Audio.h +++ b/src/modules/audio/null/Audio.h @@ -46,6 +46,7 @@ public: // Implements Audio. love::audio::Source *newSource(love::sound::Decoder *decoder); love::audio::Source *newSource(love::sound::SoundData *soundData); + love::audio::Source *newSource(int sampleRate, int bitDepth, int channels); int getSourceCount() const; int getMaxSources() const; bool play(love::audio::Source *source); diff --git a/src/modules/audio/null/Source.cpp b/src/modules/audio/null/Source.cpp index 1a6b205a9..f58bf8309 100644 --- a/src/modules/audio/null/Source.cpp +++ b/src/modules/audio/null/Source.cpp @@ -217,6 +217,17 @@ int Source::getChannels() const return 2; } +bool Source::isQueueable() const +{ + return false; +} + +void Source::queueData(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels) +{ + return; +} + + } // null } // audio } // love diff --git a/src/modules/audio/null/Source.h b/src/modules/audio/null/Source.h index 7feceee77..5e9e25113 100644 --- a/src/modules/audio/null/Source.h +++ b/src/modules/audio/null/Source.h @@ -76,6 +76,9 @@ public: virtual float getMaxDistance() const; virtual int getChannels() const; + virtual bool isQueueable() const; + virtual void queueData(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels); + private: float pitch; diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 9a7c00016..c3b1bd05e 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -157,6 +157,11 @@ love::audio::Source *Audio::newSource(love::sound::SoundData *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 { return pool->getSourceCount(); diff --git a/src/modules/audio/openal/Audio.h b/src/modules/audio/openal/Audio.h index ab586aac7..b676abf8c 100644 --- a/src/modules/audio/openal/Audio.h +++ b/src/modules/audio/openal/Audio.h @@ -71,6 +71,7 @@ public: // Implements Audio. love::audio::Source *newSource(love::sound::Decoder *decoder); love::audio::Source *newSource(love::sound::SoundData *soundData); + love::audio::Source *newSource(int sampleRate, int bitDepth, int channels); int getSourceCount() const; int getMaxSources() const; bool play(love::audio::Source *source); diff --git a/src/modules/audio/openal/Pool.cpp b/src/modules/audio/openal/Pool.cpp index 6ccd325f4..9af27f5de 100644 --- a/src/modules/audio/openal/Pool.cpp +++ b/src/modules/audio/openal/Pool.cpp @@ -283,6 +283,12 @@ double Pool::getDuration(Source *source, void *unit) return source->getDurationAtomic(unit); } +void Pool::queueData(Source *source, void *data, ALsizei length) +{ + thread::Lock lock(mutex); + source->queueDataAtomic(data, length); +} + ALuint Pool::findi(const Source *source) const { std::map::const_iterator i = playing.find((Source *)source); diff --git a/src/modules/audio/openal/Pool.h b/src/modules/audio/openal/Pool.h index 82b1272d6..facf75c04 100644 --- a/src/modules/audio/openal/Pool.h +++ b/src/modules/audio/openal/Pool.h @@ -91,6 +91,7 @@ public: void seek(Source *source, float offset, void *unit); float tell(Source *source, void *unit); double getDuration(Source *source, void *unit); + void queueData(Source *source, void *data, ALsizei length); bool play(const std::vector &sources); void stop(const std::vector &sources); diff --git a/src/modules/audio/openal/Source.cpp b/src/modules/audio/openal/Source.cpp index 327c616e9..2424f51e3 100644 --- a/src/modules/audio/openal/Source.cpp +++ b/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 \"queue\" type sound Sources can be queued with sound data.") + { + } + +}; + +class QueueNegativeLengthException : public love::Exception +{ +public: + + QueueNegativeLengthException() + : Exception("Data length cannot be negative.") + { + } + +}; + +class QueueMalformedLengthException : public love::Exception +{ +public: + + QueueMalformedLengthException(int bytes) + : Exception("Data lenght must be a multiple of sample size (%d bytes).", bytes) + { + } + +}; + StaticDataBuffer::StaticDataBuffer(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) : size(size) { @@ -134,12 +178,55 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder) , bitDepth(decoder->getBitDepth()) , decoder(decoder) , toLoop(0) + , unusedBufferTop(-1) { if (getFormat(decoder->getChannels(), decoder->getBitDepth()) == 0) throw InvalidFormatException(decoder->getChannels(), decoder->getBitDepth()); 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}; setFloatv(position, z); @@ -169,13 +256,18 @@ Source::Source(const Source &s) , bitDepth(s.bitDepth) , decoder(nullptr) , toLoop(0) + , unusedBufferTop(-1) { if (type == TYPE_STREAM) { if (s.decoder.get()) decoder.set(s.decoder->clone(), Acquire::NORETAIN); - + } + if (type != TYPE_STATIC) + { alGenBuffers(MAX_BUFFERS, streamBuffers); + for (unsigned int i = 0; i < MAX_BUFFERS; i++) + unusedBuffers[i] = streamBuffers[i]; } setFloatv(position, s.position); @@ -188,7 +280,7 @@ Source::~Source() if (valid) pool->stop(this); - if (type == TYPE_STREAM) + if (type != TYPE_STATIC) alDeleteBuffers(MAX_BUFFERS, streamBuffers); } @@ -242,49 +334,65 @@ bool Source::update() if (!valid) return false; - if (type == TYPE_STATIC) + switch (type) { - // Looping mode could have changed. - alSourcei(source, AL_LOOPING, isLooping() ? AL_TRUE : AL_FALSE); - return !isFinished(); - } - else if (type == TYPE_STREAM && (isLooping() || !isFinished())) - { - // Number of processed buffers. - ALint processed = 0; + 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 (isLooping() || !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; - alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); - - while (processed--) + offsetSamples += (curOffsetSamples - newOffsetSamples); + offsetSeconds += (curOffsetSecs - newOffsetSecs); + + for (unsigned int i = 0; i < 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 < processed; i++) + { + ALint size; + alGetBufferi(buffers[i], AL_SIZE, &size); + bufferedBytes -= size; + unusedBufferPush(buffers[i]); + } + + return !isFinished(); } - - return true; } return false; @@ -355,7 +463,7 @@ void Source::seekAtomic(float offset, void *unit) if (type == TYPE_STREAM) decoder->seek(offsetSeconds); - else if (valid) // Playing static source + else if (valid) // Playing static or queue { alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples); offsetSamples = offsetSeconds = 0; @@ -401,24 +509,37 @@ double Source::getDurationAtomic(void *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; + } + default: return 0.0; } } @@ -552,56 +673,149 @@ bool Source::isLooping() const return looping; } +void Source::queueData(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels) +{ + if (type != TYPE_QUEUE) + throw QueueTypeMismatchException(); + + if (length < 0) + throw QueueNegativeLengthException(); + + if (dataSampleRate != sampleRate || dataBitDepth != bitDepth || dataChannels != channels ) + throw QueueFormatMismatchException(); + + if (length % (bitDepth / 8 * channels) != 0) + throw QueueMalformedLengthException(bitDepth / 8 * channels); + + if (length > 0) + pool->queueData(this, data, (ALsizei)length); +} + +void Source::queueDataAtomic(void *data, ALsizei length) +{ + if (valid) + { + ALuint buffer = unusedBufferPeek(); + if (buffer == AL_NONE) + return; //FIXME: out of buffer space handling? + + alBufferData(buffer, getFormat(channels, bitDepth), data, length, sampleRate); + alSourceQueueBuffers(source, 1, &buffer); + unusedBufferPop(); + } + else //in not valid state, queue stack is "reversed" + { + //remaining buffers are stored beyond the tip of unused stack + if (unusedBufferTop == MAX_BUFFERS - 1) + return; + + ALuint buffer = unusedBuffers[++unusedBufferTop]; + alBufferData(buffer, getFormat(channels, bitDepth), data, length, sampleRate); + //new buffer must go last, so it goes to the base of the stack + for (unsigned int i = unusedBufferTop; i > 0; i--) + unusedBuffers[i] = unusedBuffers[i - 1]; + unusedBuffers[0] = buffer; + } + bufferedBytes += length; +} + +bool Source::isQueueable() const +{ + //in not valid state, queue stack is "reversed" + if (type != TYPE_QUEUE || (valid && unusedBufferTop < 0) || (!valid && unusedBufferTop == MAX_BUFFERS - 1)) + return false; + return true; +} + void Source::prepareAtomic() { // This Source may now be associated with an OpenAL source that still has // the properties of another love Source. Let's reset it to the settings // of the new one. reset(); - - if (type == TYPE_STATIC) + + switch (type) { - alSourcei(source, AL_BUFFER, staticBuffer->getBuffer()); - if (offsetSamples >= 0) - alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples); - } - else if (type == TYPE_STREAM) - { - 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); } - - if (usedBuffers > 0) - alSourceQueueBuffers(source, usedBuffers, streamBuffers); + } } void Source::teardownAtomic() { - if (type == TYPE_STATIC) + switch (type) { - alSourcef(source, AL_SAMPLE_OFFSET, 0); - } - else if (type == TYPE_STREAM) - { - decoder->seek(0); - - int queued = 0; - alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); - - while (queued--) + case TYPE_STATIC: + alSourcef(source, AL_SAMPLE_OFFSET, 0); + break; + case TYPE_STREAM: { + ALint queued; ALuint buffer; - alSourceUnqueueBuffers(source, 1, &buffer); + + alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); + for (unsigned int i = 0; i < queued; i++) + { + //since we only unqueue 1 buffer, it's OK to use singular variable pointer instead of array + alSourceUnqueueBuffers(source, 1, &buffer); + unusedBufferPush(buffer); + } + break; + } + case TYPE_QUEUE: + { + ALint queued; + ALuint buffers[MAX_BUFFERS]; + ALuint buffer; + int unused = 0; + + //store all unused buffers, also clear stack + while (unusedBufferPeek() != AL_NONE) + buffers[unused++] = *unusedBufferPop(); + + alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); + //stack needs to be filled in reverse order + for (unsigned int i = queued; i > 0; i--) + { + //since we only unqueue 1 buffer, it's OK to use singular variable pointer instead of array + alSourceUnqueueBuffers(source, 1, &buffer); + unusedBufferPush(buffer); + } + // put unused buffers at the end of stack + for (unsigned int i = 0; i < unused; i++) + unusedBuffers[unusedBufferTop + 1 + i] = buffers[i]; + + break; } } @@ -629,7 +843,7 @@ bool Source::playAtomic(ALuint source) valid = true; //if it fails it will be set to false again //but this prevents a horrible, horrible bug - if (type == TYPE_STATIC) + if (type != TYPE_STREAM) offsetSamples = offsetSeconds = 0; return success; @@ -681,7 +895,7 @@ bool Source::playAtomic(const std::vector &sources, const Source *source = (Source*) _source; source->valid = source->valid || success; - if (success && source->type == TYPE_STATIC) + if (success && source->type != TYPE_STREAM) source->offsetSamples = source->offsetSeconds = 0; } @@ -784,6 +998,21 @@ ALenum Source::getFormat(int channels, int bitDepth) const return 0; } +ALuint Source::unusedBufferPeek() +{ + return (unusedBufferTop < 0) ? AL_NONE : unusedBuffers[unusedBufferTop]; +} + +ALuint *Source::unusedBufferPop() +{ + return &unusedBuffers[unusedBufferTop--]; +} + +void Source::unusedBufferPush(ALuint buffer) +{ + unusedBuffers[++unusedBufferTop] = buffer; +} + int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d) { // Get more sound data. diff --git a/src/modules/audio/openal/Source.h b/src/modules/audio/openal/Source.h index f1cc0833c..9026a238c 100644 --- a/src/modules/audio/openal/Source.h +++ b/src/modules/audio/openal/Source.h @@ -86,6 +86,7 @@ public: Source(Pool *pool, love::sound::SoundData *soundData); Source(Pool *pool, love::sound::Decoder *decoder); + Source(Pool *pool, int sampleRate, int bitDepth, int channels); Source(const Source &s); virtual ~Source(); @@ -130,6 +131,10 @@ public: virtual float getMaxDistance() const; virtual int getChannels() const; + virtual bool isQueueable() const; + virtual void queueData(void *data, int length, int dataSampleRate, int dataBitDepth, int dataChannels); + virtual void queueDataAtomic(void *data, ALsizei length); + void prepareAtomic(); void teardownAtomic(); @@ -159,13 +164,21 @@ private: int streamAtomic(ALuint buffer, love::sound::Decoder *d); + ALuint unusedBufferPeek(); + ALuint *unusedBufferPop(); + void unusedBufferPush(ALuint buffer); + Pool *pool; ALuint source; bool valid; static const unsigned int MAX_BUFFERS = 8; ALuint streamBuffers[MAX_BUFFERS]; - + ALuint unusedBuffers[MAX_BUFFERS]; + + int unusedBufferTop; + ALsizei bufferedBytes; + StrongRef staticBuffer; float pitch; diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 32e77f251..6b0fde48d 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -44,27 +44,31 @@ int w_getSourceCount(lua_State *L) int w_newSource(lua_State *L) { - if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_ID) || luax_istype(L, 1, FILESYSTEM_FILE_DATA_ID)) - luax_convobj(L, 1, "sound", "newDecoder"); - - Source::Type stype = Source::TYPE_STREAM; - - const char *stypestr = luaL_checkstring(L, 2); - if (stypestr && !Source::getConstant(stypestr, stype)) - return luaL_error(L, "Invalid source type: %s", stypestr); - - if (stype == Source::TYPE_STATIC && luax_istype(L, 1, SOUND_DECODER_ID)) - luax_convobj(L, 1, "sound", "newSoundData"); - Source *t = 0; + + if (lua_isnumber(L, 1) && lua_isnumber(L, 2) && lua_isnumber(L, 3)) + t = instance()->newSource((int)lua_tonumber(L, 1), (int)lua_tonumber(L, 2), (int)lua_tonumber(L, 3)); + else + { + if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_ID) || luax_istype(L, 1, FILESYSTEM_FILE_DATA_ID)) + luax_convobj(L, 1, "sound", "newDecoder"); - luax_catchexcept(L, [&]() { - if (luax_istype(L, 1, SOUND_SOUND_DATA_ID)) - t = instance()->newSource(luax_totype(L, 1, SOUND_SOUND_DATA_ID)); - else if (luax_istype(L, 1, SOUND_DECODER_ID)) - t = instance()->newSource(luax_totype(L, 1, SOUND_DECODER_ID)); - }); + Source::Type stype = Source::TYPE_STREAM; + const char *stypestr = lua_isnoneornil(L, 2) ? 0 : lua_tostring(L, 2); + if (stypestr && !Source::getConstant(stypestr, stype)) + return luaL_error(L, "Invalid source type: %s", stypestr); + + if (stype == Source::TYPE_STATIC && luax_istype(L, 1, SOUND_DECODER_ID)) + luax_convobj(L, 1, "sound", "newSoundData"); + + luax_catchexcept(L, [&]() { + if (luax_istype(L, 1, SOUND_SOUND_DATA_ID)) + t = instance()->newSource(luax_totype(L, 1, SOUND_SOUND_DATA_ID)); + else if (luax_istype(L, 1, SOUND_DECODER_ID)) + t = instance()->newSource(luax_totype(L, 1, SOUND_DECODER_ID)); + }); + } if (t) { luax_pushtype(L, AUDIO_SOURCE_ID, t); @@ -72,7 +76,7 @@ int w_newSource(lua_State *L) return 1; } else - return luax_typerror(L, 1, "Decoder or SoundData"); + return luax_typerror(L, 1, "Decoder, SoundData or format description"); } static std::vector readSourceList(lua_State *L, int n) diff --git a/src/modules/audio/wrap_Source.cpp b/src/modules/audio/wrap_Source.cpp index f4bdaa397..37a4ec898 100644 --- a/src/modules/audio/wrap_Source.cpp +++ b/src/modules/audio/wrap_Source.cpp @@ -20,6 +20,7 @@ #include +#include "sound/SoundData.h" #include "wrap_Source.h" namespace love @@ -329,6 +330,35 @@ int w_Source_getChannels(lua_State *L) return 1; } +int w_Source_isQueueable(lua_State *L) +{ + Source *t = luax_checksource(L, 1); + luax_pushboolean(L, t->isQueueable()); + return 1; +} + +int w_Source_queueData(lua_State *L) +{ + Source *t = luax_checksource(L, 1); + luax_catchexcept(L, [&]() { + if (luax_istype(L, 2, SOUND_SOUND_DATA_ID)) + { + love::sound::SoundData *s = luax_totype(L, 2, SOUND_SOUND_DATA_ID); + t->queueData(s->getData(), lua_isnumber(L, 3) ? (int)lua_tonumber(L, 3) : s->getSize(), s->getSampleRate(), s->getBitDepth(), s->getChannels()); + } + else if (lua_islightuserdata(L, 2)) + { + if (lua_isnumber(L, 4) && lua_isnumber(L, 5) && lua_isnumber(L, 6)) + t->queueData(lua_touserdata(L, 2), (int)lua_tonumber(L, 3), (int)lua_tonumber(L, 4), (int)lua_tonumber(L, 5), (int)lua_tonumber(L, 6)); + else + return luaL_error(L, "No format specified."); + } + else + return luaL_error(L, "Invalid data type."); + }); + return 1; +} + int w_Source_getType(lua_State *L) { Source *t = luax_checksource(L, 1); @@ -381,6 +411,10 @@ static const luaL_Reg w_Source_functions[] = { "getRolloff", w_Source_getRolloff}, { "getChannels", w_Source_getChannels }, + + { "isQueueable", w_Source_isQueueable }, + { "queueData", w_Source_queueData }, + { "getType", w_Source_getType }, { 0, 0 }