/*
 * 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.
 *
 */

/* 2 OCT 2002 - rocko - verified reentrant
 * 2 OCT 2002 - rocko - verified timestamp clean
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include "mas/mas_dpi.h"
#include "source_internal.h"
#include "playlist.h"
#include "wav.h"
#include "wav_profile.h"

/* Default samples per frame */
#define DEFAULT_SPF 576

static int print_wav_file_info( struct wave_track_info* );
static int parse_small_chunks( FILE * , struct wave_track_info* , u_int32 );
static int32 wave_start_decode( FILE* fp, struct wave_track_info** retval_info );
static const char* wave_format_string( int format );
static const char* wave_dc_format( int format );
static void* alloc_read( FILE* fp, int32 count );
static void compute_period( struct track_info* ti, int32 spf );

int32
sourcex_init_instance( struct source_state* state )
{
    struct wave_state* ws;

    /* set up our format-dependent state */
    ws = MAS_NEW( ws );
    ws->spf = DEFAULT_SPF;
    state->fdstate = ws;

    /******
     ****** HACK
     ******
     ******/
    {
        struct mas_data_characteristic* dc;

        dc = masc_make_audio_basic_dc( MAS_LINEAR_FMT, 44100, 16, 2, MAS_LITTLE_ENDIAN_FMT );
        /* set the source to the dc */
        masd_set_data_characteristic( state->source, dc );
    }
    

    return 0;
}

int32
sourcex_configure_port( struct source_state* state, int32 portnum )
{
    return 0;
}

int32
sourcex_disconnect_port( struct source_state* state, int32 portnum )
{
    return 0;
}

int32
sourcex_exit_instance( struct source_state* state )
{
    masc_rtfree( state->fdstate );
    return 0;
}

int32
sourcex_show_state( struct source_state* state )
{
    return 0;
}

int32
sourcex_stop( struct source_state* state )
{
    struct wave_track_info* wti;

    /* rewind the file to the start of the data section */
    if ( state->ti )
    {
        wti = state->ti->fdti;
        if ( state->ti->file && wti )
            fseek( state->ti->file, wti->offset_data_start, SEEK_SET );
    }
    

    return 0;
}

int32
sourcex_play( struct source_state* state )
{
    return 0;
}

int32
sourcex_pause( struct source_state* state )
{
    return 0;
}

int32
sourcex_next_track( struct source_state* state )
{
    return 0;
}

int32
sourcex_prev_track( struct source_state* state )
{
    return 0;
}

int32
sourcex_cue_track( struct source_state* state, struct track_info* ti )
{
    struct wave_track_info* wti;

    /* rewind the file to the start of the data section */
    if ( state->ti )
    {
        wti = state->ti->fdti;
        if ( state->ti->file && wti )
        {
            fseek( state->ti->file, wti->offset_data_start, SEEK_SET );
        }
        else
        {
            return mas_error(MERR_INVALID);
        }
    }
    else
    {
        return mas_error(MERR_INVALID);
    }

    return 0;
}


/* Return (in the argument list) the next data segment from the
   source. */
int32
sourcex_get_data( struct source_state* state, struct track_info* ti, uint32 seq, struct mas_data** data_ptr )
{
    struct mas_data* data;
    struct wave_track_info* wti = ti->fdti;
    struct wave_state* ws = state->fdstate;
    int rb, length;
    int32 pos, remain;
    double sec;

    /* check to make sure we're not at the end of the file. */
    if ( feof(ti->file) )
        return MAS_STREAM_END;

    pos = ftell( ti->file );
    remain = wti->offset_data_start + wti->data_length - pos;
    if ( remain <= 0 )
        return MAS_STREAM_END;

    length = min( ws->spf * wti->bpstc, remain );
    data = MAS_NEW( data );
    masc_setup_data( data, length );
    rb = fread(data->segment, 1, length, ti->file);
    if ( rb == 0 )
    {
        masc_strike_data( data );
        masc_rtfree( data );
        return MAS_STREAM_END;
    }
    
    data->length = rb;

    /* Compute media timestamp as a function of the sequence number. */
    data->header.media_timestamp = seq * ws->spf;

    /* NTP calculation is an approximation */
    sec = (double)ws->spf * (double)seq / ( (double)wti->fmt->channels * (double)wti->fmt->samples_per_second );
    data->header.ntp_seconds = (uint32)floor(sec);
    data->header.ntp_fraction = (uint32)(4295000000.0 * (sec - (double)data->header.ntp_seconds));
    
    /* set sequence number */
    data->header.sequence = seq;

    *data_ptr = data;
    
    return 0;
}

