/*
 * Wine Driver for jack Sound Server
 *   http://jackit.sourceforge.net
 *
 * Copyright 1994 Martin Ayotte
 * Copyright 1999 Eric Pouech (async playing in waveOut/waveIn)
 * Copyright 2000 Eric Pouech (loops in waveOut)
 * Copyright 2002 Chris Morgan (jack version of this file)
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/*
 * TODO:
 *  implement audio stream resampling for any arbitrary frequenty
 *    right now we use the winmm layer to do resampling although it would 
 *    be nice to have a full set of algorithms to choose from based on cpu 
 *    time
 *
 * FIXME:
 *  pause in waveOut during loop is not handled correctly
 */

#include "config.h"

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <fcntl.h>
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "wingdi.h"
#include "winerror.h"
#include "mmddk.h"
#include "dsound.h"
#include "dsdriver.h"
#include "jack.h"
#include "wine/unicode.h"
#include "wine/library.h"
#include "wine/debug.h"

#ifdef HAVE_JACK_JACK_H
#include <jack/jack.h>
#endif


WINE_DEFAULT_DEBUG_CHANNEL(wave);

#ifdef HAVE_JACK_JACK_H

#define MAKE_FUNCPTR(f) static typeof(f) * fp_##f = NULL;

/* Function pointers for dynamic loading of libjack */
/* these are prefixed with "fp_", ie. "fp_jack_client_new" */
MAKE_FUNCPTR(jack_activate);
MAKE_FUNCPTR(jack_connect);
MAKE_FUNCPTR(jack_client_new);
MAKE_FUNCPTR(jack_client_close);
MAKE_FUNCPTR(jack_deactivate);
MAKE_FUNCPTR(jack_set_process_callback);
MAKE_FUNCPTR(jack_set_buffer_size_callback);
MAKE_FUNCPTR(jack_set_sample_rate_callback);
MAKE_FUNCPTR(jack_on_shutdown);
MAKE_FUNCPTR(jack_get_sample_rate);
MAKE_FUNCPTR(jack_port_register);
MAKE_FUNCPTR(jack_port_get_buffer);
MAKE_FUNCPTR(jack_get_ports);
MAKE_FUNCPTR(jack_port_name);
MAKE_FUNCPTR(jack_get_buffer_size);
#undef MAKE_FUNCPTR

/* define the below to work around a bug in jack where closing a port */
/* takes a very long time, so to get around this we actually don't */
/* close the port when the device is closed but instead mark the */
/* corresponding device as unused */
#define JACK_CLOSE_HACK    1

typedef jack_default_audio_sample_t sample_t;
typedef jack_nframes_t nframes_t;

/* only allow 10 output devices through this driver, this ought to be adequate */
#define MAX_WAVEOUTDRV  (10)
#define MAX_WAVEINDRV   (10)

/* state diagram for waveOut writing:
 *
 * +---------+-------------+---------------+---------------------------------+
 * |  state  |  function   |     event     |            new state	     |
 * +---------+-------------+---------------+---------------------------------+
 * |	     | open()	   |		   | STOPPED		       	     |
 * | PAUSED  | write()	   | 		   | PAUSED		       	     |
 * | STOPPED | write()	   | <thrd create> | PLAYING		  	     |
 * | PLAYING | write()	   | HEADER        | PLAYING		  	     |
 * | (other) | write()	   | <error>       |		       		     |
 * | (any)   | pause()	   | PAUSING	   | PAUSED		       	     |
 * | PAUSED  | restart()   | RESTARTING    | PLAYING (if no thrd => STOPPED) |
 * | (any)   | reset()	   | RESETTING     | STOPPED		      	     |
 * | (any)   | close()	   | CLOSING	   | CLOSED		      	     |
 * +---------+-------------+---------------+---------------------------------+
 */

/* states of the playing device */
#define	WINE_WS_PLAYING   0
#define	WINE_WS_PAUSED    1
#define	WINE_WS_STOPPED   2
#define WINE_WS_CLOSED    3

typedef struct {
    volatile int      state;      /* one of the WINE_WS_ manifest constants */
    WAVEOPENDESC      waveDesc;
    WORD              wFlags;
    PCMWAVEFORMAT     format;
    WAVEOUTCAPSW      caps;
    WORD              wDevID;
    char              interface_name[32];

    jack_port_t*      out_port_l;   /* ports for left and right channels */
    jack_port_t*      out_port_r;
    jack_client_t*    client;
    long              sample_rate;        /* jack server sample rate */

#if JACK_CLOSE_HACK
    BOOL              in_use; /* TRUE if this device is in use */
#endif

    char*             sound_buffer;
    unsigned long     buffer_size;

    DWORD             volume_left;
    DWORD             volume_right;

    LPWAVEHDR         lpQueuePtr;   /* start of queued WAVEHDRs (waiting to be notified) */
    LPWAVEHDR         lpPlayPtr;    /* start of not yet fully played buffers */
    DWORD             dwPartialOffset;  /* Offset of not yet written bytes in lpPlayPtr */

    LPWAVEHDR         lpLoopPtr;    /* pointer of first buffer in loop, if any */
    DWORD             dwLoops;      /* private copy of loop counter */
    
    DWORD             dwPlayedTotal;    /* number of bytes actually played since opening */
    DWORD             dwWrittenTotal;   /* number of bytes written to jack since opening */

    DWORD	      bytesInJack; /* bytes that we wrote during the previous JACK_Callback() */
    DWORD	      tickCountMS; /* time in MS of last JACK_Callback() */

    /* synchronization stuff */
    CRITICAL_SECTION    access_crst;
} WINE_WAVEOUT;

typedef struct {
    volatile int    state;
    WAVEOPENDESC    waveDesc;
    WORD            wFlags;
    PCMWAVEFORMAT   format;
    LPWAVEHDR       lpQueuePtr;
    DWORD           dwTotalRecorded;
    WAVEINCAPSW     caps;
    BOOL            bTriggerSupport;
    WORD              wDevID;
    char              interface_name[32];

    jack_port_t*      in_port_l;   /* ports for left and right channels */
    jack_port_t*      in_port_r;
    jack_client_t*    client;
    long              sample_rate;        /* jack server sample rate */

#if JACK_CLOSE_HACK
    BOOL              in_use; /* TRUE if this device is in use */
#endif

    char*             sound_buffer;
    unsigned long     buffer_size;

    /* synchronization stuff */
    CRITICAL_SECTION    access_crst;
} WINE_WAVEIN;

static WINE_WAVEOUT WOutDev   [MAX_WAVEOUTDRV];
static WINE_WAVEIN  WInDev    [MAX_WAVEINDRV ];

static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv);
static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc);

static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo);
static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force);

static int JACK_OpenWaveOutDevice(WINE_WAVEOUT* wwo);
static int JACK_OpenWaveInDevice(WINE_WAVEIN* wwi, WORD nChannels);

#if JACK_CLOSE_HACK
static void	JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo, BOOL close_client);
#else
static void	JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo);
#endif

#if JACK_CLOSE_HACK
static void	JACK_CloseWaveInDevice(WINE_WAVEIN* wwi, BOOL close_client);
#else
static void	JACK_CloseWaveInDevice(WINE_WAVEIN* wwi);
#endif

static DWORD bytes_to_mmtime(LPMMTIME lpTime, DWORD position,
                             PCMWAVEFORMAT* format)
{
    TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%lu nChannels=%u nAvgBytesPerSec=%lu\n",
          lpTime->wType, format->wBitsPerSample, format->wf.nSamplesPerSec,
          format->wf.nChannels, format->wf.nAvgBytesPerSec);
    TRACE("Position in bytes=%lu\n", position);

    switch (lpTime->wType) {
    case TIME_SAMPLES:
        lpTime->u.sample = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
        TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample);
        break;
    case TIME_MS:
        lpTime->u.ms = 1000.0 * position / (format->wBitsPerSample / 8 * format->wf.nChannels * format->wf.nSamplesPerSec);
        TRACE("TIME_MS=%lu\n", lpTime->u.ms);
        break;
    case TIME_SMPTE:
        lpTime->u.smpte.fps = 30;
        position = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
        position += (format->wf.nSamplesPerSec / lpTime->u.smpte.fps) - 1; /* round up */
        lpTime->u.smpte.sec = position / format->wf.nSamplesPerSec;
        position -= lpTime->u.smpte.sec * format->wf.nSamplesPerSec;
        lpTime->u.smpte.min = lpTime->u.smpte.sec / 60;
        lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min;
        lpTime->u.smpte.hour = lpTime->u.smpte.min / 60;
        lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour;
        lpTime->u.smpte.fps = 30;
        lpTime->u.smpte.frame = position * lpTime->u.smpte.fps / format->wf.nSamplesPerSec;
        TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n",
              lpTime->u.smpte.hour, lpTime->u.smpte.min,
              lpTime->u.smpte.sec, lpTime->u.smpte.frame);
        break;
    default:
        WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType);
        lpTime->wType = TIME_BYTES;
        /* fall through */
    case TIME_BYTES:
        lpTime->u.cb = position;
        TRACE("TIME_BYTES=%lu\n", lpTime->u.cb);
        break;
    }
    return MMSYSERR_NOERROR;
}


/*======================================================================*
 *                  Low level WAVE implementation			*
 *======================================================================*/

#define SAMPLE_MAX_16BIT  32767.0f

/* Alsaplayer function that applies volume changes to a buffer */
/* (C) Andy Lo A Foe */
/* Length is in terms of 32 bit samples */
void volume_effect32(void *buffer, int length, int left, int right)
{
        short *data = (short *)buffer;
        int i, v;
    
        if (right == -1) right = left;
 
        for(i = 0; i < length; i++) {
                v = (int) ((*(data) * left) / 100);
                *(data++) = (v>32767) ? 32767 : ((v<-32768) ? -32768 : v);
                v = (int) ((*(data) * right) / 100);
                *(data++) = (v>32767) ? 32767 : ((v<-32768) ? -32768 : v);
        }
}

