pax_global_header00006660000000000000000000000064126336521610014517gustar00rootroot0000000000000052 comment=e33dcfa4603fc2949886370114063d601d11aeca node-groove-2.5.0/000077500000000000000000000000001263365216100137475ustar00rootroot00000000000000node-groove-2.5.0/.gitignore000066400000000000000000000000251263365216100157340ustar00rootroot00000000000000/build /node_modules node-groove-2.5.0/LICENSE000066400000000000000000000020701263365216100147530ustar00rootroot00000000000000The 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. node-groove-2.5.0/README.md000066400000000000000000000326201263365216100152310ustar00rootroot00000000000000# 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/). ## 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 >=2.4.0 depends on libgroove >=4.3.0 * use node-groove 2.3.4 if you want to use libgroove <4.3.0 ### 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 ## 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.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()`. #### 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.setItemGain(playlistItem, gain) `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). #### playlist.setItemPeak(playlistItem, peak) See `item.peak` #### playlist.setFillMode(mode) `mode` can be: * `groove.EVERY_SINK_FULL` This is the default behavior. 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` 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()`. #### 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() Returns an array of device names which are the devices you can send audio to. #### groove.createPlayer() Creates a GroovePlayer instance which you can then configure by setting properties. #### player.deviceIndex Before calling `attach()`, set this to the index of one of the devices returned from `groove.getDevices()` or `null` to represent the default device. Use `groove.DUMMY_DEVICE` to represent a dummy audio player. #### player.targetAudioFormat The desired audio format settings with which to open the device. `groove.createPlayer()` 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` * `sampleFormat` #### player.actualAudioFormat groove sets this to the actual format you get when you open the device. Ideally will be the same as targetAudioFormat but might not be. Properties: * `sampleRate` * `channelLayout` * `sampleFormat` #### player.deviceBufferSize how big the device buffer should be, in sample frames. must be a power of 2. `groove.createPlayer()` defaults this to 1024 #### player.sinkBufferSize How big the sink buffer should be, in sample frames. `groove.createPlayer()` defaults this to 8192 #### player.useExactAudioFormat If you set this to `true`, `targetAudioFormat` and `actualAudioFormat` are ignored and no resampling, channel layout remapping, or sample format conversion will occur. The audio device will be reopened with exact parameters whenever necessary. #### 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` * `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` * `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.sinkBufferSize How big the sink buffer should be, in sample frames. `groove.createLoudnessDetector()` defaults this to 8192 #### 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.sinkBufferSize How big the sink buffer should be, in sample frames. `groove.createFingerprinter()` defaults this to 8192 #### 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()`. node-groove-2.5.0/binding.gyp000066400000000000000000000011021263365216100160740ustar00rootroot00000000000000{ "targets": [ { "target_name": "groove", "sources": [ "src/groove.cc", "src/file.cc", "src/playlist.cc", "src/player.cc", "src/playlist_item.cc", "src/loudness_detector.cc", "src/fingerprinter.cc", "src/encoder.cc" ], "libraries": [ "-lgroove", "-lgrooveplayer", "-lgrooveloudness", "-lgroovefingerprinter" ], "include_dirs": [ "= 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], " [--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(); }); } node-groove-2.5.0/example/playlist.js000066400000000000000000000030231263365216100175770ustar00rootroot00000000000000/* play several files in a row and then exit */ var groove = require('../'); var assert = require('assert'); var Batch = require('batch'); // npm install batch if (process.argv.length < 3) usage(); var playlist = groove.createPlaylist(); var player = groove.createPlayer(); player.useExactAudioFormat = true; player.on('devicereopened', function() { console.log("Device re-opened"); }); 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 batch = new Batch(); for (var i = 2; i < process.argv.length; i += 1) { batch.push(openFileFn(process.argv[i])); } batch.end(function(err, files) { files.forEach(function(file) { if (file) { playlist.insert(file); } }); player.attach(playlist, function(err) { assert.ifError(err); }); }); function openFileFn(filename) { return function(cb) { groove.open(filename, cb); }; } function cleanup() { var batch = new Batch(); var files = playlist.items().map(function(item) { return item.file; }); playlist.clear(); files.forEach(function(file) { batch.push(function(cb) { file.close(cb); }); }); batch.end(function(err) { player.detach(function(err) { if (err) console.error(err.stack); }); }); } function usage() { console.error("Usage: playlist file1 file2 ..."); process.exit(1); } node-groove-2.5.0/example/replaygain.js000066400000000000000000000030651263365216100200770ustar00rootroot00000000000000/* replaygain scanner */ var groove = require('../'); var assert = require('assert'); var Batch = require('batch'); // npm install batch 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(); } }); detector.attach(playlist, function(err) { assert.ifError(err); var batch = new Batch(); for (var i = 2; i < process.argv.length; i += 1) { batch.push(openFileFn(process.argv[i])); } batch.end(function(err, files) { files.forEach(function(file) { if (file) { playlist.insert(file, null); } }); }); }); function openFileFn(filename) { return function(cb) { groove.open(filename, cb); }; } function cleanup() { var batch = new Batch(); var files = playlist.items().map(function(item) { return item.file; }); playlist.clear(); files.forEach(function(file) { batch.push(function(cb) { file.close(cb); }); }); batch.end(function(err) { detector.detach(function(err) { if (err) console.error(err.stack); }); }); } function usage() { console.error("Usage: node replaygain.js file1 file2 ..."); process.exit(1); } node-groove-2.5.0/example/transcode.js000066400000000000000000000021421263365216100177210ustar00rootroot00000000000000/* transcode a file into ogg vorbis */ var groove = require('../'); var assert = require('assert'); 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) { assert.ifError(err); groove.open(process.argv[2], function(err, file) { assert.ifError(err); playlist.insert(file, null); }); }); function cleanup() { var file = playlist.items()[0].file; playlist.clear(); file.close(function(err) { assert.ifError(err); encoder.detach(function(err) { assert.ifError(err); }); }); } function usage() { console.error("Usage: node transcode.js inputfile outputfile"); process.exit(1); } node-groove-2.5.0/lib/000077500000000000000000000000001263365216100145155ustar00rootroot00000000000000node-groove-2.5.0/lib/index.js000066400000000000000000000052651263365216100161720ustar00rootroot00000000000000var 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; bindings.createPlayer = jsCreatePlayer; bindings.createEncoder = jsCreateEncoder; bindings.createLoudnessDetector = jsCreateLoudnessDetector; bindings.createFingerprinter = jsCreateFingerprinter; bindings.loudnessToReplayGain = loudnessToReplayGain; bindings.dBToFloat = dBToFloat; bindings.DUMMY_DEVICE = -2; 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_DEVICEREOPENED: player.emit('devicereopened'); 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 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); } node-groove-2.5.0/package.json000066400000000000000000000013761263365216100162440ustar00rootroot00000000000000{ "name": "groove", "version": "2.5.0", "description": "bindings to libgroove - generic music player library", "main": "lib/index.js", "author": "Andrew Kelley ", "repository": { "type": "git", "url": "https://github.com/andrewrk/node-groove" }, "scripts": { "test": "mocha --reporter spec", "install": "node-gyp rebuild" }, "license": "MIT", "devDependencies": { "mocha": "~2.2.5", "ncp": "~2.0.0" }, "dependencies": { "bindings": "~1.2.1", "nan": "~2.1.0" }, "gypfile": true, "bugs": { "url": "https://github.com/andrewrk/node-groove/issues" }, "homepage": "https://github.com/andrewrk/node-groove", "directories": { "example": "example", "test": "test" } } node-groove-2.5.0/src/000077500000000000000000000000001263365216100145365ustar00rootroot00000000000000node-groove-2.5.0/src/encoder.cc000066400000000000000000000373551263365216100165010ustar00rootroot00000000000000#include #include "encoder.h" #include "playlist.h" #include "playlist_item.h" using namespace v8; GNEncoder::GNEncoder() {}; GNEncoder::~GNEncoder() { groove_encoder_destroy(encoder); delete event_context->event_cb; delete event_context; }; static Nan::Persistent constructor; void GNEncoder::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("GrooveEncoder").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(2); // 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()); } Handle GNEncoder::NewInstance(GrooveEncoder *encoder) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNEncoder *gn_encoder = node::ObjectWrap::Unwrap(instance); gn_encoder->encoder = encoder; return scope.Escape(instance); } struct AttachReq { uv_work_t req; Nan::Callback *callback; GrooveEncoder *encoder; GroovePlaylist *playlist; int errcode; Nan::Persistent instance; String::Utf8Value *format_short_name; String::Utf8Value *codec_short_name; String::Utf8Value *filename; String::Utf8Value *mime_type; GNEncoder::EventContext *event_context; }; static void EventAsyncCb(uv_async_t *handle #if UV_VERSION_MAJOR == 0 , int status #endif ) { Nan::HandleScope scope; GNEncoder::EventContext *context = reinterpret_cast(handle->data); const unsigned argc = 1; Local 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 EventThreadEntry(void *arg) { GNEncoder::EventContext *context = reinterpret_cast(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); } } static void AttachAsync(uv_work_t *req) { AttachReq *r = reinterpret_cast(req->data); r->encoder->format_short_name = r->format_short_name ? **r->format_short_name : NULL; r->encoder->codec_short_name = r->codec_short_name ? **r->codec_short_name : NULL; r->encoder->filename = r->filename ? **r->filename : NULL; r->encoder->mime_type = r->mime_type ? **r->mime_type : NULL; r->errcode = groove_encoder_attach(r->encoder, r->playlist); if (r->format_short_name) { delete r->format_short_name; r->format_short_name = NULL; } if (r->codec_short_name) { delete r->codec_short_name; r->codec_short_name = NULL; } if (r->filename) { delete r->filename; r->filename = NULL; } if (r->mime_type) { delete r->mime_type; r->mime_type = NULL; } GNEncoder::EventContext *context = r->event_context; uv_cond_init(&context->cond); uv_mutex_init(&context->mutex); uv_async_init(uv_default_loop(), &context->event_async, EventAsyncCb); context->event_async.data = context; uv_thread_create(&context->event_thread, EventThreadEntry, context); } static void AttachAfter(uv_work_t *req) { Nan::HandleScope scope; AttachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("encoder attach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); Local actualAudioFormat = Nan::New(); actualAudioFormat->Set(Nan::New("sampleRate").ToLocalChecked(), Nan::New(r->encoder->actual_audio_format.sample_rate)); actualAudioFormat->Set(Nan::New("channelLayout").ToLocalChecked(), Nan::New(r->encoder->actual_audio_format.channel_layout)); actualAudioFormat->Set(Nan::New("sampleFormat").ToLocalChecked(), Nan::New(r->encoder->actual_audio_format.sample_fmt)); Local o = Nan::New(r->instance); o->Set(Nan::New("actualAudioFormat").ToLocalChecked(), actualAudioFormat); r->instance.Reset(o); } TryCatch try_catch; r->callback->Call(argc, argv); r->instance.Reset(); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } 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(); Local instance = NewInstance(encoder)->ToObject(); GNEncoder *gn_encoder = node::ObjectWrap::Unwrap(instance); EventContext *context = new EventContext; gn_encoder->event_context = context; context->emit_buffer_ok = true; context->event_cb = new Nan::Callback(info[0].As()); context->encoder = encoder; // set properties on the instance with default values from // GrooveEncoder struct Local targetAudioFormat = Nan::New(); Nan::Set(targetAudioFormat, Nan::New("sampleRate").ToLocalChecked(), Nan::New(encoder->target_audio_format.sample_rate)); Nan::Set(targetAudioFormat, Nan::New("channelLayout").ToLocalChecked(), Nan::New(encoder->target_audio_format.channel_layout)); Nan::Set(targetAudioFormat, Nan::New("sampleFormat").ToLocalChecked(), Nan::New(encoder->target_audio_format.sample_fmt)); Nan::Set(instance, Nan::New("bitRate").ToLocalChecked(), Nan::New(encoder->bit_rate)); Nan::Set(instance, Nan::New("actualAudioFormat").ToLocalChecked(), Nan::Null()); Nan::Set(instance, Nan::New("targetAudioFormat").ToLocalChecked(), targetAudioFormat); Nan::Set(instance, Nan::New("formatShortName").ToLocalChecked(), Nan::Null()); Nan::Set(instance, Nan::New("codecShortName").ToLocalChecked(), Nan::Null()); Nan::Set(instance, Nan::New("filename").ToLocalChecked(), Nan::Null()); Nan::Set(instance, Nan::New("mimeType").ToLocalChecked(), Nan::Null()); Nan::Set(instance, Nan::New("encodedBufferSize").ToLocalChecked(), Nan::New(encoder->encoded_buffer_size)); Nan::Set(instance, Nan::New("sinkBufferSize").ToLocalChecked(), Nan::New(encoder->sink_buffer_size)); info.GetReturnValue().Set(instance); } NAN_METHOD(GNEncoder::Attach) { Nan::HandleScope scope; GNEncoder *gn_encoder = node::ObjectWrap::Unwrap(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; } Local instance = info.This(); Local targetAudioFormatValue = instance->Get(Nan::New("targetAudioFormat").ToLocalChecked()); if (!targetAudioFormatValue->IsObject()) { Nan::ThrowTypeError("Expected targetAudioFormat to be an object"); return; } GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info[0]->ToObject()); AttachReq *request = new AttachReq; request->req.data = request; request->callback = new Nan::Callback(info[1].As()); request->instance.Reset(info.This()); request->playlist = gn_playlist->playlist; request->event_context = gn_encoder->event_context; GrooveEncoder *encoder = gn_encoder->encoder; request->encoder = encoder; // copy the properties from our instance to the encoder Local formatShortName = instance->Get(Nan::New("formatShortName").ToLocalChecked()); if (formatShortName->IsNull() || formatShortName->IsUndefined()) { request->format_short_name = NULL; } else { request->format_short_name = new String::Utf8Value(formatShortName->ToString()); } Local codecShortName = instance->Get(Nan::New("codecShortName").ToLocalChecked()); if (codecShortName->IsNull() || codecShortName->IsUndefined()) { request->codec_short_name = NULL; } else { request->codec_short_name = new String::Utf8Value(codecShortName->ToString()); } Local filenameStr = instance->Get(Nan::New("filename").ToLocalChecked()); if (filenameStr->IsNull() || filenameStr->IsUndefined()) { request->filename = NULL; } else { request->filename = new String::Utf8Value(filenameStr->ToString()); } Local mimeType = instance->Get(Nan::New("mimeType").ToLocalChecked()); if (mimeType->IsNull() || mimeType->IsUndefined()) { request->mime_type = NULL; } else { request->mime_type = new String::Utf8Value(mimeType->ToString()); } Local targetAudioFormat = targetAudioFormatValue->ToObject(); Local sampleRate = targetAudioFormat->Get(Nan::New("sampleRate").ToLocalChecked()); double sample_rate = sampleRate->NumberValue(); double channel_layout = targetAudioFormat->Get(Nan::New("channelLayout").ToLocalChecked())->NumberValue(); double sample_fmt = targetAudioFormat->Get(Nan::New("sampleFormat").ToLocalChecked())->NumberValue(); encoder->target_audio_format.sample_rate = (int)sample_rate; encoder->target_audio_format.channel_layout = (int)channel_layout; encoder->target_audio_format.sample_fmt = (enum GrooveSampleFormat)(int)sample_fmt; double bit_rate = instance->Get(Nan::New("bitRate").ToLocalChecked())->NumberValue(); encoder->bit_rate = (int)bit_rate; double sink_buffer_size = instance->Get(Nan::New("sinkBufferSize").ToLocalChecked())->NumberValue(); encoder->sink_buffer_size = (int)sink_buffer_size; double encoded_buffer_size = instance->Get(Nan::New("encodedBufferSize").ToLocalChecked())->NumberValue(); encoder->encoded_buffer_size = (int)encoded_buffer_size; uv_queue_work(uv_default_loop(), &request->req, AttachAsync, (uv_after_work_cb)AttachAfter); } struct DetachReq { uv_work_t req; GrooveEncoder *encoder; Nan::Callback *callback; int errcode; GNEncoder::EventContext *event_context; }; static void DetachAsyncFree(uv_handle_t *handle) { } static void DetachAsync(uv_work_t *req) { DetachReq *r = reinterpret_cast(req->data); r->errcode = groove_encoder_detach(r->encoder); uv_cond_signal(&r->event_context->cond); uv_thread_join(&r->event_context->event_thread); uv_cond_destroy(&r->event_context->cond); uv_mutex_destroy(&r->event_context->mutex); uv_close(reinterpret_cast(&r->event_context->event_async), DetachAsyncFree); } static void DetachAfter(uv_work_t *req) { Nan::HandleScope scope; DetachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("encoder detach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNEncoder::Detach) { Nan::HandleScope scope; GNEncoder *gn_encoder = node::ObjectWrap::Unwrap(info.This()); if (info.Length() < 1 || !info[0]->IsFunction()) { Nan::ThrowTypeError("Expected function arg[0]"); return; } GrooveEncoder *encoder = gn_encoder->encoder; if (!encoder->playlist) { Nan::ThrowTypeError("detach: not attached"); return; } DetachReq *request = new DetachReq; request->req.data = request; request->callback = new Nan::Callback(info[0].As()); request->encoder = encoder; request->event_context = gn_encoder->event_context; uv_queue_work(uv_default_loop(), &request->req, DetachAsync, (uv_after_work_cb)DetachAfter); return; } static void buffer_free(char *data, void *hint) { GrooveBuffer *buffer = reinterpret_cast(hint); groove_buffer_unref(buffer); } NAN_METHOD(GNEncoder::GetBuffer) { Nan::HandleScope scope; GNEncoder *gn_encoder = node::ObjectWrap::Unwrap(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 = Nan::New(); Nan::MaybeLocal bufferObject = Nan::NewBuffer( reinterpret_cast(buffer->data[0]), buffer->size, buffer_free, buffer); Nan::Set(object, Nan::New("buffer").ToLocalChecked(), bufferObject.ToLocalChecked()); if (buffer->item) { Nan::Set(object, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(buffer->item)); } else { Nan::Set(object, Nan::New("item").ToLocalChecked(), Nan::Null()); } Nan::Set(object, Nan::New("pos").ToLocalChecked(), Nan::New(buffer->pos)); Nan::Set(object, Nan::New("pts").ToLocalChecked(), Nan::New(buffer->pts)); info.GetReturnValue().Set(object); break; } case GROOVE_BUFFER_END: { Local object = Nan::New(); Nan::Set(object, Nan::New("buffer").ToLocalChecked(), Nan::Null()); Nan::Set(object, Nan::New("item").ToLocalChecked(), Nan::Null()); Nan::Set(object, Nan::New("pos").ToLocalChecked(), Nan::Null()); Nan::Set(object, Nan::New("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(info.This()); GrooveEncoder *encoder = gn_encoder->encoder; GroovePlaylistItem *item; double pos; groove_encoder_position(encoder, &item, &pos); Local obj = Nan::New(); Nan::Set(obj, Nan::New("pos").ToLocalChecked(), Nan::New(pos)); if (item) { Nan::Set(obj, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item)); } else { Nan::Set(obj, Nan::New("item").ToLocalChecked(), Nan::Null()); } info.GetReturnValue().Set(obj); } node-groove-2.5.0/src/encoder.h000066400000000000000000000016011263365216100163240ustar00rootroot00000000000000#ifndef GN_ENCODER_H #define GN_ENCODER_H #include #include #include class GNEncoder : public node::ObjectWrap { public: static void Init(); static v8::Handle 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_METHOD(Attach); static NAN_METHOD(Detach); static NAN_METHOD(GetBuffer); static NAN_METHOD(Position); }; #endif node-groove-2.5.0/src/file.cc000066400000000000000000000217241263365216100157720ustar00rootroot00000000000000#include #include "file.h" using namespace v8; GNFile::GNFile() {}; GNFile::~GNFile() {}; static Nan::Persistent constructor; void GNFile::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("GrooveFile").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); Local proto = tpl->PrototypeTemplate(); // Fields Nan::SetAccessor(proto, Nan::New("filename").ToLocalChecked(), GetFilename); Nan::SetAccessor(proto, Nan::New("dirty").ToLocalChecked(), GetDirty); Nan::SetAccessor(proto, Nan::New("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); 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()); } Handle GNFile::NewInstance(GrooveFile *file) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNFile *gn_file = node::ObjectWrap::Unwrap(instance); gn_file->file = file; return scope.Escape(instance); } NAN_GETTER(GNFile::GetDirty) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); info.GetReturnValue().Set(Nan::New(gn_file->file->dirty)); } NAN_GETTER(GNFile::GetId) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); char buf[64]; snprintf(buf, sizeof(buf), "%p", gn_file->file); info.GetReturnValue().Set(Nan::New(buf).ToLocalChecked()); } NAN_GETTER(GNFile::GetFilename) { GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); info.GetReturnValue().Set(Nan::New(gn_file->file->filename).ToLocalChecked()); } NAN_METHOD(GNFile::GetMetadata) { GNFile *gn_file = node::ObjectWrap::Unwrap(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(groove_tag_value(tag)).ToLocalChecked()); else info.GetReturnValue().Set(Nan::Null()); } NAN_METHOD(GNFile::SetMetadata) { GNFile *gn_file = node::ObjectWrap::Unwrap(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::Metadata) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); Local metadata = Nan::New(); GrooveTag *tag = NULL; while ((tag = groove_file_metadata_get(gn_file->file, "", tag, 0))) { Nan::Set(metadata, Nan::New(groove_tag_key(tag)).ToLocalChecked(), Nan::New(groove_tag_value(tag)).ToLocalChecked()); } info.GetReturnValue().Set(metadata); } NAN_METHOD(GNFile::ShortNames) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); info.GetReturnValue().Set(Nan::New(groove_file_short_names(gn_file->file)).ToLocalChecked()); } NAN_METHOD(GNFile::Duration) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); info.GetReturnValue().Set(Nan::New(groove_file_duration(gn_file->file))); } struct CloseReq { uv_work_t req; Nan::Callback *callback; GrooveFile *file; }; static void CloseAsync(uv_work_t *req) { CloseReq *r = reinterpret_cast(req->data); if (r->file) { groove_file_close(r->file); } } static void CloseAfter(uv_work_t *req) { Nan::HandleScope scope; CloseReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->file) { argv[0] = Nan::Null(); } else { argv[0] = Exception::Error(Nan::New("file already closed").ToLocalChecked()); } TryCatch try_catch; r->callback->Call(argc, argv); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNFile::Close) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); if (info.Length() < 1 || !info[0]->IsFunction()) { Nan::ThrowTypeError("Expected function arg[0]"); return; } CloseReq *request = new CloseReq; request->callback = new Nan::Callback(info[0].As()); request->file = gn_file->file; request->req.data = request; gn_file->file = NULL; uv_queue_work(uv_default_loop(), &request->req, CloseAsync, (uv_after_work_cb)CloseAfter); return; } struct OpenReq { uv_work_t req; GrooveFile *file; String::Utf8Value *filename; Nan::Callback *callback; }; static void OpenAsync(uv_work_t *req) { OpenReq *r = reinterpret_cast(req->data); r->file = groove_file_open(**r->filename); } static void OpenAfter(uv_work_t *req) { Nan::HandleScope scope; OpenReq *r = reinterpret_cast(req->data); Local argv[2]; if (r->file) { argv[0] = Nan::Null(); argv[1] = GNFile::NewInstance(r->file); } else { argv[0] = Exception::Error(Nan::New("open file failed").ToLocalChecked()); argv[1] = Nan::Null(); } TryCatch try_catch; r->callback->Call(2, argv); // cleanup delete r->filename; delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } 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; } OpenReq *request = new OpenReq; request->filename = new String::Utf8Value(info[0]->ToString()); request->callback = new Nan::Callback(info[1].As()); request->req.data = request; uv_queue_work(uv_default_loop(), &request->req, OpenAsync, (uv_after_work_cb)OpenAfter); return; } struct SaveReq { uv_work_t req; Nan::Callback *callback; GrooveFile *file; int ret; }; static void SaveAsync(uv_work_t *req) { SaveReq *r = reinterpret_cast(req->data); r->ret = groove_file_save(r->file); } static void SaveAfter(uv_work_t *req) { Nan::HandleScope scope; SaveReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->ret) { argv[0] = Exception::Error(Nan::New("Unable to open file").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNFile::Save) { Nan::HandleScope scope; GNFile *gn_file = node::ObjectWrap::Unwrap(info.This()); if (info.Length() < 1 || !info[0]->IsFunction()) { Nan::ThrowTypeError("Expected function arg[0]"); return; } SaveReq *request = new SaveReq; request->callback = new Nan::Callback(info[0].As()); request->file = gn_file->file; request->req.data = request; uv_queue_work(uv_default_loop(), &request->req, SaveAsync, (uv_after_work_cb)SaveAfter); return; } node-groove-2.5.0/src/file.h000066400000000000000000000014111263365216100156230ustar00rootroot00000000000000#ifndef GN_FILE_H #define GN_FILE_H #include #include #include class GNFile : public node::ObjectWrap { public: static void Init(); static v8::Handle 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); }; #endif node-groove-2.5.0/src/fingerprinter.cc000066400000000000000000000272661263365216100177400ustar00rootroot00000000000000#include "fingerprinter.h" #include "playlist_item.h" #include "playlist.h" using namespace v8; GNFingerprinter::GNFingerprinter() {}; GNFingerprinter::~GNFingerprinter() { groove_fingerprinter_destroy(printer); }; static Nan::Persistent constructor; void GNFingerprinter::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("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()); } Handle GNFingerprinter::NewInstance(GrooveFingerprinter *printer) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap(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(); if (!printer) { Nan::ThrowTypeError("unable to create fingerprinter"); return; } // set properties on the instance with default values from // GrooveFingerprinter struct Local instance = GNFingerprinter::NewInstance(printer)->ToObject(); GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap(instance); EventContext *context = new EventContext; gn_printer->event_context = context; context->event_cb = new Nan::Callback(info[0].As()); context->printer = printer; Nan::Set(instance, Nan::New("infoQueueSize").ToLocalChecked(), Nan::New(printer->info_queue_size)); Nan::Set(instance, Nan::New("sinkBufferSize").ToLocalChecked(), Nan::New(printer->sink_buffer_size)); info.GetReturnValue().Set(instance); } NAN_METHOD(GNFingerprinter::Position) { Nan::HandleScope scope; GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap(info.This()); GrooveFingerprinter *printer = gn_printer->printer; GroovePlaylistItem *item; double pos; groove_fingerprinter_position(printer, &item, &pos); Local obj = Nan::New(); Nan::Set(obj, Nan::New("pos").ToLocalChecked(), Nan::New(pos)); if (item) { Nan::Set(obj, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item)); } else { Nan::Set(obj, Nan::New("item").ToLocalChecked(), Nan::Null()); } info.GetReturnValue().Set(obj); } NAN_METHOD(GNFingerprinter::GetInfo) { Nan::HandleScope scope; GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap(info.This()); GrooveFingerprinter *printer = gn_printer->printer; GrooveFingerprinterInfo print_info; if (groove_fingerprinter_info_get(printer, &print_info, 0) == 1) { Local object = Nan::New(); if (print_info.fingerprint) { Local int_list = Nan::New(); for (int i = 0; i < print_info.fingerprint_size; i += 1) { Nan::Set(int_list, Nan::New(i), Nan::New(print_info.fingerprint[i])); } Nan::Set(object, Nan::New("fingerprint").ToLocalChecked(), int_list); } else { Nan::Set(object, Nan::New("fingerprint").ToLocalChecked(), Nan::Null()); } Nan::Set(object, Nan::New("duration").ToLocalChecked(), Nan::New(print_info.duration)); if (print_info.item) { Nan::Set(object, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(print_info.item)); } else { Nan::Set(object, Nan::New("item").ToLocalChecked(), Nan::Null()); } groove_fingerprinter_free_info(&print_info); info.GetReturnValue().Set(object); } else { info.GetReturnValue().Set(Nan::Null()); } } struct AttachReq { uv_work_t req; Nan::Callback *callback; GrooveFingerprinter *printer; GroovePlaylist *playlist; int errcode; Nan::Persistent instance; GNFingerprinter::EventContext *event_context; }; static void EventAsyncCb(uv_async_t *handle #if UV_VERSION_MAJOR == 0 , int status #endif ) { Nan::HandleScope scope; GNFingerprinter::EventContext *context = reinterpret_cast(handle->data); // call callback signaling that there is info ready const unsigned argc = 1; Local 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) { GNFingerprinter::EventContext *context = reinterpret_cast(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); } } static void AttachAsync(uv_work_t *req) { AttachReq *r = reinterpret_cast(req->data); r->errcode = groove_fingerprinter_attach(r->printer, r->playlist); GNFingerprinter::EventContext *context = r->event_context; uv_cond_init(&context->cond); uv_mutex_init(&context->mutex); uv_async_init(uv_default_loop(), &context->event_async, EventAsyncCb); context->event_async.data = context; uv_thread_create(&context->event_thread, EventThreadEntry, context); } static void AttachAfter(uv_work_t *req) { Nan::HandleScope scope; AttachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("fingerprinter attach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); r->instance.Reset(); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNFingerprinter::Attach) { Nan::HandleScope scope; GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap(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; } Local instance = info.This(); GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info[0]->ToObject()); AttachReq *request = new AttachReq; request->req.data = request; request->callback = new Nan::Callback(info[1].As()); request->instance.Reset(info.This()); request->playlist = gn_playlist->playlist; GrooveFingerprinter *printer = gn_printer->printer; request->printer = printer; request->event_context = gn_printer->event_context; // copy the properties from our instance to the player printer->info_queue_size = (int)instance->Get(Nan::New("infoQueueSize").ToLocalChecked())->NumberValue(); printer->sink_buffer_size = (int)instance->Get(Nan::New("sinkBufferSize").ToLocalChecked())->NumberValue(); uv_queue_work(uv_default_loop(), &request->req, AttachAsync, (uv_after_work_cb)AttachAfter); return; } struct DetachReq { uv_work_t req; GrooveFingerprinter *printer; Nan::Callback *callback; int errcode; GNFingerprinter::EventContext *event_context; }; static void DetachAsyncFree(uv_handle_t *handle) { GNFingerprinter::EventContext *context = reinterpret_cast(handle->data); delete context->event_cb; delete context; } static void DetachAsync(uv_work_t *req) { DetachReq *r = reinterpret_cast(req->data); r->errcode = groove_fingerprinter_detach(r->printer); uv_cond_signal(&r->event_context->cond); uv_thread_join(&r->event_context->event_thread); uv_cond_destroy(&r->event_context->cond); uv_mutex_destroy(&r->event_context->mutex); uv_close(reinterpret_cast(&r->event_context->event_async), DetachAsyncFree); } static void DetachAfter(uv_work_t *req) { Nan::HandleScope scope; DetachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("fingerprinter detach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNFingerprinter::Detach) { Nan::HandleScope scope; GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap(info.This()); if (info.Length() < 1 || !info[0]->IsFunction()) { Nan::ThrowTypeError("Expected function arg[0]"); return; } DetachReq *request = new DetachReq; request->req.data = request; request->callback = new Nan::Callback(info[0].As()); request->printer = gn_printer->printer; request->event_context = gn_printer->event_context; uv_queue_work(uv_default_loop(), &request->req, DetachAsync, (uv_after_work_cb)DetachAfter); return; } NAN_METHOD(GNFingerprinter::Encode) { Nan::HandleScope scope; if (info.Length() < 1 || !info[0]->IsArray()) { Nan::ThrowTypeError("Expected Array arg[0]"); return; } Local int_list = Local::Cast(info[0]); int len = int_list->Length(); int32_t *raw_fingerprint = new int32_t[len]; for (int i = 0; i < len; i += 1) { double val = int_list->Get(Nan::New(i))->NumberValue(); raw_fingerprint[i] = (int32_t)val; } char *fingerprint; groove_fingerprinter_encode(raw_fingerprint, len, &fingerprint); delete[] raw_fingerprint; Local js_fingerprint = Nan::New(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; int32_t *raw_fingerprint; int raw_fingerprint_len; groove_fingerprinter_decode(fingerprint, &raw_fingerprint, &raw_fingerprint_len); Local int_list = Nan::New(); for (int i = 0; i < raw_fingerprint_len; i += 1) { Nan::Set(int_list, Nan::New(i), Nan::New(raw_fingerprint[i])); } groove_fingerprinter_dealloc(raw_fingerprint); info.GetReturnValue().Set(int_list); } node-groove-2.5.0/src/fingerprinter.h000066400000000000000000000017721263365216100175740ustar00rootroot00000000000000#ifndef GN_FINGERPRINTER_H #define GN_FINGERPRINTER_H #include #include #include using Nan::Callback; class GNFingerprinter : public node::ObjectWrap { public: static void Init(); static v8::Handle 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; 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 node-groove-2.5.0/src/groove.cc000066400000000000000000000066671263365216100163650ustar00rootroot00000000000000#include #include #include #include "file.h" #include "player.h" #include "playlist.h" #include "playlist_item.h" #include "loudness_detector.h" #include "fingerprinter.h" #include "encoder.h" using namespace v8; 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(GetDevices) { Nan::HandleScope scope; Local deviceList = Nan::New(); int device_count = groove_device_count(); for (int i = 0; i < device_count; i += 1) { const char *name = groove_device_name(i); deviceList->Set(Nan::New(i), Nan::New(name).ToLocalChecked()); } info.GetReturnValue().Set(deviceList); } NAN_METHOD(GetVersion) { Nan::HandleScope scope; Local version = Nan::New(); Nan::Set(version, Nan::New("major").ToLocalChecked(), Nan::New(groove_version_major())); Nan::Set(version, Nan::New("minor").ToLocalChecked(), Nan::New(groove_version_minor())); Nan::Set(version, Nan::New("patch").ToLocalChecked(), Nan::New(groove_version_patch())); info.GetReturnValue().Set(version); } template static void SetProperty(target_t obj, const char* name, double n) { Nan::Set(obj, Nan::New(name).ToLocalChecked(), Nan::New(n)); } template static void SetMethod(target_t obj, const char* name, FNPTR fn) { Nan::Set(obj, Nan::New(name).ToLocalChecked(), Nan::GetFunction(Nan::New(fn)).ToLocalChecked()); } NAN_MODULE_INIT(Initialize) { groove_init(); atexit(groove_finish); GNFile::Init(); GNPlayer::Init(); GNPlaylist::Init(); GNPlaylistItem::Init(); GNLoudnessDetector::Init(); GNEncoder::Init(); GNFingerprinter::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", GROOVE_EVERY_SINK_FULL); SetProperty(target, "ANY_SINK_FULL", GROOVE_ANY_SINK_FULL); SetProperty(target, "_EVENT_NOWPLAYING", GROOVE_EVENT_NOWPLAYING); SetProperty(target, "_EVENT_BUFFERUNDERRUN", GROOVE_EVENT_BUFFERUNDERRUN); SetProperty(target, "_EVENT_DEVICEREOPENED", GROOVE_EVENT_DEVICEREOPENED); SetMethod(target, "setLogging", SetLogging); SetMethod(target, "getDevices", GetDevices); 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, "encodeFingerprint", GNFingerprinter::Encode); SetMethod(target, "decodeFingerprint", GNFingerprinter::Decode); } NODE_MODULE(groove, Initialize) node-groove-2.5.0/src/loudness_detector.cc000066400000000000000000000245351263365216100206030ustar00rootroot00000000000000#include "loudness_detector.h" #include "playlist_item.h" #include "playlist.h" using namespace v8; GNLoudnessDetector::GNLoudnessDetector() {}; GNLoudnessDetector::~GNLoudnessDetector() { groove_loudness_detector_destroy(detector); }; static Nan::Persistent constructor; void GNLoudnessDetector::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("GrooveLoudnessDetector").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(GNLoudnessDetector::New) { Nan::HandleScope scope; GNLoudnessDetector *obj = new GNLoudnessDetector(); obj->Wrap(info.This()); info.GetReturnValue().Set(info.This()); } Handle GNLoudnessDetector::NewInstance(GrooveLoudnessDetector *detector) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap(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(); if (!detector) { Nan::ThrowTypeError("unable to create loudness detector"); return; } // set properties on the instance with default values from // GrooveLoudnessDetector struct Local instance = GNLoudnessDetector::NewInstance(detector)->ToObject(); GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap(instance); EventContext *context = new EventContext; gn_detector->event_context = context; context->event_cb = new Nan::Callback(info[0].As()); context->detector = detector; Nan::Set(instance, Nan::New("infoQueueSize").ToLocalChecked(), Nan::New(detector->info_queue_size)); Nan::Set(instance, Nan::New("disableAlbum").ToLocalChecked(), Nan::New(detector->disable_album)); Nan::Set(instance, Nan::New("sinkBufferSize").ToLocalChecked(), Nan::New(detector->sink_buffer_size)); info.GetReturnValue().Set(instance); } NAN_METHOD(GNLoudnessDetector::Position) { Nan::HandleScope scope; GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap(info.This()); GrooveLoudnessDetector *detector = gn_detector->detector; GroovePlaylistItem *item; double pos; groove_loudness_detector_position(detector, &item, &pos); Local obj = Nan::New(); Nan::Set(obj, Nan::New("pos").ToLocalChecked(), Nan::New(pos)); if (item) { Nan::Set(obj, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item)); } else { Nan::Set(obj, Nan::New("item").ToLocalChecked(), Nan::Null()); } info.GetReturnValue().Set(obj); } NAN_METHOD(GNLoudnessDetector::GetInfo) { Nan::HandleScope scope; GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap(info.This()); GrooveLoudnessDetector *detector = gn_detector->detector; GrooveLoudnessDetectorInfo loudness_info; if (groove_loudness_detector_info_get(detector, &loudness_info, 0) == 1) { Local object = Nan::New(); Nan::Set(object, Nan::New("loudness").ToLocalChecked(), Nan::New(loudness_info.loudness)); Nan::Set(object, Nan::New("peak").ToLocalChecked(), Nan::New(loudness_info.peak)); Nan::Set(object, Nan::New("duration").ToLocalChecked(), Nan::New(loudness_info.duration)); if (loudness_info.item) { Nan::Set(object, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(loudness_info.item)); } else { Nan::Set(object, Nan::New("item").ToLocalChecked(), Nan::Null()); } info.GetReturnValue().Set(object); } else { info.GetReturnValue().Set(Nan::Null()); } } struct AttachReq { uv_work_t req; Nan::Callback *callback; GrooveLoudnessDetector *detector; GroovePlaylist *playlist; int errcode; Nan::Persistent instance; GNLoudnessDetector::EventContext *event_context; }; static void EventAsyncCb(uv_async_t *handle #if UV_VERSION_MAJOR == 0 , int status #endif ) { Nan::HandleScope scope; GNLoudnessDetector::EventContext *context = reinterpret_cast(handle->data); // call callback signaling that there is info ready const unsigned argc = 1; Local 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(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); } } static void AttachAsync(uv_work_t *req) { AttachReq *r = reinterpret_cast(req->data); r->errcode = groove_loudness_detector_attach(r->detector, r->playlist); GNLoudnessDetector::EventContext *context = r->event_context; uv_cond_init(&context->cond); uv_mutex_init(&context->mutex); uv_async_init(uv_default_loop(), &context->event_async, EventAsyncCb); context->event_async.data = context; uv_thread_create(&context->event_thread, EventThreadEntry, context); } static void AttachAfter(uv_work_t *req) { Nan::HandleScope scope; AttachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("loudness detector attach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); r->instance.Reset(); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNLoudnessDetector::Attach) { Nan::HandleScope scope; GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap(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; } Local instance = info.This(); GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info[0]->ToObject()); AttachReq *request = new AttachReq; request->req.data = request; request->callback = new Nan::Callback(info[1].As()); request->instance.Reset(info.This()); request->playlist = gn_playlist->playlist; GrooveLoudnessDetector *detector = gn_detector->detector; request->detector = detector; request->event_context = gn_detector->event_context; // copy the properties from our instance to the player detector->info_queue_size = (int)instance->Get(Nan::New("infoQueueSize").ToLocalChecked())->NumberValue(); detector->sink_buffer_size = (int)instance->Get(Nan::New("sinkBufferSize").ToLocalChecked())->BooleanValue(); detector->disable_album = (int)instance->Get(Nan::New("disableAlbum").ToLocalChecked())->BooleanValue(); uv_queue_work(uv_default_loop(), &request->req, AttachAsync, (uv_after_work_cb)AttachAfter); return; } struct DetachReq { uv_work_t req; GrooveLoudnessDetector *detector; Nan::Callback *callback; int errcode; GNLoudnessDetector::EventContext *event_context; }; static void DetachAsyncFree(uv_handle_t *handle) { GNLoudnessDetector::EventContext *context = reinterpret_cast(handle->data); delete context->event_cb; delete context; } static void DetachAsync(uv_work_t *req) { DetachReq *r = reinterpret_cast(req->data); r->errcode = groove_loudness_detector_detach(r->detector); uv_cond_signal(&r->event_context->cond); uv_thread_join(&r->event_context->event_thread); uv_cond_destroy(&r->event_context->cond); uv_mutex_destroy(&r->event_context->mutex); uv_close(reinterpret_cast(&r->event_context->event_async), DetachAsyncFree); } static void DetachAfter(uv_work_t *req) { Nan::HandleScope scope; DetachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("loudness detector detach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNLoudnessDetector::Detach) { Nan::HandleScope scope; GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap(info.This()); if (info.Length() < 1 || !info[0]->IsFunction()) { Nan::ThrowTypeError("Expected function arg[0]"); return; } DetachReq *request = new DetachReq; request->req.data = request; request->callback = new Nan::Callback(info[0].As()); request->detector = gn_detector->detector; request->event_context = gn_detector->event_context; uv_queue_work(uv_default_loop(), &request->req, DetachAsync, (uv_after_work_cb)DetachAfter); return; } node-groove-2.5.0/src/loudness_detector.h000066400000000000000000000016651263365216100204440ustar00rootroot00000000000000#ifndef GN_LOUDNESS_DETECTOR_H #define GN_LOUDNESS_DETECTOR_H #include #include #include class GNLoudnessDetector : public node::ObjectWrap { public: static void Init(); static v8::Handle 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 node-groove-2.5.0/src/player.cc000066400000000000000000000303011263365216100163360ustar00rootroot00000000000000#include #include "player.h" #include "playlist.h" #include "playlist_item.h" using namespace v8; GNPlayer::GNPlayer() {}; GNPlayer::~GNPlayer() { groove_player_destroy(player); delete event_context->event_cb; delete event_context; }; static Nan::Persistent constructor; void GNPlayer::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("GroovePlayer").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(2); Local proto = tpl->PrototypeTemplate(); // Fields Nan::SetAccessor(proto, Nan::New("id").ToLocalChecked(), GetId); Nan::SetAccessor(proto, Nan::New("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()); } Handle GNPlayer::NewInstance(GroovePlayer *player) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNPlayer *gn_player = node::ObjectWrap::Unwrap(instance); gn_player->player = player; return scope.Escape(instance); } NAN_GETTER(GNPlayer::GetId) { Nan::HandleScope scope; GNPlayer *gn_player = node::ObjectWrap::Unwrap(info.This()); char buf[64]; snprintf(buf, sizeof(buf), "%p", gn_player->player); info.GetReturnValue().Set(Nan::New(buf).ToLocalChecked()); } NAN_GETTER(GNPlayer::GetPlaylist) { Nan::HandleScope scope; GNPlayer *gn_player = node::ObjectWrap::Unwrap(info.This()); GroovePlaylist *playlist = gn_player->player->playlist; if (playlist) { Local 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(info.This()); GroovePlaylistItem *item; double pos; groove_player_position(gn_player->player, &item, &pos); Local obj = Nan::New(); Nan::Set(obj, Nan::New("pos").ToLocalChecked(), Nan::New(pos)); if (item) { Local tmp = GNPlaylistItem::NewInstance(item); Nan::Set(obj, Nan::New("item").ToLocalChecked(), tmp); } else { Nan::Set(obj, Nan::New("item").ToLocalChecked(), Nan::Null()); } info.GetReturnValue().Set(obj); } struct AttachReq { uv_work_t req; Nan::Callback *callback; GroovePlayer *player; GroovePlaylist *playlist; int errcode; Nan::Persistent instance; int device_index; GNPlayer::EventContext *event_context; }; static void EventAsyncCb(uv_async_t *handle #if UV_VERSION_MAJOR == 0 , int status #endif ) { Nan::HandleScope scope; GNPlayer::EventContext *context = reinterpret_cast(handle->data); // flush events GroovePlayerEvent event; const unsigned argc = 1; Local argv[argc]; while (groove_player_event_get(context->player, &event, 0) > 0) { argv[0] = Nan::New(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 EventThreadEntry(void *arg) { GNPlayer::EventContext *context = reinterpret_cast(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); } } static void AttachAsync(uv_work_t *req) { AttachReq *r = reinterpret_cast(req->data); r->player->device_index = r->device_index; r->errcode = groove_player_attach(r->player, r->playlist); GNPlayer::EventContext *context = r->event_context; uv_cond_init(&context->cond); uv_mutex_init(&context->mutex); uv_async_init(uv_default_loop(), &context->event_async, EventAsyncCb); context->event_async.data = context; uv_thread_create(&context->event_thread, EventThreadEntry, context); } static void AttachAfter(uv_work_t *req) { Nan::HandleScope scope; AttachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("player attach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); Local actualAudioFormat = Nan::New(); actualAudioFormat->Set(Nan::New("sampleRate").ToLocalChecked(), Nan::New(r->player->actual_audio_format.sample_rate)); actualAudioFormat->Set(Nan::New("channelLayout").ToLocalChecked(), Nan::New(r->player->actual_audio_format.channel_layout)); actualAudioFormat->Set(Nan::New("sampleFormat").ToLocalChecked(), Nan::New(r->player->actual_audio_format.sample_fmt)); Local o = Nan::New(r->instance); Nan::Set(o, Nan::New("actualAudioFormat").ToLocalChecked(), actualAudioFormat); r->instance.Reset(o); } TryCatch try_catch; r->callback->Call(argc, argv); r->instance.Reset(); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } 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(); Local instance = NewInstance(player)->ToObject(); GNPlayer *gn_player = node::ObjectWrap::Unwrap(instance); EventContext *context = new EventContext; gn_player->event_context = context; context->event_cb = new Nan::Callback(info[0].As()); context->player = player; // set properties on the instance with default values from // GroovePlayer struct Local targetAudioFormat = Nan::New(); Nan::Set(targetAudioFormat, Nan::New("sampleRate").ToLocalChecked(), Nan::New(player->target_audio_format.sample_rate)); Nan::Set(targetAudioFormat, Nan::New("channelLayout").ToLocalChecked(), Nan::New(player->target_audio_format.channel_layout)); Nan::Set(targetAudioFormat, Nan::New("sampleFormat").ToLocalChecked(), Nan::New(player->target_audio_format.sample_fmt)); instance->Set(Nan::New("deviceIndex").ToLocalChecked(), Nan::Null()); instance->Set(Nan::New("actualAudioFormat").ToLocalChecked(), Nan::Null()); instance->Set(Nan::New("targetAudioFormat").ToLocalChecked(), targetAudioFormat); instance->Set(Nan::New("deviceBufferSize").ToLocalChecked(), Nan::New(player->device_buffer_size)); instance->Set(Nan::New("sinkBufferSize").ToLocalChecked(), Nan::New(player->sink_buffer_size)); info.GetReturnValue().Set(instance); } NAN_METHOD(GNPlayer::Attach) { Nan::HandleScope scope; GNPlayer *gn_player = node::ObjectWrap::Unwrap(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; } Local instance = info.This(); Local targetAudioFormatValue = instance->Get(Nan::New("targetAudioFormat").ToLocalChecked()); if (!targetAudioFormatValue->IsObject()) { Nan::ThrowTypeError("Expected targetAudioFormat to be an object"); return; } GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info[0]->ToObject()); AttachReq *request = new AttachReq; request->req.data = request; request->callback = new Nan::Callback(info[1].As()); request->instance.Reset(info.This()); request->playlist = gn_playlist->playlist; GroovePlayer *player = gn_player->player; request->player = player; request->event_context = gn_player->event_context; // copy the properties from our instance to the player Local deviceIndex = instance->Get(Nan::New("deviceIndex").ToLocalChecked()); Local useExactAudioFormat = instance->Get(Nan::New("useExactAudioFormat").ToLocalChecked()); player->use_exact_audio_format = useExactAudioFormat->BooleanValue(); if (deviceIndex->IsNull() || deviceIndex->IsUndefined()) { request->device_index = -1; } else { request->device_index = (int) deviceIndex->NumberValue(); } Local targetAudioFormat = targetAudioFormatValue->ToObject(); Local sampleRate = targetAudioFormat->Get(Nan::New("sampleRate").ToLocalChecked()); double sample_rate = sampleRate->NumberValue(); double channel_layout = targetAudioFormat->Get(Nan::New("channelLayout").ToLocalChecked())->NumberValue(); double sample_fmt = targetAudioFormat->Get(Nan::New("sampleFormat").ToLocalChecked())->NumberValue(); player->target_audio_format.sample_rate = (int)sample_rate; player->target_audio_format.channel_layout = (int)channel_layout; player->target_audio_format.sample_fmt = (enum GrooveSampleFormat)(int)sample_fmt; double device_buffer_size = instance->Get(Nan::New("deviceBufferSize").ToLocalChecked())->NumberValue(); player->device_buffer_size = (int)device_buffer_size; double sink_buffer_size = instance->Get(Nan::New("sinkBufferSize").ToLocalChecked())->NumberValue(); player->sink_buffer_size = (int)sink_buffer_size; uv_queue_work(uv_default_loop(), &request->req, AttachAsync, (uv_after_work_cb)AttachAfter); } struct DetachReq { uv_work_t req; GroovePlayer *player; Nan::Callback *callback; int errcode; GNPlayer::EventContext *event_context; }; static void DetachAsyncFree(uv_handle_t *handle) { } static void DetachAsync(uv_work_t *req) { DetachReq *r = reinterpret_cast(req->data); r->errcode = groove_player_detach(r->player); uv_cond_signal(&r->event_context->cond); uv_thread_join(&r->event_context->event_thread); uv_cond_destroy(&r->event_context->cond); uv_mutex_destroy(&r->event_context->mutex); uv_close(reinterpret_cast(&r->event_context->event_async), DetachAsyncFree); } static void DetachAfter(uv_work_t *req) { Nan::HandleScope scope; DetachReq *r = reinterpret_cast(req->data); const unsigned argc = 1; Local argv[argc]; if (r->errcode < 0) { argv[0] = Exception::Error(Nan::New("player detach failed").ToLocalChecked()); } else { argv[0] = Nan::Null(); } TryCatch try_catch; r->callback->Call(argc, argv); delete r->callback; delete r; if (try_catch.HasCaught()) { node::FatalException(try_catch); } } NAN_METHOD(GNPlayer::Detach) { Nan::HandleScope scope; GNPlayer *gn_player = node::ObjectWrap::Unwrap(info.This()); if (info.Length() < 1 || !info[0]->IsFunction()) { Nan::ThrowTypeError("Expected function arg[0]"); return; } DetachReq *request = new DetachReq; request->req.data = request; request->callback = new Nan::Callback(info[0].As()); request->player = gn_player->player; request->event_context = gn_player->event_context; uv_queue_work(uv_default_loop(), &request->req, DetachAsync, (uv_after_work_cb)DetachAfter); return; } node-groove-2.5.0/src/player.h000066400000000000000000000016021263365216100162020ustar00rootroot00000000000000#ifndef GN_PLAYER_H #define GN_PLAYER_H #include #include #include class GNPlayer : public node::ObjectWrap { public: static void Init(); static v8::Handle 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 node-groove-2.5.0/src/playlist.cc000066400000000000000000000177671263365216100167300ustar00rootroot00000000000000#include #include "playlist.h" #include "playlist_item.h" #include "file.h" using namespace v8; GNPlaylist::GNPlaylist() { }; GNPlaylist::~GNPlaylist() { // TODO move this somewhere else because we create multiple objects with // the same playlist pointer in player.playlist or encoder.playlist // for example groove_playlist_destroy(playlist); }; static Nan::Persistent constructor; void GNPlaylist::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("GroovePlaylist").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); Local proto = tpl->PrototypeTemplate(); // Fields Nan::SetAccessor(proto, Nan::New("id").ToLocalChecked(), GetId); Nan::SetAccessor(proto, Nan::New("gain").ToLocalChecked(), GetGain); // Methods 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, "setItemGain", SetItemGain); Nan::SetPrototypeMethod(tpl, "setItemPeak", SetItemPeak); 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()); } Handle GNPlaylist::NewInstance(GroovePlaylist *playlist) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(instance); gn_playlist->playlist = playlist; return scope.Escape(instance); } NAN_GETTER(GNPlaylist::GetId) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); char buf[64]; snprintf(buf, sizeof(buf), "%p", gn_playlist->playlist); info.GetReturnValue().Set(Nan::New(buf).ToLocalChecked()); } NAN_GETTER(GNPlaylist::GetGain) { Nan::HandleScope(); GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); info.GetReturnValue().Set(Nan::New(gn_playlist->playlist->gain)); } NAN_METHOD(GNPlaylist::Play) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); groove_playlist_play(gn_playlist->playlist); return; } NAN_METHOD(GNPlaylist::Playlist) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); Local playlist = Nan::New(); GroovePlaylistItem *item = gn_playlist->playlist->head; int i = 0; while (item) { Nan::Set(playlist, Nan::New(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(info.This()); groove_playlist_pause(gn_playlist->playlist); return; } NAN_METHOD(GNPlaylist::Seek) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); GNPlaylistItem *gn_playlist_item = node::ObjectWrap::Unwrap(info[0]->ToObject()); double pos = info[1]->NumberValue(); groove_playlist_seek(gn_playlist->playlist, gn_playlist_item->playlist_item, pos); return; } NAN_METHOD(GNPlaylist::Insert) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); GNFile *gn_file = node::ObjectWrap::Unwrap(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(info[3]->ToObject()); item = gn_pl_item->playlist_item; } GroovePlaylistItem *result = groove_playlist_insert(gn_playlist->playlist, gn_file->file, gain, peak, item); Local tmp = GNPlaylistItem::NewInstance(result); info.GetReturnValue().Set(tmp); } NAN_METHOD(GNPlaylist::Remove) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info[0]->ToObject()); groove_playlist_remove(gn_playlist->playlist, gn_pl_item->playlist_item); return; } NAN_METHOD(GNPlaylist::DecodePosition) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); GroovePlaylistItem *item; double pos = -1.0; groove_playlist_position(gn_playlist->playlist, &item, &pos); Local obj = Nan::New(); Nan::Set(obj, Nan::New("pos").ToLocalChecked(), Nan::New(pos)); if (item) { Nan::Set(obj, Nan::New("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item)); } else { Nan::Set(obj, Nan::New("item").ToLocalChecked(), Nan::Null()); } info.GetReturnValue().Set(obj); } NAN_METHOD(GNPlaylist::Playing) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); int playing = groove_playlist_playing(gn_playlist->playlist); info.GetReturnValue().Set(Nan::New(playing)); } NAN_METHOD(GNPlaylist::Clear) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); groove_playlist_clear(gn_playlist->playlist); } NAN_METHOD(GNPlaylist::Count) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); int count = groove_playlist_count(gn_playlist->playlist); info.GetReturnValue().Set(Nan::New(count)); } NAN_METHOD(GNPlaylist::SetItemGain) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info[0]->ToObject()); double gain = info[1]->NumberValue(); groove_playlist_set_item_gain(gn_playlist->playlist, gn_pl_item->playlist_item, gain); } NAN_METHOD(GNPlaylist::SetItemPeak) { Nan::HandleScope(); GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(info.This()); GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info[0]->ToObject()); double peak = info[1]->NumberValue(); groove_playlist_set_item_peak(gn_playlist->playlist, gn_pl_item->playlist_item, peak); } NAN_METHOD(GNPlaylist::SetGain) { Nan::HandleScope scope; GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap(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(info.This()); groove_playlist_set_fill_mode(gn_playlist->playlist, info[0]->NumberValue()); } NAN_METHOD(GNPlaylist::Create) { Nan::HandleScope scope; GroovePlaylist *playlist = groove_playlist_create(); Local tmp = GNPlaylist::NewInstance(playlist); info.GetReturnValue().Set(tmp); } node-groove-2.5.0/src/playlist.h000066400000000000000000000020471263365216100165530ustar00rootroot00000000000000#ifndef GN_PLAYLIST_H #define GN_PLAYLIST_H #include #include #include class GNPlaylist : public node::ObjectWrap { public: static void Init(); static v8::Handle NewInstance(GroovePlaylist *playlist); static NAN_METHOD(Create); GroovePlaylist *playlist; private: GNPlaylist(); ~GNPlaylist(); static NAN_METHOD(New); 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(SetItemGain); static NAN_METHOD(SetItemPeak); static NAN_METHOD(SetGain); static NAN_METHOD(SetFillMode); }; #endif node-groove-2.5.0/src/playlist_item.cc000066400000000000000000000046721263365216100177350ustar00rootroot00000000000000#include "playlist_item.h" #include "file.h" using namespace v8; GNPlaylistItem::GNPlaylistItem() { }; GNPlaylistItem::~GNPlaylistItem() { }; static Nan::Persistent constructor; void GNPlaylistItem::Init() { // Prepare constructor template Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("GroovePlaylistItem").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); Local proto = tpl->PrototypeTemplate(); // Fields Nan::SetAccessor(proto, Nan::New("file").ToLocalChecked(), GetFile); Nan::SetAccessor(proto, Nan::New("id").ToLocalChecked(), GetId); Nan::SetAccessor(proto, Nan::New("gain").ToLocalChecked(), GetGain); Nan::SetAccessor(proto, Nan::New("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()); } Handle GNPlaylistItem::NewInstance(GroovePlaylistItem *playlist_item) { Nan::EscapableHandleScope scope; Local cons = Nan::New(constructor); Local instance = cons->NewInstance(); GNPlaylistItem *gn_playlist_item = node::ObjectWrap::Unwrap(instance); gn_playlist_item->playlist_item = playlist_item; return scope.Escape(instance); } NAN_GETTER(GNPlaylistItem::GetFile) { GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info.This()); Local tmp = GNFile::NewInstance(gn_pl_item->playlist_item->file); info.GetReturnValue().Set(tmp); } NAN_GETTER(GNPlaylistItem::GetId) { GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info.This()); char buf[64]; snprintf(buf, sizeof(buf), "%p", gn_pl_item->playlist_item); info.GetReturnValue().Set(Nan::New(buf).ToLocalChecked()); } NAN_GETTER(GNPlaylistItem::GetGain) { GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info.This()); double gain = gn_pl_item->playlist_item->gain; info.GetReturnValue().Set(Nan::New(gain)); } NAN_GETTER(GNPlaylistItem::GetPeak) { GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap(info.This()); double peak = gn_pl_item->playlist_item->peak; info.GetReturnValue().Set(Nan::New(peak)); } node-groove-2.5.0/src/playlist_item.h000066400000000000000000000011171263365216100175660ustar00rootroot00000000000000#ifndef GN_PLAYLIST_ITEM_H #define GN_PLAYLIST_ITEM_H #include #include #include class GNPlaylistItem : public node::ObjectWrap { public: static void Init(); static v8::Handle 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 node-groove-2.5.0/test/000077500000000000000000000000001263365216100147265ustar00rootroot00000000000000node-groove-2.5.0/test/danse.ogg000077500000000000000000002545271263365216100165400ustar00rootroot00000000000000OggSsZDvorbisDqOggSsƍvorbis-Xiph.Org libVorbis I 20101101 (Schaufenugget) GENRE=ClassicalDATE=2008-02-08T22:50ALBUM=Danse MacabreTITLE=Danse MacabreBPM (beats per minute)=185}COMMENTS= 00000000 00000210 00000933 0000000001114BBD 00000000 00F7CCBC 00000000 00000000 00000000 00000000 00000000 00000000 Initial key=CSoftware=Logic Pro 8.0.2ARTIST=Kevin MacLeodComposer=Camille Saint-Saënsvorbis)BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d( Ij9g'r9iN8 Q9 &cnkn)% Y@H!RH!b!b!r!r * 2 L2餓N:騣:(B -JL1Vc]|s9s9s BCV BdB!R)r 2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d ")Ifjihm˲,˲ iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r, Y@R,r4Gs4s@BDFHJLNP@OggS*sL:[Vqnqp}xecwLŎd7AK01OsԺX땮ڳ[RT/]$"}<)kF1|l*l?dRz Vܯ$ם?z5r4y07rb6\rt5:W+;+d3?0ƶ"P&:H~ ufɶo:0lg[blS"Zݲ$ vӝd[s6(K]_GmXb {sɭɈ +c" 9W,kH!gg33ցl ͢]xWh%0@u1@^h IRBm&IY<SwtNɾA"LB"Y$f(=0 5TVQrv,Rںp#+96"@jY (LBQ,Ȉ0 #oi"H jCgqnKx|Y6YߊC$scʉ1߳w!"b93}d9T%HDy¢fb.3?FcZ~[aaTsU_zz:t%TQ__L,I4xju\}vKKSTwlobnj?8xdR֛ЂD @Kq[ 4'.vvMDO!5{Ȗ磖ՉKG|jжLU[fv_cO]s>9U2AL4. _YNTI9;9zڹ)<;+LfpYC"5@M"]W@b&ӹV4Kn~{81lO[N?<@![T6f?cuW$l[_ef5 6 e/[m&XjtកZ"C/+]f@hAi X Xh:I8,? 1EqD  b~ 3Nj9ҵh֛wưhԪ\fk]xc\2V?tܺ͛W;(N3`GwPN3"dGdӴn`?څҙs2'ɎcNR*Q!ZOwIuhf:gOc1dcOL0'.n'uOq`VthP!gP=4hNA1͡&+UAW:ugwz3<{wsn* EJmϰyow @9EҚnc6E2 / 'fc\Z-$ -zH8l p(>U#iqJ%E3&ztV|-(g"X,4L)IIv\Vq?}NVQYso>ϲ-L_」d(S/9L9_'Dje6((sQ5mr,blNjt@z>Oμ|>{$lHN10l_69g[q 4wo{hsma=Wv`N&f2f Udzo f U9%R=Z%Ir\.տNtsÛF}f.. 3kQv0,{3mս)ὀuQ:/4dKFOX&G wga>Cԛ Wz MѺGYEb{d M)ehLrOrPOmȤ7nŗ֮|tP,٩{lD_g $[0·&kW+*fgW ߤ6AȎdbdՃM>kx7⶝,GB<@3~Z! lBڨ10/N%92n@" -GZ>FjN)G)f35|4"Y7`,2SU-34M-~Wz`~wSN~ZӋx0!|h{z2]SM\>U]P5;&"kUq!{f, F icXjc!`LӰ+74'23P;ŢyN8~ݏ1s9ɬ8r35i߱vý },#blY{m߭*B=tGa `"ɶ$sW:ieCb@IN6oS) #Vf2kʛkTL@j 3@kp"$, `pB]dE *8IN m&՘d_H;R:~v>\Q\O\#2U rffKYۺ{;'KQ~q;gkrlA:!>xnxz`{ *Ngu>/s=~B{TC O2m +*t,5cϙCl HI#2 Bb^ejǶl, 0T%B$5]& D2\ғօ<<~u?DsEÀ%[.쭑ev ߼mhMlb6g%6g2$z@ݧUzyS#XԻ)ޒ.w!=s m4 8x8:mX dj*"IrVCbCul Q}4Ř_yhWEA <~ Cc#xE+~^&E/sz5:${0?!g}|b9hg?l"Pı<kqE:f@ Z@ +UL 6I - LJtd Q%zT*ZlBSU5=QpaKwF3wCF~0 dL> &$TUuW@&$91ӹ3sCC]PU{&..xaI8:dZL@tJwa+Fu_E rsfb|{qe _3f@6ފCpC$Z;lXVdf kai<;U75.w ѱD%J:=pּ;"U"R j; #ø9w!GA4L⪪uHQt`M&!DdS=E&_Qfuw6VARh6dtnm|gf'2lpuJȐjƯ}2:*f:{z*3óV}myE?u[SEHqVa(3;snƈxr"6"@.^_ dhh+hxg<σ>R-î-BUZ_4و>};4^}f>Q52 {ݲ[vǜZM^ֲ7Y'kW޼ImԹwwӹ:&g`3Pg/ K#&)3SuN֩n=WցI }dOR^,f9U]/Kۧ`6XE[(|w4 @t=kza3=WO3sM) '0 @y7B]IA]fO` (Pd`0h +N̚ߗ'`e(I@ ̾jFub3ҊOxw[WR6Βh՛MaؓrǓ.ny\ ^u?o?xl褄4{,龓B 9 +)`r'$S@{43 p82X @77E❹V !GOA>Yl!yP0}[43fWC5t&W5}~v0\iZXP l$zb۫N᪆dm- |C M}Ȃ],V\_QI}Q@s̃Kuu$5 I FuI@`f@jƼ߾w /cd s`t^YƟ0ΛaX ـ~,a 6fo}0?ﵖʐt -IާfwǗ5e4d-ީLzoQ wl&* de%鷧g/&!I&. oCfRUTBPJ/D^lceTqM|@bA m9ϐ>ù=b;o 0䕉L/aݝ8W͑մ&R5ťiI޿^>ŐOm8@v; kLRUQU[u-Wt anFЭYا@@_ m -˻7%3 #b! $ѳ4p W;1,h{UvulSj5gc_z~:I(CXTsu?;o6fy1nYk[p(0`:S ^<@3<pyٛÚ_ԈȒ$X`2s[,'I6 n9nA$h3ݔYcԪ&K~4(Vãֆ7z&?n 3o[QXZt33I+֛\>?Wٟ]qBkkHqEͦE \E> L?6z5hwTqAKM7]ߣeHj2i(·`~J`L;ڝ. گP3y姮}^ঁ{hS0L|C6'gDv۷a3}?n|}Yh٫u0_˻1q6\%9{2'˙HTo0n/J D)O-h}\FCDnAh+l t74} #uz_mk&Jffp.pn\cD|\G_)uduߵɻk,ctU6OM6rY [?' [zd;u30Q%dq ,rt*G-ANڹ^ O}3{)ejsH7@4QUɜ.>@7]I BsMT\ށ zQ pxxft9ul;USoqP}Uomu TC)>h b X7H`,uEҜ$IwftL2y/)8'8PPBi>ИkGh'pf,ө*jG+2xÉ{Mu[덣Jqq O0Y"ɩ foL * )#_>܎kUd dn>+J)`VT@fU[: * ܲ ;!3׳uqǸp՗鞘nE*͹Kc>vŴ tw7Ɂ> oZ^ߒkr*#>U@eLMgjY,qu@1d(Ȝivah^Dc!Ź-ˀdF@5ƖM,g&/ up 5GM`6>IDNFrG2(I"uU3 O~TEcUV-$,@$jY{}T/,WN#sl'VjЮ>1ON;M Q> [o}*Bwܭjm @kjZ'  IC<@UT.~LAɺ6wu8n.ut;sp;3_O>CU=*T3P.SUS]c27X3<0w!sB>^ͫC8ja+Ox'fWOne !D HFfkZ-[] [ge(1z7^9^k;z2CBy5zgHc1DT!`5 L&ʎ`mҴ$ z:x@=5g`@_ =0_0 %lO]\^*8w ΙˢYxl~`ԵB~zĭV~ڿLqaxoM7M 4if/w wxAA]l} ~ ah!".oG_031j,Q8ݨu1gK`pQp\7hM޶챪g]!s$"[kjH2b {`r~ĕ:̖HS*kRH*ov%%VJ D$1`~=7P>i4A 0n4WI @8[6@Rhc?p/) Pyh(4e99*1Jm Yg(1p7`Amf@baP&T).OggSslCYcmVdcgcxs[80&<J#Bb*`gbO>,՟U _0aMˢ)?gOAX.n>NE?iPthΖOե׵tp7|N9Λ$$yޚ>n sC@ B=˲R XF=S3V@&EWDmg@FL{jZ"$2Uq{T&sj~{Jܥ6ve]_ހ7~}fYB&-*TMqBl dӚ/CdeQJAѲ@q5(͑'?Zo@Oi?B`hџ׬OYwƲIg Luf*]8>@=ӏeңs\Kx>uXMߨގ=qV=$i#"@$Gpa@~w lOü<`b2Fa,ƛ տ5z,ՙPZ.Ӵ[E b{Qx/ٳ\Q_wc6?ȖiH฻gs_\nqo5 siA9;Q}o#C,E}a۱e,9O[8~t W5,bAD[ˏd0>J͐'>PFzH:2B 2*Ƿ<ᧄujtefZIbwhY//s؍ޓ{4< rv7չCyAf+Q!(l3QVVOZa303_&h{I2JȫLJaڍ=CD|x͙ [;/$Snn {Huetӿ'+d}Љs1}?,T~PP5EeﬠO8x cXyRd Fm}+SHoϑ€ Ž&ຬ]wZjU䐉jqkpF7}4w{K2#,1*j.Uj.3$?ϫŧCO20hf|k!ͤ"1ఐmc eZɂ;tj!P`ȿw6@IaI(ූc7 ;{i`rjF)!`j ,7E3wlEy=Zk{ܹϘ7=+Ykx6vꆝ` 1+Bpkx||!^Z2,Bs yg=,XQ9Km=$fP]iȒqZ<=B1B??,HOQwlXEjU Q]v0&IoCn?+᷆5M5ɪ^F6בˀ{'~߿`,δp6$pBqϳZ )<4 Ý+@3U@7als=ʂ]7EVכ&]x9vjm_To Փ ,T[ oF(yI!S;L d글ui@ңhª=@@ 8Wk& ljt2Lq`Ɗd `d8,#AZa/djs)(D(P> 58T* H5&e<:4t1lYC5-3KuI> t_]^璜-7/vsPEኧh>f[y%56 uiIG< MdRuu? .R@PP I?͋:4ACvxk 99>\5u6d{ӽ|'Jf>i]/O鄄N]Xhe1)f56k4Apg-|kx //?,"ldRKpٓ#U0R2Ȱkݚ$dhU@rsB=SM-J*k<-{ 9`ZUpkH0V–ªjJA)y;@u7jFmkuYۃ2j{ʯJOJrCd[vy֬n{ΩJRm'~}N@͖ P PCF cx8""Iq\)P$]DLh[}7T?``yX} ‡é $@֮cZS4sX1[7ۨj}K`^^`@Ց@ejq,/$mA*v*$3Qހc֛_[(% iXgZuFjIrOSpg|ߎ3Q0?5 B±   `BOu;hFym|m @T&,yYG=P,k^  МS,S%5NI kTi2"TsVQ9fmt%s$s 럐@wKȧ˥AD"Y bX,cؙ@$oֽ Xor0Á;u @3K?3UPEo6oN1,Iasy*`voȭ"@I@!bbQPPǚ)Ÿ_g+CP ~ͩk RFL&%~"ɹHĉB1abooT4opiIh2(ٴ+UŒ$y\>БOi7?߽72 2Z%À1 [~n H@MWvsmgzGg9md3o]}~_w+1@Ur1,{î眷Kt.K/b@5PJ`ibdП "dTew֣ٔ o8D~ͨS D^%;ʈQt=xKϳcK Fz"ks7gI/SU 2yEʆjS[^G异ҹf׃~ZAA$34-+x?@ aF_M=[;{ 2b1eJo Pl^2S eka27..f-(8 wLύiY}T3c~.rke-l=wZbXBBqm~}p=T `Y{8 ~ir)CW:J"!N*TA*D-m3>&syP@ο(HXAPKFL21o?]x.ھKE]34q&;yܮ٫fBFz(SU_5EMFˣ25g2fޝ@2xzgJ@\AO?/@&P``bpKtp*6,m30V`Pr+YZ3= V׋iTK2HˢOggSsD*'oSInljtss}N_^_zLjʆ^YA01 D_ h0n3q F8^f Pեd03.;lqlvF]~w`&gy)B/gJ9{J)l.ZO.Y{A'yiFw$h3唱|e[S|!C}r/f {D$]U<ʱjtc/[ J!z3S'wLW/l% o3tB'w1?ɦ<3M[0#|NM<(F  D,]j!vy:IY/,F 0`euPU-kJRVҮq0t}I+~~p-whjԚK@MI]z0R x}kiIj7;k\X7^3[L}WKi<ߵv s C=m[*ZN0PY/=CGg7y@APYh*EA^C]Ah@. o6 0jgq]зHpMAJST;77p HS8<` .;8 'xc#UU FgMvPAGn?k-ŝuS.XSq,O (#2JHfA'կx-p&zyFw[2@3MG<0PO MצXxujl4qI5\~@ }Xߡsw"@<@ $ 3=M+g˫!d@G -XL[9.`Y"0P`i-azt}oy?'D7IA%hIPUmjU~`XмJ#5(uTde&%>mliS@f4bHHYJJXrxyeo-EUeBg8 @$Y5{†&O f3h Ln606 w][Ԑ4(UYV;RQMZ(Ilx] NY>]\;0v x (䯝 ˓5v.pv\#kr  /BZu~~i{$ׄZCC6Af?\ak֕_RS{n|aΠ)$O;*5JIv/=mfrbN&m1־5k| ߖ|7 $K\m]H4#p@? 4@/ p 0,`7-}d]I&-)A \v()=6^ű?K[j(~y 7LKumͼM97gR!H0R90# ``N\ W<^-ˀ(>j ND5ĭ[KY*zr3$ 'zKA,`> ι3wV@Lvm?zJ,Zgث, !}n}~M.50B1kÙ!p?w'D0m9f`L~G?} 叇|/(6|d/[X;>wn辀]@BVOw.Sذ<6رG.U҃2(a4/fYJ/ZSФ%cHx8!EU0 lXuܞwxV@zMym0?Q8< f&X-XMn~*aVBgo<"}\NPl ֱyD f_3IfL/ԙ s욭NjDeC U}˚z~3K}wP;eujyqJ\xf) ingFy3 ,,`amYg-5lY$٢:t)!ϛh^D{Y& -3O6@k@(X={qρnDVv?@w/X@`xx$09`b8FA70o < ZIy$)y ,,LpˎeشLwd@cod  5,``3{aT7 M={pOYͰ_(CC$"P+@8P`=, hMnj2XsBk\~yh](ۼ*JI2H5Cxu]z9f$H83ۧݘPp YhCEjRY$KDwm4~gי/X;m5?x էSl{̞Q&p3TJPYMg*di]YzK !ifϒ}璐lh}qFI'h`Y`?P  @ET)֦Zk`L]^>x΢X(kidl˟fj)LM~؀L,7rg3Tu͸*`VjH 0(C$p@| k3Q TJ CȌzJ-F -y֜ݿnG4b ! ? 'wVB%{BkqNL~زJ>=TBո :hKm j\Xr$U&;<'/ OqԾ,詫LĞ.-,9F2=H M $IwW M VR>j9+SǑqt2ȲWMe;FrTr6Zrj15U:)g' Knֵ푶9vMFH3߇·4Y5Mj;}_,DI6,4V;B LRQ`lnm.י_Gɩ$o]aYuZvP6n<@pךUkj CU9$"~?n?;g,ckk0vdfR]@MP8g윥cw2=}ndSK6r}No +#"E\dTI|+j+:g﷩>0v.T6$qF'ʼ 3s2 ]goQ}Qt&kNrS dEAP`>wOA^`->`KTe0 U4N1!zVhC7 dt4Jydx$/~ME_,| ,,l=Zx#K ?@ eA2KXSK"ƃ_Tk:oNqjP a3L3ӌj?T*4_-v_t4l~Y&:O\@ǝ__;O>XUϤuU; @CW՟|Ef$#nJc. "!= Cb;3JE;2SS75**4("SI3UV3)W$'߬ݫo76FC8s.pP " LA^ m+&{ʰ uvbQA cadVY"$d@ًy]I`#Ӎ/V#f֢4y T~U;])$<|,Y07?~y=|4ԛEi.%iffgN{덖%lH융XrtxkFb\w?θ83*"d|^Ǒضܗ=XZ8墿@@UIѝpHON*(^ (Gr^ W?]/kŪ e\Nc_eqvΘi7gی.*7/r"\Z*o 7n^NNy@;D$ՅMģfД"jTTEs┡ @aADƒ[D0  ˺2#a@7J b3 a@ eU_*4pj`^5SC,^@~t&gkX.ha>~0Sn]fvC^׭^x7W=3֝%l'|xC#>Y3SWtw|c/PA$HT̜BHRCd86tS_ήIo(^");3G'IjZ+0fesN;ȫ S!P8և 4DZvbI j^fj} zU EFoU.Z uk&o݌Wx0!ϬQ{[222&l@X& @.1E\T]kTWY1Ӑ3 tS$]b:̈0N_*߹RN/=Vbc.? bmDD=P"šs 5q<qEIl,3BF^)>}i1߬]gOQX .XnBxk먩r(xoIM%`` anc᮵SӀfwȹk|Ӭ g>}3 4dp# 8?R-'$ &!(ؾūK!bG_EgnDn˥.B.\joIR`GE+~axi#pF HdcdU..3K.:fœW2-=Ckzex%}Jh?N@|WYU!+)"N>yg]?a6@C^ىdۿf"x} E)8 I (A%iȤ33lbmB-1wT%E n137}6,/ c^L[23h9h-as' *Gp߇ٯq=0fZ=i!QM*hId5Nt# `t06O3s垤 Ȇ ͧkf)J0 >$M Q6$H?lR6|C&ZHCf )$R8fA5H|ǙE)_ÖvV"4tcE4M|m܀n@9~x%@|5@|Xus7^3c *HHju²[ Jm5:e c"R%I&E=RX[SEϢ*` c1  J ﻀ6vXؚ"w [1EUwcXFK= V̼AAZg0b,C|$MV$$7>", BBI l "$uFHbARp&qeAW]Krdp N&5?vh;j2Kv(v $g Vẙ͝qݗNW+kU>a!B dN sAz23}LCSYP ?$}PT?I&L?:E&İ) 2L[W .΄BlǀHHJ!E] +AL ^yC43 `]O%犁zO_ uǺa``ЮBƷe &HhePJR~;fQI@^Owwm T^NjeWpnܭ$UN}2 S3%v׬k|Xwu'k컡p[ lEca]D._Zy85 $=emnϋOs)Ĩ+|s >g|:W$A,o/"PtGl_Q q ȹH 2/Nԯͩz;0ArK~Fu_ Ts*(/oBte@AsEd;גϸ&ywu5BA3dkm=s?4cs?; tt |6:hƯ/j%rSșȟ<8Ф`_ ȀXP/^]Ǥǟ̉"՞oS"Bƀ %` u$OggSsRZ5Ơ"}P [Ku$>]Fh;1l j@g2:LUSx F@Q"&mmedՙ͑k]^a=w귁'UkˮӹF[b,ӧU!>pGv3@|T" ve<& @*ʪHSn&ޓiҧfڠ??/g ;r^l.ŸO.$.MP]ک1UM*{yMLqeu$!,đb {(;<[" u~ƳaK𐼵F= 灱;N3y9ѱNR3D*٧=T x0P@[B,5!~5xG&|%$J K e)$G,gVC6'k{;}S"lP"]bQN^}w;tI#gp@ɇƧeMO ;t $DŽ3"ޥr9}e{vdlm] nWt 0S 6Ek\lTyhY:=Pd =vvuUVDl[o/bg{\Lw۬M$ jt4 ML_z]bNVӕofާü5U'P\˫tFN_nr7k῏mD m""m `@'7mT !a7P4T.3% Ϯpz_$?mlGC'vB^ c% nX=ZJunEvL6DO8L?g s} NT>6!\iv=dD$[fjSn'?[n~F[6둊-j3Y%f7mI̘CB3:\{\U[u׬q5fzg,&ڔg=+(Zq}EV93nb {` gfLuX6%dE AzW-ءfRV<bDҰm%$ l~.Z] oQPo>5Sf7fn5l3Xtfd$ow?v/3)wҧ\n)sRUz6s%t>vS.d $@$sSHzPmk<2kqf%dRQ0PPY2+Za{~.?>!>5[jf#vf睤 k;x/ L8-bt5k n7瞫}\h"3OHS~9<SC$u/xv{2]@r8S.LM^IR-QxHݮXHNbcDc v$ ?njO \ -6c ޫ5)ZJ>9X(=?j~lC/7烯74M1ZUI,];o(GVn2jߴW !%6 ~q ^]׹y3u[+u~6dk/[@fِQ%37>{,  l/\ $SUw2#:,ǯH!";>'og*͜i07wVY)=_82 Z 3 4  7 OyYdAE3?=U*߰;iNj9`p7;5O5gr'ukw0t(4(O%WbT\V0at;Ph83X,*_0>h!8~V( "ۣwɒ́ }R[F%U. 7?~ӷr4uklN J96o'MLV2 .ѹql離U0{P& gs&>[ qCF,nHC ^gdllӲI]8b,7W. S0(*f꾝el;Jv|kLp곎/23ףR9L5vՉ.=7>pP!n"Fߺڬ  ja7Ͷ  gb%- =4DZfY.n@ZL.QfGF˽ I_aM2^{ ,̂-(uJVZ])'tK eLE0FD*#gJHr"\_pv 1!efT+I?n8> ;oT^FcZOhⱳ;RBI?r>6@@|`9Ivo@ļ<;`s8@9wŬ]֩τ99rhwiݜCg;E+ԙv:] tAEd[V0Q;i;4̖g~ζqլ;.u,gvx0;~3@ôȥnU4E0t3?43gv2 w ЉDit%QX $"FJjtOA_]!E=yn;SpgXg3]Ft4c$J}ɷڵޕ;waL rD;&v(\ o<[uwгlΜ S{1t Nr>fO mWK딫!y#UH/p9dD"یFקof'8N~| /S/O$5bGnN<9ُUgL~ޔ DE.MԵδ.8ևg0^w5}f< .ijz.fj:ڐB V8ÈU q2O½e?l__qJk{/h#p@Ii@|Y2?>}B&!!Լ{yB\&?[W©PE`3Zmf?Jb:~{ۗ|ZC$dB[3 ZXH$@m_NAI{#DS=h2ß `a- -9'd"=!<຺d8+3+y iߋq6٬>==T9RT\62.*/2JTb`Fn?OQJn1ljH*guh\I@hw~2%,2^>R%U@PufNZ7ob\Xcq?@at+>Z 0̔$"T;ϕ3ךoW;A3 %#z|='EYFDոFAI"a^xd_zCCII]VbrWWd1>tSDL P0$qP7aU/oYt)m138̊Iȼ:N I<}] e\i| GA0OytI$z ^K u-3joSe F D" >Fo g\2:ZC~;:GhhG@KT&X_WRGKgp˭O7qqC:Ⱥo6ێ6̞}Kiz`|HaSo|Lu~<| s$-3j`e4]2DSYuě,r}D^ |qeԕn?}F *Z@qXf&[yY] 5960Ld@2#i!Gw"HOs>.=wx ŗ^{'$aw8SEe">e}}ʌH8apOtݝm ׈miq"(v̷hZco5y23)Xxc?̠H&J ) 9/5R5x^YRoFfC*FsgLe/+I{Dӗz~v(u=(t*іK3AEQ>CSL!%t$Lͻ*gk _z+<ړu_<̴<=4Q1dNN6"^濥)J&ҧmjvj6n+ ǎncoYK |k-pM#r2o/Dnk/ |gzdgWdfdM7C<`%UA1 d#,/Z@0ζV΋KnA74`,*$d>{UH!MbhA7N={Oj_Np":j.NLLؾ[ӗ{Y&k9o{.|Yfx,6 jN}﹀sn?/xAoha9MW4~DHʙ`zӽId"&mi[?5ʯ2"TCWue0ȓ1Nd@@BeY$d.Bs\ ̎TOı [>$CI \?ُnvancrLߙ&gu.Ϣ-^z\]y s8P`J90f$6ȦqX6^{:;kv=.M<89W>*s .q"ԅ|@)L OWC3Fk>>ĭ]6'/zn4הZ) 1')#0 @;|릶KNޜ<[ ȿ&EliYg_0[W8{؜Io8u9U=_wum%xE4w;\]y (>$xt, !jbyUXxp[iQ2y oS( ޚUYI',4Z噇LQL =~H[4#ΝΐӲjW ܩk mOx_ۺJ1ɆL]Oܟ3&vmTr2t4ܚ\fh4@$$ dr~Hɉ$H8;2B"/Cg *Dҙ?{]xy=UyHg-Q $ !(ZHܙU?Y4}߃lZ:cwޞA<x0 :% < GTD Z1YPu͵" D?HQ{%Iz܆}* Bvv`go9U0\@0Ř% GǍ O9ƷG~Xl*s 7xBoeÂxo;Cz5ƬNx$Rͤ@H?AЏO{JaNEig:L؞p Ad~@L$WA)! $UcJ2QHȉ{l>%"%ZH$+Iț"(@aٴ8 /$H[ a}  z !pȗ@ `N/Hؗ *w/1ȗ( ^ZFs!Wzw(¢~V.3?7~Io.[u,#pRE\pȽ{ϥ:::]dT(R(=N >|Wݫϟknf[m3=00]Nr \[#pAKUI GfUI&?YUY7Ǫ̃̄ @$GHn"(!)6(RȄB{cr~dݧCe(? d7NɚCv/0@0,x`+9UOKrJ~6EޫfI(P^ Ѩv!:G퍵k>x6͉ pH$e5ujS`a(Sb&D84]NsC@)xM#O.8OJATǧ(@\t=_MU%LO|yy.\ׂJ Y?$@@n2g6tB!wVϸK!tsL%U\ } I`{.~Ah3b3",=i 8>So. ݽnf&q3ޑu%dEf~g`h"`x>< 8+ @YXRC`EwY((^?V!F+fJ6:bW/afVK]nq'L8,@[4њ'Pfd$Q'דů/2&m_c>42Ⱦ#`˽=j{xï2@r(?c-mU?ٜ}N=yJŞ]c!˾4YtH9g^irIT4U2#V -`'G[kpMOY.r:wYaY]0"i[-|xpiq)<98 {8NȑXyK)ޛ>e("Ekr 肪r7&F&&'=6$ *(+fYdZڀ@id&rwuy[t~yk!u>'뮷f?vu wФ| ] tF?Ϛjm+@S{6D0bt3Ol !# JIL?+gaD-_z>lu\33w‚ 3z;@cMRn!:&bvW[UIӥ};iߧ1,،,ָ9S\]Elpαp } .g`X`.zC '/ɍOggShs Qbyvsuvel>L;{ad}Y~+b\:t+|ޝm@tK?^kM$lRNإv ןO[h}Mգ~q+cqoj,#FQWw^\3Z=[ZNEDGd %Mws/ ҝ}$ Ewl[]gŨ\nz;v٢6wˑÖ4ɩڙ. 4> 6,C=4&jU笟g}Z &w\2K`dQ*Ո^j*Z7 djVO=Zc6ko/ 3xKcYwN$9q- p ~MИHC'@,zpPP #T͐=UL˥X vZ4!-5)1 <]ntg-#Kp%`-aOs̽O}iǛg4%PJ^5 0Cfk:(,%mdm `\ļ6 .z A(z7jV- I\r 8nV9Gܽ[p@0a©N h™N% WsjiTz&]^|8@F_F68(!3At:y@=h~綩s60w{[[r֌>s/Se5m&X|ӯEy3urjzåk}-aj{.GMƨڅ'3BsݞsU>w3!vNlYunr`qQq1a\{a:||:/|wpw- ȏ^ m   d(~{J<~;4H"uh8Ώ8IȻnše*x0uxǽאԋ_[<)5\}쭮 {=DQUz0cZb%"CY$w=uµ$'[ nw~x]w ( cufֺ,{jwA._?vMv>@Lc DL܊ `~[E& 93j VoZ!gvCF'"&&?o`.UC _ꊅs1Lռ]5]՜qN~]1T  >.=eݰ?_yV|:]u f/"U,5N!b=N@UcG=%"9ԯW;S'gOΦ&U[OYN{}95 ;Β&swOer3sSgn뿯~6֜ޅ)S}w0w$1Qg!|Y@ 0? H@qj%"J t(RmA~V+~ˊ> ]1FP8- 4!  n1,ʒt\GM|[QOotKlBV0@~='Ҝ_@4IۥPUN!ްJ@Oi7rX~u I0PwOߞBq@\~&fg֯yRڕ{NϦa%zN'A@TdW@?UYdfae1 ZX m(R7 $0.`s' ^b J@A@gZTg|; : 8ll4N#X*) ' Y|K 0 ݄0,tM_~`JjI&.F /$Khe W6+NVyOz!m~f}m5*|jgfckאM9g{hR @>fgϦgbۦɦEޛ!۟`al7ff^-8 faln*ܵ3sΙꔙJͻ[x޹ mϮ[Y]cXΔLyfpױIhC$| <o, s~'GHk}!z@QJ*1 q{r\gq~Ymqq1#5c/ykVky&\H-5Lz ;ᄖX{U旤cSP5kv}M2QȄgC.9gz矌{瓷Tec ihןpLu pP ?|TzSy/!֘i Wg]V @qtuĭyfU5K>fpMªUPou9'nz9>fpg8 'pn +9.9 (Zj(@5:)̤ {!ojUWZv.V)=}QiY=j?ff]oﱚg#}W"M!t$Ѐ`᠄IͶ6d=Ք}[9V6 g].]_VzN**!-yZsתK_ Y>] |KDDX#߄` pޓDL]$l ,  ^<8b_-\ p#͠9+Zd*\~Ӽ8( e:mDxN ;ZoMp, Q X&`r{Komt3 0=p]}@\/_o`[Fp6[Y/ܐI V!A Wf@G^Q&> ~ 9̸IfAZg4{/ #p* Lп|;^`@ s0^xOx:8/P0Yb}2(M/= }xu2]kS2u1FI_w P$)'khq'2 ˩T6F+I7%LTqbɳi3wΐǻp䗹jg8%w19|O@zw)xg1c/dV'Y=/D yhN 0u. yo@uiA04{q@9 \X* 4 JJE$c 4Ry?'z*ST:<νNlA n:{3Zx7u%$JxcL W9zwK\p,R̒>u;Dh7n'.jW{`H^9݁X~Z\(j7Ȅd :c_F8Р@| ܶ'yhzMw'.`)~{/ ":PԆs'TTe w$H8wUR deAu? ӕ@BeB7UPYWBϜ]ævu`Y8i}iΛLwpğ P:| Yz@Kb Y8H4 '^D9(a^U8!΀,{tdIgC錅sBNOdS% n?x NG 8=IsߴoJ,G1hf h&Wi ;R T\f_՝Bg9$B-YAbٞ]&^>c] @,Š4hZ~{O [E%Yb'e羀8= p/> MW3moװxp@ :7p;\qs /Pp4^,P"{UULY# U=iK1{|}cg b֫Xp;f&ؽx9`7O }fܤ`\,+u-#6O:;PIQk>+$t|X9|> f7 ֧~zS s30I2EOy'8N-[p+HvqR} ;*e}x[Μ`H̸ľ:8en _oٖ3ˁ\"<:DAEX@INMz3z>OggS@s 2psuzyQRNVMN`]^k>J*c )Y ̾iϳi3(|, S5]8^8`vIn}HmC Q>p 1_)YF;:Fܠ v {͵MWtw 9U@#y|~TH.o9IoyM!O6?]7.?wN503T l{BɿJN<ٸqww'*kKwz4~=4@h<@ V +8o0[F \% zA(HB0t}GͭTD@ 5dh<n1i΀3)ݷ5 -!+J yf cK~C40arHy39l]\?56 dz?°ͮ5EsكIP2d w' 4tA lzIwWo\uϼq @D2\wXXHJr+CSVz@<Whf<~Z A,}Oj4Өr}$;?L7T.fRX7j pjЮ` ' bh_Qv|MThO/[OCb@BW;, ?i4Z֍T7Gq(. }Iװ[Kp?92S82;Arۺ˹ Xl#6X?.tVg΍M .8XzC{ײw]*P=G̷{H7+ t&GFK)8jȍ/@'Jx}9lxI^x>^/V$ kLolOg)vQ@ pY2*΢a8~'ZYJ?fqB+C~̖8iP[]N&d/.buY6 "Ȭt{,  +C;j( Ml9WmVq?>*p >}ن@"sȯ6ՎIF8։8Ӝ=kқ?a Uz.RɗtN?qDo}bi N $*nvlL?/k{J.An.vl}1X:y]m {~WpW] dUY' J=+8T`t'I8ԟ8]'|ߚ^5OW\"d>]Cgλ؛VE\}E)f@Bo?[.raYxPmu}^c?=ED'. GfN %Ʊ:$N%&ԅvy9{;gj!Oӏ{@9l;̜8՝} XjTq~'s*i<`P*qp6/aYSWV_uj48=[kksn2@$ܦ,ld|D%a ~jj 6DuIE@Bdں'%2Ƃ`P%}ǖW @@c  dh'W$ a/KOggS@s :^[vxk_pkjShq~aMJt݀n,JryjgcΘ9 P/` Fp,w/꺄[~bxzs9p͓4(vRQ9k,mۘ ~`hتH2:t}~ 07xE# |!f h(fӨFzw[Ӂ_*n $}>@RGN@_%N'0۹+R\5uqywKey~ :a ??8Sv䧨~z{cy7^<ŏJ<@3<;8Frxl<$c`"9PP@x'Pe9Q|h@@>%U=KfVX}EjpZw/PrߕN( */q]Iˡe0[* Li\e5SQl84(ϴ=S_kV$WFD,î!P%v@+ pP=E1wĐfɗJ鬟ya{ ^ ;1PWw 9M }g':w R\CtN{ψ==6,~y|._P4LYڵn/<}_\[L` [o[n0K`rxٻFG4u~A&ACީ)I0.^.86:n`FtZI5i?|2Ffn= 3ԝcE˭32e 4@PL:H=;R'~Ϝvz;¯s9!XÁ>'* ?!y;C?qxcS+@U/ @m>ח. 9<.*=YH&ݼ 7Pc@gk}r Hq  *WǶ VJ(sɐ*:tT籠]C^8aiҼm3l M'>(3{_he ]-ΩWN=3 GԏȐd?+ fW AV ̜lc5sy\Y 8@W;} n覓nj4,@AN8a" [`KȺ`",`n_^zX| KFuD,H yY-* zINp:P?ke痳5' F;2 8Ffq 6 k۸7w:4ytfҍ|!vҹȭE.,/Rr&ӯT&} '4I= Ip ʦr5pMs89dk=Г3r=`@Ao'A 7;oV-xo4"WЮ]Z}Q<kN00 `8`:. ^(BXR(yekJ Bsh6GC-Zdq$;{_[[|aAzUzk*u–vZ%s"rYv^<{УN^sCXq򱧅[ a>r+f {i甞f;3]Ǒz;H(b@ ۏ0=əL g[Sͮ;OTR9|4;v1m #{2]S tK|& 0}t4[)Enfs+^`ia``KQ 4Z\$sNOAp^ Q^I$]fR_&Ygt+OYqY<=ЌM8'1Cff3 whJ K%pYApr˾ᯒTn̑}=伲|83.JoL.a'0ꄫ臍c+xņ64zz&:1!;g`x0 y͢}ECqE03>^<;s`u>h\dwcup~psyxfn<pw{`b>%8s@J@oD9 >͢)8*^~?H/X?h0N[l]뚈TO@SL%|{> ]^r8E+xG ga2P*6ѯϡ3XP gxU2$PI@ 25 O ̫n0sJi ;.cs3 C@НF0Cw/iqC0mS5 gP@XZz="H X?>Y0`ܟ P@Y͑L\պlUD=٦3UN7KEF\0ӝمs4]v3_o|rI(d@ک+-%2;skQֲߟ<[VV=YoS \E@)R|sSWI3`C!oo@Wl3z@K%# $!#3|v.w4p_L509;a'馧38>K I sp>c9G xiHtl ykJt+ņ|:IuUd~X0h`c$G.uJr*E OϸK`XYnrl~Nયc6GC5P؏4@b ԠWPᢊ @]|M} "d@N> o#N_*y;'NyW\$}kwSb8W`mx! c8w+TR<Sjc6"B-"@`-9ͪ5("$GRl70rdӬ/ }?Ri m9sgJ0]u]0 $2 תon@/!d?Zm!R9ݶ2 W]qv*)~dK~Vw2 {{ w_@L:]{n _WVU/_w‰$k@п5>/)_ ?% `` xt aHTp eO~yȴT!]\;mfsVYt0>FW 5 ]6waZ8%P( z&VkkmNdW]_ 'ɿr2x t {Z +7w?qT_uOe+- ` 0Ͽh`~=;<~NP[ @&1l '7@lBJ76@/(@/?~C8]t4< t1 ץyNyh,(X`l-}  [MOggS@s Qkcfxmxo[xpoxky)B :{DUefmAf~^~o?$}ٟ_56q[H=n%YdpJ8IzTIu-A{d_cd O wT5cQC;rt2PTA?@ ̋ŃG<ߐ6ux_F & 1w'(  xRyo@,4);`_I`8y 8x?]w,@ }}]u^X}ZYqx]|}j}l#x)($ Ô5S~ \?'{x%?Om^BFd|Oo @ɛ[ b8@ .0|U_tm:5l!@˅lF=+D %]1K_4y b޺θ|? xk ( a 9|f5nΒ 8'<p}QR}5@u|Z/ޙs aμ xReTzm /f8mI 1bᔀ\84EJ}!f}b$ <>C:+)@Pep9SU#+{V8{ycH Pp#eb&??i7J35 v.?u'| ;08C|fʹ8BbI;%)E=Aʟq.Ua[6\%r^g>F03@yȧC/#  y-1R.{Kt䴌ddlQLL@YH@ @!ff `jMd.|6pX(&)ny`j(p 8=_q)xk "إA.qz?fFg9 :_Ud¹-Lx EpLj~tlsEb} Mw dZ]M{?/29t$d 敹^ >AY@M|抠/ u/ٽ`h|Zi5?>$J\>4= 4L4_~W w-Bh!ifSpqu44p@@j/9t% @6; sKy ǘ"PSt 798n?Կ5{ۢ?<71r`vnt'J0*2Rpadw`\ 鄳o{~p64Zf-(S$ɳCphUVNO#T2q1y7Tf0Ct TCXdf6HSQoԱS5eRG1ydXj]xO6LN禁 R3j@=4q3(= z  K$ x0_|ʻ~"` p_' o*'-rC(8^yTP (㿬Y31"kLE)ڿƺ36[z>|bqGE%c C Y .ۿRs i2u 3]<Qj>n i_y*Jn)ɣ4'~.G/!!wn]:x/@Q@@lA8D>Y R,N&FW9ͅ?x>Oq*^ט78*p, Ą}`敹fSkr~5]5~-!fTJhJ!_t+>٣|Fђ=f[[Y>D8@m@Fa]y. ΙsDF9@D>&iՐs~ONCUfkV3Nu?)xnjhZaJt¬:VQ?ũLc6 p|w]X[\A)r}Uʓ{}py5>+x?>?eۚ%x}K&Iنk2 @xiP䜍eRB?05|>W?l}Fp*uxQ Ypp $3k騴P\?qƎm7Y+>n%Ara?s!~.(Ȥ{5Vz&owڛԻi̱9E&TA[ a$ 5{!o`'l}a?\Ztm"%Cb8.P̒'3{͡!!/^8|MIp{CԆdI^{r_a䣏tvw7 MCtSj( <s.(׭@C%  F,qOggS@Gstt\yVcPO`_a>Z͂AHah/p#螁ȉ_ޤ!CFvQ#ЩB׮ mkЗWO66L J8L2P@?D=M5\bba^56?e(Tl.!$?Kq-F}}78w^_L7.:T`y#  > M:]_ '7@2;S#jfB\K藾/uŠko35%΄r=ؽ?@w5wa{Wy =>??6u-A Xw2C,)݊jS0 ¸d5rSQj tdx#d'c+}0\xi ^܎d/ueฟf]0 ~+@P=qn OhvMqX&:|  m0ޖ4g*@t0jd'@7};:C7-o@Ya`z/xf>4ǃ@uQ53@1φp|`}xXxYl!I1 0`<.8J}8>9T!,#W`(YR'Rl;DA}oY6;;|~lE}|ئG(@2MxN믻=?&L^w!W5[tUZYV@:ycUC94Bɔx:W9 z5:x l dT`B~|ߧep /Ͽss]'Ts)؞/R}i:gg dIK6 'yt: oW6KXB{)j~@3ϧ |>?G ~$ɓ/d&נ[ 6PX蕘eBn3*U95?lAs`jrSaա ,S Xw-_sϊn{4W?Bh3hO[2ImO|9UHu׋&ѷn2 Y>vOiDty' 8'_ 6=@));U'\UpqLAb|D0-k3=2~Vǂd( (8 DU?{L<޿_qgw8(x;swǥ{:xKqfj'ǺVڢV,oGO' U@$B_'M/x>HxEr #UQgIJ_I(݆Ksɝϭnvwұ^qaޙrs굶~EbPxb d$17eU3ToUsmpEe](EZ+ZDE"Z@Ƕ͛EDl^6Z0C%VnIwNC!<='ґ:F.K~+Y&~\13[\{Ms&:8V7,0 EnXFnC"?Z@;ֲ?꟬5ޗWߣn+:70 LOƞpp8$M@n!~_s$:]s)Ѳ*e>hR|<>xe~W= `be|yDs+ >0d칇[c{'MzEy4T@ww HXUtBDۜUua:+ 3*3l脛|`~ , +pr1 9 Zwr(`&- n%BxjTή 6@=OZ,x;z-ݷ:8h'3Fx?!G(:qxŸ|+7 R%b͎yƔɚz(-aAO"WgZO7Hn֮yP' aT$~6Z H41) K$bT>BG.zOnm߁u Ψ1>(_?螝=3%fAd2@PKRhHzΩxfeVxңV{F[l2&[M0pAw 5EgTξf3@&p<Ӈm`@y*N_߉J`Y\X@!4 =,@t#[7 I0 n0yV ުJ%>*r?7rmCLE|8|pz /F%v'.e_Q7yMO/In׽_֨|2xooLFr':=ij'f{! Maa]u d[&IwOC>9u4 `Z&"7z9o u7nnu8Պjp2du -?f{,[ޭ!ClH8gHk)î, xULYA>6BپEeV frH5~?@# @BPOggS@ws2a1gqqndWceYqi@ ]bk<ժv….Yȋ̏ zcv:Fu 'ݏ30 f07# C hM;Fb֭2VݿztF7~X]鿃B+4~͓~Ev2d Cqx! o=$rI]"?81{0| ۹.c_0GU{v#i~{x Ŗ 4 ;9OMupNd$ ȩ/kiOnetފ#+{@z+8t-C@RB1 X/ N#a@/ >j?heܙG`v xz *΀Op1!ǤR0G79z7{&g5&{|AM^iLcSNB"ZԚ2cS_GP* j [xl^s8_ ݙdiṮJ\x;5@ 8l7| 8WQD@P T`VdlL#9YW( ~-6v$2찛`eLzL&` sxM4 8 p2#Y(R 0 (*Z4^ A jAeb{;]tvXUW=BG@.i~+0ǰ H}2]yVJKRP}5~u:*֘7MWoGH ~aH9y6`(`ok E(r$@za'fw]4P tOé'Trz4 _k_Y@Ct\֟ʉ!ɮOݚʂ7;)7oj-( u=g,ã(b`(ph~8 @ lXB1rLj>@%^쟇C(PiPK z`nb bXAh? [{|*G8ZRhHs2y.7T1JGw'}m8Hχ!pDLIּ[K.=TwMlw }w[?U[?k @=:IPpyqtpr@9UL @/, -Ciέ]s!w=@Ki /G}'p 5p*@!:ke~\\=3JҐJW^^M.7E_@T>L+AY??vǷa`x&_ 4~i~4lϻt_λ4Ch]P&/f ƠYi6=ԣvw9==sp|><sbwND  pZ;5XX OBp2 XHfKp#x@5t~Q`; {x@H{wy6M4zBffhm=XfihOy>LB5]n%<%}=p#/uB?.@[m. HI@dѥY+JS/:CD ׋mDǞv #zUS]AKٌذ(r53%0GS p%􌵹[&0RUcصYݝfch@ۓh~\& @ܣ`qH`G L ~p͡l* ?'u!H/   P 64r%`w  ' 0,0 T\H g >Y"T#tqsf6G&H~6*4^Vto- kC5t!q' Hl?J}\z9uly=p=^'ގ<K+Gi룻Bήs/o!{|@}9kԭt&;\>j68!(@Г?? @$ ow }]0_G.2 `X1Y+80&8@N'(W(p72?)$xЮ , ΄kxF2?k>f~9o{|GEՓ-.&H0+Jc,U9DTaX㳮\X;tg"` A<{kDg:h&X~&H;,yn$l ? =@PH@oqJH w0@`q00STA&*R@u8 @>ic,*,DxV>~_ىմ&;m5ItGN hL3m5ckq5WmP(ŝ3|/`jAҬ)\4+ +aCЯ9nfH`#xOl`| @&{. Y3|C M5vg4R$N uu((88M>T=QMo2P7 |5pXr{ ,Ke 58 | #o!i -k VY\+H`ER^]q᩽d6rTԑ¹Kȩ2־5אy}2Nr73 _PQ8!E*>Gǹ^n_wۢS@Oԗ(i8!:h  U 0oF.\`$_'W0{_7yPl?@s 0J\g<&D@xhkr(7L^Ըd\ zL6%oF fh?} NұmYGdpE. ȧ6M!37RъMs{N=ߧ;HsKCSe ~j~eR(j@V ٫J{+Yו[q4`{ց$94y``^(ؒu3'M6C\ZI`>'=gH:|w,,7pWB _ nh[p|;l`pV`y?  $@H@o #6OggS@s_v6k`lclbwq_vtekL0 EZ Xkeec|5f?^{>0T]f֥ hP}my|k]qI(.e<=BD%z&' QV( RΪӹt4w™]/_hr"HOIԛÎQt@V ⭇7s?s  (rֿEJ0' m1< ㆛ *`CSl HDxp8 g< 8(E0`6V(yj@Œ.|ygTa|e$_n#װ̟) THhog:MɫQ)vmN_z56 PpBz&_Ѻ?QY@0 g%yp   j8} Ų%x"x#^Y $IQs)nvqhng|rQߺ:lGE: $$\\Ƴ:x&w{s]no.ď ai~< UYE]܅tt F!c|Dp}I2i ͓ /G:H1PifWCO4tcx7[ v  p8 w=8wWP@&Q]I`P>yNY 54+ ӃN7.]0 6} c>[&ggp^: ;!B $ w3scVOM"$\%\ Z^+%rP~7l A$Biͨ5ђ>eWIͨQr?G=k+fVЅ;0%wvC_؝v x}(C%h+U4\cjfKw:C~ѻCon`P;K8p D D" 4EWk/;@==nx;=`SExQ6qD21=M6A" Μ~iS !MO*I^wp²M/Ud+- x쁧Z("l($RM$9.y3ڸC yRT෭A<j1?[az?-;q5ĺPD 0B8"u~?^YgZ8"j(ŗ6(tgB; T)fOIhgv|41@K|У& ;/굁{[ 4гu]zK,c.;laA79 {۩DG kg=U`jnJw"\\ 8ׁ(n?Kf `wpGp֋R;@;X`<Ŗd@) Yͪ5BɒL2:$\ﳑf;סhoߒ Bp"8.jpNpPB4 ֝">ޭOԃ7kWG ȅ@_zߟOPhrӤǓݧ[tNBj~C{67tQ#O obEYcVN6=wD=]=];,u>wu `s~Qo~E=<`\mcW]w <x&H @"0SdA9)VGS)B V\ԍċ3;̖g1/X9޸Ŗ'fTz`aR3MH9ĩۘ]Yp^ܒ[BMVSJF{swc8SL-UoKVvL $LmIl]yasϹC? ڛե{|^y|~8+|XЗ)N`_ yͪ%V)K@SSȊL*xÎ׮_|ث|gSI}#\*S¤Y"1Z] kH}\d,҆A͝J.2 _2dB7pe`cI_w<0 ?5L0d/{.Ut~TUC8N@$s7sYga~;=s~h 1fn>wsV~Ӟ<Ns{&3m8{&Qs֟Z6Y=+lmF{w`=ndwK)I%(bR.5 4Ng(z,@_>i֠R Oݦ6oubApBxqۜH~VmH C hLL);z)7(Y{ {K~v(7%?y QE2+Tkc7s5JX P7J ~Ե3-$dDo2Wq@͝s{ghHW&Y3S> ̢D?)a}r޻rx`yEwKzO0@0DxP힘ڑ*`h>#@6aOggS@sfd~ji}hpoonyrTǠ mh6$jp{fn*3U4t.;D'* ChKHf @&:is*x|ȞwuMHk(F@| 5 [}M=ϼN N$o{7˱!A@?kh|J(jX˛zPoH=$)fiݳia5a^<<3k@<f llq[6Fx4gX_z^M0o}<5yP H}щsE6C\ jhWU 'u,4=A\X~w]guᎱ2zI57.rn ; zO*nGuiV]sCM25<K5u;Ty{>rբg>f@azfE;~~Tu+s_.[:?Ȅ)4`yw5W|Mʻ)z&]`<\0]qfMsї? o%t|]~PGf9{@b~`!N~Ȝ~z ćjql0WKT7vCtj3GSq'#'Y69PTI);U5z֋Rml(zN:?B.6+P.'Wi*@6`E`>?/Ltn{ J|zQ4fj&G_Mf{\[G%bq=EIuYc`2XHW `6wEB2 z`8&`ᾒ(yRVf1gl ЏC5X@_VƸwѬ\px d' DΔ*"\ N:. J?nA}=kܟW%?!P\;˾4362 m[6X*Wb3L0Ӈ߀t 1R.鷘`~|{ ps{e Jh iih[A.wΎw8wι] C9>_{K$ 3tlCvaE ;`~ @{m"2 ̓9.`U|IS&o v{<[{֊f/OXGC|Cֻ "g"%钐c Xc$~h'+9byEs_?Xk1| ۷jq~>f&wϓ[0aru.,?1[oƒ}?>jZ@/Ds@Aʉ33:? 7ě ǒM@=`&+ӻ}+{<+g")Dpe: 晀[P> #Sum(z\k W$\TUf (l{I>PKs֢i ^դ[ 3-oh`fhAtGdjg8>?`:8[b#2f;O`M{r X04|<ǧ~]p< p0 @  U aZНv|O8!j4xs zrي"cN ba(Ô$L@&W+q}d1+tݩcPxV;/v`zَKaOrׂwQ<0|{ EEީUATfYbCgфw2ݮ ^FO_:l*ww]RBQEczR޴ƞބw?LEۡ_i3ѩּi fg~8tC0 XCa>,1GT ީkJ WJzZ`*ʲ }{N>.8~yoVi6pLiNa``a'н8;[~o^|kl O=tw[C:Z&fݝvݿMywSt*+ݞ=q5f,E=zf~<2$BWFP6  pyd40ʺyWLmU5}̷ z ΂w?67S0-4o= W3 q P4ܶwD,ΐ SS#`7@]Ub"SVYEd7퓛zyLlYDcra(N-`@fvx}^&_Bxq}z~+iзg?^C``e$Zoqs AS{&{g6i'Q,{B{jz}iy `·x{\{.У#7;Wr 0 ?_xao0t0{hYpzx$LS4``DN p=Bʏ+iS7 pICN ޙ5ӅťP7ȲI|75ޞ>jH`t k5¹Tl6@`b^q33>ߒ lGyk}7?+,V2J1Jo~ dgf?\7JeQ@,ng8A|$ . I ȯKo ;8q?@3705#ix``F]el͇mWwW+ǩFÜK'9R3 (暝@03 @ 96Jkh#a]h( @@/ Zm_0.Y)itj _b15t~GݡiN%:f[9\I4`M!gpZ־]Ũ\8 d)]0"J%PB\GEM{6L?Hukdp*)\L ;:6!^~q^cM ko8ivW5,VcIҗh @*M# o&ZR0u/p:d #NoN .lPrxayӉ("/ -#2? /$|_H 04L`Y`?0H @ 6dI#m0R -8@} y *D pJ!Hx~G#ۓE)f?'{3o PL\y"'.txLY#x/!ę󘁻 5}mY lڔX@T$fev\!<큭z5_ckE Il5R8#A>2 $$>,5, \@P=i`j`/{>~] DX~; s&,fp~mPJ47NH` ؂ h`krP]|   pLIA"PP\eي WH!+hN" 8o$ڿi6/7p7<V<= rd[]֛ C n PtJw /hZ~KI[2mQQ.Ph x><£%՟ 9anH1d>*ohk#}اL3[]l~Tʦd,_ uQ@ Di}D;Ouwi~>@}s5 w|`  o)Ax"S 8 ޙ :з+O8$~x|8ﶇ'? xJny2p- PLkᴪs̒S+ir>121=E^6 ow&^f*ipy-$+? ӗpVT9 Sysh+v {HL {{> 1:~3 8 ߤA0&M o ?V3T|L :L~zX`9H@ @; @ A=7XpNP[B/3>KY]ꇿ{gvw :anpvm!_@\̳":C|?,\wG vX S2иԿqZ0;3c @y7?b chxA$|7DC 8 IOH>_ ~Lgb lC%&yN{" g`OtpA3I>8?o_qKNpճF ,Iz8`r%F0 j6P|ȊG ?UhݺC ȓvKv/ԩSȇ7R-2f8[׿Pj_),E6@R@`|ET;}^Te#(ȕwbCQ 4T?/v(4ś3{xW;~#T>NoPW{` #Vd`m4 0| 8K ^̓ 45hCqә_*` _tCNta(]wRpOge753qnݷE!Ƕw0s?äl hV IiHoĶ  PnbHk7H6 +x'Ɍ ȟ H#@3u4п2y8`2  =p|h$#i x@0~ xNȧh_5}lnn@ ]'ߒ|*C_!y-//kJ0oKqCbB(o@'Y[(mޠ |VCiY mfxGq^+4I^Hz4` k ˏ.(oV/0, hГ+~({ P~'F..WaP:`@2n@18 ȉe`|ڑpbA&Ybxy@OggS@3sZjyqfYlaWojcad P b5#_@6 zYp_nx.֏=,PHz30Y f>M/.O~&u b$񄕮!ݵHliK 20u9]5Mg$tn7fRO @1d@s&gf}g! 40jӓ-ES4: ?)9Uнw ڵ;٧f32y 03z`r#N?f&hΕ;|Û`@@ ( "ƖRiZ t>[-UH[=WZ |@,ӡ~y(;+53|@#] ȼz%$%N]v  ='x1O:ߣqu\r-_,-y.BB}xZ-)݄_uI6mό?ړ.ij'ӗO$P -خg\v禡NdVd$O=y8 ,aa%O.{@X>< <,pw` x%n q`:3? X- {W/I# o ^yКRz`RĠ?m,r{hJt=uAY cKBNe:E$k^a罣E/sQTkO h)YBܽqM751;à_$3j7qw~6р|2tSϿ,]P]ȏm$'H}FϜ`#"ſi*c)Swl۾ ؓ fl/du"ߟwt @r@HXh 9?9 ؘ Gڏ0} A).=I.: yG0#K:E0U>}|&mkE>.ߓ$UD -ro+iW/v:gw'ې˪b}(G4< ^D?gh_{@X q '.0'o@|F`bk+@ )^zp==1TgS WJ)ntr._>M]|ػ? A3ߨN9; !h~}?XCJ!K(ӳO4̧mA/>&G^c“>a&ڪw|p`)0\+w0v͟:9 "!.y raxHfAO'$k X6d$3 UvLd&֬ @~A̠8a%<.衡w|/1@wMO^9wp8X{b@ d   zX.]Z@3W{U?5WTֻFY>Qnp'us޶?5gr[IΏ_&%~KM L z1'T0Cúv}~ ~4 L74}/ @6ppnȤNu 0l*uOT`I} 2=Eh[>z A丷ZrR¢7cGqc6N~x2{Z8?"+!Pqgq'`?BcnO.}HeXt͗>)Z @Xk dTo'{lTn*s90pP&xS4}$TM>Xl`k]z>uiu! x%C+MH9LIp^lu s9p^~8 pOд|@Fy :(] nS\% 1@,K/@< #yzJ"UyG=0֗#:w^!0<}, ajSk \܍`Ho2bTjfc_*dZhYd}qw֏Ϧ8ByP1wtW/~K y;@7?RJgc95L܃i44ISpYA+ >y" =ay 2DCwN4 4=|zp.sw4dT΅xߴ44M@pj3CL34W+:I϶ZP/H QGT(S!BY8NVn V\zf䣅b`cZG_*@8%3*lRwP7׎Ҭ } ܹ 0 V?{u{Ju6, o?w@|Owv7 ß֟?;;k1p= @b+P`dM > =9gٱמ\wx_@u =.ifg| }l.g1dLc%t Ux@ `~G:sY{],k}z|w/xf$0q4#P4K8X֟tIo`  f{=Pom*URdwL藹+ e7}pFص=G=q9\z >,_AI>|YC. 0 %e@:7jl]7vt=x@?|}oOT;=a@JRv{5Y{32t3E-|pH3C[O_ r6\p~8 9p]ܿ! `? x(~ 0J Jk~Dg/ <?8``  *gyHRbE? N^ OggS@cs^6Uxbmzw{h{glM :Y}jBY_9-k?b9OwFs| k<||xta8Μ$THK7Eȷy ~f ^_ l{K@, ,4|7>e1(y ^< O$ARX P#_,y(㡪%OAjjͩwf|| !ď<iP1 $_AdzLa^|{9l;N}va}t\F3*A~,L4:e>Woplv k7:T@KûӀ8@5| \ }ko0@=_ܝp 瘐,TA{sNֿsW>fz;xln90=0͇{@@N0< SA P^幚PêGB/T7EC7J|W^w#[2Ǩp6B5x  C $"p=feHe>ᘳ/7,_{ `n@oLX8M3{w\|"W/)9.Zm(vfc . @8_@8Fz{^@  pb Ѩ= t<윯 @3|lv' /:b0W֙ waq@ƻ8@p۱$xL 0ީ=刞uԶfɍ!!ϓٟn $4 T?rR{6&3b]B:gCkjOi#j WwgM \95SN;70+ ^ Tn(cS $80=ij.;y8N+P0 X~5 x)IbȄ \;h{XTm4 >P蹋> -sJ9|5ȗcy:hAZul9(a;L ?'6 -n0x5`gd9O=YL^fhŷ!\ >𲘒hSd}CC}XͰU*Mz6e"3;!g=ON'ά&336q&Gc 940ԝyHޟڟ}.hz\Kɞdf۬nxsW܏릋{: DZ(8@X0 b^TJ#XPz̓"}neꀱZ{Z,(Z k ]N%fxqޯ۹{/-jphCtR ].hBӷe+";7L!`?̖7'ᔏaa?b"(3߿5`$ `O\jË_`+!oTvv/$(c=$.wK8݅ɦʅG2>-m_ycпiplwO Tsě&`7 7+d_a}Ww?ShO~g8#t *0'*@ 0!I@\ɸϿ(껊_V, B)%svR(@4Pldg̏T\˳Nvh}6>u~.\oj"j1Z8u}wE-<&,Sn9 f?]S@ o$۞Mp1` <7[p| A૆>~s <`hxf\tf`"!N`PxQP5uN@iq5D_Y^3D l j<xx_:ۗ3E«8@pJ%׽ׅ>p5. !hI xX‹ k{|@AV5z8jb{_ژI Poo\]3fho_ECynUy%jA:)e數L~`,獱TO ɉo;97,mktLekm"n#g-cS4qorQ:3 {c9T8М.[s5Ϯџ/9?:ro1}H ?p/ PL{ U? HU?p|@[aI=X`i$W OggSkse ;SJRSegvy͑Kl3d:̞]~<].qWg j@v9`_/p)o~?P:%pWگGsT֯^;05!Qo[ HO&s!pPR2׾mlCʨPN%F[Oџz{{_g9dn n%&7K~+i9t͸j@4Up__OLNVu'{9pʣa_GjgU4EͣdZh_r a9ܴ_")7$GO4. p DBm=~4k=56 1@4kF/kȟ9sg+ gw)1~}_J9R#8kD߮QV"dxy] u)PB%YI &^8ܼ!=-x 98 : htXohfތc,/7> gv:?.<_ir ޥ1y D+J ץC3n@ok<45S3˞S!͝ܛS3R]™%BوԵ:fyVb?+_uv>eع!~yvi}szsL˳VCiZba: )0Ah8JMe LGLnWp,2:?;Ob^?_>*`x%:`7BjիTsBtrN('uoOӱ˦N.z#OL'_'-xY_x? (PJ\fh*tzUP޻?yn5iSӏ;߯<Gn2kygSYOK)e *node-groove-2.5.0/test/test.js000066400000000000000000000113701263365216100162450ustar00rootroot00000000000000var 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, "open file failed"); 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) { assert.ok(!err); done(); }); }); }); it("update metadata", function(done) { ncp(testOgg, rwTestOgg, function(err) { assert.ok(!err); groove.open(rwTestOgg, doUpdate); }); function doUpdate(err, file) { assert.ok(!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); assert.strictEqual(player.targetAudioFormat.sampleRate, 44100); 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(); player.deviceIndex = groove.DUMMY_DEVICE; 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(); }); }); });