/* Complete the track info fields.  If needed, set the fdti pointer to
 * a format-dependent struct. */
int32
sourcex_fill_out_track_info( struct source_state* state, struct track_info* ti )
{
    struct wave_track_info*   wti;
    struct wave_state* ws = state->fdstate;
    int32 err;

    if ( ti == 0 )
        return mas_error(MERR_NULLPTR);

    err = wave_start_decode( ti->file, &wti );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "source_wav: error in .wav file");
        return err;
    }
    
    ti->fdti = wti;
    wti->bpstc = wti->fmt->channels * wti->pcm_fields->bits_per_sample / 8;
    
    /* compute the correct period to use for this track */
    compute_period( ti, ws->spf );
    
    /* compute estimated track time */
    ti->length_sec = (double)wti->data_length / wti->fmt->average_bytes_per_second;
    
#ifdef DEBUG
    print_wav_file_info( wti );
#endif

    /* rewind to start of data section */
    fseek( ti->file, wti->offset_data_start, SEEK_SET );
    
    return 0;
}

/* Is the format of the two specified tracks different? */
int
sourcex_format_diff( struct source_state* state, struct track_info* ti, struct track_info* prev_ti )
{
    struct wave_track_info* fdti;
    struct wave_track_info* prev_fdti;

    if ( ti == 0 || prev_ti == 0 )
        return mas_error(MERR_NULLPTR);

    fdti = ti->fdti;
    prev_fdti = prev_ti->fdti;
    
    if ( fdti->fmt->samples_per_second != prev_fdti->fmt->samples_per_second )
        return TRUE;

    if ( fdti->fmt->channels != prev_fdti->fmt->channels )
        return TRUE;

    if ( fdti->fmt->format_tag != prev_fdti->fmt->format_tag )
        return TRUE;

    if ( fdti->pcm_fields && prev_fdti->pcm_fields )
    {
        if ( fdti->pcm_fields->bits_per_sample != prev_fdti->pcm_fields->bits_per_sample )
            return TRUE;
    }
    
    if ( ti->period != prev_ti->period )
        return TRUE;

    return FALSE;
}

/* Allocate and return the data characteristic for this track. */
struct mas_data_characteristic*
sourcex_get_track_dc( struct source_state* state, struct track_info* ti )
{
    struct mas_data_characteristic* dc;
    struct wave_track_info* fdti;
    char str[128];
    
    if ( ti == 0 )
        return 0;

    fdti = ti->fdti;

    /* Catch formats we can't handle.  Fall through to failure,
     * otherwise continue on.  We don't have to test for these
     * again. */
    switch ( fdti->fmt->format_tag )
    {
    case WAVE_FORMAT_UNKNOWN:
    case WAVE_FORMAT_ADPCM:
    case WAVE_FORMAT_OKI_ADPCM:
    case WAVE_FORMAT_IMA_ADPCM:
    case WAVE_FORMAT_DIGISTD:
    case WAVE_FORMAT_DIGIFIX:
    case WAVE_FORMAT_DOLBY_AC2:
    case WAVE_FORMAT_GSM610:
    case WAVE_FORMAT_ROCKWELL_ADPCM:
    case WAVE_FORMAT_ROCKWELL_DIGITALK:
    case WAVE_FORMAT_G728_CELP:
    case WAVE_FORMAT_MPEG:
    case WAVE_FORMAT_MPEGLAYER3: 
    case WAVE_FORMAT_G726_ADPCM:
        return 0;
    default: break;
    }

    /* format */
    dc = MAS_NEW( dc );
    masc_setup_dc( dc, 7 );
    masc_append_dc_key_value( dc, "format", wave_dc_format( fdti->fmt->format_tag ) );

    /* sampling rate */
    sprintf(str, "%d", fdti->fmt->samples_per_second);
    masc_append_dc_key_value( dc, "sampling rate", str );

    /* channels */
    sprintf(str, "%d", fdti->fmt->channels);
    masc_append_dc_key_value( dc, "channels", str );

    /* endian */
    if ( fdti->fmt->format_tag == WAVE_FORMAT_PCM )
    {
        if ( fdti->pcm_fields->bits_per_sample > 8 )
            masc_append_dc_key_value( dc, "endian", "little" );
    }
    else
    {
        /* this is an assumption! */
        masc_append_dc_key_value( dc, "endian", "host" );
    }

    switch ( fdti->fmt->format_tag )
    {
    case WAVE_FORMAT_PCM:
        sprintf(str, "%d", fdti->pcm_fields->bits_per_sample);
        masc_append_dc_key_value( dc, "resolution", str );
        break;
    case WAVE_FORMAT_IEEE_FLOAT:
        masc_append_dc_key_value( dc, "resolution", "32" );
        break;
    case WAVE_FORMAT_ALAW: 
    case WAVE_FORMAT_MULAW:
        masc_append_dc_key_value( dc, "resolution", "8" );
        break;
    case WAVE_FORMAT_G721_ADPCM:
        masc_append_dc_key_value( dc, "resolution", "4" );
        break;
    case WAVE_FORMAT_G726_ADPCM:
        masc_append_dc_key_value( dc, "resolution", "2" );
        break;
    default: break;
    }
    
    return dc;
}