/* move 16 bit mono/stereo to 16 bit stereo */
void sample_move_d16_d16(short *dst, short *src,
                  unsigned long nsamples, int nChannels)
{
  while(nsamples--)
  {
    *dst = *src;
    dst++;

    if(nChannels == 2) src++;

    *dst = *src;
    dst++;

    src++;
  }
}

/* convert from 16 bit to floating point */
/* allow for copying of stereo data with alternating left/right */
/* channels to a buffer that will hold a single channel stream */
/* nsamples is in terms of 16bit samples */
/* src_skip is in terms of 16bit samples */
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;
  }
}       

/* convert from floating point to 16 bit */
/* allow for copying of a buffer that will hold a single channel stream */
/* to stereo data with alternating left/right channels */
/* nsamples is in terms of float samples */
/* dst_skip is in terms of 16bit samples */
void sample_move_s16_d16 (short *dst, sample_t *src,
                        unsigned long nsamples, unsigned long dst_skip)
{
  /* ALERT: signed sign-extension portability !!! */
  while (nsamples--)
  {
      *dst = (*src) * SAMPLE_MAX_16BIT;
/*      TRACE("src=(%.8f,%p) dst=(%d,%p)\n",*src,src,*dst,dst); */
      dst += dst_skip;
      src++;
  }
}       


/* 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_wwo
 */
/* 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 */
int JACK_callback_wwo (nframes_t nframes, void *arg)
{
  sample_t* out_l;
  sample_t* out_r;
  WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg;

  TRACE("wDevID: %u, nframes %u state=%u\n", wwo->wDevID, nframes,wwo->state);

  if(!wwo->client)
    ERR("client is closed, this is weird...\n");
    
  out_l = (sample_t *) fp_jack_port_get_buffer(wwo->out_port_l, nframes);
  out_r = (sample_t *) fp_jack_port_get_buffer(wwo->out_port_r, nframes);

  if(wwo->state == WINE_WS_PLAYING)
  {
    DWORD jackFramesAvailable = nframes;
    DWORD outputFramesAvailable;
    DWORD numFramesToWrite;

    long written = 0;
    char* buffer;

#if JACK_CLOSE_HACK
    if(wwo->in_use == FALSE)
    {
      /* output silence if nothing is being outputted */
      sample_silence_dS(out_l, nframes);
      sample_silence_dS(out_r, nframes);

      return 0;
    }
#endif

    TRACE("wwo.state == WINE_WS_PLAYING\n");

    /* see if our sound_buffer is large enough to hold the number of frames jack requested */
    /* Note: sound_buffer is always filled with 16-bit stereo data, even for mono mode */
    if(wwo->buffer_size < (nframes * sizeof(short) * 2))
    {
      ERR("for some reason JACK_BufSize() didn't allocate enough memory\n");
      ERR("allocated %ld bytes, need %d bytes\n", wwo->buffer_size, (nframes * sizeof(short) * 2));
      return 0;
    }

    /* while we have jackFramesAvailable and a wave header to be played */
    while(jackFramesAvailable && wwo->lpPlayPtr)
    {
      /* find the amount of audio to be played at this time */
      outputFramesAvailable = (wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset) / wwo->format.wf.nBlockAlign;

      numFramesToWrite = min(jackFramesAvailable, outputFramesAvailable);
      TRACE("dwBufferLength=(%ld) dwPartialOffset=(%ld)\n",wwo->lpPlayPtr->dwBufferLength,wwo->dwPartialOffset);
      TRACE("outputFramesAvailable == %ld, jackFramesAvailable == %ld\n", outputFramesAvailable, jackFramesAvailable);

      buffer = wwo->lpPlayPtr->lpData + wwo->dwPartialOffset;

      /* convert from mono to stereo if necessary */
      /* otherwise just memcpy to the output buffer */

      if(wwo->format.wf.nChannels == 1)
      {
	sample_move_d16_d16((short*)wwo->sound_buffer + ((nframes - jackFramesAvailable) * sizeof(short)),
			    (short*)buffer, numFramesToWrite, wwo->format.wf.nChannels);
      } else /* just copy the memory over */
      {
        memcpy(wwo->sound_buffer + ((nframes - jackFramesAvailable) * wwo->format.wf.nBlockAlign),
	       buffer, numFramesToWrite * wwo->format.wf.nBlockAlign);
      }

      /* advance to the next wave header if possible, or advance pointer */
      /* inside of the current header if we haven't completed it */
      if(numFramesToWrite == outputFramesAvailable)
      {
        wodHelper_PlayPtrNext(wwo);            /* we wrote the whole waveheader, skip to the next one*/
      }
      else
      {
        wwo->dwPartialOffset+=(numFramesToWrite * wwo->format.wf.nBlockAlign); /* else advance by the bytes we took in to write */
      }

      written+=(numFramesToWrite * wwo->format.wf.nBlockAlign); /* add on what we wrote */
      jackFramesAvailable-=numFramesToWrite; /* take away what was written in terms of output bytes */
    }

    wwo->tickCountMS = GetTickCount();    /* record the current time */
    wwo->dwWrittenTotal+=written; /* update states on wave device */
    wwo->dwPlayedTotal+=wwo->bytesInJack; /* we must have finished with the last bytes or we wouldn't be back inside of this callback again... */
    wwo->bytesInJack = written; /* record the bytes inside of jack */

    /* 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, apply volume and output */
    /* the audio to the jack server */

    /* apply volume to the buffer */
    volume_effect32(wwo->sound_buffer, (nframes - jackFramesAvailable), wwo->volume_left, wwo->volume_right);

    /* convert from stereo 16 bit to single channel 32 bit float */
    /* for each jack server channel */
    /* NOTE: we skip over two sample since we want to only get either the left or right channel */
    sample_move_d16_s16(out_l, (short*)wwo->sound_buffer,     (nframes - jackFramesAvailable), 2);
    sample_move_d16_s16(out_r, (short*)wwo->sound_buffer + 1, (nframes - jackFramesAvailable), 2);

    /* 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 */
    if(jackFramesAvailable)
    {
      ERR("buffer underrun of %ld frames\n", jackFramesAvailable);
      sample_silence_dS(out_l + (nframes - jackFramesAvailable), jackFramesAvailable);
      sample_silence_dS(out_r + (nframes - jackFramesAvailable), jackFramesAvailable);
    }
  }
  else if(wwo->state == WINE_WS_PAUSED || 
    wwo->state == WINE_WS_STOPPED ||
    wwo->state == WINE_WS_CLOSED)
  {
      /* output silence if nothing is being outputted */
      sample_silence_dS(out_l, nframes);
      sample_silence_dS(out_r, nframes);
  }

  /* notify the client of completed wave headers */
  EnterCriticalSection(&wwo->access_crst);
  wodHelper_NotifyCompletions(wwo, FALSE);
  LeaveCriticalSection(&wwo->access_crst);

  return 0;
}

/******************************************************************
 *		JACK_bufsize_wwo
 *
 *		Called whenever the jack server changes the the max number 
 *		of frames passed to JACK_callback
 */
int JACK_bufsize_wwo (nframes_t nframes, void *arg)
{
  WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg;
  DWORD buffer_required;
  TRACE("wDevID=%d\n",wwo->wDevID);
  TRACE("the maximum buffer size is now %u frames\n", nframes);

  /* make sure the callback routine has adequate memory */
    /* 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) */
  EnterCriticalSection(&wwo->access_crst);

  /* wwo->sound_buffer is always filled with 16-bit stereo data, even for mono streams */
  buffer_required = nframes * sizeof(short) * 2;
  TRACE("wwo->buffer_size (%ld) buffer_required (%ld).\n", wwo->buffer_size,buffer_required);
  if(wwo->buffer_size < buffer_required)
  {
    TRACE("expanding buffer from wwo->buffer_size == %ld, to %ld\n", 
      wwo->buffer_size, buffer_required);
    TRACE("GetProcessHeap() == %p\n", GetProcessHeap());
    wwo->buffer_size = buffer_required;

    if (wwo->sound_buffer)
      wwo->sound_buffer = HeapReAlloc(GetProcessHeap(), 0, wwo->sound_buffer, wwo->buffer_size);
    else
      wwo->sound_buffer = HeapAlloc(GetProcessHeap(), 0, wwo->buffer_size);

    /* if we don't have a buffer then error out */
    if(!wwo->sound_buffer)
    {
        ERR("error allocating sound_buffer memory\n");
        LeaveCriticalSection(&wwo->access_crst);
        return 0;
    }
  }

  LeaveCriticalSection(&wwo->access_crst);

  TRACE("ending\n");

  return 0;
}
/******************************************************************
 *		JACK_bufsize_wwi
 *
 *		Called whenever the jack server changes the the max number 
 *		of frames passed to JACK_callback
 */
int JACK_bufsize_wwi (nframes_t nframes, void *arg)
{
  TRACE("the maximum buffer size is now %u frames\n", nframes);
  return 0;
}

/******************************************************************
 *		JACK_srate
 */
int JACK_srate (nframes_t nframes, void *arg)
{
  TRACE("the sample rate is now %u/sec\n", nframes);
  return 0;
}


/******************************************************************
 *		JACK_shutdown_wwo
 */
/* if this is called then jack shut down... handle this appropriately */
void JACK_shutdown_wwo(void* arg)
{
  WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg;

  wwo->client = 0; /* reset client */

  TRACE("trying to reconnect after sleeping for a short while...\n");
  
  /* lets see if we can't reestablish the connection */
  Sleep(750); /* pause for a short period of time */
  if(!JACK_OpenWaveOutDevice(wwo))
  {
    ERR("unable to reconnect with jack...\n");
  }
}

