hypnosound 1.2.0 → 1.2.1

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/README.md CHANGED
@@ -1,2 +1,57 @@
1
1
  # hypnosound
2
- A little library for analyzing audio
2
+ A little library for extracting audio features, and optionally applying statistics to them.
3
+
4
+ ## Usage
5
+ Check out [index.html](./index.html) for a simple example. You can run it via `npm run start`.
6
+
7
+ You can either use the AudioProcessor, which maintains state and calculates the statistics for you, or use of the functions directly in a functional way. Everything can be used functionally except for spectralFlux, which requires state.
8
+
9
+ ### AudioProcessor
10
+
11
+ ```javascript
12
+ import AudioProcessor from 'hypnosound';
13
+ const a = new AudioProcessor();
14
+ console.log({
15
+ energy: a.energy(fft),
16
+ spectralCentroid: a.spectralCentroid(fft),
17
+ spectralCrest: a.spectralCrest(fft),
18
+ spectralEntropy: a.spectralEntropy(fft),
19
+ spectralFlux: a.spectralFlux(fft),
20
+ spectralKurtosis: a.spectralKurtosis(fft),
21
+ spectralRolloff: a.spectralRolloff(fft),
22
+ spectralRoughness: a.spectralRoughness(fft),
23
+ spectralSkew: a.spectralSkew(fft),
24
+ spectralSpread: a.spectralSpread(fft),
25
+ });
26
+
27
+ ```
28
+
29
+ Each audio feature comes with statistics, which are calculated automatically. You can access them like so:
30
+ ```javascript
31
+ const {value, stats} = a.energy(fft)
32
+ console.log(`the current value for energy is ${value}`);
33
+ console.log(`here are some stats: zScore: ${stats.zScore}, normalized: ${stats.normalized}, standardDeviation: ${stats.standardDeviation}, median: ${stats.median}, mean: ${stats.mean}, min: ${stats.min}, max: ${stats.max}`);
34
+ ```
35
+ ⚠️ __Warning: Each call to a function will update the statistics for that feature. so I'd recommend saving the result of the function call to a variable and then use that__
36
+
37
+ ### Functional
38
+ ```javascript
39
+ import {energy} from 'hypnosound'; // or any other audio feature EXCEPT spectralFlux
40
+ console.log(energy(fft)); // returns the instantaneous energy value.
41
+ ```
42
+
43
+ You may want to calculate statistics for the audio features on your own, but still use the functional style.
44
+ Since statistics require state, this must be managed outside the function in purely functional mode.
45
+ Here's an example of how you might do that:
46
+
47
+ ```javascript
48
+ import { makeCalculateStats, spectralCentroid } from 'hypnosound'
49
+ const calculateStats = makeCalculateStats()
50
+
51
+ const value = spectralCentroid(fft)
52
+ const stats = calculateStats(value) // WARNING: each call to calculateStats will update the state.
53
+
54
+ console.log({value, stats})
55
+ ```
56
+
57
+
package/index.html CHANGED
@@ -9,7 +9,8 @@
9
9
  <button id="start">Start Listening</button>
10
10
  <script type="module">
11
11
  import AudioProcessor from './index.js'