/** LOCAL FUNCTIONS ***************************/


int32
wave_start_decode( FILE* fp, struct wave_track_info** retval_info )
{
    struct riff_chunk_header* header;
    int32                     err;
    struct wave_track_info*   info;
    FourCC                    wave_id;

    /** Get and verify RIFF header */ 
    err = riff_start_decode( fp, &header );
    if ( err < 0 )
	return err;

    /* initialize info */
    info = MAS_NEW( info );
    if ( info == 0 )
        return mas_error(MERR_MEMORY);

    info->length = header->length;

    if ( fread(&wave_id, 4, 1, fp) != 1 )
	return mas_error(MERR_IO);
        
    if ( riff_cmp_fourcc(wave_id, RIFF_WAVE_ID) != 0)
	return mas_error(MERR_FILE_TYPE);
 
    /* read in other riff chunks - try to find the first data chunk */
    if ( ( err = parse_small_chunks(fp, info, header->length) ) < 0 )
 	return mas_error(MERR_IO);
 
    /* seek to start of data chunk */
    if (info->offset_data_start)
    {
	if (fseek(fp, info->offset_data_start, SEEK_SET) < 0)
	    return mas_error(MERR_IO);
    }

    *retval_info = info;
    return 0;
}

/* This function is called recursively for list-style chunks */
int32
parse_small_chunks( FILE* fp, struct wave_track_info* info, uint32 chunk_length )
{
    struct riff_chunk_header header;
    int32                    read_data=1;
    int                      recurse_read_data=0;
    FourCC                   label;
    uint32                   skip;
    /* accounting for 'WAVE' */
    uint32                   chunk_countdown = chunk_length - 4; 
    int                      i;
    int                      rb;

    /* loop until EOF or the end of chunk*/
    while (read_data && (chunk_countdown > 0))
    {
	if ( (read_data = riff_read_chunk_header( fp, &header )) <= 0)
	    return chunk_length - chunk_countdown;

	masc_log_message( MAS_VERBLVL_DEBUG, "source_wav: chunk: %c%c%c%c", ((char *) &(header.id))[0], ((char *) &(header.id))[1], ((char *) &(header.id))[2], ((char *) &(header.id))[3] );  
	
	masc_log_message( MAS_VERBLVL_DEBUG, "source_wav: length: %d bytes", header.length);
	skip = header.length;

	/*** Parse individual chunks, if they're small enough **/
	/*** (this is everything except WAV_ID_DATA)   */

	/* if we can't parse a chunk, we pass over it. */

        /***** FORMAT CHUNK ********************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_FORMAT) == 0)
	{
            rb = SIZEOF_FMT_COMMON_FIELDS; /* structure isn't 32-bit
                                              word aligned */
	    if ( ( info->fmt = alloc_read(fp, rb) ) == 0 )
		return mas_error(MERR_IO);

	    /* do byte-sex conversion */
	    info->fmt->format_tag = mas_letohs(info->fmt->format_tag);
	    info->fmt->channels   = mas_letohs(info->fmt->channels);
	    info->fmt->samples_per_second = mas_letohl(info->fmt->samples_per_second);
	    info->fmt->average_bytes_per_second = mas_letohl(info->fmt->average_bytes_per_second);
	    info->fmt->block_align_bytes = mas_letohs(info->fmt->block_align_bytes);
	    
	    skip -= rb;

	    /* read in PCM-specific fields */
	    if (info->fmt->format_tag == WAVE_FORMAT_PCM)
	    {
                rb = sizeof *info->pcm_fields;
                info->pcm_fields = alloc_read( fp, rb );
		info->pcm_fields->bits_per_sample = mas_letohs(info->pcm_fields->bits_per_sample);
		skip -= rb;
	    }
	}

	/***** DATA CHUNK ********************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_DATA) == 0)
	{
	    /* get current position, right after the data chunk header */
	    info->offset_data_start = ftell(fp);
	    info->data_length = header.length;
	    /* don't modify skip */
	}
	
	/***** CUE CHUNK ********************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_CUE) == 0)
	{
	    /** get number of cue points */
            rb = sizeof *info->cue_header;
	    if ( ( info->cue_header = alloc_read(fp, rb) ) == 0 )
		return mas_error(MERR_IO);
	    info->cue_header->num_cue_points = mas_letohl(info->cue_header->num_cue_points);
	    skip -= rb;

            rb = info->cue_header->num_cue_points * sizeof (struct cue_point);
	    if ( rb <= ( header.length - sizeof *info->cue_header ) )
	    {
		if ( ( info->cue_points = alloc_read( fp, rb ) ) == 0 )
		    return mas_error(MERR_IO);
		for (i=0; i<info->cue_header->num_cue_points; i++)
		{
		    info->cue_points[i].name = mas_letohl(info->cue_points[i].name); 
		    info->cue_points[i].position = mas_letohl(info->cue_points[i].position);
		    info->cue_points[i].chunk_start = mas_letohl(info->cue_points[i].chunk_start); 
		    info->cue_points[i].block_start = mas_letohl(info->cue_points[i].block_start); 
		    info->cue_points[i].sample_offset = mas_letohl(info->cue_points[i].sample_offset); 
		}
		skip -= rb;
	    }
	}
	
	/***** LABEL CHUNK ********************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_LABEL) == 0)
	{
            rb = sizeof *info->label_header;
	    if ( ( info->label_header = alloc_read(fp, rb) ) == 0)
		return mas_error(MERR_IO);
	    if (rb < header.length) /* is there a data section? */
	    {
                rb = header.length - sizeof *info->label_header;
		if ( ( info->label_data = alloc_read(fp, rb ) ) == 0 )
		    return mas_error(MERR_IO);
	    }
	    skip -= header.length;
	}

        
	/***** NOTE CHUNK ********************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_NOTE) == 0)
	{
            rb = sizeof *info->note_header;
	    if ( ( info->note_header = alloc_read(fp, rb ) ) == 0 )
		return mas_error(MERR_IO);
	    if (rb < header.length) /* is there a data section? */
	    {
                rb = header.length - sizeof *info->note_header;
		if ( ( info->note_data = alloc_read(fp, rb ) ) == 0 )
		    return mas_error(MERR_IO);
	    }
            skip -= header.length;
	}

	/***** LABEL TEXT CHUNK *****************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_TEXT) == 0)
	{
            rb = sizeof *info->label_text_header;
	    if ( ( info->label_text_header = alloc_read( fp, rb ) ) == 0 )
		return mas_error(MERR_IO);
	    if (rb < header.length) /* is there a data section? */
	    {
                rb = header.length - sizeof *info->label_text_header;
		if ( ( info->note_data = alloc_read( fp, rb ) ) == 0 )
		    return mas_error(MERR_IO);
	    }
            skip -= header.length;
        }

	/***** LIST CHUNK ********************************************/
	if ( riff_cmp_fourcc(header.id, WAVE_ID_LIST) == 0)
	{
            rb = sizeof label;
	    if ( (read_data = fread(&label, rb, 1, fp)) != 1 )
		return mas_error(MERR_IO);
	    skip -= rb;
 
	    masc_log_message( 0, "source_wav: %c%c%c%c\n", ((char *) &(label))[0], ((char *) &(label))[1], ((char *) &(label))[2], ((char *) &(label))[3] );  
	    
	    /** if this is an associated data LIST chunk, we'll recurse
		into parse_small_chunks() to read this chunk's
		contents. */
	    if ( riff_cmp_fourcc(label, "adtl") == 0 )
	    {
		recurse_read_data = parse_small_chunks(fp, info, header.length - rb );
		skip -= recurse_read_data; /* should have read the rest of this chunk */
	    }
	}

	/** skip ahead to the start of the next chunk, discounting the
	    number of bytes we've already read. */
	if ( fseek(fp, skip, SEEK_CUR) < 0 )
            return mas_error(MERR_IO);

	/* The file position counter is now just past this chunk.  */
	chunk_countdown -= header.length + sizeof (struct riff_chunk_header);
    }

    return chunk_length - chunk_countdown; /* number of bytes read */
}

