351 lines
11 KiB
Lua
351 lines
11 KiB
Lua
local audio=love.audio
|
|
local effectsSupported=audio.isEffectsSupported()
|
|
|
|
local nameList={}
|
|
local srcLib={}-- Stored bgm objects: {name='foo', source=bar, ...}, more info at function _addFile()
|
|
local lastLoadNames={}
|
|
local nowPlay={}
|
|
local lastPlay=NONE-- Directly stored last played bgm name(s)
|
|
|
|
local defaultBGM=false
|
|
local maxLoadedCount=3
|
|
local volume=1
|
|
|
|
local function task_setVolume(obj,ve,time,stop)
|
|
local vs=obj.vol
|
|
local t=0
|
|
while true do
|
|
t=time~=0 and math.min(t+coroutine.yield()/time,1) or 1
|
|
local v=MATH.mix(vs,ve,t)
|
|
obj.vol=v
|
|
obj.source:setVolume(v*volume)
|
|
if t==1 then
|
|
obj.volChanging=false
|
|
break
|
|
end
|
|
end
|
|
if stop then
|
|
obj.source:stop()
|
|
end
|
|
obj.volChanging=false
|
|
return true
|
|
end
|
|
local function task_setPitch(obj,pe,time)
|
|
local ps=obj.pitch
|
|
local t=0
|
|
while true do
|
|
t=time~=0 and math.min(t+coroutine.yield()/time,1) or 1
|
|
local p=MATH.mix(ps,pe,t)
|
|
obj.pitch=p
|
|
obj.source:setPitch(p)
|
|
if t==1 then
|
|
obj.pitchChanging=false
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
local function task_setLowgain(obj,pe,time)
|
|
local ps=obj.lowgain
|
|
local t=0
|
|
while true do
|
|
t=time~=0 and math.min(t+coroutine.yield()/time,1) or 1
|
|
local p=MATH.mix(ps,pe,t)
|
|
obj.lowgain=p
|
|
obj.source:setFilter{type='bandpass',lowgain=obj.lowgain^9.42,highgain=obj.highgain^9.42,volume=1}
|
|
if t==1 then
|
|
obj.lowgainChanging=false
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
local function task_setHighgain(obj,pe,time)
|
|
local ps=obj.highgain
|
|
local t=0
|
|
while true do
|
|
t=time~=0 and math.min(t+coroutine.yield()/time,1) or 1
|
|
local p=MATH.mix(ps,pe,t)
|
|
obj.highgain=p
|
|
obj.source:setFilter{type='bandpass',lowgain=obj.lowgain^9.42,highgain=obj.highgain^9.42,volume=1}
|
|
if t==1 then
|
|
obj.highgainChanging=false
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
local function _clearTask(obj,mode)
|
|
local taskFunc=
|
|
mode=='volume' and task_setVolume or
|
|
mode=='pitch' and task_setPitch or
|
|
mode=='lowgain' and task_setLowgain or
|
|
mode=='highgain' and task_setHighgain or
|
|
'any'
|
|
TASK.removeTask_iterate(function(task)
|
|
return task.args[1]==obj and (taskFunc=='any' or task.code==taskFunc)
|
|
end,obj)
|
|
end
|
|
|
|
local function _updateSources()
|
|
local n=#lastLoadNames
|
|
while #lastLoadNames>maxLoadedCount and n>0 do
|
|
local name=lastLoadNames[n]
|
|
if srcLib[name].source and not srcLib[name].source:isPlaying() then
|
|
srcLib[name].source=srcLib[name].source:release() and nil
|
|
_clearTask(srcLib[name],'any')
|
|
end
|
|
n=n-1
|
|
end
|
|
end
|
|
local function _addFile(name,path)
|
|
if not srcLib[name] then
|
|
table.insert(nameList,name)
|
|
srcLib[name]={
|
|
name=name,path=path,source=false,
|
|
vol=0,volChanging=false,
|
|
pitch=1,pitchChanging=false,
|
|
lowgain=1,lowgainChanging=false,
|
|
highgain=1,highgainChanging=false,
|
|
}
|
|
end
|
|
end
|
|
local function _tryLoad(name)
|
|
if srcLib[name] then
|
|
local obj=srcLib[name]
|
|
if obj.source then
|
|
return true
|
|
elseif love.filesystem.getInfo(obj.path) then
|
|
obj.source=audio.newSource(obj.path,'stream')
|
|
obj.source:setLooping(true)
|
|
table.insert(lastLoadNames,1,name)
|
|
return true
|
|
else
|
|
LOG(STRING.repD("Wrong path for BGM '$1': $2",obj.name,obj.path),5)
|
|
end
|
|
elseif name then
|
|
LOG("No BGM: "..name,5)
|
|
end
|
|
end
|
|
|
|
local BGM={}
|
|
|
|
function BGM.getList() return nameList end
|
|
function BGM.getCount() return #nameList end
|
|
|
|
function BGM.setDefault(bgms)
|
|
if type(bgms)=='string' then
|
|
bgms={bgms}
|
|
elseif type(bgms)=='table' then
|
|
for i=1,#bgms do assert(type(bgms[i])=='string',"BGM list must be list of strings") end
|
|
else
|
|
error("BGM.setDefault(bgms): bgms must be string or table")
|
|
end
|
|
defaultBGM=bgms
|
|
end
|
|
function BGM.setMaxSources(count)
|
|
assert(type(count)=='number' and count>0 and count%1==0,"BGM.setMaxSources(count): count must be positive integer")
|
|
maxLoadedCount=count
|
|
_updateSources()
|
|
end
|
|
function BGM.setVol(vol)
|
|
assert(type(vol)=='number' and vol>=0 and vol<=1,"BGM.setVol(vol): count must be in range 0~1")
|
|
volume=vol
|
|
for i=1,#nowPlay do
|
|
local bgm=nowPlay[i]
|
|
if not bgm.volChanging then
|
|
bgm.source:setVolume(bgm.vol*vol)
|
|
end
|
|
end
|
|
end
|
|
function BGM.init(name,path)
|
|
if type(name)=='table' then
|
|
for k,v in next,name do
|
|
_addFile(k,v)
|
|
end
|
|
else
|
|
_addFile(name,path)
|
|
end
|
|
table.sort(nameList)
|
|
LOG(BGM.getCount().." BGM files added")
|
|
end
|
|
|
|
function BGM.play(bgms,args)
|
|
if not args then args='' end
|
|
if not bgms then bgms=defaultBGM end
|
|
if not bgms then return end
|
|
|
|
if type(bgms)=='string' then bgms={bgms} end
|
|
assert(type(bgms)=='table',"BGM.play(name,args): name must be string or table")
|
|
|
|
if
|
|
TABLE.compare(lastPlay,bgms) and
|
|
srcLib[lastPlay[1]] and srcLib[lastPlay[1]].source and
|
|
srcLib[lastPlay[1]].source:isPlaying()
|
|
then
|
|
return
|
|
end
|
|
|
|
BGM.stop()
|
|
|
|
if not STRING.sArg(args,'-preLoad') then
|
|
lastPlay=bgms
|
|
end
|
|
|
|
for i=1,#bgms do
|
|
local bgm=bgms[i]
|
|
assert(type(bgm)=='string',"BGM list can only be list of string")
|
|
if _tryLoad(bgm) and not STRING.sArg(args,'-preLoad') then
|
|
local obj=srcLib[bgms[i]]
|
|
obj.vol=0
|
|
obj.pitch=1
|
|
obj.lowgain=1
|
|
obj.highgain=1
|
|
obj.volChanging=false
|
|
obj.pitchChanging=false
|
|
obj.lowgainChanging=false
|
|
obj.highgainChanging=false
|
|
|
|
_clearTask(obj)
|
|
|
|
local source=obj.source
|
|
source:setLooping(not STRING.sArg(args,'-noloop'))
|
|
source:setPitch(1)
|
|
source:seek(0)
|
|
source:setFilter()
|
|
if STRING.sArg(args,'-sdin') then
|
|
obj.vol=1
|
|
source:setVolume(volume)
|
|
BGM.set(bgm,'volume',1,0)
|
|
else
|
|
source:setVolume(0)
|
|
BGM.set(bgm,'volume',1,.626)
|
|
end
|
|
source:play()
|
|
|
|
table.insert(nowPlay,obj)
|
|
return true
|
|
end
|
|
end
|
|
_updateSources()
|
|
end
|
|
function BGM.stop(time)
|
|
if #nowPlay>0 then
|
|
for i=1,#nowPlay do
|
|
local obj=nowPlay[i]
|
|
_clearTask(obj,'volume')
|
|
if time==0 then
|
|
obj.source:stop()
|
|
obj.volChanging=false
|
|
else
|
|
TASK.new(task_setVolume,obj,0,time or .626,true)
|
|
obj.volChanging=true
|
|
end
|
|
end
|
|
TABLE.cut(nowPlay)
|
|
lastPlay=NONE
|
|
end
|
|
end
|
|
|
|
---@param mode
|
|
---| 'volume'
|
|
---| 'lowgain'
|
|
---| 'highgain'
|
|
---| 'volume'
|
|
---| 'pitch'
|
|
---| 'seek'
|
|
function BGM.set(bgms,mode,...)
|
|
if type(bgms)=='string' then
|
|
if bgms=='all' then
|
|
bgms=nowPlay
|
|
else
|
|
bgms={srcLib[bgms]}
|
|
end
|
|
elseif type(bgms)=='table' then
|
|
bgms=TABLE.shift(bgms)
|
|
for i=1,#bgms do
|
|
assert(type(bgms[i])=='string',"BGM list must be list of strings")
|
|
bgms[i]=srcLib[bgms[i]]
|
|
end
|
|
else
|
|
error("BGM.play(name,args): name must be string or table")
|
|
end
|
|
for i=1,#bgms do
|
|
local obj=bgms[i]
|
|
if obj.source then
|
|
if mode=='volume' then
|
|
_clearTask(obj,'volume')
|
|
|
|
local vol,time=...
|
|
if not time then time=1 end
|
|
|
|
assert(type(vol)=='number' and vol>=0 and vol<=1,"BGM.set(...,volume): volume must be in range 0~1")
|
|
assert(type(time)=='number' and time>=0,"BGM.set(...,time): time must be positive number")
|
|
|
|
TASK.new(task_setVolume,obj,vol,time)
|
|
elseif mode=='pitch' then
|
|
_clearTask(obj,'pitch')
|
|
|
|
local pitch,changeTime=...
|
|
if not pitch then pitch=1 end
|
|
if not changeTime then changeTime=1 end
|
|
|
|
assert(type(pitch)=='number' and pitch>0 and pitch<=32,"BGM.set(...,pitch): pitch must be in range 0~32")
|
|
assert(type(changeTime)=='number' and changeTime>=0,"BGM.set(...,time): time must be positive number")
|
|
|
|
TASK.new(task_setPitch,obj,pitch,changeTime)
|
|
elseif mode=='seek' then
|
|
local time=...
|
|
assert(type(time)=='number',"BGM.set(...,time): time must be number")
|
|
obj.source:seek(MATH.clamp(time,0,obj.source:getDuration()))
|
|
elseif mode=='lowgain' then
|
|
if effectsSupported then
|
|
_clearTask(obj,'lowgain')
|
|
local lowgain,changeTime=...
|
|
if not lowgain then lowgain=1 end
|
|
if not changeTime then changeTime=1 end
|
|
|
|
assert(type(lowgain)=='number' and lowgain>=0 and lowgain<=1,"BGM.set(...,lowgain,highgain): lowgain must be in range 0~1")
|
|
assert(type(changeTime)=='number' and changeTime>=0,"BGM.set(...,time): time must be positive number")
|
|
|
|
TASK.new(task_setLowgain,obj,lowgain,changeTime)
|
|
obj.lowgain=lowgain
|
|
obj.source:setFilter{type='bandpass',lowgain=obj.lowgain,highgain=obj.highgain,volume=1}
|
|
end
|
|
elseif mode=='highgain' then
|
|
if effectsSupported then
|
|
_clearTask(obj,'highgain')
|
|
local highgain,changeTime=...
|
|
if not highgain then highgain=1 end
|
|
if not changeTime then changeTime=1 end
|
|
|
|
assert(type(highgain)=='number' and highgain>=0 and highgain<=1,"BGM.set(...,lowgain,highgain): highgain must be in range 0~1")
|
|
assert(type(changeTime)=='number' and changeTime>=0,"BGM.set(...,time): time must be positive number")
|
|
|
|
TASK.new(task_setHighgain,obj,highgain,changeTime)
|
|
obj.highgain=highgain
|
|
obj.source:setFilter{type='bandpass',lowgain=obj.lowgain,highgain=obj.highgain,volume=1}
|
|
end
|
|
else
|
|
error("BGM.set(...,mode): mode must be 'volume', 'pitch', or 'seek'")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function BGM.getPlaying()
|
|
return TABLE.shift(lastPlay)
|
|
end
|
|
function BGM.isPlaying()
|
|
return #nowPlay>0 and nowPlay[1].source:isPlaying()
|
|
end
|
|
function BGM.tell()
|
|
if nowPlay[1] then
|
|
return nowPlay[1].source:tell()
|
|
end
|
|
end
|
|
function BGM.getDuration()
|
|
if nowPlay[1] then
|
|
return nowPlay[1].source:getDuration()
|
|
end
|
|
end
|
|
|
|
return BGM
|