[Pkg-javascript-commits] [node-groove] 01/10: Imported Upstream version 2.5.0

Felipe Sateler fsateler at moszumanska.debian.org
Tue Dec 15 00:15:21 UTC 2015


This is an automated email from the git hooks/post-receive script.

fsateler pushed a commit to branch master
in repository node-groove.

commit e33dcfa4603fc2949886370114063d601d11aeca
Author: Felipe Sateler <fsateler at debian.org>
Date:   Mon Dec 14 20:44:49 2015 -0300

    Imported Upstream version 2.5.0
---
 README.md                                          |  34 +-
 binding.gyp                                        |  17 +-
 example/playlist.js                                |   5 +
 lib/index.js                                       |   3 +
 package.json                                       |  12 +-
 src/encoder.cc                                     | 447 +++++++++++++++++++++
 src/{gn_encoder.h => encoder.h}                    |  18 +-
 src/file.cc                                        | 332 +++++++++++++++
 src/file.h                                         |  35 ++
 src/fingerprinter.cc                               | 381 ++++++++++++++++++
 src/fingerprinter.h                                |  44 ++
 src/gn_encoder.cc                                  | 424 -------------------
 src/gn_file.cc                                     | 332 ---------------
 src/gn_file.h                                      |  39 --
 src/gn_fingerprinter.cc                            | 373 -----------------
 src/gn_fingerprinter.h                             |  43 --
 src/gn_player.cc                                   | 357 ----------------
 src/gn_player.h                                    |  47 ---
 src/gn_playlist.cc                                 | 240 -----------
 src/gn_playlist.h                                  |  47 ---
 src/gn_playlist_item.cc                            |  71 ----
 src/gn_playlist_item.h                             |  32 --
 src/groove.cc                                      | 120 +++---
 ...n_loudness_detector.cc => loudness_detector.cc} | 222 +++++-----
 ...{gn_loudness_detector.h => loudness_detector.h} |  18 +-
 src/player.cc                                      | 364 +++++++++++++++++
 src/player.h                                       |  43 ++
 src/playlist.cc                                    | 232 +++++++++++
 src/playlist.h                                     |  44 ++
 src/playlist_item.cc                               |  71 ++++
 src/playlist_item.h                                |  28 ++
 test/test.js                                       |   3 +-
 32 files changed, 2273 insertions(+), 2205 deletions(-)

diff --git a/README.md b/README.md
index d44bb70..307d6ae 100644
--- a/README.md
+++ b/README.md
@@ -3,13 +3,21 @@
 Node.js bindings to [libgroove](https://github.com/andrewrk/libgroove) -
 generic music player backend library.
 
-Live discussion in #libgroove IRC channel on irc.freenode.org.
+Live discussion in `#libgroove` on [freenode](https://freenode.net/).
 
 ## Usage
 
-1. Install libgroove to your system.
+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
@@ -220,12 +228,16 @@ These are not instantiated directly; instead they are returned from
 
 #### 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
@@ -234,6 +246,8 @@ 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
@@ -241,6 +255,8 @@ 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()
@@ -296,6 +312,13 @@ must be a power of 2.
 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.
@@ -323,6 +346,13 @@ 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()
diff --git a/binding.gyp b/binding.gyp
index b1c647c..edd135a 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -4,19 +4,22 @@
         "target_name": "groove",
         "sources": [
           "src/groove.cc",
-          "src/gn_file.cc",
-          "src/gn_playlist.cc",
-          "src/gn_player.cc",
-          "src/gn_playlist_item.cc",
-          "src/gn_loudness_detector.cc",
-          "src/gn_fingerprinter.cc",
-          "src/gn_encoder.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": [
+            "<!(node -e \"require('nan')\")"
         ]
     }
   ]
diff --git a/example/playlist.js b/example/playlist.js
index db269af..b122cf7 100644
--- a/example/playlist.js
+++ b/example/playlist.js
@@ -8,6 +8,11 @@ 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();
diff --git a/lib/index.js b/lib/index.js
index d28641b..4592877 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -56,6 +56,9 @@ function jsCreatePlayer() {
     case bindings._EVENT_BUFFERUNDERRUN:
       player.emit('bufferunderrun');
       break;
+    case bindings._EVENT_DEVICEREOPENED:
+      player.emit('devicereopened');
+      break;
     }
   }
 }
diff --git a/package.json b/package.json
index cd17f11..3f2e4b3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "groove",
-  "version": "2.2.6",
+  "version": "2.5.0",
   "description": "bindings to libgroove - generic music player library",
   "main": "lib/index.js",
   "author": "Andrew Kelley <superjoe30 at gmail.com>",
@@ -13,15 +13,13 @@
     "install": "node-gyp rebuild"
   },
   "license": "MIT",
