@iam-protocol/pulse-sdk 0.3.9 → 0.4.0

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@iam-protocol/pulse-sdk",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "Client-side SDK for IAM Protocol — sensor capture, TBH generation, ZK proof construction",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -20,12 +20,14 @@ const SPEAKER_FEATURE_COUNT = 44;
20
20
 
21
21
  // Dynamic imports for browser compatibility
22
22
  let pitchDetector: ((buf: Float32Array) => number | null) | null = null;
23
+ let pitchDetectorRate = 0;
23
24
  let meydaModule: any = null;
24
25
 
25
- async function getPitchDetector(): Promise<(buf: Float32Array) => number | null> {
26
- if (!pitchDetector) {
26
+ async function getPitchDetector(sampleRate: number): Promise<(buf: Float32Array) => number | null> {
27
+ if (!pitchDetector || pitchDetectorRate !== sampleRate) {
27
28
  const PitchFinder = await import("pitchfinder");
28
- pitchDetector = PitchFinder.YIN({ sampleRate: 16000 });
29
+ pitchDetector = PitchFinder.YIN({ sampleRate });
30
+ pitchDetectorRate = sampleRate;
29
31
  }
30
32
  return pitchDetector;
31
33
  }
@@ -48,7 +50,7 @@ async function detectF0Contour(
48
50
  samples: Float32Array,
49
51
  sampleRate: number
50
52
  ): Promise<{ f0: number[]; amplitudes: number[]; periods: number[] }> {
51
- const detect = await getPitchDetector();
53
+ const detect = await getPitchDetector(sampleRate);
52
54
  const f0: number[] = [];
53
55
  const amplitudes: number[] = [];
54
56
  const periods: number[] = [];
@@ -244,10 +246,10 @@ async function computeLTAS(
244
246
  );
245
247
 
246
248
  if (features) {
247
- if (typeof features.spectralCentroid === "number") centroids.push(features.spectralCentroid);
248
- if (typeof features.spectralRolloff === "number") rolloffs.push(features.spectralRolloff);
249
- if (typeof features.spectralFlatness === "number") flatnesses.push(features.spectralFlatness);
250
- if (typeof features.spectralSpread === "number") spreads.push(features.spectralSpread);
249
+ if (Number.isFinite(features.spectralCentroid)) centroids.push(features.spectralCentroid);
250
+ if (Number.isFinite(features.spectralRolloff)) rolloffs.push(features.spectralRolloff);
251
+ if (Number.isFinite(features.spectralFlatness)) flatnesses.push(features.spectralFlatness);
252
+ if (Number.isFinite(features.spectralSpread)) spreads.push(features.spectralSpread);
251
253
  }
252
254
  }
253
255
 
@@ -108,16 +108,21 @@ export function autocorrelation(values: number[], lag: number = 1): number {
108
108
  function normalizeGroup(features: number[]): number[] {
109
109
  if (features.length === 0) return features;
110
110
 
111
+ // Sanitize NaN/Infinity to 0 before computing stats.
112
+ // Meyda spectral features can produce NaN on near-silent frames (0/0),
113
+ // and a single NaN would poison the entire modality group.
114
+ const clean = features.map((v) => (Number.isFinite(v) ? v : 0));
115
+
111
116
  let sum = 0;
112
- for (const v of features) sum += v;
113
- const mean = sum / features.length;
117
+ for (const v of clean) sum += v;
118
+ const mean = sum / clean.length;
114
119
 
115
120
  let sqSum = 0;
116
- for (const v of features) sqSum += (v - mean) * (v - mean);
117
- const std = Math.sqrt(sqSum / features.length);
121
+ for (const v of clean) sqSum += (v - mean) * (v - mean);
122
+ const std = Math.sqrt(sqSum / clean.length);
118
123
 
119
- if (std === 0) return features.map(() => 0);
120
- return features.map((v) => (v - mean) / std);
124
+ if (std === 0) return clean.map(() => 0);
125
+ return clean.map((v) => (v - mean) / std);
121
126
  }
122
127
 
123
128
  export function fuseFeatures(