mirror of
https://github.com/mauiaaron/apple2.git
synced 2025-02-09 18:31:03 +00:00
A simple audio buffer play queue implementation
This commit is contained in:
parent
be7a6267e2
commit
7a2d1725c7
@ -44,7 +44,8 @@ VIDEO_SRC = \
|
||||
|
||||
AUDIO_SRC = \
|
||||
src/audio/soundcore.c src/audio/soundcore-openal.c src/audio/speaker.c \
|
||||
src/audio/alhelpers.c src/audio/mockingboard.c src/audio/AY8910.c
|
||||
src/audio/playqueue.c src/audio/alhelpers.c src/audio/mockingboard.c \
|
||||
src/audio/AY8910.c
|
||||
|
||||
META_SRC = \
|
||||
src/meta/debug.l src/meta/debugger.c src/meta/opcodes.c \
|
||||
|
@ -226,8 +226,8 @@ AC_ARG_ENABLE([audio], AS_HELP_STRING([--disable-audio], [Disable emulator audio
|
||||
dnl found OpenAL ...
|
||||
AC_DEFINE(AUDIO_OPENAL, 1, [Enable OpenAL audio output])
|
||||
AC_DEFINE(AUDIO_ENABLED, 1, [Enable sound module])
|
||||
AUDIO_GLUE_C="src/audio/speaker.c src/audio/mockingboard.c"
|
||||
AUDIO_O="src/audio/soundcore.o src/audio/soundcore-openal.o src/audio/speaker.o src/audio/alhelpers.o src/audio/mockingboard.o src/audio/AY8910.o"
|
||||
AUDIO_GLUE_C="src/audio/speaker.c src/audio/mockingboard.c src/audio/playqueue.c"
|
||||
AUDIO_O="src/audio/soundcore.o src/audio/soundcore-openal.o src/audio/speaker.o src/audio/playqueue.o src/audio/alhelpers.o src/audio/mockingboard.o src/audio/AY8910.o"
|
||||
], [
|
||||
AC_MSG_WARN([Could not find OpenAL libraries, sound will be disabled])
|
||||
], [])
|
||||
|
801
src/audio/playqueue.c
Normal file
801
src/audio/playqueue.c
Normal file
@ -0,0 +1,801 @@
|
||||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 2 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* THERE ARE NO WARRANTIES WHATSOEVER.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "playqueue.h"
|
||||
|
||||
typedef struct PQListNode_s {
|
||||
struct PQListNode_s *next;
|
||||
struct PQListNode_s *prev;
|
||||
PlayNode_s node;
|
||||
} PQListNode_s;
|
||||
|
||||
typedef struct PQList_s {
|
||||
PQListNode_s *availNodes; // ->next only LL
|
||||
PQListNode_s *queuedNodes; // double LL
|
||||
PQListNode_s *queuedHead;
|
||||
} PQList_s;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static inline bool _iterate_list_and_find_node(INOUT PQListNode_s **listNodePtr, long nodeId) {
|
||||
while (*listNodePtr) {
|
||||
if ((*listNodePtr)->node.nodeId == nodeId) {
|
||||
return true;
|
||||
}
|
||||
*listNodePtr = (*listNodePtr)->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static long playq_enqueue(PlayQueue_s *_this, INOUT PlayNode_s *node) {
|
||||
long err = 0;
|
||||
|
||||
do {
|
||||
PQList_s *list = (PQList_s *)(_this->_internal);
|
||||
|
||||
// detach a node from the available pool
|
||||
PQListNode_s *listNode = list->availNodes;
|
||||
if (!listNode) {
|
||||
ERRLOG("Cannot enqueue: no slots available");
|
||||
err = -1;
|
||||
break;
|
||||
}
|
||||
list->availNodes = listNode->next;
|
||||
|
||||
// exchange data
|
||||
listNode->node.numBytes = node->numBytes;
|
||||
listNode->node.bytes = node->bytes;
|
||||
node->nodeId = listNode->node.nodeId;
|
||||
|
||||
// enqueue node
|
||||
listNode->next = list->queuedNodes;
|
||||
listNode->prev = NULL;
|
||||
if (list->queuedNodes) {
|
||||
list->queuedNodes->prev = listNode;
|
||||
}
|
||||
list->queuedNodes = listNode;
|
||||
|
||||
// reset head
|
||||
if (listNode->next == NULL) {
|
||||
list->queuedHead = listNode;
|
||||
}
|
||||
} while (0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static long playq_dequeue(PlayQueue_s *_this, OUTPARM PlayNode_s *node) {
|
||||
PQList_s *list = (PQList_s *)(_this->_internal);
|
||||
if (node) {
|
||||
*node = (PlayNode_s){ 0 };
|
||||
}
|
||||
|
||||
bool found = (list->queuedHead != NULL);
|
||||
if (found) {
|
||||
PQListNode_s *listNode = list->queuedHead;
|
||||
|
||||
// detach from queued
|
||||
list->queuedHead = list->queuedHead->prev;
|
||||
if (list->queuedHead) {
|
||||
list->queuedHead->next = NULL;
|
||||
} else {
|
||||
list->queuedNodes = NULL;
|
||||
}
|
||||
|
||||
// copy data
|
||||
if (node) {
|
||||
*node = listNode->node;
|
||||
}
|
||||
|
||||
// attach to available pool
|
||||
listNode->prev = NULL;
|
||||
listNode->next = list->availNodes;
|
||||
list->availNodes = listNode;
|
||||
|
||||
/*listNode->node.nodeId = 0;IMMUTABLE*/
|
||||
listNode->node.numBytes = 0;
|
||||
listNode->node.bytes = NULL;
|
||||
} else {
|
||||
assert(list->queuedNodes == NULL);
|
||||
}
|
||||
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
static long playq_remove(PlayQueue_s *_this, INOUT PlayNode_s *node) {
|
||||
|
||||
PQList_s *list = (PQList_s *)(_this->_internal);
|
||||
PQListNode_s *listNode = list->queuedNodes;
|
||||
|
||||
bool found = _iterate_list_and_find_node(&listNode, node->nodeId);
|
||||
if (found) {
|
||||
|
||||
// reset head
|
||||
if (listNode->next == NULL) {
|
||||
list->queuedHead = listNode->prev;
|
||||
}
|
||||
|
||||
// detach listNode from queued list ...
|
||||
if (listNode->prev) {
|
||||
listNode->prev->next = listNode->next;
|
||||
if (listNode->next) {
|
||||
listNode->next->prev = listNode->prev;
|
||||
}
|
||||
} else {
|
||||
list->queuedNodes = listNode->next;
|
||||
if (listNode->next) {
|
||||
listNode->next->prev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
*node = listNode->node; // copy data
|
||||
|
||||
listNode->next = list->availNodes;
|
||||
listNode->prev = NULL;
|
||||
list->availNodes = listNode;
|
||||
|
||||
/*listNode->node.nodeId = 0;IMMUTABLE*/
|
||||
listNode->node.numBytes = 0;
|
||||
listNode->node.bytes = NULL;
|
||||
}
|
||||
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
static void playq_drain(PlayQueue_s *_this) {
|
||||
long err = 0;
|
||||
do {
|
||||
err = _this->Dequeue(_this, NULL);
|
||||
} while (err == 0);
|
||||
}
|
||||
|
||||
static long playq_getHead(PlayQueue_s *_this, OUTPARM PlayNode_s *node) {
|
||||
long err = 0;
|
||||
|
||||
PQList_s *list = (PQList_s *)(_this->_internal);
|
||||
if (list->queuedHead) {
|
||||
*node = list->queuedHead->node;
|
||||
} else {
|
||||
*node = (PlayNode_s){ 0 };
|
||||
err = -1;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static long playq_get(PlayQueue_s *_this, OUTPARM PlayNode_s *node) {
|
||||
long err = 0;
|
||||
|
||||
PQList_s *list = (PQList_s *)(_this->_internal);
|
||||
PQListNode_s *listNode = list->queuedNodes;
|
||||
|
||||
_iterate_list_and_find_node(&listNode, node->nodeId);
|
||||
if (listNode) {
|
||||
*node = listNode->node;
|
||||
} else {
|
||||
*node = (PlayNode_s){ 0 };
|
||||
err = -1;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static bool playq_canEnqueue(PlayQueue_s *_this) {
|
||||
PQList_s *list = (PQList_s *)(_this->_internal);
|
||||
return (list->availNodes != NULL);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void playq_destroyPlayQueue(INOUT PlayQueue_s **queue) {
|
||||
|
||||
if (!(*queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
playq_drain(*queue);
|
||||
|
||||
PQList_s *list = (PQList_s *)((*queue)->_internal);
|
||||
if (list) {
|
||||
PQListNode_s *node = list->availNodes;
|
||||
while (node) {
|
||||
PQListNode_s *p = node;
|
||||
node = node->next;
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
FREE(*queue);
|
||||
}
|
||||
|
||||
PlayQueue_s *playq_createPlayQueue(const long *nodeIdPtr, unsigned long numBuffers) {
|
||||
PlayQueue_s *playq = NULL;
|
||||
|
||||
assert(numBuffers <= MAX_PLAYQ_BUFFERS);
|
||||
|
||||
do {
|
||||
playq = malloc(sizeof(PlayQueue_s));
|
||||
if (!playq) {
|
||||
ERRLOG("no memory");
|
||||
break;
|
||||
}
|
||||
|
||||
PQList_s *list = calloc(1, sizeof(PQList_s));
|
||||
playq->_internal = list;
|
||||
if (!list) {
|
||||
ERRLOG("no memory");
|
||||
break;
|
||||
}
|
||||
|
||||
bool allocSuccess = true;
|
||||
for (unsigned long i=0; i<numBuffers; i++) {
|
||||
PQListNode_s *listNode = calloc(1, sizeof(PQListNode_s));
|
||||
LOG("CREATING PlayNode_s node ID: %ld", nodeIdPtr[i]);
|
||||
listNode->node.nodeId = nodeIdPtr[i];
|
||||
if (!listNode) {
|
||||
ERRLOG("no memory");
|
||||
allocSuccess = false;
|
||||
break;
|
||||
}
|
||||
listNode->next = list->availNodes;
|
||||
list->availNodes = listNode;
|
||||
}
|
||||
if (!allocSuccess) {
|
||||
break;
|
||||
}
|
||||
|
||||
playq->Enqueue = &playq_enqueue;
|
||||
playq->Dequeue = &playq_dequeue;
|
||||
playq->Remove = &playq_remove;
|
||||
playq->Drain = &playq_drain;
|
||||
playq->GetHead = &playq_getHead;
|
||||
playq->Get = &playq_get;
|
||||
playq->CanEnqueue = &playq_canEnqueue;
|
||||
|
||||
return playq;
|
||||
} while (0);
|
||||
|
||||
if (playq) {
|
||||
playq_destroyPlayQueue(&playq);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define SELF_TEST 0
|
||||
#if SELF_TEST
|
||||
bool do_logging = true;
|
||||
FILE *error_log = NULL;
|
||||
|
||||
static void _test_creation(void) {
|
||||
LOG("begin test");
|
||||
const unsigned long maxNodes = 8;
|
||||
long nodeIds[maxNodes];
|
||||
for (unsigned long i=0; i<maxNodes; i++) {
|
||||
nodeIds[i] = i+42;
|
||||
}
|
||||
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
|
||||
assert(pq != NULL);
|
||||
playq_destroyPlayQueue(&pq);
|
||||
assert(pq == NULL);
|
||||
}
|
||||
|
||||
static void _test_internal_list_creation_integrity(void) {
|
||||
LOG("begin test");
|
||||
const unsigned long maxNodes = 8;
|
||||
long nodeIds[maxNodes];
|
||||
for (unsigned long i=0; i<maxNodes; i++) {
|
||||
nodeIds[i] = i+42;
|
||||
}
|
||||
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
|
||||
assert(pq != NULL);
|
||||
assert(pq->_internal != NULL);
|
||||
|
||||
PQList_s *list = (PQList_s *)(pq->_internal);
|
||||
|
||||
assert(list->availNodes);
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->queuedHead == NULL);
|
||||
|
||||
PQListNode_s *listNode = list->availNodes;
|
||||
unsigned int count = 0;
|
||||
while (listNode) {
|
||||
listNode = listNode->next;
|
||||
++count;
|
||||
}
|
||||
assert (count == maxNodes);
|
||||
|
||||
playq_destroyPlayQueue(&pq);
|
||||
assert(pq == NULL);
|
||||
}
|
||||
|
||||
static void _test_enqueue_dequeue(void) {
|
||||
LOG("begin test");
|
||||
const unsigned long maxNodes = 4;
|
||||
long nodeIds[maxNodes];
|
||||
for (unsigned long i=0; i<maxNodes; i++) {
|
||||
nodeIds[i] = i+42;
|
||||
}
|
||||
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
|
||||
assert(pq != NULL);
|
||||
|
||||
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
|
||||
unsigned long numBytes = 42;
|
||||
|
||||
PQList_s *list = (PQList_s *)(pq->_internal);
|
||||
|
||||
assert(list->availNodes);
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->queuedHead == NULL);
|
||||
|
||||
long err = 0;
|
||||
|
||||
PlayNode_s node0 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node0);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->queuedNodes);
|
||||
assert(list->queuedHead);
|
||||
|
||||
PlayNode_s node1 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node1);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node2 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node2);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node3 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node3);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->availNodes == NULL);
|
||||
|
||||
// test over-enqueue
|
||||
|
||||
PlayNode_s node4 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node4);
|
||||
assert(err != 0 && "this should fail");
|
||||
|
||||
// check internal list integrity forward
|
||||
|
||||
PQListNode_s *listNode = list->queuedNodes;
|
||||
assert(listNode->node.nodeId == node3.nodeId);
|
||||
assert(listNode->node.numBytes == node3.numBytes);
|
||||
assert(listNode->node.bytes == node3.bytes);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->node.nodeId == node2.nodeId);
|
||||
assert(listNode->node.numBytes == node2.numBytes);
|
||||
assert(listNode->node.bytes == node2.bytes);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->node.nodeId == node1.nodeId);
|
||||
assert(listNode->node.numBytes == node1.numBytes);
|
||||
assert(listNode->node.bytes == node1.bytes);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode == list->queuedHead);
|
||||
assert(listNode->next == NULL);
|
||||
|
||||
assert(listNode->node.nodeId == node0.nodeId);
|
||||
assert(listNode->node.numBytes == node0.numBytes);
|
||||
assert(listNode->node.bytes == node0.bytes);
|
||||
|
||||
// check internal list integrity backward
|
||||
|
||||
listNode = listNode->prev;
|
||||
PQListNode_s *prevHead1 = listNode;
|
||||
assert(listNode->node.nodeId == node1.nodeId);
|
||||
assert(listNode->node.numBytes == node1.numBytes);
|
||||
assert(listNode->node.bytes == node1.bytes);
|
||||
|
||||
listNode = listNode->prev;
|
||||
PQListNode_s *prevHead2 = listNode;
|
||||
assert(listNode->node.nodeId == node2.nodeId);
|
||||
assert(listNode->node.numBytes == node2.numBytes);
|
||||
assert(listNode->node.bytes == node2.bytes);
|
||||
|
||||
listNode = listNode->prev;
|
||||
PQListNode_s *prevHead3 = listNode;
|
||||
assert(listNode->node.nodeId == node3.nodeId);
|
||||
assert(listNode->node.numBytes == node3.numBytes);
|
||||
assert(listNode->node.bytes == node3.bytes);
|
||||
|
||||
assert(listNode == list->queuedNodes);
|
||||
assert(prevHead3 == list->queuedNodes);
|
||||
assert(listNode->prev == NULL);
|
||||
|
||||
// test one dequeue
|
||||
|
||||
PlayNode_s dqNode = { 0 };
|
||||
err = pq->Dequeue(pq, &dqNode);
|
||||
assert(err == 0);
|
||||
assert(dqNode.nodeId == node0.nodeId);
|
||||
assert(dqNode.numBytes == node0.numBytes);
|
||||
assert(dqNode.bytes == node0.bytes);
|
||||
assert(list->queuedHead == prevHead1);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes->next == NULL);
|
||||
|
||||
// test successful re-enqueue of node4
|
||||
|
||||
err = pq->Enqueue(pq, &node4);
|
||||
assert(err == 0);
|
||||
assert(list->availNodes == NULL);
|
||||
assert(list->queuedHead == prevHead1);
|
||||
assert(list->queuedNodes != prevHead3);
|
||||
|
||||
// test dequeue all the things ...
|
||||
|
||||
err = pq->Dequeue(pq, &dqNode);
|
||||
assert(err == 0);
|
||||
assert(dqNode.nodeId == node1.nodeId);
|
||||
assert(dqNode.numBytes == node1.numBytes);
|
||||
assert(dqNode.bytes == node1.bytes);
|
||||
assert(list->queuedHead == prevHead2);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes->next == NULL);
|
||||
|
||||
err = pq->Dequeue(pq, &dqNode);
|
||||
assert(err == 0);
|
||||
assert(dqNode.nodeId == node2.nodeId);
|
||||
assert(dqNode.numBytes == node2.numBytes);
|
||||
assert(dqNode.bytes == node2.bytes);
|
||||
assert(list->queuedHead == prevHead3);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes->next != NULL);
|
||||
assert(list->availNodes->next->next == NULL);
|
||||
|
||||
err = pq->Dequeue(pq, &dqNode);
|
||||
assert(err == 0);
|
||||
assert(dqNode.nodeId == node3.nodeId);
|
||||
assert(dqNode.numBytes == node3.numBytes);
|
||||
assert(dqNode.bytes == node3.bytes);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes->next != NULL);
|
||||
assert(list->availNodes->next->next != NULL);
|
||||
assert(list->availNodes->next->next->next == NULL);
|
||||
|
||||
err = pq->Dequeue(pq, &dqNode);
|
||||
assert(err == 0);
|
||||
assert(dqNode.nodeId == node4.nodeId);
|
||||
assert(dqNode.numBytes == node4.numBytes);
|
||||
assert(dqNode.bytes == node4.bytes);
|
||||
assert(list->queuedHead == NULL);
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes->next != NULL);
|
||||
assert(list->availNodes->next->next != NULL);
|
||||
assert(list->availNodes->next->next->next != NULL);
|
||||
assert(list->availNodes->next->next->next->next == NULL);
|
||||
|
||||
// test over-dequeue
|
||||
err = pq->Dequeue(pq, &dqNode);
|
||||
assert (err != 0 && "cannot dequeue with nothing there");
|
||||
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->queuedHead == NULL);
|
||||
|
||||
// cleanup
|
||||
|
||||
playq_destroyPlayQueue(&pq);
|
||||
assert(pq == NULL);
|
||||
}
|
||||
|
||||
static void _test_remove_head_of_queue(void) {
|
||||
LOG("begin test");
|
||||
const unsigned long maxNodes = 4;
|
||||
long nodeIds[maxNodes];
|
||||
for (unsigned long i=0; i<maxNodes; i++) {
|
||||
nodeIds[i] = i+42;
|
||||
}
|
||||
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
|
||||
assert(pq != NULL);
|
||||
|
||||
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
|
||||
unsigned long numBytes = 42;
|
||||
long err = 0;
|
||||
|
||||
PQList_s *list = (PQList_s *)(pq->_internal);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->queuedHead == NULL);
|
||||
|
||||
PlayNode_s node0 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node0);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->queuedNodes != NULL);
|
||||
assert(list->queuedHead != NULL);
|
||||
PQListNode_s *listHead0 = list->queuedHead;
|
||||
|
||||
PlayNode_s node1 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node1);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node2 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node2);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node3 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node3);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->queuedHead == listHead0);
|
||||
|
||||
// dequeue head node using remove
|
||||
|
||||
assert(list->availNodes == NULL);
|
||||
|
||||
PlayNode_s dqNode = {
|
||||
.nodeId = node0.nodeId,
|
||||
};
|
||||
err = pq->Remove(pq, &dqNode);
|
||||
assert(dqNode.nodeId == node0.nodeId);
|
||||
assert(dqNode.numBytes == node0.numBytes);
|
||||
assert(dqNode.bytes == node0.bytes);
|
||||
|
||||
// check integrity of inner list
|
||||
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes == listHead0);
|
||||
assert(list->availNodes->next == NULL);
|
||||
assert(list->availNodes->prev == NULL);
|
||||
assert(list->queuedNodes != NULL);
|
||||
assert(list->queuedHead != NULL);
|
||||
|
||||
PQListNode_s *listNode = list->queuedNodes;
|
||||
assert(listNode->prev == NULL);
|
||||
assert(listNode->next->prev == listNode);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->next->prev == listNode);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->next == NULL);
|
||||
assert(list->queuedHead == listNode);
|
||||
|
||||
// cleanup
|
||||
|
||||
playq_destroyPlayQueue(&pq);
|
||||
assert(pq == NULL);
|
||||
}
|
||||
|
||||
static void _test_remove_tail_of_queue(void) {
|
||||
LOG("begin test");
|
||||
const unsigned long maxNodes = 4;
|
||||
long nodeIds[maxNodes];
|
||||
for (unsigned long i=0; i<maxNodes; i++) {
|
||||
nodeIds[i] = i+42;
|
||||
}
|
||||
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
|
||||
assert(pq != NULL);
|
||||
|
||||
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
|
||||
unsigned long numBytes = 42;
|
||||
long err = 0;
|
||||
|
||||
PQList_s *list = (PQList_s *)(pq->_internal);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->queuedHead == NULL);
|
||||
|
||||
PlayNode_s node0 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node0);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->queuedNodes != NULL);
|
||||
assert(list->queuedHead != NULL);
|
||||
PQListNode_s *listHead0 = list->queuedHead;
|
||||
|
||||
PlayNode_s node1 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node1);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node2 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node2);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node3 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node3);
|
||||
assert(err == 0);
|
||||
PQListNode_s *listHead3 = list->queuedNodes;
|
||||
|
||||
assert(list->queuedHead == listHead0);
|
||||
|
||||
// dequeue head node using remove
|
||||
|
||||
assert(list->availNodes == NULL);
|
||||
|
||||
PlayNode_s dqNode = {
|
||||
.nodeId = node3.nodeId,
|
||||
};
|
||||
err = pq->Remove(pq, &dqNode);
|
||||
assert(dqNode.nodeId == node3.nodeId);
|
||||
assert(dqNode.numBytes == node3.numBytes);
|
||||
assert(dqNode.bytes == node3.bytes);
|
||||
|
||||
// check integrity of inner list
|
||||
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes == listHead3);
|
||||
assert(list->availNodes->prev == NULL);
|
||||
assert(list->availNodes->next == NULL);
|
||||
assert(list->queuedNodes != NULL);
|
||||
assert(list->queuedHead != NULL);
|
||||
assert(list->queuedHead == listHead0);
|
||||
|
||||
PQListNode_s *listNode = list->queuedNodes;
|
||||
assert(listNode->prev == NULL);
|
||||
assert(listNode->next->prev == listNode);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->next->prev == listNode);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->next == NULL);
|
||||
assert(list->queuedHead == listNode);
|
||||
|
||||
// cleanup
|
||||
|
||||
playq_destroyPlayQueue(&pq);
|
||||
assert(pq == NULL);
|
||||
}
|
||||
|
||||
static void _test_remove_middle_of_queue(void) {
|
||||
LOG("begin test");
|
||||
const unsigned long maxNodes = 4;
|
||||
long nodeIds[maxNodes];
|
||||
for (unsigned long i=0; i<maxNodes; i++) {
|
||||
nodeIds[i] = i+42;
|
||||
}
|
||||
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
|
||||
assert(pq != NULL);
|
||||
|
||||
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
|
||||
unsigned long numBytes = 42;
|
||||
long err = 0;
|
||||
|
||||
PQList_s *list = (PQList_s *)(pq->_internal);
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->queuedNodes == NULL);
|
||||
assert(list->queuedHead == NULL);
|
||||
|
||||
PlayNode_s node0 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node0);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->queuedNodes != NULL);
|
||||
assert(list->queuedHead != NULL);
|
||||
PQListNode_s *listHead0 = list->queuedHead;
|
||||
|
||||
PlayNode_s node1 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node1);
|
||||
assert(err == 0);
|
||||
|
||||
PlayNode_s node2 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node2);
|
||||
assert(err == 0);
|
||||
PQListNode_s *listHead2 = list->queuedNodes;
|
||||
|
||||
PlayNode_s node3 = {
|
||||
.numBytes = numBytes++,
|
||||
.bytes = bytesPtr++,
|
||||
};
|
||||
err = pq->Enqueue(pq, &node3);
|
||||
assert(err == 0);
|
||||
|
||||
assert(list->queuedHead == listHead0);
|
||||
|
||||
// dequeue head node using remove
|
||||
|
||||
assert(list->availNodes == NULL);
|
||||
|
||||
PlayNode_s dqNode = {
|
||||
.nodeId = node2.nodeId,
|
||||
};
|
||||
err = pq->Remove(pq, &dqNode);
|
||||
assert(dqNode.nodeId == node2.nodeId);
|
||||
assert(dqNode.numBytes == node2.numBytes);
|
||||
assert(dqNode.bytes == node2.bytes);
|
||||
|
||||
// check integrity of inner list
|
||||
|
||||
assert(list->availNodes != NULL);
|
||||
assert(list->availNodes == listHead2);
|
||||
assert(list->availNodes->prev == NULL);
|
||||
assert(list->availNodes->next == NULL);
|
||||
assert(list->queuedNodes != NULL);
|
||||
assert(list->queuedHead != NULL);
|
||||
assert(list->queuedHead == listHead0);
|
||||
|
||||
PQListNode_s *listNode = list->queuedNodes;
|
||||
assert(listNode->prev == NULL);
|
||||
assert(listNode->next->prev == listNode);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->next->prev == listNode);
|
||||
|
||||
listNode = listNode->next;
|
||||
assert(listNode->next == NULL);
|
||||
assert(list->queuedHead == listNode);
|
||||
|
||||
// cleanup
|
||||
|
||||
playq_destroyPlayQueue(&pq);
|
||||
assert(pq == NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#warning use Valgrind to check proper memory management
|
||||
error_log = stdout;
|
||||
LOG("beginning tests");
|
||||
_test_creation();
|
||||
_test_internal_list_creation_integrity();
|
||||
_test_enqueue_dequeue();
|
||||
_test_remove_head_of_queue();
|
||||
_test_remove_tail_of_queue();
|
||||
_test_remove_middle_of_queue();
|
||||
LOG("all tests successful");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
63
src/audio/playqueue.h
Normal file
63
src/audio/playqueue.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 2 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* THERE ARE NO WARRANTIES WHATSOEVER.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* A simple audio buffer play queue.
|
||||
*
|
||||
* WARNING : non-thread-safe API ... locking is callee's responsibility (if needed)
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PLAYQUEUE_H_
|
||||
#define _PLAYQUEUE_H_
|
||||
|
||||
#define MAX_PLAYQ_BUFFERS 16
|
||||
#define INVALID_NODE_ID INT_MIN
|
||||
|
||||
typedef struct PlayNode_s {
|
||||
long nodeId;
|
||||
unsigned long numBytes;
|
||||
uint8_t *bytes;
|
||||
} PlayNode_s;
|
||||
|
||||
typedef struct PlayQueue_s {
|
||||
PRIVATE void *_internal;
|
||||
|
||||
// enqueues a node (IN : numBytes, bytes OUT : nodeId)
|
||||
long (*Enqueue)(struct PlayQueue_s *_this, INOUT PlayNode_s *node);
|
||||
|
||||
// dequeues the head of the queue (OUT : full PlayNode_s data if param is non-null)
|
||||
long (*Dequeue)(struct PlayQueue_s *_this, OUTPARM PlayNode_s *node);
|
||||
|
||||
// finds and removes a specific node (IN : nodeId OUT : full PlayNode_s data)
|
||||
long (*Remove)(struct PlayQueue_s *_this, INOUT PlayNode_s *node);
|
||||
|
||||
// removes all nodes from the queue
|
||||
void (*Drain)(struct PlayQueue_s *_this);
|
||||
|
||||
// gets the head node (OUT : full PlayNode_s data)
|
||||
long (*GetHead)(struct PlayQueue_s *_this, OUTPARM PlayNode_s *node);
|
||||
|
||||
// gets a reference to a specific node (IN : nodeId OUT : full PlayNode_s data)
|
||||
long (*Get)(struct PlayQueue_s *_this, INOUT PlayNode_s *node);
|
||||
|
||||
// true if we can enqueue moar data
|
||||
bool (*CanEnqueue)(struct PlayQueue_s *_this);
|
||||
} PlayQueue_s;
|
||||
|
||||
// create a play queue object
|
||||
PlayQueue_s *playq_createPlayQueue(const long *nodeIdPtr, unsigned long numBuffers);
|
||||
|
||||
// destroy a play queue object
|
||||
void playq_destroyPlayQueue(INOUT PlayQueue_s **queue);
|
||||
|
||||
#endif /* whole file */
|
||||
|
Loading…
x
Reference in New Issue
Block a user