hypnosound 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/utils/calculateStats.js +168 -29
package/package.json
CHANGED
|
@@ -1,55 +1,194 @@
|
|
|
1
1
|
export const StatTypes = ['normalized', 'mean', 'median', 'standardDeviation', 'zScore', 'min', 'max']
|
|
2
|
+
|
|
2
3
|
export function makeCalculateStats(historySize = 500) {
|
|
3
|
-
let
|
|
4
|
+
let queue = []
|
|
5
|
+
let sum = 0
|
|
6
|
+
let sumOfSquares = 0
|
|
7
|
+
let minQueue = []
|
|
8
|
+
let maxQueue = []
|
|
9
|
+
let lowerHalf = [] // Max heap
|
|
10
|
+
let upperHalf = [] // Min heap
|
|
4
11
|
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
function updateMinMaxQueues(value) {
|
|
13
|
+
while (minQueue.length && minQueue[minQueue.length - 1] > value) {
|
|
14
|
+
minQueue.pop()
|
|
15
|
+
}
|
|
16
|
+
while (maxQueue.length && maxQueue[maxQueue.length - 1] < value) {
|
|
17
|
+
maxQueue.pop()
|
|
8
18
|
}
|
|
9
|
-
|
|
19
|
+
minQueue.push(value)
|
|
20
|
+
maxQueue.push(value)
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
function
|
|
13
|
-
|
|
23
|
+
function removeOldFromMinMaxQueues(oldValue) {
|
|
24
|
+
if (minQueue[0] === oldValue) {
|
|
25
|
+
minQueue.shift()
|
|
26
|
+
}
|
|
27
|
+
if (maxQueue[0] === oldValue) {
|
|
28
|
+
maxQueue.shift()
|
|
29
|
+
}
|
|
14
30
|
}
|
|
15
31
|
|
|
16
|
-
function
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
function addNumberToHeaps(number) {
|
|
33
|
+
if (lowerHalf.length === 0 || number < lowerHalf[0]) {
|
|
34
|
+
lowerHalf.push(number)
|
|
35
|
+
bubbleUp(lowerHalf, false)
|
|
36
|
+
} else {
|
|
37
|
+
upperHalf.push(number)
|
|
38
|
+
bubbleUp(upperHalf, true)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Rebalance heaps
|
|
42
|
+
if (lowerHalf.length > upperHalf.length + 1) {
|
|
43
|
+
upperHalf.push(extractTop(lowerHalf, false))
|
|
44
|
+
bubbleUp(upperHalf, true)
|
|
45
|
+
} else if (upperHalf.length > lowerHalf.length) {
|
|
46
|
+
lowerHalf.push(extractTop(upperHalf, true))
|
|
47
|
+
bubbleUp(lowerHalf, false)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeNumberFromHeaps(number) {
|
|
52
|
+
if (lowerHalf.includes(number)) {
|
|
53
|
+
removeNumber(lowerHalf, number, false)
|
|
54
|
+
} else if (upperHalf.includes(number)) {
|
|
55
|
+
removeNumber(upperHalf, number, true)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Rebalance heaps
|
|
59
|
+
if (lowerHalf.length > upperHalf.length + 1) {
|
|
60
|
+
upperHalf.push(extractTop(lowerHalf, false))
|
|
61
|
+
bubbleUp(upperHalf, true)
|
|
62
|
+
} else if (upperHalf.length > lowerHalf.length) {
|
|
63
|
+
lowerHalf.push(extractTop(upperHalf, true))
|
|
64
|
+
bubbleUp(lowerHalf, false)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function bubbleUp(heap, isMinHeap) {
|
|
69
|
+
let index = heap.length - 1
|
|
70
|
+
while (index > 0) {
|
|
71
|
+
let parentIdx = Math.floor((index - 1) / 2)
|
|
72
|
+
if ((isMinHeap && heap[index] < heap[parentIdx]) || (!isMinHeap && heap[index] > heap[parentIdx])) {
|
|
73
|
+
;[heap[index], heap[parentIdx]] = [heap[parentIdx], heap[index]]
|
|
74
|
+
index = parentIdx
|
|
75
|
+
} else {
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function extractTop(heap, isMinHeap) {
|
|
82
|
+
if (heap.length === 0) {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
let top = heap[0]
|
|
86
|
+
heap[0] = heap[heap.length - 1]
|
|
87
|
+
heap.pop()
|
|
88
|
+
sinkDown(heap, isMinHeap)
|
|
89
|
+
return top
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sinkDown(heap, isMinHeap) {
|
|
93
|
+
let index = 0
|
|
94
|
+
let length = heap.length
|
|
95
|
+
|
|
96
|
+
while (index < length) {
|
|
97
|
+
let leftChildIndex = 2 * index + 1
|
|
98
|
+
let rightChildIndex = 2 * index + 2
|
|
99
|
+
let swapIndex = null
|
|
100
|
+
|
|
101
|
+
if (leftChildIndex < length) {
|
|
102
|
+
if ((isMinHeap && heap[leftChildIndex] < heap[index]) || (!isMinHeap && heap[leftChildIndex] > heap[index])) {
|
|
103
|
+
swapIndex = leftChildIndex
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (rightChildIndex < length) {
|
|
108
|
+
if (
|
|
109
|
+
(isMinHeap && heap[rightChildIndex] < (swapIndex === null ? heap[index] : heap[leftChildIndex])) ||
|
|
110
|
+
(!isMinHeap && heap[rightChildIndex] > (swapIndex === null ? heap[index] : heap[leftChildIndex]))
|
|
111
|
+
) {
|
|
112
|
+
swapIndex = rightChildIndex
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (swapIndex === null) {
|
|
117
|
+
break
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
;[heap[index], heap[swapIndex]] = [heap[swapIndex], heap[index]]
|
|
121
|
+
index = swapIndex
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function removeNumber(heap, number, isMinHeap) {
|
|
126
|
+
let index = heap.indexOf(number)
|
|
127
|
+
if (index !== -1) {
|
|
128
|
+
heap[index] = heap[heap.length - 1]
|
|
129
|
+
heap.pop()
|
|
130
|
+
sinkDown(heap, isMinHeap)
|
|
131
|
+
}
|
|
21
132
|
}
|
|
22
133
|
|
|
23
|
-
function
|
|
24
|
-
|
|
134
|
+
function calculateMedian() {
|
|
135
|
+
if (lowerHalf.length === upperHalf.length) {
|
|
136
|
+
return (lowerHalf[0] + upperHalf[0]) / 2
|
|
137
|
+
} else {
|
|
138
|
+
return lowerHalf[0]
|
|
139
|
+
}
|
|
25
140
|
}
|
|
26
141
|
|
|
27
|
-
function
|
|
28
|
-
|
|
142
|
+
function calculateMAD(median) {
|
|
143
|
+
let deviations = queue.map((value) => Math.abs(value - median))
|
|
144
|
+
let mad = medianAbsoluteDeviation(deviations)
|
|
145
|
+
return mad
|
|
29
146
|
}
|
|
30
147
|
|
|
31
|
-
function
|
|
32
|
-
|
|
148
|
+
function medianAbsoluteDeviation(values) {
|
|
149
|
+
if (values.length === 0) {
|
|
150
|
+
return 0
|
|
151
|
+
}
|
|
152
|
+
let median = calculateMedian(values)
|
|
153
|
+
let absoluteDeviations = values.map((value) => Math.abs(value - median))
|
|
154
|
+
let medianAbsoluteDeviation = calculateMedian(absoluteDeviations)
|
|
155
|
+
return medianAbsoluteDeviation
|
|
33
156
|
}
|
|
34
157
|
|
|
35
158
|
return function calculateStats(value) {
|
|
36
|
-
if (typeof value !== 'number') throw new Error('Input must be a number')
|
|
159
|
+
if (typeof value !== 'number') throw new Error('Input must be a number')
|
|
160
|
+
|
|
161
|
+
updateMinMaxQueues(value)
|
|
162
|
+
addNumberToHeaps(value)
|
|
163
|
+
|
|
164
|
+
queue.push(value)
|
|
165
|
+
sum += value
|
|
166
|
+
sumOfSquares += value * value
|
|
37
167
|
|
|
38
|
-
|
|
168
|
+
if (queue.length > historySize) {
|
|
169
|
+
let removed = queue.shift()
|
|
170
|
+
sum -= removed
|
|
171
|
+
sumOfSquares -= removed * removed
|
|
172
|
+
removeOldFromMinMaxQueues(removed)
|
|
173
|
+
removeNumberFromHeaps(removed)
|
|
174
|
+
}
|
|
39
175
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
176
|
+
let mean = sum / queue.length
|
|
177
|
+
let variance = sumOfSquares / queue.length - mean * mean
|
|
178
|
+
let min = minQueue.length ? minQueue[0] : Infinity
|
|
179
|
+
let max = maxQueue.length ? maxQueue[0] : -Infinity
|
|
180
|
+
let median = calculateMedian()
|
|
181
|
+
let mad = calculateMAD(median)
|
|
45
182
|
|
|
46
183
|
return {
|
|
47
184
|
current: value,
|
|
48
|
-
mean,
|
|
185
|
+
zScore: (variance ? (value - mean) / Math.sqrt(variance) : 0) / 6,
|
|
186
|
+
normalized: mad, // Using MAD normalization as 'normalized'
|
|
187
|
+
standardDeviation: Math.sqrt(variance),
|
|
49
188
|
median,
|
|
189
|
+
mean,
|
|
50
190
|
min,
|
|
51
191
|
max,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
55
194
|
}
|