/******************************************************************
 *		JACK_shutdown_wwi
 */
/* if this is called then jack shut down... handle this appropriately */
void JACK_shutdown_wwi(void* arg)
{
  WINE_WAVEIN* wwi = (WINE_WAVEIN*)arg;

  wwi->client = 0; /* reset client */

  TRACE("trying to reconnect after sleeping for a short while...\n");
  
  /* lets see if we can't reestablish the connection */
  Sleep(750); /* pause for a short period of time */
  if(!JACK_OpenWaveInDevice(wwi,wwi->format.wf.nChannels))
  {
    ERR("unable to reconnect with jack...\n");
  }
}


/******************************************************************
 *		JACK_OpenWaveOutDevice
 */
static int JACK_OpenWaveOutDevice(WINE_WAVEOUT* wwo)
{
  const char** ports;
  int i;
  char client_name[64];
  jack_port_t* out_port_l;
  jack_port_t* out_port_r;
  jack_client_t* client;
  int failed = 0;

  TRACE("creating jack client and setting up callbacks\n");

#if JACK_CLOSE_HACK
  /* see if this device is already open */
        if(wwo->client)
        {
          /* if this device is already in use then it is bad for us to be in here */
          if(wwo->in_use)
            return 0;

          TRACE("using existing client\n");
          wwo->in_use = TRUE;
          return 1;
        }
#endif

        /* zero out the buffer pointer and the size of the buffer */
        wwo->sound_buffer = 0;
        wwo->buffer_size = 0;

        /* try to become a client of the JACK server */
        snprintf(client_name, sizeof(client_name), "wine_jack_out_%d", wwo->wDevID);
	TRACE("client name '%s'\n", client_name);
        if ((client = fp_jack_client_new (client_name)) == 0)
        {
                /* jack has problems with shutting down clients, so lets */
                /* wait a short while and try once more before we give up */
                Sleep(250);
                if ((client = fp_jack_client_new (client_name)) == 0)
                {
                  ERR("jack server not running?\n");
                  return 0;
                }
        }
	                
        /* tell the JACK server to call `JACK_callback_wwo()' whenever
           there is work to be done. */
        fp_jack_set_process_callback (client, JACK_callback_wwo, wwo);
        
        /* tell the JACK server to call `JACK_bufsize_wwo()' whenever   
           the maximum number of frames that will be passed
           to `JACK_Callback()' changes */
        fp_jack_set_buffer_size_callback (client, JACK_bufsize_wwo, wwo);

        /* tell the JACK server to call `srate()' whenever
           the sample rate of the system changes. */
        fp_jack_set_sample_rate_callback (client, JACK_srate, wwo);

        /* tell the JACK server to call `jack_shutdown()' if
           it ever shuts down, either entirely, or if it
           just decides to stop calling us. */
        fp_jack_on_shutdown (client, JACK_shutdown_wwo, wwo);
        
        /* 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. */
        wwo->sample_rate = fp_jack_get_sample_rate(client);
        TRACE("engine sample rate: %lu\n", wwo->sample_rate);
          
        /* create the left and right channel output ports */
        /* jack's ports are all mono so for stereo you need two */
        out_port_l = fp_jack_port_register (client, "out_l",
                         JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);

        out_port_r = fp_jack_port_register (client, "out_r",
                         JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);

	TRACE("Created ports. (%p) (%p)\n",out_port_l, out_port_r);

        /* save away important values to the WINE_WAVEOUT struct */
        wwo->client = client;
        wwo->out_port_l = out_port_l;
        wwo->out_port_r = out_port_r;

#if JACK_CLOSE_HACK
        wwo->in_use = TRUE; /* mark this device as in use since it now is ;-) */
#endif

	/* set initial buffer size */
	JACK_bufsize_wwo (fp_jack_get_buffer_size(client),wwo);

        /* tell the JACK server that we are ready to roll */
        if (fp_jack_activate (client))
        {
          ERR( "cannot activate client\n");
          return 0;
        }

	TRACE("jack activate.\n");
        /* figure out what the ports that we want to output on are */
        /* NOTE: we do this instead of using stuff like "alsa_pcm:playback_X" because */
        /*   this way works if names are changed */
        ports = fp_jack_get_ports(client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);

        /* display a trace of the output ports we found */
        for(i = 0; ports[i]; i++)
        {
          TRACE("ports[%d] = '%s'\n", i, ports[i]);
        }

        if(!ports)
        {
          ERR("jack_get_ports() failed to find 'JackPortIsPhysical|JackPortIsInput'\n");
        }

        /* connect the ports. Note: you can't do this before
           the client is activated (this may change in the future).
        */ 
        /* we want to connect to two ports so we have stereo output ;-) */

        if(fp_jack_connect(client, fp_jack_port_name(out_port_l), ports[0]))
        {
          ERR ("cannot connect to output port %d('%s')\n", 0, ports[0]);
          failed = 1;
        }

        if(fp_jack_connect(client, fp_jack_port_name(out_port_r), ports[1]))
        {
          ERR ("cannot connect to output port %d('%s')\n", 1, ports[1]);
          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)
        {
#if JACK_CLOSE_HACK
          JACK_CloseWaveOutDevice(wwo, TRUE);
#else
          JACK_CloseWaveOutDevice(wwo);
#endif
          return 0;
        }

        return 1; /* return success */
}

/******************************************************************
 *		JACK_CloseWaveOutDevice
 *
 *	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_CloseWaveOutDevice(WINE_WAVEOUT* wwo, BOOL close_client)
#else
static void	JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo)
#endif
{
#if JACK_CLOSE_HACK
  TRACE("wDevID: %d, close_client (wwo): %d\n", wwo->wDevID, close_client);
#else
  TRACE("wDevID: %d\n", wwo->wDevID);
#endif

#if JACK_CLOSE_HACK
  if(close_client)
  {
#endif
    fp_jack_deactivate(wwo->client); /* supposed to help the jack_client_close() to succeed */
    fp_jack_client_close (wwo->client);

    EnterCriticalSection(&wwo->access_crst);
    wwo->client = 0; /* reset client */
    HeapFree(GetProcessHeap(), 0, wwo->sound_buffer); /* free buffer memory */
    wwo->sound_buffer = 0;
    wwo->buffer_size = 0; /* zero out size of the buffer */
    LeaveCriticalSection(&wwo->access_crst);
#if JACK_CLOSE_HACK
  } else
  {
    EnterCriticalSection(&wwo->access_crst);
    TRACE("setting in_use to FALSE\n");
    wwo->in_use = FALSE;
    LeaveCriticalSection(&wwo->access_crst);
  }
#endif
}

/******************************************************************
 *		JACK_CloseWaveInDevice
 *
 *	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_CloseWaveInDevice(WINE_WAVEIN* wwi, BOOL close_client)
#else
static void	JACK_CloseWaveInDevice(WINE_WAVEIN* wwi)
#endif
{
#if JACK_CLOSE_HACK
  TRACE("wDevID: %d, close_client (wwi): %d\n", wwi->wDevID, close_client);
#else
  TRACE("wDevID: %d\n", wwi->wDevID);
#endif

#if JACK_CLOSE_HACK
  if(close_client)
  {
#endif
    fp_jack_deactivate(wwi->client); /* supposed to help the jack_client_close() to succeed */
    fp_jack_client_close (wwi->client);

    EnterCriticalSection(&wwi->access_crst);
    wwi->client = 0; /* reset client */
    HeapFree(GetProcessHeap(), 0, wwi->sound_buffer); /* free buffer memory */
    wwi->sound_buffer = 0;
    wwi->buffer_size = 0; /* zero out size of the buffer */
    LeaveCriticalSection(&wwi->access_crst);
#if JACK_CLOSE_HACK
  } else
  {
    EnterCriticalSection(&wwi->access_crst);
    TRACE("setting in_use to FALSE\n");
    wwi->in_use = FALSE;
    LeaveCriticalSection(&wwi->access_crst);
  }
#endif
}

/******************************************************************
 *		JACK_WaveRelease
 *
 *
 */
LONG	JACK_WaveRelease(void)
{ 
  int iDevice;

  TRACE("closing all open waveout devices\n");

  /* close all open output devices */
  for(iDevice = 0; iDevice < MAX_WAVEOUTDRV; iDevice++)
  {
    TRACE("iDevice == %d\n", iDevice);
    if(WOutDev[iDevice].client)
    {
#if JACK_CLOSE_HACK
      JACK_CloseWaveOutDevice(&WOutDev[iDevice], TRUE); /* close the device, FORCE the client to close */
#else
      JACK_CloseWaveOutDevice(&WOutDev[iDevice]); /* close the device, FORCE the client to close */
#endif
      DeleteCriticalSection(&(WOutDev[iDevice].access_crst)); /* delete the critical section */
    }
  }

  TRACE("closing all open wavein devices\n");

  /* close all open input devices */
  for(iDevice = 0; iDevice < MAX_WAVEINDRV; iDevice++)
  {
    TRACE("iDevice == %d\n", iDevice);
    if(WInDev[iDevice].client)
    {
#if JACK_CLOSE_HACK
      JACK_CloseWaveInDevice(&WInDev[iDevice], TRUE); /* close the device, FORCE the client to close */
#else
      JACK_CloseWaveInDevice(&WInDev[iDevice]); /* close the device, FORCE the client to close */
#endif
      DeleteCriticalSection(&(WInDev[iDevice].access_crst)); /* delete the critical section */
    }
  }

  TRACE("returning 1\n");

  return 1;
}

/******************************************************************
 *		JACK_WaveInit
 *
 * Initialize internal structures from JACK server info
 */
