Comparing version 0.0.2 to 0.0.3
@@ -0,1 +1,12 @@ | ||
0.0.3 / 2012-11-03 | ||
================== | ||
- a two examples to the "examples" dir | ||
- emit an "open" event | ||
- emit a "close" event | ||
- emit a "flush" event | ||
- properly support the "pipe" event | ||
- mpg123: fix a CoreAudio backend compilation warning | ||
- add a timeout after the flush call to ensure the backend has time to play | ||
0.0.2 / 2012-10-25 | ||
@@ -2,0 +13,0 @@ ================== |
153
index.js
@@ -40,14 +40,5 @@ | ||
// set default options | ||
if (!opts) opts = {}; | ||
if (null == opts.channels) opts.channels = 2; | ||
if (null == opts.bitDepth) opts.bitDepth = 16; | ||
if (null == opts.sampleRate) opts.sampleRate = 44100; | ||
if (null == opts.signed) opts.signed = opts.bitDepth != 8; | ||
// set options if provided | ||
if (opts) this._opts(opts); | ||
// initialize the audio handle | ||
// TODO: open async? | ||
this.audio_handle = new Buffer(binding.sizeof_audio_output_t); | ||
binding.open(this.audio_handle, opts); | ||
// chunks are sent over to the backend in "samplesPerFrame * blockAlign" size. | ||
@@ -57,16 +48,10 @@ // this is necessary because if we send too big of chunks at once, then there | ||
// CoreAudio backend) | ||
if (null == opts.samplesPerFrame) opts.samplesPerFrame = 1024; | ||
this.samplesPerFrame = 1024; | ||
// copy over options | ||
this.signed = opts.signed; | ||
this.channels = opts.channels; | ||
this.bitDepth = opts.bitDepth; | ||
this.sampleRate = opts.sampleRate; | ||
this.samplesPerFrame = opts.samplesPerFrame; | ||
// flipped after close() is called, no write() calls allowed after | ||
this._closed = false; | ||
// calculate the "block align" | ||
this.blockAlign = this.bitDepth / 8 * this.channels; | ||
// call `flush()` upon the "finish" event | ||
this.on('finish', this._flush); | ||
this.on('pipe', this._pipe); | ||
} | ||
@@ -76,2 +61,72 @@ inherits(Speaker, Writable); | ||
/** | ||
* Calls the audio backend's `open()` function, and then emits an "open" event. | ||
*/ | ||
Speaker.prototype._open = function () { | ||
debug('open()'); | ||
if (this.audio_handle) { | ||
throw new Error('_open() called more than once!'); | ||
} | ||
// set default options, if not set | ||
if (null == this.channels) { | ||
debug('setting default "channels"', 2); | ||
this.channels = 2; | ||
} | ||
if (null == this.bitDepth) { | ||
debug('setting default "bitDepth"', 16); | ||
this.bitDepth = 16; | ||
} | ||
if (null == this.sampleRate) { | ||
debug('setting default "sampleRate"', 44100); | ||
this.sampleRate = 44100; | ||
} | ||
if (null == this.signed) { | ||
debug('setting default "signed"', this.bitDepth != 8); | ||
this.signed = this.bitDepth != 8; | ||
} | ||
// calculate the "block align" | ||
this.blockAlign = this.bitDepth / 8 * this.channels; | ||
// initialize the audio handle | ||
// TODO: open async? | ||
this.audio_handle = new Buffer(binding.sizeof_audio_output_t); | ||
var r = binding.open(this.audio_handle, this); | ||
if (0 !== r) { | ||
throw new Error('open() failed: ' + r); | ||
} | ||
this.emit('open'); | ||
return this.audio_handle; | ||
}; | ||
/** | ||
* set given options | ||
*/ | ||
Speaker.prototype._opts = function (opts) { | ||
debug('opts(%j)', opts); | ||
if (null != opts.channels) { | ||
debug('setting "channels"', opts.channels); | ||
this.channels = opts.channels; | ||
} | ||
if (null != opts.bitDepth) { | ||
debug('setting "bitDepth"', opts.bitDepth); | ||
this.bitDepth = opts.bitDepth; | ||
} | ||
if (null != opts.sampleRate) { | ||
debug('setting "sampleRate"', opts.sampleRate); | ||
this.sampleRate = opts.sampleRate; | ||
} | ||
if (null != opts.signed) { | ||
debug('setting "signed"', opts.signed); | ||
this.signed = opts.signed; | ||
} | ||
if (null != opts.samplesPerFrame) { | ||
debug('setting "samplesPerFrame"', opts.samplesPerFrame); | ||
this.samplesPerFrame = opts.samplesPerFrame; | ||
} | ||
}; | ||
/** | ||
* `_write()` callback for the Writable base class. | ||
@@ -82,5 +137,13 @@ */ | ||
debug('_write() (%d 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 b; | ||
var left = chunk; | ||
var handle = this.audio_handle; | ||
if (!handle) { | ||
// this is the first time write() is being called; need to _open() | ||
handle = this._open(); | ||
} | ||
var chunkSize = this.blockAlign * this.samplesPerFrame; | ||
@@ -115,11 +178,49 @@ | ||
/** | ||
* Calls the `flush()` and `close()` bindings for the audio backend. | ||
* Called when this stream is pipe()d to from another readable stream. | ||
* If the "sampleRate", "channels", "bitDepth", and "signed" properties are, | ||
* set, then they will be used over the currently set values. | ||
*/ | ||
Speaker.prototype._pipe = function (source) { | ||
debug('_pipe()'); | ||
this._opts(source); | ||
}; | ||
/** | ||
* Calls the `flush()` bindings for the audio backend. | ||
*/ | ||
Speaker.prototype._flush = function () { | ||
debug('_flush()'); | ||
var handle = this.audio_handle; | ||
// TODO: async | ||
binding.flush(handle); | ||
binding.close(handle); | ||
// TODO: async definitely | ||
binding.flush(this.audio_handle); | ||
this.emit('flush'); | ||
// XXX: The audio backends keep ~.5 seconds worth of buffered audio data | ||
// in their system, so presumably there will be .5 seconds *more* of audio data | ||
// coming out the speakers, so we must keep the event loop alive so the process | ||
// doesn't exit. This is a nasty, nasty hack and hopefully there's a better way | ||
// to be notified when the audio has acutally finished playing. | ||
setTimeout(this.close.bind(this), 600); | ||
}; | ||
/** | ||
* Closes the audio backend. Normally this function will be called automatically | ||
* after the audio backend has finished playing the audio buffer through the | ||
* speakers. | ||
* | ||
* @api public | ||
*/ | ||
Speaker.prototype.close = function () { | ||
debug('close()'); | ||
// TODO: async maybe? | ||
binding.close(this.audio_handle); | ||
this.emit('close'); | ||
this.audio_handle = null; | ||
this._closed = true; | ||
}; |
@@ -20,3 +20,3 @@ { | ||
], | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://tootallnate.net)", | ||
@@ -23,0 +23,0 @@ "repository": { |
@@ -44,7 +44,33 @@ node-speaker | ||
### Speaker class | ||
`require('speaker')` directly returns the `Speaker` constructor. It is the only | ||
interface exported by node-speaker. | ||
TODO: document... | ||
### new Speaker([ format ]) -> Speaker instance; | ||
Creates a new `Speaker` instance, which is a writable stream that you can pipe raw | ||
PCM audio data to. The optional `format` object may contain any of the `Writable` | ||
base class options, as well as any of these PCM formatting options: | ||
* `channels` - The number of audio channels. PCM data must be interleaved. Defaults to `2`. | ||
* `bitDepth` - The number of bits per sample. Defaults to `16` (16-bit). | ||
* `sampleRate` - The number of samples per second per channel. Defaults to `44100`. | ||
* `signed` - Boolean specifying if the samples are signed or unsigned. Defaults to `true` when bit depth is 8-bit, `false` otherwise. | ||
* `samplesPerFrame` The number of samples to send to the audio backend at a time. You likely don't need to mess with this value. Defaults to `1024`. | ||
#### "open" event | ||
Fired when the backend `open()` call has completed. This happens once the first | ||
`write()` call happens on the speaker instance. | ||
#### "flush" event | ||
Fired after the speaker instance has had `end()` called, and after the audio data | ||
has been flushed to the speakers. | ||
#### "close" event | ||
Fired after the "flush" event, after the backend `close()` call has completed. | ||
This speaker instance is essentially finished after this point. | ||
Audio Backend Selection | ||
@@ -51,0 +77,0 @@ ----------------------- |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
3326447
235
300
95