├── README.md ├── conf.lua ├── lib ├── easy.lua ├── lem.lua ├── lue.lua ├── push.lua ├── shack.lua ├── soft.lua ├── stager.lua ├── trail.lua └── wave.lua ├── main.lua └── scenes └── game.lua /README.md: -------------------------------------------------------------------------------- 1 | ![kickstart][kickstart] 2 | 3 | # love-kickstart 4 | 5 | love-kickstarter is a project template that aims at providing the most straightforward tools to quickly set up a LÖVE game, which makes it useful for game jams and hackatons. 6 | 7 | Setup - **main.lua** 8 | ---------------- 9 | 10 | Set the internal resolution of your game 11 | ```lua 12 | WWIDTH, WHEIGHT = 1920, 1080 13 | ``` 14 | 15 | Add external libraries and custom classes (e.g. Player class) 16 | ```lua 17 | --Includes 18 | require "include.player" --example 19 | ``` 20 | 21 | Start making your game by editing and adding new scenes! By default, **main.lua** redirects to the **game** scene, located in "*/scenes*". 22 | ```lua 23 | --game.lua 24 | 25 | function game.update(dt) 26 | moveObjects(dt) 27 | end 28 | 29 | function game.draw() 30 | drawObjects() 31 | end 32 | ``` 33 | 34 | What's included 35 | ---------------- 36 | 37 | [push](https://github.com/Ulydev/push) - Handles everything resolution-related 38 | 39 | [shack](https://github.com/Ulydev/shack) - Lets you quickly add screen effects such as shake, rotate, shear and scale 40 | 41 | [lem](https://github.com/Ulydev/lem) - Allows you to set up events and call them whenever and wherever in your code 42 | 43 | [lue](https://github.com/Ulydev/lue) - Adds hue to your game 44 | 45 | [stager](https://github.com/Ulydev/stager) - Manages all the different scenes and lets you switch between them 46 | 47 | [wave](https://github.com/Ulydev/wave) - Audio manager with sound parsing functionalities 48 | 49 | [soft](https://github.com/Ulydev/soft) - Interpolates values 50 | 51 | [easy](https://github.com/Ulydev/easy) - A minimalist easing library 52 | 53 | [trail](https://github.com/Ulydev/trail) - Adds trail effects to your game 54 | 55 | [kickstart]: http://s32.postimg.org/t5bydkfad/love.png 56 | -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | 3 | t.identity = "model" 4 | 5 | t.title = "Game" --sets the name of the game (dont remove quotations) 6 | 7 | t.author = "You" 8 | 9 | end -------------------------------------------------------------------------------- /lib/easy.lua: -------------------------------------------------------------------------------- 1 | -- easy.lua v0.1 2 | 3 | -- Copyright (c) 2016 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local easy = {} 9 | 10 | local default = { 11 | quad = "x * x", 12 | cubic = "x * x * x", 13 | quart = "x * x * x * x", 14 | quint = "x * x * x * x * x", 15 | expo = "2 ^ (10 * (x - 1))", 16 | sine = "-math.cos(x * (math.pi * .5)) + 1", 17 | circ = "-(math.sqrt(1 - (x * x)) - 1)", 18 | back = "x * x * (2.7 * x - 1.7)", 19 | elastic = "-(2^(10 * (x - 1)) * math.sin((x - 1.075) * (math.pi * 2) / .3))" 20 | } 21 | 22 | local func = {} 23 | 24 | local function makefunc(str, expr) 25 | local load = loadstring or load 26 | return load("return function(x) " .. str:gsub("%$e", expr) .. " end")() 27 | end 28 | 29 | local function generateEase(name, f) 30 | func[name .. "in"] = makefunc("return $e", f) 31 | func[name .. "out"] = makefunc([[ 32 | x = 1 - x 33 | return 1 - ($e) 34 | ]], f) 35 | func[name .. "inout"] = makefunc([[ 36 | x = x * 2 37 | if x < 1 then 38 | return .5 * ($e) 39 | else 40 | x = 2 - x 41 | return .5 * (1 - ($e)) + .5 42 | end 43 | ]], f) 44 | end 45 | 46 | function easy:get(name, value) 47 | if not func[name] then name = name .. "in" end 48 | assert(func[name] ~= nil, "Function doesn't exist") 49 | return func[name](value) 50 | end 51 | 52 | function easy:add(name, f) 53 | assert(func[name] == nil, "Function already exists") 54 | return generateEase(name, f) 55 | end 56 | 57 | --all credits to rxi 58 | 59 | for k, v in pairs(default) do 60 | generateEase(k, v) 61 | end 62 | 63 | --shortcut to easy:get 64 | 65 | setmetatable(easy, { __call = easy.get }) 66 | 67 | return easy 68 | -------------------------------------------------------------------------------- /lib/lem.lua: -------------------------------------------------------------------------------- 1 | -- lem.lua v0.1 2 | 3 | -- Copyright (c) 2015 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local lem = {} 9 | lem.__index = lem 10 | 11 | function lem:new() 12 | local _manager = {} 13 | setmetatable(_manager, lem) 14 | 15 | _manager.handlers = {} 16 | 17 | return _manager 18 | end 19 | 20 | --[[ Private ]]-- 21 | 22 | function lem:eventExists(eventname) 23 | return self.handlers[eventname] and true or false 24 | end 25 | 26 | function lem:getHandlers(eventname) 27 | return self.handlers[eventname] 28 | end 29 | 30 | function lem:initHandlers(eventname) 31 | self.handlers[eventname] = {} 32 | end 33 | 34 | function lem:registerHandler(eventname, callback) 35 | table.insert(self.handlers[eventname], callback) 36 | end 37 | 38 | --[[ Public ]]-- 39 | 40 | function lem:on(eventname, callback) 41 | if not self:eventExists(eventname) then self:initHandlers(eventname) end 42 | self:registerHandler(eventname, callback) 43 | return self 44 | end 45 | 46 | function lem:emit(eventname, params) 47 | if not self:eventExists(eventname) then return self end 48 | for k, v in pairs(self:getHandlers(eventname)) do --v = callback 49 | v(params) 50 | end 51 | return self 52 | end 53 | 54 | function lem:remove(eventname, callback) 55 | if not self:eventExists(eventname) then return self end 56 | local callbackString = string.dump(callback) 57 | for k, v in pairs(self:getHandlers(eventname)) do 58 | if string.dump(v) == callbackString then 59 | self.handlers[eventname][k] = nil 60 | end 61 | end 62 | return self 63 | end 64 | 65 | function lem:reset(eventname) 66 | if not self:eventExists(eventname) then return self end 67 | self:initHandlers(eventname) 68 | return self 69 | end 70 | 71 | function lem:getListenerCount(eventname) 72 | return (self:eventExists(eventname) and #self:getHandlers(eventname) or -1) 73 | end 74 | 75 | --[[ Aliases ]]-- 76 | 77 | function lem:addListener(...) return self:on(...) end 78 | function lem:removeListener(...) return self:remove(...) end 79 | function lem:removeListeners(...) return self:reset(...) end 80 | 81 | --[[ End ]]-- 82 | 83 | return lem 84 | -------------------------------------------------------------------------------- /lib/lue.lua: -------------------------------------------------------------------------------- 1 | -- lue.lua v0.1 2 | 3 | -- Copyright (c) 2016 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local lue, lueObject = { 9 | hue = 0, 10 | intensity = 0, 11 | speed = 50, 12 | c = {} 13 | }, {} 14 | 15 | setmetatable(lue, lue) 16 | 17 | --[[ Private ]]-- 18 | 19 | local function lerp(a, b, k) --smooth transitions 20 | if a == b then 21 | return a 22 | else 23 | if math.abs(a-b) < 0.005 then return b else return a * (1-k) + b * k end 24 | end 25 | end 26 | 27 | local function HSL(h, s, l, a) --where the magic happens 28 | if s<=0 then return l,l,l,a end 29 | h, s, l = (h%255)/256*6, s/255, l/255 30 | local c = (1-math.abs(2*l-1))*s 31 | local x = (1-math.abs(h%2-1))*c 32 | local m,r,g,b = (l-.5*c), 0,0,0 33 | if h < 1 then r,g,b = c,x,0 34 | elseif h < 2 then r,g,b = x,c,0 35 | elseif h < 3 then r,g,b = 0,c,x 36 | elseif h < 4 then r,g,b = 0,x,c 37 | elseif h < 5 then r,g,b = x,0,c 38 | else r,g,b = c,0,x 39 | end return {(r+m)*255, (g+m)*255, (b+m)*255, a} 40 | end 41 | 42 | --[[ Public ]]-- 43 | 44 | function lue:update(dt) 45 | 46 | self.hue = self.hue + self.speed * dt 47 | if self.hue > 255 then 48 | self.hue = self.hue - 255 49 | elseif self.hue < 0 then 50 | self.hue = self.hue + 255 51 | end 52 | 53 | for k, v in pairs(self.c) do 54 | v:update(dt) 55 | end 56 | 57 | end 58 | 59 | function lueObject:update(dt) 60 | 61 | if self.target then 62 | local type, target = self.target.type, self.target.color 63 | if type == "hue" then 64 | target = lue:getHueColor(unpack(target)) 65 | end 66 | for i = 1, 4 do 67 | self.color[i] = lerp(self.color[i] or 255, target[i] or 255, self.speed * dt) 68 | end 69 | elseif self.hue then 70 | self.color = lue:getHueColor(unpack(self.hue)) 71 | end 72 | 73 | end 74 | 75 | -- 76 | 77 | function lue:initColor(name, f) 78 | 79 | if not self.c[name] then self.c[name] = lue:newColor(f) end 80 | 81 | end 82 | 83 | function lue:newColor(f) 84 | 85 | local _object = {} 86 | 87 | setmetatable(_object, { __index = lueObject }) 88 | 89 | return _object 90 | end 91 | 92 | function lue:setColor(name, f) 93 | 94 | self:initColor(name) 95 | 96 | return self.c[name]:setColor(f) 97 | 98 | end 99 | 100 | function lueObject:setColor(f) 101 | 102 | if not (f.color or f.speed or f.hue) then f = { color = f } end --mind blown 103 | 104 | f.speed = f.speed and (f.speed == true and 1 or f.speed) or false 105 | 106 | if f.speed and self.color then 107 | self.target = { type = f.hue and "hue" or "color", color = f.color or f.hue } 108 | self.speed = f.speed 109 | else 110 | self.color = f.color or lue:getHueColor(unpack(f.hue)) 111 | if f.hue then self.hue = f.hue end 112 | end 113 | 114 | return self 115 | 116 | end 117 | 118 | function lue:getColor(name, target) 119 | 120 | return self.c[name] and self.c[name]:getColor(target) or false 121 | 122 | end 123 | 124 | function lueObject:getColor(target) 125 | 126 | local color = ((target and self.target) and self.target or self.color) 127 | return color and {color[1], color[2], color[3], color[4] or 255} or false 128 | 129 | end 130 | 131 | -- 132 | 133 | function lue:setHueIntensity(intensity) 134 | self.intensity = intensity 135 | return self 136 | end 137 | 138 | function lue:setHueSpeed(speed) 139 | self.speed = speed 140 | return self 141 | end 142 | 143 | -- 144 | 145 | function lue:getHueColor(s, l, a, offset) 146 | local _s, _l = math.min(math.max(0, s+self.intensity), 255), math.min(math.max(0, l+self.intensity), 255) 147 | return HSL(self.hue + (offset or 0), _s, _l, a) 148 | end 149 | 150 | function lue:getHueIntensity() return self.intensity end 151 | 152 | function lue:getHueSpeed() return self.speed end 153 | 154 | function lue:getHue() return self.hue end 155 | 156 | --[[ End ]]-- 157 | 158 | return lue -------------------------------------------------------------------------------- /lib/push.lua: -------------------------------------------------------------------------------- 1 | -- push.lua v0.1 2 | 3 | -- Copyright (c) 2016 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | -- push.lua v0.1 9 | 10 | -- Copyright (c) 2015 Ulysse Ramage 11 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | local push = {} 16 | setmetatable(push, push) 17 | 18 | function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, f) 19 | 20 | f = f or {} 21 | 22 | self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT 23 | self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT 24 | self._fullscreen = f.fullscreen or self._fullscreen or false 25 | self._resizable = f.resizable or self._resizable or false 26 | if f.canvas == nil then f.canvas = true end 27 | 28 | love.window.setMode( self._RWIDTH, self._RHEIGHT, {fullscreen = self._fullscreen, borderless = false, resizable = self._resizable} ) 29 | 30 | self:initValues() 31 | 32 | if f.canvas then self:createCanvas() end 33 | 34 | self._borderColor = {0, 0, 0} 35 | 36 | end 37 | 38 | function push:createCanvas() 39 | self._canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT) 40 | end 41 | 42 | function push:initValues() 43 | self._SCALEX, self._SCALEY = self._RWIDTH/self._WWIDTH, self._RHEIGHT/self._WHEIGHT 44 | self._SCALE = math.min(self._SCALEX, self._SCALEY) 45 | self._OFFSET = {x = (self._SCALEX - self._SCALE) * (self._WWIDTH/2), y = (self._SCALEY - self._SCALE) * (self._WHEIGHT/2)} 46 | self._GWIDTH, self._GHEIGHT = self._RWIDTH-self._OFFSET.x*2, self._RHEIGHT-self._OFFSET.y*2 47 | 48 | self._INV_SCALE = 1/self._SCALE 49 | end 50 | 51 | function push:setShader(shader) 52 | self._shader = shader 53 | end 54 | 55 | function push:apply(operation, shader) 56 | if operation == "start" then 57 | if self._canvas then 58 | love.graphics.push() 59 | love.graphics.setCanvas(self._canvas) 60 | else 61 | love.graphics.translate(self._OFFSET.x, self._OFFSET.y) 62 | love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE, self._WHEIGHT*self._SCALE) 63 | love.graphics.push() 64 | love.graphics.scale(self._SCALE) 65 | end 66 | elseif operation == "end" then 67 | if self._canvas then 68 | love.graphics.pop() 69 | love.graphics.setCanvas() 70 | 71 | love.graphics.translate(self._OFFSET.x, self._OFFSET.y) 72 | love.graphics.setColor(255, 255, 255) 73 | love.graphics.setShader(shader or self._shader) 74 | love.graphics.draw(self._canvas, 0, 0, 0, self._SCALE, self._SCALE) 75 | love.graphics.setCanvas(self._canvas) 76 | love.graphics.clear() 77 | love.graphics.setCanvas() 78 | love.graphics.setShader() 79 | else 80 | love.graphics.pop() 81 | love.graphics.setScissor() 82 | end 83 | end 84 | end 85 | 86 | function push:calculateScale(offset) 87 | self._SCALEX, self._SCALEY = self._RWIDTH/self._WWIDTH, self._RHEIGHT/self._WHEIGHT 88 | self._SCALE = math.min(self._SCALEX, self._SCALEY)+offset 89 | self._OFFSET = {x = (self._SCALEX - self._SCALE) * (self._WWIDTH/2), y = (self._SCALEY - self._SCALE) * (self._WHEIGHT/2)} 90 | end 91 | 92 | function push:setBorderColor(color) 93 | self._borderColor = color 94 | end 95 | 96 | function push:toGame(x, y) 97 | x, y = x-self._OFFSET.x, y-self._OFFSET.y 98 | local normalX, normalY = x/self._GWIDTH, y/self._GHEIGHT 99 | x, y = (x>=0 and x<=self._WWIDTH*self._SCALE) and normalX*self._WWIDTH or nil, (y>=0 and y<=self._WHEIGHT*self._SCALE) and normalY*self._WHEIGHT or nil 100 | return x, y 101 | end 102 | 103 | --doesn't work - TODO 104 | function push:toReal(x, y) 105 | return x+self._OFFSET.x, y+self._OFFSET.y 106 | end 107 | 108 | function push:switchFullscreen(winw, winh) 109 | self._fullscreen = not self._fullscreen 110 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 111 | self._RWIDTH = self._fullscreen and windowWidth or winw or windowWidth*.5 112 | self._RHEIGHT = self._fullscreen and windowHeight or winh or windowHeight*.5 113 | self:initValues() 114 | love.window.setFullscreen(self._fullscreen, "desktop") 115 | end 116 | 117 | function push:resize(w, h) 118 | self._RWIDTH = w 119 | self._RHEIGHT = h 120 | self:initValues() 121 | end 122 | 123 | function push:getWidth() return self._WWIDTH end 124 | function push:getHeight() return self._WHEIGHT end 125 | function push:getDimensions() return self._WWIDTH, self._WHEIGHT end 126 | 127 | return push 128 | -------------------------------------------------------------------------------- /lib/shack.lua: -------------------------------------------------------------------------------- 1 | -- shack.lua v0.1 2 | 3 | -- Copyright (c) 2015 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local shack = { 9 | shaking = 0, 10 | shakingTarget = 0, 11 | 12 | rotation = 0, 13 | rotationTarget = 0, 14 | 15 | scale = { x = 1, y = 1 }, 16 | scaleTarget = { x = 1, y = 1 }, 17 | 18 | shear = { x = 0, y = 0 }, 19 | shearTarget = { x = 0, y = 0 }, 20 | 21 | width = love.graphics.getWidth(), 22 | height = love.graphics.getHeight() 23 | } 24 | setmetatable(shack, shack) 25 | 26 | --[[ Private ]]-- 27 | 28 | local function lerp(a, b, k) --smooth transitions 29 | if a == b then 30 | return a 31 | else 32 | if math.abs(a-b) < 0.005 then return b else return a * (1-k) + b * k end 33 | end 34 | end 35 | 36 | --[[ Public ]]-- 37 | 38 | function shack:setDimensions(width, height) 39 | self.width, self.height = width, height 40 | return self 41 | end 42 | 43 | function shack:update(dt) 44 | 45 | local _speed = 7 46 | 47 | self.shaking = lerp(self.shaking, self.shakingTarget, _speed*dt) 48 | self.rotation = lerp(self.rotation, self.rotationTarget, _speed*dt) 49 | 50 | self.scale.x = lerp(self.scale.x, self.scaleTarget.x, _speed*dt) 51 | self.scale.y = lerp(self.scale.y, self.scaleTarget.y, _speed*dt) 52 | 53 | self.shear.x = lerp(self.shear.x, self.shearTarget.x, _speed*dt) 54 | self.shear.y = lerp(self.shear.y, self.shearTarget.y, _speed*dt) 55 | 56 | end 57 | 58 | function shack:apply() 59 | love.graphics.translate(self.width*.5, self.height*.5) 60 | love.graphics.rotate((math.random()-.5)*self.rotation) 61 | love.graphics.scale(self.scale.x, self.scale.y) 62 | love.graphics.translate(-self.width*.5, -self.height*.5) 63 | 64 | love.graphics.translate((math.random()-.5)*self.shaking, (math.random()-.5)*self.shaking) 65 | 66 | love.graphics.shear(self.shear.x*.01, self.shear.y*.01) 67 | 68 | return self 69 | end 70 | 71 | -- 72 | 73 | function shack:setShake(shaking) 74 | self.shaking = shaking or 0 75 | return self 76 | end 77 | 78 | function shack:setRotation(rotation) 79 | self.rotation = rotation or 0 80 | return self 81 | end 82 | 83 | function shack:setShear(x, y) 84 | self.shear = { x = x or 0, y = y or 0 } 85 | return self 86 | end 87 | 88 | function shack:setScale(x, y) 89 | if not y then 90 | local _s = x or 1 91 | self.scale = { x = _s, y = _s } 92 | else 93 | self.scale = { x = x or 1, y = y or 1 } 94 | end 95 | return self 96 | end 97 | 98 | function shack:setShakeTarget(shaking) 99 | self.shakingTarget = shaking or 0 100 | return self 101 | end 102 | 103 | function shack:setRotationTarget(rotation) 104 | self.rotationTarget = rotation or 0 105 | return self 106 | end 107 | 108 | function shack:setScaleTarget(x, y) 109 | if not y then 110 | local _s = x or 1 111 | self.scaleTarget = { x = _s, y = _s } 112 | else 113 | self.scaleTarget = { x = x or 1, y = y or 1 } 114 | end 115 | return self 116 | end 117 | 118 | function shack:setShearTarget(x, y) 119 | self.shearTarget = { x = x or 0, y = y or 0 } 120 | return self 121 | end 122 | 123 | -- 124 | 125 | function shack:getShake() return self.shaking end 126 | function shack:getShakeTarget() return self.shakingTarget end 127 | 128 | function shack:getRotation() return self.rotation end 129 | function shack:getRotationTarget() return self.rotationTarget end 130 | 131 | function shack:getScale() return self.scale.x, self.scale.y end 132 | function shack:getScaleTarget() return self.scaleTarget.x, self.scaleTarget.y end 133 | 134 | function shack:getShear() return self.shear.x, self.shear.y end 135 | function shack:getShearTarget() return self.shearTarget.x, self.shearTarget.y end 136 | 137 | --[[ Aliases ]]-- 138 | 139 | function shack:shake(...) return self:setShake(...) end 140 | function shack:rotate(...) return self:setRotation(...) end 141 | function shack:zoom(...) return self:setScale(...) end 142 | function shack:tilt(...) return self:setShear(...) end 143 | 144 | --[[ End ]]-- 145 | 146 | return shack -------------------------------------------------------------------------------- /lib/soft.lua: -------------------------------------------------------------------------------- 1 | -- soft.lua v0.1 2 | 3 | -- Copyright (c) 2016 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local soft, softObject = { 9 | speed = 5, 10 | var = {} 11 | }, {} 12 | 13 | setmetatable(soft, soft) 14 | 15 | --[[ Private ]]-- 16 | 17 | local function lerp(a, b, k) --smooth transitions 18 | if a == b then 19 | return a 20 | else 21 | if math.abs(a-b) < 0.005 then return b else return a * (1-k) + b * k end 22 | end 23 | end 24 | 25 | --[[ Public ]]-- 26 | 27 | function soft:update(dt) 28 | for k, v in pairs(self.var) do 29 | v:update(dt) 30 | end 31 | end 32 | 33 | function softObject:update(dt) 34 | if self.target then self.value = lerp(self.value, self.target, self.speed * dt) end 35 | end 36 | 37 | -- 38 | 39 | function soft:new(value) 40 | local _object = { 41 | value = value 42 | } 43 | 44 | setmetatable(_object, { __index = softObject }) 45 | self.var[_object] = _object 46 | 47 | return _object 48 | end 49 | 50 | function softObject:set(value, params) 51 | if params and params.reset then self.target = value end 52 | self.value = value 53 | return self 54 | end 55 | 56 | function softObject:to(target, params) 57 | self.target = target 58 | self.speed = params and params.speed or soft.speed 59 | return self 60 | end 61 | 62 | function softObject:setSpeed(speed) 63 | self.speed = speed 64 | return self 65 | end 66 | 67 | function softObject:get(target) 68 | return target and self.target or self.value 69 | end 70 | 71 | function softObject:getTarget() return self:get(true) end 72 | 73 | --[[ Aliases ]]-- 74 | 75 | softObject.s = softObject.set 76 | softObject.t = softObject.to 77 | 78 | softObject.g = softObject.get 79 | softObject.gt = softObject.getTarget 80 | 81 | --[[ End ]]-- 82 | 83 | return soft -------------------------------------------------------------------------------- /lib/stager.lua: -------------------------------------------------------------------------------- 1 | -- stager.lua v0.1 2 | 3 | -- Copyright (c) 2016 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local stager = { 9 | state = {}, 10 | global = { 11 | update = {}, 12 | draw = {} 13 | } 14 | } 15 | setmetatable(stager, stager) 16 | 17 | --[[ Public ]]-- 18 | 19 | function stager:new() --create new state 20 | return {} 21 | end 22 | 23 | function stager:switch(path, params) 24 | 25 | if self.state.unload then self.state.unload() end 26 | 27 | local matches={} 28 | for match in string.gmatch(path,"[^;]+") do 29 | matches[#matches+1] = match 30 | end 31 | path = matches[1] 32 | 33 | package.loaded[path]=false 34 | 35 | self.state = require(path) 36 | 37 | if self.state.load then self.state.load(params) end 38 | 39 | return self 40 | end 41 | 42 | function stager:update(dt) 43 | if self.state.update then self.state.update(dt) end 44 | return self 45 | end 46 | 47 | function stager:draw() 48 | if self.state.draw then self.state.draw() end 49 | return self 50 | end 51 | 52 | return stager -------------------------------------------------------------------------------- /lib/trail.lua: -------------------------------------------------------------------------------- 1 | -- trail.lua v0.1 2 | -- inspired by Spyro and Taehl's work ( https://love2d.org/forums/ucp.php?i=pm&mode=compose&u=134819 ) 3 | 4 | -- Copyright (c) 2016 Ulysse Ramage 5 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | local trail, trailObject = { 10 | var = {} 11 | }, {} 12 | 13 | local default = { 14 | --point 15 | duration = 1, 16 | amount = 100, 17 | rotation = 0, 18 | fill = "fill", 19 | 20 | --mesh 21 | mode = "stretch", 22 | width = 5 23 | } 24 | 25 | setmetatable(trail, trail) 26 | 27 | --[[ Public ]]-- 28 | 29 | function trail:new(f) 30 | 31 | local _object = f 32 | 33 | assert(_object.content, "No content set.") 34 | 35 | _object.points = {} 36 | _object.duration = f.duration or default.duration 37 | _object.amount = f.amount or default.amount * (_object.duration / default.duration) 38 | _object.fade = f.fade 39 | _object.delay = _object.duration / _object.amount 40 | _object.time = _object.delay 41 | 42 | if f.type == "point" then 43 | 44 | _object.content.rotation = default.rotation 45 | 46 | elseif f.type == "mesh" then 47 | 48 | _object.content.mode = _object.content.mode or default.mode 49 | _object.width = f.content.width or default.width 50 | _object.content.mesh = love.graphics.newMesh(_object.duration / _object.delay / _object.delay, "strip") 51 | _object.content.mesh:setTexture(_object.content.source) 52 | 53 | end 54 | 55 | _object.active = true 56 | 57 | setmetatable(_object, { __index = trailObject }) 58 | 59 | _object:reset() 60 | _object:setMotion() 61 | 62 | self.var[_object] = _object 63 | 64 | return _object 65 | 66 | end 67 | 68 | function trailObject:reset() 69 | 70 | if self.type == "mesh" then 71 | if self.content.mode == "repeat" then 72 | self.content.length = 0 73 | end 74 | self.vertices = {} 75 | end 76 | 77 | end 78 | 79 | --{ x, y, duration, width, pmotion=pm} 80 | --{ x, y, alpha, rotation, pmotion=pm} 81 | 82 | function trailObject:add(x, y) 83 | 84 | if self.type == "point" then 85 | 86 | table.insert(self.points, 1, { x, y, 1, self.rotation, motion = self:copyMotion() }) 87 | 88 | elseif self.type == "mesh" then 89 | 90 | if self.content.mode == "repeat" then 91 | self.content.length = self.points[1][5] 92 | end 93 | 94 | table.insert(self.points, 1, { self.x, self.y, 1 }) 95 | 96 | end 97 | 98 | end 99 | 100 | -- 101 | 102 | function trail:update(dt) 103 | 104 | for k, v in pairs(self.var) do 105 | v:update(dt) 106 | end 107 | 108 | end 109 | 110 | function trailObject:update(dt) 111 | 112 | assert(self.x and self.y, "No position set.") 113 | 114 | if self.active then 115 | self.time = self.time - dt 116 | if self.time < 0 then 117 | if self.type == "point" then print("added stuff") end 118 | self:add(self.x, self.y) 119 | self.time = self.time % self.delay 120 | end 121 | end 122 | 123 | if self.type == "mesh" then 124 | self.points[1] = { 125 | self.x, 126 | self.y, 127 | 1, 128 | self.width, 129 | motion = self:copyMotion() 130 | } 131 | end 132 | 133 | for i = #self.points, 1, -1 do 134 | local point = self.points[i] 135 | 136 | point[3] = math.max(point[3] - dt / self.duration, 0) 137 | 138 | -- hack: point motion! 139 | local _motion = point.motion 140 | if _motion then 141 | point[1] = point[1] + _motion.x * dt 142 | point[2] = point[2] + _motion.y * dt 143 | _motion.x = _motion.x + _motion.vx * dt 144 | _motion.y = _motion.y + _motion.vy * dt 145 | end 146 | 147 | if self.type == "mesh" then 148 | 149 | self:updateVertex(i) 150 | 151 | end 152 | 153 | if point[3] <= 0 then 154 | table.remove(self.points, #self.points) 155 | if self.type == "mesh" then 156 | table.remove(self.vertices, #self.vertices) 157 | table.remove(self.vertices, #self.vertices) 158 | end 159 | end 160 | 161 | end 162 | 163 | if self.type == "mesh" then 164 | 165 | if #self.vertices > 3 then 166 | 167 | self.content.mesh:setVertices(self.vertices) 168 | 169 | end 170 | 171 | end 172 | 173 | return self 174 | 175 | end 176 | 177 | function trailObject:updateVertex(index) 178 | 179 | local pv = self.points[index+1] 180 | local v = self.points[index] 181 | local nv = self.points[index-1] 182 | 183 | local vertices = self.vertices 184 | 185 | if self.content.mode == "stretch" then 186 | 187 | if index == 1 then 188 | 189 | local dist = ((v[1]-pv[1])^2+(v[2]-pv[2])^2)^.5 190 | local vert = {(v[2]-pv[2])*v[4]/(dist*2), (v[1]-pv[1])*v[4]/(dist*2)} 191 | 192 | vertices[1] = { 193 | v[1]+vert[1], v[2]-vert[2], v[3], 0 194 | } 195 | vertices[2] = { 196 | v[1]-vert[1], v[2]+vert[2], v[3], 1 197 | } 198 | 199 | elseif index == #self.points then 200 | 201 | local dist = math.sqrt((nv[1]-v[1])^2+(nv[2]-v[2])^2) 202 | local vert = {(nv[2]-v[2])*v[4]/(dist*2), -(nv[1]-v[1])*v[4]/(dist*2)} 203 | 204 | vertices[#self.points*2-1] = { 205 | v[1]+vert[1], v[2]-vert[2], v[3], 0 206 | } 207 | vertices[#self.points*2] = { 208 | v[1]-vert[1], v[2]+vert[2], v[3], 1 209 | } 210 | 211 | else 212 | 213 | local dist = math.sqrt((nv[1]-pv[1])^2+(nv[2]-pv[2])^2) 214 | local vert = {(nv[2]-pv[2])*v[4]/(dist*2), (nv[1]-pv[1])*v[4]/(dist*2)} 215 | 216 | vertices[index*2-1] = { 217 | v[1]+vert[1], v[2]-vert[2], v[3], 0 218 | } 219 | vertices[index*2] = { 220 | v[1]-vert[1], v[2]+vert[2], v[3], 1 221 | } 222 | 223 | end 224 | 225 | elseif self.content.mode == "repeat" then 226 | 227 | if index == 1 then 228 | local dist = math.sqrt((v[1]-pv[1])^2+(v[2]-pv[2])^2) 229 | local vert = {(v[2]-pv[2])*v[4]/(dist*2), (v[1]-pv[1])*v[4]/(dist*2)} 230 | 231 | v[5] = dist / (self.content.source:getWidth()/(self.content.source:getHeight()/self.width)) + self.content.length 232 | 233 | vertices[1] = { 234 | v[1]+vert[1], v[2]-vert[2], v[5], 0, 255, 255, 255, 255*v[3] 235 | } 236 | vertices[2] = { 237 | v[1]-vert[1], v[2]+vert[2], v[5], 1, 255, 255, 255, 255*v[3] 238 | } 239 | 240 | elseif index == #self.points then 241 | 242 | local dist = math.sqrt((nv[1]-v[1])^2+(nv[2]-v[2])^2) 243 | local vert = {(nv[2]-v[2])*v[4]/(dist*2), -(nv[1]-v[1])*v[4]/(dist*2)} 244 | 245 | vertices[#self.points*2-1] = { 246 | v[1]+vert[1], v[2]-vert[2], v[5], 0, 255, 255, 255, 1 247 | } 248 | vertices[#self.points*2] = { 249 | v[1]-vert[1], v[2]+vert[2], v[5], 1, 255, 255, 255, 1 250 | } 251 | 252 | else 253 | 254 | local dist = math.sqrt((nv[1]-pv[1])^2+(nv[2]-pv[2])^2) 255 | local vert = {(nv[2]-pv[2])*v[4]/(dist*2), (nv[1]-pv[1])*v[4]/(dist*2)} 256 | 257 | vertices[index*2-1] = { 258 | v[1]+vert[1], v[2]-vert[2], v[5], 0, 255, 255, 255, 255*(v[3] + self.delay) 259 | } 260 | vertices[index*2] = { 261 | v[1]-vert[1], v[2]+vert[2], v[5], 1, 255, 255, 255, 255*(v[3] + self.delay) 262 | } 263 | 264 | end 265 | 266 | end 267 | 268 | end 269 | 270 | -- 271 | 272 | function trailObject:setPosition(x, y) 273 | 274 | local _init = not (self.x and self.y) 275 | 276 | self.x, self.y = x, y 277 | 278 | if _init then 279 | self.points = { 280 | { x, y, 1, self.width, 0 }, 281 | { x, y, 1 - self.delay, self.width, 0 } 282 | } 283 | end 284 | 285 | return self 286 | 287 | end 288 | 289 | function trailObject:setDuration(duration) 290 | 291 | self.duration = duration 292 | return self 293 | 294 | end 295 | 296 | function trailObject:setMotion(x, y, vx, vy) 297 | 298 | self.motion = { 299 | x = x or 0, 300 | y = y or 0, 301 | vx = vx or 0, 302 | vy = vy or 0 303 | } 304 | return self 305 | 306 | end 307 | 308 | function trailObject:copyMotion() 309 | 310 | return { 311 | x = self.motion.x, 312 | y = self.motion.y, 313 | vx = self.motion.vx, 314 | vy = self.motion.vy 315 | } 316 | 317 | end 318 | 319 | function trailObject:setRotation(rotation) 320 | 321 | self.rotation = rotation 322 | return self 323 | 324 | end 325 | 326 | -- 327 | 328 | function trailObject:draw() 329 | 330 | local col = {} 331 | col[1], col[2], col[3], col[4] = love.graphics.getColor() 332 | 333 | if self.type == "point" then 334 | 335 | for i = #self.points, 1, -1 do 336 | 337 | local point = self.points[i] 338 | 339 | local size = self.fade and (self.fade == "grow" and (2-point[3]) or point[3]) or 1 340 | love.graphics.setColor(col[1], col[2], col[3], col[4] * point[3]) 341 | if self.content.type == "image" then 342 | local width, height = self.content.source:getWidth() * size, self.content.source:getHeight() * size 343 | love.graphics.draw(self.content.source, point[1], point[2], point[4], size, size, (width/size)*.5, (height/size)*.5) 344 | elseif self.content.type == "rectangle" then 345 | local width, height = self.content.width * size, self.content.height * size 346 | love.graphics.rectangle(self.content.fill or default.fill, point[1]-width*.5, point[2]-height*.5, width, height) 347 | elseif self.content.type == "circle" then 348 | local radius = self.content.radius * size 349 | love.graphics.circle(self.content.fill or default.fill, point[1], point[2], radius) 350 | end 351 | 352 | end 353 | 354 | elseif self.type == "mesh" then 355 | 356 | self.content.mesh:setDrawRange() 357 | love.graphics.draw(self.content.mesh) 358 | 359 | end 360 | 361 | love.graphics.setColor(unpack(col)) 362 | 363 | end 364 | 365 | function trailObject:enable() 366 | if self.type == "mesh" then return self end 367 | 368 | self.active = true 369 | if self.type == "point" then self.time = self.delay end 370 | return self 371 | 372 | end 373 | 374 | function trailObject:disable() 375 | if self.type == "mesh" then return self end 376 | 377 | self.active = false 378 | return self 379 | 380 | end 381 | 382 | function trailObject:setWidth(width) 383 | 384 | self.width = width 385 | return self 386 | 387 | end 388 | 389 | --[[ Aliases ]]-- 390 | 391 | function trailObject:toggle() return self.active and self:disable() or self:enable() end 392 | 393 | --[[ End ]]-- 394 | 395 | return trail -------------------------------------------------------------------------------- /lib/wave.lua: -------------------------------------------------------------------------------- 1 | -- wave.lua v0.1 2 | 3 | -- Copyright (c) 2016 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local wave, waveObject = { 9 | overwrite = false, 10 | 11 | energyRatio = 1.2, --energy peak detection threshold 12 | trainSize = 108, --train size for convolution, blocks of 1024 (430 = 10s) 13 | 14 | minLength = 2048 --minimum length, in samples, to parse audio 15 | 16 | }, {} 17 | setmetatable(wave, wave) 18 | 19 | --[[ Private ]]-- 20 | 21 | local tr2 = 1.0594630943592952645 22 | 23 | local playInstance, stopInstance, isPlaying 24 | 25 | local function removeStopped(sources) 26 | local remove = {} 27 | for s in pairs(sources) do 28 | remove[s] = true 29 | end 30 | for s in pairs(remove) do 31 | sources[s] = nil 32 | end 33 | end 34 | 35 | local function lerp(a, b, k) --smooth transitions 36 | if a == b then 37 | return a 38 | else 39 | if math.abs(a-b) < 0.005 then return b else return a * (1-k) + b * k end 40 | end 41 | end 42 | 43 | --[[ Public - Wave ]]-- 44 | 45 | function wave:newSource(path, type) 46 | local _object = { 47 | _paused = false, 48 | path = path, 49 | type = type, 50 | instances = {}, 51 | looping = false, 52 | pitch = 1, 53 | volume = 1 54 | } 55 | 56 | setmetatable(_object, { __index = waveObject }) 57 | 58 | if _object.type == "static" then 59 | _object:parse() 60 | end 61 | 62 | return _object 63 | end 64 | 65 | --[[ Public - Source ]]-- 66 | 67 | function waveObject:play(pitched) 68 | 69 | removeStopped(self.instances) 70 | if self._paused then self:stop() end 71 | 72 | self._paused = false 73 | 74 | local instance = love.audio.newSource(self.data or self.path, self.type) 75 | 76 | -- overwrite instance:stop() and instance:play() 77 | if not (playInstance and stopInstance) then 78 | playInstance = getmetatable(instance).play 79 | 80 | stopInstance = getmetatable(instance).stop 81 | 82 | isPlaying = getmetatable(instance).isPlaying 83 | 84 | if wave.overwrite then 85 | getmetatable(instance).play = error 86 | getmetatable(instance).stop = function(this) 87 | stopInstance(this) 88 | self.instances[this] = nil 89 | end 90 | getmetatable(instance).isPlaying = error 91 | end 92 | end 93 | 94 | instance:setLooping(self.looping) 95 | instance:setPitch(self.pitch + (pitched and (math.random()-.5)*.1 or 0) ) 96 | instance:setVolume(self.volume) 97 | 98 | self.instances[instance] = instance 99 | playInstance(instance) 100 | 101 | if self:isMusic() or self:isParsed() then 102 | self.instance = instance 103 | if self:isMusic() then --start listening to beat 104 | 105 | self.duration = self.duration or self.instance:getDuration() 106 | self.previousFrame = love.timer.getTime() * 1000 107 | self.lastTime = 0 108 | self.time = 0 109 | self.beat = 0 110 | self.beatTime = 0 111 | 112 | end 113 | return self 114 | else 115 | return self 116 | end 117 | 118 | end 119 | 120 | function waveObject:stop() 121 | for s in pairs(self.instances) do 122 | s:stop() 123 | end 124 | self._paused = false 125 | self.instances = {} 126 | return self 127 | end 128 | 129 | function waveObject:pause() 130 | if self._paused then return end 131 | for s in pairs(self.instances) do 132 | s:pause() 133 | end 134 | self._paused = true 135 | return self 136 | end 137 | 138 | function waveObject:resume() 139 | if not self._paused then return end 140 | for s in pairs(self.instances) do 141 | s:resume() 142 | end 143 | self._paused = false 144 | return self 145 | end 146 | 147 | function waveObject:seek(position, unit) 148 | for s in pairs(self.instances) do 149 | s:seek(position, unit) 150 | end 151 | self.time = position * 1000 152 | self.lastTime = position * 1000 153 | self.previousFrame = love.timer.getTime() * 1000 154 | self.beatTime = self:calculateBeat() 155 | self.beat = math.floor(self.beatTime) 156 | return self 157 | end 158 | 159 | --// Configure music 160 | 161 | function waveObject:parse() 162 | assert(not self.data, "Audio is already parsed.") 163 | self.data = love.sound.newSoundData(self.path) 164 | self.length = self.data:getSampleCount() 165 | self.duration = self.data:getDuration() 166 | return self 167 | end 168 | 169 | -- 170 | 171 | function waveObject:setBPM(bpm) 172 | self.bpm = bpm 173 | self.bps = 60 / bpm 174 | self.detector = nil 175 | return self 176 | end 177 | 178 | function waveObject:setOffset(offset) 179 | self.offset = offset 180 | return self 181 | end 182 | 183 | function waveObject:onBeat(f) 184 | self.onBeat = f 185 | return self 186 | end 187 | 188 | function waveObject:getBeat() 189 | return self.beat 190 | end 191 | 192 | function waveObject:getBeatTime() 193 | return self.beatTime - self.beat 194 | end 195 | 196 | function waveObject:calculateBeat() 197 | return (self.bpm / 60) * ((self.time + (self.offset or 0)) % (self.duration * 1000)) / 1000 198 | end 199 | 200 | -- 201 | 202 | function waveObject:fadeIn(target) 203 | if self:getTargetVolume() ~= 0 and not self:isPaused() then return self end 204 | if self:isPlaying() and self:isPaused() then --playing but paused 205 | self:resume() 206 | elseif not self:isPlaying() then --not playing 207 | self:play() 208 | end 209 | self:setTargetVolume(target or 1) 210 | self.isFadingOut = nil 211 | return self 212 | end 213 | 214 | function waveObject:fadeOut(speed) 215 | self.isFadingOut = speed or true 216 | self:setTargetVolume(0) 217 | return self 218 | end 219 | 220 | -- 221 | 222 | function waveObject:setIntensity(intensity) 223 | self.intensity = intensity 224 | self.energy = 0 225 | return self 226 | end 227 | 228 | function waveObject:getEnergy() 229 | if not self:isParsed() then return 0 end 230 | return self.energy 231 | end 232 | 233 | --// Get/set properties 234 | for _, property in ipairs{'looping', 'pitch', 'volume'} do 235 | local name = property:sub(1,1):upper() .. property:sub(2) 236 | 237 | waveObject['get' .. name] = function(self) 238 | return self[property] 239 | end 240 | 241 | waveObject['set' .. name] = function(self, val, force) 242 | if force then waveObject['setTarget' .. name](self, val) end 243 | self[property] = val 244 | for s in pairs(self.instances) do 245 | s['set' .. name](s, val) 246 | end 247 | return self 248 | end 249 | end 250 | 251 | for _, property in ipairs{'pitch', 'volume'} do 252 | local name = 'Target' .. property:sub(1,1):upper() .. property:sub(2) 253 | waveObject['get' .. name] = function(self) 254 | return self['target' .. property] 255 | end 256 | 257 | waveObject['set' .. name] = function(self, val) 258 | self['target' .. property] = val 259 | return self 260 | end 261 | end 262 | 263 | function waveObject:tone(offset) 264 | 265 | local pitch = self:getTargetPitch() or self:getPitch() 266 | 267 | pitch = pitch * (tr2 ^ offset) 268 | 269 | return pitch 270 | 271 | end 272 | 273 | function waveObject:octave(offset) return self:tone(offset * 12) end 274 | 275 | --// Beat detection 276 | 277 | function waveObject:detectBPM() 278 | assert(self:isParsed(), "Parse audio before enabling beat detection.") 279 | 280 | local length = self.length 281 | local size = math.floor(length/1024) 282 | 283 | self.detector = { 284 | 285 | length = length, 286 | e1024 = {}, --size 287 | e44100 = {}, --size 288 | energyPeak = {}, --size + 21 289 | 290 | size = size 291 | } 292 | 293 | for i=0, size + 21 do 294 | self.detector.energyPeak[i] = 0 295 | end 296 | 297 | self:setBPM( self:calculateBPM() ) --heavy stuff 298 | 299 | return self 300 | end 301 | 302 | function waveObject:calculateEnergy(offset, size, limiter) 303 | 304 | local _energy = 0 305 | for i = offset, offset + size do 306 | _energy = _energy + (self.data:getSample(i) ^ 2)/ (limiter or size); 307 | end 308 | 309 | return _energy 310 | end 311 | 312 | function waveObject:normalizeSignal(signal, size, maxValue) 313 | local max = 0 314 | 315 | for i = 0, size do 316 | if (math.abs(signal[i]) > max) then 317 | max = math.abs(signal[i]) 318 | end 319 | end 320 | 321 | --adjust signal 322 | local ratio = maxValue / max 323 | 324 | for i = 0, size do 325 | signal[i] = signal[i] * ratio 326 | end 327 | 328 | return signal 329 | end 330 | 331 | function waveObject:findMax(signal, position, size) 332 | local half = size * .5 333 | local max = 0 334 | local maxPosition = position 335 | 336 | for i = position-half, position+half do 337 | 338 | if signal[i] > max then 339 | max = signal[i] 340 | maxPosition = i 341 | end 342 | 343 | end 344 | 345 | return maxPosition 346 | end 347 | 348 | function waveObject:calculateBPM() 349 | 350 | local _d = self.detector 351 | 352 | local _size = _d.size --length/1024 353 | 354 | --calculate instant energy 355 | 356 | for i = 0, _size + 42 do 357 | _d.e1024[i] = self:calculateEnergy(1024 * i, 4096) --4096 makes it smoother 358 | end 359 | 360 | --calculate average energy for 1 sec 361 | 362 | _d.e44100[0] = 0 363 | 364 | --average of the first 43 first e1024 gives the average e44100 365 | 366 | local sum = 0 367 | 368 | for i = 0, 43 do 369 | sum = sum + _d.e1024[i]; 370 | end 371 | 372 | _d.e44100[0] = sum / 43; 373 | 374 | --fill others 375 | 376 | for i = 1, _size do 377 | sum = sum - _d.e1024[i - 1] + _d.e1024[i + 42] --because 42 is the only answer 378 | _d.e44100[i] = sum / 43; 379 | end 380 | 381 | --calculate ratio 382 | 383 | for i = 21, _size do 384 | 385 | -- -21 centers e1024 on e44100's second 386 | 387 | if (_d.e1024[i] > wave.energyRatio * _d.e44100[i-21]) then 388 | 389 | _d.energyPeak[i] = 1 390 | 391 | end 392 | 393 | end 394 | 395 | --calculate BPM 396 | 397 | --calculate time between every energy peak 398 | 399 | local T = {} 400 | 401 | local iPrevious = 0 402 | 403 | for i = 1, _size do 404 | 405 | if (_d.energyPeak[i] == 1) and (_d.energyPeak[i-1] == 0) then 406 | 407 | local di = i - iPrevious 408 | 409 | if (di > 5) then --interferences 410 | 411 | table.insert(T, di) 412 | 413 | iPrevious = i 414 | 415 | end 416 | end 417 | end 418 | 419 | --let the statistics begin 420 | 421 | local maxOccT, avgOccT = 0, 0 422 | 423 | --count the frequency of each interval 424 | 425 | local occT = {}; --86 = max 2 blocks of 43 gap (=2s) 426 | 427 | for i = 0, 86 do 428 | occT[i] = 0 429 | end 430 | 431 | for i = 1, #T do 432 | if(T[i] <= 86) then 433 | occT[T[i]] = occT[T[i]] + 1 434 | end 435 | end 436 | 437 | 438 | local occMax = 0 439 | 440 | for i = 1, 86 do 441 | 442 | if occT[i] > occMax then 443 | 444 | occMaxT = i 445 | 446 | occMax = occT[i] 447 | 448 | end 449 | end 450 | 451 | --average of max + max neighbour for more precision 452 | 453 | local neighbour = occMaxT - 1 454 | 455 | if (occT[occMaxT + 1] > occT[neighbour]) then 456 | neighbour = occMaxT + 1 457 | end 458 | 459 | local div = occT[occMaxT] + occT[neighbour] 460 | 461 | if div == 0 then 462 | avgOccT = 0 463 | else 464 | avgOccT = (occMaxT * occT[occMaxT] + neighbour * occT[neighbour]) / div 465 | end 466 | 467 | 468 | 469 | --last step 470 | 471 | local bpm = 60 / (avgOccT * (1024/44100)) --FINALLY WE GOT IT WOOHOO 472 | 473 | while bpm < 90 or bpm > 180 do --TEST - REMOVE 474 | if bpm < 90 then 475 | bpm = bpm * 2 476 | else 477 | bpm = bpm * .5 478 | end 479 | end 480 | 481 | return math.floor(bpm + .5) 482 | end 483 | 484 | --// Update 485 | 486 | function waveObject:update(dt) 487 | if not self:isPaused() then 488 | if self:isMusic() then self:updateBeat(dt) end 489 | if self:isParsed() then self:updateEnergy(dt) end 490 | end 491 | 492 | self:updateProperties(dt) 493 | 494 | return self 495 | end 496 | 497 | function waveObject:updateBeat(dt) 498 | local _instance = self.instances[self.instance] 499 | if not _instance then return self end 500 | 501 | local _offset = love.timer.getTime() * 1000 502 | 503 | local _elapsedBeats = 0 504 | 505 | self.time = self.time + _offset - self.previousFrame 506 | 507 | self.previousFrame = _offset 508 | local _position = _instance:tell("seconds") * 1000 509 | if _position < self.lastTime then --music looped 510 | self.time = _position 511 | self.lastTime = _position 512 | _elapsedBeats = _elapsedBeats + 1 513 | self.beat = 0 514 | elseif _position ~= self.lastTime then --updates music time, but with easing 515 | self.time = (self.time + (_position))/2 516 | self.lastTime = _position 517 | end 518 | 519 | self.beatTime = self:calculateBeat() 520 | 521 | local _beat = math.floor(self.beatTime) 522 | _elapsedBeats = _elapsedBeats + _beat - self.beat 523 | self.beat = _beat 524 | 525 | if self.onBeat then 526 | for i = 1, _elapsedBeats do 527 | self.onBeat() 528 | end 529 | end 530 | 531 | return self 532 | end 533 | 534 | function waveObject:updateEnergy(dt) 535 | local _instance = self.instances[self.instance] 536 | if not _instance then return self end 537 | 538 | local _sample = _instance:tell( "samples" ) 539 | local size = 1024 540 | if _sample > size then 541 | 542 | local _energy = self:calculateEnergy(_sample, size, self.intensity) 543 | self.energy = lerp(self.energy, _energy, 10*dt) 544 | 545 | end 546 | return self 547 | end 548 | 549 | function waveObject:updateProperties(dt) 550 | 551 | if self:getTargetPitch() then 552 | self:setPitch(lerp(self:getPitch(), self:getTargetPitch(), 2*dt)) 553 | end 554 | 555 | if self:getTargetVolume() then 556 | self:setVolume( 557 | lerp( 558 | self:getVolume(), 559 | self:getTargetVolume(), 560 | ((self.isFadingOut and self.isFadingOut ~= true) and self.isFadingOut or 2)*dt) 561 | ) 562 | end 563 | 564 | if self.isFadingOut and self:getVolume() == self:getTargetVolume() then 565 | self.isFadingOut = nil 566 | self:pause() 567 | end 568 | 569 | end 570 | 571 | --// Checks 572 | 573 | function waveObject:isMusic() return self.bpm and true or false end 574 | function waveObject:isParsed() return (self.data and self.duration > 10) and true or false end 575 | 576 | function waveObject:isPaused() return self._paused end 577 | 578 | function waveObject:isPlaying() 579 | local _playing = false 580 | for s in pairs(self.instances) do 581 | if s then _playing = true end --if there's at least one instance 582 | end 583 | return _playing 584 | end 585 | 586 | --[[ Aliases ]]-- 587 | 588 | waveObject.isLooping = waveObject.getLooping 589 | 590 | function waveObject:fade(inout, target) if inout == "in" then return self:fadeIn(target) else return self:fadeOut() end end 591 | 592 | --[[ End ]]-- 593 | 594 | return wave 595 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | io.stdout:setvbuf('no') --fixes print issues 2 | 3 | --//////////////////////////////////-- 4 | --//-\\-//-[[- SETTINGS -]]-\\-//-\\-- 5 | --\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\-- 6 | 7 | WWIDTH, WHEIGHT = 1920, 1080 --16/9 aspect ratio 8 | 9 | --//////////////////////////////////-- 10 | --//-\\-//-[[- INCLUDES -]]-\\-//-\\-- 11 | --\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\-- 12 | 13 | --Libraries 14 | push = require "lib.push" 15 | screen = require "lib.shack" --Screen effects (shake, rotate, shear, scale) 16 | lem = require "lib.lem" --Events 17 | lue = require "lib.lue" --Hue 18 | state = require "lib.stager" --Scenes and transitions 19 | audio = require "lib.wave" --Audio 20 | trail = require "lib.trail" --Trails 21 | soft = require "lib.soft" --Lerp 22 | ease = require "lib.easy" --Easing 23 | 24 | 25 | 26 | --Includes - Custom libraries 27 | 28 | 29 | 30 | --Classes 31 | 32 | 33 | 34 | --///////////////////////////////-- 35 | --//-\\-//-[[- SETUP -]]-\\-//-\\-- 36 | --\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\-- 37 | 38 | local os = love.system.getOS() 39 | 40 | phoneMode = (os == "iOS" or os == "Android") and true or false --handles mobile platforms 41 | fullscreenMode = phoneMode and true or false --enables fullscreen if on mobile 42 | 43 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 44 | 45 | if fullscreenMode then 46 | RWIDTH, RHEIGHT = windowWidth, windowHeight 47 | else 48 | RWIDTH = windowWidth*.7 RHEIGHT = windowHeight*.7 49 | end 50 | 51 | push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, { 52 | fullscreen = fullscreenMode, 53 | resizable = not phoneMode 54 | }) 55 | 56 | --///////////////////////////////////-- 57 | --//-\\-//-[[- FUNCTIONS -]]-\\-//-\\-- 58 | --\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\-- 59 | 60 | function love.load() 61 | 62 | screen:setDimensions(push:getDimensions()) 63 | 64 | state:switch("scenes/game", {}) 65 | 66 | end 67 | 68 | function love.update(dt) 69 | 70 | screen:update(dt) 71 | lue:update(dt) 72 | soft:update(dt) 73 | trail:update(dt) 74 | 75 | state:update(dt) 76 | 77 | end 78 | 79 | function love.draw() 80 | 81 | push:apply("start") 82 | screen:apply() 83 | 84 | state:draw() 85 | 86 | push:apply("end") 87 | 88 | end 89 | 90 | if not phoneMode then 91 | function love.resize(w, h) 92 | return push:resize(w, h) 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /scenes/game.lua: -------------------------------------------------------------------------------- 1 | local game = state:new() 2 | 3 | --[[ State ]]-- 4 | 5 | function game.load(params) 6 | 7 | love.graphics.setNewFont(92) 8 | 9 | --load 10 | 11 | end 12 | 13 | function game.update(dt) 14 | 15 | --update 16 | 17 | end 18 | 19 | function game.draw() 20 | 21 | local time = love.timer.getTime() 22 | love.graphics.printf("Welcome!", math.cos(time*5)*100, WHEIGHT*.5 + math.sin(time)*100, WWIDTH, "center") 23 | 24 | --draw 25 | 26 | end 27 | 28 | function game.unload() 29 | 30 | --unload 31 | 32 | end 33 | 34 | --[[ External ]]-- 35 | 36 | function love.keypressed(key, scancode, isrepeat) 37 | 38 | --keypressed 39 | 40 | end 41 | 42 | --[[ End ]]-- 43 | 44 | return game --------------------------------------------------------------------------------