/*------------------------------------------------------------------------------

   Copyright (c) 2000-2007 Tyrell Corporation. All rights reserved.

   Copyright (c) 2005 Nicholas Humfrey. All rights reserved.

   Tyrell DarkIce

   File     : JackDspSource.cpp
   Version  : $Revision$
   Author   : $Author$
   Location : $HeadURL$
   
   Copyright notice:

    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.

------------------------------------------------------------------------------*/

/* ============================================================ include files */

#include "AudioSource.h"

#ifdef SUPPORT_JACK_DSP
// only compile this code if there is support for it

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#else
#error need unistd.h
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#else
#error need string.h
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#else
#error need sys/types.h
#endif

#ifdef HAVE_MATH_H
#include <math.h>
#else
#error need math.h
#endif

#include "Util.h"
#include "Exception.h"
#include "JackDspSource.h"


/* ===================================================  local data structures */


/* ================================================  local constants & macros */

/*------------------------------------------------------------------------------
 *  File identity
 *----------------------------------------------------------------------------*/
static const char fileid[] = "$Id$";


/* ===============================================  local function prototypes */


/* =============================================================  module code */

/*------------------------------------------------------------------------------
 *  Initialize the object
 *----------------------------------------------------------------------------*/
void
JackDspSource :: init ( const char* name )           throw ( Exception )
{
    // Set defaults
    ports[0]     = NULL;        // Left Port
    ports[1]     = NULL;        // Right Port
    rb[0]        = NULL;        // Left Ring Buffer
    rb[1]        = NULL;        // Right Ring Buffer
    client       = NULL;
    auto_connect = false;       // Default is to not auto connect the JACK ports
    tmp_buffer   = NULL;        // Buffer big enough for one 'read' of audio

    // Auto connect the ports ?
    if ( Util::strEq( name, "jack_auto", 9) ) {
        auto_connect = true;
    }
    
    // Check the sample size
    if (getBitsPerSample() != 16) {
        throw Exception( __FILE__, __LINE__,
                        "JackDspSource doesn't support non 16-bit samples");
    }
}



/*------------------------------------------------------------------------------
 *  De-initialize the object
 *----------------------------------------------------------------------------*/
void
JackDspSource :: strip ( void )                      throw ( Exception )
{
    if ( isOpen() ) {
        close();
    }
    
    // Free the temporary buffer
    if (tmp_buffer) {
        free(tmp_buffer);
        tmp_buffer = NULL;
    }

}

/*------------------------------------------------------------------------------
 *  Attempt to connect up the JACK ports automatically
 *   - Just connect left&right to the first two output ports we find
 *----------------------------------------------------------------------------*/
void
JackDspSource :: do_auto_connect ( void )                   throw ( Exception )
{
    const char **all_ports;
    unsigned int ch = 0;
    int          i;

    Reporter::reportEvent( 10, "JackDspSource :: do_auto_connect");
    
    // Get a list of all the jack ports
    all_ports = jack_get_ports (client, NULL, NULL, JackPortIsOutput);
    if (!ports) {
        throw Exception( __FILE__, __LINE__, "jack_get_ports() returned NULL.");
    }
    
    // Step through each port name
    for (i = 0; all_ports[i]; ++i) {

        const char* in  = all_ports[i];
        const char* out = jack_port_name( ports[ch] );
        
        Reporter::reportEvent( 2, "Connecting", in, "to", out);
        
        if (jack_connect(client, in, out)) {
            throw Exception( __FILE__, __LINE__,
                            "Failed to jack_connect() ports", in, out);
        }
    
        // Found enough ports ?
        if (++ch >= getChannel()) break;
    }
    
    free( all_ports );

}


/*------------------------------------------------------------------------------
 *  Open the audio source
 *----------------------------------------------------------------------------*/
bool
JackDspSource :: open ( void )                       throw ( Exception )
{
    char         client_name[255];
    size_t       rb_size;
    unsigned int c;
    
    if ( isOpen() ) {
        return false;
    }

    // Register client with Jack
    snprintf(client_name, 255, "darkice-%d", getpid());
    if ((client = jack_client_new(client_name)) == NULL) {
        throw Exception( __FILE__, __LINE__, "JACK server not running?");
    }
    Reporter::reportEvent( 1, "Registering as JACK client", client_name);


    // Check the sample rate is correct
    if (jack_get_sample_rate( client ) != (jack_nframes_t)getSampleRate()) {
        throw Exception( __FILE__, __LINE__,
                        "JACK server sample rate is different than "
                        "sample rate in darkice config file");
    }


    // Register ports with Jack
    if (getChannel() == 1) {
        if (!(ports[0] = jack_port_register(client,
                                            "mono",
                                            JACK_DEFAULT_AUDIO_TYPE,
                                            JackPortIsInput,
                                            0))) {
            throw Exception( __FILE__, __LINE__,
                            "Cannot register input port", "mono");
        }
    } else if (getChannel() == 2) {
        if (!(ports[0] = jack_port_register(client,
                                            "left",
                                            JACK_DEFAULT_AUDIO_TYPE,
                                            JackPortIsInput,
                                            0))) {
            throw Exception( __FILE__, __LINE__,
                            "Cannot register input port", "left");
        }
        if (!(ports[1] = jack_port_register(client,
                                            "right",
                                            JACK_DEFAULT_AUDIO_TYPE,
                                            JackPortIsInput, 0))) {
            throw Exception( __FILE__, __LINE__,
                            "Cannot register input port", "right");
        }
    } else {
        throw Exception( __FILE__, __LINE__,
                        "Invalid number of channels", getChannel());
    }


    // Create a ring buffer for each channel
    rb_size = 2
            * jack_get_sample_rate(client)
            * sizeof (jack_default_audio_sample_t);
    for (c=0; c<getChannel(); c++) {
        rb[c] = jack_ringbuffer_create(rb_size);
        if (!rb[c]) {
            throw Exception( __FILE__, __LINE__,
                            "Failed to create ringbuffer for", "channel", c);
        }
    }


    // Set the callbacks
    jack_on_shutdown(client, JackDspSource::shutdown_callback, (void*)this);
    if (jack_set_process_callback(client,
                                  JackDspSource::process_callback,
                                  (void*)this)) {
        throw Exception( __FILE__, __LINE__, "Failed to set process callback");
    }    

    // Activate client
    if (jack_activate(client)) {
        throw Exception( __FILE__, __LINE__, "Can't activate client");
    }
    
    // Attempt to automatically connect up our input ports to something ?
    if (auto_connect) {
        do_auto_connect();
    }
    
    return true;
}


