local task = require "lib.utils.task" local ease = require "lib.utils.easing" local EFFECTS_SUPPORTED = love.audio.isEffectsSupported() --- @alias SourceFilter { type: "bandpass"|"highpass"|"lowpass", volume: number, highgain: number, lowgain: number } --- @class Audio --- @field musicVolume number --- @field soundVolume number --- @field looped boolean --- @field from love.Source? --- @field to love.Source? audio = {} audio.__index = audio --- здесь мы должны выгружать значения из файлика с сохранением настроек local function new(musicVolume, soundVolume) return setmetatable({ musicVolume = musicVolume, soundVolume = soundVolume, looped = true }, audio) end function audio:update(dt) if self.fader then local t = self.fader.value if self.from then self.from:setVolume(self.musicVolume * (1 - t)) end if self.to then self.to:setVolume(self.musicVolume * t) end end end --- if from is nil, than we have fade in to; --- if to is nil, than we have fade out from --- --- also we should guarantee, that from and to have the same volume --- @param from love.Source --- @param to love.Source --- @param ms number? in milliseconds function audio:crossfade(from, to, ms) print("[Audio]: Triggered crossfade") self:play(to) to:setVolume(0) self.from = from self.to = to -- Using a dummy object to tween a value from 0 to 1 local fade = { value = 0 } task.tween(fade, { value = 1 }, ms or 1000, ease.easeOutCubic)(function() self.from:setVolume(0) self.to:setVolume(self.musicVolume) self.from:stop() print("[Audio]: Crossfade done") end) -- We need a custom update loop for the volume during the tween -- Since task.tween updates properties, we can use a "monitor" task or just rely on `update` if we expose the task? -- Actually, task.tween updates `fade.value` every frame. -- We need to apply this value to the audio sources every frame. -- BUT task.tween doesn't provide an "onUpdate" callback easily. -- Workaround: create a task that runs every frame? Or modify task.tween? -- Let's check task.lua... it updates properties on the target object. -- So `fade.value` will change. We need to apply it. -- We can hook into `update` or use a metatable on `fade`? -- Let's use a "reactive" table with metatable setters? Overkill. -- Simplest way: Add `onUpdate` to task.tween or just use `audio:update` to poll `fade.value`? -- But `fade` is local. -- Better approach: Tween `self` properties? No, volume is on Source userdate. -- Let's add a `fader` object to `self` that we tween, and in `update` we apply it. self.fader = { value = 0 } task.tween(self.fader, { value = 1 }, ms or 1000, ease.easeOutCubic)(function() self.fader = nil end) end --- @param source love.Source --- @param settings SourceFilter? --- @param effectName string? function audio:play(source, settings, effectName) if source:getType() == "stream" then source:setLooping(self.looped) source:setVolume(self.musicVolume) source:play() else source:setVolume(self.soundVolume) source:play() end if settings and EFFECTS_SUPPORTED then source.setFilter(source, settings) end if effectName and EFFECTS_SUPPORTED then source:setEffect(effectName, true) end end function audio:setMusicVolume(volume) self.musicVolume = volume end function audio:setSoundVolume(volume) self.soundVolume = volume end return { new = new }