LONG JACK_WaveInit(void)
{
    int i;
    CHAR szPname[MAXPNAMELEN];

    TRACE("called\n");

    /* setup function pointers */
#define LOAD_FUNCPTR(f) if((fp_##f = wine_dlsym(jackhandle, #f, NULL, 0)) == NULL) goto sym_not_found;    
    LOAD_FUNCPTR(jack_activate);
    LOAD_FUNCPTR(jack_connect);
    LOAD_FUNCPTR(jack_client_new);
    LOAD_FUNCPTR(jack_client_close);
    LOAD_FUNCPTR(jack_deactivate);
    LOAD_FUNCPTR(jack_set_process_callback);
    LOAD_FUNCPTR(jack_set_buffer_size_callback);
    LOAD_FUNCPTR(jack_set_sample_rate_callback);
    LOAD_FUNCPTR(jack_on_shutdown);
    LOAD_FUNCPTR(jack_get_sample_rate);
    LOAD_FUNCPTR(jack_port_register);
    LOAD_FUNCPTR(jack_port_get_buffer);
    LOAD_FUNCPTR(jack_get_ports);
    LOAD_FUNCPTR(jack_port_name);
    LOAD_FUNCPTR(jack_get_buffer_size);
#undef LOAD_FUNCPTR

    /* start with output device */

    for (i = 0; i < MAX_WAVEOUTDRV; ++i)
    {
      WOutDev[i].client = 0; /* initialize the client to 0 */

#if JACK_CLOSE_HACK
      WOutDev[i].in_use = FALSE;
      WInDev[i].in_use  = FALSE;
#endif

      memset(&WOutDev[i].caps, 0, sizeof(WOutDev[i].caps));

      WOutDev[i].caps.wMid = 0x00FF; 	/* Manufac ID */
      WOutDev[i].caps.wPid = 0x0001; 	/* Product ID */
      snprintf(szPname, sizeof(szPname), "JACK WaveOut %d", i);
      MultiByteToWideChar(CP_ACP, 0, szPname, -1, WOutDev[i].caps.szPname, sizeof(WOutDev[i].caps.szPname)/sizeof(WCHAR));
      snprintf(WOutDev[i].interface_name, sizeof(WOutDev[i].interface_name), "winejack: %d", i);

      WOutDev[i].caps.vDriverVersion = 0x0100;
      WOutDev[i].caps.dwFormats = 0x00000000;
      WOutDev[i].caps.dwSupport = WAVECAPS_VOLUME;
    
      WOutDev[i].caps.wChannels = 2;
      WOutDev[i].caps.dwSupport |= WAVECAPS_LRVOLUME;

/* NOTE: we don't support any 8 bit modes so note that */
/*      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M08;
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S08; */
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S16;
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M16;
/*      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M08;
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S08; */
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M16;
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S16;
/*      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M08;
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S08;*/
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M16;
      WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S16;
    }

    /* then do input device */
    for (i = 0; i < MAX_WAVEINDRV; ++i)
    {
        /* TODO: we should initialize read stuff here */
        memset(&WInDev[i].caps, 0, sizeof(WInDev[i].caps));

	WInDev[i].caps.wMid = 0x00FF;
	WInDev[i].caps.wPid = 0x0001;
        snprintf(szPname, sizeof(szPname), "JACK WaveIn %d", i);
        MultiByteToWideChar(CP_ACP, 0, szPname, -1, WInDev[i].caps.szPname, sizeof(WInDev[i].caps.szPname)/sizeof(WCHAR));
        snprintf(WInDev[i].interface_name, sizeof(WInDev[i].interface_name), "winejack: %d", i);

	WInDev[i].caps.vDriverVersion = 0x0100;

	WInDev[i].caps.wChannels = 0x2;
	/* NOTE: we don't support any 8 bit modes so note that */
/*      WInDev[i].caps.dwFormats |= WAVE_FORMAT_4M08;
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_4S08; */
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_4S16;
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_4M16;
/*      WInDev[i].caps.dwFormats |= WAVE_FORMAT_2M08;
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_2S08; */
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_2M16;
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_2S16;
/*      WInDev[i].caps.dwFormats |= WAVE_FORMAT_1M08;
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_1S08;*/
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_1M16;
	WInDev[i].caps.dwFormats |= WAVE_FORMAT_1S16;
	WInDev[i].caps.wReserved1 = 0;
    }
    
    return 1;		/* return success */

/* error path for function pointer loading errors */
sym_not_found:
    WINE_MESSAGE(
      "Wine cannot find certain functions that it needs inside the jack"
      "library.  To enable Wine to use the jack audio server please "
      "install libjack\n");
    wine_dlclose(jackhandle, NULL, 0);
    jackhandle = NULL;
    return FALSE;
}

/*======================================================================*
 *                  Low level WAVE OUT implementation			*
 *======================================================================*/

/**************************************************************************
 * 			wodNotifyClient			[internal]
 */
static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
{
    TRACE("wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2);
    
    switch (wMsg) {
    case WOM_OPEN:
    case WOM_CLOSE:
    case WOM_DONE:
      if (wwo->wFlags != DCB_NULL &&
        !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags,
          (HDRVR)wwo->waveDesc.hWave, wMsg, wwo->waveDesc.dwInstance,
          dwParam1, dwParam2))
      {
        WARN("can't notify client !\n");
        return MMSYSERR_ERROR;
      }
    break;
    default:
      FIXME("Unknown callback message %u\n", wMsg);
        return MMSYSERR_INVALPARAM;
    }
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				wodHelper_BeginWaveHdr          [internal]
 *
 * Makes the specified lpWaveHdr the currently playing wave header.
 * If the specified wave header is a begin loop and we're not already in
 * a loop, setup the loop.
 */
static void wodHelper_BeginWaveHdr(WINE_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr)
{
    EnterCriticalSection(&wwo->access_crst);

    wwo->lpPlayPtr = lpWaveHdr;

    if (!lpWaveHdr)
    {
       LeaveCriticalSection(&wwo->access_crst);
       return;
    }

    if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)
    {
      if (wwo->lpLoopPtr)
      {
        WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
        TRACE("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
      } else
      {
        TRACE("Starting loop (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
        wwo->lpLoopPtr = lpWaveHdr;
        /* Windows does not touch WAVEHDR.dwLoops,
         * so we need to make an internal copy */
        wwo->dwLoops = lpWaveHdr->dwLoops;
      }
    }
    wwo->dwPartialOffset = 0;

    LeaveCriticalSection(&wwo->access_crst);
}


/**************************************************************************
 * 				wodHelper_PlayPtrNext	        [internal]
 *
 * Advance the play pointer to the next waveheader, looping if required.
 */
static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo)
{
  LPWAVEHDR lpWaveHdr;

  EnterCriticalSection(&wwo->access_crst);

  lpWaveHdr = wwo->lpPlayPtr;

  wwo->dwPartialOffset = 0;
  if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr)
  {
    /* We're at the end of a loop, loop if required */
    if (--wwo->dwLoops > 0)
    {
      wwo->lpPlayPtr = wwo->lpLoopPtr;
    } else
    {
      /* Handle overlapping loops correctly */
      if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
        FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
        /* shall we consider the END flag for the closing loop or for
         * the opening one or for both ???
         * code assumes for closing loop only
         */
      } else
      {
        lpWaveHdr = lpWaveHdr->lpNext;
      }
      wwo->lpLoopPtr = NULL;
      wodHelper_BeginWaveHdr(wwo, lpWaveHdr);
    }
  } else
  {
     /* We're not in a loop.  Advance to the next wave header */
    TRACE("not inside of a loop, advancing to next wave header\n");
    wodHelper_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
  }

  LeaveCriticalSection(&wwo->access_crst);

  return lpWaveHdr;
}

/* if force is TRUE then notify the client that all the headers were completed */
static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force)
{
  LPWAVEHDR		lpWaveHdr;
  DWORD retval;

  TRACE("called\n");

  EnterCriticalSection(&wwo->access_crst);

  /* Start from lpQueuePtr and keep notifying until:
   * - we hit an unwritten wavehdr
   * - we hit the beginning of a running loop
   * - we hit a wavehdr which hasn't finished playing
   */
  while ((lpWaveHdr = wwo->lpQueuePtr) &&
           (force || 
            (lpWaveHdr != wwo->lpPlayPtr &&
             lpWaveHdr != wwo->lpLoopPtr)))
  {
    wwo->lpQueuePtr = lpWaveHdr->lpNext;

    lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
    lpWaveHdr->dwFlags |= WHDR_DONE;
    TRACE("notifying client: lpWaveHdr=(%p) lpPlayPtr=(%p) dwFlags=(%ld)\n",
	  lpWaveHdr, wwo->lpPlayPtr, lpWaveHdr->dwFlags);

    wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
  }
  TRACE("Not notifying client: lpWaveHdr=(%p) lpPlayPtr=(%p) lpLoopPtr=(%p)\n",
	lpWaveHdr, wwo->lpPlayPtr, wwo->lpLoopPtr);
  retval = (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != 
              wwo->lpLoopPtr) ? 0 : INFINITE;

  LeaveCriticalSection(&wwo->access_crst);

  return retval;
}

/**************************************************************************
 * 				wodHelper_Reset			[internal]
 *
 * Resets current output stream.
 */