void
compute_period( struct track_info* ti, int32 spf )
{
    struct wave_track_info* wti = ti->fdti;

    /* Try to retrieve the id of a clock at our sampling rate. */
    /* This clock can be overridden, see cue_track */
    ti->period_clkid = masd_mc_match_rate( wti->fmt->samples_per_second );
    
    if ( ti->period_clkid < 0 )
    {
        /** clock doesn't exist */
        /* period is calculated as the granules * samples per frame
           normalized to frequency rate and # of channels */
        ti->period_clkid = MAS_MC_SYSCLK_US;
        ti->period = spf * 1000 / wti->fmt->samples_per_second;
    }
    else
    {
        /* This is preferable if the clock is available */
        ti->period = spf;
    }
}

/* DEBUGGING HELPER FUNCTIONS ******************************************/

int32
print_wav_file_info( struct wave_track_info* info )
{
    int i;

    masc_log_message( 0, "source_wav: RIFF chunk length: %d", info->length);
    
    if (info->fmt)
    {
	masc_log_message( 0, "source_wav: [format chunk]\n");
	masc_log_message( 0, "source_wav:               format tag: %s", wave_format_string(info->fmt->format_tag));
	masc_log_message( 0, "source_wav:                 channels: %d", info->fmt->channels);
	masc_log_message( 0, "source_wav:       samples per second: %d", info->fmt->samples_per_second);
	masc_log_message( 0, "source_wav: average bytes per second: %d",
	       info->fmt->average_bytes_per_second);
	masc_log_message( 0, "source_wav:        block align bytes: %d",
	       info->fmt->block_align_bytes);
	if(info->pcm_fields)
	    masc_log_message( 0, "source_wav:      PCM bits per sample: %d",
		   info->pcm_fields->bits_per_sample);
    }
    
    if (info->offset_data_start)
    {
	masc_log_message( 0, "source_wav: [data chunk]");
	masc_log_message( 0, "source_wav: data chunk begins at: %d bytes in", info->offset_data_start);
	masc_log_message( 0, "source_wav:      data chunk size: %d bytes", info->data_length);
    }
    
    if (info->label_text_header)
    {
	masc_log_message( 0, "source_wav: [label text chunk]");
	masc_log_message( 0, "source_wav:          name: %d", info->label_text_header->name);
	masc_log_message( 0, "source_wav: sample length: %d", info->label_text_header->sample_length);
	masc_log_message( 0, "source_wav:       purpose: %d", info->label_text_header->purpose);
	masc_log_message( 0, "source_wav:       country: %d", info->label_text_header->country);
	masc_log_message( 0, "source_wav:      language: %d", info->label_text_header->language);
	masc_log_message( 0, "source_wav:       dialect: %d", info->label_text_header->dialect);
	masc_log_message( 0, "source_wav:      codepage: %d", info->label_text_header->codepage);
	if (info->label_text_data) masc_log_message( 0, "source_wav: data: %s", info->label_text_data);
	
    }

    if (info->label_header)
    {
	masc_log_message( 0, "source_wav: [label chunk]");
	masc_log_message( 0, "source_wav: name: %d", info->label_header->name);
	if (info->label_data) masc_log_message( 0, "source_wav: data: %s", info->label_data);
    }

    if (info->cue_header)
    {
	masc_log_message( 0, "source_wav: [cue points chunk]");
	masc_log_message( 0, "source_wav:  number: %d", info->cue_header->num_cue_points);
	for (i=0; i<info->cue_header->num_cue_points; i++)
	{
	    masc_log_message( 0, "source_wav:   [cue %d]", i);
	    masc_log_message( 0, "source_wav:              name: %d", info->cue_points[i].name);
	    masc_log_message( 0, "source_wav:          position: %d", info->cue_points[i].position);
	    masc_log_message( 0, "source_wav:        chunk name: %c%c%c%c",
		   ((char *) &(info->cue_points[i].chunk_name))[0],
		   ((char *) &(info->cue_points[i].chunk_name))[1],
		   ((char *) &(info->cue_points[i].chunk_name))[2], 
		   ((char *) &(info->cue_points[i].chunk_name))[3]);
	
	    masc_log_message( 0, "source_wav:       chunk start: %d", info->cue_points[i].chunk_start);
	    masc_log_message( 0, "source_wav:       block start: %d", info->cue_points[i].block_start);
	    masc_log_message( 0, "source_wav:     sample offset: %d", info->cue_points[i].sample_offset);
	}
    }
    return 0;
}