/*------------------------------------------------------------------------------
 *  Check wether read() would return anything
 *----------------------------------------------------------------------------*/
bool
JackDspSource :: canRead ( unsigned int   sec,
                           unsigned int   usec )    throw ( Exception )
{
    size_t available=0;
    
    if ( !isOpen() ) {
        return false;
    }
    
    // How many bytes available in ring buffer ?
    available = jack_ringbuffer_read_space( rb[0] );
    if (available)    return true;
    
    // Sleep and check again
    // FIXME: should we really sleep the full duration ?
    usleep( (sec*1000000) + usec );
    
    available = jack_ringbuffer_read_space( rb[0] );
    if (available) {
        return true;
    } else {
        return false;
    }
}


/*------------------------------------------------------------------------------
 *  Read from the audio source
 *----------------------------------------------------------------------------*/
unsigned int
JackDspSource :: read (   void          * buf,
                          unsigned int    len )     throw ( Exception )
{
    jack_nframes_t samples         = len / 2 / getChannel();
    jack_nframes_t samples_read[2] = {0,0};
    short        * output          = (short*)buf;
    unsigned int c, n;

    if ( !isOpen() ) {
        return 0;
    }


    // Ensure the temporary buffer is big enough
    tmp_buffer = (jack_default_audio_sample_t*)realloc(tmp_buffer,
                             samples * sizeof( jack_default_audio_sample_t ) );
    if (!tmp_buffer) {
        throw Exception( __FILE__, __LINE__, "realloc on tmp_buffer failed");
    }


    for (c=0; c<getChannel(); c++)
    {    
        // Copy frames from ring buffer to temporary buffer
        // and then convert samples to output buffer
        int bytes_read = jack_ringbuffer_read(rb[c],
                                             (char*)tmp_buffer,
                              samples * sizeof( jack_default_audio_sample_t ));
        samples_read[c] = bytes_read / sizeof( jack_default_audio_sample_t );
        

        // Convert samples from float to short and put in output buffer
        for(n=0; n<samples_read[c]; n++) {
            int tmp = lrintf(tmp_buffer[n] * 32768.0f);
            if (tmp > SHRT_MAX) {
                output[n*getChannel()+c] = SHRT_MAX;
            } else if (tmp < SHRT_MIN) {
                output[n*getChannel()+c] = SHRT_MIN;
            } else {
                output[n*getChannel()+c] = (short) tmp;
            }
        }
    }

    // Didn't get as many samples as we wanted ?
    if (getChannel() == 2 && samples_read[0] != samples_read[1]) {
        Reporter::reportEvent( 2,
                              "Warning: Read a different number of samples "
                              "for left and right channels");
    }

    // Return the number of bytes put in the output buffer
    return samples_read[0] * 2 * getChannel();
}


/*------------------------------------------------------------------------------
 *  Close the audio source
 *----------------------------------------------------------------------------*/
void
JackDspSource :: close ( void )                  throw ( Exception )
{
    unsigned int i;

    if ( !isOpen() ) {
        return;
    }

    for(i = 0; i < getChannel(); i++) {
        // Close the port for channel
        if ( ports[i] ) {
            jack_port_unregister( client, ports[i] );
            ports[i] = NULL;
        }
        
        // Free up the ring buffer for channel
        if ( rb[i] ) {
            jack_ringbuffer_free( rb[i] );
            rb[i] = NULL;
        }
    }

    /* Leave the jack graph */
    if (client) {
        jack_client_close(client);
        client = NULL;
    }

}


/*------------------------------------------------------------------------------
 *  Callback called by JACK when audio is available
 *
 *  Don't do anything too expensive here
 *      - just shove audio samples in ring buffer
 *----------------------------------------------------------------------------*/
int
JackDspSource :: process_callback( jack_nframes_t nframes, void *arg )
{
    JackDspSource* self     = (JackDspSource*)arg;
    size_t         to_write = sizeof (jack_default_audio_sample_t) * nframes;
    unsigned int   c;
    
    // Wait until it is ready
    if (self->client == NULL) {
        return 0;
    }

    /* copy data to ringbuffer; one per channel */
    for (c=0; c < self->getChannel(); c++) {    
        char *buf  = (char*)jack_port_get_buffer(self->ports[c], nframes);
        size_t len = jack_ringbuffer_write(self->rb[c], buf, to_write);
        if (len < to_write) {
            Reporter::reportEvent( 1, "failed to write to ring ruffer");
            return 1;
         }
    }

    // Success
    return 0;
}

/*------------------------------------------------------------------------------
 *  Callback called when 
 *----------------------------------------------------------------------------*/
void
JackDspSource :: shutdown_callback( void *arg )
{
    //JackDspSource* self = (JackDspSource*)arg;

    Reporter::reportEvent( 1, "JackDspSource :: shutdown_callback");
}


#endif // SUPPORT_JACK_DSP

