/*
    CD-DA "Red Book" audio sound hardware handler
    Relies on the actual CD logic and reading in cdrom.c.
*/

#include "sndintrf.h"
#include "streams.h"
#include "cdrom.h"
#include "cdda.h"
#include "sound.h"
#include "driver.h"

typedef struct _cdda_info cdda_info;
struct _cdda_info
{
	sound_stream *		stream;
	cdrom_file *		disc;

	INT8				audio_playing, audio_pause, audio_ended_normally;
	UINT32				audio_lba, audio_length;

	UINT8 *				audio_cache;
	UINT32				audio_samples;
	UINT32				audio_bptr;
};

INLINE cdda_info *get_safe_token(const device_config *device)
{
	assert(device != NULL);
	assert(device->token != NULL);
	assert(device->type == SOUND);
	assert(sound_get_type(device) == SOUND_CDDA);
	return (cdda_info *)device->token;
}

#define MAX_SECTORS ( 4 )

static void get_audio_data(cdda_info *info, stream_sample_t *bufL, stream_sample_t *bufR, UINT32 samples_wanted);


/*-------------------------------------------------
    cdda_update - stream update callback
-------------------------------------------------*/

static STREAM_UPDATE( cdda_update )
{
	cdda_info *info = param;
	get_audio_data(info, &outputs[0][0], &outputs[1][0], samples);
}


/*-------------------------------------------------
    DEVICE_START( cdda ) - audio start callback
-------------------------------------------------*/

static DEVICE_START( cdda )
{
	const struct CDDAinterface *intf;
	cdda_info *info = get_safe_token(device);

	/* allocate an audio cache */
	info->audio_cache = auto_malloc( CD_MAX_SECTOR_DATA * MAX_SECTORS );

	intf = device->static_config;

	info->stream = stream_create(device, 0, 2, 44100, info, cdda_update);

	state_save_register_device_item( device, 0, info->audio_playing );
	state_save_register_device_item( device, 0, info->audio_pause );
	state_save_register_device_item( device, 0, info->audio_ended_normally );
	state_save_register_device_item( device, 0, info->audio_lba );
	state_save_register_device_item( device, 0, info->audio_length );
	state_save_register_device_item_pointer( device, 0, info->audio_cache, CD_MAX_SECTOR_DATA * MAX_SECTORS );
	state_save_register_device_item( device, 0, info->audio_samples );
	state_save_register_device_item( device, 0, info->audio_bptr );
}


/*-------------------------------------------------
    cdda_set_cdrom - set the CD-ROM file for the
    given CDDA stream
-------------------------------------------------*/

void cdda_set_cdrom(const device_config *device, void *file)
{
	cdda_info *info = get_safe_token(device);
	info->disc = (cdrom_file *)file;
}


/*-------------------------------------------------
    cdda_from_cdrom - find the CDDA stream
    that references the given CD-ROM file
-------------------------------------------------*/

const device_config *cdda_from_cdrom(running_machine *machine, void *file)
{
	const device_config *device;

	for (device = sound_first(machine->config); device != NULL; device = sound_next(device))
		if (sound_get_type(device) == SOUND_CDDA)
		{
			cdda_info *info = get_safe_token(device);
			if (info->disc == file)
				return device;
		}

	return NULL;
}


/*-------------------------------------------------
    cdda_start_audio - begin playback of a Red
    Book audio track
-------------------------------------------------*/

void cdda_start_audio(const device_config *device, UINT32 startlba, UINT32 numblocks)
{
	cdda_info *info = get_safe_token(device);

	stream_update(info->stream);
	info->audio_playing = TRUE;
	info->audio_pause = FALSE;
	info->audio_ended_normally = FALSE;
	info->audio_lba = startlba;
	info->audio_length = numblocks;
}


/*-------------------------------------------------
    cdda_stop_audio - stop playback of a Red Book
    audio track
-------------------------------------------------*/

void cdda_stop_audio(const device_config *device)
{
	cdda_info *info = get_safe_token(device);

	stream_update(info->stream);
	info->audio_playing = FALSE;
	info->audio_ended_normally = TRUE;
}


/*-------------------------------------------------
    cdda_pause_audio - pause/unpause playback of
    a Red Book audio track
-------------------------------------------------*/

void cdda_pause_audio(const device_config *device, int pause)
{
	cdda_info *info = get_safe_token(device);

	stream_update(info->stream);
	info->audio_pause = pause;
}


/*-------------------------------------------------
    cdda_get_audio_lba - returns the current LBA
    (physical sector) during Red Book playback
-------------------------------------------------*/

