decibri 0.0.1 → 1.0.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/LICENSE +191 -0
- package/README.md +294 -0
- package/binding.gyp +122 -0
- package/examples/wav-capture.js +48 -0
- package/examples/websocket-server.js +28 -0
- package/examples/websocket-stream.js +44 -0
- package/index.js +190 -0
- package/package.json +55 -6
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/linux-arm64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/src/decibri.cc +390 -0
- package/src/mac_permission.mm +21 -0
- package/types/index.d.ts +273 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#import <AVFoundation/AVFoundation.h>
|
|
2
|
+
|
|
3
|
+
// CheckMicrophonePermission — macOS only (compiled via binding.gyp OS=='mac').
|
|
4
|
+
//
|
|
5
|
+
// Returns nullptr if the process may proceed (Authorized or NotDetermined).
|
|
6
|
+
// Returns a static error string if permission is Denied or Restricted.
|
|
7
|
+
//
|
|
8
|
+
// NotDetermined: PortAudio will trigger the OS permission dialog on first
|
|
9
|
+
// access naturally. After the user denies, status becomes Denied and the
|
|
10
|
+
// next call returns the error string.
|
|
11
|
+
extern "C" const char* CheckMicrophonePermission() {
|
|
12
|
+
AVAuthorizationStatus status =
|
|
13
|
+
[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
|
14
|
+
|
|
15
|
+
if (status == AVAuthorizationStatusDenied ||
|
|
16
|
+
status == AVAuthorizationStatusRestricted) {
|
|
17
|
+
return "Microphone access denied. "
|
|
18
|
+
"Enable access in System Settings \xe2\x86\x92 Privacy & Security \xe2\x86\x92 Microphone.";
|
|
19
|
+
}
|
|
20
|
+
return nullptr; // Authorized or NotDetermined — proceed
|
|
21
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
import { Readable, ReadableOptions } from 'stream';
|
|
4
|
+
|
|
5
|
+
// ─── Interfaces ───────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/** Describes a single audio input device returned by `Decibri.devices()`. */
|
|
8
|
+
export interface DeviceInfo {
|
|
9
|
+
/** PortAudio device index — pass as `options.device` to target this device. */
|
|
10
|
+
index: number;
|
|
11
|
+
/** Human-readable device name reported by the OS. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Maximum number of input channels supported by this device. */
|
|
14
|
+
maxInputChannels: number;
|
|
15
|
+
/** Device's native/preferred sample rate in Hz. */
|
|
16
|
+
defaultSampleRate: number;
|
|
17
|
+
/** Whether this is the current system default input device. */
|
|
18
|
+
isDefault: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Version strings returned by `Decibri.version()`. */
|
|
22
|
+
export interface VersionInfo {
|
|
23
|
+
/** decibri package version (e.g. `"1.0.0"`). */
|
|
24
|
+
decibri: string;
|
|
25
|
+
/** Bundled PortAudio version string (e.g. `"PortAudio V19.7.0-devel..."`). */
|
|
26
|
+
portaudio: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Constructor options for `Decibri`.
|
|
31
|
+
* All standard Node.js `ReadableOptions` (e.g. `highWaterMark`) are also accepted.
|
|
32
|
+
*/
|
|
33
|
+
export interface DecibriOptions extends ReadableOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Samples per second.
|
|
36
|
+
* @default 16000
|
|
37
|
+
* @minimum 1000
|
|
38
|
+
* @maximum 384000
|
|
39
|
+
*/
|
|
40
|
+
sampleRate?: number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Number of input channels.
|
|
44
|
+
* @default 1
|
|
45
|
+
* @minimum 1
|
|
46
|
+
* @maximum 32
|
|
47
|
+
*/
|
|
48
|
+
channels?: number;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Frames delivered per audio callback — controls chunk size and latency.
|
|
52
|
+
* At 16 kHz the default of 1600 produces 100 ms chunks.
|
|
53
|
+
* @default 1600
|
|
54
|
+
* @minimum 64
|
|
55
|
+
* @maximum 65536
|
|
56
|
+
*/
|
|
57
|
+
framesPerBuffer?: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Device to capture from. Pass a numeric index from `Decibri.devices()`,
|
|
61
|
+
* or a case-insensitive substring of the device name.
|
|
62
|
+
* Omit to use the system default input device.
|
|
63
|
+
* Throws `TypeError` if a name string matches zero or multiple devices.
|
|
64
|
+
*/
|
|
65
|
+
device?: number | string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Enable voice activity detection. When `true`, emits `'speech'` when RMS
|
|
69
|
+
* energy crosses `vadThreshold` and `'silence'` after `vadHoldoff` ms of
|
|
70
|
+
* sub-threshold audio.
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
vad?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* RMS energy threshold for speech detection (0–1 normalised scale).
|
|
77
|
+
* Tune upward in noisy environments.
|
|
78
|
+
* @default 0.01
|
|
79
|
+
* @minimum 0
|
|
80
|
+
* @maximum 1
|
|
81
|
+
*/
|
|
82
|
+
vadThreshold?: number;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Milliseconds of sub-threshold audio before `'silence'` is emitted.
|
|
86
|
+
* Prevents rapid toggling on natural speech pauses.
|
|
87
|
+
* @default 300
|
|
88
|
+
* @minimum 0
|
|
89
|
+
*/
|
|
90
|
+
vadHoldoff?: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Sample encoding format.
|
|
94
|
+
* - `'int16'` — 16-bit signed integer, little-endian (default)
|
|
95
|
+
* - `'float32'` — 32-bit IEEE 754 float, little-endian
|
|
96
|
+
*
|
|
97
|
+
* Both formats emit a `Buffer`. For zero-copy typed array access:
|
|
98
|
+
* ```ts
|
|
99
|
+
* // int16 (default)
|
|
100
|
+
* const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / 2);
|
|
101
|
+
* // float32
|
|
102
|
+
* const samples = new Float32Array(chunk.buffer, chunk.byteOffset, chunk.length / 4);
|
|
103
|
+
* ```
|
|
104
|
+
* @default 'int16'
|
|
105
|
+
*/
|
|
106
|
+
format?: 'int16' | 'float32';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── Decibri ─────────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A Node.js `Readable` stream that captures raw PCM audio from the microphone.
|
|
113
|
+
*
|
|
114
|
+
* **Audio format** (default):
|
|
115
|
+
* - Encoding: 16-bit signed integer (Int16), little-endian
|
|
116
|
+
* - Sample rate: 16 000 Hz
|
|
117
|
+
* - Channels: 1 (mono)
|
|
118
|
+
*
|
|
119
|
+
* Each `'data'` event emits a `Buffer` of Int16 samples. To view as a typed
|
|
120
|
+
* array without copying:
|
|
121
|
+
* ```ts
|
|
122
|
+
* mic.on('data', (chunk: Buffer) => {
|
|
123
|
+
* const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / 2);
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* import Decibri from 'decibri';
|
|
130
|
+
*
|
|
131
|
+
* const mic = new Decibri({ sampleRate: 16000, channels: 1 });
|
|
132
|
+
*
|
|
133
|
+
* mic.on('data', (chunk) => {
|
|
134
|
+
* // chunk is a Buffer of Int16 PCM samples
|
|
135
|
+
* });
|
|
136
|
+
* mic.on('error', (err) => console.error(err));
|
|
137
|
+
* mic.on('backpressure', () => console.warn('Consumer too slow — consider dropping frames'));
|
|
138
|
+
*
|
|
139
|
+
* setTimeout(() => mic.stop(), 5000);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare class Decibri extends Readable {
|
|
143
|
+
constructor(options?: DecibriOptions);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Stop microphone capture and end the stream cleanly.
|
|
147
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
148
|
+
*/
|
|
149
|
+
stop(): void;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* `true` while the microphone is actively capturing audio.
|
|
153
|
+
*/
|
|
154
|
+
readonly isOpen: boolean;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns all available audio input devices on the system.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* const devices = Decibri.devices();
|
|
162
|
+
* // [
|
|
163
|
+
* // { index: 0, name: 'Built-in Microphone', maxInputChannels: 1,
|
|
164
|
+
* // defaultSampleRate: 44100, isDefault: true },
|
|
165
|
+
* // ...
|
|
166
|
+
* // ]
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
static devices(): DeviceInfo[];
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns version information for decibri and the bundled PortAudio library.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* Decibri.version();
|
|
177
|
+
* // { decibri: '1.0.0', portaudio: 'PortAudio V19.7.0-devel...' }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
static version(): VersionInfo;
|
|
181
|
+
|
|
182
|
+
// ── Event overloads ─────────────────────────────────────────────────────────
|
|
183
|
+
//
|
|
184
|
+
// The 'backpressure' event is added to all listener methods below.
|
|
185
|
+
// It fires when push() returns false — the mic cannot be paused, so the
|
|
186
|
+
// consumer should drain the stream or drop frames.
|
|
187
|
+
|
|
188
|
+
addListener(event: 'close', listener: () => void): this;
|
|
189
|
+
addListener(event: 'data', listener: (chunk: Buffer) => void): this;
|
|
190
|
+
addListener(event: 'end', listener: () => void): this;
|
|
191
|
+
addListener(event: 'error', listener: (err: Error) => void): this;
|
|
192
|
+
addListener(event: 'pause', listener: () => void): this;
|
|
193
|
+
addListener(event: 'readable', listener: () => void): this;
|
|
194
|
+
addListener(event: 'resume', listener: () => void): this;
|
|
195
|
+
addListener(event: 'backpressure', listener: () => void): this;
|
|
196
|
+
addListener(event: 'speech', listener: () => void): this;
|
|
197
|
+
addListener(event: 'silence', listener: () => void): this;
|
|
198
|
+
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
199
|
+
|
|
200
|
+
emit(event: 'close'): boolean;
|
|
201
|
+
emit(event: 'data', chunk: Buffer): boolean;
|
|
202
|
+
emit(event: 'end'): boolean;
|
|
203
|
+
emit(event: 'error', err: Error): boolean;
|
|
204
|
+
emit(event: 'pause'): boolean;
|
|
205
|
+
emit(event: 'readable'): boolean;
|
|
206
|
+
emit(event: 'resume'): boolean;
|
|
207
|
+
emit(event: 'backpressure'): boolean;
|
|
208
|
+
emit(event: 'speech'): boolean;
|
|
209
|
+
emit(event: 'silence'): boolean;
|
|
210
|
+
emit(event: string | symbol, ...args: any[]): boolean;
|
|
211
|
+
|
|
212
|
+
on(event: 'close', listener: () => void): this;
|
|
213
|
+
on(event: 'data', listener: (chunk: Buffer) => void): this;
|
|
214
|
+
on(event: 'end', listener: () => void): this;
|
|
215
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
216
|
+
on(event: 'pause', listener: () => void): this;
|
|
217
|
+
on(event: 'readable', listener: () => void): this;
|
|
218
|
+
on(event: 'resume', listener: () => void): this;
|
|
219
|
+
on(event: 'backpressure', listener: () => void): this;
|
|
220
|
+
on(event: 'speech', listener: () => void): this;
|
|
221
|
+
on(event: 'silence', listener: () => void): this;
|
|
222
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
223
|
+
|
|
224
|
+
once(event: 'close', listener: () => void): this;
|
|
225
|
+
once(event: 'data', listener: (chunk: Buffer) => void): this;
|
|
226
|
+
once(event: 'end', listener: () => void): this;
|
|
227
|
+
once(event: 'error', listener: (err: Error) => void): this;
|
|
228
|
+
once(event: 'pause', listener: () => void): this;
|
|
229
|
+
once(event: 'readable', listener: () => void): this;
|
|
230
|
+
once(event: 'resume', listener: () => void): this;
|
|
231
|
+
once(event: 'backpressure', listener: () => void): this;
|
|
232
|
+
once(event: 'speech', listener: () => void): this;
|
|
233
|
+
once(event: 'silence', listener: () => void): this;
|
|
234
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
235
|
+
|
|
236
|
+
prependListener(event: 'close', listener: () => void): this;
|
|
237
|
+
prependListener(event: 'data', listener: (chunk: Buffer) => void): this;
|
|
238
|
+
prependListener(event: 'end', listener: () => void): this;
|
|
239
|
+
prependListener(event: 'error', listener: (err: Error) => void): this;
|
|
240
|
+
prependListener(event: 'pause', listener: () => void): this;
|
|
241
|
+
prependListener(event: 'readable', listener: () => void): this;
|
|
242
|
+
prependListener(event: 'resume', listener: () => void): this;
|
|
243
|
+
prependListener(event: 'backpressure', listener: () => void): this;
|
|
244
|
+
prependListener(event: 'speech', listener: () => void): this;
|
|
245
|
+
prependListener(event: 'silence', listener: () => void): this;
|
|
246
|
+
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
247
|
+
|
|
248
|
+
prependOnceListener(event: 'close', listener: () => void): this;
|
|
249
|
+
prependOnceListener(event: 'data', listener: (chunk: Buffer) => void): this;
|
|
250
|
+
prependOnceListener(event: 'end', listener: () => void): this;
|
|
251
|
+
prependOnceListener(event: 'error', listener: (err: Error) => void): this;
|
|
252
|
+
prependOnceListener(event: 'pause', listener: () => void): this;
|
|
253
|
+
prependOnceListener(event: 'readable', listener: () => void): this;
|
|
254
|
+
prependOnceListener(event: 'resume', listener: () => void): this;
|
|
255
|
+
prependOnceListener(event: 'backpressure', listener: () => void): this;
|
|
256
|
+
prependOnceListener(event: 'speech', listener: () => void): this;
|
|
257
|
+
prependOnceListener(event: 'silence', listener: () => void): this;
|
|
258
|
+
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
259
|
+
|
|
260
|
+
removeListener(event: 'close', listener: () => void): this;
|
|
261
|
+
removeListener(event: 'data', listener: (chunk: Buffer) => void): this;
|
|
262
|
+
removeListener(event: 'end', listener: () => void): this;
|
|
263
|
+
removeListener(event: 'error', listener: (err: Error) => void): this;
|
|
264
|
+
removeListener(event: 'pause', listener: () => void): this;
|
|
265
|
+
removeListener(event: 'readable', listener: () => void): this;
|
|
266
|
+
removeListener(event: 'resume', listener: () => void): this;
|
|
267
|
+
removeListener(event: 'backpressure', listener: () => void): this;
|
|
268
|
+
removeListener(event: 'speech', listener: () => void): this;
|
|
269
|
+
removeListener(event: 'silence', listener: () => void): this;
|
|
270
|
+
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export = Decibri;
|