/* FluidSynth - A Software Synthesizer
 *
 * Copyright (C) 2003  Peter Hanappe and others.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307, USA
 */

/* fluid_dart.c
 *
 * Driver for OS/2 DART
 *
 */

#include "fluid_adriver.h"
#include "fluid_settings.h"
#include "fluid_sys.h"

#if DART_SUPPORT

/* To avoid name conflict */
#undef VERSION

#define INCL_DOS
#include <os2.h>

#define INCL_OS2MM
#include <os2me.h>

#define NUM_MIX_BUFS        2

/** fluid_dart_audio_driver_t
 *
 * This structure should not be accessed directly. Use audio port
 * functions instead.
 */
typedef struct {
    fluid_audio_driver_t driver;
    fluid_synth_t* synth;
    int frame_size;
    USHORT usDeviceID;                          /* Amp Mixer device id     */
    MCI_MIX_BUFFER MixBuffers[NUM_MIX_BUFS];    /* Device buffers          */
    MCI_MIXSETUP_PARMS MixSetupParms;           /* Mixer parameters        */
    MCI_BUFFER_PARMS BufferParms;               /* Device buffer parms     */
} fluid_dart_audio_driver_t;

static fluid_dart_audio_driver_t* m_dev;
static HMODULE m_hmodMDM = NULLHANDLE;
static ULONG (APIENTRY *m_pfnmciSendCommand)(USHORT, USHORT, ULONG, PVOID, USHORT) = NULL;

fluid_audio_driver_t* new_fluid_dart_audio_driver(fluid_settings_t* settings,
                          fluid_synth_t* synth);

int delete_fluid_dart_audio_driver(fluid_audio_driver_t* p);
void fluid_dart_audio_driver_settings(fluid_settings_t* settings);

static LONG APIENTRY fluid_dart_audio_run( ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags );

/**************************************************************
 *
 *        DART audio driver
 *
 */

void fluid_dart_audio_driver_settings(fluid_settings_t* settings)
{
    fluid_settings_register_str(settings, "audio.dart.device", "default", 0, NULL, NULL);
}