12
- document.getElementById('start').addEventListener('click', async () => {
12
+ const button = document.getElementById('start')
13
+ button.addEventListener('click', async () => {
13
14
  const a = new AudioProcessor()
14
15
  try {
15
16
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
@@ -19,7 +20,7 @@
19
20
 
20
21
  source.connect(analyser);
21
22
  analyser.fftSize = 32768; // Or whatever size you need
22
-
23
+ analyser.smoothingTimeConstant = 0
23
24
  const bufferLength = analyser.frequencyBinCount;
24
25
  const dataArray = new Uint8Array(bufferLength);
25
26
 
@@ -41,7 +42,6 @@
41
42
  spectralSkew: a.spectralSkew(dataArray),
42
43
  spectralSpread: a.spectralSpread(dataArray),
43
44
  });
44
- // console.log(a.spectralCentroid(dataArray).value);
45
45
  };
46
46
 
47
47
  draw();
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { makeCalculateStats } from './src/utils/calculateStats.js'
2
-
2
+ import {applyKaiserWindow} from './src/utils/applyKaiserWindow.js'
3
3
  import energy from './src/audio/energy.js'
4
4
  import spectralCentroid from './src/audio/spectralCentroid.js'
5
5
  import spectralCrest from './src/audio/spectralCrest.js'
@@ -40,61 +40,71 @@ class AudioProcessor {
40
40
  }
41
41
 
42
42
  energy = (fft) => {
43
- const value = energy(fft)
43
+ const windowedFft = applyKaiserWindow(fft)
44
+ const value = energy(windowedFft)
44
45
  const stats = this.statCalculators.energy(value)
45
46
  return { value, stats }
46
47
  }
47
48
 
48
49
  spectralCentroid = (fft) => {
49
- const value = spectralCentroid(fft)
50
+ const windowedFft = applyKaiserWindow(fft)
51
+ const value = spectralCentroid(applyKaiserWindow(windowedFft))
50
52
  const stats = this.statCalculators.spectralCentroid(value)
51
53
  return { value, stats }
52
54
  }
53
55
 
54
56
  spectralCrest = (fft) => {
55
- const value = spectralCrest(fft)
57
+ const windowedFft = applyKaiserWindow(fft)
58
+ const value = spectralCrest(windowedFft)
56
59
  const stats = this.statCalculators.spectralCentroid(value)
57
60
  return { value, stats }
58
61
  }
59
62
 
60
63
  spectralEntropy = (fft) => {
61
- const value = spectralEntropy(fft)
64
+ const windowedFft = applyKaiserWindow(fft)
65
+ const value = spectralEntropy(windowedFft)
62
66
  const stats = this.statCalculators.spectralEntropy(value)
63
67
  return { value, stats }
64
68
  }
65
69
 
66
70
  spectralFlux = (fft) => {
67
- const value = spectralFlux(fft, this.previousValue.spectralFlux)
68
- this.previousValue.spectralFlux = new Uint8Array(fft)
71
+ const windowedFft = applyKaiserWindow(fft)
72
+ const value = spectralFlux(windowedFft, this.previousValue.spectralFlux)
73
+ this.previousValue.spectralFlux = new Uint8Array(windowedFft)
69
74
  const stats = this.statCalculators.spectralFlux(value)
70
75
  return { value, stats }
71
76
  }
72
77
  spectralKurtosis = (fft) => {
73
- const value = spectralKurtosis(fft)
78
+ const windowedFft = applyKaiserWindow(fft)
79
+ const value = spectralKurtosis(windowedFft)
74
80
  const stats = this.statCalculators.spectralKurtosis(value)
75
81
  return { value, stats }
76
82
  }
77
83
 
78
84
  spectralRolloff = (fft) => {
79
- const value = spectralRolloff(fft)
85
+ const windowedFft = applyKaiserWindow(fft)
86
+ const value = spectralRolloff(windowedFft)
80
87
  const stats = this.statCalculators.spectralRolloff(value)
81
88
  return { value, stats }
82
89
  }
83
90
 
84
91
  spectralRoughness = (fft) => {
85
- const value = spectralRoughness(fft)
92
+ const windowedFft = applyKaiserWindow(fft)
93
+ const value = spectralRoughness(windowedFft)
86
94
  const stats = this.statCalculators.spectralRoughness(value)
87
95
  return { value, stats }
88
96
  }
89
97
 
90
98
  spectralSkew = (fft) => {
91
- const value = spectralSkew(fft)
99
+ const windowedFft = applyKaiserWindow(fft)
100
+ const value = spectralSkew(windowedFft)
92
101
  const stats = this.statCalculators.spectralSkew(value)
93
102
  return { value, stats }
94
103
  }
95
104
 
96
105
  spectralSpread = (fft) => {
97
- const value = spectralSpread(fft)
106
+ const windowedFft = applyKaiserWindow(fft)
107
+ const value = spectralSpread(windowedFft)
98
108
  const stats = this.statCalculators.spectralSpread(value)
99
109
  return { value, stats }
100
110
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hypnosound",
3
3
  "type": "module",
4
- "version": "1.2.0",
4
+ "version": "1.2.1",
5
5
  "description": "A small library for analyzing audio",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -1,17 +1,25 @@
1
- import mu from '../utils/mu.js'
2
1
  export default function spectralCentroid(fft) {
3
2
  const computed = calculateSpectralCentroid(fft) // Process FFT data
4
3
  return computed * 1.5
5
4
  }
6
-
7
5
  function calculateSpectralCentroid(ampSpectrum) {
8
- const centroid = mu(1, ampSpectrum)
9
- if (centroid === null) return null
6
+ if (!ampSpectrum.length) return null // Early exit if the spectrum is empty
7
+
8
+ let numerator = 0
9
+ let denominator = 0
10
+
11
+ // Calculate the weighted sum (numerator) and the sum of the amplitudes (denominator)
12
+ ampSpectrum.forEach((amplitude, index) => {
13
+ numerator += index * amplitude
14
+ denominator += amplitude
15
+ })
16
+
17
+ // Avoid dividing by zero
18
+ if (denominator === 0) return null
19
+
20
+ const centroidIndex = numerator / denominator
21
+ // Normalize the centroid index to be between 0 and 1
22
+ const normalizedCentroid = centroidIndex / (ampSpectrum.length - 1)
10
23
 
11
- // Maximum centroid occurs when all energy is at the highest frequency bin
12
- const maxCentroid = mu(
13
- 1,
14
- ampSpectrum.map((val, index) => (index === ampSpectrum.length - 1 ? 1 : 0)),
15
- )
16
- return centroid / maxCentroid // Normalize the centroid
24
+ return normalizedCentroid
17
25
  }
@@ -0,0 +1,31 @@
1
+ export function applyKaiserWindow(audioBuffer, beta = 5.658) {
2
+ // Beta default based on common use
3
+ const N = audioBuffer.length
4
+ const windowedBuffer = new Float32Array(N)
5
+ const I0Beta = I0(beta) // Calculate the zeroth order modified Bessel function of the first kind for beta
6
+
7
+ for (let n = 0; n < N; n++) {
8
+ const windowValue = I0(beta * Math.sqrt(1 - Math.pow((2 * n) / (N - 1) - 1, 2))) / I0Beta
9
+ windowedBuffer[n] = audioBuffer[n] * windowValue
10
+ }
11
+
12
+ return windowedBuffer
13
+ }
14
+
15
+ // Calculate the zeroth order modified Bessel function of the first kind
16
+ // This approximation is suitable for the window function calculation
17
+ function I0(x) {
18
+ let sum = 1.0
19
+ let y = x / 2.0
20
+ let term = 1.0
21
+ let k = 1
22
+
23
+ while (term > 1e-6 * sum) {
24
+ // Continue until the added value is insignificant
25
+ term *= (y / k) ** 2
26
+ sum += term
27
+ k++
28
+ }
29
+
30
+ return sum
31
+ }
package/cmd.js DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import AudioProcessor from './index.js'
3
- console.log(AudioProcessor)