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]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