-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAudio Analyzer.lua
180 lines (143 loc) · 5.54 KB
/
Audio Analyzer.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
local CLASS = {}
--// SERVICES //--
local RUN_SERVICE = game:GetService("RunService")
--// CONSTANTS //--
--// VARIABLES //--
--// CONSTRUCTOR //--
function CLASS.new()
local dataTable = setmetatable(
{
},
CLASS
)
local proxyTable = setmetatable(
{
},
{
__index = function(self, index)
return dataTable[index]
end,
__newindex = function(self, index, newValue)
dataTable[index] = newValue
end
}
)
return proxyTable
end
--// FUNCTIONS //--
local function loadSound(sound)
local timeStamp = tick()
repeat wait(1) until (sound.TimeLength > 0) or (tick() - timeStamp > 5)
if (sound.TimeLength == 0) then
error("Sound Analyzer Timeout Error: Sound took too long to load or is of length 0")
end
end
local function getMeanFromArray(array)
local mean = 0
for _, value in pairs(array) do
mean = mean + value
end
return mean/#array
end
local function getMedianFromArray(array)
table.sort(array, function(a, b) return a > b end)
return array[math.ceil(#array/2)]
end
local function shallowClone(targetTable)
local clone = {}
for _, value in pairs(targetTable) do
table.insert(clone, value)
end
return clone
end
--// METHODS //--
function CLASS:processSoundByResolution(sound, sampleResolution)
assert(sound ~= nil, "Sound Analyzer Argument Error: Sound expected, got nil")
assert(typeof(sound) == "Instance", "Sound Analyzer Argument Error: Sound expected, got " .. typeof(sound))
assert(sound.ClassName == "Sound", "Sound Analyzer Argument Error: Sound expected, got " .. sound.ClassName)
assert(sampleResolution ~= nil, "Sound Analyzer Argument Error: number expected, got nil")
assert(typeof(sampleResolution) == "number", "Sound Analyzer Argument Error: number expected, got " .. typeof(sampleResolution))
assert(sampleResolution > 0, "Sound Analyzer Argument Error: sampleResolution (" .. sampleResolution .. ") must be a positive value")
loadSound(sound)
return self:processSoundByPlaybackSpeed(sound, (sound.TimeLength * 60)/sampleResolution)
end
function CLASS:processSoundByTime(sound, sampleTime)
assert(sound ~= nil, "Sound Analyzer Argument Error: Sound expected, got nil")
assert(typeof(sound) == "Instance", "Sound Analyzer Argument Error: Sound expected, got " .. typeof(sound))
assert(sound.ClassName == "Sound", "Sound Analyzer Argument Error: Sound expected, got " .. sound.ClassName)
assert(sampleTime ~= nil, "Sound Analyzer Argument Error: number expected, got nil")
assert(typeof(sampleTime) == "number", "Sound Analyzer Argument Error: number expected, got " .. typeof(sampleTime))
assert(sampleTime > 0, "Sound Analyzer Argument Error: sampleTime (" .. sampleTime .. ") must be a positive value")
loadSound(sound)
return self:processSoundByPlaybackSpeed(sound, sound.TimeLength/sampleTime)
end
function CLASS:processSoundByPlaybackSpeed(sound, samplePlaybackSpeed)
assert(sound ~= nil, "Sound Analyzer Argument Error: Sound expected, got nil")
assert(typeof(sound) == "Instance", "Sound Analyzer Argument Error: Sound expected, got " .. typeof(sound))
assert(sound.ClassName == "Sound", "Sound Analyzer Argument Error: Sound expected, got " .. sound.ClassName)
assert(samplePlaybackSpeed ~= nil, "Sound Analyzer Argument Error: number expected, got nil")
assert(typeof(samplePlaybackSpeed) == "number", "Sound Analyzer Argument Error: number expected, got " .. typeof(samplePlaybackSpeed))
if (samplePlaybackSpeed < 0.01) or (samplePlaybackSpeed > 20) then
warn("Sound Analyzer Argument Warning: PlaybackSpeed (" .. samplePlaybackSpeed .. ") must be between 0.01 or 20 otherwise quality will be affected")
end
loadSound(sound)
local sampledSound = self:sampleSoundByPlaybackSpeed(sound, samplePlaybackSpeed)
local soundData = self:processSampledSound(sampledSound)
return soundData
end
function CLASS:sampleSoundByPlaybackSpeed(sound, samplePlaybackSpeed)
local clone = sound:Clone()
clone.Parent = script
loadSound(clone)
clone.Volume = 0
clone.PlaybackSpeed = samplePlaybackSpeed
clone.Looped = false
local sampledTimePosition, sampledPlaybackLoudness = {}, {}
local timePosition = 0
local ended = false
local sampleConnection
sampleConnection = RUN_SERVICE.Stepped:Connect(function(_, dt)
if (ended) then
sampleConnection:Disconnect()
else
timePosition = timePosition + dt
table.insert(sampledTimePosition, timePosition * samplePlaybackSpeed)
table.insert(sampledPlaybackLoudness, clone.PlaybackLoudness)
end
end)
local endedConnection
endedConnection = clone.Ended:Connect(function()
endedConnection:Disconnect()
ended = true
end)
clone:Play()
clone.Ended:wait()
clone:Destroy()
return {sampledTimePosition = sampledTimePosition, sampledPlaybackLoudness = sampledPlaybackLoudness}
end
function CLASS:processSampledSound(sampledSound)
local highest, lowest = 0, 0
local mean = 0
local range = 0
local median = 0
local clonedSampledPlaybackLoudness = shallowClone(sampledSound.sampledPlaybackLoudness)
table.sort(clonedSampledPlaybackLoudness, function(a, b) return a > b end)
highest = clonedSampledPlaybackLoudness[1]
table.sort(clonedSampledPlaybackLoudness, function(a, b) return a < b end)
lowest = clonedSampledPlaybackLoudness[1]
mean = getMeanFromArray(clonedSampledPlaybackLoudness)
median = getMedianFromArray(clonedSampledPlaybackLoudness)
range = (highest - lowest)
return {
playbackLoudnessArray = sampledSound.sampledPlaybackLoudness,
timePositionArray = sampledSound.sampledTimePosition,
highest = highest,
lowest = lowest,
mean = mean,
median = median,
range = range
}
end
--// INSTRUCTIONS //--
CLASS.__index = CLASS
return CLASS