-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.js
192 lines (163 loc) · 4.63 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* Module dependencies.
*/
var os = require('os');
process.dlopen(module, 'build/Release/binding.node', os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL)
var debug = require('debug')('libao');
var inherits = require('util').inherits;
var Writable = require('readable-stream/writable');
binding = module.exports
/**
* Module exports.
*/
exports = module.exports = libao;
/**
* The `libao` class accepts raw PCM data written to it, and then sends that data
* to the default output device of the OS.
*
* @param {Object} opts options object
* @api public
*/
function libao (opts) {
if (!(this instanceof libao)) return new libao(opts);
// default lwm and hwm to 0
if (!opts) opts = {};
if (!opts.lowWaterMark) opts.lowWaterMark = 0;
if (!opts.highWaterMark) opts.highWaterMark = 0;
Writable.call(this, opts);
// the `ao_device` pointer Buffer instance
this._audio_handle = null;
// flipped after close() is called, no write() calls allowed after
this._closed = false;
// set PCM format
this._format(opts);
// bind event listeners
this._format = this._format.bind(this);
this.on('pipe', this._pipe);
this.on('unpipe', this._unpipe);
}
inherits(libao, Writable);
/**
* Calls the audio backend's `open()` function, and then emits an "open" event.
*
* @api private
*/
libao.prototype._open = function () {
debug('open()');
if (this._audio_handle) {
throw new Error('_open() called more than once!');
}
// set default options, if not set
if (!this.channels) {
debug('setting default %o: %o', 'channels', 2);
this.channels = 2;
}
if (!this.bitDepth) {
debug('setting default %o: %o', 'bitDepth', 32);
this.bitDepth = 32;
}
if (!this.sampleRate) {
debug('setting default %o: %o', 'sampleRate', 44100);
this.sampleRate = 44100;
}
// initialize the audio handle
this._audio_handle = binding.open(this.channels, this.sampleRate, this.bitDepth);
if (!this._audio_handle) {
throw new Error('open() failed');
}
this.emit('open');
return this._audio_handle;
};
/**
* Set given PCM formatting options. Called during instantiation on the passed in
* options object, on the stream given to the "pipe" event, and a final time if
* that stream emits a "format" event.
*
* @param {Object} opts
* @api private
*/
libao.prototype._format = function (opts) {
debug('format(object keys = %o)', Object.keys(opts));
if (opts.channels) {
debug('setting %o: %o', 'channels', opts.channels);
this.channels = opts.channels;
}
if (opts.bitDepth) {
debug('setting %o: %o', "bitDepth", opts.bitDepth);
this.bitDepth = opts.bitDepth;
}
if (opts.sampleRate) {
debug('setting %o: %o', "sampleRate", opts.sampleRate);
this.sampleRate = opts.sampleRate;
}
};
/**
* `_write()` callback for the Writable base class.
*
* @param {Buffer} chunk
* @param {String} encoding
* @param {Function} done
* @api private
*/
libao.prototype._write = function (chunk, encoding, done) {
debug('_write() (%o bytes)', chunk.length);
if (this._closed) {
// close() has already been called. this should not be called
return done(new Error('write() call after close() call'));
}
var handle = this._audio_handle;
if (!handle) {
// this is the first time write() is being called; need to _open()
try {
handle = libao.prototype._open.call(this);
} catch (e) {
return done(e);
}
}
if (this._closed) {
debug('aborting remainder of write() call (%o bytes), since module is `_closed`', left.length);
return done();
}
binding.write(handle, chunk, chunk.length, function onwrite(r) {
if (!r) {
done(new Error('write() failed: ' + r));
} else {
debug('write successful');
done();
}
});
};
/**
* Called when this stream is pipe()d to from another readable stream.
* If the "sampleRate", "channels" and "bitDepth" properties are
* set, then they will be used over the currently set values.
*
* @api private
*/
libao.prototype._pipe = function (source) {
debug('_pipe()');
this._format(source);
source.once('format', this._format);
};
libao.prototype._unpipe = function (source) {
debug('_unpipe()');
source.removeListener('format', this._format);
};
/**
* Closes the audio backend.
*
* @api public
*/
libao.prototype.close = function () {
debug('close()');
if (this._closed) return debug('already closed...');
if (this._audio_handle) {
debug('invoking close() native binding');
binding.close(this._audio_handle);
this._audio_handle = null;
} else {
debug('not invoking close() bindings since no `_audio_handle`');
}
this._closed = true;
this.emit('close');
};