@parastud/react-native-vban 0.1.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/README.md +87 -0
- package/android/build.gradle +15 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/java/expo/modules/systemaudiocapture/SystemAudioCaptureModule.kt +57 -0
- package/android/src/main/java/expo/modules/systemaudiocapture/SystemAudioCaptureService.kt +151 -0
- package/android/src/main/java/expo/modules/systemaudiocapture/VbanReceiverModule.kt +216 -0
- package/android/src/main/java/expo/modules/systemaudiocapture/VbanSendEngine.kt +141 -0
- package/android/src/main/java/expo/modules/systemaudiocapture/VbanSenderModule.kt +129 -0
- package/expo-module.config.json +10 -0
- package/package.json +35 -0
- package/src/NativeVbanReceiver.ts +71 -0
- package/src/NativeVbanSender.ts +131 -0
- package/src/index.ts +31 -0
- package/src/types.ts +17 -0
- package/src/vban/constants.ts +93 -0
- package/src/vban/index.ts +3 -0
- package/src/vban/packet.ts +159 -0
- package/src/vban/pcm.ts +99 -0
package/src/vban/pcm.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert between VBAN wire PCM formats and normalized Float32 samples in the
|
|
3
|
+
* range [-1, 1). Samples are interleaved by channel, matching the VBAN payload
|
|
4
|
+
* layout (frame0_ch0, frame0_ch1, frame1_ch0, ...).
|
|
5
|
+
*
|
|
6
|
+
* Int16 is the common case (Voicemeeter's default), but Byte8/Int24/Int32 and
|
|
7
|
+
* native Float32 are handled too. Float64 and the packed 10/12-bit formats are
|
|
8
|
+
* not implemented yet and throw.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DATA_TYPE_SIZE, VbanDataType } from './constants';
|
|
12
|
+
|
|
13
|
+
/** Decode a raw VBAN payload into interleaved Float32 samples in [-1, 1). */
|
|
14
|
+
export function pcmToFloat32(payload: Uint8Array, dataType: VbanDataType): Float32Array {
|
|
15
|
+
const sampleSize = DATA_TYPE_SIZE[dataType];
|
|
16
|
+
const count = Math.floor(payload.length / sampleSize);
|
|
17
|
+
const out = new Float32Array(count);
|
|
18
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
19
|
+
|
|
20
|
+
switch (dataType) {
|
|
21
|
+
case VbanDataType.Byte8:
|
|
22
|
+
for (let i = 0; i < count; i++) out[i] = view.getInt8(i) / 128;
|
|
23
|
+
break;
|
|
24
|
+
case VbanDataType.Int16:
|
|
25
|
+
for (let i = 0; i < count; i++) out[i] = view.getInt16(i * 2, true) / 32768;
|
|
26
|
+
break;
|
|
27
|
+
case VbanDataType.Int24:
|
|
28
|
+
for (let i = 0; i < count; i++) {
|
|
29
|
+
const b0 = payload[i * 3];
|
|
30
|
+
const b1 = payload[i * 3 + 1];
|
|
31
|
+
const b2 = payload[i * 3 + 2];
|
|
32
|
+
let v = b0 | (b1 << 8) | (b2 << 16);
|
|
33
|
+
if (v & 0x800000) v |= ~0xffffff; // sign-extend 24 -> 32 bit
|
|
34
|
+
out[i] = v / 8388608;
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
case VbanDataType.Int32:
|
|
38
|
+
for (let i = 0; i < count; i++) out[i] = view.getInt32(i * 4, true) / 2147483648;
|
|
39
|
+
break;
|
|
40
|
+
case VbanDataType.Float32:
|
|
41
|
+
for (let i = 0; i < count; i++) out[i] = view.getFloat32(i * 4, true);
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
throw new Error(`pcmToFloat32: unsupported data type ${VbanDataType[dataType]}`);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Clamp to [-1, 1) so encoding never wraps around. */
|
|
50
|
+
function clamp(x: number): number {
|
|
51
|
+
return x < -1 ? -1 : x > 0.999969482421875 ? 0.999969482421875 : x;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Encode interleaved Float32 samples into a raw VBAN payload. */
|
|
55
|
+
export function float32ToPcm(samples: Float32Array, dataType: VbanDataType): Uint8Array {
|
|
56
|
+
const sampleSize = DATA_TYPE_SIZE[dataType];
|
|
57
|
+
const out = new Uint8Array(samples.length * sampleSize);
|
|
58
|
+
const view = new DataView(out.buffer);
|
|
59
|
+
|
|
60
|
+
switch (dataType) {
|
|
61
|
+
case VbanDataType.Byte8:
|
|
62
|
+
for (let i = 0; i < samples.length; i++) view.setInt8(i, Math.round(clamp(samples[i]) * 128));
|
|
63
|
+
break;
|
|
64
|
+
case VbanDataType.Int16:
|
|
65
|
+
for (let i = 0; i < samples.length; i++) {
|
|
66
|
+
view.setInt16(i * 2, Math.round(clamp(samples[i]) * 32768), true);
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
case VbanDataType.Int24:
|
|
70
|
+
for (let i = 0; i < samples.length; i++) {
|
|
71
|
+
const v = Math.round(clamp(samples[i]) * 8388608) & 0xffffff;
|
|
72
|
+
out[i * 3] = v & 0xff;
|
|
73
|
+
out[i * 3 + 1] = (v >> 8) & 0xff;
|
|
74
|
+
out[i * 3 + 2] = (v >> 16) & 0xff;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case VbanDataType.Int32:
|
|
78
|
+
for (let i = 0; i < samples.length; i++) {
|
|
79
|
+
view.setInt32(i * 4, Math.round(clamp(samples[i]) * 2147483648), true);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case VbanDataType.Float32:
|
|
83
|
+
for (let i = 0; i < samples.length; i++) view.setFloat32(i * 4, samples[i], true);
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
throw new Error(`float32ToPcm: unsupported data type ${VbanDataType[dataType]}`);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Peak absolute amplitude of interleaved samples, useful for a level meter. */
|
|
92
|
+
export function peakLevel(samples: Float32Array): number {
|
|
93
|
+
let peak = 0;
|
|
94
|
+
for (let i = 0; i < samples.length; i++) {
|
|
95
|
+
const a = samples[i] < 0 ? -samples[i] : samples[i];
|
|
96
|
+
if (a > peak) peak = a;
|
|
97
|
+
}
|
|
98
|
+
return peak;
|
|
99
|
+
}
|