Repository: andrewrk/node-groove
Branch: master
Commit: 905cc5c0b2c2
Files: 36
Total size: 128.0 KB
Directory structure:
gitextract_vdtlnlz5/
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── binding.gyp
├── example/
│ ├── devices.js
│ ├── fingerprint.js
│ ├── metadata.js
│ ├── playlist.js
│ ├── replaygain.js
│ ├── transcode.js
│ └── waveform.js
├── lib/
│ └── index.js
├── package.json
├── src/
│ ├── device.cc
│ ├── device.h
│ ├── encoder.cc
│ ├── encoder.h
│ ├── file.cc
│ ├── file.h
│ ├── fingerprinter.cc
│ ├── fingerprinter.h
│ ├── groove.cc
│ ├── groove.h
│ ├── loudness_detector.cc
│ ├── loudness_detector.h
│ ├── player.cc
│ ├── player.h
│ ├── playlist.cc
│ ├── playlist.h
│ ├── playlist_item.cc
│ ├── playlist_item.h
│ ├── waveform_builder.cc
│ └── waveform_builder.h
└── test/
├── danse.ogg
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/build
/node_modules
================================================
FILE: CHANGELOG.md
================================================
# 3.0.0 (UNRELEASED)
* `player.setItemGain` and `player.setItemPeak` are gone in favor of
`player.setItemGainPeak`
* `groove.getDevices()` returns an object instead of an array.
* `ANY_SINK_FULL` is now default instead of `EVERY_SINK_FULL`
* `player.targetAudioFormat`, `player.actualAudioFormat`,
`encoder.targetAudioFormat`, `encoder.actualAudioFormat`:
- `channelLayout` - instead of a number it is an array of channel ids
* `sampleFormat` - sample format enum values are different
* `player.deviceBufferSize` - removed. This functionality no longer exists.
* `player.sinkBufferSize` - removed. This functionality no longer exists.
* `detector.sinkBufferSize` - removed. This functionality no longer exists.
* `printer.sinkBufferSize` - removed. This functionality no longer exists.
* `encoder.sinkBufferSize` - removed. This functionality no longer exists.
* `player.deviceIndex` - removed in favor of `player.device`.
* `player.device` is mandatory, and you must get a device reference by calling
`groove.getDevices()`. You must call `groove.connectSoundBackend()` before
calling `groove.getDevices()`.
* After calling `groove.createPlaylist` you must call `playlist.destroy` when finished
with the playlist.
* Add `GrooveWaveformBuilder` for creating waveform visualizations
* player: 'nowplaying' event renamed to 'nowPlaying'
* player: 'bufferunderrun' event renamed to 'bufferUnderrun'
* player: add more events: 'deviceClosed', 'deviceOpened', 'deviceOpenError',
'endOfPlaylist', 'wakeup'.
* player: `targetAudioFormat`, `actualAudioFormat`, and `useExactAudioFormat`
no longer exist. Instead, the player always opens the device with exactly the
correct audio parameters for each song, or if the device doesn't support that,
the next best in terms of audio quality.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014 Andrew Kelley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# node-groove
Node.js bindings to [libgroove](https://github.com/andrewrk/libgroove) -
generic music player backend library.
Live discussion in `#libgroove` on [freenode](https://freenode.net/).
Here are the main interfaces. See API Documentation below for more details.
* GrooveFile - represents an open audio file.
* GroovePlaylist - put files in the playlist, and the playlist decodes the
files and fills up the attached sinks.
* GroovePlayer - attach this sink to a playlist to play the decoded audio over
the system's speakers.
* GrooveEncoder - attach this sink to a playlist to obtain encoded audio
buffers, such as an mp3 stream.
* GrooveLoudnessDetector - attach this sink to a playlist to compute how loud
files sound to the human ear, along with the "true peak" value. You can use
this to implement ReplayGain.
* GrooveFingerprinter - attach this sink to a playlist to compute an acoustid
fingerprint. This can be used to look up a file by its audio content and
figure out which tags should be applied.
* GrooveWaveformBuilder - attach this sink to a playlist to compute a JSON
representation of an audio file. This can be used to display a visualization
of the audio file.
## Usage
1. Install libgroove to your system. libgroove is a set of 4 libraries;
node-groove depends on all of them. So for example on ubuntu, make sure to
install libgroove-dev, libgrooveplayer-dev, libgrooveloudness-dev, and
libgroovefingerprinter-dev.
2. `npm install --save groove`
### Versions
* node-groove >=3.0.0 depends on libgroove >=5.0.0
* node-groove >=2.4.0 <3.0.0 depends on libgroove >=4.3.0 <5.0.0
* node-groove 2.3.4 depends on libgroove <4.3.0
See CHANGELOG.md for release notes and upgrade guide.
### Get Metadata from File
```js
var groove = require('groove');
groove.open("danse-macabre.ogg", function(err, file) {
if (err) throw err;
console.log(file.metadata());
console.log("duration:", file.duration());
file.close(function(err) {
if (err) throw err;
});
});
```
#### More Examples
* example/metadata.js - read or update metadata in a media file
* example/playlist.js - play several files in a row and then exit
* example/replaygain.js - compute replaygain values for media files
* example/transcode.js - convert and splice several files together
* example/fingerprint.js - create an acoustid fingerprint for media files
* example/devices.js - list the playback devices on the system
* example/waveform.js - calculate a waveformjs compatible representation of a media file
## API Documentation
### globals
#### groove.setLogging(level)
`level` can be:
* `groove.LOG_QUIET`
* `groove.LOG_ERROR`
* `groove.LOG_WARNING`
* `groove.LOG_INFO`
#### groove.loudnessToReplayGain(loudness)
Converts a loudness value which is in LUFS to the ReplayGain-suggested dB
adjustment.
#### groove.dBToFloat(dB)
Converts dB format volume adjustment to a floating point gain format.
#### groove.getVersion()
Returns an object with these properties:
* `major`
* `minor`
* `patch`
### GrooveFile
#### groove.open(filename, callback)
`callback(err, file)`
#### file.close(callback)
`callback(err)`
#### file.duration()
In seconds.
#### file.shortNames()
A comma-separated list of short names for the format.
#### file.getMetadata(key, [flags])
Flags:
* `groove.TAG_MATCH_CASE`
* `groove.TAG_DONT_OVERWRITE`
* `groove.TAG_APPEND`
#### file.setMetadata(key, value, [flags])
See `getMetadata` for flags.
Pass `null` for `value` to delete a key.
#### file.metadata()
This returns an object populated with all the metadata.
Updating the object does nothing. Use `setMetadata` to
update metadata and then `save` to write changes to disk.
#### file.dirty
Boolean whether `save` will do anything.
#### file.filename
The string that was passed to `groove.open`
#### file.overrideDuration(duration)
If you know for sure the actual duration of the file, call this function
to set the actual duration in seconds of the file. `GrooveWaveformBuilder`
will use this value instead of `file.duration()`.
This must only be called when no `GroovePlaylistItem` references to this file.
#### file.save(callback)
`callback(err)`
### GroovePlaylist
#### groove.createPlaylist()
A playlist managers keeping an audio buffer full. To send the buffer
to your speakers, use `playlist.createPlayer()`.
Note: you probably only want one playlist. In node-groove, a playlist is
a low-level audio processing concept, not to be confused with user-facing
playlists where users might add, remove, and re-order songs.
#### playlist.destroy()
When finished with your playlist you must destroy it.
#### playlist.items()
Returns a read-only array of playlist items.
Use `playlist.insert` and `playlist.remove` to modify.
`[playlistItem1, playlistItem2, ...]`
#### playlist.play()
#### playlist.pause()
#### playlist.seek(playlistItem, position)
Seek to `playlistItem`, `position` seconds into the song.
#### playlist.insert(file, gain, peak, nextPlaylistItem)
Creates a new playlist item with file and puts it in the playlist before
`nextPlaylistItem`. If `nextPlaylistItem` is `null`, appends the new
item to the playlist.
`gain` is a float format volume adjustment that applies only to this item.
defaults to 1.0
`peak` is float format, see `item.peak`.
defaults to 1.0
Returns the newly added playlist item.
Once you add a file to the playlist, you must not `file.close()` it until
you first remove it from the playlist.
#### playlist.remove(playlistItem)
Remove `playlistItem` from the playlist.
Note that you are responsible for calling `file.close()` on every file
that you open with `groove.open`. `playlist.remove` will not close files.
#### playlist.position()
Returns `{item, pos}` where `item` is the playlist item currently being
decoded and `pos` is how many seconds into the song the decode head is.
Note that typically you are more interested in the position of the play head,
not the decode head. Example methods which return the play head are
`player.position()` and `encoder.position()`.
#### playlist.playing()
Returns `true` or `false`.
#### playlist.clear()
Remove all playlist items.
#### playlist.count()
How many items are on the playlist.
#### playlist.gain
#### playlist.setGain(value)
Between 0.0 and 1.0. You probably want to leave this at 1.0, since using
replaygain will typically lower your volume a significant amount.
#### playlist.setItemGainPeak(playlistItem, gain, peak)
`gain` is a float that affects the volume of the specified playlist item only.
To convert from dB to float, use exp(log(10) * 0.05 * dBValue).
See `item.peak`
#### playlist.setFillMode(mode)
`mode` can be:
* `groove.EVERY_SINK_FULL`
The playlist will decode audio if any sinks are not full. If any sinks do
not drain fast enough the data will buffer up in the playlist.
* `groove.ANY_SINK_FULL`
This is the default behavior. With this behavior, the playlist will stop
decoding audio when any attached sink is full, and then resume decoding
audio every sink is not full.
Defaults to `groove.EVERY_SINK_FULL`.
### GroovePlaylistItem
These are not instantiated directly; instead they are returned from
`playlist.items()`.
A `GroovePlaylistItem` is merely a pointer into a `GroovePlaylist`. If you
remove a playlist item from a playlist, any playlist item references you
have lying around become dangling pointers.
#### item.file
Read-only.
#### item.gain
A volume adjustment in float format to apply to the file when it plays.
This is typically used for loudness compensation, for example ReplayGain.
To convert from dB to float, use `groove.dBToFloat`
Read-only. Use `playlist.setItemGain` to modify.
#### item.peak
The sample peak of this playlist item is assumed to be 1.0 in float
format. If you know for certain that the peak is less than 1.0, you
may set this value which may allow the volume adjustment to use
a pure amplifier rather than a compressor. This results in slightly
better audio quality.
Read-only. Use `playlist.setItemPeak` to modify.
#### item.id
Every time you obtain a playlist item from groove, you will get a fresh
JavaScript object, but it might point to the same underlying libgroove pointer
as another. The `id` field is a way to check if two playlist items reference
the same one.
Read-only.
### GroovePlayer
#### groove.getDevices()
Before you can call this function, you must call
`groove.connectSoundBackend()`.
Returns an object like this:
```js
{
list: [
{
name: "User-Friendly Device Name",
id: "unique device ID that persists across plugs and unplugs",
isRaw: false, // true if this device would claim exclusive access
probeError: 3, // non zero if scanning this device did not work
},
//...
],
defaultIndex: 0,
}
```
#### groove.connectSoundBackend([backend])
`backend` is optional. If left blank the best backend is automatically
selected. Otherwise it can be one of these:
* `groove.BACKEND_JACK`
* `groove.BACKEND_PULSEAUDIO`
* `groove.BACKEND_ALSA`
* `groove.BACKEND_COREAUDIO`
* `groove.BACKEND_WASAPI`
* `groove.BACKEND_DUMMY`
#### groove.disconnectSoundBackend()
#### groove.createPlayer()
Creates a GroovePlayer instance which you can then configure by setting
properties.
#### player.device
Before calling `attach()`, set this to one of the devices
returned from `groove.getDevices()`.
#### player.attach(playlist, callback)
Sends audio to sound device.
`callback(err)`
#### player.detach(callback)
`callback(err)`
#### player.position()
Returns `{item, pos}` where `item` is the playlist item currently being
played and `pos` is how many seconds into the song the play head is.
#### player.on('nowplaying', handler)
Fires when the item that is now playing changes. It can be `null`.
`handler()`
#### player.on('bufferunderrun', handler)
Fires when a buffer underrun occurs. Ideally you'll never see this.
`handler()`
#### player.on('devicereopened', handler)
Fires when you have set `useExactAudioFormat` to `true` and the audio device
has been closed and re-opened to match incoming audio data.
`handler()`
### GrooveEncoder
#### groove.createEncoder()
#### encoder.bitRate
select encoding quality by choosing a target bit rate
#### encoder.formatShortName
optional - help libgroove guess which format to use.
`avconv -formats` to get a list of possibilities.
#### encoder.codecShortName
optional - help libgroove guess which codec to use.
`avconv-codecs` to get a list of possibilities.
#### encoder.filename
optional - provide an example filename to help libgroove guess
which format/codec to use.
#### encoder.mimeType
optional - provide a mime type string to help libgrooove guess
which format/codec to use.
#### encoder.targetAudioFormat
The desired audio format settings with which to encode.
`groove.createEncoder()` defaults these to 44100 Hz,
signed 16-bit int, stereo.
These are preferences; if a setting cannot be used, a substitute will
be used instead. In this case, actualAudioFormat will be updated to reflect
the substituted values.
Properties:
* `sampleRate`
* `channelLayout` - array of channel ids
* `sampleFormat`
#### encoder.actualAudioFormat
groove sets this to the actual format you get when you attach the encoder.
Ideally will be the same as targetAudioFormat but might not be.
Properties:
* `sampleRate`
* `channelLayout` - array of channel ids
* `sampleFormat`
#### encoder.sinkBufferSize
How big the sink buffer should be, in sample frames.
`createEncoder` defaults this to 8192
#### encoder.encodedBufferSize
How big the encoded audio buffer should be, in bytes.
`createEncoder` defaults this to 16384
#### encoder.attach(playlist, callback)
`callback(err)`
#### encoder.detach(callback)
`callback(err)`
#### encoder.getBuffer()
Returns `null` if no buffer available, or an object with these properties:
* `buffer` - a node `Buffer` instance which is the encoded data for this chunk
this can be `null` in which case this buffer is actually the end of
playlist sentinel.
* `item` - the GroovePlaylistItem of which this buffer is encoded data for
* `pos` - position in seconds that this buffer represents in into the item
#### encoder.on('buffer', handler)
`handler()`
Emitted when there is a buffer available to get. You still need to get the
buffer with `getBuffer()`.
#### encoder.position()
Returns `{item, pos}` where `item` is the playlist item currently being
encoded and `pos` is how many seconds into the song the encode head is.
### GrooveLoudnessDetector
#### groove.createLoudnessDetector()
returns a GrooveLoudnessDetector
#### detector.infoQueueSize
Set this to determine how far ahead into the playlist to look.
#### detector.disableAlbum
Set to `true` to only compute track loudness. This is faster and requires less
memory than computing both.
#### detector.attach(playlist, callback)
`callback(err)`
#### detector.detach(callback)
`callback(err)`
#### detector.getInfo()
Returns `null` if no info available, or an object with these properties:
* `loudness` - loudness in LUFS
* `peak` - sample peak in float format of the file
* `duration` - duration in seconds of the track
* `item` - the GroovePlaylistItem that this applies to, or `null` if it applies
to the entire album.
#### detector.position()
Returns `{item, pos}` where `item` is the playlist item currently being
detected and `pos` is how many seconds into the song the detect head is.
#### detector.on('info', handler)
`handler()`
Emitted when there is info available to get. You still need to get the info
with `getInfo()`.
### GrooveFingerprinter
#### groove.createFingerprinter()
returns a GrooveFingerprinter
#### groove.encodeFingerprint(rawFingerprint)
Given an Array of integers which is the raw fingerprint, encode it into a
string which can be submitted to acoustid.org.
#### groove.decodeFingerprint(fingerprint)
Given the fingerprint string, returns a list of integers which is the raw
fingerprint data.
#### printer.infoQueueSize
Set this to determine how far ahead into the playlist to look.
#### printer.attach(playlist, callback)
`callback(err)`
#### printer.detach(callback)
`callback(err)`
#### printer.getInfo()
Returns `null` if no info available, or an object with these properties:
* `fingerprint` - integer array which is the raw fingerprint
* `duration` - duration in seconds of the track
* `item` - the GroovePlaylistItem that this applies to, or `null` if it applies
to the entire album.
#### printer.position()
Returns `{item, pos}` where `item` is the playlist item currently being
fingerprinted and `pos` is how many seconds into the song the printer head is.
#### printer.on('info', handler)
`handler()`
Emitted when there is info available to get. You still need to get the info
with `getInfo()`.
### GrooveWaveformBuilder
#### groove.createWaveformBuilder()
returns a GrooveWaveformBuilder
#### waveform.widthInFrames
How many frames wide the waveform data will be. Defaults to 1920.
If you have a song with 100 frames and `widthInFrames` is 50, then each
waveform data frame will correspond to 2 frames of the original song.
#### printer.infoQueueSizeBytes
Set this to determine how far ahead into the playlist to look.
#### waveform.attach(playlist, callback)
`callback(err)`
#### waveform.detach(callback)
`callback(err)`
#### waveform.getInfo()
Returns `null` if no info available, or an object with these properties:
* `buffer` - A `Buffer` of the waveform data, one unsigned 8 bit integer per
`widthInFrames`.
* `expectedDuration` - This is the duration in seconds that was used to create
the waveform data. If this is different than `actualDuration` then the data
is invalid and must be re-calculated, this time using `file.overrideDuration()`
* `actualDuration` - This is the correct duration in seconds for the track,
known only after waveform calculation is complete.
* `item` - the GroovePlaylistItem that this applies to, or `null` if this info
signals the end of playlist.
#### waveform.position()
Returns `{item, pos}` where `item` is the playlist item currently being
calculated and `pos` is how many seconds into the song the waveform head is.
#### waveform.on('info', handler)
`handler()`
Emitted when there is info available to get. You still need to get the info
with `getInfo()`.
================================================
FILE: binding.gyp
================================================
{
"targets": [
{
"target_name": "groove",
"sources": [
"src/player.cc",
"src/groove.cc",
"src/file.cc",
"src/playlist.cc",
"src/playlist_item.cc",
"src/waveform_builder.cc",
"src/loudness_detector.cc",
"src/fingerprinter.cc",
"src/encoder.cc",
"src/device.cc",
],
"libraries": [
"-lgroove"
],
"include_dirs": [
"<!(node -e \"require('nan')\")"
]
}
]
}
================================================
FILE: example/devices.js
================================================
/* list available output devices */
var groove = require('../');
var assert = require('assert');
groove.connectSoundBackend();
var devices = groove.getDevices();
for (var i = 0; i < devices.list.length; i += 1) {
if (devices.list[i].isRaw) continue;
var isDefault = (i === devices.defaultIndex);
var defaultString = isDefault ? "(default) " : "";
console.log(defaultString + devices.list[i].name);
}
================================================
FILE: example/fingerprint.js
================================================
/* generate the acoustid fingerprint of songs */
var groove = require('../');
var Pend = require('pend');
if (process.argv.length < 3) usage();
var playlist = groove.createPlaylist();
var printer = groove.createFingerprinter();
printer.on('info', function() {
var info = printer.getInfo();
if (info.item) {
console.log(info.item.file.filename, "fingerprint:");
console.log(info.fingerprint);
} else {
cleanup();
}
});
var files = [];
var pend = new Pend();
for (var i = 2; i < process.argv.length; i += 1) {
var o = {
file: null,
filename: process.argv[i],
};
files.push(o);
pend.go(openFileFn(o));
}
pend.wait(function(err) {
if (err) throw err;
files.forEach(function(o) {
playlist.insert(o.file, null);
});
printer.attach(playlist, function(err) {
if (err) throw err;
});
});
function openFileFn(o) {
return function(cb) {
groove.open(o.filename, function(err, file) {
if (err) return cb(err);
o.file = file;
cb();
});
};
}
function cleanup() {
playlist.clear();
files.forEach(function(o) {
pend.go(function(cb) {
o.file.close(cb);
});
});
pend.wait(function(err) {
if (err) throw err;
printer.detach(function(err) {
if (err) throw err;
playlist.destroy();
});
});
}
function usage() {
console.error("Usage: node fingerprint.js file1 file2 ...");
process.exit(1);
}
================================================
FILE: example/metadata.js
================================================
/* read or update metadata in a media file */
var groove = require('../');
if (process.argv.length < 3) usage();
groove.setLogging(groove.LOG_INFO);
var filename = process.argv[2];
groove.open(filename, function(err, file) {
if (err) {
console.error("error opening file:", err.stack);
process.exit(1);
}
var key, value;
for (var i = 3; i < process.argv.length; i += 1) {
var arg = process.argv[i];
if (arg === '--update') {
if (i + 2 >= process.argv.length) {
console.error("--update requires 2 arguments");
cleanup(file, usage);
return;
}
key = process.argv[++i];
value = process.argv[++i];
file.setMetadata(key, value);
} else if (arg === '--delete') {
if (i + 1 >= process.argv.length) {
console.error("--delete requires 1 argument");
cleanup(file, usage);
return;
}
key = process.argv[++i];
file.setMetadata(key, null);
} else {
cleanup(file, usage);
return;
}
}
console.log("duration", "=", file.duration());
var metadata = file.metadata();
for (key in metadata) {
value = metadata[key];
console.log(key, "=", value);
}
if (file.dirty) {
file.save(handleSaveErr);
} else {
cleanup(file);
}
function handleSaveErr(err) {
if (err) console.error("Error saving:", err.stack);
cleanup(file);
}
});
function usage() {
console.error("Usage:", process.argv[0], process.argv[1],
"<file> [--update key value] [--delete key]");
process.exit(1);
}
function cleanup(file, cb) {
file.close(function(err) {
if (err) console.error("Error closing file:", err.stack);
if (cb) cb();
});
}
================================================
FILE: example/playlist.js
================================================
/* play several files in a row and then exit */
var groove = require('../');
var Pend = require('pend');
if (process.argv.length < 3) usage();
var playlist = groove.createPlaylist();
var player = groove.createPlayer();
groove.connectSoundBackend();
var devices = groove.getDevices();
var defaultDevice = devices.list[devices.defaultIndex];
player.device = defaultDevice;
player.on('nowPlaying', function() {
var current = player.position();
if (!current.item) {
cleanup();
return;
}
var artist = current.item.file.getMetadata('artist');
var title = current.item.file.getMetadata('title');
console.log("Now playing:", artist, "-", title);
});
var files = [];
var pend = new Pend();
for (var i = 2; i < process.argv.length; i += 1) {
var o = {
filename: process.argv[i],
file: null,
};
files.push(o);
pend.go(openFileFn(o));
}
pend.wait(function(err) {
if (err) throw err;
files.forEach(function(o) {
playlist.insert(o.file);
});
player.attach(playlist, function(err) {
if (err) throw err;
});
});
function openFileFn(o, filename) {
return function(cb) {
groove.open(o.filename, function(err, file) {
if (err) return cb(err);
o.file = file;
cb();
});
};
}
function cleanup() {
playlist.clear();
files.forEach(function(o) {
pend.go(function(cb) {
o.file.close(cb);
});
});
pend.wait(function(err) {
if (err) throw err;
player.detach(function(err) {
if (err) throw err;
playlist.destroy();
});
});
}
function usage() {
console.error("Usage: playlist file1 file2 ...");
process.exit(1);
}
================================================
FILE: example/replaygain.js
================================================
/* replaygain scanner */
var groove = require('../');
var Pend = require('pend');
if (process.argv.length < 3) usage();
var playlist = groove.createPlaylist();
var detector = groove.createLoudnessDetector();
detector.on('info', function() {
var info = detector.getInfo();
if (info.item) {
console.log(info.item.file.filename, "gain:",
groove.loudnessToReplayGain(info.loudness), "peak:", info.peak,
"duration:", info.duration);
} else {
console.log("all files gain:",
groove.loudnessToReplayGain(info.loudness), "peak:", info.peak,
"duration:", info.duration);
cleanup();
}
});
var files = [];
var pend = new Pend();
detector.attach(playlist, function(err) {
if (err) throw err;
var pend = new Pend();
for (var i = 2; i < process.argv.length; i += 1) {
var o = {
filename: process.argv[i],
file: null,
};
files.push(o);
pend.go(openFileFn(o));
}
pend.wait(function(err) {
if (err) throw err;
files.forEach(function(o) {
playlist.insert(o.file, null);
});
});
});
function openFileFn(o) {
return function(cb) {
groove.open(o.filename, function(err, file) {
if (err) return cb(err);
o.file = file;
cb();
});
};
}
function cleanup() {
playlist.clear();
files.forEach(function(o) {
pend.go(function(cb) {
o.file.close(cb);
});
});
pend.wait(function(err) {
if (err) throw err;
detector.detach(function(err) {
if (err) throw err;
playlist.destroy();
});
});
}
function usage() {
console.error("Usage: node replaygain.js file1 file2 ...");
process.exit(1);
}
================================================
FILE: example/transcode.js
================================================
/* transcode a file into ogg vorbis */
var groove = require('../');
var fs = require('fs');
if (process.argv.length < 4) usage();
groove.setLogging(groove.LOG_INFO);
var playlist = groove.createPlaylist();
var encoder = groove.createEncoder();
encoder.formatShortName = "ogg";
encoder.codecShortName = "vorbis";
var outStream = fs.createWriteStream(process.argv[3]);
encoder.on('buffer', function() {
var buffer;
while (buffer = encoder.getBuffer()) {
if (buffer.buffer) {
outStream.write(buffer.buffer);
} else {
cleanup();
return;
}
}
});
encoder.attach(playlist, function(err) {
if (err) throw err;
groove.open(process.argv[2], function(err, file) {
if (err) throw err;
playlist.insert(file, null);
});
});
function cleanup() {
var file = playlist.items()[0].file;
playlist.clear();
file.close(function(err) {
if (err) throw err;
encoder.detach(function(err) {
if (err) throw err;
playlist.destroy();
});
});
}
function usage() {
console.error("Usage: node transcode.js inputfile outputfile");
process.exit(1);
}
================================================
FILE: example/waveform.js
================================================
/* calculate a waveformjs compatible representation of a media file */
var groove = require('../');
main();
function main() {
var inputFilename = null;
for (var i = 2; i < process.argv.length; i += 1) {
var arg = process.argv[i];
var overrideDuration = null;
if (arg[0] === "-" && arg[1] === "-") {
if (++i < process.argv.length) {
if (arg === "--override-duration") {
overrideDuration = parseFloat(process.argv[i]);
} else {
usageAndExit();
}
} else {
usageAndExit();
}
} else if (!inputFilename) {
inputFilename = arg;
} else {
usageAndExit();
}
}
if (!inputFilename) usageAndExit();
var playlist = groove.createPlaylist();
var waveform = groove.createWaveformBuilder();
var file = null;
waveform.on('info', function() {
var info = waveform.getInfo();
if (!info.item) {
cleanup();
return;
}
if (Math.abs(info.expectedDuration - info.actualDuration) > 0.1) {
console.error("invalid duration. re-run with --override-duration " + info.actualDuration);
process.exit(1);
return;
}
var data = {
duration: info.actualDuration,
waveformjs: Array.prototype.slice.call(info.buffer, 0),
};
console.log(JSON.stringify(data));
});
groove.open(inputFilename, function(err, openedFile) {
if (err) throw err;
file = openedFile;
if (overrideDuration) {
file.overrideDuration(overrideDuration);
}
playlist.insert(file, null);
waveform.attach(playlist, function(err) {
if (err) throw err;
});
});
function cleanup() {
playlist.clear();
waveform.detach(function(err) {
if (err) throw err;
playlist.destroy();
file.close(function(err) {
if (err) throw err;
});
});
}
}
function usageAndExit() {
console.error("Usage: node waveform.js [--override-duration seconds] file");
process.exit(1);
}
================================================
FILE: lib/index.js
================================================
var bindings = require('bindings')('groove.node');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var DB_SCALE = Math.log(10.0) * 0.05;
/* "C++ modules aren't really for doing complex things that need to be
* strewn across multiple modules. Just get your binding done as quick
* as possible, get out of there, and then wrap it in JS for all the fancy stuff
*
* -isaacs
*/
// hi-jack some of the native methods
var bindingsCreatePlayer = bindings.createPlayer;
var bindingsCreateLoudnessDetector = bindings.createLoudnessDetector;
var bindingsCreateFingerprinter = bindings.createFingerprinter;
var bindingsCreateEncoder = bindings.createEncoder;
var bindingsCreateWaveformBuilder = bindings.createWaveformBuilder;
bindings.createPlayer = jsCreatePlayer;
bindings.createEncoder = jsCreateEncoder;
bindings.createLoudnessDetector = jsCreateLoudnessDetector;
bindings.createFingerprinter = jsCreateFingerprinter;
bindings.createWaveformBuilder = jsCreateWaveformBuilder;
bindings.loudnessToReplayGain = loudnessToReplayGain;
bindings.dBToFloat = dBToFloat;
module.exports = bindings;
function jsCreateEncoder() {
var encoder = bindingsCreateEncoder(eventCb);
postHocInherit(encoder, EventEmitter);
EventEmitter.call(encoder);
return encoder;
function eventCb() {
encoder.emit('buffer');
}
}
function jsCreatePlayer() {
var player = bindingsCreatePlayer(eventCb);
postHocInherit(player, EventEmitter);
EventEmitter.call(player);
return player;
function eventCb(id) {
switch (id) {
case bindings._EVENT_NOWPLAYING:
player.emit('nowPlaying');
break;
case bindings._EVENT_BUFFERUNDERRUN:
player.emit('bufferUnderrun');
break;
case bindings._EVENT_DEVICE_CLOSED:
player.emit('deviceClosed');
break;
case bindings._EVENT_DEVICE_OPENED:
player.emit('deviceOpened');
break;
case bindings._EVENT_DEVICE_OPEN_ERROR:
player.emit('deviceOpenError');
break;
case bindings._EVENT_END_OF_PLAYLIST:
player.emit('endOfPlaylist');
break;
case bindings._EVENT_WAKEUP:
player.emit('wakeup');
break;
}
}
}
function jsCreateLoudnessDetector() {
var detector = bindingsCreateLoudnessDetector(eventCb);
postHocInherit(detector, EventEmitter);
EventEmitter.call(detector);
return detector;
function eventCb() {
detector.emit('info');
}
}
function jsCreateFingerprinter() {
var printer = bindingsCreateFingerprinter(eventCb);
postHocInherit(printer, EventEmitter);
EventEmitter.call(printer);
return printer;
function eventCb() {
printer.emit('info');
}
}
function jsCreateWaveformBuilder() {
var waveform = bindingsCreateWaveformBuilder(eventCb);
postHocInherit(waveform, EventEmitter);
EventEmitter.call(waveform);
return waveform;
function eventCb() {
waveform.emit('info');
}
}
function postHocInherit(baseInstance, Super) {
var baseProto = Object.getPrototypeOf(baseInstance);
var superProto = Super.prototype;
Object.keys(superProto).forEach(function(method) {
if (!baseProto[method]) baseProto[method] = superProto[method];
});
}
function clamp_rg(x) {
if (x > 51.0) return 51.0;
else if (x < -51.0) return -51.0;
else return x;
}
function loudnessToReplayGain(loudness) {
return clamp_rg(-18.0 - loudness);
}
function dBToFloat(dB) {
return Math.exp(dB * DB_SCALE);
}
================================================
FILE: package.json
================================================
{
"name": "groove",
"version": "2.4.0",
"description": "bindings to libgroove - generic music player library",
"main": "lib/index.js",
"author": "Andrew Kelley <superjoe30@gmail.com>",
"repository": {
"type": "git",
"url": "https://github.com/andrewrk/node-groove"
},
"scripts": {
"test": "mocha --reporter spec",
"install": "node-gyp rebuild"
},
"license": "MIT",
"devDependencies": {
"mocha": "~2.4.5",
"ncp": "~2.0.0",
"pend": "~1.2.0"
},
"dependencies": {
"bindings": "~1.2.1",
"nan": "~2.3.5"
},
"gypfile": true,
"bugs": {
"url": "https://github.com/andrewrk/node-groove/issues"
},
"homepage": "https://github.com/andrewrk/node-groove",
"directories": {
"example": "example",
"test": "test"
}
}
================================================
FILE: src/device.cc
================================================
#include "device.h"
#include "file.h"
using namespace v8;
GNDevice::GNDevice() { };
GNDevice::~GNDevice() {
soundio_device_unref(device);
};
static Nan::Persistent<v8::Function> constructor;
void GNDevice::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("SoundIoDevice").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("name").ToLocalChecked(), GetName);
Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
Nan::SetAccessor(proto, Nan::New<String>("softwareLatencyMin").ToLocalChecked(), GetSoftwareLatencyMin);
Nan::SetAccessor(proto, Nan::New<String>("softwareLatencyMax").ToLocalChecked(), GetSoftwareLatencyMax);
Nan::SetAccessor(proto, Nan::New<String>("softwareLatencyCurrent").ToLocalChecked(), GetSoftwareLatencyCurrent);
Nan::SetAccessor(proto, Nan::New<String>("isRaw").ToLocalChecked(), GetIsRaw);
Nan::SetAccessor(proto, Nan::New<String>("probeError").ToLocalChecked(), GetProbeError);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNDevice::New) {
Nan::HandleScope scope;
GNDevice *obj = new GNDevice();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNDevice::NewInstance(SoundIoDevice *device) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(instance);
gn_device->device = device;
return scope.Escape(instance);
}
NAN_GETTER(GNDevice::GetName) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<String>(device->name).ToLocalChecked());
}
NAN_GETTER(GNDevice::GetId) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<String>(device->id).ToLocalChecked());
}
NAN_GETTER(GNDevice::GetSoftwareLatencyMin) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<Number>(device->software_latency_min));
}
NAN_GETTER(GNDevice::GetSoftwareLatencyMax) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<Number>(device->software_latency_max));
}
NAN_GETTER(GNDevice::GetSoftwareLatencyCurrent) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<Number>(device->software_latency_current));
}
NAN_GETTER(GNDevice::GetIsRaw) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<Number>(device->is_raw));
}
NAN_GETTER(GNDevice::GetProbeError) {
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());
SoundIoDevice *device = gn_device->device;
info.GetReturnValue().Set(Nan::New<Number>(device->probe_error));
}
================================================
FILE: src/device.h
================================================
#ifndef GN_DEVICE_H
#define GN_DEVICE_H
#include <node.h>
#include <nan.h>
#include <groove/groove.h>
class GNDevice : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(SoundIoDevice *device);
SoundIoDevice *device;
private:
GNDevice();
~GNDevice();
static NAN_METHOD(New);
static NAN_GETTER(GetName);
static NAN_GETTER(GetId);
static NAN_GETTER(GetSoftwareLatencyMin);
static NAN_GETTER(GetSoftwareLatencyMax);
static NAN_GETTER(GetSoftwareLatencyCurrent);
static NAN_GETTER(GetIsRaw);
static NAN_GETTER(GetProbeError);
};
#endif
================================================
FILE: src/encoder.cc
================================================
#include <node_buffer.h>
#include "encoder.h"
#include "playlist.h"
#include "playlist_item.h"
#include "groove.h"
using namespace v8;
GNEncoder::GNEncoder() {};
GNEncoder::~GNEncoder() {
groove_encoder_destroy(encoder);
delete event_context->event_cb;
delete event_context;
};
static Nan::Persistent<v8::Function> constructor;
void GNEncoder::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GrooveEncoder").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(2);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("actualAudioFormat").ToLocalChecked(), GetActualAudioFormat);
// Methods
Nan::SetPrototypeMethod(tpl, "attach", Attach);
Nan::SetPrototypeMethod(tpl, "detach", Detach);
Nan::SetPrototypeMethod(tpl, "getBuffer", GetBuffer);
Nan::SetPrototypeMethod(tpl, "position", Position);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNEncoder::New) {
Nan::HandleScope scope;
GNEncoder *obj = new GNEncoder();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
NAN_GETTER(GNEncoder::GetActualAudioFormat) {
Nan::HandleScope scope;
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
GrooveEncoder *encoder = gn_encoder->encoder;
Local<Array> layout = Nan::New<Array>();
for (int ch = 0; ch < encoder->actual_audio_format.layout.channel_count; ch += 1) {
Nan::Set(layout, Nan::New<Number>(ch),
Nan::New<Number>(encoder->actual_audio_format.layout.channels[ch]));
}
Local<Object> actualAudioFormat = Nan::New<Object>();
Nan::Set(actualAudioFormat, Nan::New<String>("sampleRate").ToLocalChecked(),
Nan::New<Number>(encoder->actual_audio_format.sample_rate));
Nan::Set(actualAudioFormat, Nan::New<String>("channelLayout").ToLocalChecked(), layout);
Nan::Set(actualAudioFormat, Nan::New<String>("sampleFormat").ToLocalChecked(),
Nan::New<Number>(encoder->actual_audio_format.format));
info.GetReturnValue().Set(actualAudioFormat);
}
Local<Value> GNEncoder::NewInstance(GrooveEncoder *encoder) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
gn_encoder->encoder = encoder;
return scope.Escape(instance);
}
static void EncoderEventAsyncCb(uv_async_t *handle) {
Nan::HandleScope scope;
GNEncoder::EventContext *context = reinterpret_cast<GNEncoder::EventContext *>(handle->data);
const unsigned argc = 1;
Local<Value> argv[argc];
argv[0] = Nan::Undefined();
TryCatch try_catch;
context->event_cb->Call(argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
uv_mutex_lock(&context->mutex);
uv_cond_signal(&context->cond);
uv_mutex_unlock(&context->mutex);
}
static void EncoderEventThreadEntry(void *arg) {
GNEncoder::EventContext *context = reinterpret_cast<GNEncoder::EventContext *>(arg);
while (groove_encoder_buffer_peek(context->encoder, 1) > 0) {
uv_mutex_lock(&context->mutex);
if (context->emit_buffer_ok) {
context->emit_buffer_ok = false;
uv_async_send(&context->event_async);
}
uv_cond_wait(&context->cond, &context->mutex);
uv_mutex_unlock(&context->mutex);
}
}
class EncoderAttachWorker : public Nan::AsyncWorker {
public:
EncoderAttachWorker(Nan::Callback *callback, GrooveEncoder *encoder, GroovePlaylist *playlist,
GNEncoder::EventContext *event_context,
String::Utf8Value *format_short_name,
String::Utf8Value *codec_short_name,
String::Utf8Value *filename,
String::Utf8Value *mime_type) :
Nan::AsyncWorker(callback)
{
this->encoder = encoder;
this->playlist = playlist;
this->event_context = event_context;
this->format_short_name = format_short_name;
this->codec_short_name = codec_short_name;
this->filename = filename;
this->mime_type = mime_type;
}
~EncoderAttachWorker() {
delete format_short_name;
delete codec_short_name;
delete filename;
delete mime_type;
}
void Execute() {
encoder->format_short_name = format_short_name ? **format_short_name : NULL;
encoder->codec_short_name = codec_short_name ? **codec_short_name : NULL;
encoder->filename = filename ? **filename : NULL;
encoder->mime_type = mime_type ? **mime_type : NULL;
int err;
if ((err = groove_encoder_attach(encoder, playlist))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_init(&event_context->cond);
uv_mutex_init(&event_context->mutex);
event_context->event_async.data = event_context;
uv_async_init(uv_default_loop(), &event_context->event_async, EncoderEventAsyncCb);
uv_thread_create(&event_context->event_thread, EncoderEventThreadEntry, event_context);
}
GrooveEncoder *encoder;
GroovePlaylist *playlist;
GNEncoder::EventContext *event_context;
String::Utf8Value *format_short_name;
String::Utf8Value *codec_short_name;
String::Utf8Value *filename;
String::Utf8Value *mime_type;
};
NAN_METHOD(GNEncoder::Create) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
GrooveEncoder *encoder = groove_encoder_create(get_groove());
Local<Object> instance = NewInstance(encoder)->ToObject();
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
EventContext *context = new EventContext;
gn_encoder->event_context = context;
context->emit_buffer_ok = true;
context->event_cb = new Nan::Callback(info[0].As<Function>());
context->encoder = encoder;
// set properties on the instance with default values from
// GrooveEncoder struct
Local<Array> layout = Nan::New<Array>();
for (int ch = 0; ch < encoder->target_audio_format.layout.channel_count; ch += 1) {
Nan::Set(layout, Nan::New<Number>(ch),
Nan::New<Number>(encoder->target_audio_format.layout.channels[ch]));
}
Local<Object> targetAudioFormat = Nan::New<Object>();
Nan::Set(targetAudioFormat, Nan::New<String>("sampleRate").ToLocalChecked(),
Nan::New<Number>(encoder->target_audio_format.sample_rate));
Nan::Set(targetAudioFormat, Nan::New<String>("channelLayout").ToLocalChecked(), layout);
Nan::Set(targetAudioFormat, Nan::New<String>("sampleFormat").ToLocalChecked(),
Nan::New<Number>(encoder->target_audio_format.format));
Nan::Set(instance, Nan::New<String>("bitRate").ToLocalChecked(), Nan::New<Number>(encoder->bit_rate));
Nan::Set(instance, Nan::New<String>("actualAudioFormat").ToLocalChecked(), Nan::Null());
Nan::Set(instance, Nan::New<String>("targetAudioFormat").ToLocalChecked(), targetAudioFormat);
Nan::Set(instance, Nan::New<String>("formatShortName").ToLocalChecked(), Nan::Null());
Nan::Set(instance, Nan::New<String>("codecShortName").ToLocalChecked(), Nan::Null());
Nan::Set(instance, Nan::New<String>("filename").ToLocalChecked(), Nan::Null());
Nan::Set(instance, Nan::New<String>("mimeType").ToLocalChecked(), Nan::Null());
Nan::Set(instance, Nan::New<String>("encodedBufferSize").ToLocalChecked(), Nan::New<Number>(encoder->encoded_buffer_size));
info.GetReturnValue().Set(instance);
}
NAN_METHOD(GNEncoder::Attach) {
Nan::HandleScope scope;
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("Expected object arg[0]");
return;
}
if (info.Length() < 2 || !info[1]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[1]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
Local<Object> instance = info.This();
Local<Value> targetAudioFormatValue = instance->Get(Nan::New<String>("targetAudioFormat").ToLocalChecked());
if (!targetAudioFormatValue->IsObject()) {
Nan::ThrowTypeError("Expected targetAudioFormat to be an object");
return;
}
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
GrooveEncoder *encoder = gn_encoder->encoder;
// copy the properties from our instance to the encoder
Local<Value> formatShortName = instance->Get(Nan::New<String>("formatShortName").ToLocalChecked());
String::Utf8Value *format_short_name;
if (formatShortName->IsNull() || formatShortName->IsUndefined()) {
format_short_name = NULL;
} else {
format_short_name = new String::Utf8Value(formatShortName->ToString());
}
Local<Value> codecShortName = instance->Get(Nan::New<String>("codecShortName").ToLocalChecked());
String::Utf8Value *codec_short_name;
if (codecShortName->IsNull() || codecShortName->IsUndefined()) {
codec_short_name = NULL;
} else {
codec_short_name = new String::Utf8Value(codecShortName->ToString());
}
Local<Value> filenameStr = instance->Get(Nan::New<String>("filename").ToLocalChecked());
String::Utf8Value *filename;
if (filenameStr->IsNull() || filenameStr->IsUndefined()) {
filename = NULL;
} else {
filename = new String::Utf8Value(filenameStr->ToString());
}
Local<Value> mimeType = instance->Get(Nan::New<String>("mimeType").ToLocalChecked());
String::Utf8Value *mime_type;
if (mimeType->IsNull() || mimeType->IsUndefined()) {
mime_type = NULL;
} else {
mime_type = new String::Utf8Value(mimeType->ToString());
}
Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
Local<Array> layout = Local<Array>::Cast(
targetAudioFormat->Get(Nan::New<String>("channelLayout").ToLocalChecked()));
encoder->target_audio_format.layout.channel_count = layout->Length();
for (int ch = 0; ch < encoder->target_audio_format.layout.channel_count; ch += 1) {
Local<Value> channelId = layout->Get(Nan::New<Number>(ch));
encoder->target_audio_format.layout.channels[ch] = (SoundIoChannelId)(int)channelId->NumberValue();
}
double sample_fmt = targetAudioFormat->Get(Nan::New<String>("sampleFormat").ToLocalChecked())->NumberValue();
encoder->target_audio_format.format = (SoundIoFormat)(int)sample_fmt;
Local<Value> sampleRate = targetAudioFormat->Get(Nan::New<String>("sampleRate").ToLocalChecked());
double sample_rate = sampleRate->NumberValue();
encoder->target_audio_format.sample_rate = (int)sample_rate;
double bit_rate = instance->Get(Nan::New<String>("bitRate").ToLocalChecked())->NumberValue();
encoder->bit_rate = (int)bit_rate;
double encoded_buffer_size = instance->Get(Nan::New<String>("encodedBufferSize").ToLocalChecked())->NumberValue();
encoder->encoded_buffer_size = (int)encoded_buffer_size;
AsyncQueueWorker(new EncoderAttachWorker(callback, encoder, gn_playlist->playlist, gn_encoder->event_context,
format_short_name, codec_short_name, filename, mime_type));
}
class EncoderDetachWorker : public Nan::AsyncWorker {
public:
EncoderDetachWorker(Nan::Callback *callback, GrooveEncoder *encoder,
GNEncoder::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->encoder = encoder;
this->event_context = event_context;
}
~EncoderDetachWorker() {}
void Execute() {
int err;
if ((err = groove_encoder_detach(encoder))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_signal(&event_context->cond);
uv_thread_join(&event_context->event_thread);
uv_cond_destroy(&event_context->cond);
uv_mutex_destroy(&event_context->mutex);
uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);
}
GrooveEncoder *encoder;
GNEncoder::EventContext *event_context;
};
NAN_METHOD(GNEncoder::Detach) {
Nan::HandleScope scope;
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
GrooveEncoder *encoder = gn_encoder->encoder;
if (!encoder->playlist) {
Nan::ThrowTypeError("detach: not attached");
return;
}
AsyncQueueWorker(new EncoderDetachWorker(callback, encoder, gn_encoder->event_context));
}
static void encoder_buffer_free(char *data, void *hint) {
GrooveBuffer *buffer = reinterpret_cast<GrooveBuffer*>(hint);
groove_buffer_unref(buffer);
}
NAN_METHOD(GNEncoder::GetBuffer) {
Nan::HandleScope scope;
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
GrooveEncoder *encoder = gn_encoder->encoder;
GrooveBuffer *buffer;
int buf_result = groove_encoder_buffer_get(encoder, &buffer, 0);
uv_mutex_lock(&gn_encoder->event_context->mutex);
gn_encoder->event_context->emit_buffer_ok = true;
uv_cond_signal(&gn_encoder->event_context->cond);
uv_mutex_unlock(&gn_encoder->event_context->mutex);
switch (buf_result) {
case GROOVE_BUFFER_YES: {
Local<Object> object = Nan::New<Object>();
Nan::MaybeLocal<Object> bufferObject = Nan::NewBuffer(
reinterpret_cast<char*>(buffer->data[0]), buffer->size,
encoder_buffer_free, buffer);
Nan::Set(object, Nan::New<String>("buffer").ToLocalChecked(), bufferObject.ToLocalChecked());
if (buffer->item) {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(),
GNPlaylistItem::NewInstance(buffer->item));
} else {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
Nan::Set(object, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(buffer->pos));
Nan::Set(object, Nan::New<String>("pts").ToLocalChecked(), Nan::New<Number>(buffer->pts));
info.GetReturnValue().Set(object);
break;
}
case GROOVE_BUFFER_END: {
Local<Object> object = Nan::New<Object>();
Nan::Set(object, Nan::New<String>("buffer").ToLocalChecked(), Nan::Null());
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
Nan::Set(object, Nan::New<String>("pos").ToLocalChecked(), Nan::Null());
Nan::Set(object, Nan::New<String>("pts").ToLocalChecked(), Nan::Null());
info.GetReturnValue().Set(object);
break;
}
default:
info.GetReturnValue().Set(Nan::Null());
}
}
NAN_METHOD(GNEncoder::Position) {
Nan::HandleScope scope;
GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
GrooveEncoder *encoder = gn_encoder->encoder;
GroovePlaylistItem *item;
double pos;
groove_encoder_position(encoder, &item, &pos);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
if (item) {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
} else {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(obj);
}
================================================
FILE: src/encoder.h
================================================
#ifndef GN_ENCODER_H
#define GN_ENCODER_H
#include <node.h>
#include <nan.h>
#include <groove/encoder.h>
class GNEncoder : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GrooveEncoder *encoder);
static NAN_METHOD(Create);
struct EventContext {
uv_thread_t event_thread;
uv_async_t event_async;
uv_cond_t cond;
uv_mutex_t mutex;
GrooveEncoder *encoder;
Nan::Callback *event_cb;
bool emit_buffer_ok;
};
GrooveEncoder *encoder;
EventContext *event_context;
private:
GNEncoder();
~GNEncoder();
static NAN_METHOD(New);
static NAN_GETTER(GetActualAudioFormat);
static NAN_METHOD(Attach);
static NAN_METHOD(Detach);
static NAN_METHOD(GetBuffer);
static NAN_METHOD(Position);
};
#endif
================================================
FILE: src/file.cc
================================================
#include <node.h>
#include "file.h"
#include "groove.h"
using namespace v8;
GNFile::GNFile() {};
GNFile::~GNFile() {};
static Nan::Persistent<v8::Function> constructor;
void GNFile::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GrooveFile").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("filename").ToLocalChecked(), GetFilename);
Nan::SetAccessor(proto, Nan::New<String>("dirty").ToLocalChecked(), GetDirty);
Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
// Methods
Nan::SetPrototypeMethod(tpl, "close", Close);
Nan::SetPrototypeMethod(tpl, "getMetadata", GetMetadata);
Nan::SetPrototypeMethod(tpl, "setMetadata", SetMetadata);
Nan::SetPrototypeMethod(tpl, "metadata", Metadata);
Nan::SetPrototypeMethod(tpl, "shortNames", ShortNames);
Nan::SetPrototypeMethod(tpl, "save", Save);
Nan::SetPrototypeMethod(tpl, "duration", Duration);
Nan::SetPrototypeMethod(tpl, "overrideDuration", OverrideDuration);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNFile::New) {
Nan::HandleScope scope;
assert(info.IsConstructCall());
GNFile *obj = new GNFile();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNFile::NewInstance(GrooveFile *file) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(instance);
gn_file->file = file;
return scope.Escape(instance);
}
NAN_GETTER(GNFile::GetDirty) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
info.GetReturnValue().Set(Nan::New<Boolean>(gn_file->file->dirty));
}
NAN_GETTER(GNFile::GetId) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
char buf[64];
snprintf(buf, sizeof(buf), "%p", gn_file->file);
info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
}
NAN_GETTER(GNFile::GetFilename) {
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
info.GetReturnValue().Set(Nan::New<String>(gn_file->file->filename).ToLocalChecked());
}
NAN_METHOD(GNFile::GetMetadata) {
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
if (info.Length() < 1 || !info[0]->IsString()) {
Nan::ThrowTypeError("Expected string arg[0]");
return;
}
int flags = 0;
if (info.Length() >= 2) {
if (!info[1]->IsNumber()) {
Nan::ThrowTypeError("Expected number arg[1]");
return;
}
flags = (int)info[1]->NumberValue();
}
String::Utf8Value key_str(info[0]->ToString());
GrooveTag *tag = groove_file_metadata_get(gn_file->file, *key_str, NULL, flags);
if (tag) {
info.GetReturnValue().Set(Nan::New<String>(groove_tag_value(tag)).ToLocalChecked());
return;
}
info.GetReturnValue().Set(Nan::Null());
}
NAN_METHOD(GNFile::SetMetadata) {
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
if (info.Length() < 1 || !info[0]->IsString()) {
Nan::ThrowTypeError("Expected string arg[0]");
return;
}
if (info.Length() < 2 || !info[0]->IsString()) {
Nan::ThrowTypeError("Expected string arg[1]");
return;
}
int flags = 0;
if (info.Length() >= 3) {
if (!info[2]->IsNumber()) {
Nan::ThrowTypeError("Expected number arg[2]");
return;
}
flags = (int)info[2]->NumberValue();
}
String::Utf8Value key_str(info[0]->ToString());
String::Utf8Value val_str(info[1]->ToString());
int err = groove_file_metadata_set(gn_file->file, *key_str, *val_str, flags);
if (err < 0) {
Nan::ThrowTypeError("set metadata failed");
return;
}
return;
}
NAN_METHOD(GNFile::OverrideDuration) {
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
if (info.Length() < 1 || !info[0]->IsNumber()) {
Nan::ThrowTypeError("Expected number arg[0]");
return;
}
double duration = info[0]->NumberValue();
gn_file->file->override_duration = duration;
}
NAN_METHOD(GNFile::Metadata) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
Local<Object> metadata = Nan::New<Object>();
GrooveTag *tag = NULL;
while ((tag = groove_file_metadata_get(gn_file->file, "", tag, 0))) {
Nan::Set(metadata, Nan::New<String>(groove_tag_key(tag)).ToLocalChecked(),
Nan::New<String>(groove_tag_value(tag)).ToLocalChecked());
}
info.GetReturnValue().Set(metadata);
}
NAN_METHOD(GNFile::ShortNames) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
info.GetReturnValue().Set(Nan::New<String>(groove_file_short_names(gn_file->file)).ToLocalChecked());
}
NAN_METHOD(GNFile::Duration) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(groove_file_duration(gn_file->file)));
}
class CloseWorker : public Nan::AsyncWorker {
public:
CloseWorker(Nan::Callback *callback, GrooveFile *file) : Nan::AsyncWorker(callback) {
this->file = file;
}
~CloseWorker() {}
void Execute() {
if (file) {
groove_file_destroy(file);
} else {
SetErrorMessage("file already closed");
}
}
GrooveFile *file;
};
NAN_METHOD(GNFile::Close) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
AsyncQueueWorker(new CloseWorker(callback, gn_file->file));
gn_file->file = NULL;
}
class OpenWorker : public Nan::AsyncWorker {
public:
OpenWorker(Nan::Callback *callback, String::Utf8Value *filename) : Nan::AsyncWorker(callback) {
this->filename = filename;
}
~OpenWorker() {
delete filename;
}
void Execute() {
file = groove_file_create(get_groove());
if (!file) {
SetErrorMessage(groove_strerror(GrooveErrorNoMem));
return;
}
int err;
if ((err = groove_file_open(file, **filename, **filename))) {
SetErrorMessage(groove_strerror(err));
return;
}
}
void HandleOKCallback() {
Nan::HandleScope scope;
Local<Value> argv[] = {Nan::Null(), GNFile::NewInstance(file)};
callback->Call(2, argv);
}
GrooveFile *file;
String::Utf8Value *filename;
};
NAN_METHOD(GNFile::Open) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsString()) {
Nan::ThrowTypeError("Expected string arg[0]");
return;
}
if (info.Length() < 2 || !info[1]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[1]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
String::Utf8Value *filename = new String::Utf8Value(info[0]->ToString());
AsyncQueueWorker(new OpenWorker(callback, filename));
}
class SaveWorker : public Nan::AsyncWorker {
public:
SaveWorker(Nan::Callback *callback, GrooveFile *file) : Nan::AsyncWorker(callback) {
this->file = file;
}
~SaveWorker() { }
void Execute() {
int err;
if ((err = groove_file_save(file))) {
SetErrorMessage(groove_strerror(err));
return;
}
}
GrooveFile *file;
};
NAN_METHOD(GNFile::Save) {
Nan::HandleScope scope;
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
AsyncQueueWorker(new SaveWorker(callback, gn_file->file));
}
================================================
FILE: src/file.h
================================================
#ifndef GN_FILE_H
#define GN_FILE_H
#include <node.h>
#include <nan.h>
#include <groove/groove.h>
class GNFile : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GrooveFile *file);
static NAN_METHOD(Open);
GrooveFile *file;
private:
GNFile();
~GNFile();
static NAN_METHOD(New);
static NAN_GETTER(GetDirty);
static NAN_GETTER(GetId);
static NAN_GETTER(GetFilename);
static NAN_METHOD(Close);
static NAN_METHOD(Duration);
static NAN_METHOD(GetMetadata);
static NAN_METHOD(SetMetadata);
static NAN_METHOD(Metadata);
static NAN_METHOD(ShortNames);
static NAN_METHOD(Save);
static NAN_METHOD(OverrideDuration);
};
#endif
================================================
FILE: src/fingerprinter.cc
================================================
#include "fingerprinter.h"
#include "playlist_item.h"
#include "playlist.h"
#include "groove.h"
using namespace v8;
GNFingerprinter::GNFingerprinter() {};
GNFingerprinter::~GNFingerprinter() {
groove_fingerprinter_destroy(printer);
delete event_context->event_cb;
delete event_context;
};
static Nan::Persistent<v8::Function> constructor;
void GNFingerprinter::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GrooveFingerprinter").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(2);
// Methods
Nan::SetPrototypeMethod(tpl, "attach", Attach);
Nan::SetPrototypeMethod(tpl, "detach", Detach);
Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo);
Nan::SetPrototypeMethod(tpl, "position", Position);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNFingerprinter::New) {
Nan::HandleScope scope;
GNFingerprinter *obj = new GNFingerprinter();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNFingerprinter::NewInstance(GrooveFingerprinter *printer) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);
gn_printer->printer = printer;
return scope.Escape(instance);
}
NAN_METHOD(GNFingerprinter::Create) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
GrooveFingerprinter *printer = groove_fingerprinter_create(get_groove());
if (!printer) {
Nan::ThrowTypeError("unable to create fingerprinter");
return;
}
// set properties on the instance with default values from
// GrooveFingerprinter struct
Local<Object> instance = GNFingerprinter::NewInstance(printer)->ToObject();
GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);
EventContext *context = new EventContext;
gn_printer->event_context = context;
context->event_cb = new Nan::Callback(info[0].As<Function>());
context->printer = printer;
Nan::Set(instance, Nan::New<String>("infoQueueSize").ToLocalChecked(),
Nan::New<Number>(printer->info_queue_size));
info.GetReturnValue().Set(instance);
}
NAN_METHOD(GNFingerprinter::Position) {
Nan::HandleScope scope;
GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());
GrooveFingerprinter *printer = gn_printer->printer;
GroovePlaylistItem *item;
double pos;
groove_fingerprinter_position(printer, &item, &pos);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
if (item) {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
} else {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(obj);
}
NAN_METHOD(GNFingerprinter::GetInfo) {
Nan::HandleScope scope;
GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());
GrooveFingerprinter *printer = gn_printer->printer;
GrooveFingerprinterInfo print_info;
if (groove_fingerprinter_info_get(printer, &print_info, 0) == 1) {
Local<Object> object = Nan::New<Object>();
if (print_info.fingerprint) {
Local<Array> int_list = Nan::New<Array>();
for (int i = 0; i < print_info.fingerprint_size; i += 1) {
Nan::Set(int_list, Nan::New<Number>(i), Nan::New<Number>(print_info.fingerprint[i]));
}
Nan::Set(object, Nan::New<String>("fingerprint").ToLocalChecked(), int_list);
} else {
Nan::Set(object, Nan::New<String>("fingerprint").ToLocalChecked(), Nan::Null());
}
Nan::Set(object, Nan::New<String>("duration").ToLocalChecked(), Nan::New<Number>(print_info.duration));
if (print_info.item) {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(print_info.item));
} else {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
groove_fingerprinter_free_info(&print_info);
info.GetReturnValue().Set(object);
} else {
info.GetReturnValue().Set(Nan::Null());
}
}
static void PrinterEventAsyncCb(uv_async_t *handle) {
Nan::HandleScope scope;
GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(handle->data);
// call callback signaling that there is info ready
const unsigned argc = 1;
Local<Value> argv[argc];
argv[0] = Nan::Null();
TryCatch try_catch;
context->event_cb->Call(argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
uv_mutex_lock(&context->mutex);
uv_cond_signal(&context->cond);
uv_mutex_unlock(&context->mutex);
}
static void PrinterEventThreadEntry(void *arg) {
GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(arg);
while (groove_fingerprinter_info_peek(context->printer, 1) > 0) {
uv_mutex_lock(&context->mutex);
uv_async_send(&context->event_async);
uv_cond_wait(&context->cond, &context->mutex);
uv_mutex_unlock(&context->mutex);
}
}
class PrinterAttachWorker : public Nan::AsyncWorker {
public:
PrinterAttachWorker(Nan::Callback *callback, GrooveFingerprinter *printer, GroovePlaylist *playlist,
GNFingerprinter::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->printer = printer;
this->playlist = playlist;
this->event_context = event_context;
}
~PrinterAttachWorker() {}
void Execute() {
int err;
if ((err = groove_fingerprinter_attach(printer, playlist))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_init(&event_context->cond);
uv_mutex_init(&event_context->mutex);
event_context->event_async.data = event_context;
uv_async_init(uv_default_loop(), &event_context->event_async, PrinterEventAsyncCb);
uv_thread_create(&event_context->event_thread, PrinterEventThreadEntry, event_context);
}
GrooveFingerprinter *printer;
GroovePlaylist *playlist;
GNFingerprinter::EventContext *event_context;
};
NAN_METHOD(GNFingerprinter::Attach) {
Nan::HandleScope scope;
GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("Expected object arg[0]");
return;
}
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
if (info.Length() < 2 || !info[1]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[1]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
Local<Object> instance = info.This();
GrooveFingerprinter *printer = gn_printer->printer;
// copy the properties from our instance to the player
printer->info_queue_size = (int)instance->Get(Nan::New<String>("infoQueueSize").ToLocalChecked())->NumberValue();
AsyncQueueWorker(new PrinterAttachWorker(callback, printer, gn_playlist->playlist, gn_printer->event_context));
}
class PrinterDetachWorker : public Nan::AsyncWorker {
public:
PrinterDetachWorker(Nan::Callback *callback, GrooveFingerprinter *printer,
GNFingerprinter::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->printer = printer;
this->event_context = event_context;
}
~PrinterDetachWorker() {}
void Execute() {
int err;
if ((err = groove_fingerprinter_detach(printer))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_signal(&event_context->cond);
uv_thread_join(&event_context->event_thread);
uv_cond_destroy(&event_context->cond);
uv_mutex_destroy(&event_context->mutex);
uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);
}
GrooveFingerprinter *printer;
GNFingerprinter::EventContext *event_context;
};
NAN_METHOD(GNFingerprinter::Detach) {
Nan::HandleScope scope;
GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
AsyncQueueWorker(new PrinterDetachWorker(callback, gn_printer->printer, gn_printer->event_context));
}
NAN_METHOD(GNFingerprinter::Encode) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsArray()) {
Nan::ThrowTypeError("Expected Array arg[0]");
return;
}
Local<Array> int_list = Local<Array>::Cast(info[0]);
int len = int_list->Length();
uint32_t *raw_fingerprint = new uint32_t[len];
for (int i = 0; i < len; i += 1) {
double val = int_list->Get(Nan::New<Number>(i))->NumberValue();
raw_fingerprint[i] = (uint32_t)val;
}
char *fingerprint;
groove_fingerprinter_encode(raw_fingerprint, len, &fingerprint);
delete[] raw_fingerprint;
Local<String> js_fingerprint = Nan::New<String>(fingerprint).ToLocalChecked();
groove_fingerprinter_dealloc(fingerprint);
info.GetReturnValue().Set(js_fingerprint);
}
NAN_METHOD(GNFingerprinter::Decode) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsString()) {
Nan::ThrowTypeError("Expected String arg[0]");
return;
}
String::Utf8Value utf8fingerprint(info[0]->ToString());
char *fingerprint = *utf8fingerprint;
uint32_t *raw_fingerprint;
int raw_fingerprint_len;
groove_fingerprinter_decode(fingerprint, &raw_fingerprint, &raw_fingerprint_len);
Local<Array> int_list = Nan::New<Array>();
for (int i = 0; i < raw_fingerprint_len; i += 1) {
Nan::Set(int_list, Nan::New<Number>(i), Nan::New<Number>(raw_fingerprint[i]));
}
groove_fingerprinter_dealloc(raw_fingerprint);
info.GetReturnValue().Set(int_list);
}
================================================
FILE: src/fingerprinter.h
================================================
#ifndef GN_FINGERPRINTER_H
#define GN_FINGERPRINTER_H
#include <node.h>
#include <nan.h>
#include <groove/fingerprinter.h>
class GNFingerprinter : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GrooveFingerprinter *printer);
static NAN_METHOD(Create);
static NAN_METHOD(Encode);
static NAN_METHOD(Decode);
struct EventContext {
uv_thread_t event_thread;
uv_async_t event_async;
uv_cond_t cond;
uv_mutex_t mutex;
GrooveFingerprinter *printer;
Nan::Callback *event_cb;
};
EventContext *event_context;
GrooveFingerprinter *printer;
private:
GNFingerprinter();
~GNFingerprinter();
static NAN_METHOD(New);
static NAN_METHOD(Attach);
static NAN_METHOD(Detach);
static NAN_METHOD(GetInfo);
static NAN_METHOD(Position);
};
#endif
================================================
FILE: src/groove.cc
================================================
#include <node.h>
#include <nan.h>
#include <cstdlib>
#include "groove.h"
#include "file.h"
#include "player.h"
#include "playlist.h"
#include "playlist_item.h"
#include "loudness_detector.h"
#include "fingerprinter.h"
#include "waveform_builder.h"
#include "encoder.h"
#include "device.h"
using namespace v8;
static SoundIo *soundio = NULL;
static Groove *groove = NULL;
Groove *get_groove() {
return groove;
}
NAN_METHOD(SetLogging) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsNumber()) {
Nan::ThrowTypeError("Expected 1 number argument");
return;
}
groove_set_logging(info[0]->NumberValue());
}
NAN_METHOD(ConnectSoundBackend) {
SoundIoBackend backend = SoundIoBackendNone;
if (info.Length() == 1) {
if (!info[0]->IsNumber()) {
Nan::ThrowTypeError("Expected 0 or 1 args");
return;
}
backend = (SoundIoBackend)(int)info[0]->NumberValue();
} else if (info.Length() > 1) {
Nan::ThrowTypeError("Expected 0 or 1 args");
return;
}
if (soundio->current_backend != SoundIoBackendNone)
soundio_disconnect(soundio);
int err = (backend == SoundIoBackendNone) ?
soundio_connect(soundio) : soundio_connect_backend(soundio, backend);
if (err) {
Nan::ThrowError(soundio_strerror(err));
return;
}
}
NAN_METHOD(DisconnectSoundBackend) {
if (soundio->current_backend != SoundIoBackendNone)
soundio_disconnect(soundio);
}
NAN_METHOD(GetDevices) {
Nan::HandleScope scope;
if (soundio->current_backend == SoundIoBackendNone) {
Nan::ThrowError("no backend connected");
return;
}
soundio_flush_events(soundio);
int output_count = soundio_output_device_count(soundio);
int default_output = soundio_default_output_device_index(soundio);
Local<Array> deviceList = Nan::New<Array>();
for (int i = 0; i < output_count; i += 1) {
struct SoundIoDevice *device = soundio_get_output_device(soundio, i);
Local<Value> deviceObject = GNDevice::NewInstance(device);
Nan::Set(deviceList, Nan::New<Number>(i), deviceObject);
}
Local<Object> ret_value = Nan::New<Object>();
Nan::Set(ret_value, Nan::New<String>("list").ToLocalChecked(), deviceList);
Nan::Set(ret_value, Nan::New<String>("defaultIndex").ToLocalChecked(), Nan::New<Number>(default_output));
info.GetReturnValue().Set(ret_value);
}
NAN_METHOD(GetVersion) {
Nan::HandleScope scope;
Local<Object> version = Nan::New<Object>();
Nan::Set(version, Nan::New<String>("major").ToLocalChecked(), Nan::New<Number>(groove_version_major()));
Nan::Set(version, Nan::New<String>("minor").ToLocalChecked(), Nan::New<Number>(groove_version_minor()));
Nan::Set(version, Nan::New<String>("patch").ToLocalChecked(), Nan::New<Number>(groove_version_patch()));
info.GetReturnValue().Set(version);
}
template <typename target_t>
static void SetProperty(target_t obj, const char* name, double n) {
Nan::Set(obj, Nan::New<String>(name).ToLocalChecked(), Nan::New<Number>(n));
}
template <typename target_t, typename FNPTR>
static void SetMethod(target_t obj, const char* name, FNPTR fn) {
Nan::Set(obj, Nan::New<String>(name).ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(fn)).ToLocalChecked());
}
static void cleanup(void) {
groove_destroy(groove);
soundio_destroy(soundio);
}
NAN_MODULE_INIT(Initialize) {
int err;
soundio = soundio_create();
if (!soundio) {
fprintf(stderr, "unable to initialize libsoundio: out of memory\n");
abort();
}
if ((err = groove_create(&groove))) {
fprintf(stderr, "unable to initialize libgroove: %s\n", groove_strerror(err));
abort();
}
atexit(cleanup);
GNFile::Init();
GNPlayer::Init();
GNPlaylist::Init();
GNPlaylistItem::Init();
GNLoudnessDetector::Init();
GNEncoder::Init();
GNFingerprinter::Init();
GNDevice::Init();
GNWaveformBuilder::Init();
SetProperty(target, "LOG_QUIET", GROOVE_LOG_QUIET);
SetProperty(target, "LOG_ERROR", GROOVE_LOG_ERROR);
SetProperty(target, "LOG_WARNING", GROOVE_LOG_WARNING);
SetProperty(target, "LOG_INFO", GROOVE_LOG_INFO);
SetProperty(target, "TAG_MATCH_CASE", GROOVE_TAG_MATCH_CASE);
SetProperty(target, "TAG_DONT_OVERWRITE", GROOVE_TAG_DONT_OVERWRITE);
SetProperty(target, "TAG_APPEND", GROOVE_TAG_APPEND);
SetProperty(target, "EVERY_SINK_FULL", GrooveFillModeEverySinkFull);
SetProperty(target, "ANY_SINK_FULL", GrooveFillModeAnySinkFull);
SetProperty(target, "_EVENT_NOWPLAYING", GROOVE_EVENT_NOWPLAYING);
SetProperty(target, "_EVENT_BUFFERUNDERRUN", GROOVE_EVENT_BUFFERUNDERRUN);
SetProperty(target, "_EVENT_DEVICE_CLOSED", GROOVE_EVENT_DEVICE_CLOSED);
SetProperty(target, "_EVENT_DEVICE_OPENED", GROOVE_EVENT_DEVICE_OPENED);
SetProperty(target, "_EVENT_DEVICE_OPEN_ERROR", GROOVE_EVENT_DEVICE_OPEN_ERROR);
SetProperty(target, "_EVENT_END_OF_PLAYLIST", GROOVE_EVENT_END_OF_PLAYLIST);
SetProperty(target, "_EVENT_WAKEUP", GROOVE_EVENT_WAKEUP);
SetProperty(target, "BACKEND_JACK", SoundIoBackendJack);
SetProperty(target, "BACKEND_PULSEAUDIO", SoundIoBackendPulseAudio);
SetProperty(target, "BACKEND_ALSA", SoundIoBackendAlsa);
SetProperty(target, "BACKEND_COREAUDIO", SoundIoBackendCoreAudio);
SetProperty(target, "BACKEND_WASAPI", SoundIoBackendWasapi);
SetProperty(target, "BACKEND_DUMMY", SoundIoBackendDummy);
SetMethod(target, "setLogging", SetLogging);
SetMethod(target, "getDevices", GetDevices);
SetMethod(target, "connectSoundBackend", ConnectSoundBackend);
SetMethod(target, "disconnectSoundBackend", DisconnectSoundBackend);
SetMethod(target, "getVersion", GetVersion);
SetMethod(target, "open", GNFile::Open);
SetMethod(target, "createPlayer", GNPlayer::Create);
SetMethod(target, "createPlaylist", GNPlaylist::Create);
SetMethod(target, "createLoudnessDetector", GNLoudnessDetector::Create);
SetMethod(target, "createEncoder", GNEncoder::Create);
SetMethod(target, "createFingerprinter", GNFingerprinter::Create);
SetMethod(target, "createWaveformBuilder", GNWaveformBuilder::Create);
SetMethod(target, "encodeFingerprint", GNFingerprinter::Encode);
SetMethod(target, "decodeFingerprint", GNFingerprinter::Decode);
}
NODE_MODULE(groove, Initialize)
================================================
FILE: src/groove.h
================================================
#ifndef GN_GROOVE_H
#define GN_GROOVE_H
#include <groove/groove.h>
Groove *get_groove();
#endif
================================================
FILE: src/loudness_detector.cc
================================================
#include "loudness_detector.h"
#include "playlist_item.h"
#include "playlist.h"
#include "groove.h"
using namespace v8;
GNLoudnessDetector::GNLoudnessDetector() {};
GNLoudnessDetector::~GNLoudnessDetector() {
groove_loudness_detector_destroy(detector);
delete event_context->event_cb;
delete event_context;
};
static Nan::Persistent<v8::Function> constructor;
void GNLoudnessDetector::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GrooveLoudnessDetector").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
// Methods
Nan::SetPrototypeMethod(tpl, "attach", Attach);
Nan::SetPrototypeMethod(tpl, "detach", Detach);
Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo);
Nan::SetPrototypeMethod(tpl, "position", Position);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNLoudnessDetector::New) {
Nan::HandleScope scope;
GNLoudnessDetector *obj = new GNLoudnessDetector();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNLoudnessDetector::NewInstance(GrooveLoudnessDetector *detector) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(instance);
gn_detector->detector = detector;
return scope.Escape(instance);
}
NAN_METHOD(GNLoudnessDetector::Create) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
GrooveLoudnessDetector *detector = groove_loudness_detector_create(get_groove());
if (!detector) {
Nan::ThrowTypeError("unable to create loudness detector");
return;
}
// set properties on the instance with default values from
// GrooveLoudnessDetector struct
Local<Object> instance = GNLoudnessDetector::NewInstance(detector)->ToObject();
GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(instance);
EventContext *context = new EventContext;
gn_detector->event_context = context;
context->event_cb = new Nan::Callback(info[0].As<Function>());
context->detector = detector;
Nan::Set(instance, Nan::New<String>("infoQueueSize").ToLocalChecked(),
Nan::New<Number>(detector->info_queue_size));
Nan::Set(instance, Nan::New<String>("disableAlbum").ToLocalChecked(),
Nan::New<Boolean>(detector->disable_album));
info.GetReturnValue().Set(instance);
}
NAN_METHOD(GNLoudnessDetector::Position) {
Nan::HandleScope scope;
GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
GrooveLoudnessDetector *detector = gn_detector->detector;
GroovePlaylistItem *item;
double pos;
groove_loudness_detector_position(detector, &item, &pos);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
if (item) {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
} else {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(obj);
}
NAN_METHOD(GNLoudnessDetector::GetInfo) {
Nan::HandleScope scope;
GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
GrooveLoudnessDetector *detector = gn_detector->detector;
GrooveLoudnessDetectorInfo loudness_info;
if (groove_loudness_detector_info_get(detector, &loudness_info, 0) == 1) {
Local<Object> object = Nan::New<Object>();
Nan::Set(object, Nan::New<String>("loudness").ToLocalChecked(), Nan::New<Number>(loudness_info.loudness));
Nan::Set(object, Nan::New<String>("peak").ToLocalChecked(), Nan::New<Number>(loudness_info.peak));
Nan::Set(object, Nan::New<String>("duration").ToLocalChecked(), Nan::New<Number>(loudness_info.duration));
if (loudness_info.item) {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(loudness_info.item));
} else {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(object);
} else {
info.GetReturnValue().Set(Nan::Null());
}
}
static void EventAsyncCb(uv_async_t *handle) {
Nan::HandleScope scope;
GNLoudnessDetector::EventContext *context = reinterpret_cast<GNLoudnessDetector::EventContext *>(handle->data);
// call callback signaling that there is info ready
const unsigned argc = 1;
Local<Value> argv[argc];
argv[0] = Nan::Null();
TryCatch try_catch;
context->event_cb->Call(argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
uv_mutex_lock(&context->mutex);
uv_cond_signal(&context->cond);
uv_mutex_unlock(&context->mutex);
}
static void EventThreadEntry(void *arg) {
GNLoudnessDetector::EventContext *context = reinterpret_cast<GNLoudnessDetector::EventContext *>(arg);
while (groove_loudness_detector_info_peek(context->detector, 1) > 0) {
uv_mutex_lock(&context->mutex);
uv_async_send(&context->event_async);
uv_cond_wait(&context->cond, &context->mutex);
uv_mutex_unlock(&context->mutex);
}
}
class DetectorAttachWorker : public Nan::AsyncWorker {
public:
DetectorAttachWorker(Nan::Callback *callback, GrooveLoudnessDetector *detector, GroovePlaylist *playlist,
GNLoudnessDetector::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->detector = detector;
this->playlist = playlist;
this->event_context = event_context;
}
~DetectorAttachWorker() {}
void Execute() {
int err;
if ((err = groove_loudness_detector_attach(detector, playlist))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_init(&event_context->cond);
uv_mutex_init(&event_context->mutex);
event_context->event_async.data = event_context;
uv_async_init(uv_default_loop(), &event_context->event_async, EventAsyncCb);
uv_thread_create(&event_context->event_thread, EventThreadEntry, event_context);
}
GrooveLoudnessDetector *detector;
GroovePlaylist *playlist;
GNLoudnessDetector::EventContext *event_context;
};
NAN_METHOD(GNLoudnessDetector::Attach) {
Nan::HandleScope scope;
GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("Expected object arg[0]");
return;
}
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
if (info.Length() < 2 || !info[1]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[1]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
Local<Object> instance = info.This();
GrooveLoudnessDetector *detector = gn_detector->detector;
// copy the properties from our instance to the player
detector->info_queue_size = (int)instance->Get(Nan::New<String>("infoQueueSize").ToLocalChecked())->NumberValue();
detector->disable_album = (int)instance->Get(Nan::New<String>("disableAlbum").ToLocalChecked())->BooleanValue();
AsyncQueueWorker(new DetectorAttachWorker(callback, detector, gn_playlist->playlist, gn_detector->event_context));
}
class DetectorDetachWorker : public Nan::AsyncWorker {
public:
DetectorDetachWorker(Nan::Callback *callback, GrooveLoudnessDetector *detector,
GNLoudnessDetector::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->detector = detector;
this->event_context = event_context;
}
~DetectorDetachWorker() {}
void Execute() {
int err;
if ((err = groove_loudness_detector_detach(detector))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_signal(&event_context->cond);
uv_thread_join(&event_context->event_thread);
uv_cond_destroy(&event_context->cond);
uv_mutex_destroy(&event_context->mutex);
uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);
}
GrooveLoudnessDetector *detector;
GNLoudnessDetector::EventContext *event_context;
};
NAN_METHOD(GNLoudnessDetector::Detach) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
GrooveLoudnessDetector *detector = gn_detector->detector;
AsyncQueueWorker(new DetectorDetachWorker(callback, detector, gn_detector->event_context));
}
================================================
FILE: src/loudness_detector.h
================================================
#ifndef GN_LOUDNESS_DETECTOR_H
#define GN_LOUDNESS_DETECTOR_H
#include <node.h>
#include <nan.h>
#include <groove/loudness.h>
class GNLoudnessDetector : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GrooveLoudnessDetector *detector);
static NAN_METHOD(Create);
struct EventContext {
uv_thread_t event_thread;
uv_async_t event_async;
uv_cond_t cond;
uv_mutex_t mutex;
GrooveLoudnessDetector *detector;
Nan::Callback *event_cb;
};
EventContext *event_context;
GrooveLoudnessDetector *detector;
private:
GNLoudnessDetector();
~GNLoudnessDetector();
static NAN_METHOD(New);
static NAN_METHOD(Attach);
static NAN_METHOD(Detach);
static NAN_METHOD(GetInfo);
static NAN_METHOD(Position);
};
#endif
================================================
FILE: src/player.cc
================================================
#include "player.h"
#include "playlist.h"
#include "playlist_item.h"
#include "device.h"
#include "groove.h"
using namespace v8;
GNPlayer::GNPlayer() {};
GNPlayer::~GNPlayer() {
groove_player_destroy(player);
delete event_context->event_cb;
delete event_context;
};
static Nan::Persistent<v8::Function> constructor;
void GNPlayer::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GroovePlayer").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(2);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
Nan::SetAccessor(proto, Nan::New<String>("playlist").ToLocalChecked(), GetPlaylist);
// Methods
Nan::SetPrototypeMethod(tpl, "attach", Attach);
Nan::SetPrototypeMethod(tpl, "detach", Detach);
Nan::SetPrototypeMethod(tpl, "position", Position);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNPlayer::New) {
Nan::HandleScope scope;
GNPlayer *obj = new GNPlayer();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNPlayer::NewInstance(GroovePlayer *player) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);
gn_player->player = player;
return scope.Escape(instance);
}
NAN_GETTER(GNPlayer::GetId) {
Nan::HandleScope scope;
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
char buf[64];
snprintf(buf, sizeof(buf), "%p", gn_player->player);
info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
}
NAN_GETTER(GNPlayer::GetPlaylist) {
Nan::HandleScope scope;
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
GroovePlaylist *playlist = gn_player->player->playlist;
if (playlist) {
Local<Value> tmp = GNPlaylist::NewInstance(playlist);
info.GetReturnValue().Set(tmp);
} else {
info.GetReturnValue().Set(Nan::Null());
}
}
NAN_METHOD(GNPlayer::Position) {
Nan::HandleScope scope;
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
GroovePlaylistItem *item;
double pos;
groove_player_position(gn_player->player, &item, &pos);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
if (item) {
Local<Value> tmp = GNPlaylistItem::NewInstance(item);
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), tmp);
} else {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(obj);
}
static void PlayerEventAsyncCb(uv_async_t *handle) {
Nan::HandleScope scope;
GNPlayer::EventContext *context = reinterpret_cast<GNPlayer::EventContext *>(handle->data);
// flush events
GroovePlayerEvent event;
const unsigned argc = 1;
Local<Value> argv[argc];
while (groove_player_event_get(context->player, &event, 0) > 0) {
argv[0] = Nan::New<Number>(event.type);
TryCatch try_catch;
context->event_cb->Call(argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
uv_mutex_lock(&context->mutex);
uv_cond_signal(&context->cond);
uv_mutex_unlock(&context->mutex);
}
static void PlayerEventThreadEntry(void *arg) {
GNPlayer::EventContext *context = reinterpret_cast<GNPlayer::EventContext *>(arg);
while (groove_player_event_peek(context->player, 1) > 0) {
uv_mutex_lock(&context->mutex);
uv_async_send(&context->event_async);
uv_cond_wait(&context->cond, &context->mutex);
uv_mutex_unlock(&context->mutex);
}
}
class PlayerAttachWorker : public Nan::AsyncWorker {
public:
PlayerAttachWorker(Nan::Callback *callback, GroovePlayer *player, GroovePlaylist *playlist,
GNPlayer::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->player = player;
this->playlist = playlist;
this->event_context = event_context;
}
~PlayerAttachWorker() {}
void Execute() {
int err;
if ((err = groove_player_attach(player, playlist))) {
SetErrorMessage(groove_strerror(err));
return;
}
GNPlayer::EventContext *context = event_context;
uv_cond_init(&context->cond);
uv_mutex_init(&context->mutex);
context->event_async.data = context;
uv_async_init(uv_default_loop(), &context->event_async, PlayerEventAsyncCb);
uv_thread_create(&context->event_thread, PlayerEventThreadEntry, context);
}
GroovePlayer *player;
GroovePlaylist *playlist;
GNPlayer::EventContext *event_context;
};
NAN_METHOD(GNPlayer::Create) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
GroovePlayer *player = groove_player_create(get_groove());
if (!player) {
Nan::ThrowTypeError("unable to create player");
return;
}
Local<Object> instance = NewInstance(player)->ToObject();
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);
EventContext *context = new EventContext;
gn_player->event_context = context;
context->event_cb = new Nan::Callback(info[0].As<Function>());
context->player = player;
Nan::Set(instance, Nan::New<String>("device").ToLocalChecked(), Nan::Null());
info.GetReturnValue().Set(instance);
}
NAN_METHOD(GNPlayer::Attach) {
Nan::HandleScope scope;
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("Expected object arg[0]");
return;
}
if (info.Length() < 2 || !info[1]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[1]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
Local<Object> instance = info.This();
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
GroovePlayer *player = gn_player->player;
// copy the properties from our instance to the player
Local<Value> deviceObject = instance->Get(Nan::New<String>("device").ToLocalChecked());
if (!deviceObject->IsObject() || deviceObject->IsUndefined() || deviceObject->IsNull()) {
Nan::ThrowTypeError("Expected player.device to be an object");
return;
}
GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(deviceObject->ToObject());
player->device = gn_device->device;
AsyncQueueWorker(new PlayerAttachWorker(callback, player, gn_playlist->playlist, gn_player->event_context));
}
class PlayerDetachWorker : public Nan::AsyncWorker {
public:
PlayerDetachWorker(Nan::Callback *callback, GroovePlayer *player,
GNPlayer::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->player = player;
this->event_context = event_context;
}
~PlayerDetachWorker() {}
void Execute() {
int err;
if ((err = groove_player_detach(player))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_signal(&event_context->cond);
uv_thread_join(&event_context->event_thread);
uv_cond_destroy(&event_context->cond);
uv_mutex_destroy(&event_context->mutex);
uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);
}
GroovePlayer *player;
GNPlayer::EventContext *event_context;
};
NAN_METHOD(GNPlayer::Detach) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
GroovePlayer *player = gn_player->player;
AsyncQueueWorker(new PlayerDetachWorker(callback, player, gn_player->event_context));
}
================================================
FILE: src/player.h
================================================
#ifndef GN_PLAYER_H
#define GN_PLAYER_H
#include <node.h>
#include <nan.h>
#include <groove/player.h>
class GNPlayer : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GroovePlayer *player);
static NAN_METHOD(Create);
struct EventContext {
uv_thread_t event_thread;
uv_async_t event_async;
uv_cond_t cond;
uv_mutex_t mutex;
GroovePlayer *player;
Nan::Callback *event_cb;
};
GroovePlayer *player;
EventContext *event_context;
private:
GNPlayer();
~GNPlayer();
static NAN_METHOD(New);
static NAN_GETTER(GetId);
static NAN_GETTER(GetPlaylist);
static NAN_METHOD(Attach);
static NAN_METHOD(Detach);
static NAN_METHOD(Position);
};
#endif
================================================
FILE: src/playlist.cc
================================================
#include <node.h>
#include "playlist.h"
#include "playlist_item.h"
#include "file.h"
#include "groove.h"
using namespace v8;
GNPlaylist::GNPlaylist() { };
GNPlaylist::~GNPlaylist() { };
static Nan::Persistent<v8::Function> constructor;
void GNPlaylist::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GroovePlaylist").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
Nan::SetAccessor(proto, Nan::New<String>("gain").ToLocalChecked(), GetGain);
// Methods
Nan::SetPrototypeMethod(tpl, "destroy", Destroy);
Nan::SetPrototypeMethod(tpl, "play", Play);
Nan::SetPrototypeMethod(tpl, "items", Playlist);
Nan::SetPrototypeMethod(tpl, "pause", Pause);
Nan::SetPrototypeMethod(tpl, "seek", Seek);
Nan::SetPrototypeMethod(tpl, "insert", Insert);
Nan::SetPrototypeMethod(tpl, "remove", Remove);
Nan::SetPrototypeMethod(tpl, "position", DecodePosition);
Nan::SetPrototypeMethod(tpl, "playing", Playing);
Nan::SetPrototypeMethod(tpl, "clear", Clear);
Nan::SetPrototypeMethod(tpl, "count", Count);
Nan::SetPrototypeMethod(tpl, "setItemGainPeak", SetItemGainPeak);
Nan::SetPrototypeMethod(tpl, "setGain", SetGain);
Nan::SetPrototypeMethod(tpl, "setFillMode", SetFillMode);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNPlaylist::New) {
Nan::HandleScope scope;
assert(info.IsConstructCall());
GNPlaylist *obj = new GNPlaylist();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNPlaylist::NewInstance(GroovePlaylist *playlist) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(instance);
gn_playlist->playlist = playlist;
return scope.Escape(instance);
}
NAN_METHOD(GNPlaylist::Destroy) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
groove_playlist_destroy(gn_playlist->playlist);
gn_playlist->playlist = NULL;
}
NAN_GETTER(GNPlaylist::GetId) {
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
char buf[64];
snprintf(buf, sizeof(buf), "%p", gn_playlist->playlist);
info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
}
NAN_GETTER(GNPlaylist::GetGain) {
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(gn_playlist->playlist->gain));
}
NAN_METHOD(GNPlaylist::Play) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
groove_playlist_play(gn_playlist->playlist);
return;
}
NAN_METHOD(GNPlaylist::Playlist) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
Local<Array> playlist = Nan::New<Array>();
GroovePlaylistItem *item = gn_playlist->playlist->head;
int i = 0;
while (item) {
Nan::Set(playlist, Nan::New<Number>(i), GNPlaylistItem::NewInstance(item));
item = item->next;
i += 1;
}
info.GetReturnValue().Set(playlist);
}
NAN_METHOD(GNPlaylist::Pause) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
groove_playlist_pause(gn_playlist->playlist);
}
NAN_METHOD(GNPlaylist::Seek) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
GNPlaylistItem *gn_playlist_item =
node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());
double pos = info[1]->NumberValue();
groove_playlist_seek(gn_playlist->playlist, gn_playlist_item->playlist_item, pos);
}
NAN_METHOD(GNPlaylist::Insert) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info[0]->ToObject());
double gain = 1.0;
double peak = 1.0;
if (!info[1]->IsNull() && !info[1]->IsUndefined()) {
gain = info[1]->NumberValue();
}
if (!info[2]->IsNull() && !info[2]->IsUndefined()) {
peak = info[2]->NumberValue();
}
GroovePlaylistItem *item = NULL;
if (!info[3]->IsNull() && !info[3]->IsUndefined()) {
GNPlaylistItem *gn_pl_item =
node::ObjectWrap::Unwrap<GNPlaylistItem>(info[3]->ToObject());
item = gn_pl_item->playlist_item;
}
GroovePlaylistItem *result = groove_playlist_insert(gn_playlist->playlist,
gn_file->file, gain, peak, item);
info.GetReturnValue().Set(GNPlaylistItem::NewInstance(result));
}
NAN_METHOD(GNPlaylist::Remove) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());
groove_playlist_remove(gn_playlist->playlist, gn_pl_item->playlist_item);
}
NAN_METHOD(GNPlaylist::DecodePosition) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
GroovePlaylistItem *item;
double pos = -1.0;
groove_playlist_position(gn_playlist->playlist, &item, &pos);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
if (item) {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
} else {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(obj);
}
NAN_METHOD(GNPlaylist::Playing) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
int playing = groove_playlist_playing(gn_playlist->playlist);
info.GetReturnValue().Set(Nan::New<Boolean>(playing));
}
NAN_METHOD(GNPlaylist::Clear) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
groove_playlist_clear(gn_playlist->playlist);
}
NAN_METHOD(GNPlaylist::Count) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
int count = groove_playlist_count(gn_playlist->playlist);
info.GetReturnValue().Set(Nan::New<Number>(count));
}
NAN_METHOD(GNPlaylist::SetItemGainPeak) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());
double gain = info[1]->NumberValue();
double peak = info[2]->NumberValue();
groove_playlist_set_item_gain_peak(gn_playlist->playlist, gn_pl_item->playlist_item, gain, peak);
}
NAN_METHOD(GNPlaylist::SetGain) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
groove_playlist_set_gain(gn_playlist->playlist, info[0]->NumberValue());
}
NAN_METHOD(GNPlaylist::SetFillMode) {
Nan::HandleScope scope;
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
GrooveFillMode mode = (GrooveFillMode) info[0]->NumberValue();
groove_playlist_set_fill_mode(gn_playlist->playlist, mode);
}
NAN_METHOD(GNPlaylist::Create) {
Nan::HandleScope scope;
GroovePlaylist *playlist = groove_playlist_create(get_groove());
Local<Value> tmp = GNPlaylist::NewInstance(playlist);
info.GetReturnValue().Set(tmp);
}
================================================
FILE: src/playlist.h
================================================
#ifndef GN_PLAYLIST_H
#define GN_PLAYLIST_H
#include <node.h>
#include <nan.h>
#include <groove/groove.h>
class GNPlaylist : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GroovePlaylist *playlist);
static NAN_METHOD(Create);
GroovePlaylist *playlist;
private:
GNPlaylist();
~GNPlaylist();
static NAN_METHOD(New);
static NAN_METHOD(Destroy);
static NAN_GETTER(GetId);
static NAN_GETTER(GetGain);
static NAN_METHOD(Playlist);
static NAN_METHOD(Play);
static NAN_METHOD(Pause);
static NAN_METHOD(Seek);
static NAN_METHOD(Insert);
static NAN_METHOD(Remove);
static NAN_METHOD(Position);
static NAN_METHOD(DecodePosition);
static NAN_METHOD(Playing);
static NAN_METHOD(Clear);
static NAN_METHOD(Count);
static NAN_METHOD(SetItemGainPeak);
static NAN_METHOD(SetGain);
static NAN_METHOD(SetFillMode);
};
#endif
================================================
FILE: src/playlist_item.cc
================================================
#include "playlist_item.h"
#include "file.h"
using namespace v8;
GNPlaylistItem::GNPlaylistItem() { };
GNPlaylistItem::~GNPlaylistItem() { };
static Nan::Persistent<v8::Function> constructor;
void GNPlaylistItem::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GroovePlaylistItem").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("file").ToLocalChecked(), GetFile);
Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
Nan::SetAccessor(proto, Nan::New<String>("gain").ToLocalChecked(), GetGain);
Nan::SetAccessor(proto, Nan::New<String>("peak").ToLocalChecked(), GetPeak);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNPlaylistItem::New) {
Nan::HandleScope scope;
GNPlaylistItem *obj = new GNPlaylistItem();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNPlaylistItem::NewInstance(GroovePlaylistItem *playlist_item) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNPlaylistItem *gn_playlist_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(instance);
gn_playlist_item->playlist_item = playlist_item;
return scope.Escape(instance);
}
NAN_GETTER(GNPlaylistItem::GetFile) {
GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
Local<Value> tmp = GNFile::NewInstance(gn_pl_item->playlist_item->file);
info.GetReturnValue().Set(tmp);
}
NAN_GETTER(GNPlaylistItem::GetId) {
GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
char buf[64];
snprintf(buf, sizeof(buf), "%p", gn_pl_item->playlist_item);
info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
}
NAN_GETTER(GNPlaylistItem::GetGain) {
GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
double gain = gn_pl_item->playlist_item->gain;
info.GetReturnValue().Set(Nan::New<Number>(gain));
}
NAN_GETTER(GNPlaylistItem::GetPeak) {
GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
double peak = gn_pl_item->playlist_item->peak;
info.GetReturnValue().Set(Nan::New<Number>(peak));
}
================================================
FILE: src/playlist_item.h
================================================
#ifndef GN_PLAYLIST_ITEM_H
#define GN_PLAYLIST_ITEM_H
#include <node.h>
#include <nan.h>
#include <groove/groove.h>
class GNPlaylistItem : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GroovePlaylistItem *playlist_item);
GroovePlaylistItem *playlist_item;
private:
GNPlaylistItem();
~GNPlaylistItem();
static NAN_METHOD(New);
static NAN_GETTER(GetFile);
static NAN_GETTER(GetId);
static NAN_GETTER(GetGain);
static NAN_GETTER(GetPeak);
};
#endif
================================================
FILE: src/waveform_builder.cc
================================================
#include "waveform_builder.h"
#include "playlist.h"
#include "playlist_item.h"
#include "groove.h"
using namespace v8;
GNWaveformBuilder::GNWaveformBuilder() {};
GNWaveformBuilder::~GNWaveformBuilder() {
groove_waveform_destroy(waveform);
delete event_context->event_cb;
delete event_context;
};
static Nan::Persistent<v8::Function> constructor;
void GNWaveformBuilder::Init() {
// Prepare constructor template
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New<String>("GrooveWaveformBuilder").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(2);
Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
// Fields
Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
Nan::SetAccessor(proto, Nan::New<String>("playlist").ToLocalChecked(), GetPlaylist);
// Methods
Nan::SetPrototypeMethod(tpl, "attach", Attach);
Nan::SetPrototypeMethod(tpl, "detach", Detach);
Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo);
Nan::SetPrototypeMethod(tpl, "position", Position);
constructor.Reset(tpl->GetFunction());
}
NAN_METHOD(GNWaveformBuilder::New) {
Nan::HandleScope scope;
GNWaveformBuilder *obj = new GNWaveformBuilder();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
Local<Value> GNWaveformBuilder::NewInstance(GrooveWaveform *waveform) {
Nan::EscapableHandleScope scope;
Local<Function> cons = Nan::New(constructor);
Local<Object> instance = cons->NewInstance();
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(instance);
gn_waveform->waveform = waveform;
return scope.Escape(instance);
}
NAN_GETTER(GNWaveformBuilder::GetId) {
Nan::HandleScope scope;
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());
char buf[64];
snprintf(buf, sizeof(buf), "%p", gn_waveform->waveform);
info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
}
NAN_GETTER(GNWaveformBuilder::GetPlaylist) {
Nan::HandleScope scope;
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());
GroovePlaylist *playlist = gn_waveform->waveform->playlist;
if (playlist) {
Local<Value> tmp = GNPlaylist::NewInstance(playlist);
info.GetReturnValue().Set(tmp);
} else {
info.GetReturnValue().Set(Nan::Null());
}
}
NAN_METHOD(GNWaveformBuilder::Position) {
Nan::HandleScope scope;
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());
GrooveWaveform *waveform = gn_waveform->waveform;
GroovePlaylistItem *item;
double pos;
groove_waveform_position(waveform, &item, &pos);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
if (item) {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
} else {
Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(obj);
}
NAN_METHOD(GNWaveformBuilder::Create) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
GrooveWaveform *waveform = groove_waveform_create(get_groove());
if (!waveform) {
Nan::ThrowTypeError("unable to create waveform builder");
return;
}
// set properties on the instance with default values from
// GrooveWaveform struct
Local<Object> instance = GNWaveformBuilder::NewInstance(waveform)->ToObject();
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(instance);
EventContext *context = new EventContext;
gn_waveform->event_context = context;
context->event_cb = new Nan::Callback(info[0].As<Function>());
context->waveform = waveform;
Nan::Set(instance, Nan::New<String>("infoQueueSizeBytes").ToLocalChecked(),
Nan::New<Number>(waveform->info_queue_size_bytes));
Nan::Set(instance, Nan::New<String>("widthInFrames").ToLocalChecked(),
Nan::New<Number>(waveform->width_in_frames));
info.GetReturnValue().Set(instance);
}
static void buffer_free(char *data, void *hint) {
GrooveWaveformInfo *waveform_info = reinterpret_cast<GrooveWaveformInfo*>(hint);
groove_waveform_info_unref(waveform_info);
}
NAN_METHOD(GNWaveformBuilder::GetInfo) {
Nan::HandleScope scope;
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());
GrooveWaveform *waveform = gn_waveform->waveform;
GrooveWaveformInfo *waveform_info;
if (groove_waveform_info_get(waveform, &waveform_info, 0) == 1) {
Local<Object> object = Nan::New<Object>();
if (waveform_info->data_size) {
Local<Object> bufferObject = Nan::NewBuffer(
(char*)waveform_info->data, waveform_info->data_size,
buffer_free, waveform_info).ToLocalChecked();
Nan::Set(object, Nan::New<String>("buffer").ToLocalChecked(), bufferObject);
} else {
Nan::Set(object, Nan::New<String>("buffer").ToLocalChecked(), Nan::Null());
}
double expected_duration = waveform_info->expected_frame_count / (double)waveform_info->sample_rate;
double actual_duration = waveform_info->actual_frame_count / (double)waveform_info->sample_rate;
Nan::Set(object, Nan::New<String>("expectedDuration").ToLocalChecked(),
Nan::New<Number>(expected_duration));
Nan::Set(object, Nan::New<String>("actualDuration").ToLocalChecked(),
Nan::New<Number>(actual_duration));
if (waveform_info->item) {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(),
GNPlaylistItem::NewInstance(waveform_info->item));
} else {
Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
}
info.GetReturnValue().Set(object);
} else {
info.GetReturnValue().Set(Nan::Null());
}
}
static void EventAsyncCb(uv_async_t *handle) {
Nan::HandleScope scope;
GNWaveformBuilder::EventContext *context = reinterpret_cast<GNWaveformBuilder::EventContext *>(handle->data);
// call callback signaling that there is info ready
const unsigned argc = 1;
Local<Value> argv[argc];
argv[0] = Nan::Null();
TryCatch try_catch;
context->event_cb->Call(argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
uv_mutex_lock(&context->mutex);
uv_cond_signal(&context->cond);
uv_mutex_unlock(&context->mutex);
}
static void EventThreadEntry(void *arg) {
GNWaveformBuilder::EventContext *context = reinterpret_cast<GNWaveformBuilder::EventContext *>(arg);
while (groove_waveform_info_peek(context->waveform, 1) > 0) {
uv_mutex_lock(&context->mutex);
uv_async_send(&context->event_async);
uv_cond_wait(&context->cond, &context->mutex);
uv_mutex_unlock(&context->mutex);
}
}
class WaveformAttachWorker : public Nan::AsyncWorker {
public:
WaveformAttachWorker(Nan::Callback *callback, GrooveWaveform *waveform, GroovePlaylist *playlist,
GNWaveformBuilder::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->waveform = waveform;
this->playlist = playlist;
this->event_context = event_context;
}
~WaveformAttachWorker() {}
void Execute() {
int err;
if ((err = groove_waveform_attach(waveform, playlist))) {
SetErrorMessage(groove_strerror(err));
return;
}
GNWaveformBuilder::EventContext *context = event_context;
uv_cond_init(&context->cond);
uv_mutex_init(&context->mutex);
context->event_async.data = context;
uv_async_init(uv_default_loop(), &context->event_async, EventAsyncCb);
uv_thread_create(&context->event_thread, EventThreadEntry, context);
}
GrooveWaveform *waveform;
GroovePlaylist *playlist;
GNWaveformBuilder::EventContext *event_context;
};
NAN_METHOD(GNWaveformBuilder::Attach) {
Nan::HandleScope scope;
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("Expected object arg[0]");
return;
}
if (info.Length() < 2 || !info[1]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[1]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());
Local<Object> instance = info.This();
GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
GrooveWaveform *waveform = gn_waveform->waveform;
// copy the properties from our instance to the player
waveform->info_queue_size_bytes = (int)instance->Get(Nan::New<String>("infoQueueSizeBytes").ToLocalChecked())->NumberValue();
waveform->width_in_frames = (int)instance->Get(Nan::New<String>("widthInFrames").ToLocalChecked())->NumberValue();
AsyncQueueWorker(new WaveformAttachWorker(callback, waveform, gn_playlist->playlist, gn_waveform->event_context));
}
class WaveformDetachWorker : public Nan::AsyncWorker {
public:
WaveformDetachWorker(Nan::Callback *callback, GrooveWaveform *waveform,
GNWaveformBuilder::EventContext *event_context) :
Nan::AsyncWorker(callback)
{
this->waveform = waveform;
this->event_context = event_context;
}
~WaveformDetachWorker() {}
void Execute() {
int err;
if ((err = groove_waveform_detach(waveform))) {
SetErrorMessage(groove_strerror(err));
return;
}
uv_cond_signal(&event_context->cond);
uv_thread_join(&event_context->event_thread);
uv_cond_destroy(&event_context->cond);
uv_mutex_destroy(&event_context->mutex);
uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);
}
GrooveWaveform *waveform;
GroovePlaylist *playlist;
GNWaveformBuilder::EventContext *event_context;
};
NAN_METHOD(GNWaveformBuilder::Detach) {
Nan::HandleScope scope;
if (info.Length() < 1 || !info[0]->IsFunction()) {
Nan::ThrowTypeError("Expected function arg[0]");
return;
}
Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());
GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());
GrooveWaveform *waveform = gn_waveform->waveform;
AsyncQueueWorker(new WaveformDetachWorker(callback, waveform, gn_waveform->event_context));
}
================================================
FILE: src/waveform_builder.h
================================================
#ifndef GN_WAVEFORM_BUILDER_H
#define GN_WAVEFORM_BUILDER_H
#include <node.h>
#include <nan.h>
#include <groove/waveform.h>
class GNWaveformBuilder : public node::ObjectWrap {
public:
static void Init();
static v8::Local<v8::Value> NewInstance(GrooveWaveform *waveform);
static NAN_METHOD(Create);
struct EventContext {
uv_thread_t event_thread;
uv_async_t event_async;
uv_cond_t cond;
uv_mutex_t mutex;
GrooveWaveform *waveform;
Nan::Callback *event_cb;
};
EventContext *event_context;
GrooveWaveform *waveform;
private:
GNWaveformBuilder();
~GNWaveformBuilder();
static NAN_METHOD(New);
static NAN_GETTER(GetId);
static NAN_GETTER(GetPlaylist);
static NAN_METHOD(Attach);
static NAN_METHOD(Detach);
static NAN_METHOD(GetInfo);
static NAN_METHOD(Position);
};
#endif
================================================
FILE: test/test.js
================================================
var groove = require('../');
var assert = require('assert');
var path = require('path');
var fs = require('fs');
var ncp = require('ncp').ncp;
var testOgg = path.join(__dirname, "danse.ogg");
var bogusFile = __filename;
var rwTestOgg = path.join(__dirname, "danse-rw.ogg");
var it = global.it;
it("version", function() {
var ver = groove.getVersion();
assert.strictEqual(typeof ver.major, 'number');
assert.strictEqual(typeof ver.minor, 'number');
assert.strictEqual(typeof ver.patch, 'number');
});
it("logging", function() {
assert.strictEqual(groove.LOG_ERROR, 16);
groove.setLogging(groove.LOG_QUIET);
});
it("open fails for bogus file", function(done) {
groove.open(bogusFile, function(err, file) {
assert.strictEqual(err.message, "unknown format");
done();
});
});
it("open file and read metadata", function(done) {
groove.open(testOgg, function(err, file) {
assert.ok(!err);
assert.ok(file.id);
assert.strictEqual(file.filename, testOgg);
assert.strictEqual(file.dirty, false);
assert.strictEqual(file.metadata().TITLE, 'Danse Macabre');
assert.strictEqual(file.metadata().ARTIST, 'Kevin MacLeod');
assert.strictEqual(file.shortNames(), 'ogg');
assert.strictEqual(file.getMetadata('initial key'), 'C');
assert.equal(file.getMetadata('bogus nonexisting tag'), null);
file.close(function(err) {
if (err) throw err;
done();
});
});
});
it("update metadata", function(done) {
ncp(testOgg, rwTestOgg, function(err) {
assert.ok(!err);
groove.open(rwTestOgg, doUpdate);
});
function doUpdate(err, file) {
if (err) throw err;
file.setMetadata('foo new key', "libgroove rules!");
assert.strictEqual(file.getMetadata('foo new key'), 'libgroove rules!');
file.save(function(err) {
if (err) throw err;
file.close(checkUpdate);
});
}
function checkUpdate(err) {
assert.ok(!err);
groove.open(rwTestOgg, function(err, file) {
assert.ok(!err);
assert.strictEqual(file.getMetadata('foo new key'), 'libgroove rules!');
fs.unlinkSync(rwTestOgg);
done();
});
}
});
it("create empty playlist", function (done) {
var playlist = groove.createPlaylist();
assert.ok(playlist.id);
assert.deepEqual(playlist.items(), []);
done();
});
it("create empty player", function (done) {
var player = groove.createPlayer();
assert.ok(player.id);
done();
});
it("playlist item ids", function(done) {
var playlist = groove.createPlaylist();
assert.ok(playlist);
playlist.pause();
assert.equal(playlist.playing(), false);
groove.open(testOgg, function(err, file) {
assert.ok(!err, "opening file");
assert.ok(playlist.position);
assert.strictEqual(playlist.gain, 1.0);
playlist.setGain(1.0);
var returned1 = playlist.insert(file, null);
var returned2 = playlist.insert(file, null);
var items1 = playlist.items();
var items2 = playlist.items();
assert.strictEqual(items1[0].id, items2[0].id);
assert.strictEqual(items1[0].id, returned1.id);
assert.strictEqual(items2[1].id, returned2.id);
done();
});
});
it("create, attach, detach player", function(done) {
var playlist = groove.createPlaylist();
var player = groove.createPlayer();
groove.connectSoundBackend();
var devices = groove.getDevices();
var defaultDevice = devices.list[devices.defaultIndex];
player.device = defaultDevice;
player.attach(playlist, function(err) {
assert.ok(!err);
player.detach(function(err) {
assert.ok(!err);
done();
});
});
});
it("create, attach, detach loudness detector", function(done) {
var playlist = groove.createPlaylist();
var detector = groove.createLoudnessDetector();
detector.attach(playlist, function(err) {
assert.ok(!err);
detector.detach(function(err) {
assert.ok(!err);
done();
});
});
});
it("create, attach, detach encoder", function(done) {
var playlist = groove.createPlaylist();
var encoder = groove.createEncoder();
encoder.formatShortName = "ogg";
encoder.codecShortName = "vorbis";
encoder.attach(playlist, function(err) {
assert.ok(!err);
encoder.detach(function(err) {
assert.ok(!err);
done();
});
});
});
it("create, attach, detach fingerprinter", function(done) {
var playlist = groove.createPlaylist();
var fingerprinter = groove.createFingerprinter();
fingerprinter.attach(playlist, function(err) {
assert.ok(!err);
fingerprinter.detach(function(err) {
assert.ok(!err);
done();
});
});
});
it("create, attach, detach waveform builder", function(done) {
var playlist = groove.createPlaylist();
var waveform = groove.createWaveformBuilder();
waveform.attach(playlist, function(err) {
assert.ok(!err);
waveform.detach(function(err) {
assert.ok(!err);
done();
});
});
});
gitextract_vdtlnlz5/
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── binding.gyp
├── example/
│ ├── devices.js
│ ├── fingerprint.js
│ ├── metadata.js
│ ├── playlist.js
│ ├── replaygain.js
│ ├── transcode.js
│ └── waveform.js
├── lib/
│ └── index.js
├── package.json
├── src/
│ ├── device.cc
│ ├── device.h
│ ├── encoder.cc
│ ├── encoder.h
│ ├── file.cc
│ ├── file.h
│ ├── fingerprinter.cc
│ ├── fingerprinter.h
│ ├── groove.cc
│ ├── groove.h
│ ├── loudness_detector.cc
│ ├── loudness_detector.h
│ ├── player.cc
│ ├── player.h
│ ├── playlist.cc
│ ├── playlist.h
│ ├── playlist_item.cc
│ ├── playlist_item.h
│ ├── waveform_builder.cc
│ └── waveform_builder.h
└── test/
├── danse.ogg
└── test.js
SYMBOL INDEX (178 symbols across 27 files)
FILE: example/fingerprint.js
function openFileFn (line 42) | function openFileFn(o) {
function cleanup (line 52) | function cleanup() {
function usage (line 68) | function usage() {
FILE: example/metadata.js
function handleSaveErr (line 52) | function handleSaveErr(err) {
function usage (line 58) | function usage() {
function cleanup (line 64) | function cleanup(file, cb) {
FILE: example/playlist.js
function openFileFn (line 48) | function openFileFn(o, filename) {
function cleanup (line 58) | function cleanup() {
function usage (line 74) | function usage() {
FILE: example/replaygain.js
function openFileFn (line 48) | function openFileFn(o) {
function cleanup (line 58) | function cleanup() {
function usage (line 74) | function usage() {
FILE: example/transcode.js
function cleanup (line 38) | function cleanup() {
function usage (line 50) | function usage() {
FILE: example/waveform.js
function main (line 7) | function main() {
function usageAndExit (line 80) | function usageAndExit() {
FILE: lib/index.js
function jsCreateEncoder (line 31) | function jsCreateEncoder() {
function jsCreatePlayer (line 44) | function jsCreatePlayer() {
function jsCreateLoudnessDetector (line 79) | function jsCreateLoudnessDetector() {
function jsCreateFingerprinter (line 92) | function jsCreateFingerprinter() {
function jsCreateWaveformBuilder (line 104) | function jsCreateWaveformBuilder() {
function postHocInherit (line 117) | function postHocInherit(baseInstance, Super) {
function clamp_rg (line 125) | function clamp_rg(x) {
function loudnessToReplayGain (line 131) | function loudnessToReplayGain(loudness) {
function dBToFloat (line 135) | function dBToFloat(dB) {
FILE: src/device.cc
function NAN_METHOD (line 32) | NAN_METHOD(GNDevice::New) {
function NAN_GETTER (line 53) | NAN_GETTER(GNDevice::GetName) {
function NAN_GETTER (line 59) | NAN_GETTER(GNDevice::GetId) {
function NAN_GETTER (line 65) | NAN_GETTER(GNDevice::GetSoftwareLatencyMin) {
function NAN_GETTER (line 71) | NAN_GETTER(GNDevice::GetSoftwareLatencyMax) {
function NAN_GETTER (line 77) | NAN_GETTER(GNDevice::GetSoftwareLatencyCurrent) {
function NAN_GETTER (line 83) | NAN_GETTER(GNDevice::GetIsRaw) {
function NAN_GETTER (line 89) | NAN_GETTER(GNDevice::GetProbeError) {
FILE: src/device.h
function class (line 8) | class GNDevice : public node::ObjectWrap {
FILE: src/encoder.cc
function NAN_METHOD (line 37) | NAN_METHOD(GNEncoder::New) {
function NAN_GETTER (line 46) | NAN_GETTER(GNEncoder::GetActualAudioFormat) {
function EncoderEventAsyncCb (line 83) | static void EncoderEventAsyncCb(uv_async_t *handle) {
function EncoderEventThreadEntry (line 104) | static void EncoderEventThreadEntry(void *arg) {
class EncoderAttachWorker (line 117) | class EncoderAttachWorker : public Nan::AsyncWorker {
method EncoderAttachWorker (line 119) | EncoderAttachWorker(Nan::Callback *callback, GrooveEncoder *encoder, G...
method Execute (line 142) | void Execute() {
function NAN_METHOD (line 172) | NAN_METHOD(GNEncoder::Create) {
function NAN_METHOD (line 217) | NAN_METHOD(GNEncoder::Attach) {
class EncoderDetachWorker (line 302) | class EncoderDetachWorker : public Nan::AsyncWorker {
method EncoderDetachWorker (line 304) | EncoderDetachWorker(Nan::Callback *callback, GrooveEncoder *encoder,
method Execute (line 313) | void Execute() {
function NAN_METHOD (line 331) | NAN_METHOD(GNEncoder::Detach) {
function encoder_buffer_free (line 351) | static void encoder_buffer_free(char *data, void *hint) {
function NAN_METHOD (line 356) | NAN_METHOD(GNEncoder::GetBuffer) {
function NAN_METHOD (line 407) | NAN_METHOD(GNEncoder::Position) {
FILE: src/encoder.h
function class (line 8) | class GNEncoder : public node::ObjectWrap {
FILE: src/file.cc
function NAN_METHOD (line 37) | NAN_METHOD(GNFile::New) {
function NAN_GETTER (line 59) | NAN_GETTER(GNFile::GetDirty) {
function NAN_GETTER (line 65) | NAN_GETTER(GNFile::GetId) {
function NAN_GETTER (line 73) | NAN_GETTER(GNFile::GetFilename) {
function NAN_METHOD (line 78) | NAN_METHOD(GNFile::GetMetadata) {
function NAN_METHOD (line 105) | NAN_METHOD(GNFile::SetMetadata) {
function NAN_METHOD (line 137) | NAN_METHOD(GNFile::OverrideDuration) {
function NAN_METHOD (line 150) | NAN_METHOD(GNFile::Metadata) {
function NAN_METHOD (line 165) | NAN_METHOD(GNFile::ShortNames) {
function NAN_METHOD (line 171) | NAN_METHOD(GNFile::Duration) {
class CloseWorker (line 177) | class CloseWorker : public Nan::AsyncWorker {
method CloseWorker (line 179) | CloseWorker(Nan::Callback *callback, GrooveFile *file) : Nan::AsyncWor...
method Execute (line 184) | void Execute() {
function NAN_METHOD (line 195) | NAN_METHOD(GNFile::Close) {
class OpenWorker (line 211) | class OpenWorker : public Nan::AsyncWorker {
method OpenWorker (line 213) | OpenWorker(Nan::Callback *callback, String::Utf8Value *filename) : Nan...
method Execute (line 220) | void Execute() {
method HandleOKCallback (line 233) | void HandleOKCallback() {
function NAN_METHOD (line 243) | NAN_METHOD(GNFile::Open) {
class SaveWorker (line 259) | class SaveWorker : public Nan::AsyncWorker {
method SaveWorker (line 261) | SaveWorker(Nan::Callback *callback, GrooveFile *file) : Nan::AsyncWork...
method Execute (line 266) | void Execute() {
function NAN_METHOD (line 277) | NAN_METHOD(GNFile::Save) {
FILE: src/file.h
function class (line 8) | class GNFile : public node::ObjectWrap {
FILE: src/fingerprinter.cc
function NAN_METHOD (line 32) | NAN_METHOD(GNFingerprinter::New) {
function NAN_METHOD (line 53) | NAN_METHOD(GNFingerprinter::Create) {
function NAN_METHOD (line 83) | NAN_METHOD(GNFingerprinter::Position) {
function NAN_METHOD (line 104) | NAN_METHOD(GNFingerprinter::GetInfo) {
function PrinterEventAsyncCb (line 139) | static void PrinterEventAsyncCb(uv_async_t *handle) {
function PrinterEventThreadEntry (line 162) | static void PrinterEventThreadEntry(void *arg) {
class PrinterAttachWorker (line 172) | class PrinterAttachWorker : public Nan::AsyncWorker {
method PrinterAttachWorker (line 174) | PrinterAttachWorker(Nan::Callback *callback, GrooveFingerprinter *prin...
method Execute (line 184) | void Execute() {
function NAN_METHOD (line 205) | NAN_METHOD(GNFingerprinter::Attach) {
class PrinterDetachWorker (line 231) | class PrinterDetachWorker : public Nan::AsyncWorker {
method PrinterDetachWorker (line 233) | PrinterDetachWorker(Nan::Callback *callback, GrooveFingerprinter *prin...
method Execute (line 242) | void Execute() {
function NAN_METHOD (line 259) | NAN_METHOD(GNFingerprinter::Detach) {
function NAN_METHOD (line 272) | NAN_METHOD(GNFingerprinter::Encode) {
function NAN_METHOD (line 296) | NAN_METHOD(GNFingerprinter::Decode) {
FILE: src/fingerprinter.h
function class (line 8) | class GNFingerprinter : public node::ObjectWrap {
FILE: src/groove.cc
function Groove (line 20) | Groove *get_groove() {
function NAN_METHOD (line 24) | NAN_METHOD(SetLogging) {
function NAN_METHOD (line 34) | NAN_METHOD(ConnectSoundBackend) {
function NAN_METHOD (line 59) | NAN_METHOD(DisconnectSoundBackend) {
function NAN_METHOD (line 64) | NAN_METHOD(GetDevices) {
function NAN_METHOD (line 93) | NAN_METHOD(GetVersion) {
function SetProperty (line 105) | static void SetProperty(target_t obj, const char* name, double n) {
function SetMethod (line 110) | static void SetMethod(target_t obj, const char* name, FNPTR fn) {
function cleanup (line 115) | static void cleanup(void) {
function NAN_MODULE_INIT (line 120) | NAN_MODULE_INIT(Initialize) {
FILE: src/loudness_detector.cc
function NAN_METHOD (line 32) | NAN_METHOD(GNLoudnessDetector::New) {
function NAN_METHOD (line 53) | NAN_METHOD(GNLoudnessDetector::Create) {
function NAN_METHOD (line 84) | NAN_METHOD(GNLoudnessDetector::Position) {
function NAN_METHOD (line 104) | NAN_METHOD(GNLoudnessDetector::GetInfo) {
function EventAsyncCb (line 130) | static void EventAsyncCb(uv_async_t *handle) {
function EventThreadEntry (line 153) | static void EventThreadEntry(void *arg) {
class DetectorAttachWorker (line 163) | class DetectorAttachWorker : public Nan::AsyncWorker {
method DetectorAttachWorker (line 165) | DetectorAttachWorker(Nan::Callback *callback, GrooveLoudnessDetector *...
method Execute (line 175) | void Execute() {
function NAN_METHOD (line 196) | NAN_METHOD(GNLoudnessDetector::Attach) {
class DetectorDetachWorker (line 224) | class DetectorDetachWorker : public Nan::AsyncWorker {
method DetectorDetachWorker (line 226) | DetectorDetachWorker(Nan::Callback *callback, GrooveLoudnessDetector *...
method Execute (line 235) | void Execute() {
function NAN_METHOD (line 252) | NAN_METHOD(GNLoudnessDetector::Detach) {
FILE: src/loudness_detector.h
function class (line 8) | class GNLoudnessDetector : public node::ObjectWrap {
FILE: src/player.cc
function NAN_METHOD (line 37) | NAN_METHOD(GNPlayer::New) {
function NAN_GETTER (line 58) | NAN_GETTER(GNPlayer::GetId) {
function NAN_GETTER (line 66) | NAN_GETTER(GNPlayer::GetPlaylist) {
function NAN_METHOD (line 78) | NAN_METHOD(GNPlayer::Position) {
function PlayerEventAsyncCb (line 95) | static void PlayerEventAsyncCb(uv_async_t *handle) {
function PlayerEventThreadEntry (line 121) | static void PlayerEventThreadEntry(void *arg) {
class PlayerAttachWorker (line 131) | class PlayerAttachWorker : public Nan::AsyncWorker {
method PlayerAttachWorker (line 133) | PlayerAttachWorker(Nan::Callback *callback, GroovePlayer *player, Groo...
method Execute (line 143) | void Execute() {
function NAN_METHOD (line 166) | NAN_METHOD(GNPlayer::Create) {
function NAN_METHOD (line 192) | NAN_METHOD(GNPlayer::Attach) {
class PlayerDetachWorker (line 225) | class PlayerDetachWorker : public Nan::AsyncWorker {
method PlayerDetachWorker (line 227) | PlayerDetachWorker(Nan::Callback *callback, GroovePlayer *player,
method Execute (line 236) | void Execute() {
function NAN_METHOD (line 253) | NAN_METHOD(GNPlayer::Detach) {
FILE: src/player.h
function class (line 8) | class GNPlayer : public node::ObjectWrap {
FILE: src/playlist.cc
function NAN_METHOD (line 44) | NAN_METHOD(GNPlaylist::New) {
function NAN_METHOD (line 66) | NAN_METHOD(GNPlaylist::Destroy) {
function NAN_GETTER (line 73) | NAN_GETTER(GNPlaylist::GetId) {
function NAN_GETTER (line 80) | NAN_GETTER(GNPlaylist::GetGain) {
function NAN_METHOD (line 85) | NAN_METHOD(GNPlaylist::Play) {
function NAN_METHOD (line 92) | NAN_METHOD(GNPlaylist::Playlist) {
function NAN_METHOD (line 109) | NAN_METHOD(GNPlaylist::Pause) {
function NAN_METHOD (line 115) | NAN_METHOD(GNPlaylist::Seek) {
function NAN_METHOD (line 126) | NAN_METHOD(GNPlaylist::Insert) {
function NAN_METHOD (line 151) | NAN_METHOD(GNPlaylist::Remove) {
function NAN_METHOD (line 158) | NAN_METHOD(GNPlaylist::DecodePosition) {
function NAN_METHOD (line 174) | NAN_METHOD(GNPlaylist::Playing) {
function NAN_METHOD (line 181) | NAN_METHOD(GNPlaylist::Clear) {
function NAN_METHOD (line 187) | NAN_METHOD(GNPlaylist::Count) {
function NAN_METHOD (line 194) | NAN_METHOD(GNPlaylist::SetItemGainPeak) {
function NAN_METHOD (line 203) | NAN_METHOD(GNPlaylist::SetGain) {
function NAN_METHOD (line 209) | NAN_METHOD(GNPlaylist::SetFillMode) {
function NAN_METHOD (line 216) | NAN_METHOD(GNPlaylist::Create) {
FILE: src/playlist.h
function class (line 8) | class GNPlaylist : public node::ObjectWrap {
FILE: src/playlist_item.cc
function NAN_METHOD (line 27) | NAN_METHOD(GNPlaylistItem::New) {
function NAN_GETTER (line 48) | NAN_GETTER(GNPlaylistItem::GetFile) {
function NAN_GETTER (line 54) | NAN_GETTER(GNPlaylistItem::GetId) {
function NAN_GETTER (line 61) | NAN_GETTER(GNPlaylistItem::GetGain) {
function NAN_GETTER (line 67) | NAN_GETTER(GNPlaylistItem::GetPeak) {
FILE: src/playlist_item.h
function class (line 8) | class GNPlaylistItem : public node::ObjectWrap {
FILE: src/waveform_builder.cc
function NAN_METHOD (line 37) | NAN_METHOD(GNWaveformBuilder::New) {
function NAN_GETTER (line 58) | NAN_GETTER(GNWaveformBuilder::GetId) {
function NAN_GETTER (line 66) | NAN_GETTER(GNWaveformBuilder::GetPlaylist) {
function NAN_METHOD (line 78) | NAN_METHOD(GNWaveformBuilder::Position) {
function NAN_METHOD (line 99) | NAN_METHOD(GNWaveformBuilder::Create) {
function buffer_free (line 132) | static void buffer_free(char *data, void *hint) {
function NAN_METHOD (line 137) | NAN_METHOD(GNWaveformBuilder::GetInfo) {
function EventAsyncCb (line 177) | static void EventAsyncCb(uv_async_t *handle) {
function EventThreadEntry (line 200) | static void EventThreadEntry(void *arg) {
class WaveformAttachWorker (line 210) | class WaveformAttachWorker : public Nan::AsyncWorker {
method WaveformAttachWorker (line 212) | WaveformAttachWorker(Nan::Callback *callback, GrooveWaveform *waveform...
method Execute (line 222) | void Execute() {
function NAN_METHOD (line 245) | NAN_METHOD(GNWaveformBuilder::Attach) {
class WaveformDetachWorker (line 274) | class WaveformDetachWorker : public Nan::AsyncWorker {
method WaveformDetachWorker (line 276) | WaveformDetachWorker(Nan::Callback *callback, GrooveWaveform *waveform,
method Execute (line 285) | void Execute() {
function NAN_METHOD (line 303) | NAN_METHOD(GNWaveformBuilder::Detach) {
FILE: src/waveform_builder.h
function class (line 8) | class GNWaveformBuilder : public node::ObjectWrap {
FILE: test/test.js
function doUpdate (line 53) | function doUpdate(err, file) {
function checkUpdate (line 62) | function checkUpdate(err) {
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (138K chars).
[
{
"path": ".gitignore",
"chars": 21,
"preview": "/build\n/node_modules\n"
},
{
"path": "CHANGELOG.md",
"chars": 1827,
"preview": "# 3.0.0 (UNRELEASED)\n\n * `player.setItemGain` and `player.setItemPeak` are gone in favor of\n `player.setItemGainPeak`\n"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Andrew Kelley\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 16467,
"preview": "# node-groove\n\nNode.js bindings to [libgroove](https://github.com/andrewrk/libgroove) -\ngeneric music player backend lib"
},
{
"path": "binding.gyp",
"chars": 544,
"preview": "{\n \"targets\": [\n {\n \"target_name\": \"groove\",\n \"sources\": [\n \"src/player.cc\",\n \"src/g"
},
{
"path": "example/devices.js",
"chars": 419,
"preview": "/* list available output devices */\n\nvar groove = require('../');\nvar assert = require('assert');\n\ngroove.connectSoundBa"
},
{
"path": "example/fingerprint.js",
"chars": 1415,
"preview": "/* generate the acoustid fingerprint of songs */\n\nvar groove = require('../');\nvar Pend = require('pend');\n\nif (process."
},
{
"path": "example/metadata.js",
"chars": 1698,
"preview": "/* read or update metadata in a media file */\n\nvar groove = require('../');\n\nif (process.argv.length < 3) usage();\n\ngroo"
},
{
"path": "example/playlist.js",
"chars": 1630,
"preview": "/* play several files in a row and then exit */\n\nvar groove = require('../');\nvar Pend = require('pend');\n\nif (process.a"
},
{
"path": "example/replaygain.js",
"chars": 1647,
"preview": "/* replaygain scanner */\n\nvar groove = require('../');\nvar Pend = require('pend');\n\nif (process.argv.length < 3) usage()"
},
{
"path": "example/transcode.js",
"chars": 1112,
"preview": "/* transcode a file into ogg vorbis */\n\nvar groove = require('../');\nvar fs = require('fs');\n\nif (process.argv.length < "
},
{
"path": "example/waveform.js",
"chars": 1979,
"preview": "/* calculate a waveformjs compatible representation of a media file */\n\nvar groove = require('../');\n\nmain();\n\nfunction "
},
{
"path": "lib/index.js",
"chars": 3431,
"preview": "var bindings = require('bindings')('groove.node');\nvar EventEmitter = require('events').EventEmitter;\nvar util = require"
},
{
"path": "package.json",
"chars": 788,
"preview": "{\n \"name\": \"groove\",\n \"version\": \"2.4.0\",\n \"description\": \"bindings to libgroove - generic music player library\",\n \""
},
{
"path": "src/device.cc",
"chars": 3445,
"preview": "#include \"device.h\"\n#include \"file.h\"\n\nusing namespace v8;\n\nGNDevice::GNDevice() { };\nGNDevice::~GNDevice() {\n soundi"
},
{
"path": "src/device.h",
"chars": 695,
"preview": "#ifndef GN_DEVICE_H\n#define GN_DEVICE_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNDevice :"
},
{
"path": "src/encoder.cc",
"chars": 15981,
"preview": "#include <node_buffer.h>\n#include \"encoder.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"groove.h\"\n\nusin"
},
{
"path": "src/encoder.h",
"chars": 946,
"preview": "#ifndef GN_ENCODER_H\n#define GN_ENCODER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/encoder.h>\n\nclass GNEncod"
},
{
"path": "src/file.cc",
"chars": 8407,
"preview": "#include <node.h>\n#include \"file.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNFile::GNFile() {};\nGNFile::~GNFile() {};"
},
{
"path": "src/file.h",
"chars": 821,
"preview": "#ifndef GN_FILE_H\n#define GN_FILE_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNFile : publi"
},
{
"path": "src/fingerprinter.cc",
"chars": 10586,
"preview": "#include \"fingerprinter.h\"\n#include \"playlist_item.h\"\n#include \"playlist.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGN"
},
{
"path": "src/fingerprinter.h",
"chars": 987,
"preview": "#ifndef GN_FINGERPRINTER_H\n#define GN_FINGERPRINTER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/fingerprinter"
},
{
"path": "src/groove.cc",
"chars": 6470,
"preview": "#include <node.h>\n#include <nan.h>\n#include <cstdlib>\n#include \"groove.h\"\n#include \"file.h\"\n#include \"player.h\"\n#include"
},
{
"path": "src/groove.h",
"chars": 99,
"preview": "#ifndef GN_GROOVE_H\n#define GN_GROOVE_H\n\n#include <groove/groove.h>\n\nGroove *get_groove();\n\n#endif\n"
},
{
"path": "src/loudness_detector.cc",
"chars": 9237,
"preview": "#include \"loudness_detector.h\"\n#include \"playlist_item.h\"\n#include \"playlist.h\"\n#include \"groove.h\"\n\nusing namespace v8;"
},
{
"path": "src/loudness_detector.h",
"chars": 940,
"preview": "#ifndef GN_LOUDNESS_DETECTOR_H\n#define GN_LOUDNESS_DETECTOR_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/loudn"
},
{
"path": "src/player.cc",
"chars": 8406,
"preview": "#include \"player.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"device.h\"\n#include \"groove.h\"\n\nusing name"
},
{
"path": "src/player.h",
"chars": 891,
"preview": "#ifndef GN_PLAYER_H\n#define GN_PLAYER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/player.h>\n\nclass GNPlayer :"
},
{
"path": "src/playlist.cc",
"chars": 7853,
"preview": "#include <node.h>\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"file.h\"\n#include \"groove.h\"\n\nusing namespac"
},
{
"path": "src/playlist.h",
"chars": 1062,
"preview": "#ifndef GN_PLAYLIST_H\n#define GN_PLAYLIST_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNPlay"
},
{
"path": "src/playlist_item.cc",
"chars": 2489,
"preview": "#include \"playlist_item.h\"\n#include \"file.h\"\n\nusing namespace v8;\n\nGNPlaylistItem::GNPlaylistItem() { };\nGNPlaylistItem:"
},
{
"path": "src/playlist_item.h",
"chars": 590,
"preview": "#ifndef GN_PLAYLIST_ITEM_H\n#define GN_PLAYLIST_ITEM_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\ncl"
},
{
"path": "src/waveform_builder.cc",
"chars": 10898,
"preview": "#include \"waveform_builder.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n"
},
{
"path": "src/waveform_builder.h",
"chars": 986,
"preview": "#ifndef GN_WAVEFORM_BUILDER_H\n#define GN_WAVEFORM_BUILDER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/wavefor"
},
{
"path": "test/test.js",
"chars": 5256,
"preview": "var groove = require('../');\nvar assert = require('assert');\nvar path = require('path');\nvar fs = require('fs');\nvar ncp"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the andrewrk/node-groove GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (128.0 KB), approximately 32.9k tokens, and a symbol index with 178 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.