match-3/soundgen.lua

205 lines
4 KiB
Lua
Raw Permalink Normal View History

2023-04-12 11:11:12 +03:00
local smt={}
smt.__index=smt
smt.proto=smt
local function round(x)
return math.floor(x+.5)
end
function smt:bounds_and(a,b,c,...)
if not a or not b then return false end
local mi,ma
mi=math.max(a[1],b[1])
ma=math.min(a[2],b[2])
if ma-mi<=0 then return false end
if c then
return self:bounds_and({mi,ma},c,...)
end
return {mi,ma}
end
function smt:bounds_or(a,b,c,...)
local mi,ma=math.min(a[1],b[1]),math.max(a[2],b[2])
if not a then return b end
if not b then return a end
if c then
return self:bounds_or({mi,ma},c,...)
end
return {mi,ma}
end
function smt:new(fn,bounds)
local obj={}
obj.bounds=bounds
if obj.bounds==nil then
obj.bounds={-math.huge,math.huge}
end
obj.sample=fn
return setmetatable(obj,self.proto)
end
function smt:sine()
return self:new(function(self,t)
return math.sin(t*math.pi*2)
end)
end
function smt:square()
return self:new(function(self,t)
return (t%1)<=0.5 and -1 or 1
end)
end
function smt:sawtooth()
return self:new(function(self,t)
return (t%1)*2-1
end)
end
function smt:triangle()
return self:new(function(self,t)
--return ((t%1)<=0.5 and (t*2)%1 or 1-(t*2)%1)*2-1 -- sounds cool and glitchy but wrong
local a=t%1
if a>0.5 then
return (1-(a*2-1))*2-1
end
return (a*2)*2-1
end)
end
function smt:noise(y,z,x)
return self:new(function(self,t)
return love.math.noise(t,y,z,x)*2-1
end)
end
function smt:silence(a)
a=a or 0
return smt:new(function()return a end)
end
local function rated_bounds(bounds,rate)
local mi,ma=bounds[1],bounds[2]
mi,ma=mi*rate,ma*rate
mi,ma=round(mi),round(ma)-1
return mi,ma
end
local function prefi(y)
return math.min(1,math.max(-1,y))
end
function smt:compile(rate,bits,alt)
rate=rate or 44100
bits=bits or 16
local bounds=self.bounds
local mi,ma=rated_bounds(bounds,rate)
if alt then
bounds=self:bounds_and(bounds,alt.bounds)
local mi,ma=rated_bounds(bounds,rate)
local sdata=love.sound.newSoundData(ma-mi+1,rate,bits,2)
for x=mi,ma do
local x=x+mi
sdata:setSample(x*2,prefi(self:sample(x/rate+0.5/rate)))
sdata:setSample(x*2+1,prefi(alt:sample(x/rate+0.5/rate)))
end
return sdata
end
local sdata=love.sound.newSoundData(ma-mi+1,rate,bits,1)
for x=mi,ma do
local xx=x-mi
sdata:setSample(xx,prefi(self:sample(x/rate+0.5/rate)))
end
return sdata
end
function smt:phase(p)
local bb=self.bounds
if bb then
bb={bb[1]-p,bb[2]-p}
end
return self:new(function(obj,t)
return self:sample(t+p)
end,bb)
end
function smt:amp(a)
return self:new(function(obj,t)
return self:sample(t)*a
end,self.bounds)
end
function smt:freq(f)
local bb=self.bounds
if bb then
bb={bb[1]/f,bb[2]/f}
if bb[2]<bb[1] then
bb={bb[2],bb[1]}
end
end
return self:new(function(obj,t)
return self:sample(t*f)
end,bb)
end
function smt:clamp(mi,ma)
return self:new(function(nself,t)
if t<mi or t>ma then return 0 end
return self.sample(self,t)
end,self:bounds_and(self.bounds,{mi,ma}))
end
function smt.am(car,mod,mi,ma)
return smt:new(function(self,t)
return car:sample(t)*((mod:sample(t)*0.5+0.5)*(ma-mi)+mi)
end,car:bounds_and(car.bounds,mod.bounds))
end
function smt.pm(car,mod,mi,ma)
assert(car.bounds
and car.bounds[1]==-math.huge
and car.bounds[2]==math.huge,
"infinite carrier expected")
mod=mod:add(mod:silence())
return smt:new(function(self,t)
return car:sample(t+((mod:sample(t)*0.5+0.5)*(ma-mi)+mi))
end,car.bounds)
end
function smt.add(a,b,...)
if not b then
if not a then return smt:silence() end
return a
end
local ls={a,b,...}
local bbs={}
for k,v in pairs(ls) do
bbs[k]=v.bounds
end
return smt:new(function(self,t)
local r=0
for k,v in pairs(ls) do
r=r+v:sample(t)
end
return r
end,a:bounds_or(unpack(bbs)))
end
do
local fwav=smt:sine():phase(0.75):freq(0.25)
fwav=fwav:clamp(0,1)
fwav=fwav:add(smt:silence(-1):clamp(-math.huge,0))
function smt.fade(wave,ma,mi)
local ff=fwav:freq(1/(mi-ma)):phase(-ma)
return wave:am(ff,1,-1)
end
end
function smt.fm(car,mod,rate,mi,ma) -- TODO
local cframe,cache=0,{}
return smt.new(function(self,t)
end,mod.bounds)
end
return smt