bionic-audio 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bionic Audio Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # bionic-audio
2
+
3
+ Professional Windows WASAPI audio recorder library for Node.js and Bun. Record microphone, system loopback, or both simultaneously with support for multiple audio formats and advanced quality settings.
4
+
5
+ **Status:** Production Ready | **Platform:** Windows 10/11 | **Architecture:** x64, ia32
6
+
7
+ ## Features
8
+
9
+ - ๐ŸŽค **Microphone Recording**: Capture input from your microphone
10
+ - ๐Ÿ”Š **Loopback Recording**: Capture system audio (what's playing on speakers)
11
+ - ๐ŸŽ™๏ธ **Dual Recording**: Record both microphone and loopback simultaneously
12
+ - ๐Ÿ“Š **Multiple Formats**: 16-bit PCM, 24-bit PCM, or 32-bit float WAV files
13
+ - โฑ๏ธ **Duration Control**: Record for a specific duration or indefinitely
14
+ - ๐ŸŽ›๏ธ **Gain Control**: Amplify or reduce signal strength
15
+ - ๐Ÿ“ก **Stream to stdout**: Raw PCM streaming for real-time processing
16
+ - ๐Ÿงต **Threaded Capture**: Dedicated thread for capture operations
17
+ - ๐Ÿ”Œ **Easy Integration**: EventEmitter-based interface with promise support
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install bionic-audio
23
+ ```
24
+
25
+ Or globally for CLI access:
26
+
27
+ ```bash
28
+ npm install -g bionic-audio
29
+ ```
30
+
31
+ ## Requirements
32
+
33
+ - Windows 10/11
34
+ - Node.js 14+ or Bun
35
+ - `recorder.exe` binary (included in package, or build from source)
36
+
37
+ ## Quick Start
38
+
39
+ ### Basic Recording
40
+
41
+ ```typescript
42
+ import { recordToFile } from 'bionic-audio';
43
+
44
+ // Simple file recording - 5 seconds of system audio
45
+ await recordToFile('./output.wav', { duration: 5 });
46
+ ```
47
+
48
+ ### EventEmitter Interface
49
+
50
+ ```typescript
51
+ import { AudioRecorder } from 'bionic-audio';
52
+
53
+ const recorder = new AudioRecorder({
54
+ output: './recording.wav',
55
+ duration: 10,
56
+ preserve32: true // 32-bit float format
57
+ });
58
+
59
+ recorder.on('started', () => console.log('๐ŸŽค Recording started'));
60
+ recorder.on('data', (bytes) => console.log(`๐Ÿ“ Wrote ${bytes} bytes`));
61
+ recorder.on('error', (err) => console.error('โŒ Error:', err));
62
+ recorder.on('complete', (result) => {
63
+ console.log('โœ… Recording complete:', result);
64
+ });
65
+
66
+ recorder.start();
67
+
68
+ // Stop manually (or wait for duration to finish)
69
+ setTimeout(() => recorder.stop(), 5000);
70
+ ```
71
+
72
+ ### Promise-Based Interface
73
+
74
+ ```typescript
75
+ import { AudioRecorder } from 'bionic-audio';
76
+
77
+ const recorder = new AudioRecorder();
78
+
79
+ try {
80
+ const result = await recorder.record({
81
+ duration: 5,
82
+ output: './output.wav'
83
+ });
84
+
85
+ console.log(`โœ… Recording finished with exit code ${result.code}`);
86
+ } catch (error) {
87
+ console.error('โŒ Recording failed:', error);
88
+ }
89
+ ```
90
+
91
+ ### Convenience Functions
92
+
93
+ ```typescript
94
+ import {
95
+ recordToFile,
96
+ recordMicrophone,
97
+ recordLoopback,
98
+ recordBoth
99
+ } from 'bionic-audio';
100
+
101
+ // Record system audio (loopback) to file
102
+ await recordToFile('./output.wav', { duration: 10 });
103
+
104
+ // Record microphone only
105
+ await recordMicrophone('./mic.wav', { duration: 10 });
106
+
107
+ // Record system audio only
108
+ await recordLoopback('./system.wav', { duration: 10 });
109
+
110
+ // Record both simultaneously (two separate files)
111
+ await recordBoth('./combined.wav', { duration: 10 });
112
+ ```
113
+
114
+ ### Streaming to stdout
115
+
116
+ ```typescript
117
+ import { AudioRecorder } from 'bionic-audio';
118
+ import { createWriteStream } from 'fs';
119
+
120
+ const recorder = new AudioRecorder();
121
+ const output = createWriteStream('./stream.wav');
122
+
123
+ recorder.pipe(output);
124
+ recorder.start({ duration: 5, pcmOnly: true });
125
+ ```
126
+
127
+ ## API Reference
128
+
129
+ ### `new AudioRecorder(options?)`
130
+
131
+ Creates a new audio recorder instance.
132
+
133
+ **Options:**
134
+ - `output?: string` - Output file path (required for file recording)
135
+ - `duration?: number` - Recording duration in seconds (0 = infinite, default: 0)
136
+ - `gain?: number` - Signal gain/amplitude multiplier (default: 1.0)
137
+ - `preserve24?: boolean` - Keep 24-bit native format (default: false)
138
+ - `preserve32?: boolean` - Keep 32-bit float format (default: false)
139
+ - `pcmOnly?: boolean` - Output raw PCM without WAV header (default: false)
140
+ - `loopback?: boolean` - Record system audio instead of microphone (default: false)
141
+ - `microphone?: boolean` - Record microphone (default: true)
142
+ - `both?: boolean` - Record both simultaneously (default: false)
143
+ - `threaded?: boolean` - Use dedicated capture thread (default: false)
144
+
145
+ ### Methods
146
+
147
+ #### `start(options?): void`
148
+ Starts recording with optional runtime options.
149
+
150
+ #### `stop(): boolean`
151
+ Stops the recording process. Returns true if stopped, false if not running.
152
+
153
+ #### `isRecording(): boolean`
154
+ Check if currently recording.
155
+
156
+ #### `record(options?): Promise<RecorderResult>`
157
+ Promise-based recording. Blocks until recording completes or errors.
158
+
159
+ ```typescript
160
+ const result = await recorder.record({ duration: 5 });
161
+ // result: { code: number, signal: string | null, stderr: string }
162
+ ```
163
+
164
+ #### `pipe(destination: NodeJS.WritableStream): void`
165
+ Pipe raw audio data to a writable stream (e.g., for real-time processing).
166
+
167
+ #### `getProcess(): ChildProcess | null`
168
+ Access the underlying child process directly.
169
+
170
+ ### Events
171
+
172
+ - `started` - Recording has started
173
+ - `data(bytes: number)` - Data written (number of bytes)
174
+ - `stderr(data: string)` - Stderr output from recorder
175
+ - `exit(code: number, signal: string | null)` - Process exited
176
+ - `complete(result: RecorderResult)` - Recording completed successfully
177
+ - `error(error: Error)` - Error occurred
178
+
179
+ ### Convenience Functions
180
+
181
+ #### `recordToFile(path: string, options?: RecorderOptions): Promise<RecorderResult>`
182
+ Quick file recording.
183
+
184
+ #### `recordMicrophone(path: string, options?: RecorderOptions): Promise<RecorderResult>`
185
+ Record microphone only.
186
+
187
+ #### `recordLoopback(path: string, options?: RecorderOptions): Promise<RecorderResult>`
188
+ Record system audio only.
189
+
190
+ #### `recordBoth(path: string, options?: RecorderOptions): Promise<RecorderResult>`
191
+ Record both microphone and loopback simultaneously.
192
+
193
+ ## Configuration
194
+
195
+ ### Audio Quality
196
+
197
+ - **16-bit PCM** (default): Balanced quality and file size, ~1.5 MB/min for stereo at 48kHz
198
+ - **24-bit PCM** (`preserve24: true`): Higher quality, ~2.3 MB/min
199
+ - **32-bit float** (`preserve32: true`): Highest quality, lossless internal format, ~3 MB/min
200
+
201
+ ### Gain Control
202
+
203
+ ```typescript
204
+ // Amplify by 2x
205
+ recorder.start({ gain: 2.0 });
206
+
207
+ // Reduce by half
208
+ recorder.start({ gain: 0.5 });
209
+ ```
210
+
211
+ Gain is applied before clipping to prevent distortion.
212
+
213
+ ## Electron Integration
214
+
215
+ ```typescript
216
+ import { AudioRecorder } from 'bionic-audio';
217
+ import { ipcMain } from 'electron';
218
+
219
+ let recorder: AudioRecorder | null = null;
220
+
221
+ ipcMain.handle('start-recording', async (event, options) => {
222
+ recorder = new AudioRecorder(options);
223
+
224
+ recorder.on('error', (err) => {
225
+ event.sender.send('recording-error', err.message);
226
+ });
227
+
228
+ recorder.start();
229
+ return { ok: true };
230
+ });
231
+
232
+ ipcMain.handle('stop-recording', async () => {
233
+ recorder?.stop();
234
+ return { ok: true };
235
+ });
236
+ ```
237
+
238
+ ## Building from Source
239
+
240
+ ```bash
241
+ # Install dependencies
242
+ npm install
243
+
244
+ # Compile TypeScript
245
+ npm run build
246
+
247
+ # Build C++ recorder binary (requires MinGW or MSVC)
248
+ gcc -o recorder.exe main.c -lole32 -std=c99
249
+ ```
250
+
251
+ ## Troubleshooting
252
+
253
+ ### "recorder.exe not found"
254
+ Ensure the binary is in PATH or in one of these locations:
255
+ - `./recorder.exe`
256
+ - `./bin/recorder.exe`
257
+ - `../recorder.exe`
258
+ - `./dist/recorder.exe`
259
+
260
+ ### Distorted/Noisy Audio
261
+ Try enabling gain control with values < 1.0:
262
+ ```typescript
263
+ recorder.start({ gain: 0.8 });
264
+ ```
265
+
266
+ ### File Won't Play
267
+ Ensure you're using a compatible audio player. 32-bit float WAV support varies. Try 16-bit output:
268
+ ```typescript
269
+ recorder.start({ preserve32: false });
270
+ ```
271
+
272
+ ## License
273
+
274
+ MIT
@@ -0,0 +1,96 @@
1
+ import { ChildProcess } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+ export interface RecorderOptions {
4
+ /** Duration in seconds (0 = infinite) */
5
+ duration?: number;
6
+ /** Output file path or '-' for stdout */
7
+ output?: string;
8
+ /** PCM only (no WAV header) */
9
+ pcmOnly?: boolean;
10
+ /** Record loopback (system audio) */
11
+ loopback?: boolean;
12
+ /** Record microphone */
13
+ mic?: boolean;
14
+ /** Record both loopback and microphone simultaneously */
15
+ both?: boolean;
16
+ /** Gain multiplier (default 1.0) */
17
+ gain?: number;
18
+ /** Preserve 24-bit if available */
19
+ preserve24?: boolean;
20
+ /** Preserve 32-bit float (highest quality) */
21
+ preserve32?: boolean;
22
+ /** Run capture in dedicated thread */
23
+ threaded?: boolean;
24
+ /** Path to recorder.exe (default: auto-detect) */
25
+ recorderPath?: string;
26
+ }
27
+ export interface RecorderResult {
28
+ code: number;
29
+ signal: string | null;
30
+ stderr: string;
31
+ }
32
+ /**
33
+ * AudioRecorder - wrapper for Windows WASAPI recorder
34
+ *
35
+ * Usage:
36
+ * const rec = new AudioRecorder({ output: 'session.wav', duration: 10, gain: 1.5 });
37
+ * rec.start();
38
+ * rec.on('complete', (result) => console.log('Done!'));
39
+ * rec.on('error', (err) => console.error(err));
40
+ *
41
+ * // or stop manually:
42
+ * setTimeout(() => rec.stop(), 5000);
43
+ */
44
+ export declare class AudioRecorder extends EventEmitter {
45
+ private process;
46
+ private options;
47
+ private recorderExePath;
48
+ private stderrBuffer;
49
+ constructor(options?: RecorderOptions);
50
+ private findRecorderExe;
51
+ /**
52
+ * Build command-line arguments based on options
53
+ */
54
+ private buildArgs;
55
+ /**
56
+ * Start recording
57
+ */
58
+ start(): void;
59
+ /**
60
+ * Stop recording
61
+ */
62
+ stop(): boolean;
63
+ /**
64
+ * Check if recording is active
65
+ */
66
+ isRecording(): boolean;
67
+ /**
68
+ * Get the underlying child process (for advanced usage)
69
+ */
70
+ getProcess(): ChildProcess | null;
71
+ /**
72
+ * Promise-based interface
73
+ */
74
+ record(): Promise<RecorderResult>;
75
+ /**
76
+ * Stream data to a file or another stream
77
+ */
78
+ pipe(destination: NodeJS.WritableStream): void;
79
+ }
80
+ /**
81
+ * Convenience function: record to file
82
+ */
83
+ export declare function recordToFile(output: string, durationSeconds?: number, options?: Partial<RecorderOptions>): Promise<RecorderResult>;
84
+ /**
85
+ * Convenience function: record microphone
86
+ */
87
+ export declare function recordMicrophone(output: string, durationSeconds?: number, gain?: number): Promise<RecorderResult>;
88
+ /**
89
+ * Convenience function: record system audio
90
+ */
91
+ export declare function recordLoopback(output: string, durationSeconds?: number, gain?: number): Promise<RecorderResult>;
92
+ /**
93
+ * Convenience function: record both
94
+ */
95
+ export declare function recordBoth(output: string, durationSeconds?: number, gain?: number): Promise<RecorderResult>;
96
+ //# sourceMappingURL=AudioRecorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioRecorder.d.ts","sourceRoot":"","sources":["../lib/AudioRecorder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,YAAY,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,YAAY,CAAc;gBAEtB,OAAO,GAAE,eAAoB;IAoBzC,OAAO,CAAC,eAAe;IAuBvB;;OAEG;IACH,OAAO,CAAC,SAAS;IA0CjB;;OAEG;IACI,KAAK,IAAI,IAAI;IAkDpB;;OAEG;IACI,IAAI,IAAI,OAAO;IAQtB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,UAAU,IAAI,YAAY,GAAG,IAAI;IAIxC;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAQ9C;;OAEG;IACI,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI;CAStD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,GAAE,MAAW,EAC5B,OAAO,GAAE,OAAO,CAAC,eAAe,CAAM,GACrC,OAAO,CAAC,cAAc,CAAC,CAOzB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,eAAe,GAAE,MAAW,EAC5B,IAAI,GAAE,MAAY,GACjB,OAAO,CAAC,cAAc,CAAC,CAEzB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,eAAe,GAAE,MAAW,EAC5B,IAAI,GAAE,MAAY,GACjB,OAAO,CAAC,cAAc,CAAC,CAEzB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,eAAe,GAAE,MAAW,EAC5B,IAAI,GAAE,MAAY,GACjB,OAAO,CAAC,cAAc,CAAC,CAEzB"}
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AudioRecorder = void 0;
37
+ exports.recordToFile = recordToFile;
38
+ exports.recordMicrophone = recordMicrophone;
39
+ exports.recordLoopback = recordLoopback;
40
+ exports.recordBoth = recordBoth;
41
+ const child_process_1 = require("child_process");
42
+ const events_1 = require("events");
43
+ const path = __importStar(require("path"));
44
+ /**
45
+ * AudioRecorder - wrapper for Windows WASAPI recorder
46
+ *
47
+ * Usage:
48
+ * const rec = new AudioRecorder({ output: 'session.wav', duration: 10, gain: 1.5 });
49
+ * rec.start();
50
+ * rec.on('complete', (result) => console.log('Done!'));
51
+ * rec.on('error', (err) => console.error(err));
52
+ *
53
+ * // or stop manually:
54
+ * setTimeout(() => rec.stop(), 5000);
55
+ */
56
+ class AudioRecorder extends events_1.EventEmitter {
57
+ constructor(options = {}) {
58
+ super();
59
+ this.process = null;
60
+ this.stderrBuffer = '';
61
+ this.options = {
62
+ duration: options.duration ?? 10,
63
+ output: options.output ?? 'recording.wav',
64
+ pcmOnly: options.pcmOnly ?? false,
65
+ loopback: !options.mic && !options.both,
66
+ mic: options.mic ?? false,
67
+ both: options.both ?? false,
68
+ gain: options.gain ?? 1.0,
69
+ preserve24: options.preserve24 ?? false,
70
+ preserve32: options.preserve32 ?? false,
71
+ threaded: options.threaded ?? false,
72
+ ...options,
73
+ };
74
+ // Try to find recorder.exe
75
+ this.recorderExePath = options.recorderPath || this.findRecorderExe();
76
+ }
77
+ findRecorderExe() {
78
+ // Common locations
79
+ const candidates = [
80
+ path.join(__dirname, '..', 'bin', 'recorder.exe'),
81
+ path.join(__dirname, 'recorder.exe'),
82
+ path.join(process.cwd(), 'recorder.exe'),
83
+ 'd:\\recorder\\recorder.exe',
84
+ 'recorder.exe', // rely on PATH
85
+ ];
86
+ for (const candidate of candidates) {
87
+ try {
88
+ require('fs').accessSync(candidate);
89
+ return candidate;
90
+ }
91
+ catch {
92
+ //
93
+ }
94
+ }
95
+ // Default fallback
96
+ return 'recorder.exe';
97
+ }
98
+ /**
99
+ * Build command-line arguments based on options
100
+ */
101
+ buildArgs() {
102
+ const args = [];
103
+ if (this.options.duration !== undefined && this.options.duration >= 0) {
104
+ args.push('-d', String(this.options.duration));
105
+ }
106
+ if (this.options.output) {
107
+ args.push('-o', this.options.output);
108
+ }
109
+ if (this.options.pcmOnly) {
110
+ args.push('-p');
111
+ }
112
+ if (this.options.both) {
113
+ args.push('-b');
114
+ }
115
+ else if (this.options.mic) {
116
+ args.push('-m');
117
+ }
118
+ else if (this.options.loopback) {
119
+ args.push('-l');
120
+ }
121
+ if (this.options.threaded) {
122
+ args.push('-t');
123
+ }
124
+ if (this.options.gain && this.options.gain !== 1.0) {
125
+ args.push('-g', String(this.options.gain));
126
+ }
127
+ if (this.options.preserve24) {
128
+ args.push('-24');
129
+ }
130
+ if (this.options.preserve32) {
131
+ args.push('-32');
132
+ }
133
+ return args;
134
+ }
135
+ /**
136
+ * Start recording
137
+ */
138
+ start() {
139
+ if (this.process) {
140
+ throw new Error('Recording already in progress');
141
+ }
142
+ const args = this.buildArgs();
143
+ this.stderrBuffer = '';
144
+ this.process = (0, child_process_1.spawn)(this.recorderExePath, args, {
145
+ stdio: ['ignore', 'pipe', 'pipe'],
146
+ windowsHide: true,
147
+ });
148
+ // Capture stderr for diagnostics
149
+ this.process.stderr.on('data', (data) => {
150
+ const chunk = data.toString();
151
+ this.stderrBuffer += chunk;
152
+ this.emit('stderr', chunk);
153
+ });
154
+ // If output is stdout, emit data
155
+ if (this.options.output === '-') {
156
+ this.process.stdout.on('data', (data) => {
157
+ this.emit('data', data);
158
+ });
159
+ }
160
+ this.process.on('error', (err) => {
161
+ this.process = null;
162
+ this.emit('error', err);
163
+ });
164
+ this.process.on('exit', (code, signal) => {
165
+ this.process = null;
166
+ const result = {
167
+ code: code ?? -1,
168
+ signal,
169
+ stderr: this.stderrBuffer,
170
+ };
171
+ this.emit('exit', result);
172
+ if (code === 0) {
173
+ this.emit('complete', result);
174
+ }
175
+ else {
176
+ this.emit('error', new Error(`recorder exited with code ${code}`));
177
+ }
178
+ });
179
+ this.emit('started');
180
+ }
181
+ /**
182
+ * Stop recording
183
+ */
184
+ stop() {
185
+ if (!this.process) {
186
+ return false;
187
+ }
188
+ this.process.kill('SIGTERM');
189
+ return true;
190
+ }
191
+ /**
192
+ * Check if recording is active
193
+ */
194
+ isRecording() {
195
+ return this.process !== null && !this.process.killed;
196
+ }
197
+ /**
198
+ * Get the underlying child process (for advanced usage)
199
+ */
200
+ getProcess() {
201
+ return this.process;
202
+ }
203
+ /**
204
+ * Promise-based interface
205
+ */
206
+ async record() {
207
+ return new Promise((resolve, reject) => {
208
+ this.once('complete', resolve);
209
+ this.once('error', reject);
210
+ this.start();
211
+ });
212
+ }
213
+ /**
214
+ * Stream data to a file or another stream
215
+ */
216
+ pipe(destination) {
217
+ if (!this.isRecording() || this.options.output !== '-') {
218
+ throw new Error('Must start recording with output="-" to pipe data');
219
+ }
220
+ const proc = this.getProcess();
221
+ if (proc?.stdout) {
222
+ proc.stdout.pipe(destination);
223
+ }
224
+ }
225
+ }
226
+ exports.AudioRecorder = AudioRecorder;
227
+ /**
228
+ * Convenience function: record to file
229
+ */
230
+ async function recordToFile(output, durationSeconds = 10, options = {}) {
231
+ const recorder = new AudioRecorder({
232
+ output,
233
+ duration: durationSeconds,
234
+ ...options,
235
+ });
236
+ return recorder.record();
237
+ }
238
+ /**
239
+ * Convenience function: record microphone
240
+ */
241
+ async function recordMicrophone(output, durationSeconds = 10, gain = 1.0) {
242
+ return recordToFile(output, durationSeconds, { mic: true, gain });
243
+ }
244
+ /**
245
+ * Convenience function: record system audio
246
+ */
247
+ async function recordLoopback(output, durationSeconds = 10, gain = 1.0) {
248
+ return recordToFile(output, durationSeconds, { loopback: true, gain });
249
+ }
250
+ /**
251
+ * Convenience function: record both
252
+ */
253
+ async function recordBoth(output, durationSeconds = 10, gain = 1.0) {
254
+ return recordToFile(output, durationSeconds, { both: true, gain });
255
+ }
256
+ //# sourceMappingURL=AudioRecorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioRecorder.js","sourceRoot":"","sources":["../lib/AudioRecorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,292 @@
1
+ import { spawn, ChildProcess } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+ import * as path from 'path';
4
+
5
+ export interface RecorderOptions {
6
+ /** Duration in seconds (0 = infinite) */
7
+ duration?: number;
8
+ /** Output file path or '-' for stdout */
9
+ output?: string;
10
+ /** PCM only (no WAV header) */
11
+ pcmOnly?: boolean;
12
+ /** Record loopback (system audio) */
13
+ loopback?: boolean;
14
+ /** Record microphone */
15
+ mic?: boolean;
16
+ /** Record both loopback and microphone simultaneously */
17
+ both?: boolean;
18
+ /** Gain multiplier (default 1.0) */
19
+ gain?: number;
20
+ /** Preserve 24-bit if available */
21
+ preserve24?: boolean;
22
+ /** Preserve 32-bit float (highest quality) */
23
+ preserve32?: boolean;
24
+ /** Run capture in dedicated thread */
25
+ threaded?: boolean;
26
+ /** Path to recorder.exe (default: auto-detect) */
27
+ recorderPath?: string;
28
+ }
29
+
30
+ export interface RecorderResult {
31
+ code: number;
32
+ signal: string | null;
33
+ stderr: string;
34
+ }
35
+
36
+ /**
37
+ * AudioRecorder - wrapper for Windows WASAPI recorder
38
+ *
39
+ * Usage:
40
+ * const rec = new AudioRecorder({ output: 'session.wav', duration: 10, gain: 1.5 });
41
+ * rec.start();
42
+ * rec.on('complete', (result) => console.log('Done!'));
43
+ * rec.on('error', (err) => console.error(err));
44
+ *
45
+ * // or stop manually:
46
+ * setTimeout(() => rec.stop(), 5000);
47
+ */
48
+ export class AudioRecorder extends EventEmitter {
49
+ private process: ChildProcess | null = null;
50
+ private options: RecorderOptions;
51
+ private recorderExePath: string;
52
+ private stderrBuffer: string = '';
53
+
54
+ constructor(options: RecorderOptions = {}) {
55
+ super();
56
+ this.options = {
57
+ duration: options.duration ?? 10,
58
+ output: options.output ?? 'recording.wav',
59
+ pcmOnly: options.pcmOnly ?? false,
60
+ loopback: !options.mic && !options.both,
61
+ mic: options.mic ?? false,
62
+ both: options.both ?? false,
63
+ gain: options.gain ?? 1.0,
64
+ preserve24: options.preserve24 ?? false,
65
+ preserve32: options.preserve32 ?? false,
66
+ threaded: options.threaded ?? false,
67
+ ...options,
68
+ };
69
+
70
+ // Try to find recorder.exe
71
+ this.recorderExePath = options.recorderPath || this.findRecorderExe();
72
+ }
73
+
74
+ private findRecorderExe(): string {
75
+ // Common locations
76
+ const candidates = [
77
+ path.join(__dirname, '..', 'bin', 'recorder.exe'),
78
+ path.join(__dirname, 'recorder.exe'),
79
+ path.join(process.cwd(), 'recorder.exe'),
80
+ 'd:\\recorder\\recorder.exe',
81
+ 'recorder.exe', // rely on PATH
82
+ ];
83
+
84
+ for (const candidate of candidates) {
85
+ try {
86
+ require('fs').accessSync(candidate);
87
+ return candidate;
88
+ } catch {
89
+ //
90
+ }
91
+ }
92
+
93
+ // Default fallback
94
+ return 'recorder.exe';
95
+ }
96
+
97
+ /**
98
+ * Build command-line arguments based on options
99
+ */
100
+ private buildArgs(): string[] {
101
+ const args: string[] = [];
102
+
103
+ if (this.options.duration !== undefined && this.options.duration >= 0) {
104
+ args.push('-d', String(this.options.duration));
105
+ }
106
+
107
+ if (this.options.output) {
108
+ args.push('-o', this.options.output);
109
+ }
110
+
111
+ if (this.options.pcmOnly) {
112
+ args.push('-p');
113
+ }
114
+
115
+ if (this.options.both) {
116
+ args.push('-b');
117
+ } else if (this.options.mic) {
118
+ args.push('-m');
119
+ } else if (this.options.loopback) {
120
+ args.push('-l');
121
+ }
122
+
123
+ if (this.options.threaded) {
124
+ args.push('-t');
125
+ }
126
+
127
+ if (this.options.gain && this.options.gain !== 1.0) {
128
+ args.push('-g', String(this.options.gain));
129
+ }
130
+
131
+ if (this.options.preserve24) {
132
+ args.push('-24');
133
+ }
134
+
135
+ if (this.options.preserve32) {
136
+ args.push('-32');
137
+ }
138
+
139
+ return args;
140
+ }
141
+
142
+ /**
143
+ * Start recording
144
+ */
145
+ public start(): void {
146
+ if (this.process) {
147
+ throw new Error('Recording already in progress');
148
+ }
149
+
150
+ const args = this.buildArgs();
151
+ this.stderrBuffer = '';
152
+
153
+ this.process = spawn(this.recorderExePath, args, {
154
+ stdio: ['ignore', 'pipe', 'pipe'],
155
+ windowsHide: true,
156
+ });
157
+
158
+ // Capture stderr for diagnostics
159
+ this.process.stderr!.on('data', (data) => {
160
+ const chunk = data.toString();
161
+ this.stderrBuffer += chunk;
162
+ this.emit('stderr', chunk);
163
+ });
164
+
165
+ // If output is stdout, emit data
166
+ if (this.options.output === '-') {
167
+ this.process.stdout!.on('data', (data) => {
168
+ this.emit('data', data);
169
+ });
170
+ }
171
+
172
+ this.process.on('error', (err) => {
173
+ this.process = null;
174
+ this.emit('error', err);
175
+ });
176
+
177
+ this.process.on('exit', (code, signal) => {
178
+ this.process = null;
179
+ const result: RecorderResult = {
180
+ code: code ?? -1,
181
+ signal,
182
+ stderr: this.stderrBuffer,
183
+ };
184
+ this.emit('exit', result);
185
+ if (code === 0) {
186
+ this.emit('complete', result);
187
+ } else {
188
+ this.emit('error', new Error(`recorder exited with code ${code}`));
189
+ }
190
+ });
191
+
192
+ this.emit('started');
193
+ }
194
+
195
+ /**
196
+ * Stop recording
197
+ */
198
+ public stop(): boolean {
199
+ if (!this.process) {
200
+ return false;
201
+ }
202
+ this.process.kill('SIGTERM');
203
+ return true;
204
+ }
205
+
206
+ /**
207
+ * Check if recording is active
208
+ */
209
+ public isRecording(): boolean {
210
+ return this.process !== null && !this.process.killed;
211
+ }
212
+
213
+ /**
214
+ * Get the underlying child process (for advanced usage)
215
+ */
216
+ public getProcess(): ChildProcess | null {
217
+ return this.process;
218
+ }
219
+
220
+ /**
221
+ * Promise-based interface
222
+ */
223
+ public async record(): Promise<RecorderResult> {
224
+ return new Promise((resolve, reject) => {
225
+ this.once('complete', resolve);
226
+ this.once('error', reject);
227
+ this.start();
228
+ });
229
+ }
230
+
231
+ /**
232
+ * Stream data to a file or another stream
233
+ */
234
+ public pipe(destination: NodeJS.WritableStream): void {
235
+ if (!this.isRecording() || this.options.output !== '-') {
236
+ throw new Error('Must start recording with output="-" to pipe data');
237
+ }
238
+ const proc = this.getProcess();
239
+ if (proc?.stdout) {
240
+ proc.stdout.pipe(destination);
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Convenience function: record to file
247
+ */
248
+ export async function recordToFile(
249
+ output: string,
250
+ durationSeconds: number = 10,
251
+ options: Partial<RecorderOptions> = {}
252
+ ): Promise<RecorderResult> {
253
+ const recorder = new AudioRecorder({
254
+ output,
255
+ duration: durationSeconds,
256
+ ...options,
257
+ });
258
+ return recorder.record();
259
+ }
260
+
261
+ /**
262
+ * Convenience function: record microphone
263
+ */
264
+ export async function recordMicrophone(
265
+ output: string,
266
+ durationSeconds: number = 10,
267
+ gain: number = 1.0
268
+ ): Promise<RecorderResult> {
269
+ return recordToFile(output, durationSeconds, { mic: true, gain });
270
+ }
271
+
272
+ /**
273
+ * Convenience function: record system audio
274
+ */
275
+ export async function recordLoopback(
276
+ output: string,
277
+ durationSeconds: number = 10,
278
+ gain: number = 1.0
279
+ ): Promise<RecorderResult> {
280
+ return recordToFile(output, durationSeconds, { loopback: true, gain });
281
+ }
282
+
283
+ /**
284
+ * Convenience function: record both
285
+ */
286
+ export async function recordBoth(
287
+ output: string,
288
+ durationSeconds: number = 10,
289
+ gain: number = 1.0
290
+ ): Promise<RecorderResult> {
291
+ return recordToFile(output, durationSeconds, { both: true, gain });
292
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "bionic-audio",
3
+ "version": "1.0.0",
4
+ "description": "Professional Windows WASAPI audio recorder library for Node.js and Bun - record microphone and system audio with advanced features",
5
+ "main": "dist/AudioRecorder.js",
6
+ "types": "dist/AudioRecorder.d.ts",
7
+ "bin": {
8
+ "bionic-audio": "./recorder.exe"
9
+ },
10
+ "files": [
11
+ "dist/",
12
+ "recorder.exe",
13
+ "lib/",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepare": "npm run build",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "audio",
24
+ "recording",
25
+ "WASAPI",
26
+ "windows",
27
+ "loopback",
28
+ "microphone",
29
+ "audio-recording",
30
+ "system-audio",
31
+ "capture",
32
+ "wav",
33
+ "stereo",
34
+ "nodejs",
35
+ "bun",
36
+ "bionic"
37
+ ],
38
+ "author": "",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": ""
43
+ },
44
+ "bugs": {
45
+ "url": ""
46
+ },
47
+ "homepage": "",
48
+ "engines": {
49
+ "node": ">=14.0.0",
50
+ "npm": ">=6.0.0"
51
+ },
52
+ "os": [
53
+ "win32"
54
+ ],
55
+ "cpu": [
56
+ "x64",
57
+ "ia32"
58
+ ],
59
+ "devDependencies": {
60
+ "@types/node": "^20.0.0",
61
+ "typescript": "^5.0.0"
62
+ }
63
+ }
package/recorder.exe ADDED
Binary file