mirror of
https://github.com/game-stop/veejay.git
synced 2025-12-18 05:40:02 +01:00
1742 lines
56 KiB
C
1742 lines
56 KiB
C
/*
|
|
* Copyright 2003 Chris Morgan <cmorgan@alum.wpi.edu>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* NOTE: All functions that take a jack_driver_t* do NOT lock the device, in order to get a */
|
|
/* jack_driver_t* you must call getDriver() which will pthread_mutex_lock() */
|
|
|
|
#include <config.h>
|
|
#ifdef HAVE_JACK
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
#include <jack/jack.h>
|
|
#include <pthread.h>
|
|
#include <sys/time.h>
|
|
#include <libvjmem/vjmem.h>
|
|
#include "bio2jack.h"
|
|
|
|
//FIXME: this is only true if we actually have messages
|
|
//FIXME: probably want to send all state changing messages like:
|
|
// setting reset, stopped, paused etc through messages
|
|
// if we don't do this we run the risk of setting reset, then
|
|
// sending a message and having things execute not in the order that
|
|
// the user sent them
|
|
|
|
/* enable/disable TRACING through the JACK_Callback() function */
|
|
/* this can sometimes be too much information */
|
|
/* NOTE: need to enable the general tracing as well */
|
|
#define CALLBACK_TRACE 0
|
|
|
|
/* set to 1 for verbose output */
|
|
#define VERBOSE_OUTPUT 0
|
|
|
|
/* set to 1 to enable tracing */
|
|
#define TRACE_ENABLE 0
|
|
|
|
/* set to 1 to enable tracing of getDriver() and releaseDriver() */
|
|
#define TRACE_getReleaseDevice 0
|
|
|
|
|
|
#define OUTFILE stderr
|
|
|
|
#if TRACE_ENABLE
|
|
#define TRACE(...) fprintf(OUTFILE, "%s:", __FUNCTION__), \
|
|
fprintf(OUTFILE, __VA_ARGS__), \
|
|
fflush(OUTFILE);
|
|
#else
|
|
#define TRACE(...) do{}while(0)
|
|
#endif
|
|
|
|
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
|
#define max(a,b) (((a) < (b)) ? (b) : (a))
|
|
|
|
|
|
/* Structure that holds a packet of wave output data from the client */
|
|
typedef struct wave_header_s
|
|
{
|
|
char* pData; /* pointer to the audio data */
|
|
long size; /* number of bytes pointed to by pData */
|
|
|
|
struct wave_header_s *pNext; /* pointer to the next wave_header_s */
|
|
} wave_header_t;
|
|
|
|
|
|
enum cmd_enum { CMD_SET_POSITION };
|
|
|
|
/* message passing structure */
|
|
typedef struct message_s
|
|
{
|
|
enum cmd_enum command;
|
|
long data;
|
|
|
|
struct message_s *pNext; /* pointer to the next message */
|
|
} message_t;
|
|
|
|
|
|
#define MAX_OUTPUT_PORTS 10
|
|
|
|
typedef struct jack_driver_s
|
|
{
|
|
int deviceID; /* id of this device */
|
|
long sample_rate; /* samples(frames) per second */
|
|
unsigned long num_input_channels; /* number of input channels(1 is mono, 2 stereo etc..) */
|
|
unsigned long num_output_channels; /* number of output channels(1 is mono, 2 stereo etc..) */
|
|
unsigned long bits_per_channel;
|
|
unsigned long bytes_per_output_frame; /* (num_output_channels * bits_per_channel) / 8 */
|
|
unsigned long bytes_per_input_frame; /* (num_input_channels * bits_per_channel) / 8 */
|
|
|
|
unsigned long latencyMS; /* latency in ms between writing and actual audio output of the written data */
|
|
|
|
long clientBytesInJack; /* number of INPUT bytes we wrote to jack(not necessary the number of bytes we wrote to jack */
|
|
unsigned long buffer_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */
|
|
|
|
char* sound_buffer;
|
|
struct timeval previousTime; /* time of last JACK_Callback() write to jack, allows for MS accurate bytes played */
|
|
|
|
unsigned long num_ticks;
|
|
unsigned long chunk_size;
|
|
unsigned long written_client_bytes; /* input bytes we wrote to jack, not necessarily actual bytes we wrote to jack due to channel and other conversion */
|
|
unsigned long played_client_bytes; /* input bytes that jack has played */
|
|
|
|
unsigned long client_bytes; /* total bytes written by the client via JACK_Write() */
|
|
|
|
jack_port_t* output_port[MAX_OUTPUT_PORTS]; /* output ports */
|
|
jack_client_t* client; /* pointer to jack client */
|
|
|
|
char **jack_port_name; /* user given strings for the port names, can be NULL */
|
|
unsigned int jack_port_name_count; /* the number of port names given */
|
|
unsigned long jack_port_flags; /* flags to be passed to jack when opening the output ports */
|
|
|
|
wave_header_t* pPlayPtr; /* pointer to the current wave header */
|
|
long playptr_offset; /* offset to not yet written bytes in pPlayPtr */
|
|
|
|
enum status_enum state; /* one of PLAYING, PAUSED, STOPPED, CLOSED, RESET etc*/
|
|
|
|
unsigned int volume[MAX_OUTPUT_PORTS]; /* percentage of sample value to preserve, 100 would be no attenuation */
|
|
enum JACK_VOLUME_TYPE volumeEffectType; /* linear or dbAttenuation, if dbAttenuation volume is the number of dBs of
|
|
attenuation to apply, 0 volume being no attenation, full volume */
|
|
|
|
long position_byte_offset; /* an offset that we will apply to returned position queries to achieve */
|
|
/* the position that the user of the driver desires set */
|
|
|
|
bool in_use; /* true if this device is currently in use */
|
|
|
|
message_t *pMessages; /* linked list of messages we are sending to the callback process */
|
|
|
|
pthread_mutex_t mutex;
|
|
|
|
/* variables used for trying to restart the connection to jack */
|
|
bool jackd_died; /* true if jackd has died and we should try to restart it */
|
|
struct timeval last_reconnect_attempt;
|
|
} jack_driver_t;
|
|
|
|
|
|
/* enable/disable code that allows us to close a device without actually closing the jack device */
|
|
/* this works around the issue where jack doesn't always close devices by the time the close function call returns */
|
|
#define JACK_CLOSE_HACK 1
|
|
|
|
typedef jack_default_audio_sample_t sample_t;
|
|
typedef jack_nframes_t nframes_t;
|
|
|
|
/* allocate devices for output */
|
|
static int first_free_device = 0;
|
|
#define MAX_OUTDEVICES 10
|
|
static jack_driver_t outDev[MAX_OUTDEVICES];
|
|
|
|
/* default audio buffer size */
|
|
//#define SECONDS .25
|
|
#define SECONDS .4
|
|
static long MAX_BUFFERED_BYTES = (16 * 2 * (44100/(1/SECONDS))) / 8; /* 16 bits, 2 channels .25 seconds */
|
|
//static long MAX_BUFFERED_BYTES = (16 * 2 * (44100/(1/SECONDS))) / 4; /* 16 bits, 2 channels .25 seconds */
|
|
|
|
|
|
#if JACK_CLOSE_HACK
|
|
static void JACK_CloseDevice(jack_driver_t* this, bool close_client);
|
|
#else
|
|
static void JACK_CloseDevice(jack_driver_t* this);
|
|
#endif
|
|
|
|
|
|
/* Prototypes */
|
|
static int JACK_OpenDevice(jack_driver_t* this);
|
|
static long JACK_GetBytesStoredFromThis(jack_driver_t *this);
|
|
static void JACK_ResetFromThis(jack_driver_t *this);
|
|
|
|
|
|
|
|
|
|
/* Return the difference between two timeval structures in terms of milliseconds */
|
|
long TimeValDifference(struct timeval *start, struct timeval *end)
|
|
{
|
|
double long ms; /* milliseconds value */
|
|
|
|
ms = end->tv_sec - start->tv_sec; /* compute seconds difference */
|
|
ms*=(double)1000; /* convert to milliseconds */
|
|
|
|
ms+=(double)(end->tv_usec - start->tv_usec) / (double)1000; /* add on microseconds difference */
|
|
|
|
return (long)ms;
|
|
}
|
|
|
|
/* get a device and lock the devices mutex */
|
|
/* */
|
|
/* also attempt to reconnect to jack since this function is called */
|
|
/* from most other bio2jack functions it provides a good point to attempt */
|
|
/* reconnection */
|
|
jack_driver_t *getDriver(int deviceID)
|
|
{
|
|
jack_driver_t *this = &outDev[deviceID];
|
|
#if TRACE_getReleaseDevice
|
|
TRACE("deviceID == %d\n", deviceID);
|
|
#endif
|
|
pthread_mutex_lock(&this->mutex);
|
|
|
|
/* should we try to restart the jack server? */
|
|
if(this->jackd_died && this->client == 0)
|
|
{
|
|
struct timeval now;
|
|
gettimeofday(&now, 0);
|
|
|
|
/* wait 250ms before trying again */
|
|
if(TimeValDifference(&this->last_reconnect_attempt, &now) >= 250)
|
|
{
|
|
JACK_OpenDevice(this);
|
|
this->last_reconnect_attempt = now;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/* release a device's mutex */
|
|
void releaseDriver(jack_driver_t *this)
|
|
{
|
|
#if TRACE_getReleaseDevice
|
|
TRACE("deviceID == %d\n", this->deviceID);
|
|
#endif
|
|
pthread_mutex_unlock(&this->mutex);
|
|
}
|
|
|
|
|
|
/* Return a string corresponding to the input state */
|
|
char* DEBUGSTATE(enum status_enum state)
|
|
{
|
|
if(state == PLAYING)
|
|
return "PLAYING";
|
|
else if(state == PAUSED)
|
|
return "PAUSED";
|
|
else if(state == STOPPED)
|
|
return "STOPPED";
|
|
else if(state == CLOSED)
|
|
return "CLOSED";
|
|
else if(state == RESET)
|
|
return "RESET";
|
|
else
|
|
return "unknown state";
|
|
}
|
|
|
|
|
|
#define SAMPLE_MAX_16BIT 32767.0f
|
|
|
|
/* floating point volume routine */
|
|
/* volume should be a value between 0.0 and 1.0 */
|
|
static void float_volume_effect(sample_t *buf, unsigned long nsamples, float volume)
|
|
{
|
|
if(volume < 0) volume = 0;
|
|
if(volume > 1.0) volume = 1.0;
|
|
|
|
while (nsamples--)
|
|
{
|
|
*buf = (*buf) * volume;
|
|
buf++;
|
|
}
|
|
}
|
|
|
|
/* convert from any number of source channels to any number of destination channels */
|
|
static void sample_move_d16_d16(short *dst, short *src,
|
|
unsigned long nsamples, int nDstChannels, int nSrcChannels)
|
|
{
|
|
int nSrcCount, nDstCount;
|
|
|
|
TRACE("nsamples == %ld, nDstChannels == %d, nSrcChannels == %d\n", nsamples, nDstChannels, nSrcChannels);
|
|
|
|
if(!nSrcChannels && !nDstChannels)
|
|
{
|
|
//ERR("nSrcChannels of %d, nDstChannels of %d, can't have zero channels\n", nSrcChannels, nDstChannels);
|
|
return; /* in the off chance we have zero channels somewhere */
|
|
}
|
|
|
|
while(nsamples--)
|
|
{
|
|
nSrcCount = nSrcChannels;
|
|
nDstCount = nDstChannels;
|
|
|
|
/* loop until all of our destination channels are filled */
|
|
while(nDstCount)
|
|
{
|
|
nSrcCount--;
|
|
nDstCount--;
|
|
|
|
*dst = *src; /* copy the data over */
|
|
dst++;
|
|
src++;
|
|
|
|
/* if we ran out of source channels but not destination channels */
|
|
/* then start the src channels back where we were */
|
|
if(!nSrcCount && nDstCount)
|
|
{
|
|
src-=nSrcChannels;
|
|
nSrcCount = nSrcChannels;
|
|
}
|
|
}
|
|
|
|
/* advance the the position */
|
|
src+=nSrcCount;
|
|
dst+=nDstCount;
|
|
}
|
|
}
|
|
|
|
/* convert from 16 bit to floating point */
|
|
/* channels to a buffer that will hold a single channel stream */
|
|
/* src_skip is in terms of 16bit samples */
|
|
static void sample_move_d16_s16 (sample_t *dst, short *src,
|
|
unsigned long nsamples, unsigned long src_skip)
|
|
{
|
|
/* ALERT: signed sign-extension portability !!! */
|
|
while (nsamples--)
|
|
{
|
|
*dst = (*src) / SAMPLE_MAX_16BIT;
|
|
dst++;
|
|
src += src_skip;
|
|
}
|
|
}
|
|
|
|
/* fill dst buffer with nsamples worth of silence */
|
|
void sample_silence_dS (sample_t *dst, unsigned long nsamples)
|
|
{
|
|
/* ALERT: signed sign-extension portability !!! */
|
|
while (nsamples--)
|
|
{
|
|
*dst = 0;
|
|
dst++;
|
|
}
|
|
}
|
|
|
|
/******************************************************************
|
|
* JACK_callback
|
|
*
|
|
* everytime the jack server wants something from us it calls this
|
|
* function, so we either deliver it some sound to play or deliver it nothing
|
|
* to play
|
|
*/
|
|
static int JACK_callback (nframes_t nframes, void *arg)
|
|
{
|
|
sample_t* out_buffer[MAX_OUTPUT_PORTS];
|
|
jack_driver_t* this = (jack_driver_t*)arg;
|
|
int i;
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("nframes %ld, sizeof(sample_t) == %d\n", (long)nframes, sizeof(sample_t));
|
|
#endif
|
|
|
|
// if(!this->client)
|
|
// ERR("client is closed, this is weird...\n");
|
|
|
|
if(nframes != this->chunk_size)
|
|
this->chunk_size = nframes;
|
|
|
|
/* retrieve the buffers for the output ports */
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
out_buffer[i] = (sample_t *) jack_port_get_buffer(this->output_port[i], nframes);
|
|
|
|
#if 0
|
|
/* process one message */
|
|
if(this->pMessages)
|
|
{
|
|
message_t *msg = this->pMessages;
|
|
|
|
/* process a set position command */
|
|
if(msg->command == CMD_SET_POSITION)
|
|
{
|
|
/* set the position byte offset based on the client_bytes and the desired */
|
|
/* offset */
|
|
this->position_byte_offset = msg->data - this->client_bytes;
|
|
|
|
/* make all positions the same location */
|
|
//FIXME: add the two other counters to this as well
|
|
this->written_jack_bytes = this->played_bytes = this->client_bytes;
|
|
|
|
this->clientBytesInJack = 0; /* no bytes left in jack else we will get bad byte counts */
|
|
|
|
if(this->pPlayPtr != 0)
|
|
{
|
|
TRACE("ERROR, setting position but pPlayPtr != 0\n");
|
|
TRACE("state == %s\n", DEBUGSTATE(this->state));
|
|
}
|
|
|
|
TRACE("deviceID(%d), setting position to byte offset of %ld\n", this->deviceID, msg->data);
|
|
}
|
|
|
|
#if 0
|
|
if(msg->command == WRITTEN)
|
|
{
|
|
this->client_bytes = msg->data;
|
|
TRACE("deviceID(%d), setting client_bytes to %d\n", this->deviceID, this->client_bytes);
|
|
} else if(msg->command == WRITTEN_TO_JACK)
|
|
{
|
|
this->written_jack_bytes = msg->data;
|
|
TRACE("deviceID(%d), setting written_jack_bytes to %d\n", this->deviceID, this->written_jack_bytes);
|
|
}
|
|
else if(msg->command == PLAYED) /* type is PLAYED */
|
|
{
|
|
this->played_bytes = msg->data;
|
|
TRACE("deviceID(%d), setting played_bytes to %d\n", this->deviceID, this->played_bytes);
|
|
} else
|
|
{
|
|
//ERR("unknown type for this->setType\n");
|
|
}
|
|
#endif
|
|
this->pMessages = msg->pNext; /* take this message off of the queue */
|
|
free(msg); /* free up its memory */
|
|
}
|
|
#endif
|
|
|
|
/* handle playing state */
|
|
if(this->state == PLAYING)
|
|
{
|
|
unsigned long jackFramesAvailable = nframes; /* frames we have left to write to jack */
|
|
unsigned long inputFramesAvailable; /* frames we have available this loop */
|
|
unsigned long numFramesToWrite; /* num frames we are writing this loop */
|
|
|
|
long written, read;
|
|
char* buffer;
|
|
|
|
written = read = 0;
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("playing... jackFramesAvailable = %ld\n", jackFramesAvailable);
|
|
#endif
|
|
|
|
#if JACK_CLOSE_HACK
|
|
if(this->in_use == FALSE)
|
|
{
|
|
/* output silence if nothing is being outputted */
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
sample_silence_dS(out_buffer[i], nframes);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* see if our buffer is large enough for the data we are writing */
|
|
/* ie. Buffer_size < (bytes we already wrote + bytes we are going to write in this loop) */
|
|
/* Note: sound_buffer is always filled with 16-bit data */
|
|
/* so frame * 2 bytes(16 bits) * X output channels */
|
|
if(this->buffer_size < (jackFramesAvailable * sizeof(short) * this->num_output_channels))
|
|
{
|
|
//ERR("our buffer must have changed size\n");
|
|
//ERR("allocated %ld bytes, need %ld bytes\n", this->buffer_size,
|
|
// jackFramesAvailable * sizeof(short) * this->num_output_channels);
|
|
return 0;
|
|
}
|
|
|
|
/* while we have jackBytesLeft and a wave header to be played */
|
|
while(jackFramesAvailable && this->pPlayPtr)
|
|
{
|
|
/* (bytes of data) / (2 bytes(16 bits) * X input channels) == frames */
|
|
inputFramesAvailable = (this->pPlayPtr->size - this->playptr_offset) / (sizeof(short) * this->num_input_channels);
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("inputFramesAvailable == %ld, jackFramesAvailable == %ld\n", inputFramesAvailable, jackFramesAvailable);
|
|
#endif
|
|
|
|
buffer = this->pPlayPtr->pData + this->playptr_offset;
|
|
|
|
/* convert to actual frames based on the format */
|
|
if(this->bits_per_channel == 8)
|
|
inputFramesAvailable<<=1; /* multiply by 2 to get actual frames after conversion from 8 to 16 bits */
|
|
|
|
numFramesToWrite = min(jackFramesAvailable, inputFramesAvailable); /* write as many bytes as we have space remaining, or as much as we have data to write */
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("inputFramesAvailable after conversion %ld\n", inputFramesAvailable);
|
|
TRACE("nframes == %d, jackFramesAvailable == %ld,\n\tthis->num_input_channels == %ld, this->num_output_channels == %ld\n",
|
|
nframes, jackFramesAvailable, this->num_input_channels, this->num_output_channels);
|
|
#endif
|
|
|
|
/* convert from 8 bit to 16 bit and mono to stereo if necessary */
|
|
/* otherwise just memcpy to the output buffer */
|
|
// if(this->bits_per_channel == 8)
|
|
// {
|
|
// sample_move_d8_d16 ((short*)this->sound_buffer + ((jackBytesAvailableThisCallback - jackBytesLeft) / sizeof(short)),
|
|
// buffer, jackBytesToWrite, this->num_channels);
|
|
// } else if(this->num_channels == 1)
|
|
|
|
/* 16 bit input samples */
|
|
/* if we have a mismatch of channels we need to lose or duplicate data */
|
|
if(this->num_input_channels != this->num_output_channels)
|
|
{
|
|
sample_move_d16_d16((short*)this->sound_buffer + ((nframes - jackFramesAvailable) * this->bits_per_channel * this->num_output_channels) / (sizeof(short) * 8),
|
|
(short*)buffer, numFramesToWrite, this->num_output_channels, this->num_input_channels);
|
|
} else /* just copy the memory over */
|
|
{
|
|
veejay_memcpy(this->sound_buffer + ((nframes - jackFramesAvailable) * this->bits_per_channel * this->num_output_channels) / 8,
|
|
buffer, (numFramesToWrite * this->bits_per_channel * this->num_input_channels) / 8);
|
|
}
|
|
|
|
/* advance to the next wave header if possible, or advance pointer */
|
|
/* inside of the current header if we haven't completed it */
|
|
if(numFramesToWrite == inputFramesAvailable)
|
|
{
|
|
wave_header_t* pOldHeader;
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("numFramesToWrite == inputFramesAvailable, advancing to next header\n");
|
|
#endif
|
|
|
|
free(this->pPlayPtr->pData); /* free the data we've played */
|
|
this->playptr_offset = 0;
|
|
pOldHeader = this->pPlayPtr;
|
|
this->pPlayPtr = this->pPlayPtr->pNext;
|
|
free(pOldHeader); /* free the wave header structure that we just finished playing */
|
|
}
|
|
else
|
|
{
|
|
/* else advance by the bytes we took in to write */
|
|
this->playptr_offset+=((numFramesToWrite * this->bits_per_channel * this->num_input_channels) / 8);
|
|
}
|
|
|
|
/* add on what we wrote */
|
|
written+=((numFramesToWrite * this->bits_per_channel * this->num_output_channels) / 8);
|
|
read+=((numFramesToWrite * this->bits_per_channel * this->num_input_channels) / 8);
|
|
|
|
jackFramesAvailable-=numFramesToWrite; /* take away what was written */
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("jackFramesAvailable == %ld\n", jackFramesAvailable);
|
|
#endif
|
|
} /* while(jackFramesAvailable && this->pPlayPtr) */
|
|
|
|
|
|
gettimeofday(&this->previousTime, 0); /* record the current time */
|
|
this->written_client_bytes+=read;
|
|
this->played_client_bytes+=this->clientBytesInJack; /* move forward by the previous bytes we wrote since those must have finished by now */
|
|
this->clientBytesInJack = read; /* record the input bytes we wrote to jack */
|
|
this->num_ticks ++;
|
|
/* Now that we have finished filling the buffer either until it is full or until */
|
|
/* we have run out of application sound data to process, output */
|
|
/* the audio to the jack server */
|
|
|
|
/* convert from stereo 16 bit to single channel 32 bit float */
|
|
/* for each output channel */
|
|
/* NOTE: we skip over the number shorts(16 bits) we have output channels as the channel data */
|
|
/* is encoded like chan1,chan2,chan3,chan1,chan2,chan3... */
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
{
|
|
sample_move_d16_s16(out_buffer[i], (short*)this->sound_buffer + i,
|
|
(nframes - jackFramesAvailable), this->num_output_channels);
|
|
|
|
/* apply volume to the floating value */
|
|
if(this->volumeEffectType == dbAttenuation)
|
|
{
|
|
/* assume the volume setting is dB of attenuation, a volume of 0 */
|
|
/* is 0dB attenuation */
|
|
float volume = powf(10.0, -((float)this->volume[i]) / 20.0);
|
|
float_volume_effect(out_buffer[i], (nframes - jackFramesAvailable),
|
|
volume);
|
|
} else
|
|
{
|
|
float_volume_effect(out_buffer[i], (nframes - jackFramesAvailable),
|
|
((float)this->volume[i] / 100.0));
|
|
}
|
|
}
|
|
|
|
|
|
/* see if we still have jackBytesLeft here, if we do that means that we
|
|
ran out of wave data to play and had a buffer underrun, fill in
|
|
the rest of the space with zero bytes so at least there is silence */
|
|
if(jackFramesAvailable)
|
|
{
|
|
#if CALLBACK_TRACE
|
|
TRACE("buffer underrun of %ld frames\n", jackFramesAvailable);
|
|
#endif
|
|
for(i = 0 ; i < this->num_output_channels; i++)
|
|
sample_silence_dS(out_buffer[i] + (nframes - jackFramesAvailable), jackFramesAvailable);
|
|
}
|
|
}
|
|
else if(this->state == PAUSED ||
|
|
this->state == STOPPED ||
|
|
this->state == CLOSED || this->state == RESET)
|
|
{
|
|
#if CALLBACK_TRACE
|
|
TRACE("PAUSED or STOPPED or CLOSED, outputting silence\n");
|
|
#endif
|
|
gettimeofday(&this->previousTime, 0); /* record the current time */
|
|
|
|
/* output silence if nothing is being outputted */
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
sample_silence_dS(out_buffer[i], nframes);
|
|
|
|
/* if we were told to reset then zero out some variables */
|
|
/* and transition to STOPPED */
|
|
if(this->state == RESET)
|
|
{
|
|
wave_header_t *wh = this->pPlayPtr;
|
|
this->written_client_bytes = 0;
|
|
this->played_client_bytes = 0; /* number of the clients bytes that jack has played */
|
|
|
|
this->client_bytes = 0; /* bytes that the client wrote to use */
|
|
this->clientBytesInJack = 0; /* number of input bytes in jack(not necessary the number of bytes written to jack) */
|
|
|
|
this->pPlayPtr = 0;
|
|
this->playptr_offset = 0;
|
|
this->position_byte_offset = 0;
|
|
|
|
/* free up all of the buffers of audio that are queued */
|
|
/* NOTE: this needs to be done inside of the callback because */
|
|
/* the callback could be using any of these buffers */
|
|
wh = this->pPlayPtr;
|
|
while(wh)
|
|
{
|
|
wh = wh->pNext;
|
|
free(this->pPlayPtr->pData); /* free up the app data */
|
|
free(this->pPlayPtr); /* free the structure itself */
|
|
this->pPlayPtr = wh;
|
|
}
|
|
|
|
this->state = STOPPED; /* transition to STOPPED */
|
|
}
|
|
}
|
|
|
|
#if CALLBACK_TRACE
|
|
TRACE("done\n");
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/******************************************************************
|
|
* JACK_bufsize
|
|
*
|
|
* Called whenever the jack server changes the the max number
|
|
* of frames passed to JACK_callback
|
|
*/
|
|
static int JACK_bufsize (nframes_t nframes, void *arg)
|
|
{
|
|
jack_driver_t* this = (jack_driver_t*)arg;
|
|
unsigned long buffer_required;
|
|
TRACE("the maximum buffer size is now %lu frames\n", (long)nframes);
|
|
|
|
/* make sure the callback routine has adequate memory for the nframes it will get */
|
|
/* ie. Buffer_size < (bytes we already wrote + bytes we are going to write in this loop) */
|
|
/* frames * 2 bytes in 16 bits * X channels of output */
|
|
buffer_required = nframes * sizeof(short) * this->num_output_channels;
|
|
if(this->buffer_size < buffer_required)
|
|
{
|
|
TRACE("expanding buffer from this->buffer_size == %ld, to %ld\n",
|
|
this->buffer_size, buffer_required);
|
|
this->buffer_size = buffer_required;
|
|
this->sound_buffer = realloc(this->sound_buffer, this->buffer_size);
|
|
|
|
/* if we don't have a buffer then error out */
|
|
if(!this->sound_buffer)
|
|
{
|
|
//ERR("error allocating sound_buffer memory\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
TRACE("called\n");
|
|
return 0;
|
|
}
|
|
|
|
long JACK_OutputStatus(int deviceID,long int *sec, long int *usec)
|
|
{
|
|
jack_driver_t *this = &outDev[deviceID];
|
|
*sec = (long int) this->previousTime.tv_sec;
|
|
*usec = (long int) this->previousTime.tv_usec;
|
|
return this->num_ticks * this->chunk_size;
|
|
}
|
|
|
|
/******************************************************************
|
|
* JACK_srate
|
|
*/
|
|
int JACK_srate (nframes_t nframes, void *arg)
|
|
{
|
|
TRACE("the sample rate is now %lu/sec\n", (long)nframes);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/******************************************************************
|
|
* JACK_shutdown
|
|
*
|
|
* if this is called then jack shut down... handle this appropriately */
|
|
void JACK_shutdown(void* arg)
|
|
{
|
|
jack_driver_t* this = (jack_driver_t*)arg;
|
|
|
|
this->client = 0; /* reset client */
|
|
this->jackd_died = TRUE;
|
|
|
|
TRACE("jack shutdown, setting client to 0 and jackd_died to true\n");
|
|
TRACE("trying to reconnect right now\n");
|
|
|
|
/* lets see if we can't reestablish the connection */
|
|
if(JACK_OpenDevice(this) != ERR_SUCCESS)
|
|
{
|
|
//ERR("unable to reconnect with jack\n");
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************
|
|
* JACK_Error
|
|
*
|
|
* Callback for jack errors
|
|
*/
|
|
static void JACK_Error(const char *desc)
|
|
{
|
|
//ERR("%s\n", desc);
|
|
}
|
|
|
|
|
|
#if 0
|
|
/******************************************************************
|
|
* JACK_SendMessage
|
|
*
|
|
* put a message on the message queue of a driver */
|
|
static bool JACK_SendMessage(jack_driver_t* this, enum cmd_enum command, long data)
|
|
{
|
|
message_t *newMessage;
|
|
message_t **m;
|
|
|
|
newMessage = (message_t*)malloc(sizeof(message_t));
|
|
if(!newMessage)
|
|
{
|
|
//ERR("error allocating new message\n");
|
|
return FALSE;
|
|
}
|
|
|
|
newMessage->command = command;
|
|
newMessage->data = data;
|
|
newMessage->pNext = 0; /* setup the next pointer to point to null */
|
|
|
|
/* now setup the last pointer in the existing array to point to this header */
|
|
/* we use a pointer to a pointer here just to make this code more elegant */
|
|
/* and in case pMessages is null it makes that condition clean */
|
|
for(m = &(this->pMessages); *m; m = &((*m)->pNext));
|
|
*m = newMessage; /* point it to this new message */
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
|
|
/******************************************************************
|
|
* JACK_OpenDevice
|
|
*
|
|
* RETURNS: ERR_SUCCESS upon success
|
|
*/
|
|
static int JACK_OpenDevice(jack_driver_t* this)
|
|
{
|
|
const char** ports;
|
|
unsigned int i;
|
|
char client_name[64];
|
|
int failed = 0;
|
|
|
|
TRACE("creating jack client and setting up callbacks\n");
|
|
|
|
#if JACK_CLOSE_HACK
|
|
/* see if this device is already open */
|
|
if(this->client)
|
|
{
|
|
/* if this device is already in use then it is bad for us to be in here */
|
|
if(this->in_use)
|
|
return ERR_OPENING_JACK;
|
|
|
|
TRACE("using existing client\n");
|
|
this->in_use = TRUE;
|
|
return ERR_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
/* zero out the buffer pointer and the size of the buffer */
|
|
this->sound_buffer = 0;
|
|
this->buffer_size = 0;
|
|
this->playptr_offset = 0;
|
|
|
|
/* set up an error handler */
|
|
jack_set_error_function(JACK_Error);
|
|
|
|
/* try to become a client of the JACK server */
|
|
snprintf(client_name, sizeof(client_name), "bio2jack_%d_%d", 0, getpid());
|
|
TRACE("client name '%s'\n", client_name);
|
|
if ((this->client = jack_client_new(client_name)) == 0)
|
|
{
|
|
/* try once more */
|
|
if ((this->client = jack_client_new(client_name)) == 0)
|
|
{
|
|
//ERR("jack server not running?\n");
|
|
return ERR_OPENING_JACK;
|
|
}
|
|
}
|
|
|
|
TRACE("setting up jack callbacks\n");
|
|
|
|
/* JACK server to call `JACK_callback()' whenever
|
|
there is work to be done. */
|
|
jack_set_process_callback(this->client, JACK_callback, this);
|
|
|
|
/* setup a buffer size callback */
|
|
jack_set_buffer_size_callback(this->client, JACK_bufsize, this);
|
|
|
|
/* tell the JACK server to call `srate()' whenever
|
|
the sample rate of the system changes. */
|
|
jack_set_sample_rate_callback(this->client, JACK_srate, this);
|
|
|
|
/* tell the JACK server to call `jack_shutdown()' if
|
|
it ever shuts down, either entirely, or if it
|
|
just decides to stop calling us. */
|
|
jack_on_shutdown(this->client, JACK_shutdown, this);
|
|
|
|
/* display the current sample rate. once the client is activated
|
|
(see below), you should rely on your own sample rate
|
|
callback (see above) for this value. */
|
|
this->sample_rate = jack_get_sample_rate(this->client);
|
|
TRACE("engine sample rate: %lu\n", this->sample_rate);
|
|
|
|
/* create the output ports */
|
|
TRACE("creating output ports\n");
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
{
|
|
char portname[32];
|
|
sprintf(portname, "out_%d", i);
|
|
TRACE("port %d is named '%s'\n", i, portname);
|
|
/* NOTE: Yes, this is supposed to be JackPortIsOutput since this is an output */
|
|
/* port FROM bio2jack */
|
|
this->output_port[i] = jack_port_register(this->client, portname,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsOutput,
|
|
0);
|
|
}
|
|
|
|
/* set the initial buffer size */
|
|
JACK_bufsize(jack_get_buffer_size(this->client), this);
|
|
|
|
#if JACK_CLOSE_HACK
|
|
this->in_use = TRUE;
|
|
#endif
|
|
|
|
/* tell the JACK server that we are ready to roll */
|
|
TRACE("calling jack_activate()\n");
|
|
if(jack_activate(this->client))
|
|
{
|
|
//ERR( "cannot activate client\n");
|
|
return ERR_OPENING_JACK;
|
|
}
|
|
|
|
/* determine how we are to acquire port names */
|
|
if((this->jack_port_name_count == 0) || (this->jack_port_name_count == 1))
|
|
{
|
|
if(this->jack_port_name_count == 0)
|
|
{
|
|
TRACE("jack_get_ports() passing in NULL/NULL\n");
|
|
ports = jack_get_ports(this->client, NULL, NULL, this->jack_port_flags);
|
|
}
|
|
else
|
|
{
|
|
TRACE("jack_get_ports() passing in port of '%s'\n", this->jack_port_name[0]);
|
|
ports = jack_get_ports(this->client, this->jack_port_name[0], NULL, this->jack_port_flags);
|
|
}
|
|
|
|
/* display a trace of the output ports we found */
|
|
for(i = 0; ports[i]; i++)
|
|
TRACE("ports[%d] = '%s'\n", i, ports[i]);
|
|
|
|
/* see if we have enough ports */
|
|
if(i < this->num_output_channels)
|
|
{
|
|
TRACE("ERR: jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n", this->jack_port_flags);
|
|
return ERR_PORT_NOT_FOUND;
|
|
}
|
|
|
|
/* connect the ports. Note: you can't do this before
|
|
the client is activated (this may change in the future). */
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
{
|
|
TRACE("jack_connect() to port %d('%p')\n", i, this->output_port[i]);
|
|
if(jack_connect(this->client, jack_port_name(this->output_port[i]), ports[i]))
|
|
{
|
|
//ERR("cannot connect to output port %d('%s')\n", i, ports[i]);
|
|
failed = 1;
|
|
}
|
|
}
|
|
|
|
free(ports); /* free the returned array of ports */
|
|
} else
|
|
{
|
|
for(i = 0; i < this->jack_port_name_count; i++)
|
|
{
|
|
TRACE("jack_get_ports() portname %d of '%s\n", i, this->jack_port_name[i]);
|
|
ports = jack_get_ports(this->client, this->jack_port_name[i], NULL, this->jack_port_flags);
|
|
TRACE("ports[%d] = '%s'\n", 0, ports[0]); /* display a trace of the output port we found */
|
|
|
|
if(!ports)
|
|
{
|
|
//ERR("jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n", this->jack_port_flags);
|
|
return ERR_PORT_NOT_FOUND;
|
|
}
|
|
|
|
/* connect the port */
|
|
TRACE("jack_connect() to port %d('%p')\n", i, this->output_port[i]);
|
|
if(jack_connect(this->client, jack_port_name(this->output_port[i]), ports[0]))
|
|
{
|
|
//ERR("cannot connect to output port %d('%s')\n", 0, ports[0]);
|
|
failed = 1;
|
|
}
|
|
free(ports); /* free the returned array of ports */
|
|
}
|
|
}
|
|
|
|
/* if something failed we need to shut the client down and return 0 */
|
|
if(failed)
|
|
{
|
|
TRACE("failed, closing and returning error\n");
|
|
JACK_CloseDevice(this, TRUE);
|
|
return ERR_OPENING_JACK;
|
|
}
|
|
|
|
TRACE("success\n");
|
|
|
|
this->jackd_died = FALSE; /* clear out this flag so we don't keep attempting to restart things */
|
|
|
|
return ERR_SUCCESS; /* return success */
|
|
}
|
|
|
|
|
|
/******************************************************************
|
|
* JACK_CloseDevice
|
|
*
|
|
* Close the connection to the server cleanly.
|
|
* If close_client is TRUE we close the client for this device instead of
|
|
* just marking the device as in_use(JACK_CLOSE_HACK only)
|
|
*/
|
|
#if JACK_CLOSE_HACK
|
|
static void JACK_CloseDevice(jack_driver_t* this, bool close_client)
|
|
#else
|
|
static void JACK_CloseDevice(jack_driver_t* this)
|
|
#endif
|
|
{
|
|
int i;
|
|
|
|
#if JACK_CLOSE_HACK
|
|
if(close_client)
|
|
{
|
|
#endif
|
|
TRACE("closing the jack client thread\n");
|
|
if(this->client)
|
|
{
|
|
//FIXME: deactivate or no, jack_client_close() is blocking...
|
|
//jack_deactivate(this->client); /* supposed to help the jack_client_close() to succeed */
|
|
TRACE("after jack_deactivate()\n");
|
|
jack_client_close(this->client);
|
|
}
|
|
|
|
JACK_ResetFromThis(this);
|
|
this->client = 0; /* reset client */
|
|
free(this->sound_buffer); /* free buffer memory */
|
|
this->sound_buffer = 0;
|
|
this->buffer_size = 0; /* zero out size of the buffer */
|
|
|
|
/* free up the port strings */
|
|
TRACE("freeing up port strings\n");
|
|
if(this->jack_port_name_count > 1)
|
|
{
|
|
for(i = 0; i < this->jack_port_name_count; i++)
|
|
free(this->jack_port_name[i]);
|
|
free(this->jack_port_name);
|
|
}
|
|
#if JACK_CLOSE_HACK
|
|
} else
|
|
{
|
|
TRACE("setting in_use to FALSE\n");
|
|
this->in_use = FALSE;
|
|
|
|
if(!this->client)
|
|
{
|
|
TRACE("critical error, closing a device that has no client\n");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************/
|
|
/* External interface functions below */
|
|
/**************************************/
|
|
|
|
/* Clear out any buffered data, stop playing, zero out some variables */
|
|
static void JACK_ResetFromThis(jack_driver_t *this)
|
|
{
|
|
TRACE("resetting this->deviceID(%d)\n", this->deviceID);
|
|
|
|
/* NOTE: we use the RESET state so we don't need to worry about clearing out */
|
|
/* variables that the callback modifies while the callback is running */
|
|
/* we set the state to RESET and the callback clears the variables out for us */
|
|
this->state = RESET; /* tell the callback that we are to reset, the callback will transition this to STOPPED */
|
|
}
|
|
|
|
/* Clear out any buffered data, stop playing, zero out some variables */
|
|
void JACK_Reset(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
TRACE("resetting deviceID(%d)\n", deviceID);
|
|
JACK_ResetFromThis(this);
|
|
releaseDriver(this);
|
|
}
|
|
|
|
|
|
/*
|
|
* open the audio device for writing to
|
|
*
|
|
* deviceID is set to the opened device
|
|
* if client is non-zero and in_use is FALSE then just set in_use to TRUE
|
|
*
|
|
* return value is zero upon success, non-zero upon failure
|
|
*
|
|
* if ERR_RATE_MISMATCH (*rate) will be updated with the jack servers rate
|
|
*/
|
|
int JACK_Open(int* deviceID, unsigned int bits_per_channel, unsigned long *rate, int channels)
|
|
{
|
|
/* we call through to JACK_OpenEx() */
|
|
return JACK_OpenEx(deviceID, bits_per_channel, rate, channels, channels,
|
|
NULL, 0, JackPortIsPhysical);
|
|
}
|
|
|
|
/*
|
|
* see JACK_Open() for comments
|
|
* NOTE: jack_port_name has three ways of being used:
|
|
* - NULL - finds all ports with the given flags
|
|
* - A single regex string used to retreve all port names
|
|
* - A series of port names, one for each output channel
|
|
*
|
|
* we set *deviceID
|
|
*/
|
|
int JACK_OpenEx(int* deviceID, unsigned int bits_per_channel, unsigned long *rate,
|
|
unsigned int input_channels, unsigned int output_channels, char** jack_port_name,
|
|
unsigned int jack_port_name_count, unsigned long jack_port_flags)
|
|
{
|
|
jack_driver_t *this = getDriver(first_free_device);
|
|
unsigned int i;
|
|
int retval;
|
|
|
|
TRACE("bits_per_channel=%d rate=%ld, input_channels=%d, output_channels=%d\n",
|
|
bits_per_channel, *rate, input_channels, output_channels);
|
|
|
|
if(output_channels > MAX_OUTPUT_PORTS)
|
|
{
|
|
//ERR("output_channels == %d, MAX_OUTPUT_PORTS == %d\n", output_channels, MAX_OUTPUT_PORTS);
|
|
releaseDriver(this);
|
|
return ERR_TOO_MANY_OUTPUT_CHANNELS;
|
|
}
|
|
|
|
jack_port_flags|=JackPortIsInput; /* port must be input(ie we can put data into it), so mask this in */
|
|
|
|
/* check that we have the correct number of port names */
|
|
if((jack_port_name_count > 1) && (jack_port_name_count != output_channels))
|
|
{
|
|
//ERR("specified individual port names but not enough, gave %d names, need %d\n",
|
|
//jack_port_name_count, output_channels);
|
|
releaseDriver(this);
|
|
return ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH;
|
|
} else
|
|
{
|
|
/* copy this data into the device information */
|
|
this->jack_port_flags = jack_port_flags;
|
|
this->jack_port_name_count = jack_port_name_count;
|
|
|
|
if(this->jack_port_name_count != 0)
|
|
{
|
|
this->jack_port_name = (char**)vj_malloc(sizeof(char*) * this->jack_port_name_count);
|
|
for(i = 0; i < this->jack_port_name_count; i++)
|
|
{
|
|
this->jack_port_name[i] = strdup(jack_port_name[i]);
|
|
TRACE("jack_port_name[%d] == %s\n", i, jack_port_name[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->jack_port_name = NULL;
|
|
TRACE("jack_port_name = NULL\n");
|
|
}
|
|
}
|
|
|
|
/* initialize some variables */
|
|
this->in_use = FALSE;
|
|
|
|
JACK_ResetFromThis(this); /* flushes all queued buffers, sets status to STOPPED and resets some variables */
|
|
|
|
/* this->sample_rate is set by JACK_OpenDevice() */
|
|
this->bits_per_channel = bits_per_channel;
|
|
this->num_input_channels = input_channels;
|
|
this->num_output_channels = output_channels;
|
|
this->bytes_per_input_frame = (this->bits_per_channel*this->num_input_channels)/8;
|
|
this->bytes_per_output_frame = (this->bits_per_channel*this->num_output_channels)/8;
|
|
|
|
TRACE("bytes_per_output_frame == %ld\n", this->bytes_per_output_frame);
|
|
TRACE("bytes_per_input_frame == %ld\n", this->bytes_per_input_frame);
|
|
|
|
/* make sure bytes_per_frame is valid and non-zero */
|
|
if(!this->bytes_per_output_frame)
|
|
{
|
|
//ERR("bytes_per_output_frame is zero\n");
|
|
releaseDriver(this);
|
|
return ERR_BYTES_PER_OUTPUT_FRAME_INVALID;
|
|
}
|
|
|
|
/* make sure bytes_per_frame is valid and non-zero */
|
|
if(!this->bytes_per_input_frame)
|
|
{
|
|
//ERR("bytes_per_output_frame is zero\n");
|
|
releaseDriver(this);
|
|
return ERR_BYTES_PER_INPUT_FRAME_INVALID;
|
|
}
|
|
|
|
/* go and open up the device */
|
|
retval = JACK_OpenDevice(this);
|
|
if(retval != ERR_SUCCESS)
|
|
{
|
|
TRACE("error opening jack device\n");
|
|
releaseDriver(this);
|
|
return retval;
|
|
} else
|
|
{
|
|
TRACE("succeeded opening jack device\n");
|
|
}
|
|
|
|
/* make sure the sample rate of the jack server matches that of the client */
|
|
if((long)(*rate) != this->sample_rate)
|
|
{
|
|
TRACE("rate of %ld doesn't match jack sample rate of %ld, returning error\n",
|
|
*rate, this->sample_rate);
|
|
*rate = this->sample_rate;
|
|
JACK_CloseDevice(this, TRUE);
|
|
releaseDriver(this);
|
|
return ERR_RATE_MISMATCH;
|
|
}
|
|
|
|
first_free_device++; /* record that we opened this device */
|
|
|
|
TRACE("sizeof(sample_t) == %d\n", sizeof(sample_t));
|
|
|
|
this->latencyMS = 10;
|
|
|
|
TRACE("this->latencyMS == %ldms\n", this->latencyMS);
|
|
|
|
*deviceID = this->deviceID; /* set the deviceID for the caller */
|
|
releaseDriver(this);
|
|
return ERR_SUCCESS; /* success */
|
|
}
|
|
|
|
/* Close the jack device */
|
|
//FIXME: add error handling in here at some point...
|
|
/* NOTE: return 0 for success, non-zero for failure */
|
|
int JACK_Close(int deviceID)
|
|
{
|
|
jack_driver_t* this = getDriver(deviceID);
|
|
|
|
TRACE("deviceID(%d)\n", deviceID);
|
|
|
|
#if JACK_CLOSE_HACK
|
|
JACK_CloseDevice(this, TRUE);
|
|
#else
|
|
JACK_CloseDevice(this);
|
|
#endif
|
|
|
|
JACK_ResetFromThis(this); /* reset this device to a normal starting state */
|
|
|
|
first_free_device--; /* decrement device count */
|
|
|
|
releaseDriver(this);
|
|
return 0;
|
|
}
|
|
|
|
/* If we haven't already taken in the max allowed data then create a wave header */
|
|
/* to package the audio data and attach the wave header to the end of the */
|
|
/* linked list of wave headers */
|
|
/* These wave headers will be peeled off as they are played by the callback routine */
|
|
/* Return value is the number of bytes written */
|
|
/* NOTE: this function takes the length of data to be written bytes */
|
|
long JACK_Write(int deviceID, char *data, unsigned long bytes)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
wave_header_t *newWaveHeader;
|
|
wave_header_t **wh;
|
|
struct timeval now;
|
|
long bytes_stored;
|
|
|
|
TRACE("deviceID(%d), bytes == %ld\n", deviceID, bytes);
|
|
|
|
gettimeofday(&now, 0);
|
|
TRACE("Starting Time = %ld.%ld\n", now.tv_sec, now.tv_usec);
|
|
|
|
/* check and see that we have enough space for this audio */
|
|
bytes_stored = JACK_GetBytesStoredFromThis(this);
|
|
TRACE("bytes stored == %ld\n", bytes_stored);
|
|
if(bytes_stored + bytes > (long)MAX_BUFFERED_BYTES) /* hackish check for now */
|
|
{
|
|
TRACE("bytes stored(%ld) + bytes(%ld) > MAX_BUFFERED_BYTES(%ld), returning 0\n", bytes_stored, bytes, MAX_BUFFERED_BYTES);
|
|
releaseDriver(this);
|
|
return 0; /* indicate that we couldn't write any bytes */
|
|
}
|
|
|
|
newWaveHeader = (wave_header_t*)vj_malloc(sizeof(wave_header_t)); /* create a wave header for this data */
|
|
if(!newWaveHeader)
|
|
{
|
|
//ERR("error allocating memory for newWaveHeader\n");
|
|
}
|
|
|
|
newWaveHeader->pData = (char*)vj_malloc(sizeof(char) * bytes); /* allocate memory for the data */
|
|
veejay_memcpy(newWaveHeader->pData, data, sizeof(char) * bytes); /* copy in the data */
|
|
newWaveHeader->size = bytes; /* update the size */
|
|
newWaveHeader->pNext = 0; /* setup the next pointer to point to null */
|
|
|
|
/* now setup the last pointer in the existing array to point to this header */
|
|
/* we use a pointer to a pointer here just to make this code more elegant */
|
|
/* and in case pQueuePtr is null it makes that condition clean */
|
|
for(wh = &(this->pPlayPtr); *wh; wh = &((*wh)->pNext));
|
|
*wh = newWaveHeader; /* point it to this new header */
|
|
|
|
this->client_bytes += bytes; /* update client_bytes */
|
|
|
|
if (!this->pPlayPtr) /* if we have no header being played then use this one */
|
|
{
|
|
this->pPlayPtr = newWaveHeader;
|
|
this->playptr_offset = 0;
|
|
}
|
|
|
|
/* if we are currently STOPPED we should start playing now... */
|
|
if (this->state == STOPPED)
|
|
{
|
|
TRACE("currently STOPPED, transitioning to PLAYING\n");
|
|
this->state = PLAYING;
|
|
}
|
|
|
|
bytes_stored = JACK_GetBytesStoredFromThis(this);
|
|
TRACE("bytes stored == %ld\n", bytes_stored);
|
|
|
|
gettimeofday(&now, 0);
|
|
TRACE("Ending Time = %ld.%ld\n", now.tv_sec, now.tv_usec);
|
|
|
|
TRACE("returning bytes written of %ld\n", bytes);
|
|
|
|
releaseDriver(this);
|
|
return bytes; /* return the number of bytes we wrote out */
|
|
}
|
|
|
|
#if 0
|
|
/* the client is exiting, close the audio device and the corresponding client */
|
|
static void JACK_exit(int deviceID)
|
|
{
|
|
jack_driver_t* this = getDriver(deviceID);
|
|
|
|
TRACE("deviceID(%d)\n", deviceID);
|
|
|
|
JACK_CloseDevice(this, TRUE); /* close the device, FORCE the client to close */
|
|
releaseDriver(this);
|
|
}
|
|
#endif
|
|
|
|
|
|
/* return ERR_SUCCESS for success */
|
|
static int JACK_SetVolumeForChannelFromThis(jack_driver_t *this,
|
|
unsigned int channel,
|
|
unsigned int volume)
|
|
{
|
|
/* ensure that we have the channel we are setting volume for */
|
|
if(channel > (this->num_output_channels - 1))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if(volume > 100) volume = 100; /* check for values in excess of max */
|
|
|
|
this->volume[channel] = volume;
|
|
return ERR_SUCCESS;
|
|
}
|
|
|
|
/* return ERR_SUCCESS for success */
|
|
int JACK_SetVolumeForChannel(int deviceID, unsigned int channel, unsigned int volume)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
int retval = JACK_SetVolumeForChannelFromThis(this, channel, volume);
|
|
releaseDriver(this);
|
|
return retval;
|
|
}
|
|
|
|
/* Set the volume */
|
|
/* return 0 for success */
|
|
/* NOTE: we check for invalid volume values */
|
|
int JACK_SetAllVolume(int deviceID, unsigned int volume)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
unsigned int i;
|
|
|
|
TRACE("deviceID(%d), setting volume of %d\n", deviceID, volume);
|
|
|
|
for(i = 0; i < this->num_output_channels; i++)
|
|
{
|
|
if(JACK_SetVolumeForChannelFromThis(this, i, volume) != ERR_SUCCESS)
|
|
{
|
|
releaseDriver(this);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
releaseDriver(this);
|
|
return ERR_SUCCESS;
|
|
}
|
|
|
|
/* Return the current volume in the inputted pointers */
|
|
/* NOTE: we check for null pointers being passed in just in case */
|
|
void JACK_GetVolumeForChannel(int deviceID, unsigned int channel, unsigned int *volume)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
|
|
if(volume) *volume = this->volume[channel];
|
|
|
|
#if VERBOSE_OUTPUT
|
|
if(volume)
|
|
{
|
|
TRACE("deviceID(%d), returning volume of %d for channel %d\n", deviceID, *volume, channel);
|
|
} else
|
|
{
|
|
TRACE("volume is null, can't dereference it\n");
|
|
}
|
|
#endif
|
|
|
|
releaseDriver(this);
|
|
}
|
|
|
|
|
|
/* linear means 0 volume is silence, 100 is full volume */
|
|
/* dbAttenuation means 0 volume is 0dB attenuation */
|
|
/* Bio2jack defaults to linear */
|
|
enum JACK_VOLUME_TYPE JACK_SetVolumeEffectType(int deviceID, enum JACK_VOLUME_TYPE type)
|
|
{
|
|
enum JACK_VOLUME_TYPE retval;
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
|
|
TRACE("setting type of '%s'\n",
|
|
(type == dbAttenuation ? "dbAttenuation" : "linear"));
|
|
|
|
retval = this->volumeEffectType;
|
|
this->volumeEffectType = type;
|
|
|
|
releaseDriver(this);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/* Controls the state of the playback(playing, paused, ...) */
|
|
int JACK_SetState(int deviceID, enum status_enum state)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
|
|
switch (state) {
|
|
case PAUSED:
|
|
this->state = PAUSED;
|
|
break;
|
|
case PLAYING:
|
|
this->state = PLAYING;
|
|
break;
|
|
case STOPPED:
|
|
this->state = STOPPED;
|
|
break;
|
|
default:
|
|
TRACE("unknown state of %d\n", state);
|
|
}
|
|
|
|
TRACE("%s\n", DEBUGSTATE(this->state));
|
|
|
|
releaseDriver(this);
|
|
return 0;
|
|
}
|
|
|
|
/* Retrieve the current state of the device */
|
|
enum status_enum JACK_GetState(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
enum status_enum return_val;
|
|
|
|
return_val = this->state;
|
|
releaseDriver(this);
|
|
|
|
TRACE("deviceID(%d), returning current state of %s\n", deviceID, DEBUGSTATE(return_val));
|
|
return return_val;
|
|
}
|
|
|
|
/* Retrieve the number of bytes per second we are outputting */
|
|
long JACK_GetOutputBytesPerSecond(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
long return_val;
|
|
|
|
return_val = this->bytes_per_output_frame * this->sample_rate;
|
|
releaseDriver(this);
|
|
|
|
#if VERBOSE_OUTPUT
|
|
TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val);
|
|
#endif
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Retrieve the number of input bytes per second we are outputting */
|
|
static long JACK_GetInputBytesPerSecondFromThis(jack_driver_t *this)
|
|
{
|
|
long return_val;
|
|
|
|
return_val = this->bytes_per_input_frame * this->sample_rate;
|
|
#if VERBOSE_OUTPUT
|
|
TRACE("this->deviceID(%d), return_val = %ld\n", this->deviceID, return_val);
|
|
#endif
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Retrieve the number of input bytes per second we are outputting */
|
|
long JACK_GetInputBytesPerSecond(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
long return_val = JACK_GetInputBytesPerSecondFromThis(this);
|
|
releaseDriver(this);
|
|
|
|
#if VERBOSE_OUTPUT
|
|
TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val);
|
|
#endif
|
|
|
|
return return_val;
|
|
}
|
|
|
|
|
|
/* Return the number of bytes we have buffered thus far for output */
|
|
/* NOTE: convert from output bytes to input bytes in here */
|
|
static long JACK_GetBytesStoredFromThis(jack_driver_t *this)
|
|
{
|
|
long return_val;
|
|
|
|
return_val = (this->client_bytes - this->played_client_bytes);
|
|
|
|
TRACE("this->deviceID(%d), return_val = %ld\n", this->deviceID, return_val);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Return the number of bytes we have buffered thus far for output */
|
|
/* NOTE: convert from output bytes to input bytes in here */
|
|
long JACK_GetBytesStored(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
long retval = JACK_GetBytesStoredFromThis(this);
|
|
releaseDriver(this);
|
|
TRACE("deviceID(%d), retval = %ld\n", deviceID, retval);
|
|
return retval;
|
|
}
|
|
|
|
/* Return the number of bytes we can write to the device */
|
|
long JACK_GetBytesFreeSpace(int deviceID)
|
|
{
|
|
long return_val;
|
|
|
|
return_val = MAX_BUFFERED_BYTES - JACK_GetBytesStored(deviceID);
|
|
TRACE("deviceID(%d), MAX_BUFFERED_BYTES - bytes stored == %ld\n", deviceID, return_val);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Get the current position of the driver, either in bytes or */
|
|
/* in milliseconds */
|
|
/* NOTE: this is position relative to input bytes, output bytes may differ greatly due to input vs. output channel count */
|
|
static long JACK_GetPositionFromThis(jack_driver_t *this, enum pos_enum position, int type)
|
|
{
|
|
long return_val = 0;
|
|
struct timeval now;
|
|
long elapsedMS;
|
|
double sec2msFactor = 1000;
|
|
|
|
char *type_str;
|
|
|
|
/* if we are reset we should return a position of 0 */
|
|
if(this->state == RESET)
|
|
{
|
|
TRACE("we are currently RESET, returning 0\n");
|
|
return 0;
|
|
}
|
|
|
|
if(type == WRITTEN)
|
|
{
|
|
type_str = "WRITTEN";
|
|
return_val = this->client_bytes;
|
|
}
|
|
else if(type == WRITTEN_TO_JACK)
|
|
{
|
|
type_str = "WRITTEN_TO_JACK";
|
|
return_val = this->written_client_bytes;
|
|
}
|
|
else if(type == PLAYED) /* account for the elapsed time for the played_bytes */
|
|
{
|
|
type_str = "PLAYED";
|
|
return_val = this->played_client_bytes;
|
|
gettimeofday(&now, 0);
|
|
|
|
elapsedMS = TimeValDifference(&this->previousTime, &now); /* find the elapsed milliseconds since last JACK_Callback() */
|
|
|
|
TRACE("elapsedMS since last callback is '%ld'\n", elapsedMS);
|
|
|
|
/* account for the bytes played since the last JACK_Callback() */
|
|
/* NOTE: [Xms * (Bytes/Sec)] * (1 sec/1,000ms) */
|
|
/* NOTE: don't do any compensation if no data has been sent to jack since the last callback */
|
|
/* as this would result a bogus computed result */
|
|
if(this->clientBytesInJack != 0)
|
|
{
|
|
return_val+=((double)elapsedMS * ((double)JACK_GetInputBytesPerSecondFromThis(this) / sec2msFactor));
|
|
} else
|
|
{
|
|
TRACE("clientBytesInJack == 0\n");
|
|
}
|
|
}
|
|
|
|
/* add on the offset */
|
|
return_val+=this->position_byte_offset;
|
|
|
|
/* convert byte position to milliseconds value if necessary */
|
|
if(position == MILLISECONDS)
|
|
return_val = ((double)return_val / (double)JACK_GetInputBytesPerSecondFromThis(this)) * (double)sec2msFactor;
|
|
|
|
TRACE("this->deviceID(%d), type(%s), return_val = %ld\n", this->deviceID, type_str, return_val);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Get the current position of the driver, either in bytes or */
|
|
/* in milliseconds */
|
|
/* NOTE: this is position relative to input bytes, output bytes may differ greatly due to input vs. output channel count */
|
|
long JACK_GetPosition(int deviceID, enum pos_enum position, int type)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
long retval = JACK_GetPositionFromThis(this, position, type);
|
|
releaseDriver(this);
|
|
TRACE("retval == %ld\n", retval);
|
|
return retval;
|
|
}
|
|
|
|
|
|
// Set position always applies to written bytes
|
|
// NOTE: we must apply this instantly because if we pass this as a message
|
|
// to the callback we risk the user sending us audio data in the mean time
|
|
// and there is no need to send this as a message, we don't modify any
|
|
// internal variables
|
|
void JACK_SetPositionFromThis(jack_driver_t *this, enum pos_enum position, long value)
|
|
{
|
|
double sec2msFactor = 1000;
|
|
#if TRACE_ENABLE
|
|
long input_value = value;
|
|
#endif
|
|
|
|
/* convert the incoming value from milliseconds into bytes */
|
|
if(position == MILLISECONDS)
|
|
value = ((double)value*(double)JACK_GetInputBytesPerSecondFromThis(this)) / sec2msFactor;
|
|
|
|
/* ensure that if the user asks for the position */
|
|
/* they will at this instant get the correct position */
|
|
this->position_byte_offset = value - this->client_bytes;
|
|
|
|
TRACE("deviceID(%d) input_value of %ld, new value of %ld, setting position_byte_offset to %ld\n",
|
|
this->deviceID, input_value, value, this->position_byte_offset);
|
|
}
|
|
|
|
// Set position always applies to written bytes
|
|
// NOTE: we must apply this instantly because if we pass this as a message
|
|
// to the callback we risk the user sending us audio data in the mean time
|
|
// and there is no need to send this as a message, we don't modify any
|
|
// internal variables
|
|
void JACK_SetPosition(int deviceID, enum pos_enum position, long value)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
JACK_SetPositionFromThis(this, position, value);
|
|
releaseDriver(this);
|
|
|
|
TRACE("deviceID(%d) value of %ld\n",
|
|
this->deviceID, value);
|
|
}
|
|
|
|
/* Return the number of bytes per frame, or (output_channels * bits_per_channel) / 8 */
|
|
long JACK_GetBytesPerOutputFrame(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
long return_val = this->bytes_per_output_frame;
|
|
releaseDriver(this);
|
|
TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val);
|
|
return return_val;
|
|
}
|
|
|
|
/* Return the number of bytes we buffer max */
|
|
long JACK_GetMaxBufferedBytes(int deviceID)
|
|
{
|
|
long return_val;
|
|
|
|
return_val = MAX_BUFFERED_BYTES;
|
|
TRACE("getting MAX_BUFFERED_BYTES of %ld\n", return_val);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Set the max number of bytes the jack driver should buffer */
|
|
void JACK_SetMaxBufferedBytes(int deviceID, long max_buffered_bytes)
|
|
{
|
|
TRACE("setting MAX_BUFFERED_BYTES to %ld, from %ld\n", max_buffered_bytes, MAX_BUFFERED_BYTES);
|
|
MAX_BUFFERED_BYTES = max_buffered_bytes;
|
|
}
|
|
|
|
/* Get the number of output channels */
|
|
int JACK_GetNumOutputChannels(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
int return_val = this->num_output_channels;
|
|
releaseDriver(this);
|
|
TRACE("getting num_output_channels of %d\n", return_val);
|
|
return return_val;
|
|
}
|
|
|
|
/* Get the number of input channels */
|
|
int JACK_GetNumInputChannels(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
int return_val = this->num_input_channels;
|
|
releaseDriver(this);
|
|
TRACE("getting num_input_channels of %d\n", return_val);
|
|
return return_val;
|
|
}
|
|
|
|
int JACK_SetNumOutputChannels(int deviceID, int channels)
|
|
{
|
|
//FIXME: todo, should we resize buffers here by checking the size in JACK_callback() the next
|
|
// time around??
|
|
return 1;
|
|
}
|
|
|
|
/* RETURNS: previous number of input channels */
|
|
int JACK_SetNumInputChannels(int deviceID, int channels)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
int return_val = this->num_input_channels;
|
|
#if TRACE_ENABLE
|
|
int bpif = this->bytes_per_input_frame;
|
|
#endif
|
|
|
|
long positionMS = JACK_GetPositionFromThis(this, MILLISECONDS, PLAYED);
|
|
|
|
this->num_input_channels = channels;
|
|
this->bytes_per_input_frame = (this->bits_per_channel*this->num_input_channels)/8;
|
|
|
|
JACK_SetPositionFromThis(this, MILLISECONDS, positionMS);
|
|
|
|
releaseDriver(this);
|
|
|
|
TRACE("changing num_input_channels from '%d' to '%d'\n", return_val, channels);
|
|
TRACE("bytes_per_input_frame changed from '%d' to '%ld'\n", bpif, this->bytes_per_input_frame);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
/* Get the number of samples per second, the sample rate */
|
|
long JACK_GetSampleRate(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
int return_val = this->sample_rate;
|
|
releaseDriver(this);
|
|
TRACE("getting sample_rate of %d\n", return_val);
|
|
return return_val;
|
|
}
|
|
|
|
/* Initialize the jack porting libarary to a clean state */
|
|
void JACK_Init(void)
|
|
{
|
|
jack_driver_t *this;
|
|
int x, y;
|
|
|
|
TRACE("\n");
|
|
|
|
for(x = 0; x < MAX_OUTDEVICES; x++)
|
|
{
|
|
this = &outDev[x];
|
|
|
|
JACK_Reset(x);
|
|
|
|
this->deviceID = x; /* makes it easy to convert a pointer back into a deviceID */
|
|
this->client = 0;
|
|
this->in_use = FALSE;
|
|
for(y = 0; y < MAX_OUTPUT_PORTS; y++) /* make all volume 25% as a default */
|
|
this->volume[y] = 25;
|
|
this->volumeEffectType = linear;
|
|
this->state = CLOSED;
|
|
this->bytes_per_output_frame = 0;
|
|
this->bytes_per_input_frame = 0;
|
|
this->sample_rate = 0;
|
|
this->pMessages = 0; /* no messages */
|
|
this->position_byte_offset = 0; /* no offset applied now */
|
|
gettimeofday(&this->previousTime, 0); /* record the current time */
|
|
this->num_ticks = 0;
|
|
this->chunk_size = 0;
|
|
this->jackd_died = FALSE;
|
|
gettimeofday(&this->last_reconnect_attempt, 0);
|
|
|
|
pthread_mutex_init(&this->mutex, NULL);
|
|
}
|
|
|
|
TRACE("finished\n");
|
|
}
|
|
|
|
/* Get the latency, in ms, of jack */
|
|
long JACK_GetJackLatency(int deviceID)
|
|
{
|
|
jack_driver_t *this = getDriver(deviceID);
|
|
long return_val;
|
|
|
|
return_val = jack_port_get_latency(this->output_port[0]);
|
|
TRACE("got latency of %ldms\n", return_val);
|
|
|
|
releaseDriver(this);
|
|
return return_val;
|
|
}
|
|
|
|
|
|
|
|
#endif
|