/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
/*
 * $Id: play.c,v 1.4 2003/06/26 20:42:04 rocko Exp $
 *
 * Copyright (c) 2000, 2001 by Shiman Associates Inc. and Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions: The above
 * copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors or
 * copyright holders shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without
 * prior written authorization from the authors or copyright holders,
 * as applicable.
 *
 * All trademarks and registered trademarks mentioned herein are the
 * property of their respective owners. No right, title or interest in
 * or to any trademark, service mark, logo or trade name of the
 * authors or copyright holders or their licensors is granted.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mas/mas.h"
#include "mas/mas_nanosleep_reality.h"
#include "wav.h"

#define  BUFFER_SIZE           4608
#define  BUFFER_TIME_NS        26122449
#define  FORCED_STABLE_LOOPS   10
#define  NANOSLEEP_GRANULARITY 100000

/*
 * maswavplay
 *
 * This simple client demonstrates the paced push-mode audio data
 * transmission to the server.  It switches a sample rate converter in
 * as needed.  Another, simpler implementation could use the
 * server-side .wav source device (source_wav) to be completely
 * server-bound, avoiding this client interaction.  Yet another
 * implementation could use a pull-style callback interface, in which
 * the server requests data from the client as it needs it.
 *
 */

int main(int argc, char* argv[])
{
    FILE      *fp;
    int32      error;
    int        read_data;
    int        i, seq;
    char       ratestring[16];
    char       bps[8];
    u_int32    actual_usec_slept  = 0;
    u_int32    request_nsec_sleep = BUFFER_TIME_NS;
    u_int32    pre_sleep_usec_ts  = 0;
    u_int32    post_sleep_usec_ts = 0;
    u_int32    now_ts             = 0;
    u_int32    next_pending_event_ts = 0;
    u_int32    length;
    int32      err;
    mas_channel_t audio_channel;
    mas_port_t    mix_sink;
    mas_port_t    srate_source, srate_sink;
    mas_port_t    audio_source, audio_sink;
    mas_port_t    endian_sink, endian_source;
    mas_port_t    squant_sink, squant_source;
    mas_port_t    open_source; /* (!) */
    mas_device_t  endian;
    mas_device_t  srate;
    mas_device_t  squant;
    uint16     wformat, wchannels, wbps;
    uint32     wsrate;
    struct mas_data_characteristic* dc;
    struct mas_data* data;
    
    
    if (argc != 2)
    {
	fprintf(stderr, "usage: maswavplay file.wav\n");
	exit(0);
    }
    
    if ( ( error = riff_begin_read(argv[1], &fp) ) < 0 )
    {
	fprintf(stderr, "error reading .wav file %s, error %d\n",
		argv[1], error);
	exit(0);
    }
   
    printf("Connecting to MAS.\n");
    masc_log_verbosity( MAS_VERBLVL_DEBUG );
    err = mas_init();
    if (err < 0)
    {
	masc_logerror(err, "connection with server failed.");
	exit(1);
    }    
    
    masc_log_message(0, "Establishing audio output channel.");
    err = mas_make_data_channel( "masplay", &audio_channel, &audio_source, &audio_sink );

    err = mas_asm_get_port_by_name( 0, "default_mix_sink", &mix_sink );

    wav_get_format( fp, &wformat );
    if ( wformat != WAVE_FORMAT_PCM ) 
    {
	masc_log_message( 0, "Unsupported format\n");
	exit(1);
    }
    wav_get_channels( fp, &wchannels );
    if ( wchannels != 2 )
    {
	masc_log_message( 0, "masplay only supports stereo\n");
	exit(1);
    }

    masc_log_message( 0, "Instantiating endian device.");
    err = mas_asm_instantiate_device( "endian", 0, 0, &endian );
    if ( err < 0 ) 
    {
        masc_logerror(err, "Failed to instantiate endian converter device");
        exit(1);
    }
    err = mas_asm_get_port_by_name( endian, "sink", &endian_sink );
    err = mas_asm_get_port_by_name( endian, "source", &endian_source );


    /* The connection path will be this:
     * net -> endian -> [squant] -> [srate] -> mix -> audioio,
     * [] meaning 'only if required'
    */
      
    wav_pcm_get_bits_per_sample( fp, &wbps );
    wav_get_samples_per_second( fp, &wsrate );

    sprintf( bps, "%u", wbps );
    sprintf( ratestring, "%u", wsrate );


    masc_log_message( 0, "Connecting net -> endian.");
    dc = MAS_NEW( dc );
    masc_setup_dc( dc, 6 );
    
    /* wav weirdness: 8 bit data is unsigned, >8 data is signed. */
    masc_append_dc_key_value( dc, "format", (wbps==8) ? "ulinear":"linear" );

    masc_append_dc_key_value( dc, "resolution", bps );
    masc_append_dc_key_value( dc, "sampling rate", ratestring );
    masc_append_dc_key_value( dc, "channels", "2" );
    masc_append_dc_key_value( dc, "endian", "little" );
    err = mas_asm_connect_source_sink( audio_source, endian_sink, dc );
    if ( err < 0 ) 
	{
	    masc_logerror(err, "Failed to connect net audio output to endian");
	    exit(1);
	}
    

    
    /* The next device is 'if needed' only. After the following if()
       statement, open_source will contain the current unconnected
       source in the path (will be either endian_source or
       squant_source in this case)
    */
    open_source = endian_source;
    
    if ( wbps != 16 )
    {
	masc_log_message( 0, "Sample resolution is not 16 bit/sample, instantiating squant device.");
        err = mas_asm_instantiate_device( "squant", 0, 0, &squant );
	if ( err < 0 ) 
	{
	    masc_logerror(err, "Failed to instantiate squant device");
	    exit(1);
	}
	err = mas_asm_get_port_by_name( squant, "sink", &squant_sink );
	err = mas_asm_get_port_by_name( squant, "source", &squant_source );

        
        masc_log_message( 0, "Connecting endian -> squant.");

        masc_strike_dc( dc );
        masc_setup_dc( dc, 6 );
        masc_append_dc_key_value( dc,"format",(wbps==8) ? "ulinear":"linear" );
        masc_append_dc_key_value( dc, "resolution", bps );
        masc_append_dc_key_value( dc, "sampling rate", ratestring );
        masc_append_dc_key_value( dc, "channels", "2" );
        masc_append_dc_key_value( dc, "endian", "host" );
        err = mas_asm_connect_source_sink( endian_source, squant_sink, dc );
        if ( err < 0 ) 
        {
            masc_logerror(err, "Failed to connect endian output to squant");
	    exit(1);
	}

        /* sneaky: the squant device is optional -> pretend it isn't there */
        open_source = squant_source;
    }

    
    /* Another 'if necessary' device, as above */
    if ( wsrate != 44100 )
    {
	masc_log_message( 0, "Sample rate is not 44100, instantiating srate device.");
	err = mas_asm_instantiate_device( "srate", 0, 0, &srate );
	if ( err < 0 ) 
	{
	    masc_logerror(err, "Failed to instantiate srate device");
	    exit(1);
        }
	err = mas_asm_get_port_by_name( srate, "sink", &srate_sink );
	err = mas_asm_get_port_by_name( srate, "source", &srate_source );
	
	masc_log_message( 0, "Connecting to srate.");
        masc_strike_dc( dc );
	masc_setup_dc( dc, 6 );
	masc_append_dc_key_value( dc, "format", "linear" );
	masc_append_dc_key_value( dc, "resolution", "16" );
	masc_append_dc_key_value( dc, "sampling rate", ratestring );
	masc_append_dc_key_value( dc, "channels", "2" );
	masc_append_dc_key_value( dc, "endian", "host" );
	err = mas_asm_connect_source_sink( open_source, srate_sink, dc );
	if ( err < 0 ) 
	{
	    masc_logerror(err, "Failed to connect to srate");
	    exit(1);
	}

        open_source = srate_source;        
    }

    
    masc_log_message( 0, "Connecting to mix.");
    masc_strike_dc( dc );
    masc_setup_dc( dc, 6 );
    masc_append_dc_key_value( dc, "format", "linear" );
    masc_append_dc_key_value( dc, "resolution", "16" );
    masc_append_dc_key_value( dc, "sampling rate", "44100" );
    masc_append_dc_key_value( dc, "channels", "2" );
    masc_append_dc_key_value( dc, "endian", "host" );
    err = mas_asm_connect_source_sink( open_source, mix_sink, dc );
    if ( err < 0 ) 
    {
        masc_logerror(err, "Failed to connect to mixer");
        exit(1);
    }

    
    masc_log_message( 0, "playing.\n");
    i = 0;
    seq=0;

    if (wav_get_length(fp, &length) < 0)
    {
	fprintf(stderr, "error in wav subsystem\n");
	exit(1);
    }

    masc_init_timing();
    masc_get_short_usec_ts( &now_ts );
    next_pending_event_ts = now_ts;

    masc_log_message( 0, "length: %u\n", length);
    data = MAS_NEW( data );
    masc_setup_data( data, BUFFER_SIZE ); /* we can reuse this */
    data->length = BUFFER_SIZE;
    data->header.type = 10;

    /* new strategy - sleep when you can.
       Do all pending events.  Then sleep, knowing our minimum sleep
       time. */

    while (i < length)
    {
	masc_get_short_usec_ts( &now_ts );
	
	if (next_pending_event_ts - MAS_MIN_SLEEP/1000 < now_ts)
	{
	    data->header.media_timestamp = i/4;
	    data->header.sequence        = seq++;
	
	    read_data = fread( data->segment, 1, data->length, fp);
	    i += read_data;

/** Don't do this endian conversion... leave it at little endian */
#if 0
	    for (j = 0; j < read_data/2; j++)
	    {
		((int16*)(data->segment))[j] = mas_letons(((int16*)(data->segment))[j]);
	    }
#endif /**/

	    err = mas_send( audio_channel , data );
	    if ( err < 0 ) masc_log_message( 0, "!send!");
	    
#if (DEBUG >= 2)
	    masc_log_message( 0, ".");
#endif

	    next_pending_event_ts += BUFFER_TIME_NS/1000;
	}
	else /* sleep! */
	{
	    /**** Process cpu-usage modulating sleepytime ***********************/

	    /* We wrap the call to nanosleep with timestamp counters, so
	       we can keep an eye on how long we actually slept.  We work
	       with nanoseconds to control nanosleep(), but with
	       microseconds to see how long we slept.  You can only fit
	       about 4 seconds worth of nanoseconds in a 32-bit unsigned
	       int. */
	    
	    request_nsec_sleep = MAS_MIN_SLEEP;
	    pre_sleep_usec_ts = now_ts;
	    masc_realsleep(request_nsec_sleep);
	    masc_get_short_usec_ts ( &post_sleep_usec_ts );

	    actual_usec_slept = post_sleep_usec_ts - pre_sleep_usec_ts;
	    
#if (DEBUG >= 2)
	    masc_log_message( 0, "requested %u, slept %u\n", request_nsec_sleep/1000,
		   actual_usec_slept); 
#endif
	}
    }

    masc_exit_timing();
    exit(1);
    /* for now, the connection paths are managed by hand. */

    return 0;
}