fluid_audio_driver_t*
new_fluid_dart_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth)
{
    fluid_dart_audio_driver_t* dev;
    double sample_rate;
    int periods, period_size;
    UCHAR szFailedName[ 256 ];
    MCI_AMP_OPEN_PARMS AmpOpenParms;
    int i;
    ULONG rc;

    dev = FLUID_NEW(fluid_dart_audio_driver_t);
    if (dev == NULL) {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        return NULL;
    }
    FLUID_MEMSET(dev, 0, sizeof(fluid_dart_audio_driver_t));

    fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
    fluid_settings_getint(settings, "audio.periods", &periods);
    fluid_settings_getint(settings, "audio.period-size", &period_size);

    /* check the format */
    if (!fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) {
        FLUID_LOG(FLUID_ERR, "Unhandled sample format");
        goto error_recovery;
    }

    dev->synth = synth;
    dev->frame_size = 2 * sizeof(short);

    /* Load only once
     */
    if( m_hmodMDM == NULLHANDLE )
    {
        rc = DosLoadModule(szFailedName, sizeof(szFailedName), "MDM.DLL", &m_hmodMDM);

        if (rc != 0 ) {
            FLUID_LOG(FLUID_ERR, "Cannot load MDM.DLL for DART due to %s", szFailedName);
            goto error_recovery;
        }

        rc = DosQueryProcAddr(m_hmodMDM, 1, NULL, (PFN *)&m_pfnmciSendCommand);

        if (rc != 0 ) {
            FLUID_LOG(FLUID_ERR, "Cannot find mciSendCommand() in MDM.DLL");
            DosFreeModule(m_hmodMDM);
            m_hmodMDM = NULLHANDLE;
            goto error_recovery;
        }
    }

    /* open the mixer device
     */
    FLUID_MEMSET(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS));
    AmpOpenParms.usDeviceID = (USHORT)0;
    AmpOpenParms.pszDeviceType = (PSZ)MCI_DEVTYPE_AUDIO_AMPMIX;

    rc = m_pfnmciSendCommand(0, MCI_OPEN,
                             MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE,
                             (PVOID)&AmpOpenParms, 0);

    if (rc != MCIERR_SUCCESS) {
        FLUID_LOG(FLUID_ERR, "Cannot open DART, rc = %lu", rc);
        goto error_recovery;
    }

    dev->usDeviceID = AmpOpenParms.usDeviceID;

    /* Set the m_MixSetupParms data structure to match the requirements.
     * This is a global that is used to setup the mixer.
     */
    dev->MixSetupParms.ulBitsPerSample = BPS_16;
    dev->MixSetupParms.ulFormatTag = MCI_WAVE_FORMAT_PCM;
    dev->MixSetupParms.ulSamplesPerSec = sample_rate;
    dev->MixSetupParms.ulChannels = 2;

    /* Setup the mixer for playback of wave data
     */
    dev->MixSetupParms.ulFormatMode = MCI_PLAY;
    dev->MixSetupParms.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
    dev->MixSetupParms.pmixEvent    = fluid_dart_audio_run;

    rc = m_pfnmciSendCommand(dev->usDeviceID, MCI_MIXSETUP,
                             MCI_WAIT | MCI_MIXSETUP_INIT,
                             (PVOID)&dev->MixSetupParms, 0);

    if (rc != MCIERR_SUCCESS) {
        FLUID_LOG(FLUID_ERR, "Cannot setup DART, rc = %lu", rc);
        goto error_recovery;
    }

    /* Set up the m_BufferParms data structure and allocate
     * device buffers from the Amp-Mixer
     */
    dev->BufferParms.ulNumBuffers = NUM_MIX_BUFS;
    dev->BufferParms.ulBufferSize = periods * period_size * dev->frame_size;
    dev->BufferParms.pBufList = dev->MixBuffers;

    rc = m_pfnmciSendCommand(dev->usDeviceID, MCI_BUFFER,
                             MCI_WAIT | MCI_ALLOCATE_MEMORY,
                             (PVOID)&dev->BufferParms, 0);

    if ((USHORT)rc != MCIERR_SUCCESS) {
        FLUID_LOG(FLUID_ERR, "Cannot allocate memory for DART, rc = %lu", rc);
        goto error_recovery;
    }

    /* Initialize all device buffers.
     */
    for (i = 0; i < NUM_MIX_BUFS; i++) {
       FLUID_MEMSET(dev->MixBuffers[i].pBuffer, 0, dev->BufferParms.ulBufferSize);
       dev->MixBuffers[i].ulBufferLength = dev->BufferParms.ulBufferSize;
       dev->MixBuffers[i].ulFlags = 0;
       fluid_synth_write_s16(dev->synth, dev->MixBuffers[i].ulBufferLength / dev->frame_size,
                             dev->MixBuffers[i].pBuffer, 0, 2, dev->MixBuffers[i].pBuffer, 1, 2 );
    }

    /* Write buffers to kick off the amp mixer.
     */
    dev->MixSetupParms.pmixWrite(dev->MixSetupParms.ulMixHandle,
                                 dev->MixBuffers,
                                 NUM_MIX_BUFS);

    m_dev = dev;

    return (fluid_audio_driver_t*) dev;

error_recovery:

    delete_fluid_dart_audio_driver((fluid_audio_driver_t*) dev);
    return NULL;
}

int delete_fluid_dart_audio_driver(fluid_audio_driver_t* p)
{
    fluid_dart_audio_driver_t* dev = (fluid_dart_audio_driver_t*) p;

    if (dev == NULL) {
        return FLUID_OK;
    }

    if (dev->usDeviceID != 0) {
        MCI_GENERIC_PARMS    GenericParms;

        /* Send message to stop the audio device
         */
        m_pfnmciSendCommand(dev->usDeviceID, MCI_STOP, MCI_WAIT,
                            (PVOID)&GenericParms, 0);

        /* Deallocate device buffers
         */
        m_pfnmciSendCommand(dev->usDeviceID, MCI_BUFFER,
                            MCI_WAIT | MCI_DEALLOCATE_MEMORY,
                            (PVOID)&dev->BufferParms, 0);

        /* Close device the mixer device
         */
        m_pfnmciSendCommand(dev->usDeviceID, MCI_CLOSE, MCI_WAIT,
                            (PVOID)&GenericParms, 0);
    }

    FLUID_FREE(dev);
    return FLUID_OK;
}

static LONG APIENTRY fluid_dart_audio_run( ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags )
{
    switch( ulFlags ) {
        case MIX_STREAM_ERROR | MIX_WRITE_COMPLETE: /* error occur in device */
        case MIX_WRITE_COMPLETE:                    /* for playback  */
            FLUID_MEMSET(pBuffer->pBuffer, 0, pBuffer->ulBufferLength);
            fluid_synth_write_s16(m_dev->synth, pBuffer->ulBufferLength / m_dev->frame_size,
                                  pBuffer->pBuffer, 0, 2, pBuffer->pBuffer, 1, 2 );
            m_dev->MixSetupParms.pmixWrite(m_dev->MixSetupParms.ulMixHandle, pBuffer, 1);
            break;
    }

    return TRUE;
}

#endif /* #if DART_SUPPORT */
