Permalink
Browse files

use audio queue from the spotify example because it works better. tes…

…ts are passing with actual audio data
  • Loading branch information...
1 parent 096e647 commit abaa4bbb2a2746a339d7f5395658b507026a7480 @Floby committed Dec 26, 2012
Showing with 857 additions and 121 deletions.
  1. +1 −0 binding.gyp
  2. +5 −0 lib/Player.js
  3. +0 −1 lib/libspotify.js
  4. +67 −0 src/audio.cc
  5. +58 −0 src/audio.h
  6. +63 −71 src/player.cc
  7. +558 −0 src/queue.h
  8. +105 −49 test/test-050-player00-track.js
View
1 binding.gyp
@@ -8,6 +8,7 @@
"src/search.cc",
"src/track.cc",
"src/player.cc",
+ "src/audio.cc",
],
"cflags": ["-Wall", "-g", "-O0"],
"conditions" : [
View
5 lib/Player.js
@@ -3,6 +3,8 @@ var sp = require('./libspotify');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var assert = require('assert');
+var stream = require('stream');
+
function Player (session) {
@@ -14,9 +16,12 @@ function Player (session) {
this._session._sp_session.music_delivery = function(buffer) {
self.emit('data', buffer);
}
+ this.readable = true;
}
util.inherits(Player, EventEmitter);
+Player.prototype.pipe = stream.Stream.prototype.pipe;
+
Player.prototype.load = function load(track) {
assert(track instanceof sp.Track);
b.session_player_load(this._session._sp_session, track._sp_track);
View
1 lib/libspotify.js
@@ -1,4 +1,3 @@
-
exports.Player = require('./Player');
exports.Session = require('./Session');
exports.Search = require('./Search');
View
67 src/audio.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010 Spotify Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ *
+ * Audio helper functions.
+ *
+ * This file is part of the libspotify examples suite.
+ */
+
+#include "audio.h"
+#include <stdlib.h>
+
+audio_fifo_data_t* audio_get(audio_fifo_t *af)
+{
+ if(af->qlen == 0) {
+ return 0;
+ }
+ audio_fifo_data_t *afd;
+ pthread_mutex_lock(&af->mutex);
+
+ afd = TAILQ_FIRST(&af->q);
+ if(!afd) {
+ pthread_mutex_unlock(&af->mutex);
+ return NULL;
+ }
+
+ TAILQ_REMOVE(&af->q, afd, link);
+ af->qlen -= afd->nsamples;
+
+ pthread_mutex_unlock(&af->mutex);
+ return afd;
+}
+
+void audio_fifo_flush(audio_fifo_t *af)
+{
+ audio_fifo_data_t *afd;
+
+
+ pthread_mutex_lock(&af->mutex);
+
+ while((afd = TAILQ_FIRST(&af->q))) {
+ TAILQ_REMOVE(&af->q, afd, link);
+ free(afd);
+ }
+
+ af->qlen = 0;
+ pthread_mutex_unlock(&af->mutex);
+}
+
View
58 src/audio.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2006-2009 Spotify Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ *
+ * Audio output driver.
+ *
+ * This file is part of the libspotify examples suite.
+ */
+#ifndef _JUKEBOX_AUDIO_H_
+#define _JUKEBOX_AUDIO_H_
+
+#include <pthread.h>
+#include <stdint.h>
+#include "queue.h"
+
+
+/* --- Types --- */
+typedef struct audio_fifo_data {
+ TAILQ_ENTRY(audio_fifo_data) link;
+ int channels;
+ int rate;
+ int nsamples;
+ int16_t samples[0];
+} audio_fifo_data_t;
+
+typedef struct audio_fifo {
+ TAILQ_HEAD(, audio_fifo_data) q;
+ int qlen;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+} audio_fifo_t;
+
+
+/* --- Functions --- */
+extern void audio_init(audio_fifo_t *af);
+extern void audio_fifo_flush(audio_fifo_t *af);
+audio_fifo_data_t* audio_get(audio_fifo_t *af);
+
+#endif /* _JUKEBOX_AUDIO_H_ */
+
View
134 src/player.cc
@@ -16,8 +16,9 @@
* =====================================================================================
*/
-#include <math.h>
+#include <stdlib.h>
#include "common.h"
+#include "audio.h"
using namespace v8;
using namespace nsp;
@@ -32,102 +33,86 @@ static int16_t buffer[BUFFER_SIZE];
static unsigned int read_index = 0;
static unsigned int write_index = 0;
static bool buffer_full = false;
+static audio_fifo_t g_audiofifo;
-static sp_session* current_session;
+static sp_session* current_session = NULL;
/**
* spotify callback for the music_delivery event.
* See https://developer.spotify.com/technologies/libspotify/docs/12.1.45/structsp__session__callbacks.html
*/
extern int call_music_delivery_callback(sp_session* session, const sp_audioformat *format, const void *frames, int num_frames) {
- //copy these values here
- unsigned int ri = read_index;
- unsigned int wi = write_index;
-
- // if there is no frame to process, there is an audio discontinuity, let's do nothing
- if(num_frames == 0) return 0;
-
- // if our buffer is full, let's just do nothing
- if(buffer_full == true) return 0;
-
- // I don't know how to switch audio format, so let's just ignore what we don't know how to process
- if(format->channels != 2 || format->sample_rate != 44100) {
- return 0;
- }
+ audio_fifo_t *af = &g_audiofifo;
+ audio_fifo_data_t *afd;
+ size_t s;
+ current_session = session;
- // calculate how much room we have in our buffer
- int available = (ri - wi);
- if(available == 0 && false == buffer_full) available = BUFFER_SIZE;
- // if the read index is above its limit, we'll have to copy in two times
- if(wi > ri) {
- // available is negative
- available = (BUFFER_SIZE + available);
- }
- int frames_consumed = fmin(num_frames, (available / format->channels));
- int frames_ignored = num_frames - frames_consumed;
+ if (num_frames == 0)
+ return 0; // Audio discontinuity, do nothing
- if(wi < ri) {
- memcpy(&buffer[wi], frames, sizeof(int16_t) * frames_consumed * format->channels);
- }
- // if the read index is above its limit (read index), we'll have to copy in two times
- else {
- int frames_tail = fmin(frames_consumed, (BUFFER_SIZE - wi) / format->channels);
- int frames_head = fmin(0, frames_consumed - frames_tail);
- int size_frame_tail = sizeof(int16_t) * frames_tail * format->channels;
- memcpy(&buffer[wi], frames, size_frame_tail);
- if(frames_head) {
- memcpy(buffer, (int16_t*)frames + size_frame_tail*format->channels, sizeof(int16_t) * frames_head * format->channels);
- }
- }
- wi = (wi + (frames_consumed * format->channels)) % BUFFER_SIZE;
+ pthread_mutex_lock(&af->mutex);
- write_index = wi;
- buffer_full = (frames_ignored > 0);
+ /* Buffer one second of audio */
+ if (af->qlen > format->sample_rate) {
+ pthread_mutex_unlock(&af->mutex);
- current_session = session;
- return frames_ignored;
+ return 0;
+ }
+
+ s = num_frames * sizeof(int16_t) * format->channels;
+
+ afd = (audio_fifo_data_t*) malloc(sizeof(*afd) + s);
+ memcpy(afd->samples, frames, s);
+
+ afd->nsamples = num_frames;
+
+ afd->rate = format->sample_rate;
+ afd->channels = format->channels;
+
+ TAILQ_INSERT_TAIL(&af->q, afd, link);
+ af->qlen += num_frames;
+
+ pthread_cond_signal(&af->cond);
+ pthread_mutex_unlock(&af->mutex);
+
+ return num_frames;
}
static void free_music_data(char* data, void* hint) {
- delete [] data;
+ free(hint);
}
static void read_delivered_music(uv_idle_t* handle, int status) {
- unsigned int wi = write_index, ri = read_index;
- if(wi == ri && buffer_full == false) return;
+ audio_fifo_t* af = &g_audiofifo;
+ audio_fifo_data_t* afd;
- int to_read = wi - ri;
- if(to_read < 0) {
- to_read = BUFFER_SIZE + to_read;
+ if(af->qlen == 0) {
+ return;
}
- int16_t* data = new int16_t[to_read];
+ sp_session* spsession = current_session;
+ ObjectHandle<sp_session>* session = (ObjectHandle<sp_session>*) sp_session_userdata(spsession);
- if(ri < wi) {
- memcpy(data, &buffer[ri], to_read);
- }
- else {
- int read_tail = BUFFER_SIZE - to_read;
- memcpy(data, &buffer[ri], read_tail);
- memcpy(data + read_tail, buffer, to_read - read_tail);
+ Handle<Value> cbv = session->object->Get(String::New("music_delivery"));
+ if(!cbv->IsFunction()) {
+ return;
}
- ri = (ri+to_read) % BUFFER_SIZE;
- read_index = ri;
- buffer_full = false;
+ Handle<Function> cb = Local<Function>(Function::Cast(*cbv));
- Buffer* buffer = Buffer::New((char*)data, to_read * sizeof(int16_t), free_music_data, NULL);
+ while(af->qlen > 0) {
+ afd = audio_get(af);
+ if(!afd) {
+ break;
+ }
- sp_session* spsession = current_session;
- ObjectHandle<sp_session>* session = (ObjectHandle<sp_session>*) sp_session_userdata(spsession);
-
- Handle<Value> cbv = session->object->Get(String::New("music_delivery"));
- if(!cbv->IsFunction()) return;
+ Buffer* buffer = Buffer::New((char*) afd->samples, afd->nsamples * sizeof(int16_t)* afd->channels, free_music_data, afd);
+ buffer->handle_->Set(String::New("channels"), Number::New(afd->channels));
+ buffer->handle_->Set(String::New("rate"), Number::New(afd->rate));
- Handle<Function> cb = Local<Function>(Function::Cast(*cbv));
- const unsigned int argc = 1;
- Local<Value> argv[argc] = { Local<Value>::New(buffer->handle_) };
- cb->Call(Context::GetCurrent()->Global(), argc, argv);
+ Local<Value> argv[1] = { Local<Value>::New(buffer->handle_) };
+ cb->Call(Context::GetCurrent()->Global(), 1, argv);
+ }
return;
}
@@ -177,6 +162,13 @@ void nsp::init_player(Handle<Object> target) {
NODE_SET_METHOD(target, "session_player_load", Session_Player_Load);
NODE_SET_METHOD(target, "session_player_play", Session_Player_Play);
+ audio_fifo_t* af = &g_audiofifo;
+ TAILQ_INIT(&af->q);
+ af->qlen = 0;
+
+ pthread_mutex_init(&af->mutex, NULL);
+ pthread_cond_init(&af->cond, NULL);
+
uv_idle_init(uv_default_loop(), &read_music_handle);
uv_idle_start(&read_music_handle, read_delivered_music);
uv_unref((uv_handle_t*) &read_music_handle);
View
558 src/queue.h
@@ -0,0 +1,558 @@
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The
+ * elements are singly linked for minimum space and pointer manipulation
+ * overhead at the expense of O(n) removal for arbitrary elements. New
+ * elements can be added to the list after an existing element or at the
+ * head of the list. Elements being removed from the head of the list
+ * should use the explicit macro for this purpose for optimum
+ * efficiency. A singly-linked list may only be traversed in the forward
+ * direction. Singly-linked lists are ideal for applications with large
+ * datasets and few or no removals or for implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) do { \
+ (head)->lh_first = NULL; \
+} while (/*CONSTCOND*/0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+} while (/*CONSTCOND*/0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+} while (/*CONSTCOND*/0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+} while (/*CONSTCOND*/0)
+
+#define LIST_REMOVE(elm, field) do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+} while (/*CONSTCOND*/0)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = ((head)->lh_first); \
+ (var); \
+ (var) = ((var)->field.le_next))
+
+/*
+ * List access methods.
+ */
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) do { \
+ (head)->slh_first = NULL; \
+} while (/*CONSTCOND*/0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ (elm)->field.sle_next = (slistelm)->field.sle_next; \
+ (slistelm)->field.sle_next = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.sle_next = (head)->slh_first; \
+ (head)->slh_first = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ (head)->slh_first = (head)->slh_first->field.sle_next; \
+} while (/*CONSTCOND*/0)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if ((head)->slh_first == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = (head)->slh_first; \
+ while(curelm->field.sle_next != (elm)) \
+ curelm = curelm->field.sle_next; \
+ curelm->field.sle_next = \
+ curelm->field.sle_next->field.sle_next; \
+ } \
+} while (/*CONSTCOND*/0)
+
+#define SLIST_FOREACH(var, head, field) \
+ for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next)
+
+/*
+ * Singly-linked List access methods.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+#define SLIST_FIRST(head) ((head)->slh_first)
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+struct name { \
+ struct type *stqh_first; /* first element */ \
+ struct type **stqh_last; /* addr of last next element */ \
+}
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type) \
+struct { \
+ struct type *stqe_next; /* next element */ \
+}
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_INIT(head) do { \
+ (head)->stqh_first = NULL; \
+ (head)->stqh_last = &(head)->stqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \
+ (head)->stqh_last = &(elm)->field.stqe_next; \
+ (head)->stqh_first = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.stqe_next = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &(elm)->field.stqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\
+ (head)->stqh_last = &(elm)->field.stqe_next; \
+ (listelm)->field.stqe_next = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \
+ (head)->stqh_last = &(head)->stqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define STAILQ_REMOVE(head, elm, type, field) do { \
+ if ((head)->stqh_first == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->stqh_first; \
+ while (curelm->field.stqe_next != (elm)) \
+ curelm = curelm->field.stqe_next; \
+ if ((curelm->field.stqe_next = \
+ curelm->field.stqe_next->field.stqe_next) == NULL) \
+ (head)->stqh_last = &(curelm)->field.stqe_next; \
+ } \
+} while (/*CONSTCOND*/0)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for ((var) = ((head)->stqh_first); \
+ (var); \
+ (var) = ((var)->field.stqe_next))
+
+/*
+ * Singly-linked Tail queue access methods.
+ */
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqh_first; /* first element */ \
+ struct type **sqh_last; /* addr of last next element */ \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqe_next; /* next element */ \
+}
+
+/*
+ * Simple queue functions.
+ */
+#define SIMPLEQ_INIT(head) do { \
+ (head)->sqh_first = NULL; \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (head)->sqh_first = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqe_next = NULL; \
+ *(head)->sqh_last = (elm); \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (listelm)->field.sqe_next = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_REMOVE(head, elm, type, field) do { \
+ if ((head)->sqh_first == (elm)) { \
+ SIMPLEQ_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->sqh_first; \
+ while (curelm->field.sqe_next != (elm)) \
+ curelm = curelm->field.sqe_next; \
+ if ((curelm->field.sqe_next = \
+ curelm->field.sqe_next->field.sqe_next) == NULL) \
+ (head)->sqh_last = &(curelm)->field.sqe_next; \
+ } \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = ((head)->sqh_first); \
+ (var); \
+ (var) = ((var)->field.sqe_next))
+
+/*
+ * Simple queue access methods.
+ */
+#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL)
+#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
+#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define _TAILQ_HEAD(name, type, qual) \
+struct name { \
+ qual type *tqh_first; /* first element */ \
+ qual type *qual *tqh_last; /* addr of last next element */ \
+}
+#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define _TAILQ_ENTRY(type, qual) \
+struct { \
+ qual type *tqe_next; /* next element */ \
+ qual type *qual *tqe_prev; /* address of previous next element */\
+}
+#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,)
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = ((head)->tqh_first); \
+ (var); \
+ (var) = ((var)->field.tqe_next))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \
+ (var); \
+ (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
+
+/*
+ * Tail queue access methods.
+ */
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type) \
+struct name { \
+ struct type *cqh_first; /* first element */ \
+ struct type *cqh_last; /* last element */ \
+}
+
+#define CIRCLEQ_HEAD_INITIALIZER(head) \
+ { (void *)&head, (void *)&head }
+
+#define CIRCLEQ_ENTRY(type) \
+struct { \
+ struct type *cqe_next; /* next element */ \
+ struct type *cqe_prev; /* previous element */ \
+}
+
+/*
+ * Circular queue functions.
+ */
+#define CIRCLEQ_INIT(head) do { \
+ (head)->cqh_first = (void *)(head); \
+ (head)->cqh_last = (void *)(head); \
+} while (/*CONSTCOND*/0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm)->field.cqe_next; \
+ (elm)->field.cqe_prev = (listelm); \
+ if ((listelm)->field.cqe_next == (void *)(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (listelm)->field.cqe_next->field.cqe_prev = (elm); \
+ (listelm)->field.cqe_next = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm); \
+ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
+ if ((listelm)->field.cqe_prev == (void *)(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (listelm)->field.cqe_prev->field.cqe_next = (elm); \
+ (listelm)->field.cqe_prev = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.cqe_next = (head)->cqh_first; \
+ (elm)->field.cqe_prev = (void *)(head); \
+ if ((head)->cqh_last == (void *)(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (head)->cqh_first->field.cqe_prev = (elm); \
+ (head)->cqh_first = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.cqe_next = (void *)(head); \
+ (elm)->field.cqe_prev = (head)->cqh_last; \
+ if ((head)->cqh_first == (void *)(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (head)->cqh_last->field.cqe_next = (elm); \
+ (head)->cqh_last = (elm); \
+} while (/*CONSTCOND*/0)
+
+#define CIRCLEQ_REMOVE(head, elm, field) do { \
+ if ((elm)->field.cqe_next == (void *)(head)) \
+ (head)->cqh_last = (elm)->field.cqe_prev; \
+ else \
+ (elm)->field.cqe_next->field.cqe_prev = \
+ (elm)->field.cqe_prev; \
+ if ((elm)->field.cqe_prev == (void *)(head)) \
+ (head)->cqh_first = (elm)->field.cqe_next; \
+ else \
+ (elm)->field.cqe_prev->field.cqe_next = \
+ (elm)->field.cqe_next; \
+} while (/*CONSTCOND*/0)
+
+#define CIRCLEQ_FOREACH(var, head, field) \
+ for ((var) = ((head)->cqh_first); \
+ (var) != (const void *)(head); \
+ (var) = ((var)->field.cqe_next))
+
+#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
+ for ((var) = ((head)->cqh_last); \
+ (var) != (const void *)(head); \
+ (var) = ((var)->field.cqe_prev))
+
+/*
+ * Circular queue access methods.
+ */
+#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head))
+#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
+#define CIRCLEQ_LAST(head) ((head)->cqh_last)
+#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
+#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
+
+#define CIRCLEQ_LOOP_NEXT(head, elm, field) \
+ (((elm)->field.cqe_next == (void *)(head)) \
+ ? ((head)->cqh_first) \
+ : (elm->field.cqe_next))
+#define CIRCLEQ_LOOP_PREV(head, elm, field) \
+ (((elm)->field.cqe_prev == (void *)(head)) \
+ ? ((head)->cqh_last) \
+ : (elm->field.cqe_prev))
+
+#endif /* sys/queue.h */
+
View
154 test/test-050-player00-track.js
@@ -2,62 +2,118 @@ var sp = require('../lib/libspotify');
var cred = require('../spotify_key/passwd');
var testutil = require('./util');
var trycatch = require('trycatch');
-var portaudio = require('portaudio');
-
+var portaudio = null;
+try {
+ portaudio = require('portaudio');
+}
+catch(e) {
+}
+var fs = require('fs');
var session = null;
var pa = null;
-
-//exports.setUp = function(cb) {
- //portaudio.open({
- //sampleFormat: portaudio.SampleFormat16Bit,
- //channelCount: 2,
- //sampleRate: 44100
- //}, function(err, p) {
- //if(err) return cb(err);
- //pa = p;
- //testutil.getDefaultTestSession(function(s) {
- //session = s;
- //cb();
- //})
- //});
-//};
+var f = null;
exports.setUp = function(cb) {
- testutil.getDefaultTestSession(function(s) {
- session = s;
- cb();
- })
+ if(portaudio) {
+ portaudio.open({
+ sampleFormat: portaudio.SampleFormat16Bit,
+ channelCount: 2,
+ sampleRate: 44100
+ }, function(err, p) {
+ if(err) return cb(err);
+ pa = p;
+ testutil.getDefaultTestSession(function(s) {
+ session = s;
+ cb();
+ })
+ });
+ }
+ else {
+ testutil.getDefaultTestSession(function(s) {
+ session = s;
+ cb();
+ })
+ }
};
+
+//exports.setUp = function(cb) {
+ //testutil.getDefaultTestSession(function(s) {
+ //session = s;
+ //cb();
+ //})
+//};
exports.testPlaySingleGuillemotsTrack = function(test) {
var search = new sp.Search('artist:"Guillemots" track:"Fleet"');
- trycatch(function() {
- search.execute(function() {
- var track = search.tracks[0];
- test.ok(track.isReady(), 'track should be loaded');
- var player = session.getPlayer();
- test.doesNotThrow(function() {
- player.load(track);
- }, "loading a track should not throw");
- player.play();
- console.log('playing track, this may take some time');
- //player.pipe(pa);
- var got_data = false;
- player.once('data', function(chunk) {
- test.ok(chunk.length > 0, "we should get samples from the player");
- got_data = true;
+ var f = fs.createWriteStream('/tmp/node-libspotify/guillemots.raw');
+ f.on('open', function() {
+ trycatch(function() {
+ search.execute(function() {
+ var track = search.tracks[0];
+ test.ok(track.isReady(), 'track should be loaded');
+ var player = session.getPlayer();
+ test.doesNotThrow(function() {
+ player.load(track);
+ }, "loading a track should not throw");
+ player.play();
+ console.log('playing track, this may take some time');
+ if(pa) player.pipe(pa);
+ player.pipe(f);
+ var got_data = false;
+ player.once('data', function(chunk) {
+ test.ok(chunk.length > 0, "we should get samples from the player");
+ got_data = true;
+ });
+ setTimeout(function() {
+ if(pa) pa.start();
+ }, 500);
+ setTimeout(function() {
+ if(pa) pa.stop();
+ player.stop();
+ f.end();
+ test.ok(got_data, "we should have received some data from the player");
+ test.done();
+ }, 2000);
});
- setTimeout(function() {
- //pa.start();
- }, 500);
- setTimeout(function() {
- //pa.stop();
- player.stop();
- test.ok(got_data, "we should have received some data from the player");
- test.done();
- }, 2000);
- });
- }, /* catch */ function(e) {
- test.done(e);
- })
+ }, /* catch */ function(e) {
+ test.done(e);
+ })
+ });
+}
+exports.testPlaySingleAlJArreauTrack = function(test) {
+ var search = new sp.Search('artist:"Al Jarreau" track:"Boogie down"');
+ var f = fs.createWriteStream('/tmp/node-libspotify/al-jarreau.raw');
+ f.on('open', function() {
+ trycatch(function() {
+ search.execute(function() {
+ var track = search.tracks[0];
+ test.ok(track.isReady(), 'track should be loaded');
+ var player = session.getPlayer();
+ test.doesNotThrow(function() {
+ player.load(track);
+ }, "loading a track should not throw");
+ player.play();
+ console.log('playing track, this may take some time');
+ if(pa) player.pipe(pa);
+ player.pipe(f);
+ var got_data = false;
+ player.once('data', function(chunk) {
+ test.ok(chunk.length > 0, "we should get samples from the player");
+ got_data = true;
+ });
+ setTimeout(function() {
+ if(pa) pa.start();
+ }, 500);
+ setTimeout(function() {
+ if(pa) pa.stop();
+ player.stop();
+ f.end();
+ test.ok(got_data, "we should have received some data from the player");
+ test.done();
+ }, 2000);
+ });
+ }, /* catch */ function(e) {
+ test.done(e);
+ })
+ });
}

0 comments on commit abaa4bb

Please sign in to comment.