OSDN Git Service

Added JACK output support.
authorTakashi Iwai <tiwai@suse.de>
Mon, 25 Aug 2003 10:08:08 +0000 (10:08 +0000)
committerTakashi Iwai <tiwai@suse.de>
Mon, 25 Aug 2003 10:08:08 +0000 (10:08 +0000)
configure.in
timidity/Makefile.am
timidity/jack_a.c [new file with mode: 0644]
timidity/output.c

index ad82a97..2b3b9f0 100644 (file)
@@ -605,7 +605,7 @@ dnl portaudio(p) PortAudio
 dnl vorbis(v):  Ogg Vorbis
 dnl gogo(g):    MP3 GOGO
 
-audio_targets='default oss alsa sun hpux irix mme sb_dsp w32 alib nas arts esd vorbis gogo portaudio'
+audio_targets='default oss alsa sun hpux irix mme sb_dsp w32 alib nas arts esd vorbis gogo portaudio jack'
 
 AC_ARG_WITH(nas-library,
   [  --with-nas-library=library NAS absolute library path(Don't use -laudio)])
@@ -1024,6 +1024,17 @@ else
   AC_MSG_RESULT(no)
 fi
 
+dnl JACK
+AC_MSG_CHECKING(enable_audio=jack)
+if test "x$au_enable_jack" = xyes; then
+  AC_MSG_RESULT(yes)
+  SYSEXTRAS="$SYSEXTRAS jack_a.c"
+  EXTRALIBS="$EXTRALIBS $(pkg-config --libs jack)"
+  EXTRADEFS="$EXTRADEFS -DAU_JACK $(pkg-config --cflags jack)"
+else
+  AC_MSG_RESULT(no)
+fi
+
 dnl ogg's vorbis
 AC_MSG_CHECKING(enable_audio=vorbis)
 if test "x$au_enable_vorbis" = xyes; then
index fc25c72..b04d79f 100644 (file)
@@ -122,6 +122,7 @@ EXTRA_timidity_SOURCES = \
        esd_a.c \
        hpux_a.c \
        hpux_d_a.c \
+       jack_a.c \
        oss_a.c \
        mac_a.c \
        mac_qt_a.c \
diff --git a/timidity/jack_a.c b/timidity/jack_a.c
new file mode 100644 (file)
index 0000000..cde1ab3
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+    TiMidity++ -- MIDI to WAVE converter and player
+    Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
+    Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
+
+    jack_a.c - Copyright (C) 2003 Takashi Iwai <tiwai@suse.de>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    jack_a.c
+
+    Functions to play sound on the JACK system
+
+
+    Since the model of JACK (so-called "pull" model) doesn't match
+    with the current model of TiMidity ("push" model), this audio
+    driver is implemented with another intermediate ring buffer.
+
+    The audio data from timidity (S16 or U8 PCM) is converted to
+    JACK audio stream(s) in float and stored once on the ring buffer,
+    which is read eventually in the client's process callback and
+    copied to the JACK buffers.  Note that this buffer is independent
+    from the audio-queue.  (Yes, it's re-invention of wheels, but we
+    cannot handle dynamic memory allocation in JACK's process thread.)
+
+    The buffer size options (-B) specify the size of the ring buffer.
+    To get the best perfomance, you'll need to set up them to match
+    with the parameter of JACK server.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef NO_STRING_H /* for memmove */
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#include <jack/jack.h>
+
+#include "timidity.h"
+#include "common.h"
+#include "output.h"
+#include "controls.h"
+#include "timer.h"
+#include "instrum.h"
+#include "playmidi.h"
+#include "miditrace.h"
+
+/*
+ */
+static int open_jack(void);
+static void close_jack(void);
+static int write_jack(char *buf, int32 nbytes);
+static int actl_jack(int request, void *arg);
+
+
+/*
+ */
+
+#define dpm jack_play_mode
+
+PlayMode dpm = {
+       DEFAULT_RATE,
+       PE_16BIT|PE_SIGNED,
+       PF_PCM_STREAM|PF_CAN_TRACE|PF_BUFF_FRAGM_OPT,
+       -1,
+       {0},
+       "JACK device", 'j',
+       NULL,
+       open_jack,
+       close_jack,
+       write_jack,
+       actl_jack
+};
+
+/*
+ * simple ring-buffer
+ */
+
+struct tm_ringbuf {
+       long rdptr, wrptr;      /* read, write pointers (not bound in ringbuffer size!) */
+       int size;               /* ring buffer size */
+       jack_default_audio_sample_t *buf[2];    /* left, right buffers */
+};
+
+static void ringbuf_init(struct tm_ringbuf *rbuf, int size, int channels)
+{
+       int i;
+
+       memset(rbuf, 0, sizeof(*rbuf));
+       rbuf->size = size;
+       for (i = 0; i < channels; i++)
+               rbuf->buf[i] = (jack_default_audio_sample_t *)safe_malloc(sizeof(jack_default_audio_sample_t) * size);
+       rbuf->rdptr = rbuf->wrptr = 0;
+}
+
+static void ringbuf_destroy(struct tm_ringbuf *rbuf)
+{
+       int i;
+       for (i = 0; i < 2; i++) {
+               free(rbuf->buf[i]);
+               rbuf->buf[i] = NULL;
+       }
+       rbuf->rdptr = rbuf->wrptr = 0;
+}
+
+/* get avaialble samples for read */
+inline static int ringbuf_get_available(struct tm_ringbuf *rbuf)
+{
+       return rbuf->wrptr - rbuf->rdptr;
+}
+
+/* get empty sample spaces for write */
+inline static int ringbuf_get_empty(struct tm_ringbuf *rbuf)
+{
+       return rbuf->size - ringbuf_get_available(rbuf);
+}
+
+static int ringbuf_bound_readable(struct tm_ringbuf *rbuf, int size)
+{
+       int rdptr = rbuf->rdptr % rbuf->size;
+       if (rdptr + size >= rbuf->size)
+               size = rbuf->size - rdptr;
+       return size;
+}
+
+inline static jack_default_audio_sample_t *
+ringbuf_get_readbuf(struct tm_ringbuf *rbuf, int c)
+{
+       return rbuf->buf[c] + (rbuf->rdptr % rbuf->size);
+}
+
+inline static void ringbuf_read_advance(struct tm_ringbuf *rbuf, int size)
+{
+       rbuf->rdptr += size;
+}
+
+static int ringbuf_bound_writable(struct tm_ringbuf *rbuf, int size)
+{
+       int wrptr = rbuf->wrptr % rbuf->size;
+       if (wrptr + size >= rbuf->size)
+               size = rbuf->size - wrptr;
+       return size;
+}
+
+inline static jack_default_audio_sample_t *
+ringbuf_get_writebuf(struct tm_ringbuf *rbuf, int c)
+{
+       return rbuf->buf[c] + (rbuf->wrptr % rbuf->size);
+}
+
+inline static void ringbuf_write_advance(struct tm_ringbuf *rbuf, int size)
+{
+       rbuf->wrptr += size;
+}
+
+inline static void ringbuf_clear(struct tm_ringbuf *rbuf)
+{
+       rbuf->wrptr = rbuf->rdptr = 0;
+}
+
+
+/*
+ * jack control context
+ */
+struct tm_jack {
+       jack_client_t *client;
+       jack_port_t *ports[2];
+
+       int channels;           /* number of channels */
+       int sample_16bit;       /* 16bit sample */
+       int shift;              /* sample bit shift */
+       int frag_size;          /* buffer fragment size (in samples) */
+       int frags;              /* buffer fragments */
+
+       pthread_cond_t cond;
+       pthread_mutex_t lock;
+       int running;
+       int shutdown;
+
+       struct tm_ringbuf rbuf;
+};
+
+
+/*
+ * jack process callback.
+ * here we only copy the stream data from the ring buffer.
+ */
+
+static int transfer_callback(jack_nframes_t nframes, void *arg)
+{
+       struct tm_jack *ctx = (struct tm_jack *)arg;
+       int i;
+       int size;
+       jack_default_audio_sample_t *outbuf[2];
+
+       for (i = 0; i < ctx->channels; i++)
+               outbuf[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(ctx->ports[i], nframes);
+
+       if (! ctx->running) {
+               /* not running yet, set silence and quit */
+               for (i = 0; i < ctx->channels; i++)
+                       memset(outbuf[i], 0, sizeof(jack_default_audio_sample_t) * nframes);
+               return 0;
+       }
+
+       size = ringbuf_get_available(&ctx->rbuf);
+       if (size > nframes)
+               size = nframes;
+
+       while (size > 0) {
+               int size1 = ringbuf_bound_readable(&ctx->rbuf, size);
+               for (i = 0; i < ctx->channels; i++) {
+                       memcpy(outbuf[i], ringbuf_get_readbuf(&ctx->rbuf, i),
+                              size1 * sizeof(jack_default_audio_sample_t));
+                       outbuf[i] += size1;
+               }
+               ringbuf_read_advance(&ctx->rbuf, size1);
+               size -= size1;
+       }
+       /* wake up the main thread */
+       pthread_cond_signal(&ctx->cond);
+       return 0;
+}
+
+static void shutdown_callback(void *arg)
+{
+       struct tm_jack *ctx = (struct tm_jack *)arg;
+       if (! ctx->shutdown)
+               safe_exit(1);
+}
+
+
+/*
+ */
+
+static struct tm_jack jack_ctx;
+
+#define TIMIDITY_JACK_CLIENT_NAME      "TiMidity"
+#define TIMIDITY_JACK_PORT_NAME                "port_%d"
+
+static int open_jack(void)
+{
+       int i, rate;
+       int ret_val = 0;
+       struct tm_jack *ctx = &jack_ctx;
+       const char **dst_ports;
+
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->client = jack_client_new(TIMIDITY_JACK_CLIENT_NAME);
+       if (! ctx->client)
+               return -1;
+
+       jack_set_process_callback(ctx->client, transfer_callback, ctx);
+       jack_on_shutdown(ctx->client, shutdown_callback, ctx);
+
+       dpm.encoding &= ~(PE_ULAW|PE_ALAW|PE_BYTESWAP);
+       /* check channels */
+       if (dpm.encoding & PE_MONO)
+               ctx->channels = 1;
+       else
+               ctx->channels = 2;
+
+       /* sample bit check */
+       ctx->sample_16bit = (dpm.encoding & PE_16BIT) ? 1 : 0;
+       if (ctx->sample_16bit)
+               dpm.encoding |= PE_SIGNED; /* S16 only */
+       else
+               dpm.encoding &= ~PE_SIGNED; /* U8 only */
+
+       /* check sample bit shift */
+       ctx->shift = 0;
+       if (ctx->channels > 1)
+               ctx->shift++;
+       if (ctx->sample_16bit)
+               ctx->shift++;
+
+       for (i = 0; i < ctx->channels; i++) {
+               char name[32];
+               sprintf(name, TIMIDITY_JACK_PORT_NAME, i + 1);
+               ctx->ports[i] = jack_port_register(ctx->client, name,
+                                                  JACK_DEFAULT_AUDIO_TYPE,
+                                                  JackPortIsOutput, 0);
+               if (! ctx->ports[i]) {
+                       ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+                                 "Cannot register a JACK port '%s'", name);
+                       jack_client_close(ctx->client);
+                       return -1;
+               }
+       }
+
+       /* check the sample rate.
+        * if not match, correct the rate
+        */
+       rate = jack_get_sample_rate(ctx->client);
+       if (rate != dpm.rate) {
+               dpm.rate = rate;
+               ret_val = 1;
+       }
+
+       /* get the buffer sizes */
+       if (dpm.extra_param[1] != 0)
+               ctx->frag_size = dpm.extra_param[1];
+       else
+               ctx->frag_size = audio_buffer_size;
+       if (dpm.extra_param[0] == 0)
+               ctx->frags = 2;
+       else
+               ctx->frags = dpm.extra_param[0];
+
+       /* initialize rest stuffs */
+       pthread_cond_init(&ctx->cond, NULL);
+       pthread_mutex_init(&ctx->lock, NULL);
+       ringbuf_init(&ctx->rbuf, ctx->frag_size * ctx->frags, ctx->channels);
+       ctx->running = 0;
+
+       /* rock it baby */
+       if (jack_activate(ctx->client) < 0) {
+               ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Cannot activate JACK engine");
+               jack_client_close(ctx->client);
+               ctx->client = NULL;
+               return -1;
+       }
+
+       /* 
+        * it seems the JACK port connection must be done after
+        * activating jack client...
+        */
+       /* detect destination ports */
+       dst_ports = jack_get_ports(ctx->client, NULL, NULL,
+                                  JackPortIsInput|JackPortIsPhysical);
+       if (dst_ports) {
+               /* connect them */
+               for (i = 0; dst_ports[i] && i < ctx->channels; i++) {
+                       if (jack_connect(ctx->client, jack_port_name(ctx->ports[i]), dst_ports[i]))
+                               break;
+               }
+               free(dst_ports);
+       }
+
+       return ret_val;
+}
+
+/*
+ * close callback
+ */
+static void close_jack(void)
+{
+       struct tm_jack *ctx = &jack_ctx;
+       if (ctx->client) {
+               ctx->shutdown = 1;
+               ctx->running = 0;
+               jack_deactivate(ctx->client);
+               sleep(2);
+               jack_client_close(ctx->client);
+               sleep(2);
+               ringbuf_destroy(&ctx->rbuf);
+               pthread_cond_destroy(&ctx->cond);
+       }
+}
+
+/*
+ * convert 16bit PCM to JACK float [-1,1]
+ */
+static void convert_stream_16(struct tm_jack *ctx, int c, int size, short *buf)
+{
+       int i;
+       jack_default_audio_sample_t *inbuf = ringbuf_get_writebuf(&ctx->rbuf, c);
+       for (i = 0; i < size; i++, buf += ctx->channels, inbuf++) {
+               /* well, we can use ftol() in C99 but here let's leave
+                * the optimization for the compiler...
+                */
+               jack_default_audio_sample_t val;
+               val = (jack_default_audio_sample_t)*buf / 32768.0;
+               *inbuf = val;
+       }
+}
+
+/*
+ * convert 8bit PCM to JACK float [-1,1]
+ */
+static void convert_stream_8(struct tm_jack *ctx, int c, int size, char *buf)
+{
+       int i;
+       jack_default_audio_sample_t *inbuf = ringbuf_get_writebuf(&ctx->rbuf, c);
+       for (i = 0; i < size; i++, buf += ctx->channels, inbuf++) {
+               signed char cval = *buf ^ 0x80; /* to signed char */
+               *inbuf = (jack_default_audio_sample_t)cval / 128.0;
+       }
+}
+
+/*
+ * write callback
+ */
+static int write_jack(char *buf, int32 nbytes)
+{
+       struct tm_jack *ctx = &jack_ctx;
+       int nframes;
+       int i;
+
+       nframes = nbytes >> ctx->shift;
+       if (nframes <= 0)
+               return 0;
+
+       for (;;) {
+               int size = ringbuf_get_empty(&ctx->rbuf);
+               if (size > nframes)
+                       size = nframes;
+               while (size > 0) {
+                       int size1 = ringbuf_bound_writable(&ctx->rbuf, size);
+                       if (ctx->sample_16bit) {
+                               short *sbuf = (short *)buf;
+                               for (i = 0; i < ctx->channels; i++, sbuf++)
+                                       convert_stream_16(ctx, i, size1, sbuf);
+                       } else {
+                               char *sbuf = (char *)buf;
+                               for (i = 0; i < ctx->channels; i++, sbuf++)
+                                       convert_stream_8(ctx, i, size1, sbuf);
+                       }
+                       ringbuf_write_advance(&ctx->rbuf, size1);
+                       /* set running flag */
+                       ctx->running = 1;
+                       nframes -= size1;
+                       if (! nframes)
+                               return;
+                       buf += size1 << ctx->shift;
+                       size -= size1;
+               }
+
+               /* blocking behavior: sleep until the process thread
+                * wakes up...
+                */
+               pthread_cond_wait(&ctx->cond, &ctx->lock);
+       }
+}
+
+
+/*
+ * audio control callback
+ */
+static int actl_jack(int request, void *arg)
+{
+       struct tm_jack *ctx = &jack_ctx;
+
+       switch (request) {
+       case PM_REQ_GETFRAGSIZ:
+               if (ctx->frag_size == 0)
+                       return -1;
+               *((int *)arg) = ctx->frag_size;
+               return 0;
+
+       case PM_REQ_GETQSIZ:
+               if (ctx->frag_size == -1)
+                       return -1;
+               *((int *)arg) = ctx->frag_size * ctx->frags;
+               return 0;
+
+       case PM_REQ_GETFILLABLE:
+               *((int *)arg) = ringbuf_get_empty(&ctx->rbuf);
+               return 0;
+    
+       case PM_REQ_GETFILLED:
+               *((int *)arg) = ringbuf_get_available(&ctx->rbuf);
+               return 0;
+
+       case PM_REQ_GETSAMPLES:
+               *((int *)arg) = ctx->rbuf.rdptr;
+               return 0;
+
+       case PM_REQ_FLUSH:
+               if (ctx->running) {
+                       while (ringbuf_get_available(&ctx->rbuf) > 0)
+                               pthread_cond_wait(&ctx->cond, &ctx->lock);
+               }
+               /* fallthrough */
+       case PM_REQ_DISCARD:
+               ctx->running = 0;
+               ringbuf_clear(&ctx->rbuf);
+               return 0;
+
+       }
+       return -1;
+}
+
index 23ed43c..912a42c 100644 (file)
@@ -95,6 +95,10 @@ extern PlayMode esd_play_mode;
 extern PlayMode portaudio_play_mode;
 #endif /* AU_PORTAUDIO */
 
+#ifdef AU_JACK
+extern PlayMode jack_play_mode;
+#endif /* AU_NAS */
+
 #ifdef AU_NAS
 extern PlayMode nas_play_mode;
 #endif /* AU_NAS */
@@ -139,6 +143,10 @@ PlayMode *play_mode_list[] = {
   &portaudio_play_mode,
 #endif /* AU_PORTAUDIO */
 
+#if defined(AU_JACK)
+  &jack_play_mode,
+#endif /* AU_PORTAUDIO */
+
 #if defined(AU_NAS)
   &nas_play_mode,
 #endif /* AU_NAS */