const char*
wave_format_string( int format )
{
    switch (format)
    {
    case WAVE_FORMAT_UNKNOWN: return "Unknown";
    case WAVE_FORMAT_PCM: return "Microsoft PCM";
    case WAVE_FORMAT_ADPCM: return "Microsoft ADPCM";
    case WAVE_FORMAT_IEEE_FLOAT: return "IEEE Floating Point";
    case WAVE_FORMAT_ALAW: return "Microsoft A-law";
    case WAVE_FORMAT_MULAW: return "Microsoft mu-law";
    case WAVE_FORMAT_OKI_ADPCM: return "OKI ADPCM";
    case WAVE_FORMAT_IMA_ADPCM: return "IMA ADPCM";
    case WAVE_FORMAT_DIGISTD: return "Digistd";
    case WAVE_FORMAT_DIGIFIX: return "Digifix";
    case WAVE_FORMAT_DOLBY_AC2: return "Dolby AC-2";
    case WAVE_FORMAT_GSM610: return "GSM 06.10";
    case WAVE_FORMAT_ROCKWELL_ADPCM: return "Rockwell ADPCM";
    case WAVE_FORMAT_ROCKWELL_DIGITALK: return "Rockwell Digitalk";
    case WAVE_FORMAT_G721_ADPCM: return "G.721 ADPCM";
    case WAVE_FORMAT_G728_CELP: return "G.728 CELP";
    case WAVE_FORMAT_MPEG: return "MPEG";
    case WAVE_FORMAT_MPEGLAYER3: return "MPEG2 layer 3 (MP3)";
    case WAVE_FORMAT_G726_ADPCM: return "G.726 ADPCM";
    case WAVE_FORMAT_G722_ADPCM: return "G.722 ADPCM";
    default: break;
    }
    return "";
}