static  void  wodHelper_Reset(WINE_WAVEOUT* wwo, BOOL reset)
{
    EnterCriticalSection(&wwo->access_crst);
 
    /* updates current notify list */
    wodHelper_NotifyCompletions(wwo, FALSE);

    if (reset)
    {
        /* remove all wave headers and notify client that all headers were completed */
        wodHelper_NotifyCompletions(wwo, TRUE);

        wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
        wwo->state = WINE_WS_STOPPED;
        wwo->dwPlayedTotal = wwo->dwWrittenTotal = wwo->bytesInJack = 0;

        wwo->dwPartialOffset = 0;        /* Clear partial wavehdr */
    } else
    {
        if (wwo->lpLoopPtr)
        {
            /* complicated case, not handled yet (could imply modifying the loop counter) */
            FIXME("Pausing while in loop isn't correctly handled yet, except strange results\n");
            wwo->lpPlayPtr = wwo->lpLoopPtr;
            wwo->dwPartialOffset = 0;
            wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */
        } else
        {
            LPWAVEHDR   ptr;
            DWORD       sz = wwo->dwPartialOffset;

            /* reset all the data as if we had written only up to lpPlayedTotal bytes */
            /* compute the max size playable from lpQueuePtr */
            for (ptr = wwo->lpQueuePtr; ptr != wwo->lpPlayPtr; ptr = ptr->lpNext)
            {
                sz += ptr->dwBufferLength;
            }

            /* because the reset lpPlayPtr will be lpQueuePtr */
            if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("doh\n");
            wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
            wwo->dwWrittenTotal = wwo->dwPlayedTotal;
            wwo->lpPlayPtr = wwo->lpQueuePtr;
        }

        wwo->state = WINE_WS_PAUSED;
    }
 
    LeaveCriticalSection(&wwo->access_crst);
}

/**************************************************************************
 * 			wodGetDevCaps				[internal]
 */
static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize)
{
    TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize);
    
    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
      TRACE("MAX_WAVOUTDRV reached !\n");
      return MMSYSERR_BADDEVICEID;
    }

    TRACE("dwSupport=(0x%lx), dwFormats=(0x%lx)\n", WOutDev[wDevID].caps.dwSupport, WOutDev[wDevID].caps.dwFormats);
    memcpy(lpCaps, &WOutDev[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				wodOpen				[internal]
 *
 * NOTE: doesn't it seem like there is a race condition if you try to open 
 * the same device twice?
 */
static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
{
    WINE_WAVEOUT*	wwo;
    DWORD retval;

    TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
    if (lpDesc == NULL)
    {
      WARN("Invalid Parameter !\n");
      return MMSYSERR_INVALPARAM;
    }
    if (wDevID >= MAX_WAVEOUTDRV) {
      TRACE("MAX_WAVOUTDRV reached !\n");
      return MMSYSERR_BADDEVICEID;
    }

#if JACK_CLOSE_HACK
    if(WOutDev[wDevID].client && WOutDev[wDevID].in_use)
#else
    if(WOutDev[wDevID].client)
#endif
    {
      TRACE("device %d already allocated\n", wDevID);
      return MMSYSERR_ALLOCATED;
    }

    /* Only the PCM format is supported so far...
     * Also we only support 16 bit mode.
     */
    if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM ||
      lpDesc->lpFormat->nChannels == 0 ||
      lpDesc->lpFormat->nSamplesPerSec == 0 ||
      lpDesc->lpFormat->wBitsPerSample != 16)
    {
      WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld wBitsPerSample=%d !\n",
       lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
       lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample);
      return WAVERR_BADFORMAT;
    }

    if (dwFlags & WAVE_FORMAT_QUERY)
    {
      TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
       lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
       lpDesc->lpFormat->nSamplesPerSec);
      return MMSYSERR_NOERROR;
    }

    wwo = &WOutDev[wDevID];
    wwo->wDevID = wDevID;

    /* Set things up before we call JACK_OpenWaveOutDevice because */
    /* we will start getting callbacks before JACK_OpenWaveOutDevice */
    /* even returns and we want to be initialized before then */
    wwo->state = WINE_WS_STOPPED; /* start in a stopped state */
    wwo->dwPlayedTotal = 0; /* zero out these totals */
    wwo->dwWrittenTotal = 0;
    wwo->bytesInJack = 0;
    wwo->tickCountMS = 0;

    /* Initialize volume to full level */
    wwo->volume_left = 100;
    wwo->volume_right = 100;

    InitializeCriticalSection(&wwo->access_crst); /* initialize the critical section */
    EnterCriticalSection(&wwo->access_crst);

    dwFlags &= ~WAVE_DIRECTSOUND;  /* direct sound not supported, ignore the flag */

    wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
    
    memcpy(&wwo->waveDesc, lpDesc, 	     sizeof(WAVEOPENDESC));
    memcpy(&wwo->format,   lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));

    /* open up jack ports for this device */
    if (!JACK_OpenWaveOutDevice(&WOutDev[wDevID]))
    {
      ERR("JACK_OpenWaveOutDevice(%d) failed\n", wDevID);
      LeaveCriticalSection(&wwo->access_crst);
      DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */
      return MMSYSERR_ERROR;		/* return unspecified error */
    }

    LeaveCriticalSection(&wwo->access_crst);

    /* display the current wave format */
    TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n", 
    wwo->format.wBitsPerSample, wwo->format.wf.nAvgBytesPerSec,
    wwo->format.wf.nSamplesPerSec, wwo->format.wf.nChannels,
    wwo->format.wf.nBlockAlign);

    /* make sure that we have the same sample rate in our audio stream */
    /* as we do in the jack server */
    if(wwo->format.wf.nSamplesPerSec != wwo->sample_rate)
    {
      TRACE("error: jack server sample rate is '%ld', wave sample rate is '%ld'\n",
         wwo->sample_rate, wwo->format.wf.nSamplesPerSec);

#if JACK_CLOSE_HACK
      JACK_CloseWaveOutDevice(wwo, FALSE); /* close this device, don't force the client to close */
#else
      JACK_CloseWaveOutDevice(wwo); /* close this device */
#endif
      DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */
      return WAVERR_BADFORMAT;
    }

    /* check for an invalid number of bits per sample */
    if (wwo->format.wBitsPerSample == 0)
    {
      WARN("Resetting zeroed wBitsPerSample\n");
      wwo->format.wBitsPerSample = 8 *
      (wwo->format.wf.nAvgBytesPerSec /
       wwo->format.wf.nSamplesPerSec) /
       wwo->format.wf.nChannels;
    }

    EnterCriticalSection(&wwo->access_crst);
    retval = wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
    LeaveCriticalSection(&wwo->access_crst);

    return retval;
}

/**************************************************************************
 * 				wodClose			[internal]
 */
static DWORD wodClose(WORD wDevID)
{
    DWORD		ret = MMSYSERR_NOERROR;
    WINE_WAVEOUT*	wwo;

    TRACE("(%u);\n", wDevID);

    if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
    {
      WARN("bad device ID !\n");
      return MMSYSERR_BADDEVICEID;
    }
    
    wwo = &WOutDev[wDevID];
    if (wwo->lpQueuePtr)
    {
      WARN("buffers still playing !\n");
      ret = WAVERR_STILLPLAYING;
    } else
    {
      /* sanity check: this should not happen since the device must have been reset before */
      if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");

      wwo->state = WINE_WS_CLOSED; /* mark the device as closed */

#if JACK_CLOSE_HACK
      JACK_CloseWaveOutDevice(wwo, FALSE); /* close the jack device, DO NOT force the client to close */
#else
      JACK_CloseWaveOutDevice(wwo); /* close the jack device */
#endif
      DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */

      ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
    }

    return ret;
}


/**************************************************************************
 * 				wodWrite			[internal]
 * 
 */
static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
    LPWAVEHDR*wh;
    WINE_WAVEOUT *wwo;

    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
    
    /* first, do the sanity checks... */
    if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
    {
      WARN("bad dev ID !\n");
      return MMSYSERR_BADDEVICEID;
    }

    wwo = &WOutDev[wDevID];

    if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
    {
      TRACE("unprepared\n");
      return WAVERR_UNPREPARED;
    }
    
    if (lpWaveHdr->dwFlags & WHDR_INQUEUE) 
    {
      TRACE("still playing\n");
      return WAVERR_STILLPLAYING;
    }

    lpWaveHdr->dwFlags &= ~WHDR_DONE;
    lpWaveHdr->dwFlags |= WHDR_INQUEUE;
    lpWaveHdr->lpNext = 0;

    EnterCriticalSection(&wwo->access_crst);

    /* insert buffer at the end of queue */
    for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
    *wh = lpWaveHdr;

    if (!wwo->lpPlayPtr)
      wodHelper_BeginWaveHdr(wwo,lpWaveHdr);
    if (wwo->state == WINE_WS_STOPPED)
      wwo->state = WINE_WS_PLAYING;
    LeaveCriticalSection(&wwo->access_crst);

    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			wodPause				[internal]
 */
