├── README.md └── slam.lua /README.md: -------------------------------------------------------------------------------- 1 | SLAM 2 | ==== 3 | ... is the **Simple [LOVE] Audio Manager** formerly known as the **Benignly 4 | Designed Sound Manager.** It's a minimally invasive augmentation of [LOVE]'s 5 | audio module. In contrast to sources that can only have one simultaneous 6 | playing instance, SLAM sources create *instances* when played. This way you can 7 | play one source multiple times at once. Each instance will inherit the settings 8 | (volume, speed, looping, ...) of it's SLAM source, but can override them. 9 | 10 | SLAM also features tags, which can be used to modify a number of sources at the 11 | same time. 12 | 13 | Example 14 | ------- 15 | 16 | require 'slam' 17 | function love.load() 18 | music = love.audio.newSource('music.ogg', 'stream') -- creates a new SLAM source 19 | music:setLooping(true) -- all instances will be looping 20 | music:setVolume(.3) -- set volume for all instances 21 | love.audio.play(music) -- play music 22 | 23 | woosh = love.audio.newSource({'woosh1.ogg', 'woosh2.ogg'}, 'static') 24 | end 25 | 26 | function love.keypressed() 27 | local instance = woosh:play() -- creates a new instance 28 | instance:setPitch(.5 + math.random() * .5) -- set pitch for this instance only 29 | end 30 | 31 | 32 | Reference 33 | --------- 34 | 35 | ### Operations on Sources 36 | 37 | source = love.audio.newSource(what, how) 38 | 39 | Returns a new SLAM source. Accepts the same parameters as 40 | [love.audio.newSource](http://love2d.org/wiki/love.audio.newSource), with one 41 | major difference: `what` can be a table, in which case each new playing 42 | instance will pick an item of that table at random. 43 | 44 | 45 | instance = love.audio.play(source) 46 | instance = source:play() 47 | 48 | Plays a source, removes all paused instances and returns a handle to the player 49 | instance. Instances will inherit the settings (looping, pitch, volume) of 50 | `source`. 51 | 52 | 53 | love.audio.stop(source) 54 | source:stop() 55 | 56 | Stops all playing instances of a source. 57 | 58 | 59 | love.audio.stop() 60 | 61 | Stops all playing instances. 62 | 63 | 64 | source:pause() 65 | 66 | Pauses all playing instances of a source. 67 | 68 | 69 | source:resume() 70 | 71 | Resumes all paused instances of a source. **Note:** source:play() clears paused 72 | instances from a paused source. 73 | 74 | 75 | source:isStatic() 76 | 77 | Returns `true` if the source is static, `false` otherwise. 78 | 79 | 80 | looping = source:isLooping() 81 | source:setLooping(looping) 82 | pitch = source:getPitch() 83 | source:setPitch(pitch) 84 | volume = source:getVolume() 85 | source:setVolume(volume) 86 | 87 | Sets properties for all instances. Affects playing instances immediately. For 88 | details on the parameters, see the [LOVE wiki](http://love2d.org/wiki/Source). 89 | 90 | 91 | ### Instances 92 | 93 | All functions that affect LOVE Sources can be applied to SLAM instances. These 94 | are: 95 | 96 | love.audio.pause(instance) 97 | instance:pause() 98 | instance:isPaused() 99 | 100 | love.audio.play(instance) 101 | instance:play() 102 | 103 | love.audio.resume(instance) 104 | instance:resume() 105 | 106 | love.audio.rewind(instance) 107 | instance:rewind() 108 | 109 | instance:getDirection() 110 | instance:setDirection() 111 | 112 | instance:getPitch() 113 | instance:setPitch() 114 | 115 | instance:getPosition() 116 | instance:setPosition() 117 | 118 | instance:getVelocity() 119 | instance:setVelocity() 120 | 121 | instance:getVolume() 122 | instance:setVolume() 123 | 124 | instance:isLooping() 125 | instance:setLooping() 126 | 127 | See the [LOVE wiki](http://love2d.org/wiki/Source) for details. 128 | 129 | 130 | ### Tags 131 | 132 | With tags you can group several sources together and call functions upon them. 133 | A simple example: 134 | 135 | -- add some stuff to the background tag 136 | drums:addTags('music') 137 | baseline:addTags('background', 'music') -- a source can have multiple tags 138 | muttering:addTags('background') 139 | noise:addTags('background') 140 | cars:addTags('background') 141 | 142 | (...) 143 | 144 | love.audio.tags.background.setVolume(0) -- mute all background sounds 145 | love.audio.tags.music.setVolume(.1) -- ... but keep the music alive 146 | 147 | 148 | #### Functions 149 | 150 | source:addTags(tag, ...) 151 | 152 | Adds one or more tags to a source. By default, all sources are member of the 153 | tag `all`. 154 | 155 | 156 | source:removeTags(tag, ...) 157 | 158 | Remove one or more tags from a source. 159 | 160 | 161 | love.audio.tags.TAG.FUNCTION(...) 162 | love.audio.tags[TAG].FUNCTION(...) 163 | 164 | Calls `FUNCTION` on all sources tagged with `TAG`. 165 | 166 | 167 | [LOVE]: http://love2d.org 168 | -------------------------------------------------------------------------------- /slam.lua: -------------------------------------------------------------------------------- 1 | -- Simple LÖVE Audio Manager 2 | -- 3 | -- Copyright (c) 2011 Matthias Richter 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 5 | -- of this software and associated documentation files (the "Software"), to deal 6 | -- in the Software without restriction, including without limitation the rights 7 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | -- copies of the Software, and to permit persons to whom the Software is 9 | -- furnished to do so, subject to the following conditions: 10 | -- 11 | -- The above copyright notice and this permission notice shall be included in 12 | -- all copies or substantial portions of the Software. 13 | -- 14 | -- Except as contained in this notice, the name(s) of the above copyright holders 15 | -- shall not be used in advertising or otherwise to promote the sale, use or 16 | -- other dealings in this Software without prior written authorization. 17 | -- 18 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | -- THE SOFTWARE. 25 | -- 26 | 27 | local newInstance = love.audio.newSource 28 | local stop = love.audio.stop 29 | 30 | ------------------ 31 | -- source class -- 32 | ------------------ 33 | local Source = {} 34 | Source.__index = Source 35 | Source.__newindex = function(_, k) 36 | error(("Cannot write key %s"):format(tostring(k))) 37 | end 38 | 39 | local function remove_stopped(sources) 40 | local remove = {} 41 | for s in pairs(sources) do 42 | remove[s] = true 43 | end 44 | for s in pairs(remove) do 45 | sources[s] = nil 46 | end 47 | end 48 | 49 | local function get_target(target) 50 | if type(target) == "table" then 51 | return target[math.random(1, #target)] 52 | end 53 | return target 54 | end 55 | 56 | local play_instance, stop_instance 57 | function Source:play() 58 | remove_stopped(self.instances) 59 | if self._paused then 60 | self:stop() 61 | end 62 | local instance = newInstance(get_target(self.target), self.how) 63 | 64 | -- overwrite instance:stop() and instance:play() 65 | if not (play_instance and stop_instance) then 66 | play_instance = getmetatable(instance).play 67 | getmetatable(instance).play = error 68 | 69 | stop_instance = getmetatable(instance).stop 70 | getmetatable(instance).stop = function(this) 71 | stop_instance(this) 72 | self.instances[this] = nil 73 | end 74 | end 75 | 76 | instance:setLooping(self.looping) 77 | instance:setPitch(self.pitch) 78 | instance:setVolume(self.volume) 79 | 80 | self.instances[instance] = instance 81 | play_instance(instance) 82 | return instance 83 | end 84 | 85 | function Source:stop() 86 | for s in pairs(self.instances) do 87 | s:stop() 88 | end 89 | self._paused = false 90 | self.instances = {} 91 | end 92 | 93 | function Source:pause() 94 | if self._paused then 95 | return 96 | end 97 | for s in pairs(self.instances) do 98 | s:pause() 99 | end 100 | self._paused = true 101 | end 102 | 103 | function Source:resume() 104 | if not self._paused then 105 | return 106 | end 107 | for s in pairs(self.instances) do 108 | s:resume() 109 | end 110 | self._paused = false 111 | end 112 | 113 | function Source:addTags(tag, ...) 114 | if not tag then 115 | return 116 | end 117 | love.audio.tags[tag][self] = self 118 | return Source.addTags(self, ...) 119 | end 120 | 121 | function Source:removeTags(tag, ...) 122 | if not tag then 123 | return 124 | end 125 | love.audio.tags[tag][self] = nil 126 | return Source.removeTags(self, ...) 127 | end 128 | 129 | function Source:isStatic() 130 | return self.how ~= "stream" 131 | end 132 | 133 | -- getter/setter for looping, pitch and volume 134 | for _, property in ipairs({ "looping", "pitch", "volume" }) do 135 | local name = property:sub(1, 1):upper() .. property:sub(2) 136 | Source["get" .. name] = function(self) 137 | return self[property] 138 | end 139 | 140 | Source["set" .. name] = function(self, val) 141 | self[property] = val 142 | for s in pairs(self.instances) do 143 | s["set" .. name](s, val) 144 | end 145 | end 146 | end 147 | Source.isLooping = Source.getLooping 148 | 149 | -- returns true if at least a single source instance is playing 150 | function Source:isPlaying() 151 | local playing = false 152 | for s in pairs(self.instances) do 153 | if s:isPlaying() then 154 | playing = true 155 | end 156 | end 157 | return playing 158 | end 159 | 160 | -------------------------- 161 | -- love.audio interface -- 162 | -------------------------- 163 | function love.audio.newSource(target, how) 164 | local s = { 165 | _paused = false, 166 | target = target, 167 | how = how, 168 | instances = {}, 169 | looping = false, 170 | pitch = 1, 171 | volume = 1, 172 | } 173 | if how == "static" and type(target) == "string" then 174 | s.target = love.sound.newSoundData(target) 175 | end 176 | love.audio.tags.all[s] = s 177 | return setmetatable(s, Source) 178 | end 179 | 180 | function love.audio.play(source) 181 | assert(source and source.instances, "Can only play source objects.") 182 | return source:play() 183 | end 184 | 185 | function love.audio.stop(source) 186 | if source and source.stop then 187 | return source:stop() 188 | end 189 | stop() 190 | end 191 | 192 | ---------- 193 | -- tags -- 194 | ---------- 195 | local Tag = { __mode = "kv" } 196 | function Tag:__index(func) 197 | -- calls a function on all tagged sources 198 | return function(...) 199 | for s in pairs(self) do 200 | assert(type(s[func]) == "function", ("`%s' does not name a function."):format(func)) 201 | s[func](s, ...) 202 | end 203 | end 204 | end 205 | 206 | love.audio.tags = setmetatable({}, { 207 | __newindex = error, 208 | __index = function(t, k) 209 | local tag = setmetatable({}, Tag) 210 | rawset(t, k, tag) 211 | return tag 212 | end, 213 | }) 214 | --------------------------------------------------------------------------------