There has been a really clunky API introduction pattern with this recent wave of Audio classes added to Roblox’s engine. The problem became more blatant with this week’s API changes:
This is the current state of Roblox’s Audio API:
Class AudioAnalyzer : Instance
Property AudioAnalyzer.PeakLevel: number [ReadOnly]
Property AudioAnalyzer.RmsLevel: number [ReadOnly]
Property AudioAnalyzer.SpectrumEnabled: boolean
Property AudioAnalyzer.WindowSize: Enum.AudioWindowSize
Function AudioAnalyzer:GetConnectedWires(pin: string) -> { Instance }
Function AudioAnalyzer:GetInputPins() -> { any }
Function AudioAnalyzer:GetOutputPins() -> { any }
Function AudioAnalyzer:GetSpectrum() -> { any } [CustomLuaState]
Event AudioAnalyzer.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioChannelMixer : Instance [NotBrowsable]
Property AudioChannelMixer.Layout: Enum.AudioChannelLayout
Function AudioChannelMixer:GetConnectedWires(pin: string) -> { Instance }
Event AudioChannelMixer.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioChannelSplitter : Instance [NotBrowsable]
Property AudioChannelSplitter.Layout: Enum.AudioChannelLayout
Function AudioChannelSplitter:GetConnectedWires(pin: string) -> { Instance }
Event AudioChannelSplitter.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioChorus : Instance
Property AudioChorus.Bypass: boolean
Property AudioChorus.Depth: number
Property AudioChorus.Mix: number
Property AudioChorus.Rate: number
Function AudioChorus:GetConnectedWires(pin: string) -> { Instance }
Function AudioChorus:GetInputPins() -> { any }
Function AudioChorus:GetOutputPins() -> { any }
Event AudioChorus.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioCompressor : Instance
Property AudioCompressor.Attack: number
Property AudioCompressor.Bypass: boolean
Property AudioCompressor.MakeupGain: number
Property AudioCompressor.Ratio: number
Property AudioCompressor.Release: number
Property AudioCompressor.Threshold: number
Property AudioCompressor.Editor: boolean {RobloxScriptSecurity} [📁 LoadOnly] [NotReplicated]
Function AudioCompressor:GetConnectedWires(pin: string) -> { Instance }
Function AudioCompressor:GetInputPins() -> { any }
Function AudioCompressor:GetOutputPins() -> { any }
Event AudioCompressor.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioDeviceInput : Instance
Property AudioDeviceInput.AccessList: BinaryString {RobloxSecurity} [Hidden] [NotScriptable]
Property AudioDeviceInput.AccessType: Enum.AccessModifierType
Property AudioDeviceInput.Active: boolean {✏️RobloxScriptSecurity}
Property AudioDeviceInput.IsReady: boolean {RobloxScriptSecurity} [ReadOnly]
Property AudioDeviceInput.Muted: boolean
Property AudioDeviceInput.MutedByLocalUser: boolean {RobloxScriptSecurity} [NotReplicated]
Property AudioDeviceInput.Player: Player?
Property AudioDeviceInput.Volume: number
Function AudioDeviceInput:GetConnectedWires(pin: string) -> { Instance }
Function AudioDeviceInput:GetInputPins() -> { any }
Function AudioDeviceInput:GetOutputPins() -> { any }
Function AudioDeviceInput:GetUserIdAccessList() -> { any }
Function AudioDeviceInput:SetUserIdAccessList(userIds: { any }) -> ()
Event AudioDeviceInput.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioDeviceOutput : Instance
Property AudioDeviceOutput.Player: Player?
Function AudioDeviceOutput:GetConnectedWires(pin: string) -> { Instance }
Function AudioDeviceOutput:GetInputPins() -> { any }
Function AudioDeviceOutput:GetOutputPins() -> { any }
Event AudioDeviceOutput.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioDistortion : Instance
Property AudioDistortion.Bypass: boolean
Property AudioDistortion.Level: number
Function AudioDistortion:GetConnectedWires(pin: string) -> { Instance }
Function AudioDistortion:GetInputPins() -> { any }
Function AudioDistortion:GetOutputPins() -> { any }
Event AudioDistortion.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioEcho : Instance
Property AudioEcho.Bypass: boolean
Property AudioEcho.DelayTime: number
Property AudioEcho.DryLevel: number
Property AudioEcho.Feedback: number
Property AudioEcho.RampTime: number
Property AudioEcho.WetLevel: number
Function AudioEcho:GetConnectedWires(pin: string) -> { Instance }
Function AudioEcho:GetInputPins() -> { any }
Function AudioEcho:GetOutputPins() -> { any }
Function AudioEcho:Reset() -> () {RobloxScriptSecurity}
Event AudioEcho.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioEmitter : Instance
Property AudioEmitter.AngleAttenuation: BinaryString {RobloxSecurity}
Property AudioEmitter.DistanceAttenuation: BinaryString {RobloxSecurity}
Property AudioEmitter.AudioInteractionGroup: string
Property AudioEmitter.SimulationFidelity: Enum.AudioSimulationFidelity
Function AudioEmitter:GetAngleAttenuation() -> { [string]: any } [CustomLuaState]
Function AudioEmitter:GetAudibilityFor(listener: AudioListener) -> number
Function AudioEmitter:GetConnectedWires(pin: string) -> { Instance }
Function AudioEmitter:GetDistanceAttenuation() -> { [string]: any } [CustomLuaState]
Function AudioEmitter:GetInputPins() -> { any }
Function AudioEmitter:GetInteractingListeners() -> { Instance }
Function AudioEmitter:GetOutputPins() -> { any }
Function AudioEmitter:SetAngleAttenuation(curve: { [string]: any }) -> () [CustomLuaState]
Function AudioEmitter:SetDistanceAttenuation(curve: { [string]: any }) -> () [CustomLuaState]
Event AudioEmitter.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioEqualizer : Instance
Property AudioEqualizer.Bypass: boolean
Property AudioEqualizer.HighGain: number
Property AudioEqualizer.LowGain: number
Property AudioEqualizer.MidGain: number
Property AudioEqualizer.MidRange: NumberRange
Property AudioEqualizer.Editor: boolean {RobloxScriptSecurity} [📁 LoadOnly] [NotReplicated]
Function AudioEqualizer:GetConnectedWires(pin: string) -> { Instance }
Function AudioEqualizer:GetInputPins() -> { any }
Function AudioEqualizer:GetOutputPins() -> { any }
Event AudioEqualizer.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioFader : Instance
Property AudioFader.Bypass: boolean
Property AudioFader.Volume: number
Function AudioFader:GetConnectedWires(pin: string) -> { Instance }
Function AudioFader:GetInputPins() -> { any }
Function AudioFader:GetOutputPins() -> { any }
Event AudioFader.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioFilter : Instance
Property AudioFilter.Bypass: boolean
Property AudioFilter.FilterType: Enum.AudioFilterType
Property AudioFilter.Frequency: number
Property AudioFilter.Gain: number
Property AudioFilter.Q: number
Property AudioFilter.Editor: boolean {RobloxScriptSecurity} [📁 LoadOnly] [NotReplicated]
Function AudioFilter:GetConnectedWires(pin: string) -> { Instance }
Function AudioFilter:GetGainAt(frequency: number) -> number
Function AudioFilter:GetInputPins() -> { any }
Function AudioFilter:GetOutputPins() -> { any }
Event AudioFilter.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioFlanger : Instance
Property AudioFlanger.Bypass: boolean
Property AudioFlanger.Depth: number
Property AudioFlanger.Mix: number
Property AudioFlanger.Rate: number
Function AudioFlanger:GetConnectedWires(pin: string) -> { Instance }
Function AudioFlanger:GetInputPins() -> { any }
Function AudioFlanger:GetOutputPins() -> { any }
Event AudioFlanger.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioLimiter : Instance
Property AudioLimiter.Bypass: boolean
Property AudioLimiter.MaxLevel: number
Property AudioLimiter.Release: number
Property AudioLimiter.Editor: boolean {RobloxScriptSecurity} [📁 LoadOnly] [NotReplicated]
Function AudioLimiter:GetConnectedWires(pin: string) -> { Instance }
Function AudioLimiter:GetInputPins() -> { any }
Function AudioLimiter:GetOutputPins() -> { any }
Event AudioLimiter.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioListener : Instance
Property AudioListener.AudioInteractionGroup: string
Property AudioListener.AngleAttenuation: BinaryString {RobloxSecurity}
Property AudioListener.DistanceAttenuation: BinaryString {RobloxSecurity}
Property AudioListener.SimulationFidelity: Enum.AudioSimulationFidelity
Function AudioListener:GetAngleAttenuation() -> { [string]: any } [CustomLuaState]
Function AudioListener:GetAudibilityFor(emitter: AudioEmitter) -> number
Function AudioListener:GetConnectedWires(pin: string) -> { Instance }
Function AudioListener:GetDistanceAttenuation() -> { [string]: any } [CustomLuaState]
Function AudioListener:GetInputPins() -> { any }
Function AudioListener:GetInteractingEmitters() -> { Instance }
Function AudioListener:GetOutputPins() -> { any }
Function AudioListener:Reset() -> () {RobloxScriptSecurity}
Function AudioListener:SetAngleAttenuation(curve: { [string]: any }) -> () [CustomLuaState]
Function AudioListener:SetDistanceAttenuation(curve: { [string]: any }) -> () [CustomLuaState]
Event AudioListener.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioPitchShifter : Instance
Property AudioPitchShifter.Bypass: boolean
Property AudioPitchShifter.Pitch: number
Property AudioPitchShifter.WindowSize: Enum.AudioWindowSize
Function AudioPitchShifter:GetConnectedWires(pin: string) -> { Instance }
Function AudioPitchShifter:GetInputPins() -> { any }
Function AudioPitchShifter:GetOutputPins() -> { any }
Event AudioPitchShifter.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioPlayer : Instance
Property AudioPlayer.Asset: string
Property AudioPlayer.AssetId: string [📁 LoadOnly] [Hidden] [NotReplicated] [Deprecated]
Property AudioPlayer.AutoLoad: boolean
Property AudioPlayer.IsReady: boolean [ReadOnly]
Property AudioPlayer.TimeLength: number [ReadOnly]
Property AudioPlayer.IsPlaying: boolean {✏️RobloxSecurity}
Property AudioPlayer.Looping: boolean
Property AudioPlayer.PlaybackSpeed: number
Property AudioPlayer.TimePosition: number
Property AudioPlayer.LoopRegion: NumberRange
Property AudioPlayer.PlaybackRegion: NumberRange
Property AudioPlayer.Volume: number
Function AudioPlayer:GetConnectedWires(pin: string) -> { Instance }
Function AudioPlayer:GetInputPins() -> { any }
Function AudioPlayer:GetOutputPins() -> { any }
Function AudioPlayer:GetWaveformAsync(timeRange: NumberRange, samples: number) -> { any } [Yields]
Function AudioPlayer:Play() -> ()
Function AudioPlayer:Stop() -> ()
Event AudioPlayer.Ended()
Event AudioPlayer.Looped()
Event AudioPlayer.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
Class AudioReverb : Instance
Property AudioReverb.Bypass: boolean
Property AudioReverb.DecayRatio: number
Property AudioReverb.DecayTime: number
Property AudioReverb.Density: number
Property AudioReverb.Diffusion: number
Property AudioReverb.DryLevel: number
Property AudioReverb.EarlyDelayTime: number
Property AudioReverb.HighCutFrequency: number
Property AudioReverb.LateDelayTime: number
Property AudioReverb.LowShelfFrequency: number
Property AudioReverb.LowShelfGain: number
Property AudioReverb.ReferenceFrequency: number
Property AudioReverb.WetLevel: number
Function AudioReverb:GetConnectedWires(pin: string) -> { Instance }
Function AudioReverb:GetInputPins() -> { any }
Function AudioReverb:GetOutputPins() -> { any }
Function AudioReverb:Reset() -> () {RobloxScriptSecurity}
Event AudioReverb.WiringChanged(connected: boolean, pin: string, wire: Wire, instance: Instance)
The Audio instances have no superclass, they all derive from the Instance
class. This makes polymorphic analysis of an audio’s connection tree within the confines of Roblox’s typechecking system really frustrating.
This is some code I recently had to write to accomplish that:
local audioClasses = {
AudioChorus = true,
AudioCompressor = true,
AudioDistortion = true,
AudioEcho = true,
AudioEqualizer = true,
AudioFader = true,
AudioFilter = true,
AudioFlanger = true,
AudioLimiter = true,
AudioPitchShifter = true,
AudioReverb = true,
}
local inputClasses = {
AudioEmitter = true,
AudioAnalyzer = true,
AudioDeviceOutput = true
}
local outputClasses = {
AudioPlayer = true,
AudioDeviceInput = true,
}
local function expandAudioWiring(inst: Instance, callback: (instance: Instance, depth: number) -> (), depth: number?, visited: { [Instance]: true }?)
local depth = depth or 0
local visited: typeof(assert(visited)) = visited or {}
if visited[inst] then
return
end
callback(inst, depth)
visited[inst] = true
local input = false
local output = false
if audioClasses[inst.ClassName] then
input = true
output = true
elseif inputClasses[inst.ClassName] then
input = true
elseif outputClasses[inst.ClassName] then
output = true
end
if input then
local inputs = (inst :: any):GetConnectedWires("Input")
for _, wire in ipairs(inputs) do
if wire.SourceInstance then
expandAudioWiring(wire.SourceInstance, callback, depth + 1, visited)
end
if wire.TargetInstance then
expandAudioWiring(wire.TargetInstance, callback, depth + 1, visited)
end
end
end
if output then
local outputs = (inst :: any):GetConnectedWires("Output")
for _, wire in ipairs(outputs) do
if wire.SourceInstance then
expandAudioWiring(wire.SourceInstance, callback, depth + 1, visited)
end
if wire.TargetInstance then
expandAudioWiring(wire.TargetInstance, callback, depth + 1, visited)
end
end
end
end
Notice how I have to manually store a table of ClassNames and use the “:: any
” cast to deal with Luau’s type annotations not knowing what the heck is going on here.
This is why I feel like the API design is faulty as it currently stands. It needs refinement to work better in Roblox’s typechecked ecosystem. I provided feedback on this to the engineer working on Roblox’s audio feature previously, but the response I got was rather vague, alluding to some grand vision with wireable instances.
Is there some inheritance pattern here going on that makes it impossible for these members to be unioned together? (i.e. similar to TextLabel/TextBox/TextButton or ImageLabel/ImageButton)
If so then I’ll take it at face value. I just hope something can be done at the reflection level to help clean up this redundancy.