const char*
wave_dc_format( int format )
{
    switch (format)
    {
    case WAVE_FORMAT_UNKNOWN: return "";
    case WAVE_FORMAT_PCM: return "linear";
    case WAVE_FORMAT_ADPCM: return "ms_adpcm";
    case WAVE_FORMAT_IEEE_FLOAT: return "float32";
    case WAVE_FORMAT_ALAW: return "alaw";
    case WAVE_FORMAT_MULAW: return "ulaw";
    case WAVE_FORMAT_OKI_ADPCM: return "oki_adpcm";
    case WAVE_FORMAT_IMA_ADPCM: return "ima_adpcm";
    case WAVE_FORMAT_DIGISTD: return "digistd";
    case WAVE_FORMAT_DIGIFIX: return "digifix";
    case WAVE_FORMAT_DOLBY_AC2: return "dolby_ac2";
    case WAVE_FORMAT_GSM610: return "gsm610";
    case WAVE_FORMAT_ROCKWELL_ADPCM: return "rockwell_adpcm";
    case WAVE_FORMAT_ROCKWELL_DIGITALK: return "rockwell_digitalk";
    case WAVE_FORMAT_G721_ADPCM: return "g721";
    case WAVE_FORMAT_G728_CELP: return "g728";
    case WAVE_FORMAT_MPEG: return "MPEG Audio";
    case WAVE_FORMAT_MPEGLAYER3: return "MPEG Audio";
    case WAVE_FORMAT_G726_ADPCM: return "g726";
    case WAVE_FORMAT_G722_ADPCM: return "g722";
    default: break;
    }
    return "";
}

void*
alloc_read( FILE* fp, int32 count )
{
    void* data;
    
    if ( count == 0 )
        return 0;
    
    /* allocate space for the object */
    if ( (data = masc_rtalloc( count )) == 0)
	return 0;
    
    /* read in the data */
    if ( fread(data, count, 1, fp) != 1 )
    {
        masc_rtfree( data );
	return 0;
    }

    return data;
}