UINT32 cdda_get_audio_lba(const device_config *device)
{
	cdda_info *info = get_safe_token(device);

	stream_update(info->stream);
	return info->audio_lba;
}


/*-------------------------------------------------
    cdda_audio_active - returns Red Book audio
    playback status
-------------------------------------------------*/

int cdda_audio_active(const device_config *device)
{
	cdda_info *info = get_safe_token(device);

	stream_update(info->stream);
	return info->audio_playing;
}


/*-------------------------------------------------
    cdda_audio_paused - returns if Red Book
    playback is paused
-------------------------------------------------*/

int cdda_audio_paused(const device_config *device)
{
	cdda_info *info = get_safe_token(device);
	return info->audio_pause;
}


/*-------------------------------------------------
    cdda_audio_ended - returns if a Red Book
    track reached it's natural end
-------------------------------------------------*/

int cdda_audio_ended(const device_config *device)
{
	cdda_info *info = get_safe_token(device);
	return info->audio_ended_normally;
}


/*-------------------------------------------------
    get_audio_data - reads Red Book data off
    the disc if playback is in progress and
    converts it to 2 16-bit 44.1 kHz streams
-------------------------------------------------*/

static void get_audio_data(cdda_info *info, stream_sample_t *bufL, stream_sample_t *bufR, UINT32 samples_wanted)
{
	int i, sectoread, remaining;
	INT16 *audio_cache = (INT16 *) info->audio_cache;

	/* if no file, audio not playing, audio paused, or out of disc data,
       just zero fill */
	if (!info->disc || !info->audio_playing || info->audio_pause || (!info->audio_length && !info->audio_samples))
	{
		if( info->disc && info->audio_playing && !info->audio_pause && !info->audio_length )
		{
			info->audio_playing = FALSE;
			info->audio_ended_normally = TRUE;
		}

		memset(bufL, 0, sizeof(stream_sample_t)*samples_wanted);
		memset(bufR, 0, sizeof(stream_sample_t)*samples_wanted);
		return;
	}

	/* if we've got enough samples, just feed 'em out */
	if (samples_wanted <= info->audio_samples)
	{
		for (i = 0; i < samples_wanted; i++)
		{
			*bufL++ = audio_cache[ info->audio_bptr++ ];
			*bufR++ = audio_cache[ info->audio_bptr++ ];
		}

		info->audio_samples -= samples_wanted;
		return;
	}

	/* we don't have enough, so first feed what we've got */
	for (i = 0; i < info->audio_samples; i++)
	{
		*bufL++ = audio_cache[ info->audio_bptr++ ];
		*bufR++ = audio_cache[ info->audio_bptr++ ];
	}

	/* remember how much left for later */
	remaining = samples_wanted - info->audio_samples;

	/* reset the buffer and get what we can from the disc */
	info->audio_samples = 0;
	if (info->audio_length >= MAX_SECTORS)
	{
		sectoread = MAX_SECTORS;
	}
	else
	{
		sectoread = info->audio_length;
	}

	for (i = 0; i < sectoread; i++)
	{
		cdrom_read_data(info->disc, info->audio_lba, &info->audio_cache[CD_MAX_SECTOR_DATA*i], CD_TRACK_AUDIO);

		info->audio_lba++;
	}

	info->audio_samples = (CD_MAX_SECTOR_DATA*sectoread)/4;
	info->audio_length -= sectoread;

	/* CD-DA data on the disc is big-endian, flip if we're not */
	if (ENDIANNESS_NATIVE == ENDIANNESS_LITTLE)
	{
		for( i = 0; i < info->audio_samples * 2; i++ )
		{
			audio_cache[ i ] = BIG_ENDIANIZE_INT16( audio_cache[ i ] );
		}
	}

	/* reset feedout ptr */
	info->audio_bptr = 0;

	/* we've got data, feed it out by calling ourselves recursively */
	get_audio_data(info, bufL, bufR, remaining);
}



/**************************************************************************
 * Generic get_info
 **************************************************************************/

DEVICE_GET_INFO( cdda )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(cdda_info);				break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME( cdda );		break;
		case DEVINFO_FCT_STOP:							/* nothing */								break;
		case DEVINFO_FCT_RESET:							/* nothing */								break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "CD/DA");					break;
		case DEVINFO_STR_FAMILY:					strcpy(info->s, "CD Audio");				break;
		case DEVINFO_STR_VERSION:					strcpy(info->s, "1.0");						break;
		case DEVINFO_STR_SOURCE_FILE:						strcpy(info->s, __FILE__);					break;
		case DEVINFO_STR_CREDITS:					strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
	}
}

