Files
wnsrc/lua/pac3/libraries/webaudio/stream.lua
lifestorm 73479cff9e Upload
2024-08-04 22:55:00 +03:00

473 lines
11 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- made by Morten and CapsAdmin
pac.webaudio = pac.webaudio or {}
local webaudio = pac.webaudio
webaudio.Streams = webaudio.Streams or {}
webaudio.FilterType =
{
None = 0,
LowPass = 1,
HighPass = 2,
}
local listenerPosition, listenerAngle, listenerVelocity = Vector(), Angle(), Vector()
local lastListenerPosition, lastListenerPositionTime
pac.AddHook("RenderScene", "webaudio_3d", function(position, angle)
listenerPosition = position
listenerAngle = angle
lastListenerPosition = lastListenerPosition or listenerPosition
lastListenerPositionTime = lastListenerPositionTime or (CurTime() - FrameTime())
listenerVelocity = (listenerPosition - lastListenerPosition) / (CurTime() - lastListenerPositionTime)
lastListenerPosition = listenerPosition
lastListenerPositionTime = CurTime()
end)
webaudio.Streams.STREAM = {}
local STREAM = webaudio.Streams.STREAM
STREAM.__index = STREAM
-- Identity
STREAM.Id = nil
STREAM.Url = "" -- ??
-- State
STREAM.Loaded = false
-- Audio
STREAM.SampleCount = 0
-- Playback
STREAM.Paused = true
STREAM.SamplePosition = 0
STREAM.MaxLoopCount = nil
-- Playback speed
STREAM.PlaybackSpeed = 1
STREAM.AdditivePitchModifier = 0
-- Volume
STREAM.Panning = 0
STREAM.Volume = 1
STREAM.AdditiveVolumeFraction = 0
-- 3d
STREAM.Use3d = nil
STREAM.UseDoppler = true
STREAM.SourceEntity = NULL
STREAM.SourcePosition = nil
STREAM.LastSourcePosition = nil
STREAM.LastSourcePositionTime = nil
STREAM.SourceVelocity = nil
STREAM.SourceRadius = 1000
STREAM.ListenerOutOfRadius = false
local function DECLARE_PROPERTY(propertyName, javascriptSetterCode, defaultValue, filterFunction)
STREAM[propertyName] = defaultValue
STREAM["Set" .. propertyName] = function(self, value)
if filterFunction then
value = filterFunction(value, self)
end
self[propertyName] = value
self:Call(javascriptSetterCode, value)
end
STREAM["Get" .. propertyName] = function(self, ...)
return self[propertyName]
end
end
-- Identity
function STREAM:GetId()
return self.Id
end
function STREAM:SetId(id)
self.Id = id
return self
end
function STREAM:GetUrl()
return self.Url
end
function STREAM:SetUrl(url)
self.Url = url
return self
end
-- State
function STREAM:IsLoaded()
return self.Loaded
end
function STREAM:IsValid()
return true
end
function STREAM:Remove()
self.IsValid = function() return false end
end
-- Browser
function STREAM:Call(fmt, ...)
if not self.Loaded then return end
local code = string.format("try { streams[%d]%s } catch(e) { lua.print(e.toString()) }", self:GetId(), string.format(fmt, ...))
webaudio.Browser.QueueJavascript(code)
end
function STREAM:CallNow(fmt, ...)
if not self.Loaded then return end
local code = string.format("try { streams[%d]%s } catch(e) { lua.print(e.toString()) }", self:GetId(), string.format(fmt, ...))
webaudio.Browser.RunJavascript(code)
end
function STREAM:HandleBrowserMessage(messageType, ...)
if messageType == "call" then
self:HandleCallBrowserMessage(...)
elseif messageType == "fft" then
self:HandleFFTBrowserMessage(...)
elseif messageType == "stop" then
self.Paused = true
elseif messageType == "return" then
self.ReturnedValues = {...}
elseif messageType == "loaded" then
self:HandleLoadedBrowserMessage(...)
elseif t == "position" then
self:HandlePositionBrowserMessage(...)
end
end
-- Playback
function STREAM:GetMaxLoopCount()
return self.MaxLoopCount
end
function STREAM:SetMaxLoopCount(maxLoopCount)
self:Call(".max_loop = %i", maxLoopCount == true and -1 or maxLoopCount == false and 1 or tonumber(maxLoopCount) or 1)
self.MaxLoopCount = maxLoopCount
end
STREAM.SetLooping = STREAM.SetMaxLoopCount
function STREAM:GetSampleCount()
return self.SampleCount
end
function STREAM:Pause()
self.Paused = true
self:CallNow(".play(false)")
end
function STREAM:Resume()
self.Paused = false
self:UpdatePlaybackSpeed()
self:UpdateVolume()
self:CallNow(".play(true)")
end
function STREAM:Start()
self.Paused = false
self:UpdatePlaybackSpeed()
self:UpdateVolume()
self:CallNow(".play(true, 0)")
end
STREAM.Play = STREAM.Start
function STREAM:Stop()
self.Paused = true
self:CallNow(".play(false, 0)")
end
function STREAM:Restart()
self:SetSamplePosition(0)
end
function STREAM:SetPosition(positionFraction)
self:SetSamplePosition((positionFraction % 1) * self:GetSampleCount())
end
DECLARE_PROPERTY("SamplePosition", ".position = %f", 0)
-- Playback speed
function STREAM:GetPlaybackSpeed()
return self.PlaybackSpeed
end
function STREAM:GetAdditivePitchModifier()
return self.AdditivePitchModifier
end
function STREAM:SetPlaybackSpeed(playbackSpeedMultiplier)
if self.PlaybackSpeed == playbackSpeedMultiplier then return self end
self.PlaybackSpeed = playbackSpeedMultiplier
self:UpdatePlaybackSpeed()
return self
end
function STREAM:SetAdditivePitchModifier(additivePitchModifier)
if self.AdditivePitchModifier == additivePitchModifier then return self end
self.AdditivePitchModifier = additivePitchModifier
self:UpdatePlaybackSpeed()
return self
end
function STREAM:UpdatePlaybackSpeed()
self:Call(".speed = %f", self.PlaybackSpeed + self.AdditivePitchModifier)
end
-- Volume
function STREAM:GetPanning()
return self.Panning
end
function STREAM:GetVolume()
return self.Volume
end
function STREAM:GetAdditiveVolumeModifier()
return self.AdditiveVolumeModifier
end
function STREAM:SetPanning(panning)
if self.Panning == panning then return self end
self.Panning = panning
self:UpdateVolume()
return self
end
function STREAM:SetVolume(volumeFraction)
if self.Volume == volumeFraction then return self end
self.Volume = volumeFraction
self:UpdateVolume()
return self
end
function STREAM:SetAdditiveVolumeModifier (additiveVolumeFraction)
if self.AdditiveVolumeFraction == additiveVolumeFraction then return self end
self.AdditiveVolumeFraction = additiveVolumeFraction
self:UpdateVolume()
return self
end
function STREAM:UpdateSourcePosition()
if not self.SourceEntity:IsValid() then return end
self.SourcePosition = self.SourceEntity:GetPos()
end
function STREAM:UpdateVolume()
if self.Use3d then
self:UpdateVolume3d()
else
self:UpdateVolumeFlat()
end
end
function STREAM:UpdateVolumeFlat()
self:Call(".vol_right = %f", (math.Clamp(1 + self.Panning, 0, 1) * self.Volume) + self.AdditiveVolumeFraction)
self:Call(".vol_left = %f", (math.Clamp(1 - self.Panning, 0, 1) * self.Volume) + self.AdditiveVolumeFraction)
end
function STREAM:UpdateVolume3d()
self:UpdateSourcePosition()
self.SourcePosition = self.SourcePosition or Vector()
self.LastSourcePosition = self.LastSourcePosition or self.SourcePosition
self.LastSourcePositionTime = self.LastSourcePositionTime or (CurTime() - FrameTime())
self.SourceVelocity = (self.SourcePosition - self.LastSourcePosition) / (CurTime() - self.LastSourcePositionTime)
self.LastSourcePosition = self.SourcePosition
self.LastSourcePositionTime = CurTime()
local relativeSourcePosition = self.SourcePosition - listenerPosition
local distanceToSource = relativeSourcePosition:Length()
if distanceToSource < self.SourceRadius then
local pan = relativeSourcePosition:GetNormalized():Dot(listenerAngle:Right())
local volumeFraction = math.Clamp(1 - distanceToSource / self.SourceRadius, 0, 1) ^ 1.5
volumeFraction = volumeFraction * 0.75 * self.Volume
self:Call(".vol_right = %f", (math.Clamp(1 + pan, 0, 1) * volumeFraction) + self.AdditiveVolumeFraction)
self:Call(".vol_left = %f", (math.Clamp(1 - pan, 0, 1) * volumeFraction) + self.AdditiveVolumeFraction)
if self.UseDoppler then
local relativeSourcePosition = self.SourcePosition - listenerPosition
local relativeSourceVelocity = self.SourceVelocity - listenerVelocity
local relativeSourceSpeed = relativeSourcePosition:GetNormalized():Dot(-relativeSourceVelocity) * 0.0254
self:Call(".speed = %f", (self.PlaybackSpeed + (relativeSourceSpeed / webaudio.SpeedOfSound)) + self.AdditivePitchModifier)
end
self.ListenerOutOfRadius = false
else
if not self.ListenerOutOfRadius then
self:Call(".vol_right = 0")
self:Call(".vol_left = 0")
self.ListenerOutOfRadius = true
end
end
end
-- Filtering
DECLARE_PROPERTY("FilterType", ".filter_type = %i")
DECLARE_PROPERTY("FilterFraction", ".filter_fraction = %f", 0, function(num) return math.Clamp(num, 0, 1) end)
DECLARE_PROPERTY("Echo", ".useEcho(%s)", false)
DECLARE_PROPERTY("EchoDelay", ".setEchoDelay(Math.ceil(audio.sampleRate * %f))", 1, function(num) return math.max(num, 0) end)
DECLARE_PROPERTY("EchoFeedback", ".echo_feedback = %f", 0.75)
-- 3D
function STREAM:Enable3D(b)
self.Use3d = b
end
function STREAM:EnableDoppler(b)
self.UseDoppler = b
end
function STREAM:GetSourceEntity()
return self.SourceEntity
end
function STREAM:SetSourceEntity(sourceEntity, doNotRemove)
self.SourceEntity = sourceEntity
if not doNotRemove then
sourceEntity:CallOnRemove("webaudio_remove_stream_" .. tostring(self), function()
if self:IsValid() then
self:Remove()
end
end)
end
end
function STREAM:GetSourcePosition()
return self.SourcePosition
end
function STREAM:SetSourcePosition(vec)
self.SourcePosition = vec
end
function STREAM:GetSourceVelocity()
return self.SourceVelocity
end
function STREAM:SetSourceVelocity(vec)
self.SourceVelocity = vec
end
function STREAM:GetSourceRadius()
return self.SourceRadius
end
function STREAM:SetSourceRadius(sourceRadius)
self.SourceRadius = sourceRadius
end
function STREAM:Think()
if self.Paused then return end
if self.Use3d then
self:UpdateVolume3d() -- updates source position internally
else
self:UpdateSourcePosition()
end
end
function STREAM:__newindex(key, val)
if key == "OnFFT" then
if isfunction(val) then
self:Call(".usefft(true)")
else
self:Call(".usefft(false)")
end
end
rawset(self, key, val)
end
function STREAM:__tostring()
return string.format("stream[%p][%d][%s]", self, self:GetId(), self:GetUrl())
end
-- Internal browser message handlers
function STREAM:HandleCallBrowserMessage(methodName, ...)
if not self[methodName] then return end
self[methodName](self, ...)
end
function STREAM:HandleFFTBrowserMessage(serializeFFTData)
local fftArray = CompileString(serializeFFTData, "stream_fft_data")()
self.OnFFT(fftArray)
end
function STREAM:HandleLoadedBrowserMessage(sampleCount)
self.Loaded = true
self.SampleCount = sampleCount
self:SetFilterType(0)
if not self.Paused then
-- self:Play()
end
self:SetMaxLoopCount(self:GetMaxLoopCount())
self:SetEcho (self:GetEcho ())
self:SetEchoFeedback(self:GetEchoFeedback())
self:SetEchoDelay (self:GetEchoDelay ())
if self.OnLoad then
self:OnLoad()
end
end
function STREAM:HandlePositionBrowserMessage(samplePosition)
self.SamplePosition = samplePosition
end