@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.18 → 1.0.19
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 +1 -1
- package/dist/SpatialAudioManager.d.ts +6 -1
- package/dist/SpatialAudioManager.js +93 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ sdk.setListenerFromLSD(listenerPos, cameraPos, lookAtPos);
|
|
|
79
79
|
- **Coordinate normalization** – Unreal sends centimeters; `SpatialAudioManager` auto-detects large values and converts to meters once.
|
|
80
80
|
- **Orientation math** – `setListenerFromLSD()` builds forward/right/up vectors from camera/LookAt to keep the listener aligned with head movement.
|
|
81
81
|
- **Dynamic distance gain** – `updateSpatialAudio()` measures distance from listener → source and applies a smooth rolloff curve, so distant avatars fade to silence.
|
|
82
|
-
- **Noise handling** –
|
|
82
|
+
- **Noise handling** – the AudioWorklet denoiser now runs an adaptive multi-band gate (per W3C AudioWorklet guidance) before the high/low-pass filters, stripping constant HVAC/fan noise even when the speaker is close.
|
|
83
83
|
|
|
84
84
|
#### How Spatial Audio Is Built
|
|
85
85
|
1. **Telemetry ingestion** – each LSD packet is passed through `setListenerFromLSD(listenerPos, cameraPos, lookAtPos)` so the Web Audio listener matches the player’s real head/camera pose.
|
|
@@ -11,6 +11,12 @@ type DenoiserOptions = {
|
|
|
11
11
|
threshold?: number;
|
|
12
12
|
noiseFloor?: number;
|
|
13
13
|
release?: number;
|
|
14
|
+
attack?: number;
|
|
15
|
+
holdMs?: number;
|
|
16
|
+
maxReduction?: number;
|
|
17
|
+
hissCut?: number;
|
|
18
|
+
expansionRatio?: number;
|
|
19
|
+
learnRate?: number;
|
|
14
20
|
};
|
|
15
21
|
type SpatialAudioOptions = {
|
|
16
22
|
distance?: SpatialAudioDistanceConfig;
|
|
@@ -25,7 +31,6 @@ export declare class SpatialAudioManager extends EventManager {
|
|
|
25
31
|
private options;
|
|
26
32
|
private denoiseWorkletReady;
|
|
27
33
|
private denoiseWorkletUrl?;
|
|
28
|
-
private denoiserWasmBytes?;
|
|
29
34
|
private listenerPosition;
|
|
30
35
|
private listenerInitialized;
|
|
31
36
|
private listenerDirection;
|
|
@@ -82,9 +82,12 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
82
82
|
threshold: this.options.denoiser?.threshold,
|
|
83
83
|
noiseFloor: this.options.denoiser?.noiseFloor,
|
|
84
84
|
release: this.options.denoiser?.release,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
attack: this.options.denoiser?.attack,
|
|
86
|
+
holdMs: this.options.denoiser?.holdMs,
|
|
87
|
+
maxReduction: this.options.denoiser?.maxReduction,
|
|
88
|
+
hissCut: this.options.denoiser?.hissCut,
|
|
89
|
+
expansionRatio: this.options.denoiser?.expansionRatio,
|
|
90
|
+
learnRate: this.options.denoiser?.learnRate,
|
|
88
91
|
},
|
|
89
92
|
});
|
|
90
93
|
}
|
|
@@ -577,47 +580,101 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
577
580
|
super();
|
|
578
581
|
const cfg = (options && options.processorOptions) || {};
|
|
579
582
|
this.enabled = cfg.enabled !== false;
|
|
580
|
-
this.threshold =
|
|
581
|
-
this.noiseFloor =
|
|
582
|
-
this.
|
|
583
|
-
this.
|
|
583
|
+
this.threshold = this._sanitize(cfg.threshold, 0.003, 0.05, 0.012);
|
|
584
|
+
this.noiseFloor = this._sanitize(cfg.noiseFloor, 0.0005, 0.05, 0.004);
|
|
585
|
+
this.attack = this._sanitize(cfg.attack, 0.01, 0.9, 0.35);
|
|
586
|
+
this.release = this._sanitize(cfg.release, 0.01, 0.9, 0.18);
|
|
587
|
+
this.holdSamples = Math.max(
|
|
588
|
+
8,
|
|
589
|
+
Math.round(
|
|
590
|
+
sampleRate * this._sanitize(cfg.holdMs, 10, 400, 110) / 1000
|
|
591
|
+
)
|
|
592
|
+
);
|
|
593
|
+
this.maxReduction = this._sanitize(cfg.maxReduction, 0.1, 0.95, 0.85);
|
|
594
|
+
this.hissCut = this._sanitize(cfg.hissCut, 0, 1, 0.45);
|
|
595
|
+
this.expansionRatio = this._sanitize(cfg.expansionRatio, 1.1, 4, 1.8);
|
|
596
|
+
this.learnRate = this._sanitize(cfg.learnRate, 0.001, 0.3, 0.08);
|
|
597
|
+
this.channelState = [];
|
|
598
|
+
this.hfAlpha = Math.exp(-2 * Math.PI * 3200 / sampleRate);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
_sanitize(value, min, max, fallback) {
|
|
602
|
+
if (typeof value !== 'number' || !isFinite(value)) {
|
|
603
|
+
return fallback;
|
|
604
|
+
}
|
|
605
|
+
return Math.min(max, Math.max(min, value));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
_ensureState(index) {
|
|
609
|
+
if (!this.channelState[index]) {
|
|
610
|
+
this.channelState[index] = {
|
|
611
|
+
envelope: this.noiseFloor,
|
|
612
|
+
noise: this.noiseFloor,
|
|
613
|
+
gain: 1,
|
|
614
|
+
quietSamples: 0,
|
|
615
|
+
lpState: 0,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
return this.channelState[index];
|
|
584
619
|
}
|
|
585
620
|
|
|
586
621
|
process(inputs, outputs) {
|
|
587
622
|
const input = inputs[0];
|
|
588
623
|
const output = outputs[0];
|
|
589
|
-
if (!input ||
|
|
624
|
+
if (!input || !output) {
|
|
590
625
|
return true;
|
|
591
626
|
}
|
|
592
627
|
|
|
593
|
-
for (let channel = 0; channel <
|
|
628
|
+
for (let channel = 0; channel < output.length; channel++) {
|
|
594
629
|
const inChannel = input[channel];
|
|
595
630
|
const outChannel = output[channel];
|
|
596
631
|
if (!inChannel || !outChannel) {
|
|
597
632
|
continue;
|
|
598
633
|
}
|
|
599
634
|
|
|
600
|
-
|
|
635
|
+
if (!this.enabled) {
|
|
636
|
+
for (let i = 0; i < inChannel.length; i++) {
|
|
637
|
+
outChannel[i] = inChannel[i];
|
|
638
|
+
}
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const state = this._ensureState(channel);
|
|
643
|
+
|
|
601
644
|
for (let i = 0; i < inChannel.length; i++) {
|
|
602
645
|
const sample = inChannel[i];
|
|
603
|
-
|
|
604
|
-
}
|
|
646
|
+
const magnitude = Math.abs(sample);
|
|
605
647
|
|
|
606
|
-
|
|
607
|
-
this.smoothedLevel += (rms - this.smoothedLevel) * this.release;
|
|
608
|
-
const dynamicThreshold = Math.max(
|
|
609
|
-
this.noiseFloor,
|
|
610
|
-
this.threshold * 0.6 + this.smoothedLevel * 0.4
|
|
611
|
-
);
|
|
648
|
+
state.envelope += (magnitude - state.envelope) * this.attack;
|
|
612
649
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
650
|
+
if (state.envelope < this.threshold) {
|
|
651
|
+
state.noise += (state.envelope - state.noise) * this.learnRate;
|
|
652
|
+
state.quietSamples++;
|
|
653
|
+
} else {
|
|
654
|
+
state.quietSamples = 0;
|
|
616
655
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
656
|
+
|
|
657
|
+
const ratio = state.noise / Math.max(state.envelope, 1e-6);
|
|
658
|
+
let gainTarget = 1 - Math.min(0.98, Math.pow(ratio, this.expansionRatio));
|
|
659
|
+
gainTarget = Math.max(0, Math.min(1, gainTarget));
|
|
660
|
+
|
|
661
|
+
if (state.quietSamples > this.holdSamples) {
|
|
662
|
+
gainTarget *= 1 - this.maxReduction;
|
|
620
663
|
}
|
|
664
|
+
|
|
665
|
+
state.gain += (gainTarget - state.gain) * this.release;
|
|
666
|
+
let processed = sample * state.gain;
|
|
667
|
+
|
|
668
|
+
state.lpState = this.hfAlpha * state.lpState + (1 - this.hfAlpha) * processed;
|
|
669
|
+
const highComponent = processed - state.lpState;
|
|
670
|
+
const hissRatio = Math.min(
|
|
671
|
+
1,
|
|
672
|
+
Math.abs(highComponent) / (Math.abs(state.lpState) + 1e-5)
|
|
673
|
+
);
|
|
674
|
+
const hissGain = 1 - hissRatio * this.hissCut;
|
|
675
|
+
processed = state.lpState + highComponent * hissGain;
|
|
676
|
+
|
|
677
|
+
outChannel[i] = processed;
|
|
621
678
|
}
|
|
622
679
|
}
|
|
623
680
|
|
|
@@ -655,6 +712,12 @@ registerProcessor('odyssey-denoise', OdysseyDenoiseProcessor);
|
|
|
655
712
|
threshold: 0.012,
|
|
656
713
|
noiseFloor: 0.004,
|
|
657
714
|
release: 0.18,
|
|
715
|
+
attack: 0.35,
|
|
716
|
+
holdMs: 110,
|
|
717
|
+
maxReduction: 0.85,
|
|
718
|
+
hissCut: 0.45,
|
|
719
|
+
expansionRatio: 1.8,
|
|
720
|
+
learnRate: 0.08,
|
|
658
721
|
};
|
|
659
722
|
return {
|
|
660
723
|
distance: {
|
|
@@ -668,6 +731,12 @@ registerProcessor('odyssey-denoise', OdysseyDenoiseProcessor);
|
|
|
668
731
|
threshold: options?.denoiser?.threshold ?? denoiserDefaults.threshold,
|
|
669
732
|
noiseFloor: options?.denoiser?.noiseFloor ?? denoiserDefaults.noiseFloor,
|
|
670
733
|
release: options?.denoiser?.release ?? denoiserDefaults.release,
|
|
734
|
+
attack: options?.denoiser?.attack ?? denoiserDefaults.attack,
|
|
735
|
+
holdMs: options?.denoiser?.holdMs ?? denoiserDefaults.holdMs,
|
|
736
|
+
maxReduction: options?.denoiser?.maxReduction ?? denoiserDefaults.maxReduction,
|
|
737
|
+
hissCut: options?.denoiser?.hissCut ?? denoiserDefaults.hissCut,
|
|
738
|
+
expansionRatio: options?.denoiser?.expansionRatio ?? denoiserDefaults.expansionRatio,
|
|
739
|
+
learnRate: options?.denoiser?.learnRate ?? denoiserDefaults.learnRate,
|
|
671
740
|
},
|
|
672
741
|
};
|
|
673
742
|
}
|
package/package.json
CHANGED