-  "engines": {
-    "node": ">=0.10.18"
-  },
   "devDependencies": {
-    "ncp": "~0.6.0",
-    "mocha": "~1.21.0"
+    "mocha": "~2.2.5",
+    "ncp": "~2.0.0"
   },
   "dependencies": {
-    "bindings": "~1.2.1"
+    "bindings": "~1.2.1",
+    "nan": "~2.1.0"
   },
   "gypfile": true,
   "bugs": {
diff --git a/src/encoder.cc b/src/encoder.cc
new file mode 100644
index 0000000..66d2c66
--- /dev/null
+++ b/src/encoder.cc
@@ -0,0 +1,447 @@
+#include <node_buffer.h>
+#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<v8::Function> constructor;
+
+void GNEncoder::Init() {
+    // Prepare constructor template
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GrooveEncoder").ToLocalChecked());
+    tpl->InstanceTemplate()->SetInternalFieldCount(2);
+
+    // 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<Value> GNEncoder::NewInstance(GrooveEncoder *encoder) {
+    Nan::EscapableHandleScope scope;
+
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
+
+    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
+    gn_encoder->encoder = encoder;
+
+    return scope.Escape(instance);
+}
+
+struct AttachReq {
+    uv_work_t req;
+    Nan::Callback *callback;
+    GrooveEncoder *encoder;
+    GroovePlaylist *playlist;
+    int errcode;
+    Nan::Persistent<Object> 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<GNEncoder::EventContext *>(handle->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    argv[0] = Nan::Undefined();
+
+    TryCatch try_catch;
+    context->event_cb->Call(argc, argv);
+
+    if (try_catch.HasCaught()) {
+        node::FatalException(try_catch);
+    }
+
+    uv_mutex_lock(&context->mutex);
+    uv_cond_signal(&context->cond);
+    uv_mutex_unlock(&context->mutex);
+}
+
+static void EventThreadEntry(void *arg) {
+    GNEncoder::EventContext *context = reinterpret_cast<GNEncoder::EventContext *>(arg);
+    while (groove_encoder_buffer_peek(context->encoder, 1) > 0) {
+        uv_mutex_lock(&context->mutex);
+        if (context->emit_buffer_ok) {
+            context->emit_buffer_ok = false;
+            uv_async_send(&context->event_async);
+        }
+        uv_cond_wait(&context->cond, &context->mutex);
+        uv_mutex_unlock(&context->mutex);
+    }
+}
+
+static void AttachAsync(uv_work_t *req) {
+    AttachReq *r = reinterpret_cast<AttachReq *>(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<AttachReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->errcode < 0) {
+        argv[0] = Exception::Error(Nan::New<String>("encoder attach failed").ToLocalChecked());
+    } else {
+        argv[0] = Nan::Null();
+        Local<Object> actualAudioFormat = Nan::New<Object>();
+        actualAudioFormat->Set(Nan::New<String>("sampleRate").ToLocalChecked(),
+                Nan::New<Number>(r->encoder->actual_audio_format.sample_rate));
+        actualAudioFormat->Set(Nan::New<String>("channelLayout").ToLocalChecked(),
+                Nan::New<Number>(r->encoder->actual_audio_format.channel_layout));
+        actualAudioFormat->Set(Nan::New<String>("sampleFormat").ToLocalChecked(),
+                Nan::New<Number>(r->encoder->actual_audio_format.sample_fmt));
+        Local<Object> o = Nan::New(r->instance);
+        o->Set(Nan::New<String>("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<Object> instance = NewInstance(encoder)->ToObject();
+    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
+    EventContext *context = new EventContext;
+    gn_encoder->event_context = context;
+    context->emit_buffer_ok = true;
+    context->event_cb = new Nan::Callback(info[0].As<Function>());
+    context->encoder = encoder;
+
+    // set properties on the instance with default values from
+    // GrooveEncoder struct
+    Local<Object> targetAudioFormat = Nan::New<Object>();
+    Nan::Set(targetAudioFormat, Nan::New<String>("sampleRate").ToLocalChecked(),
+            Nan::New<Number>(encoder->target_audio_format.sample_rate));
+    Nan::Set(targetAudioFormat, Nan::New<String>("channelLayout").ToLocalChecked(), Nan::New<Number>(encoder->target_audio_format.channel_layout));
+    Nan::Set(targetAudioFormat, Nan::New<String>("sampleFormat").ToLocalChecked(),
+            Nan::New<Number>(encoder->target_audio_format.sample_fmt));
+
+
+    Nan::Set(instance, Nan::New<String>("bitRate").ToLocalChecked(), Nan::New<Number>(encoder->bit_rate));
+    Nan::Set(instance, Nan::New<String>("actualAudioFormat").ToLocalChecked(), Nan::Null());
+    Nan::Set(instance, Nan::New<String>("targetAudioFormat").ToLocalChecked(), targetAudioFormat);
+    Nan::Set(instance, Nan::New<String>("formatShortName").ToLocalChecked(), Nan::Null());
+    Nan::Set(instance, Nan::New<String>("codecShortName").ToLocalChecked(), Nan::Null());
+    Nan::Set(instance, Nan::New<String>("filename").ToLocalChecked(), Nan::Null());
+    Nan::Set(instance, Nan::New<String>("mimeType").ToLocalChecked(), Nan::Null());
+    Nan::Set(instance, Nan::New<String>("encodedBufferSize").ToLocalChecked(), Nan::New<Number>(encoder->encoded_buffer_size));
+    Nan::Set(instance, Nan::New<String>("sinkBufferSize").ToLocalChecked(), Nan::New<Number>(encoder->sink_buffer_size));
+
+    info.GetReturnValue().Set(instance);
+}
+
+NAN_METHOD(GNEncoder::Attach) {
+    Nan::HandleScope scope;
+
+    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
+
+    if (info.Length() < 1 || !info[0]->IsObject()) {
+        Nan::ThrowTypeError("Expected object arg[0]");
+        return;
+    }
+    if (info.Length() < 2 || !info[1]->IsFunction()) {
+        Nan::ThrowTypeError("Expected function arg[1]");
+        return;
+    }
+
+    Local<Object> instance = info.This();
+    Local<Value> targetAudioFormatValue = instance->Get(Nan::New<String>("targetAudioFormat").ToLocalChecked());
+    if (!targetAudioFormatValue->IsObject()) {
+        Nan::ThrowTypeError("Expected targetAudioFormat to be an object");
+        return;
+    }
+
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
+
+    AttachReq *request = new AttachReq;
+
+    request->req.data = request;
+    request->callback = new Nan::Callback(info[1].As<Function>());
+
+    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<Value> formatShortName = instance->Get(Nan::New<String>("formatShortName").ToLocalChecked());
+    if (formatShortName->IsNull() || formatShortName->IsUndefined()) {
+        request->format_short_name = NULL;
+    } else {
+        request->format_short_name = new String::Utf8Value(formatShortName->ToString());
+    }
+    Local<Value> codecShortName = instance->Get(Nan::New<String>("codecShortName").ToLocalChecked());
+    if (codecShortName->IsNull() || codecShortName->IsUndefined()) {
+        request->codec_short_name = NULL;
+    } else {
+        request->codec_short_name = new String::Utf8Value(codecShortName->ToString());
+    }
+    Local<Value> filenameStr = instance->Get(Nan::New<String>("filename").ToLocalChecked());
+    if (filenameStr->IsNull() || filenameStr->IsUndefined()) {
+        request->filename = NULL;
+    } else {
+        request->filename = new String::Utf8Value(filenameStr->ToString());
+    }
+    Local<Value> mimeType = instance->Get(Nan::New<String>("mimeType").ToLocalChecked());
+    if (mimeType->IsNull() || mimeType->IsUndefined()) {
+        request->mime_type = NULL;
+    } else {
+        request->mime_type = new String::Utf8Value(mimeType->ToString());
+    }
+
+    Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
+    Local<Value> sampleRate = targetAudioFormat->Get(Nan::New<String>("sampleRate").ToLocalChecked());
+    double sample_rate = sampleRate->NumberValue();
+    double channel_layout = targetAudioFormat->Get(Nan::New<String>("channelLayout").ToLocalChecked())->NumberValue();
+    double sample_fmt = targetAudioFormat->Get(Nan::New<String>("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<String>("bitRate").ToLocalChecked())->NumberValue();
+    encoder->bit_rate = (int)bit_rate;
+
+    double sink_buffer_size = instance->Get(Nan::New<String>("sinkBufferSize").ToLocalChecked())->NumberValue();
+    encoder->sink_buffer_size = (int)sink_buffer_size;
+
+    double encoded_buffer_size = instance->Get(Nan::New<String>("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<DetachReq *>(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<uv_handle_t*>(&r->event_context->event_async), DetachAsyncFree);
+}
+
+static void DetachAfter(uv_work_t *req) {
+    Nan::HandleScope scope;
+    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->errcode < 0) {
+        argv[0] = Exception::Error(Nan::New<String>("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<GNEncoder>(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<Function>());
+    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<GrooveBuffer*>(hint);
+    groove_buffer_unref(buffer);
+}
+
+NAN_METHOD(GNEncoder::GetBuffer) {
+    Nan::HandleScope scope;
+    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
+    GrooveEncoder *encoder = gn_encoder->encoder;
+
+    GrooveBuffer *buffer;
+    int buf_result = groove_encoder_buffer_get(encoder, &buffer, 0);
+
+    uv_mutex_lock(&gn_encoder->event_context->mutex);
+    gn_encoder->event_context->emit_buffer_ok = true;
+    uv_cond_signal(&gn_encoder->event_context->cond);
+    uv_mutex_unlock(&gn_encoder->event_context->mutex);
+
+    switch (buf_result) {
+        case GROOVE_BUFFER_YES: {
+            Local<Object> object = Nan::New<Object>();
+
+            Nan::MaybeLocal<Object> bufferObject = Nan::NewBuffer(
+                    reinterpret_cast<char*>(buffer->data[0]), buffer->size,
+                    buffer_free, buffer);
+            Nan::Set(object, Nan::New<String>("buffer").ToLocalChecked(), bufferObject.ToLocalChecked());
+
+            if (buffer->item) {
+                Nan::Set(object, Nan::New<String>("item").ToLocalChecked(),
+                        GNPlaylistItem::NewInstance(buffer->item));
+            } else {
+                Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+            }
+
+            Nan::Set(object, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(buffer->pos));
+            Nan::Set(object, Nan::New<String>("pts").ToLocalChecked(), Nan::New<Number>(buffer->pts));
+
+            info.GetReturnValue().Set(object);
+            break;
+        }
+        case GROOVE_BUFFER_END: {
+            Local<Object> object = Nan::New<Object>();
+
+            Nan::Set(object, Nan::New<String>("buffer").ToLocalChecked(), Nan::Null());
+            Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+            Nan::Set(object, Nan::New<String>("pos").ToLocalChecked(), Nan::Null());
+            Nan::Set(object, Nan::New<String>("pts").ToLocalChecked(), Nan::Null());
+
+            info.GetReturnValue().Set(object);
+            break;
+        }
+        default:
+            info.GetReturnValue().Set(Nan::Null());
+    }
+}
+
+NAN_METHOD(GNEncoder::Position) {
+    Nan::HandleScope scope;
+
+    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());
+    GrooveEncoder *encoder = gn_encoder->encoder;
+
+    GroovePlaylistItem *item;
+    double pos;
+    groove_encoder_position(encoder, &item, &pos);
+
+    Local<Object> obj = Nan::New<Object>();
+    Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
+    if (item) {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
+    } else {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+    }
+
+    info.GetReturnValue().Set(obj);
+}
diff --git a/src/gn_encoder.h b/src/encoder.h
similarity index 52%
rename from src/gn_encoder.h
rename to src/encoder.h
index 063bf60..aceb580 100644
--- a/src/gn_encoder.h
+++ b/src/encoder.h
@@ -2,7 +2,7 @@
 #define GN_ENCODER_H
 
 #include <node.h>
-
+#include <nan.h>
 #include <groove/encoder.h>
 
 class GNEncoder : public node::ObjectWrap {
@@ -10,7 +10,7 @@ class GNEncoder : public node::ObjectWrap {
         static void Init();
         static v8::Handle<v8::Value> NewInstance(GrooveEncoder *encoder);
 
-        static v8::Handle<v8::Value> Create(const v8::Arguments& args);
+        static NAN_METHOD(Create);
 
         struct EventContext {
             uv_thread_t event_thread;
@@ -18,7 +18,8 @@ class GNEncoder : public node::ObjectWrap {
             uv_cond_t cond;
             uv_mutex_t mutex;
             GrooveEncoder *encoder;
-            v8::Persistent<v8::Function> event_cb;
+            Nan::Callback *event_cb;
+            bool emit_buffer_ok;
         };
 
         GrooveEncoder *encoder;
@@ -27,13 +28,12 @@ class GNEncoder : public node::ObjectWrap {
         GNEncoder();
         ~GNEncoder();
 
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
+        static NAN_METHOD(New);
 
-        static v8::Handle<v8::Value> Attach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Detach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> GetBuffer(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Position(const v8::Arguments& args);
+        static NAN_METHOD(Attach);
+        static NAN_METHOD(Detach);
+        static NAN_METHOD(GetBuffer);
+        static NAN_METHOD(Position);
 };
 
 #endif
diff --git a/src/file.cc b/src/file.cc
new file mode 100644
index 0000000..32e041a
--- /dev/null
+++ b/src/file.cc
@@ -0,0 +1,332 @@
+#include <node.h>
+#include "file.h"
+
+using namespace v8;
+
+GNFile::GNFile() {};
+GNFile::~GNFile() {};
+
+static Nan::Persistent<v8::Function> constructor;
+
+void GNFile::Init() {
+    // Prepare constructor template
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GrooveFile").ToLocalChecked());
+    tpl->InstanceTemplate()->SetInternalFieldCount(1);
+    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
+
+    // Fields
+    Nan::SetAccessor(proto, Nan::New<String>("filename").ToLocalChecked(), GetFilename);
+    Nan::SetAccessor(proto, Nan::New<String>("dirty").ToLocalChecked(), GetDirty);
+    Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
+
+    // Methods
+    Nan::SetPrototypeMethod(tpl, "close", Close);
+    Nan::SetPrototypeMethod(tpl, "getMetadata", GetMetadata);
+    Nan::SetPrototypeMethod(tpl, "setMetadata", SetMetadata);
+    Nan::SetPrototypeMethod(tpl, "metadata", Metadata);
+    Nan::SetPrototypeMethod(tpl, "shortNames", ShortNames);
+    Nan::SetPrototypeMethod(tpl, "save", Save);
+    Nan::SetPrototypeMethod(tpl, "duration", Duration);
+
+    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<Value> GNFile::NewInstance(GrooveFile *file) {
+    Nan::EscapableHandleScope scope;
+
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
+
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(instance);
+    gn_file->file = file;
+
+    return scope.Escape(instance);
+}
+
+NAN_GETTER(GNFile::GetDirty) {
+    Nan::HandleScope scope;
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+    info.GetReturnValue().Set(Nan::New<Boolean>(gn_file->file->dirty));
+}
+
+NAN_GETTER(GNFile::GetId) {
+    Nan::HandleScope scope;
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+    char buf[64];
+    snprintf(buf, sizeof(buf), "%p", gn_file->file);
+    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
+}
+
+NAN_GETTER(GNFile::GetFilename) {
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+    info.GetReturnValue().Set(Nan::New<String>(gn_file->file->filename).ToLocalChecked());
+}
+
+NAN_METHOD(GNFile::GetMetadata) {
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+
+    if (info.Length() < 1 || !info[0]->IsString()) {
+        Nan::ThrowTypeError("Expected string arg[0]");
+        return;
+    }
+
+    int flags = 0;
+    if (info.Length() >= 2) {
+        if (!info[1]->IsNumber()) {
+            Nan::ThrowTypeError("Expected number arg[1]");
+            return;
+        }
+        flags = (int)info[1]->NumberValue();
+    }
+
+    String::Utf8Value key_str(info[0]->ToString());
+    GrooveTag *tag = groove_file_metadata_get(gn_file->file, *key_str, NULL, flags);
+    if (tag)
+        info.GetReturnValue().Set(Nan::New<String>(groove_tag_value(tag)).ToLocalChecked());
+    else
+        info.GetReturnValue().Set(Nan::Null());
+}
+
+NAN_METHOD(GNFile::SetMetadata) {
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+
+    if (info.Length() < 1 || !info[0]->IsString()) {
+        Nan::ThrowTypeError("Expected string arg[0]");
+        return;
+    }
+
+    if (info.Length() < 2 || !info[0]->IsString()) {
+        Nan::ThrowTypeError("Expected string arg[1]");
+        return;
+    }
+
+    int flags = 0;
+    if (info.Length() >= 3) {
+        if (!info[2]->IsNumber()) {
+            Nan::ThrowTypeError("Expected number arg[2]");
+            return;
+        }
+        flags = (int)info[2]->NumberValue();
+    }
+
+    String::Utf8Value key_str(info[0]->ToString());
+    String::Utf8Value val_str(info[1]->ToString());
+    int err = groove_file_metadata_set(gn_file->file, *key_str, *val_str, flags);
+    if (err < 0) {
+        Nan::ThrowTypeError("set metadata failed");
+        return;
+    }
+    return;
+}
+
+NAN_METHOD(GNFile::Metadata) {
+    Nan::HandleScope scope;
+
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+    Local<Object> metadata = Nan::New<Object>();
+
+    GrooveTag *tag = NULL;
+    while ((tag = groove_file_metadata_get(gn_file->file, "", tag, 0))) {
+        Nan::Set(metadata, Nan::New<String>(groove_tag_key(tag)).ToLocalChecked(),
+                Nan::New<String>(groove_tag_value(tag)).ToLocalChecked());
+    }
+
+    info.GetReturnValue().Set(metadata);
+}
+
+NAN_METHOD(GNFile::ShortNames) {
+    Nan::HandleScope scope;
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+    info.GetReturnValue().Set(Nan::New<String>(groove_file_short_names(gn_file->file)).ToLocalChecked());
+}
+
+NAN_METHOD(GNFile::Duration) {
+    Nan::HandleScope scope;
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
+    info.GetReturnValue().Set(Nan::New<Number>(groove_file_duration(gn_file->file)));
+}
+
+struct CloseReq {
+    uv_work_t req;
+    Nan::Callback *callback;
+    GrooveFile *file;
+};
+
+static void CloseAsync(uv_work_t *req) {
+    CloseReq *r = reinterpret_cast<CloseReq *>(req->data);
+    if (r->file) {
+        groove_file_close(r->file);
+    }
+}
+
+static void CloseAfter(uv_work_t *req) {
+    Nan::HandleScope scope;
+
+    CloseReq *r = reinterpret_cast<CloseReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->file) {
+        argv[0] = Nan::Null();
+    } else {
+        argv[0] = Exception::Error(Nan::New<String>("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<GNFile>(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<Function>());
+    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<OpenReq *>(req->data);
+    r->file = groove_file_open(**r->filename);
+}
+
+static void OpenAfter(uv_work_t *req) {
+    Nan::HandleScope scope;
+    OpenReq *r = reinterpret_cast<OpenReq *>(req->data);
+
+    Local<Value> argv[2];
+    if (r->file) {
+        argv[0] = Nan::Null();
+        argv[1] = GNFile::NewInstance(r->file);
+    } else {
+        argv[0] = Exception::Error(Nan::New<String>("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<Function>());
+    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<SaveReq *>(req->data);
+    r->ret = groove_file_save(r->file);
+}
+
+static void SaveAfter(uv_work_t *req) {
+    Nan::HandleScope scope;
+
+    SaveReq *r = reinterpret_cast<SaveReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->ret) {
+        argv[0] = Exception::Error(Nan::New<String>("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<GNFile>(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<Function>());
+    request->file = gn_file->file;
+    request->req.data = request;
+
+    uv_queue_work(uv_default_loop(), &request->req, SaveAsync, (uv_after_work_cb)SaveAfter);
+
+    return;
+}
diff --git a/src/file.h b/src/file.h
new file mode 100644
index 0000000..25012bd
--- /dev/null
+++ b/src/file.h
@@ -0,0 +1,35 @@
+#ifndef GN_FILE_H
+#define GN_FILE_H
+
+#include <node.h>
+#include <nan.h>
+#include <groove/groove.h>
+
+class GNFile : public node::ObjectWrap {
+    public:
+        static void Init();
+        static v8::Handle<v8::Value> NewInstance(GrooveFile *file);
+
+        static NAN_METHOD(Open);
+
+        GrooveFile *file;
+    private:
+        GNFile();
+        ~GNFile();
+
+        static NAN_METHOD(New);
+
+        static NAN_GETTER(GetDirty);
+        static NAN_GETTER(GetId);
+        static NAN_GETTER(GetFilename);
+
+        static NAN_METHOD(Close);
+        static NAN_METHOD(Duration);
+        static NAN_METHOD(GetMetadata);
+        static NAN_METHOD(SetMetadata);
+        static NAN_METHOD(Metadata);
+        static NAN_METHOD(ShortNames);
+        static NAN_METHOD(Save);
+};
+
+#endif
diff --git a/src/fingerprinter.cc b/src/fingerprinter.cc
new file mode 100644
index 0000000..6c7eda2
--- /dev/null
+++ b/src/fingerprinter.cc
@@ -0,0 +1,381 @@
+#include "fingerprinter.h"
+#include "playlist_item.h"
+#include "playlist.h"
+
+using namespace v8;
+
+GNFingerprinter::GNFingerprinter() {};
+GNFingerprinter::~GNFingerprinter() {
+    groove_fingerprinter_destroy(printer);
+};
+
+static Nan::Persistent<v8::Function> constructor;
+
+void GNFingerprinter::Init() {
+    // Prepare constructor template
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GrooveFingerprinter").ToLocalChecked());
+    tpl->InstanceTemplate()->SetInternalFieldCount(2);
+
+    // Methods
+    Nan::SetPrototypeMethod(tpl, "attach", Attach);
+    Nan::SetPrototypeMethod(tpl, "detach", Detach);
+    Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo);
+    Nan::SetPrototypeMethod(tpl, "position", Position);
+
+    constructor.Reset(tpl->GetFunction());
+}
+
+NAN_METHOD(GNFingerprinter::New) {
+    Nan::HandleScope scope;
+
+    GNFingerprinter *obj = new GNFingerprinter();
+    obj->Wrap(info.This());
+    
+    info.GetReturnValue().Set(info.This());
+}
+
+Handle<Value> GNFingerprinter::NewInstance(GrooveFingerprinter *printer) {
+    Nan::EscapableHandleScope scope;
+
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
+
+    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);
+    gn_printer->printer = printer;
+
+    return scope.Escape(instance);
+}
+
+NAN_METHOD(GNFingerprinter::Create) {
+    Nan::HandleScope scope;
+
+    if (info.Length() < 1 || !info[0]->IsFunction()) {
+        Nan::ThrowTypeError("Expected function arg[0]");
+        return;
+    }
+
+    GrooveFingerprinter *printer = groove_fingerprinter_create();
+    if (!printer) {
+        Nan::ThrowTypeError("unable to create fingerprinter");
+        return;
+    }
+
+    // set properties on the instance with default values from
+    // GrooveFingerprinter struct
+    Local<Object> instance = GNFingerprinter::NewInstance(printer)->ToObject();
+    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);
+    EventContext *context = new EventContext;
+    gn_printer->event_context = context;
+    context->event_cb = new Nan::Callback(info[0].As<Function>());
+    context->printer = printer;
+
+
+    Nan::Set(instance, Nan::New<String>("infoQueueSize").ToLocalChecked(),
+            Nan::New<Number>(printer->info_queue_size));
+    Nan::Set(instance, Nan::New<String>("sinkBufferSize").ToLocalChecked(),
+            Nan::New<Number>(printer->sink_buffer_size));
+
+    info.GetReturnValue().Set(instance);
+}
+
+NAN_METHOD(GNFingerprinter::Position) {
+    Nan::HandleScope scope;
+
+    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());
+    GrooveFingerprinter *printer = gn_printer->printer;
+
+    GroovePlaylistItem *item;
+    double pos;
+    groove_fingerprinter_position(printer, &item, &pos);
+
+    Local<Object> obj = Nan::New<Object>();
+    Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
+    if (item) {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
+    } else {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+    }
+
+    info.GetReturnValue().Set(obj);
+}
+
+NAN_METHOD(GNFingerprinter::GetInfo) {
+    Nan::HandleScope scope;
+
+    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());
+    GrooveFingerprinter *printer = gn_printer->printer;
+
+    GrooveFingerprinterInfo print_info;
+    if (groove_fingerprinter_info_get(printer, &print_info, 0) == 1) {
+        Local<Object> object = Nan::New<Object>();
+
+        if (print_info.fingerprint) {
+            Local<Array> int_list = Nan::New<Array>();
+            for (int i = 0; i < print_info.fingerprint_size; i += 1) {
+                Nan::Set(int_list, Nan::New<Number>(i), Nan::New<Number>(print_info.fingerprint[i]));
+            }
+            Nan::Set(object, Nan::New<String>("fingerprint").ToLocalChecked(), int_list);
+        } else {
+            Nan::Set(object, Nan::New<String>("fingerprint").ToLocalChecked(), Nan::Null());
+        }
+        Nan::Set(object, Nan::New<String>("duration").ToLocalChecked(), Nan::New<Number>(print_info.duration));
+
+        if (print_info.item) {
+            Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(print_info.item));
+        } else {
+            Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+        }
+
+        groove_fingerprinter_free_info(&print_info);
+
+        info.GetReturnValue().Set(object);
+    } else {
+        info.GetReturnValue().Set(Nan::Null());
+    }
+}
+
+struct AttachReq {
+    uv_work_t req;
+    Nan::Callback *callback;
+    GrooveFingerprinter *printer;
+    GroovePlaylist *playlist;
+    int errcode;
+    Nan::Persistent<Object> 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<GNFingerprinter::EventContext *>(handle->data);
+
+    // call callback signaling that there is info ready
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    argv[0] = Nan::Null();
+
+    TryCatch try_catch;
+    context->event_cb->Call(argc, argv);
+
+    if (try_catch.HasCaught()) {
+        node::FatalException(try_catch);
+    }
+
+    uv_mutex_lock(&context->mutex);
+    uv_cond_signal(&context->cond);
+    uv_mutex_unlock(&context->mutex);
+}
+
+static void EventThreadEntry(void *arg) {
+    GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(arg);
+    while (groove_fingerprinter_info_peek(context->printer, 1) > 0) {
+        uv_mutex_lock(&context->mutex);
+        uv_async_send(&context->event_async);
+        uv_cond_wait(&context->cond, &context->mutex);
+        uv_mutex_unlock(&context->mutex);
+    }
+}
+
+static void AttachAsync(uv_work_t *req) {
+    AttachReq *r = reinterpret_cast<AttachReq *>(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<AttachReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->errcode < 0) {
+        argv[0] = Exception::Error(Nan::New<String>("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<GNFingerprinter>(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<Object> instance = info.This();
+
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
+
+    AttachReq *request = new AttachReq;
+
+    request->req.data = request;
+    request->callback = new Nan::Callback(info[1].As<Function>());
+
+    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<String>("infoQueueSize").ToLocalChecked())->NumberValue();
+    printer->sink_buffer_size = (int)instance->Get(Nan::New<String>("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<GNFingerprinter::EventContext *>(handle->data);
+    delete context->event_cb;
+    delete context;
+}
+
+static void DetachAsync(uv_work_t *req) {
+    DetachReq *r = reinterpret_cast<DetachReq *>(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<uv_handle_t*>(&r->event_context->event_async), DetachAsyncFree);
+}
+
+static void DetachAfter(uv_work_t *req) {
+    Nan::HandleScope scope;
+
+    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->errcode < 0) {
+        argv[0] = Exception::Error(Nan::New<String>("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<GNFingerprinter>(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<Function>());
+    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<Array> int_list = Local<Array>::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<Number>(i))->NumberValue();
+        raw_fingerprint[i] = (int32_t)val;
+    }
+    char *fingerprint;
+    groove_fingerprinter_encode(raw_fingerprint, len, &fingerprint);
+    delete[] raw_fingerprint;
+    Local<String> js_fingerprint = Nan::New<String>(fingerprint).ToLocalChecked();
+    groove_fingerprinter_dealloc(fingerprint);
+
+    info.GetReturnValue().Set(js_fingerprint);
+}
+
+NAN_METHOD(GNFingerprinter::Decode) {
+    Nan::HandleScope scope;
+
+    if (info.Length() < 1 || !info[0]->IsString()) {
+        Nan::ThrowTypeError("Expected String arg[0]");
+        return;
+    }
+
+    String::Utf8Value utf8fingerprint(info[0]->ToString());
+    char *fingerprint = *utf8fingerprint;
+
+    int32_t *raw_fingerprint;
+    int raw_fingerprint_len;
+    groove_fingerprinter_decode(fingerprint, &raw_fingerprint, &raw_fingerprint_len);
+    Local<Array> int_list = Nan::New<Array>();
+
+    for (int i = 0; i < raw_fingerprint_len; i += 1) {
+        Nan::Set(int_list, Nan::New<Number>(i), Nan::New<Number>(raw_fingerprint[i]));
+    }
+    groove_fingerprinter_dealloc(raw_fingerprint);
+
+    info.GetReturnValue().Set(int_list);
+}
diff --git a/src/fingerprinter.h b/src/fingerprinter.h
new file mode 100644
index 0000000..9e4142c
--- /dev/null
+++ b/src/fingerprinter.h
@@ -0,0 +1,44 @@
+#ifndef GN_FINGERPRINTER_H
+#define GN_FINGERPRINTER_H
+
+#include <node.h>
+#include <nan.h>
+#include <groovefingerprinter/fingerprinter.h>
+
+using Nan::Callback;
+
+class GNFingerprinter : public node::ObjectWrap {
+    public:
+        static void Init();
+        static v8::Handle<v8::Value> NewInstance(GrooveFingerprinter *printer);
+
+        static NAN_METHOD(Create);
+
+        static NAN_METHOD(Encode);
+        static NAN_METHOD(Decode);
+
+        struct EventContext {
+            uv_thread_t event_thread;
+            uv_async_t event_async;
+            uv_cond_t cond;
+            uv_mutex_t mutex;
+            GrooveFingerprinter *printer;
+            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
diff --git a/src/gn_encoder.cc b/src/gn_encoder.cc
deleted file mode 100644
index 0791292..0000000
--- a/src/gn_encoder.cc
+++ /dev/null
@@ -1,424 +0,0 @@
-#include <node.h>
-#include <node_buffer.h>
-#include "gn_encoder.h"
-#include "gn_playlist.h"
-#include "gn_playlist_item.h"
-
-using namespace v8;
-
-GNEncoder::GNEncoder() {};
-GNEncoder::~GNEncoder() {
-    groove_encoder_destroy(encoder);
-    delete event_context;
-};
-
-Persistent<Function> GNEncoder::constructor;
-
-template <typename target_t, typename func_t>
-static void AddMethod(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
-            FunctionTemplate::New(fn)->GetFunction());
-}
-
-void GNEncoder::Init() {
-    // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GrooveEncoder"));
-    tpl->InstanceTemplate()->SetInternalFieldCount(2);
-    // Methods
-    AddMethod(tpl, "attach", Attach);
-    AddMethod(tpl, "detach", Detach);
-    AddMethod(tpl, "getBuffer", GetBuffer);
-    AddMethod(tpl, "position", Position);
-
-    constructor = Persistent<Function>::New(tpl->GetFunction());
-}
-
-Handle<Value> GNEncoder::New(const Arguments& args) {
-    HandleScope scope;
-
-    GNEncoder *obj = new GNEncoder();
-    obj->Wrap(args.This());
-    
-    return scope.Close(args.This());
-}
-
-Handle<Value> GNEncoder::NewInstance(GrooveEncoder *encoder) {
-    HandleScope scope;
-
-    Local<Object> instance = constructor->NewInstance();
-
-    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
-    gn_encoder->encoder = encoder;
-
-    return scope.Close(instance);
-}
-
-struct AttachReq {
-    uv_work_t req;
-    Persistent<Function> callback;
-    GrooveEncoder *encoder;
-    GroovePlaylist *playlist;
-    int errcode;
-    Persistent<Object> 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, int status) {
-    HandleScope scope;
-
-    GNEncoder::EventContext *context = reinterpret_cast<GNEncoder::EventContext *>(handle->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    argv[0] = Undefined();
-
-    TryCatch try_catch;
-    context->event_cb->Call(Context::GetCurrent()->Global(), 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<GNEncoder::EventContext *>(arg);
-    while (groove_encoder_buffer_peek(context->encoder, 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<AttachReq *>(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) {
-    HandleScope scope;
-    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("encoder attach failed"));
-    } else {
-        argv[0] = Null();
-
-        Local<Object> actualAudioFormat = Object::New();
-        actualAudioFormat->Set(String::NewSymbol("sampleRate"),
-                Number::New(r->encoder->actual_audio_format.sample_rate));
-        actualAudioFormat->Set(String::NewSymbol("channelLayout"),
-                Number::New(r->encoder->actual_audio_format.channel_layout));
-        actualAudioFormat->Set(String::NewSymbol("sampleFormat"),
-                Number::New(r->encoder->actual_audio_format.sample_fmt));
-
-        r->instance->Set(String::NewSymbol("actualAudioFormat"), actualAudioFormat);
-    }
-
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNEncoder::Create(const Arguments& args) {
-    HandleScope scope;
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    GrooveEncoder *encoder = groove_encoder_create();
-    Handle<Object> instance = NewInstance(encoder)->ToObject();
-    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);
-    EventContext *context = new EventContext;
-    gn_encoder->event_context = context;
-    context->event_cb = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    context->encoder = encoder;
-
-    // set properties on the instance with default values from
-    // GrooveEncoder struct
-    Local<Object> targetAudioFormat = Object::New();
-    targetAudioFormat->Set(String::NewSymbol("sampleRate"),
-            Number::New(encoder->target_audio_format.sample_rate));
-    targetAudioFormat->Set(String::NewSymbol("channelLayout"),
-            Number::New(encoder->target_audio_format.channel_layout));
-    targetAudioFormat->Set(String::NewSymbol("sampleFormat"),
-            Number::New(encoder->target_audio_format.sample_fmt));
-
-    instance->Set(String::NewSymbol("bitRate"), Number::New(encoder->bit_rate));
-    instance->Set(String::NewSymbol("actualAudioFormat"), Null());
-    instance->Set(String::NewSymbol("targetAudioFormat"), targetAudioFormat);
-    instance->Set(String::NewSymbol("formatShortName"), Null());
-    instance->Set(String::NewSymbol("codecShortName"), Null());
-    instance->Set(String::NewSymbol("filename"), Null());
-    instance->Set(String::NewSymbol("mimeType"), Null());
-    instance->Set(String::NewSymbol("sinkBufferSize"), Number::New(encoder->sink_buffer_size));
-    instance->Set(String::NewSymbol("encodedBufferSize"), Number::New(encoder->encoded_buffer_size));
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNEncoder::Attach(const Arguments& args) {
-    HandleScope scope;
-
-    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsObject()) {
-        ThrowException(Exception::TypeError(String::New("Expected object arg[0]")));
-        return scope.Close(Undefined());
-    }
-    if (args.Length() < 2 || !args[1]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
-        return scope.Close(Undefined());
-    }
-
-    Local<Object> instance = args.This();
-    Local<Value> targetAudioFormatValue = instance->Get(String::NewSymbol("targetAudioFormat"));
-    if (!targetAudioFormatValue->IsObject()) {
-        ThrowException(Exception::TypeError(String::New("Expected targetAudioFormat to be an object")));
-        return scope.Close(Undefined());
-    }
-
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args[0]->ToObject());
-
-    AttachReq *request = new AttachReq;
-
-    request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
-    request->instance = Persistent<Object>::New(args.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<Value> formatShortName = instance->Get(String::NewSymbol("formatShortName"));
-    if (formatShortName->IsNull() || formatShortName->IsUndefined()) {
-        request->format_short_name = NULL;
-    } else {
-        request->format_short_name = new String::Utf8Value(formatShortName->ToString());
-    }
-    Local<Value> codecShortName = instance->Get(String::NewSymbol("codecShortName"));
-    if (codecShortName->IsNull() || codecShortName->IsUndefined()) {
-        request->codec_short_name = NULL;
-    } else {
-        request->codec_short_name = new String::Utf8Value(codecShortName->ToString());
-    }
-    Local<Value> filenameStr = instance->Get(String::NewSymbol("filename"));
-    if (filenameStr->IsNull() || filenameStr->IsUndefined()) {
-        request->filename = NULL;
-    } else {
-        request->filename = new String::Utf8Value(filenameStr->ToString());
-    }
-    Local<Value> mimeType = instance->Get(String::NewSymbol("mimeType"));
-    if (mimeType->IsNull() || mimeType->IsUndefined()) {
-        request->mime_type = NULL;
-    } else {
-        request->mime_type = new String::Utf8Value(mimeType->ToString());
-    }
-
-    Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
-    Local<Value> sampleRate = targetAudioFormat->Get(String::NewSymbol("sampleRate"));
-    double sample_rate = sampleRate->NumberValue();
-    double channel_layout = targetAudioFormat->Get(String::NewSymbol("channelLayout"))->NumberValue();
-    double sample_fmt = targetAudioFormat->Get(String::NewSymbol("sampleFormat"))->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(String::NewSymbol("bitRate"))->NumberValue();
-    encoder->bit_rate = (int)bit_rate;
-
-    double sink_buffer_size = instance->Get(String::NewSymbol("sinkBufferSize"))->NumberValue();
-    encoder->sink_buffer_size = (int)sink_buffer_size;
-
-    double encoded_buffer_size = instance->Get(String::NewSymbol("encodedBufferSize"))->NumberValue();
-    encoder->encoded_buffer_size = (int)encoded_buffer_size;
-
-    uv_queue_work(uv_default_loop(), &request->req, AttachAsync,
-            (uv_after_work_cb)AttachAfter);
-
-    return scope.Close(Undefined());
-}
-
-struct DetachReq {
-    uv_work_t req;
-    GrooveEncoder *encoder;
-    Persistent<Function> 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<DetachReq *>(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<uv_handle_t*>(&r->event_context->event_async), DetachAsyncFree);
-}
-
-static void DetachAfter(uv_work_t *req) {
-    HandleScope scope;
-    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("encoder detach failed"));
-    } else {
-        argv[0] = Null();
-    }
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNEncoder::Detach(const Arguments& args) {
-    HandleScope scope;
-    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    GrooveEncoder *encoder = gn_encoder->encoder;
-
-    if (!encoder->playlist) {
-        ThrowException(Exception::Error(String::New("detach: not attached")));
-        return scope.Close(Undefined());
-    }
-
-    DetachReq *request = new DetachReq;
-
-    request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    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 scope.Close(Undefined());
-}
-
-static void buffer_free(char *data, void *hint) {
-    GrooveBuffer *buffer = reinterpret_cast<GrooveBuffer*>(hint);
-    groove_buffer_unref(buffer);
-}
-
-Handle<Value> GNEncoder::GetBuffer(const Arguments& args) {
-    HandleScope scope;
-    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());
-    GrooveEncoder *encoder = gn_encoder->encoder;
-
-    GrooveBuffer *buffer;
-    switch (groove_encoder_buffer_get(encoder, &buffer, 0)) {
-        case GROOVE_BUFFER_YES: {
-            Local<Object> object = Object::New();
-
-            Handle<Object> bufferObject = node::Buffer::New(
-                    reinterpret_cast<char*>(buffer->data[0]), buffer->size,
-                    buffer_free, buffer)->handle_;
-            object->Set(String::NewSymbol("buffer"), bufferObject);
-
-            if (buffer->item) {
-                object->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(buffer->item));
-            } else {
-                object->Set(String::NewSymbol("item"), Null());
-            }
-            object->Set(String::NewSymbol("pos"), Number::New(buffer->pos));
-            object->Set(String::NewSymbol("pts"), Number::New(buffer->pts));
-            return scope.Close(object);
-        }
-        case GROOVE_BUFFER_END: {
-            Local<Object> object = Object::New();
-            object->Set(String::NewSymbol("buffer"), Null());
-            object->Set(String::NewSymbol("item"), Null());
-            object->Set(String::NewSymbol("pos"), Null());
-            object->Set(String::NewSymbol("pts"), Null());
-            return scope.Close(object);
-        }
-        default:
-            return scope.Close(Null());
-    }
-}
-
-Handle<Value> GNEncoder::Position(const Arguments& args) {
-    HandleScope scope;
-
-    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(args.This());
-    GrooveEncoder *encoder = gn_encoder->encoder;
-
-    GroovePlaylistItem *item;
-    double pos;
-    groove_encoder_position(encoder, &item, &pos);
-
-    Local<Object> obj = Object::New();
-    obj->Set(String::NewSymbol("pos"), Number::New(pos));
-    if (item) {
-        obj->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(item));
-    } else {
-        obj->Set(String::NewSymbol("item"), Null());
-    }
-    return scope.Close(obj);
-}
diff --git a/src/gn_file.cc b/src/gn_file.cc
deleted file mode 100644
index ef38f50..0000000
--- a/src/gn_file.cc
+++ /dev/null
@@ -1,332 +0,0 @@
-#include <node.h>
-#include "gn_file.h"
-
-using namespace v8;
-
-GNFile::GNFile() {};
-GNFile::~GNFile() {};
-
-Persistent<Function> GNFile::constructor;
-
-template <typename target_t, typename func_t>
-static void AddGetter(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->SetAccessor(String::New(name), fn);
-}
-
-template <typename target_t, typename func_t>
-static void AddMethod(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
-            FunctionTemplate::New(fn)->GetFunction());
-}
-
-void GNFile::Init() {
-    // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GrooveFile"));
-    tpl->InstanceTemplate()->SetInternalFieldCount(1);
-    // Fields
-    AddGetter(tpl, "filename", GetFilename);
-    AddGetter(tpl, "dirty", GetDirty);
-    AddGetter(tpl, "id", GetId);
-    // Methods
-    AddMethod(tpl, "close", Close);
-    AddMethod(tpl, "getMetadata", GetMetadata);
-    AddMethod(tpl, "setMetadata", SetMetadata);
-    AddMethod(tpl, "metadata", Metadata);
-    AddMethod(tpl, "shortNames", ShortNames);
-    AddMethod(tpl, "save", Save);
-    AddMethod(tpl, "duration", Duration);
-
-    constructor = Persistent<Function>::New(tpl->GetFunction());
-}
-
-Handle<Value> GNFile::New(const Arguments& args) {
-    HandleScope scope;
-
-    GNFile *obj = new GNFile();
-    obj->Wrap(args.This());
-    
-    return scope.Close(args.This());
-}
-
-Handle<Value> GNFile::NewInstance(GrooveFile *file) {
-    HandleScope scope;
-
-    Local<Object> instance = constructor->NewInstance();
-
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(instance);
-    gn_file->file = file;
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNFile::GetDirty(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
-    return scope.Close(Boolean::New(gn_file->file->dirty));
-}
-
-Handle<Value> GNFile::GetId(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
-    char buf[64];
-    snprintf(buf, sizeof(buf), "%p", gn_file->file);
-    return scope.Close(String::New(buf));
-}
-
-Handle<Value> GNFile::GetFilename(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());
-    return scope.Close(String::New(gn_file->file->filename));
-}
-
-Handle<Value> GNFile::GetMetadata(const Arguments& args) {
-    HandleScope scope;
-
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsString()) {
-        ThrowException(Exception::TypeError(String::New("Expected string arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    int flags = 0;
-    if (args.Length() >= 2) {
-        if (!args[1]->IsNumber()) {
-            ThrowException(Exception::TypeError(String::New("Expected number arg[1]")));
-            return scope.Close(Undefined());
-        }
-        flags = (int)args[1]->NumberValue();
-    }
-
-    String::Utf8Value key_str(args[0]->ToString());
-    GrooveTag *tag = groove_file_metadata_get(gn_file->file, *key_str, NULL, flags);
-    if (tag)
-        return scope.Close(String::New(groove_tag_value(tag)));
-    return scope.Close(Null());
-}
-
-Handle<Value> GNFile::SetMetadata(const Arguments& args) {
-    HandleScope scope;
-
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsString()) {
-        ThrowException(Exception::TypeError(String::New("Expected string arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    if (args.Length() < 2 || !args[0]->IsString()) {
-        ThrowException(Exception::TypeError(String::New("Expected string arg[1]")));
-        return scope.Close(Undefined());
-    }
-
-    int flags = 0;
-    if (args.Length() >= 3) {
-        if (!args[2]->IsNumber()) {
-            ThrowException(Exception::TypeError(String::New("Expected number arg[2]")));
-            return scope.Close(Undefined());
-        }
-        flags = (int)args[2]->NumberValue();
-    }
-
-    String::Utf8Value key_str(args[0]->ToString());
-    String::Utf8Value val_str(args[1]->ToString());
-    int err = groove_file_metadata_set(gn_file->file, *key_str, *val_str, flags);
-    if (err < 0) {
-        ThrowException(Exception::Error(String::New("set metadata failed")));
-        return scope.Close(Undefined());
-    }
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNFile::Metadata(const Arguments& args) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-    Local<Object> metadata = Object::New();
-
-    GrooveTag *tag = NULL;
-    while ((tag = groove_file_metadata_get(gn_file->file, "", tag, 0)))
-        metadata->Set(String::New(groove_tag_key(tag)), String::New(groove_tag_value(tag)));
-
-    return scope.Close(metadata);
-}
-
-Handle<Value> GNFile::ShortNames(const Arguments& args) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-    return scope.Close(String::New(groove_file_short_names(gn_file->file)));
-}
-
-Handle<Value> GNFile::Duration(const Arguments& args) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-    return scope.Close(Number::New(groove_file_duration(gn_file->file)));
-}
-
-struct CloseReq {
-    uv_work_t req;
-    Persistent<Function> callback;
-    GrooveFile *file;
-};
-
-static void CloseAsync(uv_work_t *req) {
-    CloseReq *r = reinterpret_cast<CloseReq *>(req->data);
-    if (r->file) {
-        groove_file_close(r->file);
-    }
-}
-
-static void CloseAfter(uv_work_t *req) {
-    HandleScope scope;
-    CloseReq *r = reinterpret_cast<CloseReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->file) {
-        argv[0] = Null();
-    } else {
-        argv[0] = Exception::Error(String::New("file already closed"));
-    }
-
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNFile::Close(const Arguments& args) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    CloseReq *request = new CloseReq;
-
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    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 scope.Close(Undefined());
-}
-
-struct OpenReq {
-    uv_work_t req;
-    GrooveFile *file;
-    String::Utf8Value *filename;
-    Persistent<Function> callback;
-};
-
-static void OpenAsync(uv_work_t *req) {
-    OpenReq *r = reinterpret_cast<OpenReq *>(req->data);
-    r->file = groove_file_open(**r->filename);
-}
-
-static void OpenAfter(uv_work_t *req) {
-    HandleScope scope;
-    OpenReq *r = reinterpret_cast<OpenReq *>(req->data);
-
-    Handle<Value> argv[2];
-    if (r->file) {
-        argv[0] = Null();
-        argv[1] = GNFile::NewInstance(r->file);
-    } else {
-        argv[0] = Exception::Error(String::New("open file failed"));
-        argv[1] = Null();
-    }
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), 2, argv);
-
-    // cleanup
-    delete r->filename;
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNFile::Open(const Arguments& args) {
-    HandleScope scope;
-
-    if (args.Length() < 1 || !args[0]->IsString()) {
-        ThrowException(Exception::TypeError(String::New("Expected string arg[0]")));
-        return scope.Close(Undefined());
-    }
-    if (args.Length() < 2 || !args[1]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
-        return scope.Close(Undefined());
-    }
-    OpenReq *request = new OpenReq;
-
-    request->filename = new String::Utf8Value(args[0]->ToString());
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
-    request->req.data = request;
-
-    uv_queue_work(uv_default_loop(), &request->req, OpenAsync, (uv_after_work_cb)OpenAfter);
-
-    return scope.Close(Undefined());
-}
-
-struct SaveReq {
-    uv_work_t req;
-    Persistent<Function> callback;
-    GrooveFile *file;
-    int ret;
-};
-
-static void SaveAsync(uv_work_t *req) {
-    SaveReq *r = reinterpret_cast<SaveReq *>(req->data);
-    r->ret = groove_file_save(r->file);
-}
-
-static void SaveAfter(uv_work_t *req) {
-    HandleScope scope;
-    SaveReq *r = reinterpret_cast<SaveReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->ret < 0) {
-        argv[0] = Exception::Error(String::New("save failed"));
-    } else {
-        argv[0] = Null();
-    }
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNFile::Save(const Arguments& args) {
-    HandleScope scope;
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    SaveReq *request = new SaveReq;
-
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    request->file = gn_file->file;
-    request->req.data = request;
-
-    uv_queue_work(uv_default_loop(), &request->req, SaveAsync, (uv_after_work_cb)SaveAfter);
-
-    return scope.Close(Undefined());
-}
diff --git a/src/gn_file.h b/src/gn_file.h
deleted file mode 100644
index bc4b6c2..0000000
--- a/src/gn_file.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef GN_FILE_H
-#define GN_FILE_H
-
-#include <node.h>
-
-#include <groove/groove.h>
-
-class GNFile : public node::ObjectWrap {
-    public:
-        static void Init();
-        static v8::Handle<v8::Value> NewInstance(GrooveFile *file);
-
-        static v8::Handle<v8::Value> Open(const v8::Arguments& args);
-
-        GrooveFile *file;
-    private:
-        GNFile();
-        ~GNFile();
-
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
-
-        static v8::Handle<v8::Value> GetDirty(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-        static v8::Handle<v8::Value> GetId(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-        static v8::Handle<v8::Value> GetFilename(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-
-        static v8::Handle<v8::Value> Close(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Duration(const v8::Arguments& args);
-        static v8::Handle<v8::Value> GetMetadata(const v8::Arguments& args);
-        static v8::Handle<v8::Value> SetMetadata(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Metadata(const v8::Arguments& args);
-        static v8::Handle<v8::Value> ShortNames(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Save(const v8::Arguments& args);
-};
-
-#endif
diff --git a/src/gn_fingerprinter.cc b/src/gn_fingerprinter.cc
deleted file mode 100644
index 12d64ad..0000000
--- a/src/gn_fingerprinter.cc
+++ /dev/null
@@ -1,373 +0,0 @@
-#include <node.h>
-#include "gn_fingerprinter.h"
-#include "gn_playlist_item.h"
-#include "gn_playlist.h"
-
-using namespace v8;
-
-GNFingerprinter::GNFingerprinter() {};
-GNFingerprinter::~GNFingerprinter() {
-    groove_fingerprinter_destroy(printer);
-};
-
-Persistent<Function> GNFingerprinter::constructor;
-
-template <typename target_t, typename func_t>
-static void AddGetter(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
-}
-
-template <typename target_t, typename func_t>
-static void AddMethod(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
-            FunctionTemplate::New(fn)->GetFunction());
-}
-
-void GNFingerprinter::Init() {
-    // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GrooveFingerprinter"));
-    tpl->InstanceTemplate()->SetInternalFieldCount(2);
-    // Methods
-    AddMethod(tpl, "attach", Attach);
-    AddMethod(tpl, "detach", Detach);
-    AddMethod(tpl, "getInfo", GetInfo);
-    AddMethod(tpl, "position", Position);
-
-    constructor = Persistent<Function>::New(tpl->GetFunction());
-}
-
-Handle<Value> GNFingerprinter::New(const Arguments& args) {
-    HandleScope scope;
-
-    GNFingerprinter *obj = new GNFingerprinter();
-    obj->Wrap(args.This());
-    
-    return scope.Close(args.This());
-}
-
-Handle<Value> GNFingerprinter::NewInstance(GrooveFingerprinter *printer) {
-    HandleScope scope;
-
-    Local<Object> instance = constructor->NewInstance();
-
-    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);
-    gn_printer->printer = printer;
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNFingerprinter::Create(const Arguments& args) {
-    HandleScope scope;
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    GrooveFingerprinter *printer = groove_fingerprinter_create();
-    if (!printer) {
-        ThrowException(Exception::Error(String::New("unable to create fingerprinter")));
-        return scope.Close(Undefined());
-    }
-
-    // set properties on the instance with default values from
-    // GrooveFingerprinter struct
-    Local<Object> instance = GNFingerprinter::NewInstance(printer)->ToObject();
-    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);
-    EventContext *context = new EventContext;
-    gn_printer->event_context = context;
-    context->event_cb = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    context->printer = printer;
-
-
-    instance->Set(String::NewSymbol("infoQueueSize"), Number::New(printer->info_queue_size));
-    instance->Set(String::NewSymbol("sinkBufferSize"), Number::New(printer->sink_buffer_size));
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNFingerprinter::Position(const Arguments& args) {
-    HandleScope scope;
-
-    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(args.This());
-    GrooveFingerprinter *printer = gn_printer->printer;
-
-    GroovePlaylistItem *item;
-    double pos;
-    groove_fingerprinter_position(printer, &item, &pos);
-
-    Local<Object> obj = Object::New();
-    obj->Set(String::NewSymbol("pos"), Number::New(pos));
-    if (item) {
-        obj->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(item));
-    } else {
-        obj->Set(String::NewSymbol("item"), Null());
-    }
-    return scope.Close(obj);
-}
-
-Handle<Value> GNFingerprinter::GetInfo(const Arguments& args) {
-    HandleScope scope;
-    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(args.This());
-    GrooveFingerprinter *printer = gn_printer->printer;
-
-    GrooveFingerprinterInfo info;
-    if (groove_fingerprinter_info_get(printer, &info, 0) == 1) {
-        Local<Object> object = Object::New();
-
-        if (info.fingerprint) {
-            Local<Array> int_list = Array::New();
-            for (int i = 0; i < info.fingerprint_size; i += 1) {
-                int_list->Set(Number::New(i), Number::New(info.fingerprint[i]));
-            }
-            object->Set(String::NewSymbol("fingerprint"), int_list);
-        } else {
-            object->Set(String::NewSymbol("fingerprint"), Null());
-        }
-        object->Set(String::NewSymbol("duration"), Number::New(info.duration));
-
-        if (info.item) {
-            object->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(info.item));
-        } else {
-            object->Set(String::NewSymbol("item"), Null());
-        }
-
-        groove_fingerprinter_free_info(&info);
-        return scope.Close(object);
-    } else {
-        return scope.Close(Null());
-    }
-}
-
-struct AttachReq {
-    uv_work_t req;
-    Persistent<Function> callback;
-    GrooveFingerprinter *printer;
-    GroovePlaylist *playlist;
-    int errcode;
-    Persistent<Object> instance;
-    GNFingerprinter::EventContext *event_context;
-};
-
-static void EventAsyncCb(uv_async_t *handle, int status) {
-    HandleScope scope;
-
-    GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(handle->data);
-
-    // call callback signaling that there is info ready
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    argv[0] = Null();
-
-    TryCatch try_catch;
-    context->event_cb->Call(Context::GetCurrent()->Global(), 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<GNFingerprinter::EventContext *>(arg);
-    while (groove_fingerprinter_info_peek(context->printer, 1) > 0) {
-        uv_mutex_lock(&context->mutex);
-        uv_async_send(&context->event_async);
-        uv_cond_wait(&context->cond, &context->mutex);
-        uv_mutex_unlock(&context->mutex);
-    }
-}
-
-static void AttachAsync(uv_work_t *req) {
-    AttachReq *r = reinterpret_cast<AttachReq *>(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) {
-    HandleScope scope;
-    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("fingerprinter attach failed"));
-    } else {
-        argv[0] = Null();
-    }
-
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNFingerprinter::Attach(const Arguments& args) {
-    HandleScope scope;
-
-    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsObject()) {
-        ThrowException(Exception::TypeError(String::New("Expected object arg[0]")));
-        return scope.Close(Undefined());
-    }
-    if (args.Length() < 2 || !args[1]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
-        return scope.Close(Undefined());
-    }
-
-    Local<Object> instance = args.This();
-
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args[0]->ToObject());
-
-    AttachReq *request = new AttachReq;
-
-    request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
-    request->instance = Persistent<Object>::New(args.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(String::NewSymbol("infoQueueSize"))->NumberValue();
-    printer->sink_buffer_size = (int)instance->Get(String::NewSymbol("sinkBufferSize"))->NumberValue();
-
-    uv_queue_work(uv_default_loop(), &request->req, AttachAsync,
-            (uv_after_work_cb)AttachAfter);
-
-    return scope.Close(Undefined());
-}
-
-struct DetachReq {
-    uv_work_t req;
-    GrooveFingerprinter *printer;
-    Persistent<Function> callback;
-    int errcode;
-    GNFingerprinter::EventContext *event_context;
-};
-
-static void DetachAsyncFree(uv_handle_t *handle) {
-    GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(handle->data);
-    delete context;
-}
-
-static void DetachAsync(uv_work_t *req) {
-    DetachReq *r = reinterpret_cast<DetachReq *>(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<uv_handle_t*>(&r->event_context->event_async), DetachAsyncFree);
-}
-
-static void DetachAfter(uv_work_t *req) {
-    HandleScope scope;
-    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("fingerprinter detach failed"));
-    } else {
-        argv[0] = Null();
-    }
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNFingerprinter::Detach(const Arguments& args) {
-    HandleScope scope;
-    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    DetachReq *request = new DetachReq;
-
-    request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    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 scope.Close(Undefined());
-}
-
-Handle<Value> GNFingerprinter::Encode(const Arguments& args) {
-    HandleScope scope;
-
-    if (args.Length() < 1 || !args[0]->IsArray()) {
-        ThrowException(Exception::TypeError(String::New("Expected Array arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    Local<Array> int_list = Local<Array>::Cast(args[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(Number::New(i))->NumberValue();
-        raw_fingerprint[i] = (int32_t)val;
-    }
-    char *fingerprint;
-    groove_fingerprinter_encode(raw_fingerprint, len, &fingerprint);
-    delete[] raw_fingerprint;
-    Local<String> js_fingerprint = String::New(fingerprint);
-    groove_fingerprinter_dealloc(fingerprint);
-    return scope.Close(js_fingerprint);
-}
-
-Handle<Value> GNFingerprinter::Decode(const Arguments& args) {
-    HandleScope scope;
-
-    if (args.Length() < 1 || !args[0]->IsString()) {
-        ThrowException(Exception::TypeError(String::New("Expected String arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    String::Utf8Value utf8fingerprint(args[0]->ToString());
-    char *fingerprint = *utf8fingerprint;
-
-    int32_t *raw_fingerprint;
-    int raw_fingerprint_len;
-    groove_fingerprinter_decode(fingerprint, &raw_fingerprint, &raw_fingerprint_len);
-    Local<Array> int_list = Array::New();
-
-    for (int i = 0; i < raw_fingerprint_len; i += 1) {
-        int_list->Set(Number::New(i), Number::New(raw_fingerprint[i]));
-    }
-    groove_fingerprinter_dealloc(raw_fingerprint);
-
-    return scope.Close(int_list);
-}
diff --git a/src/gn_fingerprinter.h b/src/gn_fingerprinter.h
deleted file mode 100644
index 16a5c56..0000000
--- a/src/gn_fingerprinter.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef GN_FINGERPRINTER_H
-#define GN_FINGERPRINTER_H
-
-#include <node.h>
-
-#include <groovefingerprinter/fingerprinter.h>
-
-class GNFingerprinter : public node::ObjectWrap {
-    public:
-        static void Init();
-        static v8::Handle<v8::Value> NewInstance(GrooveFingerprinter *printer);
-
-        static v8::Handle<v8::Value> Create(const v8::Arguments& args);
-
-        static v8::Handle<v8::Value> Encode(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Decode(const v8::Arguments& args);
-
-        struct EventContext {
-            uv_thread_t event_thread;
-            uv_async_t event_async;
-            uv_cond_t cond;
-            uv_mutex_t mutex;
-            GrooveFingerprinter *printer;
-            v8::Persistent<v8::Function> event_cb;
-        };
-
-        EventContext *event_context;
-        GrooveFingerprinter *printer;
-
-    private:
-        GNFingerprinter();
-        ~GNFingerprinter();
-
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
-
-        static v8::Handle<v8::Value> Attach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Detach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> GetInfo(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Position(const v8::Arguments& args);
-};
-
-#endif
diff --git a/src/gn_player.cc b/src/gn_player.cc
deleted file mode 100644
index a991353..0000000
--- a/src/gn_player.cc
+++ /dev/null
@@ -1,357 +0,0 @@
-#include <node.h>
-#include "gn_player.h"
-#include "gn_playlist.h"
-#include "gn_playlist_item.h"
-
-using namespace v8;
-
-GNPlayer::GNPlayer() {};
-GNPlayer::~GNPlayer() {
-    groove_player_destroy(player);
-    delete event_context;
-};
-
-Persistent<Function> GNPlayer::constructor;
-
-template <typename target_t, typename func_t>
-static void AddGetter(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
-}
-
-template <typename target_t, typename func_t>
-static void AddMethod(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
-            FunctionTemplate::New(fn)->GetFunction());
-}
-
-void GNPlayer::Init() {
-    // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GroovePlayer"));
-    tpl->InstanceTemplate()->SetInternalFieldCount(2);
-    // Fields
-    AddGetter(tpl, "id", GetId);
-    AddGetter(tpl, "playlist", GetPlaylist);
-    // Methods
-    AddMethod(tpl, "attach", Attach);
-    AddMethod(tpl, "detach", Detach);
-    AddMethod(tpl, "position", Position);
-
-    constructor = Persistent<Function>::New(tpl->GetFunction());
-}
-
-Handle<Value> GNPlayer::New(const Arguments& args) {
-    HandleScope scope;
-
-    GNPlayer *obj = new GNPlayer();
-    obj->Wrap(args.This());
-    
-    return scope.Close(args.This());
-}
-
-Handle<Value> GNPlayer::NewInstance(GroovePlayer *player) {
-    HandleScope scope;
-
-    Local<Object> instance = constructor->NewInstance();
-
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);
-    gn_player->player = player;
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNPlayer::GetId(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
-    char buf[64];
-    snprintf(buf, sizeof(buf), "%p", gn_player->player);
-    return scope.Close(String::New(buf));
-}
-
-Handle<Value> GNPlayer::GetPlaylist(Local<String> property,
-        const AccessorInfo &info)
-{
-    HandleScope scope;
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
-    GroovePlaylist *playlist = gn_player->player->playlist;
-    if (playlist) {
-        return scope.Close(GNPlaylist::NewInstance(playlist));
-    } else {
-        return scope.Close(Null());
-    }
-}
-
-Handle<Value> GNPlayer::Position(const Arguments& args) {
-    HandleScope scope;
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(args.This());
-    GroovePlaylistItem *item;
-    double pos;
-    groove_player_position(gn_player->player, &item, &pos);
-    Local<Object> obj = Object::New();
-    obj->Set(String::NewSymbol("pos"), Number::New(pos));
-    if (item) {
-        obj->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(item));
-    } else {
-        obj->Set(String::NewSymbol("item"), Null());
-    }
-    return scope.Close(obj);
-}
-
-struct AttachReq {
-    uv_work_t req;
-    Persistent<Function> callback;
-    GroovePlayer *player;
-    GroovePlaylist *playlist;
-    int errcode;
-    Persistent<Object> instance;
-    int device_index;
-    GNPlayer::EventContext *event_context;
-};
-
-static void EventAsyncCb(uv_async_t *handle, int status) {
-    HandleScope scope;
-
-    GNPlayer::EventContext *context = reinterpret_cast<GNPlayer::EventContext *>(handle->data);
-
-    // flush events
-    GroovePlayerEvent event;
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    while (groove_player_event_get(context->player, &event, 0) > 0) {
-        argv[0] = Number::New(event.type);
-
-        TryCatch try_catch;
-        context->event_cb->Call(Context::GetCurrent()->Global(), 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<GNPlayer::EventContext *>(arg);
-    while (groove_player_event_peek(context->player, 1) > 0) {
-        uv_mutex_lock(&context->mutex);
-        uv_async_send(&context->event_async);
-        uv_cond_wait(&context->cond, &context->mutex);
-        uv_mutex_unlock(&context->mutex);
-    }
-}
-
-static void AttachAsync(uv_work_t *req) {
-    AttachReq *r = reinterpret_cast<AttachReq *>(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) {
-    HandleScope scope;
-    AttachReq *r = reinterpret_cast<AttachReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("player attach failed"));
-    } else {
-        argv[0] = Null();
-
-        Local<Object> actualAudioFormat = Object::New();
-        actualAudioFormat->Set(String::NewSymbol("sampleRate"),
-                Number::New(r->player->actual_audio_format.sample_rate));
-        actualAudioFormat->Set(String::NewSymbol("channelLayout"),
-                Number::New(r->player->actual_audio_format.channel_layout));
-        actualAudioFormat->Set(String::NewSymbol("sampleFormat"),
-                Number::New(r->player->actual_audio_format.sample_fmt));
-
-        r->instance->Set(String::NewSymbol("actualAudioFormat"), actualAudioFormat);
-    }
-
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNPlayer::Create(const Arguments& args) {
-    HandleScope scope;
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    GroovePlayer *player = groove_player_create();
-    Handle<Object> instance = NewInstance(player)->ToObject();
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);
-    EventContext *context = new EventContext;
-    gn_player->event_context = context;
-    context->event_cb = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    context->player = player;
-
-    // set properties on the instance with default values from
-    // GroovePlayer struct
-    Local<Object> targetAudioFormat = Object::New();
-    targetAudioFormat->Set(String::NewSymbol("sampleRate"),
-            Number::New(player->target_audio_format.sample_rate));
-    targetAudioFormat->Set(String::NewSymbol("channelLayout"),
-            Number::New(player->target_audio_format.channel_layout));
-    targetAudioFormat->Set(String::NewSymbol("sampleFormat"),
-            Number::New(player->target_audio_format.sample_fmt));
-
-    instance->Set(String::NewSymbol("deviceIndex"), Null());
-    instance->Set(String::NewSymbol("actualAudioFormat"), Null());
-    instance->Set(String::NewSymbol("targetAudioFormat"), targetAudioFormat);
-    instance->Set(String::NewSymbol("deviceBufferSize"),
-            Number::New(player->device_buffer_size));
-    instance->Set(String::NewSymbol("sinkBufferSize"),
-            Number::New(player->sink_buffer_size));
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNPlayer::Attach(const Arguments& args) {
-    HandleScope scope;
-
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsObject()) {
-        ThrowException(Exception::TypeError(String::New("Expected object arg[0]")));
-        return scope.Close(Undefined());
-    }
-    if (args.Length() < 2 || !args[1]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
-        return scope.Close(Undefined());
-    }
-
-    Local<Object> instance = args.This();
-    Local<Value> targetAudioFormatValue = instance->Get(String::NewSymbol("targetAudioFormat"));
-    if (!targetAudioFormatValue->IsObject()) {
-        ThrowException(Exception::TypeError(String::New("Expected targetAudioFormat to be an object")));
-        return scope.Close(Undefined());
-    }
-
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args[0]->ToObject());
-
-    AttachReq *request = new AttachReq;
-
-    request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
-    request->instance = Persistent<Object>::New(args.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<Value> deviceIndex = instance->Get(String::NewSymbol("deviceIndex"));
-
-    if (deviceIndex->IsNull() || deviceIndex->IsUndefined()) {
-        request->device_index = -1;
-    } else {
-        request->device_index = (int) deviceIndex->NumberValue();
-    }
-    Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
-    Local<Value> sampleRate = targetAudioFormat->Get(String::NewSymbol("sampleRate"));
-    double sample_rate = sampleRate->NumberValue();
-    double channel_layout = targetAudioFormat->Get(String::NewSymbol("channelLayout"))->NumberValue();
-    double sample_fmt = targetAudioFormat->Get(String::NewSymbol("sampleFormat"))->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(String::NewSymbol("deviceBufferSize"))->NumberValue();
-    player->device_buffer_size = (int)device_buffer_size;
-
-    double sink_buffer_size = instance->Get(String::NewSymbol("sinkBufferSize"))->NumberValue();
-    player->sink_buffer_size = (int)sink_buffer_size;
-
-    uv_queue_work(uv_default_loop(), &request->req, AttachAsync,
-            (uv_after_work_cb)AttachAfter);
-
-    return scope.Close(Undefined());
-}
-
-struct DetachReq {
-    uv_work_t req;
-    GroovePlayer *player;
-    Persistent<Function> 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<DetachReq *>(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<uv_handle_t*>(&r->event_context->event_async), DetachAsyncFree);
-}
-
-static void DetachAfter(uv_work_t *req) {
-    HandleScope scope;
-    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
-
-    const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("player detach failed"));
-    } else {
-        argv[0] = Null();
-    }
-    TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
-
-    delete r;
-
-    if (try_catch.HasCaught()) {
-        node::FatalException(try_catch);
-    }
-}
-
-Handle<Value> GNPlayer::Detach(const Arguments& args) {
-    HandleScope scope;
-    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(args.This());
-
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
-    }
-
-    DetachReq *request = new DetachReq;
-
-    request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
-    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 scope.Close(Undefined());
-}
diff --git a/src/gn_player.h b/src/gn_player.h
deleted file mode 100644
index d5092de..0000000
--- a/src/gn_player.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#ifndef GN_PLAYER_H
-#define GN_PLAYER_H
-
-#include <node.h>
-
-#include <grooveplayer/player.h>
-
-class GNPlayer : public node::ObjectWrap {
-    public:
-        static void Init();
-        static v8::Handle<v8::Value> NewInstance(GroovePlayer *player);
-
-        static v8::Handle<v8::Value> Create(const v8::Arguments& args);
-
-        struct EventContext {
-            uv_thread_t event_thread;
-            uv_async_t event_async;
-            uv_cond_t cond;
-            uv_mutex_t mutex;
-            GroovePlayer *player;
-            v8::Persistent<v8::Function> event_cb;
-        };
-
-
-        GroovePlayer *player;
-        EventContext *event_context;
-
-    private:
-        GNPlayer();
-        ~GNPlayer();
-
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
-
-        static v8::Handle<v8::Value> GetId(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-
-        static v8::Handle<v8::Value> GetPlaylist(
-                v8::Local<v8::String> property, const v8::AccessorInfo &info);
-
-        static v8::Handle<v8::Value> Attach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Detach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Position(const v8::Arguments& args);
-};
-
-#endif
-
diff --git a/src/gn_playlist.cc b/src/gn_playlist.cc
deleted file mode 100644
index 050d561..0000000
--- a/src/gn_playlist.cc
+++ /dev/null
@@ -1,240 +0,0 @@
-#include <node.h>
-#include "gn_playlist.h"
-#include "gn_playlist_item.h"
-#include "gn_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);
-};
-
-Persistent<Function> GNPlaylist::constructor;
-
-template <typename target_t, typename func_t>
-static void AddGetter(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
-}
-
-template <typename target_t, typename func_t>
-static void AddMethod(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
-            FunctionTemplate::New(fn)->GetFunction());
-}
-
-void GNPlaylist::Init() {
-    // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GroovePlaylist"));
-    tpl->InstanceTemplate()->SetInternalFieldCount(1);
-    // Fields
-    AddGetter(tpl, "id", GetId);
-    AddGetter(tpl, "gain", GetGain);
-    // Methods
-    AddMethod(tpl, "play", Play);
-    AddMethod(tpl, "items", Playlist);
-    AddMethod(tpl, "pause", Pause);
-    AddMethod(tpl, "seek", Seek);
-    AddMethod(tpl, "insert", Insert);
-    AddMethod(tpl, "remove", Remove);
-    AddMethod(tpl, "position", DecodePosition);
-    AddMethod(tpl, "playing", Playing);
-    AddMethod(tpl, "clear", Clear);
-    AddMethod(tpl, "count", Count);
-    AddMethod(tpl, "setItemGain", SetItemGain);
-    AddMethod(tpl, "setItemPeak", SetItemPeak);
-    AddMethod(tpl, "setGain", SetGain);
-    AddMethod(tpl, "setFillMode", SetFillMode);
-
-    constructor = Persistent<Function>::New(tpl->GetFunction());
-}
-
-Handle<Value> GNPlaylist::New(const Arguments& args) {
-    HandleScope scope;
-
-    GNPlaylist *obj = new GNPlaylist();
-    obj->Wrap(args.This());
-    
-    return scope.Close(args.This());
-}
-
-Handle<Value> GNPlaylist::NewInstance(GroovePlaylist *playlist) {
-    HandleScope scope;
-
-    Local<Object> instance = constructor->NewInstance();
-
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(instance);
-    gn_playlist->playlist = playlist;
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNPlaylist::GetId(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
-    char buf[64];
-    snprintf(buf, sizeof(buf), "%p", gn_playlist->playlist);
-    return scope.Close(String::New(buf));
-}
-
-Handle<Value> GNPlaylist::GetGain(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
-    return scope.Close(Number::New(gn_playlist->playlist->gain));
-}
-
-Handle<Value> GNPlaylist::Play(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    groove_playlist_play(gn_playlist->playlist);
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::Playlist(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-
-    Local<Array> playlist = Array::New();
-
-    GroovePlaylistItem *item = gn_playlist->playlist->head;
-    int i = 0;
-    while (item) {
-        playlist->Set(Number::New(i), GNPlaylistItem::NewInstance(item));
-        item = item->next;
-        i += 1;
-    }
-
-    return scope.Close(playlist);
-}
-
-Handle<Value> GNPlaylist::Pause(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    groove_playlist_pause(gn_playlist->playlist);
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::Seek(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    GNPlaylistItem *gn_playlist_item =
-        node::ObjectWrap::Unwrap<GNPlaylistItem>(args[0]->ToObject());
-
-    double pos = args[1]->NumberValue();
-    groove_playlist_seek(gn_playlist->playlist, gn_playlist_item->playlist_item, pos);
-
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::Insert(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(args[0]->ToObject());
-    double gain = 1.0;
-    double peak = 1.0;
-    if (!args[1]->IsNull() && !args[1]->IsUndefined()) {
-        gain = args[1]->NumberValue();
-    }
-    if (!args[2]->IsNull() && !args[2]->IsUndefined()) {
-        peak = args[2]->NumberValue();
-    }
-    GroovePlaylistItem *item = NULL;
-    if (!args[3]->IsNull() && !args[3]->IsUndefined()) {
-        GNPlaylistItem *gn_pl_item =
-            node::ObjectWrap::Unwrap<GNPlaylistItem>(args[3]->ToObject());
-        item = gn_pl_item->playlist_item;
-    }
-    GroovePlaylistItem *result = groove_playlist_insert(gn_playlist->playlist,
-            gn_file->file, gain, peak, item);
-
-    return scope.Close(GNPlaylistItem::NewInstance(result));
-}
-
-Handle<Value> GNPlaylist::Remove(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(args[0]->ToObject());
-    groove_playlist_remove(gn_playlist->playlist, gn_pl_item->playlist_item);
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::DecodePosition(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    GroovePlaylistItem *item;
-    double pos = -1.0;
-    groove_playlist_position(gn_playlist->playlist, &item, &pos);
-    Local<Object> obj = Object::New();
-    obj->Set(String::NewSymbol("pos"), Number::New(pos));
-    if (item) {
-        obj->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(item));
-    } else {
-        obj->Set(String::NewSymbol("item"), Null());
-    }
-    return scope.Close(obj);
-}
-
-Handle<Value> GNPlaylist::Playing(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    int playing = groove_playlist_playing(gn_playlist->playlist);
-    return scope.Close(Boolean::New(playing));
-}
-
-Handle<Value> GNPlaylist::Clear(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    groove_playlist_clear(gn_playlist->playlist);
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::Count(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    int count = groove_playlist_count(gn_playlist->playlist);
-    return scope.Close(Number::New(count));
-}
-
-Handle<Value> GNPlaylist::SetItemGain(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(args[0]->ToObject());
-    double gain = args[1]->NumberValue();
-    groove_playlist_set_item_gain(gn_playlist->playlist, gn_pl_item->playlist_item, gain);
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::SetItemPeak(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(args[0]->ToObject());
-    double peak = args[1]->NumberValue();
-    groove_playlist_set_item_peak(gn_playlist->playlist, gn_pl_item->playlist_item, peak);
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::SetGain(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    groove_playlist_set_gain(gn_playlist->playlist, args[0]->NumberValue());
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::SetFillMode(const Arguments& args) {
-    HandleScope scope;
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args.This());
-    groove_playlist_set_fill_mode(gn_playlist->playlist, args[0]->NumberValue());
-    return scope.Close(Undefined());
-}
-
-Handle<Value> GNPlaylist::Create(const Arguments& args) {
-    HandleScope scope;
-
-    GroovePlaylist *playlist = groove_playlist_create();
-    return scope.Close(GNPlaylist::NewInstance(playlist));
-}
diff --git a/src/gn_playlist.h b/src/gn_playlist.h
deleted file mode 100644
index 2dddbd6..0000000
--- a/src/gn_playlist.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#ifndef GN_PLAYLIST_H
-#define GN_PLAYLIST_H
-
-#include <node.h>
-
-#include <groove/groove.h>
-
-class GNPlaylist : public node::ObjectWrap {
-    public:
-        static void Init();
-        static v8::Handle<v8::Value> NewInstance(GroovePlaylist *playlist);
-
-        static v8::Handle<v8::Value> Create(const v8::Arguments& args);
-
-        GroovePlaylist *playlist;
-
-
-    private:
-        GNPlaylist();
-        ~GNPlaylist();
-
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
-
-        static v8::Handle<v8::Value> GetId(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-        static v8::Handle<v8::Value> GetGain(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-
-        static v8::Handle<v8::Value> Playlist(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Play(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Pause(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Seek(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Insert(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Remove(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Position(const v8::Arguments& args);
-        static v8::Handle<v8::Value> DecodePosition(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Playing(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Clear(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Count(const v8::Arguments& args);
-        static v8::Handle<v8::Value> SetItemGain(const v8::Arguments& args);
-        static v8::Handle<v8::Value> SetItemPeak(const v8::Arguments& args);
-        static v8::Handle<v8::Value> SetGain(const v8::Arguments& args);
-        static v8::Handle<v8::Value> SetFillMode(const v8::Arguments& args);
-};
-
-#endif
diff --git a/src/gn_playlist_item.cc b/src/gn_playlist_item.cc
deleted file mode 100644
index db2cb24..0000000
--- a/src/gn_playlist_item.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-#include <node.h>
-#include "gn_playlist_item.h"
-#include "gn_file.h"
-
-using namespace v8;
-
-GNPlaylistItem::GNPlaylistItem() { };
-GNPlaylistItem::~GNPlaylistItem() { };
-
-Persistent<Function> GNPlaylistItem::constructor;
-
-template <typename target_t, typename func_t>
-static void AddGetter(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
-}
-
-void GNPlaylistItem::Init() {
-    // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GroovePlaylistItem"));
-    tpl->InstanceTemplate()->SetInternalFieldCount(1);
-    // Fields
-    AddGetter(tpl, "file", GetFile);
-    AddGetter(tpl, "id", GetId);
-    AddGetter(tpl, "gain", GetGain);
-
-    constructor = Persistent<Function>::New(tpl->GetFunction());
-}
-
-Handle<Value> GNPlaylistItem::New(const Arguments& args) {
-    HandleScope scope;
-
-    GNPlaylistItem *obj = new GNPlaylistItem();
-    obj->Wrap(args.This());
-    
-    return scope.Close(args.This());
-}
-
-Handle<Value> GNPlaylistItem::NewInstance(GroovePlaylistItem *playlist_item) {
-    HandleScope scope;
-
-    Local<Object> instance = constructor->NewInstance();
-
-    GNPlaylistItem *gn_playlist_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(instance);
-    gn_playlist_item->playlist_item = playlist_item;
-
-    return scope.Close(instance);
-}
-
-Handle<Value> GNPlaylistItem::GetFile(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
-    return scope.Close(GNFile::NewInstance(gn_pl_item->playlist_item->file));
-}
-
-Handle<Value> GNPlaylistItem::GetId(Local<String> property, const AccessorInfo &info) {
-    HandleScope scope;
-    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
-    char buf[64];
-    snprintf(buf, sizeof(buf), "%p", gn_pl_item->playlist_item);
-    return scope.Close(String::New(buf));
-}
-
-Handle<Value> GNPlaylistItem::GetGain(Local<String> property,
-        const AccessorInfo &info)
-{
-    HandleScope scope;
-    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
-    double gain = gn_pl_item->playlist_item->gain;
-    return scope.Close(Number::New(gain));
-}
diff --git a/src/gn_playlist_item.h b/src/gn_playlist_item.h
deleted file mode 100644
index 15645f7..0000000
--- a/src/gn_playlist_item.h
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef GN_PLAYLIST_ITEM_H
-#define GN_PLAYLIST_ITEM_H
-
-#include <node.h>
-
-#include <groove/groove.h>
-
-class GNPlaylistItem : public node::ObjectWrap {
-    public:
-        static void Init();
-        static v8::Handle<v8::Value> NewInstance(GroovePlaylistItem *playlist_item);
-
-        GroovePlaylistItem *playlist_item;
-    private:
-        GNPlaylistItem();
-        ~GNPlaylistItem();
-
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
-
-        static v8::Handle<v8::Value> GetFile(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-        static v8::Handle<v8::Value> GetId(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-        static v8::Handle<v8::Value> GetGain(v8::Local<v8::String> property,
-                const v8::AccessorInfo &info);
-
-};
-
-#endif
-
-
diff --git a/src/groove.cc b/src/groove.cc
index f4979a8..71f7ee1 100644
--- a/src/groove.cc
+++ b/src/groove.cc
@@ -1,55 +1,62 @@
 #include <node.h>
+#include <nan.h>
 #include <cstdlib>
-#include "gn_file.h"
-#include "gn_player.h"
-#include "gn_playlist.h"
-#include "gn_playlist_item.h"
-#include "gn_loudness_detector.h"
-#include "gn_fingerprinter.h"
-#include "gn_encoder.h"
+#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;
-using namespace node;
 
-Handle<Value> SetLogging(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(SetLogging) {
+    Nan::HandleScope scope;
 
-    if (args.Length() < 1 || !args[0]->IsNumber()) {
-        ThrowException(Exception::TypeError(String::New("Expected 1 number argument")));
-        return scope.Close(Undefined());
+    if (info.Length() < 1 || !info[0]->IsNumber()) {
+        Nan::ThrowTypeError("Expected 1 number argument");
+        return;
     }
-    groove_set_logging(args[0]->NumberValue());
-    return scope.Close(Undefined());
+    groove_set_logging(info[0]->NumberValue());
 }
 
-Handle<Value> GetDevices(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(GetDevices) {
+    Nan::HandleScope scope;
 
-    Local<Array> deviceList = Array::New();
+    Local<Array> deviceList = Nan::New<Array>();
     int device_count = groove_device_count();
     for (int i = 0; i < device_count; i += 1) {
         const char *name = groove_device_name(i);
-        deviceList->Set(Number::New(i), String::New(name));
+        deviceList->Set(Nan::New<Number>(i), Nan::New<String>(name).ToLocalChecked());
     }
-    return scope.Close(deviceList);
+
+    info.GetReturnValue().Set(deviceList);
 }
 
-Handle<Value> GetVersion(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(GetVersion) {
+    Nan::HandleScope scope;
+
+    Local<Object> version = Nan::New<Object>();
+    Nan::Set(version, Nan::New<String>("major").ToLocalChecked(), Nan::New<Number>(groove_version_major()));
+    Nan::Set(version, Nan::New<String>("minor").ToLocalChecked(), Nan::New<Number>(groove_version_minor()));
+    Nan::Set(version, Nan::New<String>("patch").ToLocalChecked(), Nan::New<Number>(groove_version_patch()));
 
-    Local<Object> version = Object::New();
-    version->Set(String::NewSymbol("major"), Number::New(groove_version_major()));
-    version->Set(String::NewSymbol("minor"), Number::New(groove_version_minor()));
-    version->Set(String::NewSymbol("patch"), Number::New(groove_version_patch()));
-    return scope.Close(version);
+    info.GetReturnValue().Set(version);
 }
 
 template <typename target_t>
 static void SetProperty(target_t obj, const char* name, double n) {
-    obj->Set(String::NewSymbol(name), Number::New(n));
+    Nan::Set(obj, Nan::New<String>(name).ToLocalChecked(), Nan::New<Number>(n));
+}
+
+template <typename target_t, typename FNPTR>
+static void SetMethod(target_t obj, const char* name, FNPTR fn) {
+    Nan::Set(obj, Nan::New<String>(name).ToLocalChecked(),
+            Nan::GetFunction(Nan::New<FunctionTemplate>(fn)).ToLocalChecked());
 }
 
-void Initialize(Handle<Object> exports) {
+NAN_MODULE_INIT(Initialize) {
     groove_init();
     atexit(groove_finish);
 
@@ -61,33 +68,34 @@ void Initialize(Handle<Object> exports) {
     GNEncoder::Init();
     GNFingerprinter::Init();
 
-    SetProperty(exports, "LOG_QUIET", GROOVE_LOG_QUIET);
-    SetProperty(exports, "LOG_ERROR", GROOVE_LOG_ERROR);
-    SetProperty(exports, "LOG_WARNING", GROOVE_LOG_WARNING);
-    SetProperty(exports, "LOG_INFO", GROOVE_LOG_INFO);
-
-    SetProperty(exports, "TAG_MATCH_CASE", GROOVE_TAG_MATCH_CASE);
-    SetProperty(exports, "TAG_DONT_OVERWRITE", GROOVE_TAG_DONT_OVERWRITE);
-    SetProperty(exports, "TAG_APPEND", GROOVE_TAG_APPEND);
-
-    SetProperty(exports, "EVERY_SINK_FULL", GROOVE_EVERY_SINK_FULL);
-    SetProperty(exports, "ANY_SINK_FULL", GROOVE_ANY_SINK_FULL);
-
-    SetProperty(exports, "_EVENT_NOWPLAYING", GROOVE_EVENT_NOWPLAYING);
-    SetProperty(exports, "_EVENT_BUFFERUNDERRUN", GROOVE_EVENT_BUFFERUNDERRUN);
-
-    SetMethod(exports, "setLogging", SetLogging);
-    SetMethod(exports, "getDevices", GetDevices);
-    SetMethod(exports, "getVersion", GetVersion);
-    SetMethod(exports, "open", GNFile::Open);
-    SetMethod(exports, "createPlayer", GNPlayer::Create);
-    SetMethod(exports, "createPlaylist", GNPlaylist::Create);
-    SetMethod(exports, "createLoudnessDetector", GNLoudnessDetector::Create);
-    SetMethod(exports, "createEncoder", GNEncoder::Create);
-    SetMethod(exports, "createFingerprinter", GNFingerprinter::Create);
-
-    SetMethod(exports, "encodeFingerprint", GNFingerprinter::Encode);
-    SetMethod(exports, "decodeFingerprint", GNFingerprinter::Decode);
+    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)
diff --git a/src/gn_loudness_detector.cc b/src/loudness_detector.cc
similarity index 50%
rename from src/gn_loudness_detector.cc
rename to src/loudness_detector.cc
index 4d5a218..31677b0 100644
--- a/src/gn_loudness_detector.cc
+++ b/src/loudness_detector.cc
@@ -1,7 +1,6 @@
-#include <node.h>
-#include "gn_loudness_detector.h"
-#include "gn_playlist_item.h"
-#include "gn_playlist.h"
+#include "loudness_detector.h"
+#include "playlist_item.h"
+#include "playlist.h"
 
 using namespace v8;
 
@@ -10,65 +9,56 @@ GNLoudnessDetector::~GNLoudnessDetector() {
     groove_loudness_detector_destroy(detector);
 };
 
-Persistent<Function> GNLoudnessDetector::constructor;
-
-template <typename target_t, typename func_t>
-static void AddGetter(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->SetAccessor(String::NewSymbol(name), fn);
-}
-
-template <typename target_t, typename func_t>
-static void AddMethod(target_t tpl, const char* name, func_t fn) {
-    tpl->PrototypeTemplate()->Set(String::NewSymbol(name),
-            FunctionTemplate::New(fn)->GetFunction());
-}
+static Nan::Persistent<v8::Function> constructor;
 
 void GNLoudnessDetector::Init() {
     // Prepare constructor template
-    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
-    tpl->SetClassName(String::NewSymbol("GrooveLoudnessDetector"));
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GrooveLoudnessDetector").ToLocalChecked());
     tpl->InstanceTemplate()->SetInternalFieldCount(2);
+
     // Methods
-    AddMethod(tpl, "attach", Attach);
-    AddMethod(tpl, "detach", Detach);
-    AddMethod(tpl, "getInfo", GetInfo);
-    AddMethod(tpl, "position", Position);
+    Nan::SetPrototypeMethod(tpl, "attach", Attach);
+    Nan::SetPrototypeMethod(tpl, "detach", Detach);
+    Nan::SetPrototypeMethod(tpl, "getInfo", GetInfo);
+    Nan::SetPrototypeMethod(tpl, "position", Position);
 
-    constructor = Persistent<Function>::New(tpl->GetFunction());
+    constructor.Reset(tpl->GetFunction());
 }
 
-Handle<Value> GNLoudnessDetector::New(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(GNLoudnessDetector::New) {
+    Nan::HandleScope scope;
 
     GNLoudnessDetector *obj = new GNLoudnessDetector();
-    obj->Wrap(args.This());
+    obj->Wrap(info.This());
     
-    return scope.Close(args.This());
+    info.GetReturnValue().Set(info.This());
 }
 
 Handle<Value> GNLoudnessDetector::NewInstance(GrooveLoudnessDetector *detector) {
-    HandleScope scope;
+    Nan::EscapableHandleScope scope;
 
-    Local<Object> instance = constructor->NewInstance();
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
 
     GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(instance);
     gn_detector->detector = detector;
 
-    return scope.Close(instance);
+    return scope.Escape(instance);
 }
 
-Handle<Value> GNLoudnessDetector::Create(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(GNLoudnessDetector::Create) {
+    Nan::HandleScope scope;
 
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
+    if (info.Length() < 1 || !info[0]->IsFunction()) {
+        Nan::ThrowTypeError("Expected function arg[0]");
+        return;
     }
 
     GrooveLoudnessDetector *detector = groove_loudness_detector_create();
     if (!detector) {
-        ThrowException(Exception::Error(String::New("unable to create loudness detector")));
-        return scope.Close(Undefined());
+        Nan::ThrowTypeError("unable to create loudness detector");
+        return;
     }
 
     // set properties on the instance with default values from
@@ -77,85 +67,93 @@ Handle<Value> GNLoudnessDetector::Create(const Arguments& args) {
     GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(instance);
     EventContext *context = new EventContext;
     gn_detector->event_context = context;
-    context->event_cb = Persistent<Function>::New(Local<Function>::Cast(args[0]));
+    context->event_cb = new Nan::Callback(info[0].As<Function>());
     context->detector = detector;
 
+    Nan::Set(instance, Nan::New<String>("infoQueueSize").ToLocalChecked(),
+            Nan::New<Number>(detector->info_queue_size));
+    Nan::Set(instance, Nan::New<String>("disableAlbum").ToLocalChecked(),
+            Nan::New<Boolean>(detector->disable_album));
+    Nan::Set(instance, Nan::New<String>("sinkBufferSize").ToLocalChecked(),
+            Nan::New<Boolean>(detector->sink_buffer_size));
 
-    instance->Set(String::NewSymbol("infoQueueSize"), Number::New(detector->info_queue_size));
-    instance->Set(String::NewSymbol("sinkBufferSize"), Number::New(detector->sink_buffer_size));
-    instance->Set(String::NewSymbol("disableAlbum"), Boolean::New(detector->disable_album));
-
-    return scope.Close(instance);
+    info.GetReturnValue().Set(instance);
 }
 
-Handle<Value> GNLoudnessDetector::Position(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(GNLoudnessDetector::Position) {
+    Nan::HandleScope scope;
 
-    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(args.This());
+    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
     GrooveLoudnessDetector *detector = gn_detector->detector;
 
     GroovePlaylistItem *item;
     double pos;
     groove_loudness_detector_position(detector, &item, &pos);
 
-    Local<Object> obj = Object::New();
-    obj->Set(String::NewSymbol("pos"), Number::New(pos));
+    Local<Object> obj = Nan::New<Object>();
+    Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
     if (item) {
-        obj->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(item));
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
     } else {
-        obj->Set(String::NewSymbol("item"), Null());
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
     }
-    return scope.Close(obj);
+    info.GetReturnValue().Set(obj);
 }
 
-Handle<Value> GNLoudnessDetector::GetInfo(const Arguments& args) {
-    HandleScope scope;
-    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(args.This());
+NAN_METHOD(GNLoudnessDetector::GetInfo) {
+    Nan::HandleScope scope;
+
+    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
     GrooveLoudnessDetector *detector = gn_detector->detector;
 
-    GrooveLoudnessDetectorInfo info;
-    if (groove_loudness_detector_info_get(detector, &info, 0) == 1) {
-        Local<Object> object = Object::New();
+    GrooveLoudnessDetectorInfo loudness_info;
+    if (groove_loudness_detector_info_get(detector, &loudness_info, 0) == 1) {
+        Local<Object> object = Nan::New<Object>();
 
-        object->Set(String::NewSymbol("loudness"), Number::New(info.loudness));
-        object->Set(String::NewSymbol("peak"), Number::New(info.peak));
-        object->Set(String::NewSymbol("duration"), Number::New(info.duration));
+        Nan::Set(object, Nan::New<String>("loudness").ToLocalChecked(), Nan::New<Number>(loudness_info.loudness));
+        Nan::Set(object, Nan::New<String>("peak").ToLocalChecked(), Nan::New<Number>(loudness_info.peak));
+        Nan::Set(object, Nan::New<String>("duration").ToLocalChecked(), Nan::New<Number>(loudness_info.duration));
 
-        if (info.item) {
-            object->Set(String::NewSymbol("item"), GNPlaylistItem::NewInstance(info.item));
+        if (loudness_info.item) {
+            Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(loudness_info.item));
         } else {
-            object->Set(String::NewSymbol("item"), Null());
+            Nan::Set(object, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
         }
 
-        return scope.Close(object);
+        info.GetReturnValue().Set(object);
     } else {
-        return scope.Close(Null());
+        info.GetReturnValue().Set(Nan::Null());
     }
 }
 
 struct AttachReq {
     uv_work_t req;
-    Persistent<Function> callback;
+    Nan::Callback *callback;
     GrooveLoudnessDetector *detector;
     GroovePlaylist *playlist;
     int errcode;
-    Persistent<Object> instance;
+    Nan::Persistent<Object> instance;
     GNLoudnessDetector::EventContext *event_context;
 };
 
-static void EventAsyncCb(uv_async_t *handle, int status) {
-    HandleScope scope;
+static void EventAsyncCb(uv_async_t *handle
+#if UV_VERSION_MAJOR == 0
+        , int status
+#endif
+        )
+{
+    Nan::HandleScope scope;
 
     GNLoudnessDetector::EventContext *context = reinterpret_cast<GNLoudnessDetector::EventContext *>(handle->data);
 
     // call callback signaling that there is info ready
 
     const unsigned argc = 1;
-    Handle<Value> argv[argc];
-    argv[0] = Null();
+    Local<Value> argv[argc];
+    argv[0] = Nan::Null();
 
     TryCatch try_catch;
-    context->event_cb->Call(Context::GetCurrent()->Global(), argc, argv);
+    context->event_cb->Call(argc, argv);
 
     if (try_catch.HasCaught()) {
         node::FatalException(try_catch);
@@ -193,20 +191,23 @@ static void AttachAsync(uv_work_t *req) {
 }
 
 static void AttachAfter(uv_work_t *req) {
-    HandleScope scope;
+    Nan::HandleScope scope;
+
     AttachReq *r = reinterpret_cast<AttachReq *>(req->data);
 
     const unsigned argc = 1;
-    Handle<Value> argv[argc];
+    Local<Value> argv[argc];
     if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("loudness detector attach failed"));
+        argv[0] = Exception::Error(Nan::New<String>("loudness detector attach failed").ToLocalChecked());
     } else {
-        argv[0] = Null();
+        argv[0] = Nan::Null();
     }
 
     TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
+    r->callback->Call(argc, argv);
 
+    r->instance.Reset();
+    delete r->callback;
     delete r;
 
     if (try_catch.HasCaught()) {
@@ -214,55 +215,58 @@ static void AttachAfter(uv_work_t *req) {
     }
 }
 
-Handle<Value> GNLoudnessDetector::Attach(const Arguments& args) {
-    HandleScope scope;
+NAN_METHOD(GNLoudnessDetector::Attach) {
+    Nan::HandleScope scope;
 
-    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(args.This());
+    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
 
-    if (args.Length() < 1 || !args[0]->IsObject()) {
-        ThrowException(Exception::TypeError(String::New("Expected object arg[0]")));
-        return scope.Close(Undefined());
+    if (info.Length() < 1 || !info[0]->IsObject()) {
+        Nan::ThrowTypeError("Expected object arg[0]");
+        return;
     }
-    if (args.Length() < 2 || !args[1]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[1]")));
-        return scope.Close(Undefined());
+    if (info.Length() < 2 || !info[1]->IsFunction()) {
+        Nan::ThrowTypeError("Expected function arg[1]");
+        return;
     }
 
-    Local<Object> instance = args.This();
+    Local<Object> instance = info.This();
 
-    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(args[0]->ToObject());
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
 
     AttachReq *request = new AttachReq;
 
     request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
-    request->instance = Persistent<Object>::New(args.This());
+    request->callback = new Nan::Callback(info[1].As<Function>());
+
+    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(String::NewSymbol("infoQueueSize"))->NumberValue();
-    detector->sink_buffer_size = (int)instance->Get(String::NewSymbol("sinkBufferSize"))->NumberValue();
-    detector->disable_album = (int)instance->Get(String::NewSymbol("disableAlbum"))->BooleanValue();
+    detector->info_queue_size = (int)instance->Get(Nan::New<String>("infoQueueSize").ToLocalChecked())->NumberValue();
+    detector->sink_buffer_size = (int)instance->Get(Nan::New<String>("sinkBufferSize").ToLocalChecked())->BooleanValue();
+    detector->disable_album = (int)instance->Get(Nan::New<String>("disableAlbum").ToLocalChecked())->BooleanValue();
 
     uv_queue_work(uv_default_loop(), &request->req, AttachAsync,
             (uv_after_work_cb)AttachAfter);
 
-    return scope.Close(Undefined());
+    return;
 }
 
 struct DetachReq {
     uv_work_t req;
     GrooveLoudnessDetector *detector;
-    Persistent<Function> callback;
+    Nan::Callback *callback;
     int errcode;
     GNLoudnessDetector::EventContext *event_context;
 };
 
 static void DetachAsyncFree(uv_handle_t *handle) {
     GNLoudnessDetector::EventContext *context = reinterpret_cast<GNLoudnessDetector::EventContext *>(handle->data);
+    delete context->event_cb;
     delete context;
 }
 
@@ -277,19 +281,21 @@ static void DetachAsync(uv_work_t *req) {
 }
 
 static void DetachAfter(uv_work_t *req) {
-    HandleScope scope;
+    Nan::HandleScope scope;
+
     DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
 
     const unsigned argc = 1;
-    Handle<Value> argv[argc];
+    Local<Value> argv[argc];
     if (r->errcode < 0) {
-        argv[0] = Exception::Error(String::New("loudness detector detach failed"));
+        argv[0] = Exception::Error(Nan::New<String>("loudness detector detach failed").ToLocalChecked());
     } else {
-        argv[0] = Null();
+        argv[0] = Nan::Null();
     }
     TryCatch try_catch;
-    r->callback->Call(Context::GetCurrent()->Global(), argc, argv);
+    r->callback->Call(argc, argv);
 
+    delete r->callback;
     delete r;
 
     if (try_catch.HasCaught()) {
@@ -297,24 +303,24 @@ static void DetachAfter(uv_work_t *req) {
     }
 }
 
-Handle<Value> GNLoudnessDetector::Detach(const Arguments& args) {
-    HandleScope scope;
-    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(args.This());
+NAN_METHOD(GNLoudnessDetector::Detach) {
+    Nan::HandleScope scope;
+    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());
 
-    if (args.Length() < 1 || !args[0]->IsFunction()) {
-        ThrowException(Exception::TypeError(String::New("Expected function arg[0]")));
-        return scope.Close(Undefined());
+    if (info.Length() < 1 || !info[0]->IsFunction()) {
+        Nan::ThrowTypeError("Expected function arg[0]");
+        return;
     }
 
     DetachReq *request = new DetachReq;
 
     request->req.data = request;
-    request->callback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
+    request->callback = new Nan::Callback(info[0].As<Function>());
     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 scope.Close(Undefined());
+    return;
 }
diff --git a/src/gn_loudness_detector.h b/src/loudness_detector.h
similarity index 55%
rename from src/gn_loudness_detector.h
rename to src/loudness_detector.h
index 4f89d50..87af0c3 100644
--- a/src/gn_loudness_detector.h
+++ b/src/loudness_detector.h
@@ -2,7 +2,7 @@
 #define GN_LOUDNESS_DETECTOR_H
 
 #include <node.h>
-
+#include <nan.h>
 #include <grooveloudness/loudness.h>
 
 class GNLoudnessDetector : public node::ObjectWrap {
@@ -10,8 +10,7 @@ class GNLoudnessDetector : public node::ObjectWrap {
         static void Init();
         static v8::Handle<v8::Value> NewInstance(GrooveLoudnessDetector *detector);
 
-        static v8::Handle<v8::Value> Create(const v8::Arguments& args);
-
+        static NAN_METHOD(Create);
 
         struct EventContext {
             uv_thread_t event_thread;
@@ -19,7 +18,7 @@ class GNLoudnessDetector : public node::ObjectWrap {
             uv_cond_t cond;
             uv_mutex_t mutex;
             GrooveLoudnessDetector *detector;
-            v8::Persistent<v8::Function> event_cb;
+            Nan::Callback *event_cb;
         };
 
         EventContext *event_context;
@@ -29,13 +28,12 @@ class GNLoudnessDetector : public node::ObjectWrap {
         GNLoudnessDetector();
         ~GNLoudnessDetector();
 
-        static v8::Persistent<v8::Function> constructor;
-        static v8::Handle<v8::Value> New(const v8::Arguments& args);
+        static NAN_METHOD(New);
 
-        static v8::Handle<v8::Value> Attach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Detach(const v8::Arguments& args);
-        static v8::Handle<v8::Value> GetInfo(const v8::Arguments& args);
-        static v8::Handle<v8::Value> Position(const v8::Arguments& args);
+        static NAN_METHOD(Attach);
+        static NAN_METHOD(Detach);
+        static NAN_METHOD(GetInfo);
+        static NAN_METHOD(Position);
 };
 
 #endif
diff --git a/src/player.cc b/src/player.cc
new file mode 100644
index 0000000..e1aff3a
--- /dev/null
+++ b/src/player.cc
@@ -0,0 +1,364 @@
+#include <node.h>
+#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<v8::Function> constructor;
+
+void GNPlayer::Init() {
+    // Prepare constructor template
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GroovePlayer").ToLocalChecked());
+    tpl->InstanceTemplate()->SetInternalFieldCount(2);
+    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
+
+    // Fields
+    Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
+    Nan::SetAccessor(proto, Nan::New<String>("playlist").ToLocalChecked(), GetPlaylist);
+
+    // Methods
+    Nan::SetPrototypeMethod(tpl, "attach", Attach);
+    Nan::SetPrototypeMethod(tpl, "detach", Detach);
+    Nan::SetPrototypeMethod(tpl, "position", Position);
+
+    constructor.Reset(tpl->GetFunction());
+}
+
+NAN_METHOD(GNPlayer::New) {
+    Nan::HandleScope scope;
+
+    GNPlayer *obj = new GNPlayer();
+    obj->Wrap(info.This());
+    
+    info.GetReturnValue().Set(info.This());
+}
+
+Handle<Value> GNPlayer::NewInstance(GroovePlayer *player) {
+    Nan::EscapableHandleScope scope;
+
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
+
+    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);
+    gn_player->player = player;
+
+    return scope.Escape(instance);
+}
+
+NAN_GETTER(GNPlayer::GetId) {
+    Nan::HandleScope scope;
+    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
+    char buf[64];
+    snprintf(buf, sizeof(buf), "%p", gn_player->player);
+    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
+}
+
+NAN_GETTER(GNPlayer::GetPlaylist) {
+    Nan::HandleScope scope;
+    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
+    GroovePlaylist *playlist = gn_player->player->playlist;
+    if (playlist) {
+        Local<Value> tmp = GNPlaylist::NewInstance(playlist);
+        info.GetReturnValue().Set(tmp);
+    } else {
+        info.GetReturnValue().Set(Nan::Null());
+    }
+}
+
+NAN_METHOD(GNPlayer::Position) {
+    Nan::HandleScope scope;
+    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
+    GroovePlaylistItem *item;
+    double pos;
+    groove_player_position(gn_player->player, &item, &pos);
+    Local<Object> obj = Nan::New<Object>();
+    Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
+    if (item) {
+        Local<Value> tmp = GNPlaylistItem::NewInstance(item);
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), tmp);
+    } else {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+    }
+    info.GetReturnValue().Set(obj);
+}
+
+struct AttachReq {
+    uv_work_t req;
+    Nan::Callback *callback;
+    GroovePlayer *player;
+    GroovePlaylist *playlist;
+    int errcode;
+    Nan::Persistent<Object> 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<GNPlayer::EventContext *>(handle->data);
+
+    // flush events
+    GroovePlayerEvent event;
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    while (groove_player_event_get(context->player, &event, 0) > 0) {
+        argv[0] = Nan::New<Number>(event.type);
+
+        TryCatch try_catch;
+        context->event_cb->Call(argc, argv);
+
+        if (try_catch.HasCaught()) {
+            node::FatalException(try_catch);
+        }
+    }
+
+    uv_mutex_lock(&context->mutex);
+    uv_cond_signal(&context->cond);
+    uv_mutex_unlock(&context->mutex);
+}
+
+static void EventThreadEntry(void *arg) {
+    GNPlayer::EventContext *context = reinterpret_cast<GNPlayer::EventContext *>(arg);
+    while (groove_player_event_peek(context->player, 1) > 0) {
+        uv_mutex_lock(&context->mutex);
+        uv_async_send(&context->event_async);
+        uv_cond_wait(&context->cond, &context->mutex);
+        uv_mutex_unlock(&context->mutex);
+    }
+}
+
+static void AttachAsync(uv_work_t *req) {
+    AttachReq *r = reinterpret_cast<AttachReq *>(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<AttachReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->errcode < 0) {
+        argv[0] = Exception::Error(Nan::New<String>("player attach failed").ToLocalChecked());
+    } else {
+        argv[0] = Nan::Null();
+
+        Local<Object> actualAudioFormat = Nan::New<Object>();
+        actualAudioFormat->Set(Nan::New<String>("sampleRate").ToLocalChecked(),
+                Nan::New<Number>(r->player->actual_audio_format.sample_rate));
+        actualAudioFormat->Set(Nan::New<String>("channelLayout").ToLocalChecked(),
+                Nan::New<Number>(r->player->actual_audio_format.channel_layout));
+        actualAudioFormat->Set(Nan::New<String>("sampleFormat").ToLocalChecked(),
+                Nan::New<Number>(r->player->actual_audio_format.sample_fmt));
+
+        Local<Object> o = Nan::New(r->instance);
+        Nan::Set(o, Nan::New<String>("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<Object> instance = NewInstance(player)->ToObject();
+    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);
+    EventContext *context = new EventContext;
+    gn_player->event_context = context;
+    context->event_cb = new Nan::Callback(info[0].As<Function>());
+    context->player = player;
+
+    // set properties on the instance with default values from
+    // GroovePlayer struct
+    Local<Object> targetAudioFormat = Nan::New<Object>();
+    Nan::Set(targetAudioFormat, Nan::New<String>("sampleRate").ToLocalChecked(),
+            Nan::New<Number>(player->target_audio_format.sample_rate));
+    Nan::Set(targetAudioFormat, Nan::New<String>("channelLayout").ToLocalChecked(),
+            Nan::New<Number>(player->target_audio_format.channel_layout));
+    Nan::Set(targetAudioFormat, Nan::New<String>("sampleFormat").ToLocalChecked(),
+            Nan::New<Number>(player->target_audio_format.sample_fmt));
+
+    instance->Set(Nan::New<String>("deviceIndex").ToLocalChecked(), Nan::Null());
+    instance->Set(Nan::New<String>("actualAudioFormat").ToLocalChecked(), Nan::Null());
+    instance->Set(Nan::New<String>("targetAudioFormat").ToLocalChecked(), targetAudioFormat);
+    instance->Set(Nan::New<String>("deviceBufferSize").ToLocalChecked(),
+            Nan::New<Number>(player->device_buffer_size));
+    instance->Set(Nan::New<String>("sinkBufferSize").ToLocalChecked(),
+            Nan::New<Number>(player->sink_buffer_size));
+
+    info.GetReturnValue().Set(instance);
+}
+
+NAN_METHOD(GNPlayer::Attach) {
+    Nan::HandleScope scope;
+
+    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());
+
+    if (info.Length() < 1 || !info[0]->IsObject()) {
+        Nan::ThrowTypeError("Expected object arg[0]");
+        return;
+    }
+    if (info.Length() < 2 || !info[1]->IsFunction()) {
+        Nan::ThrowTypeError("Expected function arg[1]");
+        return;
+    }
+
+    Local<Object> instance = info.This();
+    Local<Value> targetAudioFormatValue = instance->Get(Nan::New<String>("targetAudioFormat").ToLocalChecked());
+    if (!targetAudioFormatValue->IsObject()) {
+        Nan::ThrowTypeError("Expected targetAudioFormat to be an object");
+        return;
+    }
+
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());
+
+    AttachReq *request = new AttachReq;
+
+    request->req.data = request;
+    request->callback = new Nan::Callback(info[1].As<Function>());
+
+    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<Value> deviceIndex = instance->Get(Nan::New<String>("deviceIndex").ToLocalChecked());
+
+    Local<Value> useExactAudioFormat = instance->Get(Nan::New<String>("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<Object> targetAudioFormat = targetAudioFormatValue->ToObject();
+    Local<Value> sampleRate = targetAudioFormat->Get(Nan::New<String>("sampleRate").ToLocalChecked());
+    double sample_rate = sampleRate->NumberValue();
+    double channel_layout = targetAudioFormat->Get(Nan::New<String>("channelLayout").ToLocalChecked())->NumberValue();
+    double sample_fmt = targetAudioFormat->Get(Nan::New<String>("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<String>("deviceBufferSize").ToLocalChecked())->NumberValue();
+    player->device_buffer_size = (int)device_buffer_size;
+
+    double sink_buffer_size = instance->Get(Nan::New<String>("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<DetachReq *>(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<uv_handle_t*>(&r->event_context->event_async), DetachAsyncFree);
+}
+
+static void DetachAfter(uv_work_t *req) {
+    Nan::HandleScope scope;
+    DetachReq *r = reinterpret_cast<DetachReq *>(req->data);
+
+    const unsigned argc = 1;
+    Local<Value> argv[argc];
+    if (r->errcode < 0) {
+        argv[0] = Exception::Error(Nan::New<String>("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<GNPlayer>(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<Function>());
+    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;
+}
diff --git a/src/player.h b/src/player.h
new file mode 100644
index 0000000..f2f8909
--- /dev/null
+++ b/src/player.h
@@ -0,0 +1,43 @@
+#ifndef GN_PLAYER_H
+#define GN_PLAYER_H
+
+#include <node.h>
+#include <nan.h>
+#include <grooveplayer/player.h>
+
+class GNPlayer : public node::ObjectWrap {
+    public:
+        static void Init();
+        static v8::Handle<v8::Value> NewInstance(GroovePlayer *player);
+
+        static NAN_METHOD(Create);
+
+        struct EventContext {
+            uv_thread_t event_thread;
+            uv_async_t event_async;
+            uv_cond_t cond;
+            uv_mutex_t mutex;
+            GroovePlayer *player;
+            Nan::Callback *event_cb;
+        };
+
+
+        GroovePlayer *player;
+        EventContext *event_context;
+
+    private:
+        GNPlayer();
+        ~GNPlayer();
+
+        static NAN_METHOD(New);
+
+        static NAN_GETTER(GetId);
+        static NAN_GETTER(GetPlaylist);
+
+        static NAN_METHOD(Attach);
+        static NAN_METHOD(Detach);
+        static NAN_METHOD(Position);
+};
+
+#endif
+
diff --git a/src/playlist.cc b/src/playlist.cc
new file mode 100644
index 0000000..b727d8d
--- /dev/null
+++ b/src/playlist.cc
@@ -0,0 +1,232 @@
+#include <node.h>
+#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<v8::Function> constructor;
+
+void GNPlaylist::Init() {
+    // Prepare constructor template
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GroovePlaylist").ToLocalChecked());
+    tpl->InstanceTemplate()->SetInternalFieldCount(1);
+    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
+
+    // Fields
+    Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
+    Nan::SetAccessor(proto, Nan::New<String>("gain").ToLocalChecked(), GetGain);
+
+    // Methods
+    Nan::SetPrototypeMethod(tpl, "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<Value> GNPlaylist::NewInstance(GroovePlaylist *playlist) {
+    Nan::EscapableHandleScope scope;
+
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
+
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(instance);
+    gn_playlist->playlist = playlist;
+
+    return scope.Escape(instance);
+}
+
+NAN_GETTER(GNPlaylist::GetId) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    char buf[64];
+    snprintf(buf, sizeof(buf), "%p", gn_playlist->playlist);
+    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
+}
+
+NAN_GETTER(GNPlaylist::GetGain) {
+    Nan::HandleScope();
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    info.GetReturnValue().Set(Nan::New<Number>(gn_playlist->playlist->gain));
+}
+
+NAN_METHOD(GNPlaylist::Play) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    groove_playlist_play(gn_playlist->playlist);
+    return;
+}
+
+NAN_METHOD(GNPlaylist::Playlist) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+
+    Local<Array> playlist = Nan::New<Array>();
+
+    GroovePlaylistItem *item = gn_playlist->playlist->head;
+    int i = 0;
+    while (item) {
+        Nan::Set(playlist, Nan::New<Number>(i), GNPlaylistItem::NewInstance(item));
+        item = item->next;
+        i += 1;
+    }
+
+    info.GetReturnValue().Set(playlist);
+}
+
+NAN_METHOD(GNPlaylist::Pause) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    groove_playlist_pause(gn_playlist->playlist);
+    return;
+}
+
+NAN_METHOD(GNPlaylist::Seek) {
+    Nan::HandleScope scope;
+
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    GNPlaylistItem *gn_playlist_item =
+        node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());
+
+    double pos = info[1]->NumberValue();
+    groove_playlist_seek(gn_playlist->playlist, gn_playlist_item->playlist_item, pos);
+
+    return;
+}
+
+NAN_METHOD(GNPlaylist::Insert) {
+    Nan::HandleScope scope;
+
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info[0]->ToObject());
+    double gain = 1.0;
+    double peak = 1.0;
+    if (!info[1]->IsNull() && !info[1]->IsUndefined()) {
+        gain = info[1]->NumberValue();
+    }
+    if (!info[2]->IsNull() && !info[2]->IsUndefined()) {
+        peak = info[2]->NumberValue();
+    }
+    GroovePlaylistItem *item = NULL;
+    if (!info[3]->IsNull() && !info[3]->IsUndefined()) {
+        GNPlaylistItem *gn_pl_item =
+            node::ObjectWrap::Unwrap<GNPlaylistItem>(info[3]->ToObject());
+        item = gn_pl_item->playlist_item;
+    }
+    GroovePlaylistItem *result = groove_playlist_insert(gn_playlist->playlist,
+            gn_file->file, gain, peak, item);
+
+    Local<Value> tmp = GNPlaylistItem::NewInstance(result);
+    info.GetReturnValue().Set(tmp);
+}
+
+NAN_METHOD(GNPlaylist::Remove) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());
+    groove_playlist_remove(gn_playlist->playlist, gn_pl_item->playlist_item);
+    return;
+}
+
+NAN_METHOD(GNPlaylist::DecodePosition) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    GroovePlaylistItem *item;
+    double pos = -1.0;
+    groove_playlist_position(gn_playlist->playlist, &item, &pos);
+    Local<Object> obj = Nan::New<Object>();
+    Nan::Set(obj, Nan::New<String>("pos").ToLocalChecked(), Nan::New<Number>(pos));
+    if (item) {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), GNPlaylistItem::NewInstance(item));
+    } else {
+        Nan::Set(obj, Nan::New<String>("item").ToLocalChecked(), Nan::Null());
+    }
+    info.GetReturnValue().Set(obj);
+}
+
+NAN_METHOD(GNPlaylist::Playing) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    int playing = groove_playlist_playing(gn_playlist->playlist);
+    info.GetReturnValue().Set(Nan::New<Boolean>(playing));
+}
+
+NAN_METHOD(GNPlaylist::Clear) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    groove_playlist_clear(gn_playlist->playlist);
+}
+
+NAN_METHOD(GNPlaylist::Count) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    int count = groove_playlist_count(gn_playlist->playlist);
+    info.GetReturnValue().Set(Nan::New<Number>(count));
+}
+
+NAN_METHOD(GNPlaylist::SetItemGain) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());
+    double gain = info[1]->NumberValue();
+    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<GNPlaylist>(info.This());
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(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<GNPlaylist>(info.This());
+    groove_playlist_set_gain(gn_playlist->playlist, info[0]->NumberValue());
+}
+
+NAN_METHOD(GNPlaylist::SetFillMode) {
+    Nan::HandleScope scope;
+    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());
+    groove_playlist_set_fill_mode(gn_playlist->playlist, info[0]->NumberValue());
+}
+
+NAN_METHOD(GNPlaylist::Create) {
+    Nan::HandleScope scope;
+    GroovePlaylist *playlist = groove_playlist_create();
+    Local<Value> tmp = GNPlaylist::NewInstance(playlist);
+    info.GetReturnValue().Set(tmp);
+}
diff --git a/src/playlist.h b/src/playlist.h
new file mode 100644
index 0000000..8a287e9
--- /dev/null
+++ b/src/playlist.h
@@ -0,0 +1,44 @@
+#ifndef GN_PLAYLIST_H
+#define GN_PLAYLIST_H
+
+#include <node.h>
+#include <nan.h>
+#include <groove/groove.h>
+
+class GNPlaylist : public node::ObjectWrap {
+    public:
+        static void Init();
+        static v8::Handle<v8::Value> 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
diff --git a/src/playlist_item.cc b/src/playlist_item.cc
new file mode 100644
index 0000000..3a3cae6
--- /dev/null
+++ b/src/playlist_item.cc
@@ -0,0 +1,71 @@
+#include "playlist_item.h"
+#include "file.h"
+
+using namespace v8;
+
+GNPlaylistItem::GNPlaylistItem() { };
+GNPlaylistItem::~GNPlaylistItem() { };
+
+static Nan::Persistent<v8::Function> constructor;
+
+void GNPlaylistItem::Init() {
+    // Prepare constructor template
+    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+    tpl->SetClassName(Nan::New<String>("GroovePlaylistItem").ToLocalChecked());
+    tpl->InstanceTemplate()->SetInternalFieldCount(1);
+    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();
+
+    // Fields
+    Nan::SetAccessor(proto, Nan::New<String>("file").ToLocalChecked(), GetFile);
+    Nan::SetAccessor(proto, Nan::New<String>("id").ToLocalChecked(), GetId);
+    Nan::SetAccessor(proto, Nan::New<String>("gain").ToLocalChecked(), GetGain);
+    Nan::SetAccessor(proto, Nan::New<String>("peak").ToLocalChecked(), GetPeak);
+
+    constructor.Reset(tpl->GetFunction());
+}
+
+NAN_METHOD(GNPlaylistItem::New) {
+    Nan::HandleScope scope;
+
+    GNPlaylistItem *obj = new GNPlaylistItem();
+    obj->Wrap(info.This());
+    
+    info.GetReturnValue().Set(info.This());
+}
+
+Handle<Value> GNPlaylistItem::NewInstance(GroovePlaylistItem *playlist_item) {
+    Nan::EscapableHandleScope scope;
+
+    Local<Function> cons = Nan::New(constructor);
+    Local<Object> instance = cons->NewInstance();
+
+    GNPlaylistItem *gn_playlist_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(instance);
+    gn_playlist_item->playlist_item = playlist_item;
+
+    return scope.Escape(instance);
+}
+
+NAN_GETTER(GNPlaylistItem::GetFile) {
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
+    Local<Value> tmp = GNFile::NewInstance(gn_pl_item->playlist_item->file);
+    info.GetReturnValue().Set(tmp);
+}
+
+NAN_GETTER(GNPlaylistItem::GetId) {
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
+    char buf[64];
+    snprintf(buf, sizeof(buf), "%p", gn_pl_item->playlist_item);
+    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
+}
+
+NAN_GETTER(GNPlaylistItem::GetGain) {
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
+    double gain = gn_pl_item->playlist_item->gain;
+    info.GetReturnValue().Set(Nan::New<Number>(gain));
+}
+
+NAN_GETTER(GNPlaylistItem::GetPeak) {
+    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());
+    double peak = gn_pl_item->playlist_item->peak;
+    info.GetReturnValue().Set(Nan::New<Number>(peak));
+}
diff --git a/src/playlist_item.h b/src/playlist_item.h
new file mode 100644
index 0000000..4cba511
--- /dev/null
+++ b/src/playlist_item.h
@@ -0,0 +1,28 @@
+#ifndef GN_PLAYLIST_ITEM_H
+#define GN_PLAYLIST_ITEM_H
+
+#include <node.h>
+#include <nan.h>
+#include <groove/groove.h>
+
+class GNPlaylistItem : public node::ObjectWrap {
+    public:
+        static void Init();
+        static v8::Handle<v8::Value> NewInstance(GroovePlaylistItem *playlist_item);
+
+        GroovePlaylistItem *playlist_item;
+    private:
+        GNPlaylistItem();
+        ~GNPlaylistItem();
+
+        static NAN_METHOD(New);
+
+        static NAN_GETTER(GetFile);
+        static NAN_GETTER(GetId);
+        static NAN_GETTER(GetGain);
+        static NAN_GETTER(GetPeak);
+};
+
+#endif
+
+
diff --git a/test/test.js b/test/test.js
index 67846a8..e06e509 100644
--- a/test/test.js
+++ b/test/test.js
@@ -55,7 +55,7 @@ it("update metadata", function(done) {
         file.setMetadata('foo new key', "libgroove rules!");
         assert.strictEqual(file.getMetadata('foo new key'), 'libgroove rules!');
         file.save(function(err) {
-            assert.ok(!err);
+            if (err) throw err;
             file.close(checkUpdate);
         });
     }
@@ -155,3 +155,4 @@ it("create, attach, detach fingerprinter", function(done) {
         });
     });
 });
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-groove.git



More information about the Pkg-javascript-commits mailing list