static DWORD wodPause(WORD wDevID)
{
  TRACE("(%u);!\n", wDevID);
    
  if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
  {
    WARN("bad device ID !\n");
    return MMSYSERR_BADDEVICEID;
  }
    
  TRACE("[3-PAUSING]\n");

  EnterCriticalSection(&(WOutDev[wDevID].access_crst));
  wodHelper_Reset(&WOutDev[wDevID], FALSE);
  LeaveCriticalSection(&(WOutDev[wDevID].access_crst));

  return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			wodRestart				[internal]
 */
static DWORD wodRestart(WORD wDevID)
{
    TRACE("(%u);\n", wDevID);

    if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
    {
      WARN("bad device ID !\n");
      return MMSYSERR_BADDEVICEID;
    }

    if (WOutDev[wDevID].state == WINE_WS_PAUSED)
    {
      EnterCriticalSection(&(WOutDev[wDevID].access_crst));
      WOutDev[wDevID].state = WINE_WS_PLAYING;
      LeaveCriticalSection(&(WOutDev[wDevID].access_crst));
    }
    
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			wodReset				[internal]
 */
static DWORD wodReset(WORD wDevID)
{
    TRACE("(%u);\n", wDevID);
    
    if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
    {
      WARN("bad device ID !\n");
      return MMSYSERR_BADDEVICEID;
    }

    EnterCriticalSection(&(WOutDev[wDevID].access_crst));
    wodHelper_Reset(&WOutDev[wDevID], TRUE);
    LeaveCriticalSection(&(WOutDev[wDevID].access_crst));

    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				wodGetPosition			[internal]
 */
static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
{
    DWORD		val;
    WINE_WAVEOUT*	wwo;
    DWORD elapsedMS;

    TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize);

    if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
    {
      WARN("bad device ID !\n");
      return MMSYSERR_BADDEVICEID;
    }

    /* if null pointer to time structure return error */
    if (lpTime == NULL)	return MMSYSERR_INVALPARAM;

    wwo = &WOutDev[wDevID];

    EnterCriticalSection(&(WOutDev[wDevID].access_crst));
    val = wwo->dwPlayedTotal;
    elapsedMS = GetTickCount() - wwo->tickCountMS;
    LeaveCriticalSection(&(WOutDev[wDevID].access_crst));

    /* account for the bytes played since the last JACK_Callback() */
    val+=((elapsedMS * wwo->format.wf.nAvgBytesPerSec) / 1000);

    return bytes_to_mmtime(lpTime, val, &wwo->format);
}

/**************************************************************************
 * 				wodBreakLoop			[internal]
 */
static DWORD wodBreakLoop(WORD wDevID)
{
  TRACE("(%u);\n", wDevID);

  if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client)
  {
    WARN("bad device ID !\n");
    return MMSYSERR_BADDEVICEID;
  }

  EnterCriticalSection(&(WOutDev[wDevID].access_crst));

  if (WOutDev[wDevID].state == WINE_WS_PLAYING && WOutDev[wDevID].lpLoopPtr != NULL)
  {
    /* ensure exit at end of current loop */
    WOutDev[wDevID].dwLoops = 1;
  }

  LeaveCriticalSection(&(WOutDev[wDevID].access_crst));

  return MMSYSERR_NOERROR;
}
    
/**************************************************************************
 * 				wodGetVolume			[internal]
 */
static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
{
  DWORD left, right;
    
  left = WOutDev[wDevID].volume_left;
  right = WOutDev[wDevID].volume_right;
        
  TRACE("(%u, %p);\n", wDevID, lpdwVol);
 
  *lpdwVol = ((left * 0xFFFFl) / 100) + (((right * 0xFFFFl) / 100) <<
              16);
    
  return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				wodSetVolume			[internal]
 */
static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
{
  DWORD left, right;
 
  left  = (LOWORD(dwParam) * 100) / 0xFFFFl;
  right = (HIWORD(dwParam) * 100) / 0xFFFFl;
 
  TRACE("(%u, %08lX);\n", wDevID, dwParam);

  EnterCriticalSection(&(WOutDev[wDevID].access_crst));

  WOutDev[wDevID].volume_left = left;
  WOutDev[wDevID].volume_right = right;

  LeaveCriticalSection(&(WOutDev[wDevID].access_crst));

  return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				wodGetNumDevs			[internal]
 */
static DWORD wodGetNumDevs(void)
{
  return MAX_WAVEOUTDRV;
}

/**************************************************************************
 *                              wodDevInterfaceSize             [internal]
 */
static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
{
    TRACE("(%u, %p)\n", wDevID, dwParam1);
                                                                                                       
    *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
                                    NULL, 0 ) * sizeof(WCHAR);
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 *                              wodDevInterface                 [internal]
 */
static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
{
    if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
                                        NULL, 0 ) * sizeof(WCHAR))
    {
        MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
                            dwParam1, dwParam2 / sizeof(WCHAR));
        return MMSYSERR_NOERROR;
    }
    return MMSYSERR_INVALPARAM;
}

/**************************************************************************
 * 				wodMessage (WINEJACK.7)
 */
DWORD WINAPI JACK_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
			    DWORD dwParam1, DWORD dwParam2)
{
  TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
  wDevID, wMsg, dwUser, dwParam1, dwParam2);
    
  switch (wMsg) {
  case DRVM_INIT:
  case DRVM_EXIT:
  case DRVM_ENABLE:
  case DRVM_DISABLE:
    /* FIXME: Pretend this is supported */
    return 0;
  case WODM_OPEN:             return wodOpen(wDevID, (LPWAVEOPENDESC)dwParam1,	dwParam2);
  case WODM_CLOSE:            return wodClose(wDevID);
  case WODM_WRITE:            return wodWrite(wDevID, (LPWAVEHDR)dwParam1,		dwParam2);
  case WODM_PAUSE:            return wodPause(wDevID);
  case WODM_GETPOS:           return wodGetPosition(wDevID, (LPMMTIME)dwParam1, 		dwParam2);
  case WODM_BREAKLOOP:        return wodBreakLoop(wDevID);
  case WODM_PREPARE:          return MMSYSERR_NOTSUPPORTED;
  case WODM_UNPREPARE:        return MMSYSERR_NOTSUPPORTED;
  case WODM_GETDEVCAPS:       return wodGetDevCaps(wDevID, (LPWAVEOUTCAPSW)dwParam1,	dwParam2);
  case WODM_GETNUMDEVS:       return wodGetNumDevs();
  case WODM_GETPITCH:         return MMSYSERR_NOTSUPPORTED;
  case WODM_SETPITCH:         return MMSYSERR_NOTSUPPORTED;
  case WODM_GETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED;
  case WODM_SETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED;
  case WODM_GETVOLUME:        return wodGetVolume(wDevID, (LPDWORD)dwParam1);
  case WODM_SETVOLUME:        return wodSetVolume(wDevID, dwParam1);
  case WODM_RESTART:          return wodRestart(wDevID);
  case WODM_RESET:            return wodReset(wDevID);

  case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize       (wDevID, (LPDWORD)dwParam1);
  case DRV_QUERYDEVICEINTERFACE:     return wodDevInterface           (wDevID, (PWCHAR)dwParam1, dwParam2);
  case DRV_QUERYDSOUNDIFACE:	return wodDsCreate(wDevID, (PIDSDRIVER*)dwParam1);
  case DRV_QUERYDSOUNDDESC:	return wodDsDesc(wDevID, (PDSDRIVERDESC)dwParam1);
  default:
    FIXME("unknown message %d!\n", wMsg);
    }
    return MMSYSERR_NOTSUPPORTED;
}

/*======================================================================*
 *                  Low level DSOUND implementation			*
 *======================================================================*/

typedef struct IDsDriverImpl IDsDriverImpl;
typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;

struct IDsDriverImpl
{
    /* IUnknown fields */
    const IDsDriverVtbl *lpVtbl;
    DWORD		ref;
    /* IDsDriverImpl fields */
    UINT		wDevID;
    IDsDriverBufferImpl*primary;
};

struct IDsDriverBufferImpl
{
    /* IUnknown fields */
    const IDsDriverBufferVtbl *lpVtbl;
    DWORD ref;
    /* IDsDriverBufferImpl fields */
    IDsDriverImpl* drv;
    DWORD buflen;
};

static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv)
{
    /* we can't perform memory mapping as we don't have a file stream 
      interface with jack like we do with oss */
    MESSAGE("This sound card's driver does not support direct access\n");
    MESSAGE("The (slower) DirectSound HEL mode will be used instead.\n");
    return MMSYSERR_NOTSUPPORTED;
}

static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc)
{
    memset(desc, 0, sizeof(*desc));
    strcpy(desc->szDesc, "Wine jack DirectSound Driver");
    strcpy(desc->szDrvname, "winejack.drv");
    return MMSYSERR_NOERROR;
}

/*======================================================================*
 *                  Low level WAVE IN implementation			*
 *======================================================================*/

/**************************************************************************
 * 			widNotifyClient			[internal]
 */
static DWORD widNotifyClient(WINE_WAVEIN* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
{
    TRACE("wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2);
    
    switch (wMsg) {
    case WIM_OPEN:
    case WIM_CLOSE:
    case WIM_DATA:
      if (wwi->wFlags != DCB_NULL &&
        !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags,
          (HDRVR)wwi->waveDesc.hWave, wMsg, wwi->waveDesc.dwInstance,
          dwParam1, dwParam2))
      {
        WARN("can't notify client !\n");
        return MMSYSERR_ERROR;
      }
    break;
    default:
      FIXME("Unknown callback message %u\n", wMsg);
        return MMSYSERR_INVALPARAM;
    }
    return MMSYSERR_NOERROR;
}

/******************************************************************
 *    JACK_callback_wwi
 */
/* everytime the jack server wants something from us it calls this 
   function */
int JACK_callback_wwi (nframes_t nframes, void *arg)
{
    sample_t* in_l;
    sample_t* in_r = 0;
    WINE_WAVEIN* wwi = (WINE_WAVEIN*)arg;

    TRACE("wDevID: %u, nframes %u\n", wwi->wDevID, nframes);
    
    if(!wwi->client)
	ERR("client is closed, this is weird...\n");
    
    in_l = (sample_t *) fp_jack_port_get_buffer(wwi->in_port_l, nframes);

    if (wwi->in_port_r)
      in_r = (sample_t *) fp_jack_port_get_buffer(wwi->in_port_r, nframes);
    
    EnterCriticalSection(&wwi->access_crst);
    
    if((wwi->lpQueuePtr != NULL) && (wwi->state == WINE_WS_PLAYING))
    {
	LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr;
	nframes_t jackFramesLeft = nframes;

#if JACK_CLOSE_HACK
	if(wwi->in_use == FALSE)
	{
	  /* do nothing if nothing is being recorded */
	    return 0;
	}
#endif
	
	TRACE("wwi.state == WINE_WS_PLAYING\n");
	
	while (lpWaveHdr && jackFramesLeft)
	{
	    DWORD waveHdrFramesLeft = (lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded) / (sizeof(short) * wwi->format.wf.nChannels); 
	    DWORD numFrames = min (jackFramesLeft, waveHdrFramesLeft);
	    
	    TRACE ("dwBufferLength=(%lu) dwBytesRecorded=(%ld)\n",   lpWaveHdr->dwBufferLength, lpWaveHdr->dwBytesRecorded);
	    TRACE ("jackFramesLeft=(%u) waveHdrFramesLeft=(%lu)\n", jackFramesLeft, waveHdrFramesLeft);

	    if (!in_r) {
	      /* mono */
	      sample_move_s16_d16((short *)((char *)lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded), in_l+(nframes-jackFramesLeft), numFrames, 1);
	    } else {
	      /* stereo */
	      sample_move_s16_d16((short *)((char *)lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded), 
				  in_l+(nframes-jackFramesLeft), numFrames, 2);
	      sample_move_s16_d16((short *)((char *)lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded + sizeof(short)), 
				  in_r+(nframes-jackFramesLeft), numFrames, 2);
	    }
	    
	    lpWaveHdr->dwBytesRecorded += (numFrames * sizeof(short) * wwi->format.wf.nChannels );
	    jackFramesLeft -= numFrames;

	    if (lpWaveHdr->dwBytesRecorded >= lpWaveHdr->dwBufferLength)
	    {
		/* must copy the value of next waveHdr, because we have no idea of what
		 * will be done with the content of lpWaveHdr in callback
		 */
		LPWAVEHDR	lpNext = lpWaveHdr->lpNext;
				
		lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
		lpWaveHdr->dwFlags |=  WHDR_DONE;

		TRACE("WaveHdr full. dwBytesRecorded=(%lu) dwFlags=(0x%lx)\n",lpWaveHdr->dwBytesRecorded,lpWaveHdr->dwFlags);

		widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);

		lpWaveHdr = wwi->lpQueuePtr = lpNext;
	    }
	}
	TRACE ("jackFramesLeft=(%u) lpWaveHdr=(%p)\n", jackFramesLeft, lpWaveHdr);
	if (jackFramesLeft > 0) { WARN("Record buffer ran out of WaveHdrs\n"); }
    }
    
    LeaveCriticalSection(&wwi->access_crst);

    return 0;
}

