[
  {
    "path": ".gitignore",
    "content": "/build\n/node_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 3.0.0 (UNRELEASED)\n\n * `player.setItemGain` and `player.setItemPeak` are gone in favor of\n   `player.setItemGainPeak`\n * `groove.getDevices()` returns an object instead of an array.\n * `ANY_SINK_FULL` is now default instead of `EVERY_SINK_FULL`\n * `player.targetAudioFormat`, `player.actualAudioFormat`,\n   `encoder.targetAudioFormat`, `encoder.actualAudioFormat`:\n   - `channelLayout` - instead of a number it is an array of channel ids\n   * `sampleFormat` - sample format enum values are different\n * `player.deviceBufferSize` - removed. This functionality no longer exists.\n * `player.sinkBufferSize` - removed. This functionality no longer exists.\n * `detector.sinkBufferSize` - removed. This functionality no longer exists.\n * `printer.sinkBufferSize` - removed. This functionality no longer exists.\n * `encoder.sinkBufferSize` - removed. This functionality no longer exists.\n * `player.deviceIndex` - removed in favor of `player.device`.\n * `player.device` is mandatory, and you must get a device reference by calling\n   `groove.getDevices()`. You must call `groove.connectSoundBackend()` before\n   calling `groove.getDevices()`.\n * After calling `groove.createPlaylist` you must call `playlist.destroy` when finished\n   with the playlist.\n * Add `GrooveWaveformBuilder` for creating waveform visualizations\n * player: 'nowplaying' event renamed to 'nowPlaying'\n * player: 'bufferunderrun' event renamed to 'bufferUnderrun'\n * player: add more events: 'deviceClosed', 'deviceOpened', 'deviceOpenError',\n   'endOfPlaylist', 'wakeup'.\n * player: `targetAudioFormat`, `actualAudioFormat`, and `useExactAudioFormat`\n   no longer exist. Instead, the player always opens the device with exactly the\n   correct audio parameters for each song, or if the device doesn't support that,\n   the next best in terms of audio quality.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Andrew Kelley\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# node-groove\n\nNode.js bindings to [libgroove](https://github.com/andrewrk/libgroove) -\ngeneric music player backend library.\n\nLive discussion in `#libgroove` on [freenode](https://freenode.net/).\n\nHere are the main interfaces. See API Documentation below for more details.\n\n * GrooveFile - represents an open audio file.\n * GroovePlaylist - put files in the playlist, and the playlist decodes the\n   files and fills up the attached sinks.\n * GroovePlayer - attach this sink to a playlist to play the decoded audio over\n   the system's speakers.\n * GrooveEncoder - attach this sink to a playlist to obtain encoded audio\n   buffers, such as an mp3 stream.\n * GrooveLoudnessDetector - attach this sink to a playlist to compute how loud\n   files sound to the human ear, along with the \"true peak\" value. You can use\n   this to implement ReplayGain.\n * GrooveFingerprinter - attach this sink to a playlist to compute an acoustid\n   fingerprint. This can be used to look up a file by its audio content and\n   figure out which tags should be applied.\n * GrooveWaveformBuilder - attach this sink to a playlist to compute a JSON\n   representation of an audio file. This can be used to display a visualization\n   of the audio file.\n\n## Usage\n\n1. Install libgroove to your system. libgroove is a set of 4 libraries;\n   node-groove depends on all of them. So for example on ubuntu, make sure to\n   install libgroove-dev, libgrooveplayer-dev, libgrooveloudness-dev, and\n   libgroovefingerprinter-dev.\n2. `npm install --save groove`\n\n### Versions\n\n * node-groove >=3.0.0 depends on libgroove >=5.0.0\n * node-groove >=2.4.0 <3.0.0 depends on libgroove >=4.3.0 <5.0.0\n * node-groove 2.3.4 depends on libgroove <4.3.0\n\nSee CHANGELOG.md for release notes and upgrade guide.\n\n### Get Metadata from File\n\n```js\nvar groove = require('groove');\n\ngroove.open(\"danse-macabre.ogg\", function(err, file) {\n  if (err) throw err;\n  console.log(file.metadata());\n  console.log(\"duration:\", file.duration());\n  file.close(function(err) {\n    if (err) throw err;\n  });\n});\n```\n\n#### More Examples\n\n * example/metadata.js - read or update metadata in a media file\n * example/playlist.js - play several files in a row and then exit\n * example/replaygain.js - compute replaygain values for media files\n * example/transcode.js - convert and splice several files together\n * example/fingerprint.js - create an acoustid fingerprint for media files\n * example/devices.js - list the playback devices on the system\n * example/waveform.js - calculate a waveformjs compatible representation of a media file\n\n## API Documentation\n\n### globals\n\n#### groove.setLogging(level)\n\n`level` can be:\n\n * `groove.LOG_QUIET`\n * `groove.LOG_ERROR`\n * `groove.LOG_WARNING`\n * `groove.LOG_INFO`\n\n#### groove.loudnessToReplayGain(loudness)\n\nConverts a loudness value which is in LUFS to the ReplayGain-suggested dB\nadjustment.\n\n#### groove.dBToFloat(dB)\n\nConverts dB format volume adjustment to a floating point gain format.\n\n#### groove.getVersion()\n\nReturns an object with these properties:\n\n * `major`\n * `minor`\n * `patch`\n\n### GrooveFile\n\n#### groove.open(filename, callback)\n\n`callback(err, file)`\n\n#### file.close(callback)\n\n`callback(err)`\n\n#### file.duration()\n\nIn seconds.\n\n#### file.shortNames()\n\nA comma-separated list of short names for the format.\n\n#### file.getMetadata(key, [flags])\n\nFlags:\n\n * `groove.TAG_MATCH_CASE`\n * `groove.TAG_DONT_OVERWRITE`\n * `groove.TAG_APPEND`\n\n#### file.setMetadata(key, value, [flags])\n\nSee `getMetadata` for flags.\n\nPass `null` for `value` to delete a key.\n\n#### file.metadata()\n\nThis returns an object populated with all the metadata.\nUpdating the object does nothing. Use `setMetadata` to\nupdate metadata and then `save` to write changes to disk.\n\n#### file.dirty\n\nBoolean whether `save` will do anything.\n\n#### file.filename\n\nThe string that was passed to `groove.open`\n\n#### file.overrideDuration(duration)\n\nIf you know for sure the actual duration of the file, call this function\nto set the actual duration in seconds of the file. `GrooveWaveformBuilder`\nwill use this value instead of `file.duration()`.\n\nThis must only be called when no `GroovePlaylistItem` references to this file.\n\n#### file.save(callback)\n\n`callback(err)`\n\n### GroovePlaylist\n\n#### groove.createPlaylist()\n\nA playlist managers keeping an audio buffer full. To send the buffer\nto your speakers, use `playlist.createPlayer()`.\n\nNote: you probably only want one playlist. In node-groove, a playlist is\na low-level audio processing concept, not to be confused with user-facing\nplaylists where users might add, remove, and re-order songs.\n\n#### playlist.destroy()\n\nWhen finished with your playlist you must destroy it.\n\n#### playlist.items()\n\nReturns a read-only array of playlist items.\nUse `playlist.insert` and `playlist.remove` to modify.\n\n`[playlistItem1, playlistItem2, ...]`\n\n#### playlist.play()\n\n#### playlist.pause()\n\n#### playlist.seek(playlistItem, position)\n\nSeek to `playlistItem`, `position` seconds into the song.\n\n#### playlist.insert(file, gain, peak, nextPlaylistItem)\n\nCreates a new playlist item with file and puts it in the playlist before\n`nextPlaylistItem`. If `nextPlaylistItem` is `null`, appends the new\nitem to the playlist.\n\n`gain` is a float format volume adjustment that applies only to this item.\ndefaults to 1.0\n\n`peak` is float format, see `item.peak`.\ndefaults to 1.0\n\nReturns the newly added playlist item.\n\nOnce you add a file to the playlist, you must not `file.close()` it until\nyou first remove it from the playlist.\n\n#### playlist.remove(playlistItem)\n\nRemove `playlistItem` from the playlist.\n\nNote that you are responsible for calling `file.close()` on every file\nthat you open with `groove.open`. `playlist.remove` will not close files.\n\n#### playlist.position()\n\nReturns `{item, pos}` where `item` is the playlist item currently being\ndecoded and `pos` is how many seconds into the song the decode head is.\n\nNote that typically you are more interested in the position of the play head,\nnot the decode head. Example methods which return the play head are\n`player.position()` and `encoder.position()`.\n\n#### playlist.playing()\n\nReturns `true` or `false`.\n\n#### playlist.clear()\n\nRemove all playlist items.\n\n#### playlist.count()\n\nHow many items are on the playlist.\n\n#### playlist.gain\n\n#### playlist.setGain(value)\n\nBetween 0.0 and 1.0. You probably want to leave this at 1.0, since using\nreplaygain will typically lower your volume a significant amount.\n\n#### playlist.setItemGainPeak(playlistItem, gain, peak)\n\n`gain` is a float that affects the volume of the specified playlist item only.\nTo convert from dB to float, use exp(log(10) * 0.05 * dBValue).\n\nSee `item.peak`\n\n#### playlist.setFillMode(mode)\n\n`mode` can be:\n\n * `groove.EVERY_SINK_FULL`\n\n    The playlist will decode audio if any sinks are not full. If any sinks do\n    not drain fast enough the data will buffer up in the playlist.\n\n * `groove.ANY_SINK_FULL`\n\n    This is the default behavior. With this behavior, the playlist will stop\n    decoding audio when any attached sink is full, and then resume decoding\n    audio every sink is not full.\n\nDefaults to `groove.EVERY_SINK_FULL`.\n\n### GroovePlaylistItem\n\nThese are not instantiated directly; instead they are returned from\n`playlist.items()`.\n\nA `GroovePlaylistItem` is merely a pointer into a `GroovePlaylist`. If you\nremove a playlist item from a playlist, any playlist item references you\nhave lying around become dangling pointers.\n\n#### item.file\n\nRead-only.\n\n#### item.gain\n\nA volume adjustment in float format to apply to the file when it plays.\nThis is typically used for loudness compensation, for example ReplayGain.\nTo convert from dB to float, use `groove.dBToFloat`\n\nRead-only. Use `playlist.setItemGain` to modify.\n\n#### item.peak\n\nThe sample peak of this playlist item is assumed to be 1.0 in float\nformat. If you know for certain that the peak is less than 1.0, you\nmay set this value which may allow the volume adjustment to use\na pure amplifier rather than a compressor. This results in slightly\nbetter audio quality.\n\nRead-only. Use `playlist.setItemPeak` to modify.\n\n#### item.id\n\nEvery time you obtain a playlist item from groove, you will get a fresh\nJavaScript object, but it might point to the same underlying libgroove pointer\nas another. The `id` field is a way to check if two playlist items reference\nthe same one.\n\nRead-only.\n\n### GroovePlayer\n\n#### groove.getDevices()\n\nBefore you can call this function, you must call\n`groove.connectSoundBackend()`.\n\nReturns an object like this:\n\n```js\n{\n  list: [\n    {\n      name: \"User-Friendly Device Name\",\n      id: \"unique device ID that persists across plugs and unplugs\",\n      isRaw: false, // true if this device would claim exclusive access\n      probeError: 3, // non zero if scanning this device did not work\n    },\n    //...\n  ],\n  defaultIndex: 0,\n}\n```\n\n#### groove.connectSoundBackend([backend])\n\n`backend` is optional. If left blank the best backend is automatically\nselected. Otherwise it can be one of these:\n\n * `groove.BACKEND_JACK`\n * `groove.BACKEND_PULSEAUDIO`\n * `groove.BACKEND_ALSA`\n * `groove.BACKEND_COREAUDIO`\n * `groove.BACKEND_WASAPI`\n * `groove.BACKEND_DUMMY`\n\n#### groove.disconnectSoundBackend()\n\n#### groove.createPlayer()\n\nCreates a GroovePlayer instance which you can then configure by setting\nproperties.\n\n#### player.device\n\nBefore calling `attach()`, set this to one of the devices\nreturned from `groove.getDevices()`.\n\n#### player.attach(playlist, callback)\n\nSends audio to sound device.\n\n`callback(err)`\n\n#### player.detach(callback)\n\n`callback(err)`\n\n#### player.position()\n\nReturns `{item, pos}` where `item` is the playlist item currently being\nplayed and `pos` is how many seconds into the song the play head is.\n\n#### player.on('nowplaying', handler)\n\nFires when the item that is now playing changes. It can be `null`.\n\n`handler()`\n\n#### player.on('bufferunderrun', handler)\n\nFires when a buffer underrun occurs. Ideally you'll never see this.\n\n`handler()`\n\n#### player.on('devicereopened', handler)\n\nFires when you have set `useExactAudioFormat` to `true` and the audio device\nhas been closed and re-opened to match incoming audio data.\n\n`handler()`\n\n### GrooveEncoder\n\n#### groove.createEncoder()\n\n#### encoder.bitRate\n\nselect encoding quality by choosing a target bit rate\n\n#### encoder.formatShortName\n\noptional - help libgroove guess which format to use.\n`avconv -formats` to get a list of possibilities.\n\n#### encoder.codecShortName\n\noptional - help libgroove guess which codec to use.\n`avconv-codecs` to get a list of possibilities.\n\n#### encoder.filename\n\noptional - provide an example filename to help libgroove guess\nwhich format/codec to use.\n\n#### encoder.mimeType\n\noptional - provide a mime type string to help libgrooove guess\nwhich format/codec to use.\n\n#### encoder.targetAudioFormat\n\nThe desired audio format settings with which to encode.\n`groove.createEncoder()` defaults these to 44100 Hz,\nsigned 16-bit int, stereo.\nThese are preferences; if a setting cannot be used, a substitute will\nbe used instead. In this case, actualAudioFormat will be updated to reflect\nthe substituted values.\n\nProperties:\n\n * `sampleRate`\n * `channelLayout` - array of channel ids\n * `sampleFormat`\n\n#### encoder.actualAudioFormat\n\ngroove sets this to the actual format you get when you attach the encoder.\nIdeally will be the same as targetAudioFormat but might not be.\n\nProperties:\n\n * `sampleRate`\n * `channelLayout` - array of channel ids\n * `sampleFormat`\n\n#### encoder.sinkBufferSize\n\nHow big the sink buffer should be, in sample frames.\n`createEncoder` defaults this to 8192\n\n#### encoder.encodedBufferSize\n\nHow big the encoded audio buffer should be, in bytes.\n`createEncoder` defaults this to 16384\n\n#### encoder.attach(playlist, callback)\n\n`callback(err)`\n\n#### encoder.detach(callback)\n\n`callback(err)`\n\n#### encoder.getBuffer()\n\nReturns `null` if no buffer available, or an object with these properties:\n\n * `buffer` - a node `Buffer` instance which is the encoded data for this chunk\n   this can be `null` in which case this buffer is actually the end of\n   playlist sentinel.\n * `item` - the GroovePlaylistItem of which this buffer is encoded data for\n * `pos` - position in seconds that this buffer represents in into the item\n\n#### encoder.on('buffer', handler)\n\n`handler()`\n\nEmitted when there is a buffer available to get. You still need to get the\nbuffer with `getBuffer()`.\n\n#### encoder.position()\n\nReturns `{item, pos}` where `item` is the playlist item currently being\nencoded and `pos` is how many seconds into the song the encode head is.\n\n### GrooveLoudnessDetector\n\n#### groove.createLoudnessDetector()\n\nreturns a GrooveLoudnessDetector\n\n#### detector.infoQueueSize\n\nSet this to determine how far ahead into the playlist to look.\n\n#### detector.disableAlbum\n\nSet to `true` to only compute track loudness. This is faster and requires less\nmemory than computing both.\n\n#### detector.attach(playlist, callback)\n\n`callback(err)`\n\n#### detector.detach(callback)\n\n`callback(err)`\n\n#### detector.getInfo()\n\nReturns `null` if no info available, or an object with these properties:\n\n * `loudness` - loudness in LUFS\n * `peak` - sample peak in float format of the file\n * `duration` - duration in seconds of the track\n * `item` - the GroovePlaylistItem that this applies to, or `null` if it applies\n   to the entire album.\n\n#### detector.position()\n\nReturns `{item, pos}` where `item` is the playlist item currently being\ndetected and `pos` is how many seconds into the song the detect head is.\n\n#### detector.on('info', handler)\n\n`handler()`\n\nEmitted when there is info available to get. You still need to get the info\nwith `getInfo()`.\n\n### GrooveFingerprinter\n\n#### groove.createFingerprinter()\n\nreturns a GrooveFingerprinter\n\n#### groove.encodeFingerprint(rawFingerprint)\n\nGiven an Array of integers which is the raw fingerprint, encode it into a\nstring which can be submitted to acoustid.org.\n\n#### groove.decodeFingerprint(fingerprint)\n\nGiven the fingerprint string, returns a list of integers which is the raw\nfingerprint data.\n\n#### printer.infoQueueSize\n\nSet this to determine how far ahead into the playlist to look.\n\n#### printer.attach(playlist, callback)\n\n`callback(err)`\n\n#### printer.detach(callback)\n\n`callback(err)`\n\n#### printer.getInfo()\n\nReturns `null` if no info available, or an object with these properties:\n\n * `fingerprint` - integer array which is the raw fingerprint\n * `duration` - duration in seconds of the track\n * `item` - the GroovePlaylistItem that this applies to, or `null` if it applies\n   to the entire album.\n\n#### printer.position()\n\nReturns `{item, pos}` where `item` is the playlist item currently being\nfingerprinted and `pos` is how many seconds into the song the printer head is.\n\n#### printer.on('info', handler)\n\n`handler()`\n\nEmitted when there is info available to get. You still need to get the info\nwith `getInfo()`.\n\n### GrooveWaveformBuilder\n\n#### groove.createWaveformBuilder()\n\nreturns a GrooveWaveformBuilder\n\n#### waveform.widthInFrames\n\nHow many frames wide the waveform data will be. Defaults to 1920.\n\nIf you have a song with 100 frames and `widthInFrames` is 50, then each\nwaveform data frame will correspond to 2 frames of the original song.\n\n#### printer.infoQueueSizeBytes\n\nSet this to determine how far ahead into the playlist to look.\n\n#### waveform.attach(playlist, callback)\n\n`callback(err)`\n\n#### waveform.detach(callback)\n\n`callback(err)`\n\n#### waveform.getInfo()\n\nReturns `null` if no info available, or an object with these properties:\n\n * `buffer` - A `Buffer` of the waveform data, one unsigned 8 bit integer per\n   `widthInFrames`.\n * `expectedDuration` - This is the duration in seconds that was used to create\n   the waveform data. If this is different than `actualDuration` then the data\n   is invalid and must be re-calculated, this time using `file.overrideDuration()`\n * `actualDuration` - This is the correct duration in seconds for the track,\n   known only after waveform calculation is complete.\n * `item` - the GroovePlaylistItem that this applies to, or `null` if this info\n   signals the end of playlist.\n\n#### waveform.position()\n\nReturns `{item, pos}` where `item` is the playlist item currently being\ncalculated and `pos` is how many seconds into the song the waveform head is.\n\n#### waveform.on('info', handler)\n\n`handler()`\n\nEmitted when there is info available to get. You still need to get the info\nwith `getInfo()`.\n"
  },
  {
    "path": "binding.gyp",
    "content": "{\n  \"targets\": [\n    {\n        \"target_name\": \"groove\",\n        \"sources\": [\n          \"src/player.cc\",\n          \"src/groove.cc\",\n          \"src/file.cc\",\n          \"src/playlist.cc\",\n          \"src/playlist_item.cc\",\n          \"src/waveform_builder.cc\",\n          \"src/loudness_detector.cc\",\n          \"src/fingerprinter.cc\",\n          \"src/encoder.cc\",\n          \"src/device.cc\",\n        ],\n        \"libraries\": [\n            \"-lgroove\"\n        ],\n        \"include_dirs\": [\n            \"<!(node -e \\\"require('nan')\\\")\"\n        ]\n    }\n  ]\n}\n"
  },
  {
    "path": "example/devices.js",
    "content": "/* list available output devices */\n\nvar groove = require('../');\nvar assert = require('assert');\n\ngroove.connectSoundBackend();\nvar devices = groove.getDevices();\n\nfor (var i = 0; i < devices.list.length; i += 1) {\n    if (devices.list[i].isRaw) continue;\n    var isDefault = (i === devices.defaultIndex);\n    var defaultString = isDefault ? \"(default) \" : \"\";\n    console.log(defaultString + devices.list[i].name);\n}\n"
  },
  {
    "path": "example/fingerprint.js",
    "content": "/* generate the acoustid fingerprint of songs */\n\nvar groove = require('../');\nvar Pend = require('pend');\n\nif (process.argv.length < 3) usage();\n\nvar playlist = groove.createPlaylist();\nvar printer = groove.createFingerprinter();\n\nprinter.on('info', function() {\n  var info = printer.getInfo();\n  if (info.item) {\n    console.log(info.item.file.filename, \"fingerprint:\");\n    console.log(info.fingerprint);\n  } else {\n    cleanup();\n  }\n});\n\nvar files = [];\nvar pend = new Pend();\n\nfor (var i = 2; i < process.argv.length; i += 1) {\n  var o = {\n    file: null,\n    filename: process.argv[i],\n  };\n  files.push(o);\n  pend.go(openFileFn(o));\n}\npend.wait(function(err) {\n  if (err) throw err;\n  files.forEach(function(o) {\n    playlist.insert(o.file, null);\n  });\n  printer.attach(playlist, function(err) {\n    if (err) throw err;\n  });\n});\n\nfunction openFileFn(o) {\n  return function(cb) {\n    groove.open(o.filename, function(err, file) {\n      if (err) return cb(err);\n      o.file = file;\n      cb();\n    });\n  };\n}\n\nfunction cleanup() {\n  playlist.clear();\n  files.forEach(function(o) {\n    pend.go(function(cb) {\n      o.file.close(cb);\n    });\n  });\n  pend.wait(function(err) {\n    if (err) throw err;\n    printer.detach(function(err) {\n      if (err) throw err;\n      playlist.destroy();\n    });\n  });\n}\n\nfunction usage() {\n  console.error(\"Usage: node fingerprint.js file1 file2 ...\");\n  process.exit(1);\n}\n\n"
  },
  {
    "path": "example/metadata.js",
    "content": "/* read or update metadata in a media file */\n\nvar groove = require('../');\n\nif (process.argv.length < 3) usage();\n\ngroove.setLogging(groove.LOG_INFO);\n\nvar filename = process.argv[2];\ngroove.open(filename, function(err, file) {\n  if (err) {\n    console.error(\"error opening file:\", err.stack);\n    process.exit(1);\n  }\n  var key, value;\n  for (var i = 3; i < process.argv.length; i += 1) {\n    var arg = process.argv[i];\n    if (arg === '--update') {\n      if (i + 2 >= process.argv.length) {\n        console.error(\"--update requires 2 arguments\");\n        cleanup(file, usage);\n        return;\n      }\n      key = process.argv[++i];\n      value = process.argv[++i];\n      file.setMetadata(key, value);\n    } else if (arg === '--delete') {\n      if (i + 1 >= process.argv.length) {\n        console.error(\"--delete requires 1 argument\");\n        cleanup(file, usage);\n        return;\n      }\n      key = process.argv[++i];\n      file.setMetadata(key, null);\n    } else {\n      cleanup(file, usage);\n      return;\n    }\n  }\n\n  console.log(\"duration\", \"=\", file.duration());\n  var metadata = file.metadata();\n  for (key in metadata) {\n    value = metadata[key];\n    console.log(key, \"=\", value);\n  }\n  if (file.dirty) {\n    file.save(handleSaveErr);\n  } else {\n    cleanup(file);\n  }\n  function handleSaveErr(err) {\n    if (err) console.error(\"Error saving:\", err.stack);\n    cleanup(file);\n  }\n});\n\nfunction usage() {\n  console.error(\"Usage:\", process.argv[0], process.argv[1],\n      \"<file> [--update key value] [--delete key]\");\n  process.exit(1);\n}\n\nfunction cleanup(file, cb) {\n  file.close(function(err) {\n    if (err) console.error(\"Error closing file:\", err.stack);\n    if (cb) cb();\n  });\n}\n"
  },
  {
    "path": "example/playlist.js",
    "content": "/* play several files in a row and then exit */\n\nvar groove = require('../');\nvar Pend = require('pend');\n\nif (process.argv.length < 3) usage();\n\nvar playlist = groove.createPlaylist();\nvar player = groove.createPlayer();\n\ngroove.connectSoundBackend();\nvar devices = groove.getDevices();\nvar defaultDevice = devices.list[devices.defaultIndex];\nplayer.device = defaultDevice;\n\nplayer.on('nowPlaying', function() {\n  var current = player.position();\n  if (!current.item) {\n    cleanup();\n    return;\n  }\n  var artist = current.item.file.getMetadata('artist');\n  var title = current.item.file.getMetadata('title');\n  console.log(\"Now playing:\", artist, \"-\", title);\n});\n\nvar files = [];\n\nvar pend = new Pend();\nfor (var i = 2; i < process.argv.length; i += 1) {\n  var o = {\n    filename: process.argv[i],\n    file: null,\n  };\n  files.push(o);\n  pend.go(openFileFn(o));\n}\npend.wait(function(err) {\n  if (err) throw err;\n  files.forEach(function(o) {\n    playlist.insert(o.file);\n  });\n  player.attach(playlist, function(err) {\n    if (err) throw err;\n  });\n});\n\nfunction openFileFn(o, filename) {\n  return function(cb) {\n    groove.open(o.filename, function(err, file) {\n      if (err) return cb(err);\n      o.file = file;\n      cb();\n    });\n  };\n}\n\nfunction cleanup() {\n  playlist.clear();\n  files.forEach(function(o) {\n    pend.go(function(cb) {\n      o.file.close(cb);\n    });\n  });\n  pend.wait(function(err) {\n    if (err) throw err;\n    player.detach(function(err) {\n      if (err) throw err;\n      playlist.destroy();\n    });\n  });\n}\n\nfunction usage() {\n  console.error(\"Usage: playlist file1 file2 ...\");\n  process.exit(1);\n}\n"
  },
  {
    "path": "example/replaygain.js",
    "content": "/* replaygain scanner */\n\nvar groove = require('../');\nvar Pend = require('pend');\n\nif (process.argv.length < 3) usage();\n\nvar playlist = groove.createPlaylist();\nvar detector = groove.createLoudnessDetector();\n\ndetector.on('info', function() {\n  var info = detector.getInfo();\n  if (info.item) {\n    console.log(info.item.file.filename, \"gain:\",\n      groove.loudnessToReplayGain(info.loudness), \"peak:\", info.peak,\n      \"duration:\", info.duration);\n  } else {\n    console.log(\"all files gain:\",\n      groove.loudnessToReplayGain(info.loudness), \"peak:\", info.peak,\n      \"duration:\", info.duration);\n    cleanup();\n  }\n});\n\nvar files = [];\nvar pend = new Pend();\n\ndetector.attach(playlist, function(err) {\n  if (err) throw err;\n\n  var pend = new Pend();\n  for (var i = 2; i < process.argv.length; i += 1) {\n    var o = {\n      filename: process.argv[i],\n      file: null,\n    };\n    files.push(o);\n    pend.go(openFileFn(o));\n  }\n  pend.wait(function(err) {\n    if (err) throw err;\n    files.forEach(function(o) {\n      playlist.insert(o.file, null);\n    });\n  });\n});\n\nfunction openFileFn(o) {\n  return function(cb) {\n    groove.open(o.filename, function(err, file) {\n      if (err) return cb(err);\n      o.file = file;\n      cb();\n    });\n  };\n}\n\nfunction cleanup() {\n  playlist.clear();\n  files.forEach(function(o) {\n    pend.go(function(cb) {\n      o.file.close(cb);\n    });\n  });\n  pend.wait(function(err) {\n    if (err) throw err;\n    detector.detach(function(err) {\n      if (err) throw err;\n      playlist.destroy();\n    });\n  });\n}\n\nfunction usage() {\n  console.error(\"Usage: node replaygain.js file1 file2 ...\");\n  process.exit(1);\n}\n"
  },
  {
    "path": "example/transcode.js",
    "content": "/* transcode a file into ogg vorbis */\n\nvar groove = require('../');\nvar fs = require('fs');\n\nif (process.argv.length < 4) usage();\n\ngroove.setLogging(groove.LOG_INFO);\n\nvar playlist = groove.createPlaylist();\nvar encoder = groove.createEncoder();\nencoder.formatShortName = \"ogg\";\nencoder.codecShortName = \"vorbis\";\n\nvar outStream = fs.createWriteStream(process.argv[3]);\n\nencoder.on('buffer', function() {\n  var buffer;\n  while (buffer = encoder.getBuffer()) {\n    if (buffer.buffer) {\n      outStream.write(buffer.buffer);\n    } else {\n      cleanup();\n      return;\n    }\n  }\n});\n\nencoder.attach(playlist, function(err) {\n  if (err) throw err;\n\n  groove.open(process.argv[2], function(err, file) {\n    if (err) throw err;\n    playlist.insert(file, null);\n  });\n});\n\nfunction cleanup() {\n  var file = playlist.items()[0].file;\n  playlist.clear();\n  file.close(function(err) {\n    if (err) throw err;\n    encoder.detach(function(err) {\n      if (err) throw err;\n      playlist.destroy();\n    });\n  });\n}\n\nfunction usage() {\n  console.error(\"Usage: node transcode.js inputfile outputfile\");\n  process.exit(1);\n}\n"
  },
  {
    "path": "example/waveform.js",
    "content": "/* calculate a waveformjs compatible representation of a media file */\n\nvar groove = require('../');\n\nmain();\n\nfunction main() {\n  var inputFilename = null;\n\n  for (var i = 2; i < process.argv.length; i += 1) {\n    var arg = process.argv[i];\n    var overrideDuration = null;\n    if (arg[0] === \"-\" && arg[1] === \"-\") {\n      if (++i < process.argv.length) {\n        if (arg === \"--override-duration\") {\n          overrideDuration = parseFloat(process.argv[i]);\n        } else {\n          usageAndExit();\n        }\n      } else {\n        usageAndExit();\n      }\n    } else if (!inputFilename) {\n      inputFilename = arg;\n    } else {\n      usageAndExit();\n    }\n  }\n\n  if (!inputFilename) usageAndExit();\n\n  var playlist = groove.createPlaylist();\n  var waveform = groove.createWaveformBuilder();\n  var file = null;\n\n  waveform.on('info', function() {\n    var info = waveform.getInfo();\n    if (!info.item) {\n      cleanup();\n      return;\n    }\n    if (Math.abs(info.expectedDuration - info.actualDuration) > 0.1) {\n      console.error(\"invalid duration. re-run with --override-duration \" + info.actualDuration);\n      process.exit(1);\n      return;\n    }\n    var data = {\n      duration: info.actualDuration,\n      waveformjs: Array.prototype.slice.call(info.buffer, 0),\n    };\n    console.log(JSON.stringify(data));\n  });\n\n  groove.open(inputFilename, function(err, openedFile) {\n    if (err) throw err;\n\n    file = openedFile;\n    if (overrideDuration) {\n      file.overrideDuration(overrideDuration);\n    }\n\n    playlist.insert(file, null);\n    waveform.attach(playlist, function(err) {\n      if (err) throw err;\n    });\n  });\n\n  function cleanup() {\n    playlist.clear();\n    waveform.detach(function(err) {\n      if (err) throw err;\n      playlist.destroy();\n      file.close(function(err) {\n        if (err) throw err;\n      });\n    });\n  }\n}\n\nfunction usageAndExit() {\n  console.error(\"Usage: node waveform.js [--override-duration seconds] file\");\n  process.exit(1);\n}\n\n"
  },
  {
    "path": "lib/index.js",
    "content": "var bindings = require('bindings')('groove.node');\nvar EventEmitter = require('events').EventEmitter;\nvar util = require('util');\n\nvar DB_SCALE = Math.log(10.0) * 0.05;\n\n/* \"C++ modules aren't really for doing complex things that need to be\n * strewn across multiple modules.  Just get your binding done as quick\n * as possible, get out of there, and then wrap it in JS for all the fancy stuff\n *\n * -isaacs\n */\n\n// hi-jack some of the native methods\nvar bindingsCreatePlayer = bindings.createPlayer;\nvar bindingsCreateLoudnessDetector = bindings.createLoudnessDetector;\nvar bindingsCreateFingerprinter = bindings.createFingerprinter;\nvar bindingsCreateEncoder = bindings.createEncoder;\nvar bindingsCreateWaveformBuilder = bindings.createWaveformBuilder;\n\nbindings.createPlayer = jsCreatePlayer;\nbindings.createEncoder = jsCreateEncoder;\nbindings.createLoudnessDetector = jsCreateLoudnessDetector;\nbindings.createFingerprinter = jsCreateFingerprinter;\nbindings.createWaveformBuilder = jsCreateWaveformBuilder;\nbindings.loudnessToReplayGain = loudnessToReplayGain;\nbindings.dBToFloat = dBToFloat;\n\nmodule.exports = bindings;\n\nfunction jsCreateEncoder() {\n  var encoder = bindingsCreateEncoder(eventCb);\n\n  postHocInherit(encoder, EventEmitter);\n  EventEmitter.call(encoder);\n\n  return encoder;\n\n  function eventCb() {\n    encoder.emit('buffer');\n  }\n}\n\nfunction jsCreatePlayer() {\n  var player = bindingsCreatePlayer(eventCb);\n\n  postHocInherit(player, EventEmitter);\n  EventEmitter.call(player);\n\n  return player;\n\n  function eventCb(id) {\n    switch (id) {\n    case bindings._EVENT_NOWPLAYING:\n      player.emit('nowPlaying');\n      break;\n    case bindings._EVENT_BUFFERUNDERRUN:\n      player.emit('bufferUnderrun');\n      break;\n    case bindings._EVENT_DEVICE_CLOSED:\n      player.emit('deviceClosed');\n      break;\n    case bindings._EVENT_DEVICE_OPENED:\n      player.emit('deviceOpened');\n      break;\n    case bindings._EVENT_DEVICE_OPEN_ERROR:\n      player.emit('deviceOpenError');\n      break;\n    case bindings._EVENT_END_OF_PLAYLIST:\n      player.emit('endOfPlaylist');\n      break;\n    case bindings._EVENT_WAKEUP:\n      player.emit('wakeup');\n      break;\n    }\n  }\n}\n\nfunction jsCreateLoudnessDetector() {\n  var detector = bindingsCreateLoudnessDetector(eventCb);\n\n  postHocInherit(detector, EventEmitter);\n  EventEmitter.call(detector);\n\n  return detector;\n\n  function eventCb() {\n    detector.emit('info');\n  }\n}\n\nfunction jsCreateFingerprinter() {\n  var printer = bindingsCreateFingerprinter(eventCb);\n  postHocInherit(printer, EventEmitter);\n  EventEmitter.call(printer);\n\n  return printer;\n\n  function eventCb() {\n    printer.emit('info');\n  }\n}\n\nfunction jsCreateWaveformBuilder() {\n  var waveform = bindingsCreateWaveformBuilder(eventCb);\n\n  postHocInherit(waveform, EventEmitter);\n  EventEmitter.call(waveform);\n\n  return waveform;\n\n  function eventCb() {\n    waveform.emit('info');\n  }\n}\n\nfunction postHocInherit(baseInstance, Super) {\n  var baseProto = Object.getPrototypeOf(baseInstance);\n  var superProto = Super.prototype;\n  Object.keys(superProto).forEach(function(method) {\n    if (!baseProto[method]) baseProto[method] = superProto[method];\n  });\n}\n\nfunction clamp_rg(x) {\n  if (x > 51.0) return 51.0;\n  else if (x < -51.0) return -51.0;\n  else return x;\n}\n\nfunction loudnessToReplayGain(loudness) {\n  return clamp_rg(-18.0 - loudness);\n}\n\nfunction dBToFloat(dB) {\n  return Math.exp(dB * DB_SCALE);\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"groove\",\n  \"version\": \"2.4.0\",\n  \"description\": \"bindings to libgroove - generic music player library\",\n  \"main\": \"lib/index.js\",\n  \"author\": \"Andrew Kelley <superjoe30@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/andrewrk/node-groove\"\n  },\n  \"scripts\": {\n    \"test\": \"mocha --reporter spec\",\n    \"install\": \"node-gyp rebuild\"\n  },\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"mocha\": \"~2.4.5\",\n    \"ncp\": \"~2.0.0\",\n    \"pend\": \"~1.2.0\"\n  },\n  \"dependencies\": {\n    \"bindings\": \"~1.2.1\",\n    \"nan\": \"~2.3.5\"\n  },\n  \"gypfile\": true,\n  \"bugs\": {\n    \"url\": \"https://github.com/andrewrk/node-groove/issues\"\n  },\n  \"homepage\": \"https://github.com/andrewrk/node-groove\",\n  \"directories\": {\n    \"example\": \"example\",\n    \"test\": \"test\"\n  }\n}\n"
  },
  {
    "path": "src/device.cc",
    "content": "#include \"device.h\"\n#include \"file.h\"\n\nusing namespace v8;\n\nGNDevice::GNDevice() { };\nGNDevice::~GNDevice() {\n    soundio_device_unref(device);\n};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNDevice::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"SoundIoDevice\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(1);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"name\").ToLocalChecked(), GetName);\n    Nan::SetAccessor(proto, Nan::New<String>(\"id\").ToLocalChecked(), GetId);\n    Nan::SetAccessor(proto, Nan::New<String>(\"softwareLatencyMin\").ToLocalChecked(), GetSoftwareLatencyMin);\n    Nan::SetAccessor(proto, Nan::New<String>(\"softwareLatencyMax\").ToLocalChecked(), GetSoftwareLatencyMax);\n    Nan::SetAccessor(proto, Nan::New<String>(\"softwareLatencyCurrent\").ToLocalChecked(), GetSoftwareLatencyCurrent);\n    Nan::SetAccessor(proto, Nan::New<String>(\"isRaw\").ToLocalChecked(), GetIsRaw);\n    Nan::SetAccessor(proto, Nan::New<String>(\"probeError\").ToLocalChecked(), GetProbeError);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNDevice::New) {\n    Nan::HandleScope scope;\n\n    GNDevice *obj = new GNDevice();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNDevice::NewInstance(SoundIoDevice *device) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(instance);\n    gn_device->device = device;\n\n    return scope.Escape(instance);\n}\n\nNAN_GETTER(GNDevice::GetName) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<String>(device->name).ToLocalChecked());\n}\n\nNAN_GETTER(GNDevice::GetId) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<String>(device->id).ToLocalChecked());\n}\n\nNAN_GETTER(GNDevice::GetSoftwareLatencyMin) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<Number>(device->software_latency_min));\n}\n\nNAN_GETTER(GNDevice::GetSoftwareLatencyMax) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<Number>(device->software_latency_max));\n}\n\nNAN_GETTER(GNDevice::GetSoftwareLatencyCurrent) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<Number>(device->software_latency_current));\n}\n\nNAN_GETTER(GNDevice::GetIsRaw) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<Number>(device->is_raw));\n}\n\nNAN_GETTER(GNDevice::GetProbeError) {\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(info.This());\n    SoundIoDevice *device = gn_device->device;\n    info.GetReturnValue().Set(Nan::New<Number>(device->probe_error));\n}\n"
  },
  {
    "path": "src/device.h",
    "content": "#ifndef GN_DEVICE_H\n#define GN_DEVICE_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNDevice : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(SoundIoDevice *device);\n\n        SoundIoDevice *device;\n    private:\n        GNDevice();\n        ~GNDevice();\n\n        static NAN_METHOD(New);\n\n        static NAN_GETTER(GetName);\n        static NAN_GETTER(GetId);\n        static NAN_GETTER(GetSoftwareLatencyMin);\n        static NAN_GETTER(GetSoftwareLatencyMax);\n        static NAN_GETTER(GetSoftwareLatencyCurrent);\n        static NAN_GETTER(GetIsRaw);\n        static NAN_GETTER(GetProbeError);\n};\n\n#endif\n\n\n"
  },
  {
    "path": "src/encoder.cc",
    "content": "#include <node_buffer.h>\n#include \"encoder.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNEncoder::GNEncoder() {};\nGNEncoder::~GNEncoder() {\n    groove_encoder_destroy(encoder);\n    delete event_context->event_cb;\n    delete event_context;\n};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNEncoder::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GrooveEncoder\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(2);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"actualAudioFormat\").ToLocalChecked(), GetActualAudioFormat);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"attach\", Attach);\n    Nan::SetPrototypeMethod(tpl, \"detach\", Detach);\n    Nan::SetPrototypeMethod(tpl, \"getBuffer\", GetBuffer);\n    Nan::SetPrototypeMethod(tpl, \"position\", Position);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNEncoder::New) {\n    Nan::HandleScope scope;\n\n    GNEncoder *obj = new GNEncoder();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nNAN_GETTER(GNEncoder::GetActualAudioFormat) {\n    Nan::HandleScope scope;\n\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());\n    GrooveEncoder *encoder = gn_encoder->encoder;\n\n    Local<Array> layout = Nan::New<Array>();\n\n    for (int ch = 0; ch < encoder->actual_audio_format.layout.channel_count; ch += 1) {\n        Nan::Set(layout, Nan::New<Number>(ch),\n                Nan::New<Number>(encoder->actual_audio_format.layout.channels[ch]));\n    }\n\n    Local<Object> actualAudioFormat = Nan::New<Object>();\n    Nan::Set(actualAudioFormat, Nan::New<String>(\"sampleRate\").ToLocalChecked(),\n            Nan::New<Number>(encoder->actual_audio_format.sample_rate));\n\n    Nan::Set(actualAudioFormat, Nan::New<String>(\"channelLayout\").ToLocalChecked(), layout);\n\n    Nan::Set(actualAudioFormat, Nan::New<String>(\"sampleFormat\").ToLocalChecked(),\n            Nan::New<Number>(encoder->actual_audio_format.format));\n\n    info.GetReturnValue().Set(actualAudioFormat);\n}\n\nLocal<Value> GNEncoder::NewInstance(GrooveEncoder *encoder) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);\n    gn_encoder->encoder = encoder;\n\n    return scope.Escape(instance);\n}\n\nstatic void EncoderEventAsyncCb(uv_async_t *handle) {\n    Nan::HandleScope scope;\n\n    GNEncoder::EventContext *context = reinterpret_cast<GNEncoder::EventContext *>(handle->data);\n\n    const unsigned argc = 1;\n    Local<Value> argv[argc];\n    argv[0] = Nan::Undefined();\n\n    TryCatch try_catch;\n    context->event_cb->Call(argc, argv);\n\n    if (try_catch.HasCaught()) {\n        node::FatalException(try_catch);\n    }\n\n    uv_mutex_lock(&context->mutex);\n    uv_cond_signal(&context->cond);\n    uv_mutex_unlock(&context->mutex);\n}\n\nstatic void EncoderEventThreadEntry(void *arg) {\n    GNEncoder::EventContext *context = reinterpret_cast<GNEncoder::EventContext *>(arg);\n    while (groove_encoder_buffer_peek(context->encoder, 1) > 0) {\n        uv_mutex_lock(&context->mutex);\n        if (context->emit_buffer_ok) {\n            context->emit_buffer_ok = false;\n            uv_async_send(&context->event_async);\n        }\n        uv_cond_wait(&context->cond, &context->mutex);\n        uv_mutex_unlock(&context->mutex);\n    }\n}\n\nclass EncoderAttachWorker : public Nan::AsyncWorker {\npublic:\n    EncoderAttachWorker(Nan::Callback *callback, GrooveEncoder *encoder, GroovePlaylist *playlist,\n            GNEncoder::EventContext *event_context,\n            String::Utf8Value *format_short_name,\n            String::Utf8Value *codec_short_name,\n            String::Utf8Value *filename,\n            String::Utf8Value *mime_type) :\n        Nan::AsyncWorker(callback)\n    {\n        this->encoder = encoder;\n        this->playlist = playlist;\n        this->event_context = event_context;\n        this->format_short_name = format_short_name;\n        this->codec_short_name = codec_short_name;\n        this->filename = filename;\n        this->mime_type = mime_type;\n    }\n    ~EncoderAttachWorker() {\n        delete format_short_name;\n        delete codec_short_name;\n        delete filename;\n        delete mime_type;\n    }\n\n    void Execute() {\n        encoder->format_short_name = format_short_name ? **format_short_name : NULL;\n        encoder->codec_short_name = codec_short_name ? **codec_short_name : NULL;\n        encoder->filename = filename ? **filename : NULL;\n        encoder->mime_type = mime_type ? **mime_type : NULL;\n\n        int err;\n        if ((err = groove_encoder_attach(encoder, playlist))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n\n        uv_cond_init(&event_context->cond);\n        uv_mutex_init(&event_context->mutex);\n\n        event_context->event_async.data = event_context;\n        uv_async_init(uv_default_loop(), &event_context->event_async, EncoderEventAsyncCb);\n\n        uv_thread_create(&event_context->event_thread, EncoderEventThreadEntry, event_context);\n    }\n\n    GrooveEncoder *encoder;\n    GroovePlaylist *playlist;\n    GNEncoder::EventContext *event_context;\n    String::Utf8Value *format_short_name;\n    String::Utf8Value *codec_short_name;\n    String::Utf8Value *filename;\n    String::Utf8Value *mime_type;\n};\n\nNAN_METHOD(GNEncoder::Create) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    GrooveEncoder *encoder = groove_encoder_create(get_groove());\n    Local<Object> instance = NewInstance(encoder)->ToObject();\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(instance);\n    EventContext *context = new EventContext;\n    gn_encoder->event_context = context;\n    context->emit_buffer_ok = true;\n    context->event_cb = new Nan::Callback(info[0].As<Function>());\n    context->encoder = encoder;\n\n    // set properties on the instance with default values from\n    // GrooveEncoder struct\n    Local<Array> layout = Nan::New<Array>();\n    for (int ch = 0; ch < encoder->target_audio_format.layout.channel_count; ch += 1) {\n        Nan::Set(layout, Nan::New<Number>(ch), \n            Nan::New<Number>(encoder->target_audio_format.layout.channels[ch]));\n    }\n\n    Local<Object> targetAudioFormat = Nan::New<Object>();\n    Nan::Set(targetAudioFormat, Nan::New<String>(\"sampleRate\").ToLocalChecked(),\n            Nan::New<Number>(encoder->target_audio_format.sample_rate));\n    Nan::Set(targetAudioFormat, Nan::New<String>(\"channelLayout\").ToLocalChecked(), layout);\n    Nan::Set(targetAudioFormat, Nan::New<String>(\"sampleFormat\").ToLocalChecked(),\n            Nan::New<Number>(encoder->target_audio_format.format));\n\n\n    Nan::Set(instance, Nan::New<String>(\"bitRate\").ToLocalChecked(), Nan::New<Number>(encoder->bit_rate));\n    Nan::Set(instance, Nan::New<String>(\"actualAudioFormat\").ToLocalChecked(), Nan::Null());\n    Nan::Set(instance, Nan::New<String>(\"targetAudioFormat\").ToLocalChecked(), targetAudioFormat);\n    Nan::Set(instance, Nan::New<String>(\"formatShortName\").ToLocalChecked(), Nan::Null());\n    Nan::Set(instance, Nan::New<String>(\"codecShortName\").ToLocalChecked(), Nan::Null());\n    Nan::Set(instance, Nan::New<String>(\"filename\").ToLocalChecked(), Nan::Null());\n    Nan::Set(instance, Nan::New<String>(\"mimeType\").ToLocalChecked(), Nan::Null());\n    Nan::Set(instance, Nan::New<String>(\"encodedBufferSize\").ToLocalChecked(), Nan::New<Number>(encoder->encoded_buffer_size));\n\n    info.GetReturnValue().Set(instance);\n}\n\nNAN_METHOD(GNEncoder::Attach) {\n    Nan::HandleScope scope;\n\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsObject()) {\n        Nan::ThrowTypeError(\"Expected object arg[0]\");\n        return;\n    }\n    if (info.Length() < 2 || !info[1]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[1]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());\n\n    Local<Object> instance = info.This();\n    Local<Value> targetAudioFormatValue = instance->Get(Nan::New<String>(\"targetAudioFormat\").ToLocalChecked());\n    if (!targetAudioFormatValue->IsObject()) {\n        Nan::ThrowTypeError(\"Expected targetAudioFormat to be an object\");\n        return;\n    }\n\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());\n    GrooveEncoder *encoder = gn_encoder->encoder;\n\n    // copy the properties from our instance to the encoder\n    Local<Value> formatShortName = instance->Get(Nan::New<String>(\"formatShortName\").ToLocalChecked());\n    String::Utf8Value *format_short_name;\n    if (formatShortName->IsNull() || formatShortName->IsUndefined()) {\n        format_short_name = NULL;\n    } else {\n        format_short_name = new String::Utf8Value(formatShortName->ToString());\n    }\n\n    Local<Value> codecShortName = instance->Get(Nan::New<String>(\"codecShortName\").ToLocalChecked());\n    String::Utf8Value *codec_short_name;\n    if (codecShortName->IsNull() || codecShortName->IsUndefined()) {\n        codec_short_name = NULL;\n    } else {\n        codec_short_name = new String::Utf8Value(codecShortName->ToString());\n    }\n\n    Local<Value> filenameStr = instance->Get(Nan::New<String>(\"filename\").ToLocalChecked());\n    String::Utf8Value *filename;\n    if (filenameStr->IsNull() || filenameStr->IsUndefined()) {\n        filename = NULL;\n    } else {\n        filename = new String::Utf8Value(filenameStr->ToString());\n    }\n\n    Local<Value> mimeType = instance->Get(Nan::New<String>(\"mimeType\").ToLocalChecked());\n    String::Utf8Value *mime_type;\n    if (mimeType->IsNull() || mimeType->IsUndefined()) {\n        mime_type = NULL;\n    } else {\n        mime_type = new String::Utf8Value(mimeType->ToString());\n    }\n\n    Local<Object> targetAudioFormat = targetAudioFormatValue->ToObject();\n\n    Local<Array> layout = Local<Array>::Cast(\n            targetAudioFormat->Get(Nan::New<String>(\"channelLayout\").ToLocalChecked()));\n\n    encoder->target_audio_format.layout.channel_count = layout->Length();\n    for (int ch = 0; ch < encoder->target_audio_format.layout.channel_count; ch += 1) {\n        Local<Value> channelId = layout->Get(Nan::New<Number>(ch));\n        encoder->target_audio_format.layout.channels[ch] = (SoundIoChannelId)(int)channelId->NumberValue();\n    }\n    double sample_fmt = targetAudioFormat->Get(Nan::New<String>(\"sampleFormat\").ToLocalChecked())->NumberValue();\n    encoder->target_audio_format.format = (SoundIoFormat)(int)sample_fmt;\n\n    Local<Value> sampleRate = targetAudioFormat->Get(Nan::New<String>(\"sampleRate\").ToLocalChecked());\n    double sample_rate = sampleRate->NumberValue();\n    encoder->target_audio_format.sample_rate = (int)sample_rate;\n\n    double bit_rate = instance->Get(Nan::New<String>(\"bitRate\").ToLocalChecked())->NumberValue();\n    encoder->bit_rate = (int)bit_rate;\n\n    double encoded_buffer_size = instance->Get(Nan::New<String>(\"encodedBufferSize\").ToLocalChecked())->NumberValue();\n    encoder->encoded_buffer_size = (int)encoded_buffer_size;\n\n    AsyncQueueWorker(new EncoderAttachWorker(callback, encoder, gn_playlist->playlist, gn_encoder->event_context,\n                format_short_name, codec_short_name, filename, mime_type));\n}\n\nclass EncoderDetachWorker : public Nan::AsyncWorker {\npublic:\n    EncoderDetachWorker(Nan::Callback *callback, GrooveEncoder *encoder,\n            GNEncoder::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->encoder = encoder;\n        this->event_context = event_context;\n    }\n    ~EncoderDetachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_encoder_detach(encoder))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n\n        uv_cond_signal(&event_context->cond);\n        uv_thread_join(&event_context->event_thread);\n        uv_cond_destroy(&event_context->cond);\n        uv_mutex_destroy(&event_context->mutex);\n        uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);\n    }\n\n    GrooveEncoder *encoder;\n    GNEncoder::EventContext *event_context;\n};\n\nNAN_METHOD(GNEncoder::Detach) {\n    Nan::HandleScope scope;\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n\n    GrooveEncoder *encoder = gn_encoder->encoder;\n\n    if (!encoder->playlist) {\n        Nan::ThrowTypeError(\"detach: not attached\");\n        return;\n    }\n\n    AsyncQueueWorker(new EncoderDetachWorker(callback, encoder, gn_encoder->event_context));\n}\n\nstatic void encoder_buffer_free(char *data, void *hint) {\n    GrooveBuffer *buffer = reinterpret_cast<GrooveBuffer*>(hint);\n    groove_buffer_unref(buffer);\n}\n\nNAN_METHOD(GNEncoder::GetBuffer) {\n    Nan::HandleScope scope;\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());\n    GrooveEncoder *encoder = gn_encoder->encoder;\n\n    GrooveBuffer *buffer;\n    int buf_result = groove_encoder_buffer_get(encoder, &buffer, 0);\n\n    uv_mutex_lock(&gn_encoder->event_context->mutex);\n    gn_encoder->event_context->emit_buffer_ok = true;\n    uv_cond_signal(&gn_encoder->event_context->cond);\n    uv_mutex_unlock(&gn_encoder->event_context->mutex);\n\n    switch (buf_result) {\n        case GROOVE_BUFFER_YES: {\n            Local<Object> object = Nan::New<Object>();\n\n            Nan::MaybeLocal<Object> bufferObject = Nan::NewBuffer(\n                    reinterpret_cast<char*>(buffer->data[0]), buffer->size,\n                    encoder_buffer_free, buffer);\n            Nan::Set(object, Nan::New<String>(\"buffer\").ToLocalChecked(), bufferObject.ToLocalChecked());\n\n            if (buffer->item) {\n                Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(),\n                        GNPlaylistItem::NewInstance(buffer->item));\n            } else {\n                Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n            }\n\n            Nan::Set(object, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(buffer->pos));\n            Nan::Set(object, Nan::New<String>(\"pts\").ToLocalChecked(), Nan::New<Number>(buffer->pts));\n\n            info.GetReturnValue().Set(object);\n            break;\n        }\n        case GROOVE_BUFFER_END: {\n            Local<Object> object = Nan::New<Object>();\n\n            Nan::Set(object, Nan::New<String>(\"buffer\").ToLocalChecked(), Nan::Null());\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n            Nan::Set(object, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::Null());\n            Nan::Set(object, Nan::New<String>(\"pts\").ToLocalChecked(), Nan::Null());\n\n            info.GetReturnValue().Set(object);\n            break;\n        }\n        default:\n            info.GetReturnValue().Set(Nan::Null());\n    }\n}\n\nNAN_METHOD(GNEncoder::Position) {\n    Nan::HandleScope scope;\n\n    GNEncoder *gn_encoder = node::ObjectWrap::Unwrap<GNEncoder>(info.This());\n    GrooveEncoder *encoder = gn_encoder->encoder;\n\n    GroovePlaylistItem *item;\n    double pos;\n    groove_encoder_position(encoder, &item, &pos);\n\n    Local<Object> obj = Nan::New<Object>();\n    Nan::Set(obj, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(pos));\n    if (item) {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(item));\n    } else {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n    }\n\n    info.GetReturnValue().Set(obj);\n}\n"
  },
  {
    "path": "src/encoder.h",
    "content": "#ifndef GN_ENCODER_H\n#define GN_ENCODER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/encoder.h>\n\nclass GNEncoder : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GrooveEncoder *encoder);\n\n        static NAN_METHOD(Create);\n\n        struct EventContext {\n            uv_thread_t event_thread;\n            uv_async_t event_async;\n            uv_cond_t cond;\n            uv_mutex_t mutex;\n            GrooveEncoder *encoder;\n            Nan::Callback *event_cb;\n            bool emit_buffer_ok;\n        };\n\n        GrooveEncoder *encoder;\n        EventContext *event_context;\n    private:\n        GNEncoder();\n        ~GNEncoder();\n\n        static NAN_METHOD(New);\n\n        static NAN_GETTER(GetActualAudioFormat);\n\n        static NAN_METHOD(Attach);\n        static NAN_METHOD(Detach);\n        static NAN_METHOD(GetBuffer);\n        static NAN_METHOD(Position);\n};\n\n#endif\n"
  },
  {
    "path": "src/file.cc",
    "content": "#include <node.h>\n#include \"file.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNFile::GNFile() {};\nGNFile::~GNFile() {};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNFile::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GrooveFile\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(1);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"filename\").ToLocalChecked(), GetFilename);\n    Nan::SetAccessor(proto, Nan::New<String>(\"dirty\").ToLocalChecked(), GetDirty);\n    Nan::SetAccessor(proto, Nan::New<String>(\"id\").ToLocalChecked(), GetId);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"close\", Close);\n    Nan::SetPrototypeMethod(tpl, \"getMetadata\", GetMetadata);\n    Nan::SetPrototypeMethod(tpl, \"setMetadata\", SetMetadata);\n    Nan::SetPrototypeMethod(tpl, \"metadata\", Metadata);\n    Nan::SetPrototypeMethod(tpl, \"shortNames\", ShortNames);\n    Nan::SetPrototypeMethod(tpl, \"save\", Save);\n    Nan::SetPrototypeMethod(tpl, \"duration\", Duration);\n    Nan::SetPrototypeMethod(tpl, \"overrideDuration\", OverrideDuration);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNFile::New) {\n    Nan::HandleScope scope;\n    assert(info.IsConstructCall());\n\n    GNFile *obj = new GNFile();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNFile::NewInstance(GrooveFile *file) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(instance);\n    gn_file->file = file;\n\n    return scope.Escape(instance);\n}\n\nNAN_GETTER(GNFile::GetDirty) {\n    Nan::HandleScope scope;\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n    info.GetReturnValue().Set(Nan::New<Boolean>(gn_file->file->dirty));\n}\n\nNAN_GETTER(GNFile::GetId) {\n    Nan::HandleScope scope;\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%p\", gn_file->file);\n    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());\n}\n\nNAN_GETTER(GNFile::GetFilename) {\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n    info.GetReturnValue().Set(Nan::New<String>(gn_file->file->filename).ToLocalChecked());\n}\n\nNAN_METHOD(GNFile::GetMetadata) {\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsString()) {\n        Nan::ThrowTypeError(\"Expected string arg[0]\");\n        return;\n    }\n\n    int flags = 0;\n    if (info.Length() >= 2) {\n        if (!info[1]->IsNumber()) {\n            Nan::ThrowTypeError(\"Expected number arg[1]\");\n            return;\n        }\n        flags = (int)info[1]->NumberValue();\n    }\n\n    String::Utf8Value key_str(info[0]->ToString());\n    GrooveTag *tag = groove_file_metadata_get(gn_file->file, *key_str, NULL, flags);\n    if (tag) {\n        info.GetReturnValue().Set(Nan::New<String>(groove_tag_value(tag)).ToLocalChecked());\n        return;\n    }\n\n    info.GetReturnValue().Set(Nan::Null());\n}\n\nNAN_METHOD(GNFile::SetMetadata) {\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsString()) {\n        Nan::ThrowTypeError(\"Expected string arg[0]\");\n        return;\n    }\n\n    if (info.Length() < 2 || !info[0]->IsString()) {\n        Nan::ThrowTypeError(\"Expected string arg[1]\");\n        return;\n    }\n\n    int flags = 0;\n    if (info.Length() >= 3) {\n        if (!info[2]->IsNumber()) {\n            Nan::ThrowTypeError(\"Expected number arg[2]\");\n            return;\n        }\n        flags = (int)info[2]->NumberValue();\n    }\n\n    String::Utf8Value key_str(info[0]->ToString());\n    String::Utf8Value val_str(info[1]->ToString());\n    int err = groove_file_metadata_set(gn_file->file, *key_str, *val_str, flags);\n    if (err < 0) {\n        Nan::ThrowTypeError(\"set metadata failed\");\n        return;\n    }\n    return;\n}\n\nNAN_METHOD(GNFile::OverrideDuration) {\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsNumber()) {\n        Nan::ThrowTypeError(\"Expected number arg[0]\");\n        return;\n    }\n\n    double duration = info[0]->NumberValue();\n\n    gn_file->file->override_duration = duration;\n}\n\nNAN_METHOD(GNFile::Metadata) {\n    Nan::HandleScope scope;\n\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n    Local<Object> metadata = Nan::New<Object>();\n\n    GrooveTag *tag = NULL;\n    while ((tag = groove_file_metadata_get(gn_file->file, \"\", tag, 0))) {\n        Nan::Set(metadata, Nan::New<String>(groove_tag_key(tag)).ToLocalChecked(),\n                Nan::New<String>(groove_tag_value(tag)).ToLocalChecked());\n    }\n\n    info.GetReturnValue().Set(metadata);\n}\n\nNAN_METHOD(GNFile::ShortNames) {\n    Nan::HandleScope scope;\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n    info.GetReturnValue().Set(Nan::New<String>(groove_file_short_names(gn_file->file)).ToLocalChecked());\n}\n\nNAN_METHOD(GNFile::Duration) {\n    Nan::HandleScope scope;\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n    info.GetReturnValue().Set(Nan::New<Number>(groove_file_duration(gn_file->file)));\n}\n\nclass CloseWorker : public Nan::AsyncWorker {\npublic:\n    CloseWorker(Nan::Callback *callback, GrooveFile *file) : Nan::AsyncWorker(callback) {\n        this->file = file;\n    }\n    ~CloseWorker() {}\n\n    void Execute() {\n        if (file) {\n            groove_file_destroy(file);\n        } else {\n            SetErrorMessage(\"file already closed\");\n        }\n    }\n\n    GrooveFile *file;\n};\n\nNAN_METHOD(GNFile::Close) {\n    Nan::HandleScope scope;\n\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n    AsyncQueueWorker(new CloseWorker(callback, gn_file->file));\n\n    gn_file->file = NULL;\n}\n\nclass OpenWorker : public Nan::AsyncWorker {\npublic:\n    OpenWorker(Nan::Callback *callback, String::Utf8Value *filename) : Nan::AsyncWorker(callback) {\n        this->filename = filename;\n    }\n    ~OpenWorker() {\n        delete filename;\n    }\n\n    void Execute() {\n        file = groove_file_create(get_groove());\n        if (!file) {\n            SetErrorMessage(groove_strerror(GrooveErrorNoMem));\n            return;\n        }\n        int err;\n        if ((err = groove_file_open(file, **filename, **filename))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n    }\n\n    void HandleOKCallback() {\n        Nan::HandleScope scope;\n        Local<Value> argv[] = {Nan::Null(), GNFile::NewInstance(file)};\n        callback->Call(2, argv);\n    }\n\n    GrooveFile *file;\n    String::Utf8Value *filename;\n};\n\nNAN_METHOD(GNFile::Open) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsString()) {\n        Nan::ThrowTypeError(\"Expected string arg[0]\");\n        return;\n    }\n    if (info.Length() < 2 || !info[1]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[1]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());\n    String::Utf8Value *filename = new String::Utf8Value(info[0]->ToString());\n    AsyncQueueWorker(new OpenWorker(callback, filename));\n}\n\nclass SaveWorker : public Nan::AsyncWorker {\npublic:\n    SaveWorker(Nan::Callback *callback, GrooveFile *file) : Nan::AsyncWorker(callback) {\n        this->file = file;\n    }\n    ~SaveWorker() { }\n\n    void Execute() {\n        int err;\n        if ((err = groove_file_save(file))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n    }\n\n    GrooveFile *file;\n};\n\nNAN_METHOD(GNFile::Save) {\n    Nan::HandleScope scope;\n\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n    AsyncQueueWorker(new SaveWorker(callback, gn_file->file));\n}\n"
  },
  {
    "path": "src/file.h",
    "content": "#ifndef GN_FILE_H\n#define GN_FILE_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNFile : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GrooveFile *file);\n\n        static NAN_METHOD(Open);\n\n        GrooveFile *file;\n    private:\n        GNFile();\n        ~GNFile();\n\n        static NAN_METHOD(New);\n\n        static NAN_GETTER(GetDirty);\n        static NAN_GETTER(GetId);\n        static NAN_GETTER(GetFilename);\n\n        static NAN_METHOD(Close);\n        static NAN_METHOD(Duration);\n        static NAN_METHOD(GetMetadata);\n        static NAN_METHOD(SetMetadata);\n        static NAN_METHOD(Metadata);\n        static NAN_METHOD(ShortNames);\n        static NAN_METHOD(Save);\n        static NAN_METHOD(OverrideDuration);\n};\n\n#endif\n"
  },
  {
    "path": "src/fingerprinter.cc",
    "content": "#include \"fingerprinter.h\"\n#include \"playlist_item.h\"\n#include \"playlist.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNFingerprinter::GNFingerprinter() {};\nGNFingerprinter::~GNFingerprinter() {\n    groove_fingerprinter_destroy(printer);\n    delete event_context->event_cb;\n    delete event_context;\n};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNFingerprinter::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GrooveFingerprinter\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(2);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"attach\", Attach);\n    Nan::SetPrototypeMethod(tpl, \"detach\", Detach);\n    Nan::SetPrototypeMethod(tpl, \"getInfo\", GetInfo);\n    Nan::SetPrototypeMethod(tpl, \"position\", Position);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNFingerprinter::New) {\n    Nan::HandleScope scope;\n\n    GNFingerprinter *obj = new GNFingerprinter();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNFingerprinter::NewInstance(GrooveFingerprinter *printer) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);\n    gn_printer->printer = printer;\n\n    return scope.Escape(instance);\n}\n\nNAN_METHOD(GNFingerprinter::Create) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    GrooveFingerprinter *printer = groove_fingerprinter_create(get_groove());\n    if (!printer) {\n        Nan::ThrowTypeError(\"unable to create fingerprinter\");\n        return;\n    }\n\n    // set properties on the instance with default values from\n    // GrooveFingerprinter struct\n    Local<Object> instance = GNFingerprinter::NewInstance(printer)->ToObject();\n    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(instance);\n    EventContext *context = new EventContext;\n    gn_printer->event_context = context;\n    context->event_cb = new Nan::Callback(info[0].As<Function>());\n    context->printer = printer;\n\n\n    Nan::Set(instance, Nan::New<String>(\"infoQueueSize\").ToLocalChecked(),\n            Nan::New<Number>(printer->info_queue_size));\n\n    info.GetReturnValue().Set(instance);\n}\n\nNAN_METHOD(GNFingerprinter::Position) {\n    Nan::HandleScope scope;\n\n    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());\n    GrooveFingerprinter *printer = gn_printer->printer;\n\n    GroovePlaylistItem *item;\n    double pos;\n    groove_fingerprinter_position(printer, &item, &pos);\n\n    Local<Object> obj = Nan::New<Object>();\n    Nan::Set(obj, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(pos));\n    if (item) {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(item));\n    } else {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n    }\n\n    info.GetReturnValue().Set(obj);\n}\n\nNAN_METHOD(GNFingerprinter::GetInfo) {\n    Nan::HandleScope scope;\n\n    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());\n    GrooveFingerprinter *printer = gn_printer->printer;\n\n    GrooveFingerprinterInfo print_info;\n    if (groove_fingerprinter_info_get(printer, &print_info, 0) == 1) {\n        Local<Object> object = Nan::New<Object>();\n\n        if (print_info.fingerprint) {\n            Local<Array> int_list = Nan::New<Array>();\n            for (int i = 0; i < print_info.fingerprint_size; i += 1) {\n                Nan::Set(int_list, Nan::New<Number>(i), Nan::New<Number>(print_info.fingerprint[i]));\n            }\n            Nan::Set(object, Nan::New<String>(\"fingerprint\").ToLocalChecked(), int_list);\n        } else {\n            Nan::Set(object, Nan::New<String>(\"fingerprint\").ToLocalChecked(), Nan::Null());\n        }\n        Nan::Set(object, Nan::New<String>(\"duration\").ToLocalChecked(), Nan::New<Number>(print_info.duration));\n\n        if (print_info.item) {\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(print_info.item));\n        } else {\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n        }\n\n        groove_fingerprinter_free_info(&print_info);\n\n        info.GetReturnValue().Set(object);\n    } else {\n        info.GetReturnValue().Set(Nan::Null());\n    }\n}\n\nstatic void PrinterEventAsyncCb(uv_async_t *handle) {\n    Nan::HandleScope scope;\n\n    GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(handle->data);\n\n    // call callback signaling that there is info ready\n\n    const unsigned argc = 1;\n    Local<Value> argv[argc];\n    argv[0] = Nan::Null();\n\n    TryCatch try_catch;\n    context->event_cb->Call(argc, argv);\n\n    if (try_catch.HasCaught()) {\n        node::FatalException(try_catch);\n    }\n\n    uv_mutex_lock(&context->mutex);\n    uv_cond_signal(&context->cond);\n    uv_mutex_unlock(&context->mutex);\n}\n\nstatic void PrinterEventThreadEntry(void *arg) {\n    GNFingerprinter::EventContext *context = reinterpret_cast<GNFingerprinter::EventContext *>(arg);\n    while (groove_fingerprinter_info_peek(context->printer, 1) > 0) {\n        uv_mutex_lock(&context->mutex);\n        uv_async_send(&context->event_async);\n        uv_cond_wait(&context->cond, &context->mutex);\n        uv_mutex_unlock(&context->mutex);\n    }\n}\n\nclass PrinterAttachWorker : public Nan::AsyncWorker {\npublic:\n    PrinterAttachWorker(Nan::Callback *callback, GrooveFingerprinter *printer, GroovePlaylist *playlist,\n            GNFingerprinter::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->printer = printer;\n        this->playlist = playlist;\n        this->event_context = event_context;\n    }\n    ~PrinterAttachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_fingerprinter_attach(printer, playlist))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n\n        uv_cond_init(&event_context->cond);\n        uv_mutex_init(&event_context->mutex);\n\n        event_context->event_async.data = event_context;\n        uv_async_init(uv_default_loop(), &event_context->event_async, PrinterEventAsyncCb);\n\n        uv_thread_create(&event_context->event_thread, PrinterEventThreadEntry, event_context);\n    }\n\n    GrooveFingerprinter *printer;\n    GroovePlaylist *playlist;\n    GNFingerprinter::EventContext *event_context;\n};\n\nNAN_METHOD(GNFingerprinter::Attach) {\n    Nan::HandleScope scope;\n\n    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsObject()) {\n        Nan::ThrowTypeError(\"Expected object arg[0]\");\n        return;\n    }\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());\n\n    if (info.Length() < 2 || !info[1]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[1]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());\n\n    Local<Object> instance = info.This();\n    GrooveFingerprinter *printer = gn_printer->printer;\n\n    // copy the properties from our instance to the player\n    printer->info_queue_size = (int)instance->Get(Nan::New<String>(\"infoQueueSize\").ToLocalChecked())->NumberValue();\n\n    AsyncQueueWorker(new PrinterAttachWorker(callback, printer, gn_playlist->playlist, gn_printer->event_context));\n}\n\nclass PrinterDetachWorker : public Nan::AsyncWorker {\npublic:\n    PrinterDetachWorker(Nan::Callback *callback, GrooveFingerprinter *printer,\n            GNFingerprinter::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->printer = printer;\n        this->event_context = event_context;\n    }\n    ~PrinterDetachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_fingerprinter_detach(printer))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n        uv_cond_signal(&event_context->cond);\n        uv_thread_join(&event_context->event_thread);\n        uv_cond_destroy(&event_context->cond);\n        uv_mutex_destroy(&event_context->mutex);\n        uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);\n    }\n\n    GrooveFingerprinter *printer;\n    GNFingerprinter::EventContext *event_context;\n};\n\nNAN_METHOD(GNFingerprinter::Detach) {\n    Nan::HandleScope scope;\n    GNFingerprinter *gn_printer = node::ObjectWrap::Unwrap<GNFingerprinter>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n\n    AsyncQueueWorker(new PrinterDetachWorker(callback, gn_printer->printer, gn_printer->event_context));\n}\n\nNAN_METHOD(GNFingerprinter::Encode) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsArray()) {\n        Nan::ThrowTypeError(\"Expected Array arg[0]\");\n        return;\n    }\n\n    Local<Array> int_list = Local<Array>::Cast(info[0]);\n    int len = int_list->Length();\n    uint32_t *raw_fingerprint = new uint32_t[len];\n    for (int i = 0; i < len; i += 1) {\n        double val = int_list->Get(Nan::New<Number>(i))->NumberValue();\n        raw_fingerprint[i] = (uint32_t)val;\n    }\n    char *fingerprint;\n    groove_fingerprinter_encode(raw_fingerprint, len, &fingerprint);\n    delete[] raw_fingerprint;\n    Local<String> js_fingerprint = Nan::New<String>(fingerprint).ToLocalChecked();\n    groove_fingerprinter_dealloc(fingerprint);\n\n    info.GetReturnValue().Set(js_fingerprint);\n}\n\nNAN_METHOD(GNFingerprinter::Decode) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsString()) {\n        Nan::ThrowTypeError(\"Expected String arg[0]\");\n        return;\n    }\n\n    String::Utf8Value utf8fingerprint(info[0]->ToString());\n    char *fingerprint = *utf8fingerprint;\n\n    uint32_t *raw_fingerprint;\n    int raw_fingerprint_len;\n    groove_fingerprinter_decode(fingerprint, &raw_fingerprint, &raw_fingerprint_len);\n    Local<Array> int_list = Nan::New<Array>();\n\n    for (int i = 0; i < raw_fingerprint_len; i += 1) {\n        Nan::Set(int_list, Nan::New<Number>(i), Nan::New<Number>(raw_fingerprint[i]));\n    }\n    groove_fingerprinter_dealloc(raw_fingerprint);\n\n    info.GetReturnValue().Set(int_list);\n}\n"
  },
  {
    "path": "src/fingerprinter.h",
    "content": "#ifndef GN_FINGERPRINTER_H\n#define GN_FINGERPRINTER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/fingerprinter.h>\n\nclass GNFingerprinter : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GrooveFingerprinter *printer);\n\n        static NAN_METHOD(Create);\n\n        static NAN_METHOD(Encode);\n        static NAN_METHOD(Decode);\n\n        struct EventContext {\n            uv_thread_t event_thread;\n            uv_async_t event_async;\n            uv_cond_t cond;\n            uv_mutex_t mutex;\n            GrooveFingerprinter *printer;\n            Nan::Callback *event_cb;\n        };\n\n        EventContext *event_context;\n        GrooveFingerprinter *printer;\n\n    private:\n        GNFingerprinter();\n        ~GNFingerprinter();\n\n        static NAN_METHOD(New);\n\n        static NAN_METHOD(Attach);\n        static NAN_METHOD(Detach);\n        static NAN_METHOD(GetInfo);\n        static NAN_METHOD(Position);\n};\n\n#endif\n"
  },
  {
    "path": "src/groove.cc",
    "content": "#include <node.h>\n#include <nan.h>\n#include <cstdlib>\n#include \"groove.h\"\n#include \"file.h\"\n#include \"player.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"loudness_detector.h\"\n#include \"fingerprinter.h\"\n#include \"waveform_builder.h\"\n#include \"encoder.h\"\n#include \"device.h\"\n\nusing namespace v8;\n\nstatic SoundIo *soundio = NULL;\nstatic Groove *groove = NULL;\n\nGroove *get_groove() {\n    return groove;\n}\n\nNAN_METHOD(SetLogging) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsNumber()) {\n        Nan::ThrowTypeError(\"Expected 1 number argument\");\n        return;\n    }\n    groove_set_logging(info[0]->NumberValue());\n}\n\nNAN_METHOD(ConnectSoundBackend) {\n    SoundIoBackend backend = SoundIoBackendNone;\n    if (info.Length() == 1) {\n        if (!info[0]->IsNumber()) {\n            Nan::ThrowTypeError(\"Expected 0 or 1 args\");\n            return;\n        }\n        backend = (SoundIoBackend)(int)info[0]->NumberValue();\n    } else if (info.Length() > 1) {\n        Nan::ThrowTypeError(\"Expected 0 or 1 args\");\n        return;\n    }\n\n    if (soundio->current_backend != SoundIoBackendNone)\n        soundio_disconnect(soundio);\n\n    int err = (backend == SoundIoBackendNone) ?\n        soundio_connect(soundio) : soundio_connect_backend(soundio, backend);\n\n    if (err) {\n        Nan::ThrowError(soundio_strerror(err));\n        return;\n    }\n}\n\nNAN_METHOD(DisconnectSoundBackend) {\n    if (soundio->current_backend != SoundIoBackendNone)\n        soundio_disconnect(soundio);\n}\n\nNAN_METHOD(GetDevices) {\n    Nan::HandleScope scope;\n\n    if (soundio->current_backend == SoundIoBackendNone) {\n        Nan::ThrowError(\"no backend connected\");\n        return;\n    }\n\n    soundio_flush_events(soundio);\n\n    int output_count = soundio_output_device_count(soundio);\n    int default_output = soundio_default_output_device_index(soundio);\n\n    Local<Array> deviceList = Nan::New<Array>();\n\n    for (int i = 0; i < output_count; i += 1) {\n        struct SoundIoDevice *device = soundio_get_output_device(soundio, i);\n        Local<Value> deviceObject = GNDevice::NewInstance(device);\n        Nan::Set(deviceList, Nan::New<Number>(i), deviceObject);\n    }\n\n    Local<Object> ret_value = Nan::New<Object>();\n\n    Nan::Set(ret_value, Nan::New<String>(\"list\").ToLocalChecked(), deviceList);\n    Nan::Set(ret_value, Nan::New<String>(\"defaultIndex\").ToLocalChecked(), Nan::New<Number>(default_output));\n\n    info.GetReturnValue().Set(ret_value);\n}\n\nNAN_METHOD(GetVersion) {\n    Nan::HandleScope scope;\n\n    Local<Object> version = Nan::New<Object>();\n    Nan::Set(version, Nan::New<String>(\"major\").ToLocalChecked(), Nan::New<Number>(groove_version_major()));\n    Nan::Set(version, Nan::New<String>(\"minor\").ToLocalChecked(), Nan::New<Number>(groove_version_minor()));\n    Nan::Set(version, Nan::New<String>(\"patch\").ToLocalChecked(), Nan::New<Number>(groove_version_patch()));\n\n    info.GetReturnValue().Set(version);\n}\n\ntemplate <typename target_t>\nstatic void SetProperty(target_t obj, const char* name, double n) {\n    Nan::Set(obj, Nan::New<String>(name).ToLocalChecked(), Nan::New<Number>(n));\n}\n\ntemplate <typename target_t, typename FNPTR>\nstatic void SetMethod(target_t obj, const char* name, FNPTR fn) {\n    Nan::Set(obj, Nan::New<String>(name).ToLocalChecked(),\n            Nan::GetFunction(Nan::New<FunctionTemplate>(fn)).ToLocalChecked());\n}\n\nstatic void cleanup(void) {\n    groove_destroy(groove);\n    soundio_destroy(soundio);\n}\n\nNAN_MODULE_INIT(Initialize) {\n    int err;\n\n    soundio = soundio_create();\n    if (!soundio) {\n        fprintf(stderr, \"unable to initialize libsoundio: out of memory\\n\");\n        abort();\n    }\n\n    if ((err = groove_create(&groove))) {\n        fprintf(stderr, \"unable to initialize libgroove: %s\\n\", groove_strerror(err));\n        abort();\n    }\n    atexit(cleanup);\n\n    GNFile::Init();\n    GNPlayer::Init();\n    GNPlaylist::Init();\n    GNPlaylistItem::Init();\n    GNLoudnessDetector::Init();\n    GNEncoder::Init();\n    GNFingerprinter::Init();\n    GNDevice::Init();\n    GNWaveformBuilder::Init();\n\n    SetProperty(target, \"LOG_QUIET\", GROOVE_LOG_QUIET);\n    SetProperty(target, \"LOG_ERROR\", GROOVE_LOG_ERROR);\n    SetProperty(target, \"LOG_WARNING\", GROOVE_LOG_WARNING);\n    SetProperty(target, \"LOG_INFO\", GROOVE_LOG_INFO);\n\n    SetProperty(target, \"TAG_MATCH_CASE\", GROOVE_TAG_MATCH_CASE);\n    SetProperty(target, \"TAG_DONT_OVERWRITE\", GROOVE_TAG_DONT_OVERWRITE);\n    SetProperty(target, \"TAG_APPEND\", GROOVE_TAG_APPEND);\n\n    SetProperty(target, \"EVERY_SINK_FULL\", GrooveFillModeEverySinkFull);\n    SetProperty(target, \"ANY_SINK_FULL\", GrooveFillModeAnySinkFull);\n\n    SetProperty(target, \"_EVENT_NOWPLAYING\", GROOVE_EVENT_NOWPLAYING);\n    SetProperty(target, \"_EVENT_BUFFERUNDERRUN\", GROOVE_EVENT_BUFFERUNDERRUN);\n    SetProperty(target, \"_EVENT_DEVICE_CLOSED\", GROOVE_EVENT_DEVICE_CLOSED);\n    SetProperty(target, \"_EVENT_DEVICE_OPENED\", GROOVE_EVENT_DEVICE_OPENED);\n    SetProperty(target, \"_EVENT_DEVICE_OPEN_ERROR\", GROOVE_EVENT_DEVICE_OPEN_ERROR);\n    SetProperty(target, \"_EVENT_END_OF_PLAYLIST\", GROOVE_EVENT_END_OF_PLAYLIST);\n    SetProperty(target, \"_EVENT_WAKEUP\", GROOVE_EVENT_WAKEUP);\n\n    SetProperty(target, \"BACKEND_JACK\", SoundIoBackendJack);\n    SetProperty(target, \"BACKEND_PULSEAUDIO\", SoundIoBackendPulseAudio);\n    SetProperty(target, \"BACKEND_ALSA\", SoundIoBackendAlsa);\n    SetProperty(target, \"BACKEND_COREAUDIO\", SoundIoBackendCoreAudio);\n    SetProperty(target, \"BACKEND_WASAPI\", SoundIoBackendWasapi);\n    SetProperty(target, \"BACKEND_DUMMY\", SoundIoBackendDummy);\n\n    SetMethod(target, \"setLogging\", SetLogging);\n    SetMethod(target, \"getDevices\", GetDevices);\n    SetMethod(target, \"connectSoundBackend\", ConnectSoundBackend);\n    SetMethod(target, \"disconnectSoundBackend\", DisconnectSoundBackend);\n    SetMethod(target, \"getVersion\", GetVersion);\n    SetMethod(target, \"open\", GNFile::Open);\n    SetMethod(target, \"createPlayer\", GNPlayer::Create);\n    SetMethod(target, \"createPlaylist\", GNPlaylist::Create);\n    SetMethod(target, \"createLoudnessDetector\", GNLoudnessDetector::Create);\n    SetMethod(target, \"createEncoder\", GNEncoder::Create);\n    SetMethod(target, \"createFingerprinter\", GNFingerprinter::Create);\n    SetMethod(target, \"createWaveformBuilder\", GNWaveformBuilder::Create);\n\n    SetMethod(target, \"encodeFingerprint\", GNFingerprinter::Encode);\n    SetMethod(target, \"decodeFingerprint\", GNFingerprinter::Decode);\n}\n\nNODE_MODULE(groove, Initialize)\n"
  },
  {
    "path": "src/groove.h",
    "content": "#ifndef GN_GROOVE_H\n#define GN_GROOVE_H\n\n#include <groove/groove.h>\n\nGroove *get_groove();\n\n#endif\n"
  },
  {
    "path": "src/loudness_detector.cc",
    "content": "#include \"loudness_detector.h\"\n#include \"playlist_item.h\"\n#include \"playlist.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNLoudnessDetector::GNLoudnessDetector() {};\nGNLoudnessDetector::~GNLoudnessDetector() {\n    groove_loudness_detector_destroy(detector);\n    delete event_context->event_cb;\n    delete event_context;\n};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNLoudnessDetector::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GrooveLoudnessDetector\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(1);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"attach\", Attach);\n    Nan::SetPrototypeMethod(tpl, \"detach\", Detach);\n    Nan::SetPrototypeMethod(tpl, \"getInfo\", GetInfo);\n    Nan::SetPrototypeMethod(tpl, \"position\", Position);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNLoudnessDetector::New) {\n    Nan::HandleScope scope;\n\n    GNLoudnessDetector *obj = new GNLoudnessDetector();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNLoudnessDetector::NewInstance(GrooveLoudnessDetector *detector) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(instance);\n    gn_detector->detector = detector;\n\n    return scope.Escape(instance);\n}\n\nNAN_METHOD(GNLoudnessDetector::Create) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    GrooveLoudnessDetector *detector = groove_loudness_detector_create(get_groove());\n    if (!detector) {\n        Nan::ThrowTypeError(\"unable to create loudness detector\");\n        return;\n    }\n\n    // set properties on the instance with default values from\n    // GrooveLoudnessDetector struct\n    Local<Object> instance = GNLoudnessDetector::NewInstance(detector)->ToObject();\n    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(instance);\n    EventContext *context = new EventContext;\n    gn_detector->event_context = context;\n    context->event_cb = new Nan::Callback(info[0].As<Function>());\n    context->detector = detector;\n\n    Nan::Set(instance, Nan::New<String>(\"infoQueueSize\").ToLocalChecked(),\n            Nan::New<Number>(detector->info_queue_size));\n    Nan::Set(instance, Nan::New<String>(\"disableAlbum\").ToLocalChecked(),\n            Nan::New<Boolean>(detector->disable_album));\n\n    info.GetReturnValue().Set(instance);\n}\n\nNAN_METHOD(GNLoudnessDetector::Position) {\n    Nan::HandleScope scope;\n\n    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());\n    GrooveLoudnessDetector *detector = gn_detector->detector;\n\n    GroovePlaylistItem *item;\n    double pos;\n    groove_loudness_detector_position(detector, &item, &pos);\n\n    Local<Object> obj = Nan::New<Object>();\n    Nan::Set(obj, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(pos));\n    if (item) {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(item));\n    } else {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n    }\n    info.GetReturnValue().Set(obj);\n}\n\nNAN_METHOD(GNLoudnessDetector::GetInfo) {\n    Nan::HandleScope scope;\n\n    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());\n    GrooveLoudnessDetector *detector = gn_detector->detector;\n\n    GrooveLoudnessDetectorInfo loudness_info;\n    if (groove_loudness_detector_info_get(detector, &loudness_info, 0) == 1) {\n        Local<Object> object = Nan::New<Object>();\n\n        Nan::Set(object, Nan::New<String>(\"loudness\").ToLocalChecked(), Nan::New<Number>(loudness_info.loudness));\n        Nan::Set(object, Nan::New<String>(\"peak\").ToLocalChecked(), Nan::New<Number>(loudness_info.peak));\n        Nan::Set(object, Nan::New<String>(\"duration\").ToLocalChecked(), Nan::New<Number>(loudness_info.duration));\n\n        if (loudness_info.item) {\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(loudness_info.item));\n        } else {\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n        }\n\n        info.GetReturnValue().Set(object);\n    } else {\n        info.GetReturnValue().Set(Nan::Null());\n    }\n}\n\nstatic void EventAsyncCb(uv_async_t *handle) {\n    Nan::HandleScope scope;\n\n    GNLoudnessDetector::EventContext *context = reinterpret_cast<GNLoudnessDetector::EventContext *>(handle->data);\n\n    // call callback signaling that there is info ready\n\n    const unsigned argc = 1;\n    Local<Value> argv[argc];\n    argv[0] = Nan::Null();\n\n    TryCatch try_catch;\n    context->event_cb->Call(argc, argv);\n\n    if (try_catch.HasCaught()) {\n        node::FatalException(try_catch);\n    }\n\n    uv_mutex_lock(&context->mutex);\n    uv_cond_signal(&context->cond);\n    uv_mutex_unlock(&context->mutex);\n}\n\nstatic void EventThreadEntry(void *arg) {\n    GNLoudnessDetector::EventContext *context = reinterpret_cast<GNLoudnessDetector::EventContext *>(arg);\n    while (groove_loudness_detector_info_peek(context->detector, 1) > 0) {\n        uv_mutex_lock(&context->mutex);\n        uv_async_send(&context->event_async);\n        uv_cond_wait(&context->cond, &context->mutex);\n        uv_mutex_unlock(&context->mutex);\n    }\n}\n\nclass DetectorAttachWorker : public Nan::AsyncWorker {\npublic:\n    DetectorAttachWorker(Nan::Callback *callback, GrooveLoudnessDetector *detector, GroovePlaylist *playlist,\n            GNLoudnessDetector::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->detector = detector;\n        this->playlist = playlist;\n        this->event_context = event_context;\n    }\n    ~DetectorAttachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_loudness_detector_attach(detector, playlist))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n\n        uv_cond_init(&event_context->cond);\n        uv_mutex_init(&event_context->mutex);\n\n        event_context->event_async.data = event_context;\n        uv_async_init(uv_default_loop(), &event_context->event_async, EventAsyncCb);\n\n        uv_thread_create(&event_context->event_thread, EventThreadEntry, event_context);\n    }\n\n    GrooveLoudnessDetector *detector;\n    GroovePlaylist *playlist;\n    GNLoudnessDetector::EventContext *event_context;\n};\n\nNAN_METHOD(GNLoudnessDetector::Attach) {\n    Nan::HandleScope scope;\n\n    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsObject()) {\n        Nan::ThrowTypeError(\"Expected object arg[0]\");\n        return;\n    }\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());\n\n    if (info.Length() < 2 || !info[1]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[1]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());\n\n    Local<Object> instance = info.This();\n\n    GrooveLoudnessDetector *detector = gn_detector->detector;\n\n    // copy the properties from our instance to the player\n    detector->info_queue_size = (int)instance->Get(Nan::New<String>(\"infoQueueSize\").ToLocalChecked())->NumberValue();\n    detector->disable_album = (int)instance->Get(Nan::New<String>(\"disableAlbum\").ToLocalChecked())->BooleanValue();\n\n    AsyncQueueWorker(new DetectorAttachWorker(callback, detector, gn_playlist->playlist, gn_detector->event_context));\n}\n\nclass DetectorDetachWorker : public Nan::AsyncWorker {\npublic:\n    DetectorDetachWorker(Nan::Callback *callback, GrooveLoudnessDetector *detector,\n            GNLoudnessDetector::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->detector = detector;\n        this->event_context = event_context;\n    }\n    ~DetectorDetachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_loudness_detector_detach(detector))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n        uv_cond_signal(&event_context->cond);\n        uv_thread_join(&event_context->event_thread);\n        uv_cond_destroy(&event_context->cond);\n        uv_mutex_destroy(&event_context->mutex);\n        uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);\n    }\n\n    GrooveLoudnessDetector *detector;\n    GNLoudnessDetector::EventContext *event_context;\n};\n\nNAN_METHOD(GNLoudnessDetector::Detach) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n    GNLoudnessDetector *gn_detector = node::ObjectWrap::Unwrap<GNLoudnessDetector>(info.This());\n    GrooveLoudnessDetector *detector = gn_detector->detector;\n\n    AsyncQueueWorker(new DetectorDetachWorker(callback, detector, gn_detector->event_context));\n}\n"
  },
  {
    "path": "src/loudness_detector.h",
    "content": "#ifndef GN_LOUDNESS_DETECTOR_H\n#define GN_LOUDNESS_DETECTOR_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/loudness.h>\n\nclass GNLoudnessDetector : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GrooveLoudnessDetector *detector);\n\n        static NAN_METHOD(Create);\n\n        struct EventContext {\n            uv_thread_t event_thread;\n            uv_async_t event_async;\n            uv_cond_t cond;\n            uv_mutex_t mutex;\n            GrooveLoudnessDetector *detector;\n            Nan::Callback *event_cb;\n        };\n\n        EventContext *event_context;\n        GrooveLoudnessDetector *detector;\n\n    private:\n        GNLoudnessDetector();\n        ~GNLoudnessDetector();\n\n        static NAN_METHOD(New);\n\n        static NAN_METHOD(Attach);\n        static NAN_METHOD(Detach);\n        static NAN_METHOD(GetInfo);\n        static NAN_METHOD(Position);\n};\n\n#endif\n"
  },
  {
    "path": "src/player.cc",
    "content": "#include \"player.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"device.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNPlayer::GNPlayer() {};\nGNPlayer::~GNPlayer() {\n    groove_player_destroy(player);\n    delete event_context->event_cb;\n    delete event_context;\n};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNPlayer::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GroovePlayer\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(2);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"id\").ToLocalChecked(), GetId);\n    Nan::SetAccessor(proto, Nan::New<String>(\"playlist\").ToLocalChecked(), GetPlaylist);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"attach\", Attach);\n    Nan::SetPrototypeMethod(tpl, \"detach\", Detach);\n    Nan::SetPrototypeMethod(tpl, \"position\", Position);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNPlayer::New) {\n    Nan::HandleScope scope;\n\n    GNPlayer *obj = new GNPlayer();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNPlayer::NewInstance(GroovePlayer *player) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);\n    gn_player->player = player;\n\n    return scope.Escape(instance);\n}\n\nNAN_GETTER(GNPlayer::GetId) {\n    Nan::HandleScope scope;\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%p\", gn_player->player);\n    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());\n}\n\nNAN_GETTER(GNPlayer::GetPlaylist) {\n    Nan::HandleScope scope;\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());\n    GroovePlaylist *playlist = gn_player->player->playlist;\n    if (playlist) {\n        Local<Value> tmp = GNPlaylist::NewInstance(playlist);\n        info.GetReturnValue().Set(tmp);\n    } else {\n        info.GetReturnValue().Set(Nan::Null());\n    }\n}\n\nNAN_METHOD(GNPlayer::Position) {\n    Nan::HandleScope scope;\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());\n    GroovePlaylistItem *item;\n    double pos;\n    groove_player_position(gn_player->player, &item, &pos);\n    Local<Object> obj = Nan::New<Object>();\n    Nan::Set(obj, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(pos));\n    if (item) {\n        Local<Value> tmp = GNPlaylistItem::NewInstance(item);\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), tmp);\n    } else {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n    }\n    info.GetReturnValue().Set(obj);\n}\n\nstatic void PlayerEventAsyncCb(uv_async_t *handle) {\n    Nan::HandleScope scope;\n\n    GNPlayer::EventContext *context = reinterpret_cast<GNPlayer::EventContext *>(handle->data);\n\n    // flush events\n    GroovePlayerEvent event;\n\n    const unsigned argc = 1;\n    Local<Value> argv[argc];\n    while (groove_player_event_get(context->player, &event, 0) > 0) {\n        argv[0] = Nan::New<Number>(event.type);\n\n        TryCatch try_catch;\n        context->event_cb->Call(argc, argv);\n\n        if (try_catch.HasCaught()) {\n            node::FatalException(try_catch);\n        }\n    }\n\n    uv_mutex_lock(&context->mutex);\n    uv_cond_signal(&context->cond);\n    uv_mutex_unlock(&context->mutex);\n}\n\nstatic void PlayerEventThreadEntry(void *arg) {\n    GNPlayer::EventContext *context = reinterpret_cast<GNPlayer::EventContext *>(arg);\n    while (groove_player_event_peek(context->player, 1) > 0) {\n        uv_mutex_lock(&context->mutex);\n        uv_async_send(&context->event_async);\n        uv_cond_wait(&context->cond, &context->mutex);\n        uv_mutex_unlock(&context->mutex);\n    }\n}\n\nclass PlayerAttachWorker : public Nan::AsyncWorker {\npublic:\n    PlayerAttachWorker(Nan::Callback *callback, GroovePlayer *player, GroovePlaylist *playlist,\n            GNPlayer::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->player = player;\n        this->playlist = playlist;\n        this->event_context = event_context;\n    }\n    ~PlayerAttachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_player_attach(player, playlist))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n\n        GNPlayer::EventContext *context = event_context;\n\n        uv_cond_init(&context->cond);\n        uv_mutex_init(&context->mutex);\n\n        context->event_async.data = context;\n        uv_async_init(uv_default_loop(), &context->event_async, PlayerEventAsyncCb);\n\n        uv_thread_create(&context->event_thread, PlayerEventThreadEntry, context);\n    }\n\n    GroovePlayer *player;\n    GroovePlaylist *playlist;\n    GNPlayer::EventContext *event_context;\n};\n\nNAN_METHOD(GNPlayer::Create) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    GroovePlayer *player = groove_player_create(get_groove());\n    if (!player) {\n        Nan::ThrowTypeError(\"unable to create player\");\n        return;\n    }\n\n    Local<Object> instance = NewInstance(player)->ToObject();\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(instance);\n    EventContext *context = new EventContext;\n    gn_player->event_context = context;\n    context->event_cb = new Nan::Callback(info[0].As<Function>());\n    context->player = player;\n\n    Nan::Set(instance, Nan::New<String>(\"device\").ToLocalChecked(), Nan::Null());\n\n    info.GetReturnValue().Set(instance);\n}\n\nNAN_METHOD(GNPlayer::Attach) {\n    Nan::HandleScope scope;\n\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsObject()) {\n        Nan::ThrowTypeError(\"Expected object arg[0]\");\n        return;\n    }\n    if (info.Length() < 2 || !info[1]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[1]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());\n\n    Local<Object> instance = info.This();\n\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());\n\n    GroovePlayer *player = gn_player->player;\n\n    // copy the properties from our instance to the player\n    Local<Value> deviceObject = instance->Get(Nan::New<String>(\"device\").ToLocalChecked());\n    if (!deviceObject->IsObject() || deviceObject->IsUndefined() || deviceObject->IsNull()) {\n        Nan::ThrowTypeError(\"Expected player.device to be an object\");\n        return;\n    }\n    GNDevice *gn_device = node::ObjectWrap::Unwrap<GNDevice>(deviceObject->ToObject());\n    player->device = gn_device->device;\n\n    AsyncQueueWorker(new PlayerAttachWorker(callback, player, gn_playlist->playlist, gn_player->event_context));\n}\n\nclass PlayerDetachWorker : public Nan::AsyncWorker {\npublic:\n    PlayerDetachWorker(Nan::Callback *callback, GroovePlayer *player,\n            GNPlayer::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->player = player;\n        this->event_context = event_context;\n    }\n    ~PlayerDetachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_player_detach(player))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n        uv_cond_signal(&event_context->cond);\n        uv_thread_join(&event_context->event_thread);\n        uv_cond_destroy(&event_context->cond);\n        uv_mutex_destroy(&event_context->mutex);\n        uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);\n    }\n\n    GroovePlayer *player;\n    GNPlayer::EventContext *event_context;\n};\n\nNAN_METHOD(GNPlayer::Detach) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n    GNPlayer *gn_player = node::ObjectWrap::Unwrap<GNPlayer>(info.This());\n    GroovePlayer *player = gn_player->player;\n\n    AsyncQueueWorker(new PlayerDetachWorker(callback, player, gn_player->event_context));\n}\n"
  },
  {
    "path": "src/player.h",
    "content": "#ifndef GN_PLAYER_H\n#define GN_PLAYER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/player.h>\n\nclass GNPlayer : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GroovePlayer *player);\n\n        static NAN_METHOD(Create);\n\n        struct EventContext {\n            uv_thread_t event_thread;\n            uv_async_t event_async;\n            uv_cond_t cond;\n            uv_mutex_t mutex;\n            GroovePlayer *player;\n            Nan::Callback *event_cb;\n        };\n\n\n        GroovePlayer *player;\n        EventContext *event_context;\n\n    private:\n        GNPlayer();\n        ~GNPlayer();\n\n        static NAN_METHOD(New);\n\n        static NAN_GETTER(GetId);\n        static NAN_GETTER(GetPlaylist);\n\n        static NAN_METHOD(Attach);\n        static NAN_METHOD(Detach);\n        static NAN_METHOD(Position);\n};\n\n#endif\n\n"
  },
  {
    "path": "src/playlist.cc",
    "content": "#include <node.h>\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"file.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNPlaylist::GNPlaylist() { };\nGNPlaylist::~GNPlaylist() { };\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNPlaylist::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GroovePlaylist\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(1);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"id\").ToLocalChecked(), GetId);\n    Nan::SetAccessor(proto, Nan::New<String>(\"gain\").ToLocalChecked(), GetGain);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"destroy\", Destroy);\n    Nan::SetPrototypeMethod(tpl, \"play\", Play);\n    Nan::SetPrototypeMethod(tpl, \"items\", Playlist);\n    Nan::SetPrototypeMethod(tpl, \"pause\", Pause);\n    Nan::SetPrototypeMethod(tpl, \"seek\", Seek);\n    Nan::SetPrototypeMethod(tpl, \"insert\", Insert);\n    Nan::SetPrototypeMethod(tpl, \"remove\", Remove);\n    Nan::SetPrototypeMethod(tpl, \"position\", DecodePosition);\n    Nan::SetPrototypeMethod(tpl, \"playing\", Playing);\n    Nan::SetPrototypeMethod(tpl, \"clear\", Clear);\n    Nan::SetPrototypeMethod(tpl, \"count\", Count);\n    Nan::SetPrototypeMethod(tpl, \"setItemGainPeak\", SetItemGainPeak);\n    Nan::SetPrototypeMethod(tpl, \"setGain\", SetGain);\n    Nan::SetPrototypeMethod(tpl, \"setFillMode\", SetFillMode);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNPlaylist::New) {\n    Nan::HandleScope scope;\n    assert(info.IsConstructCall());\n\n    GNPlaylist *obj = new GNPlaylist();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNPlaylist::NewInstance(GroovePlaylist *playlist) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(instance);\n    gn_playlist->playlist = playlist;\n\n    return scope.Escape(instance);\n}\n\nNAN_METHOD(GNPlaylist::Destroy) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    groove_playlist_destroy(gn_playlist->playlist);\n    gn_playlist->playlist = NULL;\n}\n\nNAN_GETTER(GNPlaylist::GetId) {\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%p\", gn_playlist->playlist);\n    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());\n}\n\nNAN_GETTER(GNPlaylist::GetGain) {\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    info.GetReturnValue().Set(Nan::New<Number>(gn_playlist->playlist->gain));\n}\n\nNAN_METHOD(GNPlaylist::Play) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    groove_playlist_play(gn_playlist->playlist);\n    return;\n}\n\nNAN_METHOD(GNPlaylist::Playlist) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n\n    Local<Array> playlist = Nan::New<Array>();\n\n    GroovePlaylistItem *item = gn_playlist->playlist->head;\n    int i = 0;\n    while (item) {\n        Nan::Set(playlist, Nan::New<Number>(i), GNPlaylistItem::NewInstance(item));\n        item = item->next;\n        i += 1;\n    }\n\n    info.GetReturnValue().Set(playlist);\n}\n\nNAN_METHOD(GNPlaylist::Pause) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    groove_playlist_pause(gn_playlist->playlist);\n}\n\nNAN_METHOD(GNPlaylist::Seek) {\n    Nan::HandleScope scope;\n\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    GNPlaylistItem *gn_playlist_item =\n        node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());\n\n    double pos = info[1]->NumberValue();\n    groove_playlist_seek(gn_playlist->playlist, gn_playlist_item->playlist_item, pos);\n}\n\nNAN_METHOD(GNPlaylist::Insert) {\n    Nan::HandleScope scope;\n\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    GNFile *gn_file = node::ObjectWrap::Unwrap<GNFile>(info[0]->ToObject());\n    double gain = 1.0;\n    double peak = 1.0;\n    if (!info[1]->IsNull() && !info[1]->IsUndefined()) {\n        gain = info[1]->NumberValue();\n    }\n    if (!info[2]->IsNull() && !info[2]->IsUndefined()) {\n        peak = info[2]->NumberValue();\n    }\n    GroovePlaylistItem *item = NULL;\n    if (!info[3]->IsNull() && !info[3]->IsUndefined()) {\n        GNPlaylistItem *gn_pl_item =\n            node::ObjectWrap::Unwrap<GNPlaylistItem>(info[3]->ToObject());\n        item = gn_pl_item->playlist_item;\n    }\n    GroovePlaylistItem *result = groove_playlist_insert(gn_playlist->playlist,\n            gn_file->file, gain, peak, item);\n\n    info.GetReturnValue().Set(GNPlaylistItem::NewInstance(result));\n}\n\nNAN_METHOD(GNPlaylist::Remove) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());\n    groove_playlist_remove(gn_playlist->playlist, gn_pl_item->playlist_item);\n}\n\nNAN_METHOD(GNPlaylist::DecodePosition) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    GroovePlaylistItem *item;\n    double pos = -1.0;\n    groove_playlist_position(gn_playlist->playlist, &item, &pos);\n    Local<Object> obj = Nan::New<Object>();\n    Nan::Set(obj, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(pos));\n    if (item) {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(item));\n    } else {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n    }\n    info.GetReturnValue().Set(obj);\n}\n\nNAN_METHOD(GNPlaylist::Playing) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    int playing = groove_playlist_playing(gn_playlist->playlist);\n    info.GetReturnValue().Set(Nan::New<Boolean>(playing));\n}\n\nNAN_METHOD(GNPlaylist::Clear) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    groove_playlist_clear(gn_playlist->playlist);\n}\n\nNAN_METHOD(GNPlaylist::Count) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    int count = groove_playlist_count(gn_playlist->playlist);\n    info.GetReturnValue().Set(Nan::New<Number>(count));\n}\n\nNAN_METHOD(GNPlaylist::SetItemGainPeak) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info[0]->ToObject());\n    double gain = info[1]->NumberValue();\n    double peak = info[2]->NumberValue();\n    groove_playlist_set_item_gain_peak(gn_playlist->playlist, gn_pl_item->playlist_item, gain, peak);\n}\n\nNAN_METHOD(GNPlaylist::SetGain) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    groove_playlist_set_gain(gn_playlist->playlist, info[0]->NumberValue());\n}\n\nNAN_METHOD(GNPlaylist::SetFillMode) {\n    Nan::HandleScope scope;\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info.This());\n    GrooveFillMode mode = (GrooveFillMode) info[0]->NumberValue();\n    groove_playlist_set_fill_mode(gn_playlist->playlist, mode);\n}\n\nNAN_METHOD(GNPlaylist::Create) {\n    Nan::HandleScope scope;\n    GroovePlaylist *playlist = groove_playlist_create(get_groove());\n    Local<Value> tmp = GNPlaylist::NewInstance(playlist);\n    info.GetReturnValue().Set(tmp);\n}\n"
  },
  {
    "path": "src/playlist.h",
    "content": "#ifndef GN_PLAYLIST_H\n#define GN_PLAYLIST_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNPlaylist : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GroovePlaylist *playlist);\n\n        static NAN_METHOD(Create);\n\n        GroovePlaylist *playlist;\n\n\n    private:\n        GNPlaylist();\n        ~GNPlaylist();\n\n        static NAN_METHOD(New);\n        static NAN_METHOD(Destroy);\n\n        static NAN_GETTER(GetId);\n        static NAN_GETTER(GetGain);\n\n        static NAN_METHOD(Playlist);\n        static NAN_METHOD(Play);\n        static NAN_METHOD(Pause);\n        static NAN_METHOD(Seek);\n        static NAN_METHOD(Insert);\n        static NAN_METHOD(Remove);\n        static NAN_METHOD(Position);\n        static NAN_METHOD(DecodePosition);\n        static NAN_METHOD(Playing);\n        static NAN_METHOD(Clear);\n        static NAN_METHOD(Count);\n        static NAN_METHOD(SetItemGainPeak);\n        static NAN_METHOD(SetGain);\n        static NAN_METHOD(SetFillMode);\n};\n\n#endif\n"
  },
  {
    "path": "src/playlist_item.cc",
    "content": "#include \"playlist_item.h\"\n#include \"file.h\"\n\nusing namespace v8;\n\nGNPlaylistItem::GNPlaylistItem() { };\nGNPlaylistItem::~GNPlaylistItem() { };\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNPlaylistItem::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GroovePlaylistItem\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(1);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"file\").ToLocalChecked(), GetFile);\n    Nan::SetAccessor(proto, Nan::New<String>(\"id\").ToLocalChecked(), GetId);\n    Nan::SetAccessor(proto, Nan::New<String>(\"gain\").ToLocalChecked(), GetGain);\n    Nan::SetAccessor(proto, Nan::New<String>(\"peak\").ToLocalChecked(), GetPeak);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNPlaylistItem::New) {\n    Nan::HandleScope scope;\n\n    GNPlaylistItem *obj = new GNPlaylistItem();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNPlaylistItem::NewInstance(GroovePlaylistItem *playlist_item) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNPlaylistItem *gn_playlist_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(instance);\n    gn_playlist_item->playlist_item = playlist_item;\n\n    return scope.Escape(instance);\n}\n\nNAN_GETTER(GNPlaylistItem::GetFile) {\n    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());\n    Local<Value> tmp = GNFile::NewInstance(gn_pl_item->playlist_item->file);\n    info.GetReturnValue().Set(tmp);\n}\n\nNAN_GETTER(GNPlaylistItem::GetId) {\n    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%p\", gn_pl_item->playlist_item);\n    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());\n}\n\nNAN_GETTER(GNPlaylistItem::GetGain) {\n    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());\n    double gain = gn_pl_item->playlist_item->gain;\n    info.GetReturnValue().Set(Nan::New<Number>(gain));\n}\n\nNAN_GETTER(GNPlaylistItem::GetPeak) {\n    GNPlaylistItem *gn_pl_item = node::ObjectWrap::Unwrap<GNPlaylistItem>(info.This());\n    double peak = gn_pl_item->playlist_item->peak;\n    info.GetReturnValue().Set(Nan::New<Number>(peak));\n}\n"
  },
  {
    "path": "src/playlist_item.h",
    "content": "#ifndef GN_PLAYLIST_ITEM_H\n#define GN_PLAYLIST_ITEM_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/groove.h>\n\nclass GNPlaylistItem : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GroovePlaylistItem *playlist_item);\n\n        GroovePlaylistItem *playlist_item;\n    private:\n        GNPlaylistItem();\n        ~GNPlaylistItem();\n\n        static NAN_METHOD(New);\n\n        static NAN_GETTER(GetFile);\n        static NAN_GETTER(GetId);\n        static NAN_GETTER(GetGain);\n        static NAN_GETTER(GetPeak);\n};\n\n#endif\n\n\n"
  },
  {
    "path": "src/waveform_builder.cc",
    "content": "#include \"waveform_builder.h\"\n#include \"playlist.h\"\n#include \"playlist_item.h\"\n#include \"groove.h\"\n\nusing namespace v8;\n\nGNWaveformBuilder::GNWaveformBuilder() {};\nGNWaveformBuilder::~GNWaveformBuilder() {\n    groove_waveform_destroy(waveform);\n    delete event_context->event_cb;\n    delete event_context;\n};\n\nstatic Nan::Persistent<v8::Function> constructor;\n\nvoid GNWaveformBuilder::Init() {\n    // Prepare constructor template\n    Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);\n    tpl->SetClassName(Nan::New<String>(\"GrooveWaveformBuilder\").ToLocalChecked());\n    tpl->InstanceTemplate()->SetInternalFieldCount(2);\n    Local<ObjectTemplate> proto = tpl->PrototypeTemplate();\n\n    // Fields\n    Nan::SetAccessor(proto, Nan::New<String>(\"id\").ToLocalChecked(), GetId);\n    Nan::SetAccessor(proto, Nan::New<String>(\"playlist\").ToLocalChecked(), GetPlaylist);\n\n    // Methods\n    Nan::SetPrototypeMethod(tpl, \"attach\", Attach);\n    Nan::SetPrototypeMethod(tpl, \"detach\", Detach);\n    Nan::SetPrototypeMethod(tpl, \"getInfo\", GetInfo);\n    Nan::SetPrototypeMethod(tpl, \"position\", Position);\n\n    constructor.Reset(tpl->GetFunction());\n}\n\nNAN_METHOD(GNWaveformBuilder::New) {\n    Nan::HandleScope scope;\n\n    GNWaveformBuilder *obj = new GNWaveformBuilder();\n    obj->Wrap(info.This());\n    \n    info.GetReturnValue().Set(info.This());\n}\n\nLocal<Value> GNWaveformBuilder::NewInstance(GrooveWaveform *waveform) {\n    Nan::EscapableHandleScope scope;\n\n    Local<Function> cons = Nan::New(constructor);\n    Local<Object> instance = cons->NewInstance();\n\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(instance);\n    gn_waveform->waveform = waveform;\n\n    return scope.Escape(instance);\n}\n\nNAN_GETTER(GNWaveformBuilder::GetId) {\n    Nan::HandleScope scope;\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());\n    char buf[64];\n    snprintf(buf, sizeof(buf), \"%p\", gn_waveform->waveform);\n    info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());\n}\n\nNAN_GETTER(GNWaveformBuilder::GetPlaylist) {\n    Nan::HandleScope scope;\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());\n    GroovePlaylist *playlist = gn_waveform->waveform->playlist;\n    if (playlist) {\n        Local<Value> tmp = GNPlaylist::NewInstance(playlist);\n        info.GetReturnValue().Set(tmp);\n    } else {\n        info.GetReturnValue().Set(Nan::Null());\n    }\n}\n\nNAN_METHOD(GNWaveformBuilder::Position) {\n    Nan::HandleScope scope;\n\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());\n    GrooveWaveform *waveform = gn_waveform->waveform;\n\n    GroovePlaylistItem *item;\n    double pos;\n    groove_waveform_position(waveform, &item, &pos);\n\n    Local<Object> obj = Nan::New<Object>();\n    Nan::Set(obj, Nan::New<String>(\"pos\").ToLocalChecked(), Nan::New<Number>(pos));\n    if (item) {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), GNPlaylistItem::NewInstance(item));\n    } else {\n        Nan::Set(obj, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n    }\n\n    info.GetReturnValue().Set(obj);\n}\n\nNAN_METHOD(GNWaveformBuilder::Create) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    GrooveWaveform *waveform = groove_waveform_create(get_groove());\n    if (!waveform) {\n        Nan::ThrowTypeError(\"unable to create waveform builder\");\n        return;\n    }\n\n    // set properties on the instance with default values from\n    // GrooveWaveform struct\n    Local<Object> instance = GNWaveformBuilder::NewInstance(waveform)->ToObject();\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(instance);\n    EventContext *context = new EventContext;\n    gn_waveform->event_context = context;\n    context->event_cb = new Nan::Callback(info[0].As<Function>());\n    context->waveform = waveform;\n\n\n    Nan::Set(instance, Nan::New<String>(\"infoQueueSizeBytes\").ToLocalChecked(),\n            Nan::New<Number>(waveform->info_queue_size_bytes));\n\n    Nan::Set(instance, Nan::New<String>(\"widthInFrames\").ToLocalChecked(),\n            Nan::New<Number>(waveform->width_in_frames));\n\n    info.GetReturnValue().Set(instance);\n}\n\nstatic void buffer_free(char *data, void *hint) {\n    GrooveWaveformInfo *waveform_info = reinterpret_cast<GrooveWaveformInfo*>(hint);\n    groove_waveform_info_unref(waveform_info);\n}\n\nNAN_METHOD(GNWaveformBuilder::GetInfo) {\n    Nan::HandleScope scope;\n\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());\n    GrooveWaveform *waveform = gn_waveform->waveform;\n\n    GrooveWaveformInfo *waveform_info;\n    if (groove_waveform_info_get(waveform, &waveform_info, 0) == 1) {\n        Local<Object> object = Nan::New<Object>();\n\n        if (waveform_info->data_size) {\n            Local<Object> bufferObject = Nan::NewBuffer(\n                    (char*)waveform_info->data, waveform_info->data_size,\n                    buffer_free, waveform_info).ToLocalChecked();\n            Nan::Set(object, Nan::New<String>(\"buffer\").ToLocalChecked(), bufferObject);\n        } else {\n            Nan::Set(object, Nan::New<String>(\"buffer\").ToLocalChecked(), Nan::Null());\n        }\n\n        double expected_duration = waveform_info->expected_frame_count / (double)waveform_info->sample_rate;\n        double actual_duration = waveform_info->actual_frame_count / (double)waveform_info->sample_rate;\n\n        Nan::Set(object, Nan::New<String>(\"expectedDuration\").ToLocalChecked(),\n                Nan::New<Number>(expected_duration));\n        Nan::Set(object, Nan::New<String>(\"actualDuration\").ToLocalChecked(),\n                Nan::New<Number>(actual_duration));\n\n        if (waveform_info->item) {\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(),\n                    GNPlaylistItem::NewInstance(waveform_info->item));\n        } else {\n            Nan::Set(object, Nan::New<String>(\"item\").ToLocalChecked(), Nan::Null());\n        }\n\n        info.GetReturnValue().Set(object);\n    } else {\n        info.GetReturnValue().Set(Nan::Null());\n    }\n}\n\nstatic void EventAsyncCb(uv_async_t *handle) {\n    Nan::HandleScope scope;\n\n    GNWaveformBuilder::EventContext *context = reinterpret_cast<GNWaveformBuilder::EventContext *>(handle->data);\n\n    // call callback signaling that there is info ready\n\n    const unsigned argc = 1;\n    Local<Value> argv[argc];\n    argv[0] = Nan::Null();\n\n    TryCatch try_catch;\n    context->event_cb->Call(argc, argv);\n\n    if (try_catch.HasCaught()) {\n        node::FatalException(try_catch);\n    }\n\n    uv_mutex_lock(&context->mutex);\n    uv_cond_signal(&context->cond);\n    uv_mutex_unlock(&context->mutex);\n}\n\nstatic void EventThreadEntry(void *arg) {\n    GNWaveformBuilder::EventContext *context = reinterpret_cast<GNWaveformBuilder::EventContext *>(arg);\n    while (groove_waveform_info_peek(context->waveform, 1) > 0) {\n        uv_mutex_lock(&context->mutex);\n        uv_async_send(&context->event_async);\n        uv_cond_wait(&context->cond, &context->mutex);\n        uv_mutex_unlock(&context->mutex);\n    }\n}\n\nclass WaveformAttachWorker : public Nan::AsyncWorker {\npublic:\n    WaveformAttachWorker(Nan::Callback *callback, GrooveWaveform *waveform, GroovePlaylist *playlist,\n            GNWaveformBuilder::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->waveform = waveform;\n        this->playlist = playlist;\n        this->event_context = event_context;\n    }\n    ~WaveformAttachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_waveform_attach(waveform, playlist))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n\n        GNWaveformBuilder::EventContext *context = event_context;\n\n        uv_cond_init(&context->cond);\n        uv_mutex_init(&context->mutex);\n\n        context->event_async.data = context;\n        uv_async_init(uv_default_loop(), &context->event_async, EventAsyncCb);\n\n        uv_thread_create(&context->event_thread, EventThreadEntry, context);\n    }\n\n    GrooveWaveform *waveform;\n    GroovePlaylist *playlist;\n    GNWaveformBuilder::EventContext *event_context;\n};\n\nNAN_METHOD(GNWaveformBuilder::Attach) {\n    Nan::HandleScope scope;\n\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());\n\n    if (info.Length() < 1 || !info[0]->IsObject()) {\n        Nan::ThrowTypeError(\"Expected object arg[0]\");\n        return;\n    }\n    if (info.Length() < 2 || !info[1]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[1]\");\n        return;\n    }\n    Nan::Callback *callback = new Nan::Callback(info[1].As<Function>());\n\n    Local<Object> instance = info.This();\n\n    GNPlaylist *gn_playlist = node::ObjectWrap::Unwrap<GNPlaylist>(info[0]->ToObject());\n\n    GrooveWaveform *waveform = gn_waveform->waveform;\n\n    // copy the properties from our instance to the player\n    waveform->info_queue_size_bytes = (int)instance->Get(Nan::New<String>(\"infoQueueSizeBytes\").ToLocalChecked())->NumberValue();\n    waveform->width_in_frames = (int)instance->Get(Nan::New<String>(\"widthInFrames\").ToLocalChecked())->NumberValue();\n\n\n    AsyncQueueWorker(new WaveformAttachWorker(callback, waveform, gn_playlist->playlist, gn_waveform->event_context));\n}\n\nclass WaveformDetachWorker : public Nan::AsyncWorker {\npublic:\n    WaveformDetachWorker(Nan::Callback *callback, GrooveWaveform *waveform,\n            GNWaveformBuilder::EventContext *event_context) :\n        Nan::AsyncWorker(callback)\n    {\n        this->waveform = waveform;\n        this->event_context = event_context;\n    }\n    ~WaveformDetachWorker() {}\n\n    void Execute() {\n        int err;\n        if ((err = groove_waveform_detach(waveform))) {\n            SetErrorMessage(groove_strerror(err));\n            return;\n        }\n        uv_cond_signal(&event_context->cond);\n        uv_thread_join(&event_context->event_thread);\n        uv_cond_destroy(&event_context->cond);\n        uv_mutex_destroy(&event_context->mutex);\n        uv_close(reinterpret_cast<uv_handle_t*>(&event_context->event_async), NULL);\n    }\n\n    GrooveWaveform *waveform;\n    GroovePlaylist *playlist;\n    GNWaveformBuilder::EventContext *event_context;\n};\n\nNAN_METHOD(GNWaveformBuilder::Detach) {\n    Nan::HandleScope scope;\n\n    if (info.Length() < 1 || !info[0]->IsFunction()) {\n        Nan::ThrowTypeError(\"Expected function arg[0]\");\n        return;\n    }\n\n    Nan::Callback *callback = new Nan::Callback(info[0].As<Function>());\n    GNWaveformBuilder *gn_waveform = node::ObjectWrap::Unwrap<GNWaveformBuilder>(info.This());\n    GrooveWaveform *waveform = gn_waveform->waveform;\n\n    AsyncQueueWorker(new WaveformDetachWorker(callback, waveform, gn_waveform->event_context));\n}\n"
  },
  {
    "path": "src/waveform_builder.h",
    "content": "#ifndef GN_WAVEFORM_BUILDER_H\n#define GN_WAVEFORM_BUILDER_H\n\n#include <node.h>\n#include <nan.h>\n#include <groove/waveform.h>\n\nclass GNWaveformBuilder : public node::ObjectWrap {\n    public:\n        static void Init();\n        static v8::Local<v8::Value> NewInstance(GrooveWaveform *waveform);\n\n        static NAN_METHOD(Create);\n\n        struct EventContext {\n            uv_thread_t event_thread;\n            uv_async_t event_async;\n            uv_cond_t cond;\n            uv_mutex_t mutex;\n            GrooveWaveform *waveform;\n            Nan::Callback *event_cb;\n        };\n\n        EventContext *event_context;\n        GrooveWaveform *waveform;\n\n    private:\n        GNWaveformBuilder();\n        ~GNWaveformBuilder();\n\n        static NAN_METHOD(New);\n\n        static NAN_GETTER(GetId);\n        static NAN_GETTER(GetPlaylist);\n\n        static NAN_METHOD(Attach);\n        static NAN_METHOD(Detach);\n        static NAN_METHOD(GetInfo);\n        static NAN_METHOD(Position);\n};\n\n#endif\n"
  },
  {
    "path": "test/test.js",
    "content": "var groove = require('../');\nvar assert = require('assert');\nvar path = require('path');\nvar fs = require('fs');\nvar ncp = require('ncp').ncp;\nvar testOgg = path.join(__dirname, \"danse.ogg\");\nvar bogusFile = __filename;\nvar rwTestOgg = path.join(__dirname, \"danse-rw.ogg\");\nvar it = global.it;\n\nit(\"version\", function() {\n  var ver = groove.getVersion();\n  assert.strictEqual(typeof ver.major, 'number');\n  assert.strictEqual(typeof ver.minor, 'number');\n  assert.strictEqual(typeof ver.patch, 'number');\n});\n\nit(\"logging\", function() {\n    assert.strictEqual(groove.LOG_ERROR, 16);\n    groove.setLogging(groove.LOG_QUIET);\n});\n\nit(\"open fails for bogus file\", function(done) {\n    groove.open(bogusFile, function(err, file) {\n        assert.strictEqual(err.message, \"unknown format\");\n        done();\n    });\n});\n\nit(\"open file and read metadata\", function(done) {\n    groove.open(testOgg, function(err, file) {\n        assert.ok(!err);\n        assert.ok(file.id);\n        assert.strictEqual(file.filename, testOgg);\n        assert.strictEqual(file.dirty, false);\n        assert.strictEqual(file.metadata().TITLE, 'Danse Macabre');\n        assert.strictEqual(file.metadata().ARTIST, 'Kevin MacLeod');\n        assert.strictEqual(file.shortNames(), 'ogg');\n        assert.strictEqual(file.getMetadata('initial key'), 'C');\n        assert.equal(file.getMetadata('bogus nonexisting tag'), null);\n        file.close(function(err) {\n            if (err) throw err;\n            done();\n        });\n    });\n});\n\nit(\"update metadata\", function(done) {\n    ncp(testOgg, rwTestOgg, function(err) {\n        assert.ok(!err);\n        groove.open(rwTestOgg, doUpdate);\n    });\n    function doUpdate(err, file) {\n        if (err) throw err;\n        file.setMetadata('foo new key', \"libgroove rules!\");\n        assert.strictEqual(file.getMetadata('foo new key'), 'libgroove rules!');\n        file.save(function(err) {\n            if (err) throw err;\n            file.close(checkUpdate);\n        });\n    }\n    function checkUpdate(err) {\n        assert.ok(!err);\n        groove.open(rwTestOgg, function(err, file) {\n            assert.ok(!err);\n            assert.strictEqual(file.getMetadata('foo new key'), 'libgroove rules!');\n            fs.unlinkSync(rwTestOgg);\n            done();\n        });\n    }\n});\n\nit(\"create empty playlist\", function (done) {\n    var playlist = groove.createPlaylist();\n    assert.ok(playlist.id);\n    assert.deepEqual(playlist.items(), []);\n    done();\n});\n\nit(\"create empty player\", function (done) {\n    var player = groove.createPlayer();\n    assert.ok(player.id);\n    done();\n});\n\nit(\"playlist item ids\", function(done) {\n    var playlist = groove.createPlaylist();\n    assert.ok(playlist);\n    playlist.pause();\n    assert.equal(playlist.playing(), false);\n    groove.open(testOgg, function(err, file) {\n        assert.ok(!err, \"opening file\");\n        assert.ok(playlist.position);\n        assert.strictEqual(playlist.gain, 1.0);\n        playlist.setGain(1.0);\n        var returned1 = playlist.insert(file, null);\n        var returned2 = playlist.insert(file, null);\n        var items1 = playlist.items();\n        var items2 = playlist.items();\n        assert.strictEqual(items1[0].id, items2[0].id);\n        assert.strictEqual(items1[0].id, returned1.id);\n        assert.strictEqual(items2[1].id, returned2.id);\n        done();\n    });\n});\n\nit(\"create, attach, detach player\", function(done) {\n    var playlist = groove.createPlaylist();\n    var player = groove.createPlayer();\n    groove.connectSoundBackend();\n    var devices = groove.getDevices();\n    var defaultDevice = devices.list[devices.defaultIndex];\n    player.device = defaultDevice;\n    player.attach(playlist, function(err) {\n        assert.ok(!err);\n        player.detach(function(err) {\n            assert.ok(!err);\n            done();\n        });\n    });\n});\n\nit(\"create, attach, detach loudness detector\", function(done) {\n  var playlist = groove.createPlaylist();\n  var detector = groove.createLoudnessDetector();\n  detector.attach(playlist, function(err) {\n    assert.ok(!err);\n    detector.detach(function(err) {\n      assert.ok(!err);\n      done();\n    });\n  });\n});\n\nit(\"create, attach, detach encoder\", function(done) {\n    var playlist = groove.createPlaylist();\n    var encoder = groove.createEncoder();\n    encoder.formatShortName = \"ogg\";\n    encoder.codecShortName = \"vorbis\";\n    encoder.attach(playlist, function(err) {\n        assert.ok(!err);\n        encoder.detach(function(err) {\n            assert.ok(!err);\n            done();\n        });\n    });\n});\n\nit(\"create, attach, detach fingerprinter\", function(done) {\n    var playlist = groove.createPlaylist();\n    var fingerprinter = groove.createFingerprinter();\n    fingerprinter.attach(playlist, function(err) {\n        assert.ok(!err);\n        fingerprinter.detach(function(err) {\n            assert.ok(!err);\n            done();\n        });\n    });\n});\n\nit(\"create, attach, detach waveform builder\", function(done) {\n    var playlist = groove.createPlaylist();\n    var waveform = groove.createWaveformBuilder();\n    waveform.attach(playlist, function(err) {\n        assert.ok(!err);\n        waveform.detach(function(err) {\n            assert.ok(!err);\n            done();\n        });\n    });\n});\n"
  }
]