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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hypnosound",
3
3
  "type": "module",
4
- "version": "1.5.1",
4
+ "version": "1.5.2",
5
5
  "description": "A small library for analyzing audio",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -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 history = [];
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 addValue(value) {
6
- if (history.length >= historySize) {
7
- history.shift(); // Remove the oldest value to maintain size
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
- history.push(value);
19
+ minQueue.push(value)
20
+ maxQueue.push(value)
10
21
  }
11
22
 
12
- function calculateMean() {
13
- return history.reduce((sum, val) => sum + val, 0) / history.length;
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 calculateMedian() {
17
- if (history.length === 0) return null;
18
- const sortedHistory = [...history].sort((a, b) => a - b);
19
- const mid = Math.floor(sortedHistory.length / 2);
20
- return sortedHistory.length % 2 !== 0 ? sortedHistory[mid] : (sortedHistory[mid - 1] + sortedHistory[mid]) / 2;
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 calculateMin() {
24
- return Math.min(...history);
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 calculateMax() {
28
- return Math.max(...history);
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 calculateStandardDeviation(mean) {
32
- return Math.sqrt(history.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / history.length);
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
- addValue(value);
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
- const mean = calculateMean();
41
- const median = calculateMedian();
42
- const min = calculateMin();
43
- const max = calculateMax();
44
- const standardDeviation = calculateStandardDeviation(mean);
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
- standardDeviation
53
- };
54
- };
192
+ }
193
+ }
55
194
  }