/******************************************************************
 *		JACK_OpenWaveInDevice
 */
static int JACK_OpenWaveInDevice(WINE_WAVEIN* wwi, WORD nChannels)
{
  const char** ports;
  int i;
  char client_name[64];
  jack_port_t* in_port_l;
  jack_port_t* in_port_r = 0;
  jack_client_t* client;
  int failed = 0;

  TRACE("creating jack client and setting up callbacks\n");

  if ((nChannels == 0) || (nChannels > 2)) {
    ERR ("nChannels = (%d), but we only support mono or stereo.\n", nChannels);
    return 0;
  }

#if JACK_CLOSE_HACK
  /* see if this device is already open */
        if(wwi->client)
        {
          /* if this device is already in use then it is bad for us to be in here */
          if(wwi->in_use)
            return 0;

          TRACE("using existing client\n");
          wwi->in_use = TRUE;
          return 1;
        }
#endif

        /* zero out the buffer pointer and the size of the buffer */
        wwi->sound_buffer = 0;
        wwi->buffer_size = 0;

        /* try to become a client of the JACK server */
        snprintf(client_name, sizeof(client_name), "wine_jack_in_%d", wwi->wDevID);
        TRACE("client name '%s'\n", client_name);
        if ((client = fp_jack_client_new (client_name)) == 0)
        {
                /* jack has problems with shutting down clients, so lets */
                /* wait a short while and try once more before we give up */
                Sleep(250);
                if ((client = fp_jack_client_new (client_name)) == 0)
                {
                  ERR("jack server not running?\n");
                  return 0;
                }
        }
        wwi->client = client;
                
        /* tell the JACK server to call `JACK_wwi_callback()' whenever
           there is work to be done. */
        fp_jack_set_process_callback (client, JACK_callback_wwi, wwi);
        
        /* tell the JACK server to call `JACK_bufsize_wwi()' whenever   
           the maximum number of frames that will be passed
           to `JACK_Callback()' changes */
        fp_jack_set_buffer_size_callback (client, JACK_bufsize_wwi, wwi);
          
        /* tell the JACK server to call `srate()' whenever
           the sample rate of the system changes. */
        fp_jack_set_sample_rate_callback (client, JACK_srate, wwi);

        /* tell the JACK server to call `jack_shutdown()' if
           it ever shuts down, either entirely, or if it
           just decides to stop calling us. */
        fp_jack_on_shutdown (client, JACK_shutdown_wwi, wwi);
        
        /* 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. */
        wwi->sample_rate = fp_jack_get_sample_rate(client);
        TRACE("engine sample rate: %lu\n", wwi->sample_rate);
          
        /* create the left and right channel output ports */
        /* jack's ports are all mono so for stereo you need two */
        in_port_l = fp_jack_port_register (client, "in_l",
                         JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
        wwi->in_port_l = in_port_l;
	TRACE("Created port. (%p)\n", in_port_l);

	if (nChannels == 2)
	{
	  in_port_r = fp_jack_port_register (client, "in_r",
			   JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); 
	  TRACE("Created port. (%p)\n", in_port_r);
	} 
	wwi->in_port_r = in_port_r;

#if JACK_CLOSE_HACK
        wwi->in_use = TRUE; /* mark this device as in use since it now is ;-) */
#endif

	TRACE("activating client.\n");
        /* tell the JACK server that we are ready to roll */
        if (fp_jack_activate (client))
        {
          ERR( "cannot activate client\n");
          return 0;
        }
	TRACE("activated client.\n");
        /* figure out what the ports that we want to output on are */
        /* NOTE: we do this instead of using stuff like "alsa_pcm:playback_X" because */
        /*   this way works if names are changed */
        ports = fp_jack_get_ports(client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput);

        /* display a trace of the output ports we found */
        for(i = 0; ports[i]; i++)
        {
          TRACE("ports[%d] = '%s'\n", i, ports[i]);
        }

        if(!ports)
        {
          ERR("jack_get_ports() failed to find 'JackPortIsPhysical|JackPortIsOutput'\n");
        }

        /* connect the ports. Note: you can't do this before
           the client is activated (this may change in the future).
        */ 
        /* we want to connect to two ports so we have stereo input ;-) */

	if(fp_jack_connect(client, ports[0], fp_jack_port_name(in_port_l)))
	{
	  ERR ("cannot connect to input port %d('%s')\n", 0, ports[0]);
	  failed = 1;
	}
	TRACE("Connected (%s)<->(%s)\n",ports[0],fp_jack_port_name(in_port_l));

	if ((nChannels == 2) && in_port_r) {
	  if(fp_jack_connect(client, ports[1], fp_jack_port_name(in_port_r)))
	  {
	    ERR ("cannot connect to input port %d('%s')\n", 1, ports[1]);
	    failed = 1;
	  }
	  TRACE("Connected (%s)<->(%s)\n",ports[1],fp_jack_port_name(in_port_r));
	}
        free(ports); /* free the returned array of ports */

        /* if something failed we need to shut the client down and return 0 */
        if(failed)
        {
#if JACK_CLOSE_HACK
          JACK_CloseWaveInDevice(wwi, TRUE);
#else
          JACK_CloseWaveInDevice(wwi);
#endif
          return 0;
        }

	TRACE("return success.\n");
        return 1; /* return success */
}

/**************************************************************************
 * 			widGetDevCaps				[internal]
 */
static DWORD widGetDevCaps(WORD wDevID, LPWAVEINCAPSW lpCaps, DWORD dwSize)
{
    TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize);

    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;

    if (wDevID >= MAX_WAVEINDRV) {
	TRACE("MAX_WAVEINDRV reached !\n");
	return MMSYSERR_BADDEVICEID;
    }

    memcpy(lpCaps, &WInDev[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				widOpen				[internal]
 */
static DWORD widOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
{
    WINE_WAVEIN*	wwi;
    DWORD retval;

    TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
    if (lpDesc == NULL)
    {
	WARN("Invalid Parameter !\n");
	return MMSYSERR_INVALPARAM;
    }
    if (wDevID >= MAX_WAVEINDRV) {
	TRACE ("MAX_WAVEINDRV reached !\n");
	return MMSYSERR_BADDEVICEID;
    }

#if JACK_CLOSE_HACK
    if(WInDev[wDevID].client && WOutDev[wDevID].in_use)
#else
    if(WInDev[wDevID].client)
#endif
    {
      TRACE("device %d already allocated\n", wDevID);
      return MMSYSERR_ALLOCATED;
    }

    /* Only the PCM format is supported so far...
     * Also we only support 16 bit mode.
     */
    if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM ||
      lpDesc->lpFormat->nChannels == 0 ||
      lpDesc->lpFormat->nSamplesPerSec == 0 ||
      lpDesc->lpFormat->wBitsPerSample!=16)
    {
      WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld wBitsPerSample=%d !\n",
       lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
       lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample);
      return WAVERR_BADFORMAT;
    }

    if (dwFlags & WAVE_FORMAT_QUERY)
    {
      TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
       lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
       lpDesc->lpFormat->nSamplesPerSec);
      return MMSYSERR_NOERROR;
    }

    wwi = &WInDev[wDevID];
    wwi->wDevID = wDevID;

    /* Set things up before we call JACK_OpenWaveOutDevice because */
    /* we will start getting callbacks before JACK_OpenWaveOutDevice */
    /* even returns and we want to be initialized before then */
    wwi->state = WINE_WS_STOPPED; /* start in a stopped state */

    InitializeCriticalSection(&wwi->access_crst); /* initialize the critical section */
    EnterCriticalSection(&wwi->access_crst);

    /* open up jack ports for this device */
    if (!JACK_OpenWaveInDevice(&WInDev[wDevID], lpDesc->lpFormat->nChannels))
    {
      ERR("JACK_OpenWaveInDevice(%d) failed\n", wDevID);
      LeaveCriticalSection(&wwi->access_crst);
      DeleteCriticalSection(&wwi->access_crst);
      return MMSYSERR_ERROR;		/* return unspecified error */
    }

    dwFlags &= ~WAVE_DIRECTSOUND;  /* direct sound not supported, ignore the flag */

    wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
    
    memcpy(&wwi->waveDesc, lpDesc, 	     sizeof(WAVEOPENDESC));
    memcpy(&wwi->format,   lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));

    LeaveCriticalSection(&wwi->access_crst);

    /* display the current wave format */
    TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n", 
    wwi->format.wBitsPerSample, wwi->format.wf.nAvgBytesPerSec,
    wwi->format.wf.nSamplesPerSec, wwi->format.wf.nChannels,
    wwi->format.wf.nBlockAlign);

    /* make sure that we have the same sample rate in our audio stream */
    /* as we do in the jack server */
    if(wwi->format.wf.nSamplesPerSec != wwi->sample_rate)
    {
      TRACE("error: jack server sample rate is '%ld', wave sample rate is '%ld'\n",
         wwi->sample_rate, wwi->format.wf.nSamplesPerSec);

#if JACK_CLOSE_HACK
      JACK_CloseWaveInDevice(wwi, FALSE); /* close this device, don't force the client to close */
#else
      JACK_CloseWaveInDevice(wwi); /* close this device */
#endif
      DeleteCriticalSection(&wwi->access_crst);
      return WAVERR_BADFORMAT;
    }

    /* check for an invalid number of bits per sample */
    if (wwi->format.wBitsPerSample == 0)
    {
      WARN("Resetting zeroed wBitsPerSample\n");
      wwi->format.wBitsPerSample = 8 *
      (wwi->format.wf.nAvgBytesPerSec /
       wwi->format.wf.nSamplesPerSec) /
       wwi->format.wf.nChannels;
    }

    TRACE("notify client.\n");
    EnterCriticalSection(&wwi->access_crst);
    retval = widNotifyClient(wwi, WIM_OPEN, 0L, 0L);
    LeaveCriticalSection(&wwi->access_crst);

    return retval;
}
/**************************************************************************
 * 				widClose			[internal]
 */
