feature/audioengine #26

Manually merged
PeaAshMeter merged 11 commits from feature/audioengine into main 2026-01-18 17:56:13 +03:00
16 changed files with 181 additions and 2 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

91
lib/audio.lua Normal file
View File

@ -0,0 +1,91 @@
local ease = require "lib.utils.easing"
local AnimationNode = require "lib.animation_node"
--- @alias SourceFilter { type: "bandpass"|"highpass"|"lowpass", volume: number, highgain: number, lowgain: number }
--- @class Audio
--- @field musicVolume number
--- @field soundVolume number
--- @field looped boolean
--- @field animationNode AnimationNode?
--- @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.animationNode and self.animationNode.state == "running" then
self.animationNode:update(dt)
self.from:setVolume(self.musicVolume - self.animationNode:getValue() * self.musicVolume)
self.to:setVolume(self.animationNode:getValue() * self.musicVolume)
-- print(self.animationNode.t)
elseif self.animationNode and self.animationNode.state == "finished" then
self.from:stop()
self.animationNode:finish()
self.animationNode = nil
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
self.animationNode = AnimationNode {
function(node) end,
onEnd = function()
self.from:setVolume(0)
self.to:setVolume(self.musicVolume)
print("[Audio]: Crossfade done")
end,
duration = ms or 1000,
easing = ease.easeOutCubic,
}
self.animationNode:run()
end
--- @param source love.Source
--- @param settings SourceFilter?
--- @param effectName string?
function audio:play(source, settings, effectName)
if settings then
source:setFilter(settings)
end
if effectName then
source:setEffect(effectName, true)
end
if source:getType() == "stream" then
source:setLooping(self.looped)
source:setVolume(self.musicVolume)
return source:play()
end
source:setVolume(self.soundVolume)
return source:play()
end
function audio:setMusicVolume(volume)
self.musicVolume = volume
end
function audio:setSoundVolume(volume)
self.soundVolume = volume
end
return { new = new }

View File

@ -18,6 +18,9 @@ local path = nil
local function new(type, template)
local size = Vec3 { 30, 30 } -- magic numbers for testing purposes only
print(type, template, size)
Tree.audio:play(Tree.assets.files.audio.music.level1.battle)
return setmetatable({
size = size,
characters = {},

52
lib/music.lua Normal file
View File

@ -0,0 +1,52 @@
-- --- @class Music
-- --- @field source table<string, love.Source> audio streams, that supports multitrack (kind of)
-- --- @field offset number
-- music = {}
-- music.__index = music
-- --- @param path string accepts path to dir with some music files (example: "main_ambient"; "player/theme1" and etc etc)
-- local function new(path)
-- local dir = Tree.assets.files.audio.music[path]
-- --- @type table<string, love.Source>
-- local source = {}
-- print(dir)
-- for _, v in pairs(dir) do
-- print(v.filename)
-- source[v.filename] = v.source
-- print(v.filename)
-- end
-- print('[music]: new source: ', table.concat(source, ' '))
-- return setmetatable({ source = source, offset = 0 }, music)
-- end
-- function music:update()
-- for _, v in ipairs(self.source) do
-- v:seek()
-- end
-- end
-- --- pause stemfile or music at all
-- --- @param filename? string
-- function music:pause(filename)
-- if filename then
-- self.source[filename]:pause()
-- else
-- for _, v in pairs(self.source) do
-- v:pause()
-- end
-- end
-- end
-- --- play music stemfile by his name
-- --- @param filename string
-- --- @return boolean
-- function music:play(filename)
-- print('[music]: ', table.concat(self.source, ' '))
-- self.source[filename]:seek(self.offset, "seconds")
-- return self.source[filename]:play()
-- end
-- return { new = new }

9
lib/sound.lua Normal file
View File

@ -0,0 +1,9 @@
-- --- @class Sound
-- --- @field source love.Source just a sound
-- sound = {}
-- local function new()
-- return setmetatable({}, sound)
-- end
-- return { new }

View File

@ -51,7 +51,9 @@ function walk:cast(caster, target)
local sprite = caster:has(Tree.behaviors.sprite)
if not sprite then return true end
AnimationNode {
function(node) caster:has(Tree.behaviors.map):followPath(path, node) end,
function(node)
caster:has(Tree.behaviors.map):followPath(path, node)
end,
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end,
}:run()
@ -88,7 +90,10 @@ function regenerateMana:cast(caster, target)
if not sprite then return true end
AnimationNode {
function(node)
local audioPath = Tree.assets.files.audio
sprite:animate("hurt", node)
Tree.audio:crossfade(audioPath.music.level1.battle,
audioPath.music.level1.choral, 5000)
end,
onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end
}:run()
@ -141,7 +146,16 @@ function attack:cast(caster, target)
children = {
AnimationNode {
function(node)
local audioPath = Tree.assets.files.audio
targetSprite:animate("hurt", node)
--- @type SourceFilter
local settings = {
type = "bandpass",
volume = 1,
highgain = 0.1,
lowgain = 0.1
}
Tree.audio:play(audioPath.sounds.hurt, settings)
end
}
}

View File

@ -9,9 +9,11 @@ Tree = {
Tree.fonts = (require "lib.utils.font_manager"):load("WDXL_Lubrifont_TC"):loadTheme("Roboto_Mono") -- дефолтный шрифт
Tree.panning = require "lib/panning"
Tree.controls = require "lib.controls"
Tree.audio = (require "lib.audio").new(1, 1)
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен
Tree.behaviors = (require "lib.utils.behavior_loader")("lib/character/behaviors") --- @todo написать нормальную загрузку поведений
-- Tree.audio = (require "lib.audio").new(1, 1)
-- Tree.behaviors.map = require "lib.character.behaviors.map"
-- Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
-- Tree.behaviors.sprite = require "lib.character.behaviors.sprite"

View File

@ -50,6 +50,10 @@ function AssetBundle.loadFile(path)
return love.graphics.newShader(path);
elseif (ext == "lua") then
return require(string.gsub(path, ".lua", ""))
elseif (ext == "ogg") and string.find(path, "sounds") then
return love.audio.newSource(path, 'static')
elseif (ext == "ogg") and string.find(path, "music") then
return love.audio.newSource(path, 'stream')
end
return filedata
end

View File

@ -18,7 +18,10 @@ function love.load()
end
Tree.level.turnOrder:endRound()
print("Now playing:", Tree.level.turnOrder.current)
love.window.setMode(1280, 720, { resizable = true, msaa = 4, vsync = true })
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
-- Level1_music = (require "lib.music").new("level1/bass")
-- Level1_music:play("bass")
end
local lt = "0"
@ -29,6 +32,7 @@ function love.update(dt)
testLayout:update(dt) -- потом UI, потому что нужно перехватить жесты и не пустить их дальше
Tree.panning:update(dt)
Tree.level:update(dt)
Tree.audio:update(dt)
Tree.controls:cache()