mirror of
https://github.com/holub/mame
synced 2025-04-22 00:11:58 +03:00
285 lines
8.3 KiB
C
285 lines
8.3 KiB
C
/* pmutil.c -- some helpful utilities for building midi
|
|
applications that use PortMidi
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include "portmidi.h"
|
|
#include "pmutil.h"
|
|
#include "pminternal.h"
|
|
|
|
#if defined(WIN32) || defined(_MSC_VER)
|
|
#define bzero(addr, siz) memset(addr, 0, siz)
|
|
#endif
|
|
|
|
// #define QUEUE_DEBUG 1
|
|
#ifdef QUEUE_DEBUG
|
|
#include "stdio.h"
|
|
#endif
|
|
|
|
typedef struct {
|
|
long head;
|
|
long tail;
|
|
long len;
|
|
long overflow;
|
|
int32_t msg_size; /* number of int32_t in a message including extra word */
|
|
int32_t peek_overflow;
|
|
int32_t *buffer;
|
|
int32_t *peek;
|
|
int32_t peek_flag;
|
|
} PmQueueRep;
|
|
|
|
|
|
PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg)
|
|
{
|
|
int32_t int32s_per_msg =
|
|
(int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) &
|
|
~(sizeof(int32_t) - 1)) / sizeof(int32_t));
|
|
PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
|
|
if (!queue) /* memory allocation failed */
|
|
return NULL;
|
|
|
|
/* need extra word per message for non-zero encoding */
|
|
queue->len = num_msgs * (int32s_per_msg + 1);
|
|
queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t));
|
|
bzero(queue->buffer, queue->len * sizeof(int32_t));
|
|
if (!queue->buffer) {
|
|
pm_free(queue);
|
|
return NULL;
|
|
} else { /* allocate the "peek" buffer */
|
|
queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t));
|
|
if (!queue->peek) {
|
|
/* free everything allocated so far and return */
|
|
pm_free(queue->buffer);
|
|
pm_free(queue);
|
|
return NULL;
|
|
}
|
|
}
|
|
bzero(queue->buffer, queue->len * sizeof(int32_t));
|
|
queue->head = 0;
|
|
queue->tail = 0;
|
|
/* msg_size is in words */
|
|
queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */
|
|
queue->overflow = FALSE;
|
|
queue->peek_overflow = FALSE;
|
|
queue->peek_flag = FALSE;
|
|
return queue;
|
|
}
|
|
|
|
|
|
PMEXPORT PmError Pm_QueueDestroy(PmQueue *q)
|
|
{
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
|
|
/* arg checking */
|
|
if (!queue || !queue->buffer || !queue->peek)
|
|
return pmBadPtr;
|
|
|
|
pm_free(queue->peek);
|
|
pm_free(queue->buffer);
|
|
pm_free(queue);
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg)
|
|
{
|
|
long head;
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
int i;
|
|
int32_t *msg_as_int32 = (int32_t *) msg;
|
|
|
|
/* arg checking */
|
|
if (!queue)
|
|
return pmBadPtr;
|
|
/* a previous peek operation encountered an overflow, but the overflow
|
|
* has not yet been reported to client, so do it now. No message is
|
|
* returned, but on the next call, we will return the peek buffer.
|
|
*/
|
|
if (queue->peek_overflow) {
|
|
queue->peek_overflow = FALSE;
|
|
return pmBufferOverflow;
|
|
}
|
|
if (queue->peek_flag) {
|
|
memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t));
|
|
queue->peek_flag = FALSE;
|
|
return pmGotData;
|
|
}
|
|
|
|
head = queue->head;
|
|
/* if writer overflows, it writes queue->overflow = tail+1 so that
|
|
* when the reader gets to that position in the buffer, it can
|
|
* return the overflow condition to the reader. The problem is that
|
|
* at overflow, things have wrapped around, so tail == head, and the
|
|
* reader will detect overflow immediately instead of waiting until
|
|
* it reads everything in the buffer, wrapping around again to the
|
|
* point where tail == head. So the condition also checks that
|
|
* queue->buffer[head] is zero -- if so, then the buffer is now
|
|
* empty, and we're at the point in the msg stream where overflow
|
|
* occurred. It's time to signal overflow to the reader. If
|
|
* queue->buffer[head] is non-zero, there's a message there and we
|
|
* should read all the way around the buffer before signalling overflow.
|
|
* There is a write-order dependency here, but to fail, the overflow
|
|
* field would have to be written while an entire buffer full of
|
|
* writes are still pending. I'm assuming out-of-order writes are
|
|
* possible, but not that many.
|
|
*/
|
|
if (queue->overflow == head + 1 && !queue->buffer[head]) {
|
|
queue->overflow = 0; /* non-overflow condition */
|
|
return pmBufferOverflow;
|
|
}
|
|
|
|
/* test to see if there is data in the queue -- test from back
|
|
* to front so if writer is simultaneously writing, we don't
|
|
* waste time discovering the write is not finished
|
|
*/
|
|
for (i = queue->msg_size - 1; i >= 0; i--) {
|
|
if (!queue->buffer[head + i]) {
|
|
return pmNoData;
|
|
}
|
|
}
|
|
memcpy(msg, (char *) &queue->buffer[head + 1],
|
|
sizeof(int32_t) * (queue->msg_size - 1));
|
|
/* fix up zeros */
|
|
i = queue->buffer[head];
|
|
while (i < queue->msg_size) {
|
|
int32_t j;
|
|
i--; /* msg does not have extra word so shift down */
|
|
j = msg_as_int32[i];
|
|
msg_as_int32[i] = 0;
|
|
i = j;
|
|
}
|
|
/* signal that data has been removed by zeroing: */
|
|
bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size);
|
|
|
|
/* update head */
|
|
head += queue->msg_size;
|
|
if (head == queue->len) head = 0;
|
|
queue->head = head;
|
|
return pmGotData; /* success */
|
|
}
|
|
|
|
|
|
|
|
PMEXPORT PmError Pm_SetOverflow(PmQueue *q)
|
|
{
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
long tail;
|
|
/* arg checking */
|
|
if (!queue)
|
|
return pmBadPtr;
|
|
/* no more enqueue until receiver acknowledges overflow */
|
|
if (queue->overflow) return pmBufferOverflow;
|
|
tail = queue->tail;
|
|
queue->overflow = tail + 1;
|
|
return pmBufferOverflow;
|
|
}
|
|
|
|
|
|
PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg)
|
|
{
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
long tail;
|
|
int i;
|
|
int32_t *src = (int32_t *) msg;
|
|
int32_t *ptr;
|
|
int32_t *dest;
|
|
int rslt;
|
|
if (!queue)
|
|
return pmBadPtr;
|
|
/* no more enqueue until receiver acknowledges overflow */
|
|
if (queue->overflow) return pmBufferOverflow;
|
|
rslt = Pm_QueueFull(q);
|
|
/* already checked above: if (rslt == pmBadPtr) return rslt; */
|
|
tail = queue->tail;
|
|
if (rslt) {
|
|
queue->overflow = tail + 1;
|
|
return pmBufferOverflow;
|
|
}
|
|
|
|
/* queue is has room for message, and overflow flag is cleared */
|
|
ptr = &queue->buffer[tail];
|
|
dest = ptr + 1;
|
|
for (i = 1; i < queue->msg_size; i++) {
|
|
int32_t j = src[i - 1];
|
|
if (!j) {
|
|
*ptr = i;
|
|
ptr = dest;
|
|
} else {
|
|
*dest = j;
|
|
}
|
|
dest++;
|
|
}
|
|
*ptr = i;
|
|
tail += queue->msg_size;
|
|
if (tail == queue->len) tail = 0;
|
|
queue->tail = tail;
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PMEXPORT int Pm_QueueEmpty(PmQueue *q)
|
|
{
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
return (!queue) || /* null pointer -> return "empty" */
|
|
(queue->buffer[queue->head] == 0 && !queue->peek_flag);
|
|
}
|
|
|
|
|
|
PMEXPORT int Pm_QueueFull(PmQueue *q)
|
|
{
|
|
long tail;
|
|
int i;
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
/* arg checking */
|
|
if (!queue)
|
|
return pmBadPtr;
|
|
tail = queue->tail;
|
|
/* test to see if there is space in the queue */
|
|
for (i = 0; i < queue->msg_size; i++) {
|
|
if (queue->buffer[tail + i]) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PMEXPORT void *Pm_QueuePeek(PmQueue *q)
|
|
{
|
|
PmError rslt;
|
|
int32_t temp;
|
|
PmQueueRep *queue = (PmQueueRep *) q;
|
|
/* arg checking */
|
|
if (!queue)
|
|
return NULL;
|
|
|
|
if (queue->peek_flag) {
|
|
return queue->peek;
|
|
}
|
|
/* this is ugly: if peek_overflow is set, then Pm_Dequeue()
|
|
* returns immediately with pmBufferOverflow, but here, we
|
|
* want Pm_Dequeue() to really check for data. If data is
|
|
* there, we can return it
|
|
*/
|
|
temp = queue->peek_overflow;
|
|
queue->peek_overflow = FALSE;
|
|
rslt = Pm_Dequeue(q, queue->peek);
|
|
queue->peek_overflow = temp;
|
|
|
|
if (rslt == 1) {
|
|
queue->peek_flag = TRUE;
|
|
return queue->peek;
|
|
} else if (rslt == pmBufferOverflow) {
|
|
/* when overflow is indicated, the queue is empty and the
|
|
* first message that was dropped by Enqueue (signalling
|
|
* pmBufferOverflow to its caller) would have been the next
|
|
* message in the queue. Pm_QueuePeek will return NULL, but
|
|
* remember that an overflow occurred. (see Pm_Dequeue)
|
|
*/
|
|
queue->peek_overflow = TRUE;
|
|
}
|
|
return NULL;
|
|
}
|
|
|