static DWORD widClose(WORD wDevID)
{
    DWORD		ret = MMSYSERR_NOERROR;
    WINE_WAVEIN*	wwi;

    TRACE("(%u);\n", wDevID);

    if (wDevID >= MAX_WAVEINDRV || !WInDev[wDevID].client)
    {
      WARN("bad device ID !\n");
      return MMSYSERR_BADDEVICEID;
    }
    
    wwi = &WInDev[wDevID];
    if (wwi->lpQueuePtr)
    {
      WARN("buffers still playing !\n");
      ret = WAVERR_STILLPLAYING;
    } else
    {
	/* sanity check: this should not happen since the device must have been reset before */
	if (wwi->lpQueuePtr) ERR("out of sync\n");
	
	wwi->state = WINE_WS_CLOSED; /* mark the device as closed */
	
#if JACK_CLOSE_HACK
	JACK_CloseWaveInDevice(wwi, FALSE); /* close the jack device, DO NOT force the client to close */
#else
	JACK_CloseWaveInDevice(wwi); /* close the jack device */
#endif
	DeleteCriticalSection(&wwi->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */
	
	ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
    }

    return ret;
}

/**************************************************************************
 * 				widAddBuffer		[internal]
 */
static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
    WINE_WAVEIN* wwi = &WInDev[wDevID];

    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);

    if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) {
				WARN("can't do it !\n");
				return MMSYSERR_INVALHANDLE;
    }
    if (!(lpWaveHdr->dwFlags & WHDR_PREPARED)) {
				TRACE("never been prepared !\n");
				return WAVERR_UNPREPARED;
    }
    if (lpWaveHdr->dwFlags & WHDR_INQUEUE) {
				TRACE("header already in use !\n");
				return WAVERR_STILLPLAYING;
    }

    lpWaveHdr->dwFlags |= WHDR_INQUEUE;
    lpWaveHdr->dwFlags &= ~WHDR_DONE;
    lpWaveHdr->dwBytesRecorded = 0;
    lpWaveHdr->lpNext = NULL;

    EnterCriticalSection(&wwi->access_crst);
    /* insert buffer at end of queue */
    {
	LPWAVEHDR* wh;
	for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
	*wh=lpWaveHdr;
    }
    LeaveCriticalSection(&wwi->access_crst);

    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			widStart				[internal]
 */
static DWORD widStart(WORD wDevID)
{
    TRACE("(%u);\n", wDevID);
    if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) {
	WARN("can't start recording !\n");
	return MMSYSERR_INVALHANDLE;
    }

    WInDev[wDevID].state = WINE_WS_PLAYING;
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			widStop					[internal]
 */
static DWORD widStop(WORD wDevID)
{
    WINE_WAVEIN* wwi = &WInDev[wDevID];

    TRACE("(%u);\n", wDevID);
    if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) {
	WARN("can't stop !\n");
	return MMSYSERR_INVALHANDLE;
    }
    
    if (wwi->state != WINE_WS_STOPPED)
    {
	WAVEHDR*		lpWaveHdr;
	/* do something here to stop recording ??? */
	
	/* return current buffer to app */
	lpWaveHdr = wwi->lpQueuePtr;
	if (lpWaveHdr)
	{
	    LPWAVEHDR	lpNext = lpWaveHdr->lpNext;
	    TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
	    lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
	    lpWaveHdr->dwFlags |= WHDR_DONE;
	    widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
	    wwi->lpQueuePtr = lpNext;
	}
    }
    wwi->state = WINE_WS_STOPPED;

    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			widReset				[internal]
 */
static DWORD widReset(WORD wDevID)
{
    WINE_WAVEIN* wwi = &WInDev[wDevID];
    WAVEHDR*		lpWaveHdr;

    TRACE("(%u);\n", wDevID);
    if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) {
	WARN("can't reset !\n");
	return MMSYSERR_INVALHANDLE;
    }

    wwi->state = WINE_WS_STOPPED;

    /* return all buffers to the app */
    for (lpWaveHdr = wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = lpWaveHdr->lpNext) {
	TRACE("reset %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
	lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
	lpWaveHdr->dwFlags |= WHDR_DONE;
	
	widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
    }
    wwi->lpQueuePtr = NULL;

    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 				widGetNumDevs			[internal]
 */
static DWORD widGetNumDevs(void)
{
  return MAX_WAVEINDRV;
}

/**************************************************************************
 *                              widDevInterfaceSize             [internal]
 */
static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
{
    TRACE("(%u, %p)\n", wDevID, dwParam1);
                                                                                                       
                                                                                                       
    *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WInDev[wDevID].interface_name, -1,
                                    NULL, 0 ) * sizeof(WCHAR);
    return MMSYSERR_NOERROR;
}

/**************************************************************************
 *                              widDevInterface                 [internal]
 */
static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
{
    if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WInDev[wDevID].interface_name, -1,
                                        NULL, 0 ) * sizeof(WCHAR))
    {
        MultiByteToWideChar(CP_ACP, 0, WInDev[wDevID].interface_name, -1,
                            dwParam1, dwParam2 / sizeof(WCHAR));
        return MMSYSERR_NOERROR;
    }
    return MMSYSERR_INVALPARAM;
}

/**************************************************************************
 * 				widMessage (WINEJACK.6)
 */
DWORD WINAPI JACK_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser, 
			    DWORD dwParam1, DWORD dwParam2)
{
  TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
  wDevID, wMsg, dwUser, dwParam1, dwParam2);

    switch (wMsg) {
    case DRVM_INIT:
    case DRVM_EXIT:
    case DRVM_ENABLE:
    case DRVM_DISABLE:
	/* FIXME: Pretend this is supported */
	return 0;
    case WIDM_OPEN:	 	return widOpen		(wDevID, (LPWAVEOPENDESC)dwParam1,	dwParam2);
    case WIDM_CLOSE:		return widClose		(wDevID);
    case WIDM_ADDBUFFER:	return widAddBuffer	(wDevID, (LPWAVEHDR)dwParam1, dwParam2);
    case WIDM_PREPARE:		return MMSYSERR_NOTSUPPORTED;
    case WIDM_UNPREPARE:	return MMSYSERR_NOTSUPPORTED;
    case WIDM_GETDEVCAPS:	return widGetDevCaps	(wDevID, (LPWAVEINCAPSW)dwParam1,	dwParam2);
    case WIDM_GETNUMDEVS:	return widGetNumDevs();
    case WIDM_RESET:		return widReset		(wDevID);
    case WIDM_START:		return widStart		(wDevID);
    case WIDM_STOP:		return widStop		(wDevID);
    case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize       (wDevID, (LPDWORD)dwParam1);
    case DRV_QUERYDEVICEINTERFACE:     return widDevInterface           (wDevID, (PWCHAR)dwParam1, dwParam2);
    default:
	FIXME("unknown message %d!\n", wMsg);
    }

  return MMSYSERR_NOTSUPPORTED;
}

#else /* !HAVE_JACK_JACK_H */

/**************************************************************************
 * 				widMessage (WINEJACK.6)
 */
DWORD WINAPI JACK_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser, 
			    DWORD dwParam1, DWORD dwParam2)
{
  FIXME("(%u, %04X, %08lX, %08lX, %08lX):jack support not compiled into wine\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
  return MMSYSERR_NOTENABLED;
}

/**************************************************************************
 * 				wodMessage (WINEJACK.7)
 */
DWORD WINAPI JACK_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser, 
			    DWORD dwParam1, DWORD dwParam2)
{
  FIXME("(%u, %04X, %08lX, %08lX, %08lX):jack support not compiled into wine\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
  return MMSYSERR_NOTENABLED;
}

#endif /* HAVE_JACK_JACK_H */
