/*************************************************************************
ldcore.c
Private core laserdisc player implementation.
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
*************************************************************************/
#include "driver.h"
#include "ldcore.h"
#include "avcomp.h"
#include "streams.h"
#include "vbiparse.h"
#include "config.h"
#include "sound/custom.h"
/***************************************************************************
DEBUGGING
***************************************************************************/
#define LOG_SLIDER 0
/***************************************************************************
CONSTANTS
***************************************************************************/
/* we simulate extra lead-in and lead-out tracks */
#define VIRTUAL_LEAD_IN_TRACKS 200
#define MAX_TOTAL_TRACKS 54000
#define VIRTUAL_LEAD_OUT_TRACKS 200
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
/* video frame data */
typedef struct _frame_data frame_data;
struct _frame_data
{
bitmap_t * bitmap; /* cached bitmap */
bitmap_t * visbitmap; /* wrapper around bitmap with only visible lines */
UINT8 numfields; /* number of fields in this frame */
INT32 lastfield; /* last absolute field number */
};
/* core-specific data */
struct _ldcore_data
{
/* general config */
laserdisc_config config; /* copy of the inline config */
ldplayer_interface intf; /* interface to the player */
/* disc parameters */
chd_file * disc; /* handle to the disc itself */
UINT8 * vbidata; /* pointer to precomputed VBI data */
int width; /* width of video */
int height; /* height of video */
UINT32 fps_times_1million; /* frame rate of video */
int samplerate; /* audio samplerate */
chd_error readresult; /* result of the most recent read */
UINT32 chdtracks; /* number of tracks in the CHD */
av_codec_decompress_config avconfig; /* decompression configuration */
/* core states */
UINT8 audiosquelch; /* audio squelch state: bit 0 = audio 1, bit 1 = audio 2 */
UINT8 videosquelch; /* video squelch state: bit 0 = on/off */
UINT8 fieldnum; /* field number (0 or 1) */
INT32 curtrack; /* current track at this end of this vsync */
UINT32 maxtrack; /* maximum track number */
attoseconds_t attospertrack; /* attoseconds per track, or 0 if not moving */
attotime sliderupdate; /* time of last slider update */
/* video data */
frame_data frame[3]; /* circular list of frames */
UINT8 videoindex; /* index of the current video buffer */
bitmap_t videotarget; /* fake target bitmap for decompression */
bitmap_t * emptyframe; /* blank frame */
/* audio data */
INT16 * audiobuffer[2]; /* buffer for audio samples */
UINT32 audiobufsize; /* size of buffer */
UINT32 audiobufin; /* input index */
UINT32 audiobufout; /* output index */
UINT32 audiocursamples; /* current samples this track */
UINT32 audiomaxsamples; /* maximum samples per track */
int audiocustom; /* custom sound index */
/* metadata */
vbi_metadata metadata[2]; /* metadata parsed from the stream, for each field */
/* I/O data */
UINT8 datain; /* current input data value */
UINT8 linein[LASERDISC_INPUT_LINES]; /* current input line state */
UINT8 dataout; /* current output data value */
UINT8 lineout[LASERDISC_OUTPUT_LINES]; /* current output line state */
/* video updating */
UINT8 videoenable; /* is video enabled? */
render_texture * videotex; /* texture for the video */
UINT8 overenable; /* is the overlay enabled? */
bitmap_t * overbitmap[2]; /* overlay bitmaps */
int overindex; /* index of the overlay bitmap */
render_texture * overtex; /* texture for the overlay */
};
/* sound callback info */
typedef struct _sound_token sound_token;
struct _sound_token
{
sound_stream * stream;
laserdisc_state * ld;
};
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
/* generic helper functions */
static TIMER_CALLBACK( perform_player_update );
static void read_track_data(laserdisc_state *ld);
static void process_track_data(const device_config *device);
//static void render_display(UINT16 *videodata, UINT32 rowpixels, UINT32 width, int frame);
static void *custom_start(int clock, const custom_sound_interface *config);
static void custom_stream_callback(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int samples);
static void configuration_load(running_machine *machine, int config_type, xml_data_node *parentnode);
static void configuration_save(running_machine *machine, int config_type, xml_data_node *parentnode);
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
static const ldplayer_interface *player_interfaces[] =
{
// &pr7820_interface,
&pr8210_interface,
&simutrek_interface,
&ldv1000_interface,
// &ldp1450_interface,
// &vp932_interface,
&vp931_interface
};
const custom_sound_interface laserdisc_custom_interface =
{
custom_start
};
static const UINT8 numberfont[10][8] =
{
{
0x30, // ..xx....
0x48, // .x..x...
0x84, // x....x..
0x84, // x....x..
0x84, // x....x..
0x48, // .x..x...
0x30 // ..xx....
},
{
0x10, // ...x....
0x30, // ..xx....
0x50, // .x.x....
0x10, // ...x....
0x10, // ...x....
0x10, // ...x....
0x7c // .xxxxx..
},
{
0x78, // .xxxx...
0x84, // x....x..
0x04, // .....x..
0x38, // ..xxx...
0x40, // .x......
0x80, // x.......
0xfc // xxxxxx..
},
{
0x78, // .xxxx...
0x84, // x....x..
0x04, // .....x..
0x38, // ..xxx...
0x04, // .....x..
0x84, // x....x..
0x78 // .xxxx...
},
{
0x08, // ....x...
0x18, // ...xx...
0x28, // ..x.x...
0x48, // .x..x...
0xfc, // xxxxxx..
0x08, // ....x...
0x08 // ....x...
},
{
0xfc, // xxxxxx..
0x80, // x.......
0x80, // x.......
0xf8, // xxxxx...
0x04, // .....x..
0x84, // x....x..
0x78 // .xxxx...
},
{
0x38, // ..xxx...
0x40, // .x......
0x80, // x.......
0xf8, // xxxxx...
0x84, // x....x..
0x84, // x....x..
0x78 // .xxxx...
},
{
0xfc, // xxxxxx..
0x04, // .....x..
0x08, // ....x...
0x10, // ...x....
0x20, // ..x.....
0x20, // ..x.....
0x20 // ..x.....
},
{
0x78, // .xxxx...
0x84, // x....x..
0x84, // x....x..
0x78, // .xxxx...
0x84, // x....x..
0x84, // x....x..
0x78 // .xxxx...
},
{
0x78, // .xxxx..
0x84, // x....x.
0x84, // x....x.
0x7c, // .xxxxx.
0x04, // .....x.
0x08, // ....x..
0x70, // .xxx...
}
};
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
get_safe_token - makes sure that the passed
in device is, in fact, a laserdisc device
-------------------------------------------------*/
INLINE laserdisc_state *get_safe_token(const device_config *device)
{
assert(device != NULL);
assert(device->token != NULL);
assert(device->type == LASERDISC);
return (laserdisc_state *)device->token;
}
/*-------------------------------------------------
update_audio - update the audio stream to the
current time
-------------------------------------------------*/
INLINE void update_audio(ldcore_data *ldcore)
{
if (ldcore->audiocustom != -1)
{
sound_token *token = custom_get_token(ldcore->audiocustom);
stream_update(token->stream);
}
}
/*-------------------------------------------------
add_and_clamp_track - add a delta to the
current track and clamp to minimum/maximum
values
-------------------------------------------------*/
INLINE void add_and_clamp_track(ldcore_data *ldcore, INT32 delta)
{
ldcore->curtrack += delta;
ldcore->curtrack = MAX(ldcore->curtrack, 1);
ldcore->curtrack = MIN(ldcore->curtrack, ldcore->maxtrack - 1);
}
/*-------------------------------------------------
fillbitmap_yuy16 - fill a YUY16 bitmap with a
given color pattern
-------------------------------------------------*/
INLINE void fillbitmap_yuy16(bitmap_t *bitmap, UINT8 yval, UINT8 cr, UINT8 cb)
{
UINT16 color0 = (yval << 8) | cb;
UINT16 color1 = (yval << 8) | cr;
int x, y;
/* write 32 bits of color (2 pixels at a time) */
for (y = 0; y < bitmap->height; y++)
{
UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
for (x = 0; x < bitmap->width / 2; x++)
{
*dest++ = color0;
*dest++ = color1;
}
}
}
/***************************************************************************
GENERIC IMPLEMENTATION
***************************************************************************/
/*-------------------------------------------------
update_slider_pos - based on the current
speed and elapsed time, update the current
track position
-------------------------------------------------*/
static void update_slider_pos(ldcore_data *ldcore, attotime curtime)
{
/* if not moving, update to now */
if (ldcore->attospertrack == 0)
ldcore->sliderupdate = curtime;
/* otherwise, compute the number of tracks covered */
else
{
attoseconds_t delta = attotime_to_attoseconds(attotime_sub(curtime, ldcore->sliderupdate));
INT32 tracks_covered;
/* determine how many tracks we covered and advance */
if (ldcore->attospertrack >= 0)
{
tracks_covered = delta / ldcore->attospertrack;
add_and_clamp_track(ldcore, tracks_covered);
if (tracks_covered != 0)
ldcore->sliderupdate = attotime_add_attoseconds(ldcore->sliderupdate, tracks_covered * ldcore->attospertrack);
}
else
{
tracks_covered = delta / -ldcore->attospertrack;
add_and_clamp_track(ldcore, -tracks_covered);
if (tracks_covered != 0)
ldcore->sliderupdate = attotime_add_attoseconds(ldcore->sliderupdate, tracks_covered * -ldcore->attospertrack);
}
}
}
/*-------------------------------------------------
vblank_state_changed - called on each state
change of the VBLANK signal
-------------------------------------------------*/
static void vblank_state_changed(const device_config *screen, void *param, int vblank_state)
{
const device_config *device = param;
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
attotime curtime = timer_get_time();
/* update current track based on slider speed */
update_slider_pos(ldcore, curtime);
/* on rising edge, process previously-read frame and inform the player */
if (vblank_state)
{
/* call the player's VSYNC callback */
if (ldcore->intf.vsync != NULL)
(*ldcore->intf.vsync)(ld, &ldcore->metadata[ldcore->fieldnum], ldcore->fieldnum, curtime);
/* set a timer to begin fetching the next frame just before the VBI data would be fetched */
timer_set(video_screen_get_time_until_pos(screen, 16*2, 0), ld, 0, perform_player_update);
}
}
/*-------------------------------------------------
vblank_state_changed - called on each state
change of the VBLANK signal
-------------------------------------------------*/
static TIMER_CALLBACK( perform_player_update )
{
laserdisc_state *ld = ptr;
ldcore_data *ldcore = ld->core;
attotime curtime = timer_get_time();
/* wait for previous read and decode to finish */
process_track_data(ld->device);
/* update current track based on slider speed */
update_slider_pos(ldcore, curtime);
/* update the state */
if (ldcore->intf.update != NULL)
add_and_clamp_track(ldcore, (*ldcore->intf.update)(ld, &ldcore->metadata[ldcore->fieldnum], ldcore->fieldnum, curtime));
/* flush any audio before we read more */
update_audio(ldcore);
/* start reading the track data for the next round */
ldcore->fieldnum ^= 1;
read_track_data(ld);
}
/*-------------------------------------------------
laserdisc_data_w - write data to the given
laserdisc player
-------------------------------------------------*/
void laserdisc_data_w(const device_config *device, UINT8 data)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
UINT8 prev = ldcore->datain;
ldcore->datain = data;
/* call through to the player-specific write handler */
if (ldcore->intf.writedata != NULL)
(*ldcore->intf.writedata)(ld, prev, data);
}
/*-------------------------------------------------
laserdisc_line_w - control an input line
-------------------------------------------------*/
void laserdisc_line_w(const device_config *device, UINT8 line, UINT8 newstate)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
assert(line < LASERDISC_INPUT_LINES);
assert(newstate == ASSERT_LINE || newstate == CLEAR_LINE || newstate == PULSE_LINE);
/* assert */
if (newstate == ASSERT_LINE || newstate == PULSE_LINE)
{
if (ldcore->linein[line] != ASSERT_LINE)
{
/* call through to the player-specific line handler */
if (ldcore->intf.writeline[line] != NULL)
(*ldcore->intf.writeline[line])(ld, CLEAR_LINE, ASSERT_LINE);
}
ldcore->linein[line] = ASSERT_LINE;
}
/* deassert */
if (newstate == CLEAR_LINE || newstate == PULSE_LINE)
{
if (ldcore->linein[line] != CLEAR_LINE)
{
/* call through to the player-specific line handler */
if (ldcore->intf.writeline[line] != NULL)
(*ldcore->intf.writeline[line])(ld, ASSERT_LINE, CLEAR_LINE);
}
ldcore->linein[line] = CLEAR_LINE;
}
}
/*-------------------------------------------------
laserdisc_data_r - return the current
data byte
-------------------------------------------------*/
UINT8 laserdisc_data_r(const device_config *device)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
UINT8 result = ldcore->dataout;
/* call through to the player-specific data handler */
if (ldcore->intf.readdata != NULL)
result = (*ldcore->intf.readdata)(ld);
return result;
}
/*-------------------------------------------------
laserdisc_line_r - return the current state
of an output line
-------------------------------------------------*/
UINT8 laserdisc_line_r(const device_config *device, UINT8 line)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
UINT8 result;
assert(line < LASERDISC_OUTPUT_LINES);
result = ldcore->lineout[line];
/* call through to the player-specific data handler */
if (ldcore->intf.readline[line] != NULL)
result = (*ldcore->intf.readline[line])(ld);
return result;
}
/*-------------------------------------------------
laserdisc_get_video - return the current
video frame; return TRUE if valid or FALSE
if video off
-------------------------------------------------*/
int laserdisc_get_video(const device_config *device, bitmap_t **bitmap)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
frame_data *frame;
/* determine the most recent live set of frames */
frame = &ldcore->frame[ldcore->videoindex];
if (frame->numfields < 2)
frame = &ldcore->frame[(ldcore->videoindex + ARRAY_LENGTH(ldcore->frame) - 1) % ARRAY_LENGTH(ldcore->frame)];
/* if no video present, return the empty frame */
if (ldcore->videosquelch || frame->numfields < 2)
{
*bitmap = ldcore->emptyframe;
return FALSE;
}
else
{
*bitmap = frame->visbitmap;
return TRUE;
}
}
/*-------------------------------------------------
laserdisc_get_field_code - return raw field
information read from the disc
-------------------------------------------------*/
UINT32 laserdisc_get_field_code(const device_config *device, UINT32 code)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
int field = ldcore->fieldnum;
switch (code)
{
case LASERDISC_CODE_WHITE_FLAG:
return ldcore->metadata[field].white;
case LASERDISC_CODE_LINE16:
return ldcore->metadata[field].line16;
case LASERDISC_CODE_LINE17:
return ldcore->metadata[field].line17;
case LASERDISC_CODE_LINE18:
return ldcore->metadata[field].line18;
case LASERDISC_CODE_LINE1718:
return ldcore->metadata[field].line1718;
}
return 0;
}
/***************************************************************************
PLAYER-TO-CORE INTERFACES
***************************************************************************/
/*-------------------------------------------------
ldcore_get_safe_token - return a token with
type checking from a device
-------------------------------------------------*/
laserdisc_state *ldcore_get_safe_token(const device_config *device)
{
return get_safe_token(device);
}
/*-------------------------------------------------
ldcore_set_audio_squelch - set the left/right
audio squelch states
-------------------------------------------------*/
void ldcore_set_audio_squelch(laserdisc_state *ld, UINT8 squelchleft, UINT8 squelchright)
{
update_audio(ld->core);
ld->core->audiosquelch = (squelchleft ? 1 : 0) | (squelchright ? 2 : 0);
}
/*-------------------------------------------------
ldcore_set_video_squelch - set the video
squelch state
-------------------------------------------------*/
void ldcore_set_video_squelch(laserdisc_state *ld, UINT8 squelch)
{
ld->core->videosquelch = squelch;
}
/*-------------------------------------------------
ldcore_set_slider_speed - dynamically change
the slider speed
-------------------------------------------------*/
void ldcore_set_slider_speed(laserdisc_state *ld, INT32 tracks_per_vsync)
{
ldcore_data *ldcore = ld->core;
attotime vsyncperiod = video_screen_get_frame_period(ld->screen);
update_slider_pos(ldcore, timer_get_time());
/* if 0, set the time to 0 */
if (tracks_per_vsync == 0)
ldcore->attospertrack = 0;
/* positive values store positive times */
else if (tracks_per_vsync > 0)
ldcore->attospertrack = attotime_to_attoseconds(attotime_div(vsyncperiod, tracks_per_vsync));
/* negative values store negative times */
else
ldcore->attospertrack = -attotime_to_attoseconds(attotime_div(vsyncperiod, -tracks_per_vsync));
if (LOG_SLIDER)
printf("Slider speed = %d\n", tracks_per_vsync);
}
/*-------------------------------------------------
ldcore_advance_slider - advance the slider by
a certain number of tracks
-------------------------------------------------*/
void ldcore_advance_slider(laserdisc_state *ld, INT32 numtracks)
{
ldcore_data *ldcore = ld->core;
update_slider_pos(ldcore, timer_get_time());
add_and_clamp_track(ldcore, numtracks);
if (LOG_SLIDER)
printf("Advance by %d\n", numtracks);
}
/*-------------------------------------------------
ldcore_get_slider_position - get the current
slider position
-------------------------------------------------*/
slider_position ldcore_get_slider_position(laserdisc_state *ld)
{
ldcore_data *ldcore = ld->core;
/* update the slider position first */
update_slider_pos(ldcore, timer_get_time());
/* return the status */
if (ldcore->curtrack == 1)
return SLIDER_MINIMUM;
else if (ldcore->curtrack < VIRTUAL_LEAD_IN_TRACKS)
return SLIDER_VIRTUAL_LEADIN;
else if (ldcore->curtrack < VIRTUAL_LEAD_IN_TRACKS + ldcore->chdtracks)
return SLIDER_CHD;
else if (ldcore->curtrack < VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS)
return SLIDER_OUTSIDE_CHD;
else if (ldcore->curtrack < ldcore->maxtrack - 1)
return SLIDER_VIRTUAL_LEADOUT;
else
return SLIDER_MAXIMUM;
}
/***************************************************************************
GENERIC HELPER FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
ldcore_generic_update - generically update in
a way that works for most situations
-------------------------------------------------*/
INT32 ldcore_generic_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime, ldplayer_state *newstate)
{
INT32 advanceby = 0;
int frame;
/* start by assuming the state doesn't change */
*newstate = ld->state;
/* handle things based on the state */
switch (ld->state.state)
{
case LDSTATE_EJECTING:
/* when time expires, switch to the ejected state */
if (attotime_compare(curtime, ld->state.endtime) >= 0)
newstate->state = LDSTATE_EJECTED;
break;
case LDSTATE_EJECTED:
/* do nothing */
break;
case LDSTATE_PARKED:
/* do nothing */
break;
case LDSTATE_LOADING:
/* when time expires, switch to the spinup state */
if (attotime_compare(curtime, ld->state.endtime) >= 0)
newstate->state = LDSTATE_SPINUP;
advanceby = -GENERIC_SEARCH_SPEED;
break;
case LDSTATE_SPINUP:
/* when time expires, switch to the playing state */
if (attotime_compare(curtime, ld->state.endtime) >= 0)
newstate->state = LDSTATE_PLAYING;
advanceby = -GENERIC_SEARCH_SPEED;
break;
case LDSTATE_PAUSING:
/* if he hit the start of a frame, switch to paused state */
if (is_start_of_frame(vbi))
{
newstate->state = LDSTATE_PAUSED;
newstate->param = fieldnum;
}
/* else advance until we hit it */
else if (fieldnum == 1)
advanceby = 1;
break;
case LDSTATE_PAUSED:
/* if we paused on field 1, we must flip back and forth */
if (ld->state.param == 1)
advanceby = (fieldnum == 1) ? 1 : -1;
break;
case LDSTATE_PLAYING:
/* if we hit the target frame, switch to the paused state */
if (ld->state.param > 0 && is_start_of_frame(vbi) && frame_from_metadata(vbi) == ld->state.param)
{
newstate->state = LDSTATE_PAUSED;
newstate->param = fieldnum;
}
/* otherwise after the second field of each frame */
else if (fieldnum == 1)
advanceby = 1;
break;
case LDSTATE_PLAYING_SLOW_REVERSE:
/* after the second field of each frame, see if we need to advance */
if (fieldnum == 1 && ++ld->state.substate > ld->state.param)
{
advanceby = -1;
ld->state.substate = 0;
}
break;
case LDSTATE_PLAYING_SLOW_FORWARD:
/* after the second field of each frame, see if we need to advance */
if (fieldnum == 1 && ++ld->state.substate > ld->state.param)
{
advanceby = 1;
ld->state.substate = 0;
}
break;
case LDSTATE_PLAYING_FAST_REVERSE:
/* advance after the second field of each frame */
if (fieldnum == 1)
advanceby = -ld->state.param;
break;
case LDSTATE_PLAYING_FAST_FORWARD:
/* advance after the second field of each frame */
if (fieldnum == 1)
advanceby = ld->state.param;
break;
case LDSTATE_SCANNING:
/* advance after the second field of each frame */
if (fieldnum == 1)
advanceby = ld->state.param >> 8;
/* after we run out of vsyncs, revert to the saved state */
if (++ld->state.substate >= (ld->state.param & 0xff))
*newstate = ld->savestate;
break;
case LDSTATE_STEPPING_REVERSE:
/* wait for the first field of the frame and then leap backwards */
if (is_start_of_frame(vbi))
{
advanceby = (fieldnum == 1) ? -1 : -2;
newstate->state = LDSTATE_PAUSING;
}
break;
case LDSTATE_STEPPING_FORWARD:
/* wait for the first field of the frame and then switch to pausing state */
if (is_start_of_frame(vbi))
newstate->state = LDSTATE_PAUSING;
break;
case LDSTATE_SEEKING:
/* if we're in the final state, look for a matching frame and pause there */
frame = frame_from_metadata(vbi);
if (ld->state.substate == 1 && is_start_of_frame(vbi) && frame == ld->state.param)
{
newstate->state = LDSTATE_PAUSED;
newstate->param = fieldnum;
}
/* otherwise, if we got frame data from the VBI, update our seeking logic */
else if (ld->state.substate == 0 && frame != FRAME_NOT_PRESENT)
{
INT32 delta = (ld->state.param - 2) - frame;
/* if we're within a couple of frames, just play until we hit it */
if (delta >= 0 && delta <= 2)
ld->state.substate++;
/* otherwise, compute the delta assuming 1:1 track to frame; this will correct eventually */
else
{
if (delta < 0)
delta--;
advanceby = delta;
advanceby = MIN(advanceby, GENERIC_SEARCH_SPEED);
advanceby = MAX(advanceby, -GENERIC_SEARCH_SPEED);
}
}
/* otherwise, keep advancing until we know what's up */
else
{
if (fieldnum == 1)
advanceby = 1;
}
break;
}
return advanceby;
}
/*-------------------------------------------------
read_track_data - read and process data for
a particular video track
-------------------------------------------------*/
static void read_track_data(laserdisc_state *ld)
{
ldcore_data *ldcore = ld->core;
UINT32 tracknum = ldcore->curtrack;
UINT32 fieldnum = ldcore->fieldnum;
UINT32 curfield = tracknum * 2 + fieldnum;
frame_data *frame;
UINT32 vbiframe;
UINT32 readhunk;
INT32 chdtrack;
frame = &ldcore->frame[ldcore->videoindex];
/* special cases for playing forward */
if (curfield == frame->lastfield + 1)
{
/* if the previous field had a frame number, force the new field to pair with the previous one */
if ((ldcore->metadata[fieldnum ^ 1].line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
frame->numfields = 1;
/* if the twice-previous field was a frame number, and we've moved one since then, consider this one done */
else if (frame->numfields >= 2 && (ldcore->metadata[fieldnum].line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
{
ldcore->videoindex = (ldcore->videoindex + 1) % ARRAY_LENGTH(ldcore->frame);
frame = &ldcore->frame[ldcore->videoindex];
frame->numfields = 0;
}
}
/* all other cases */
else
{
/* otherwise, keep the frames in sync with the absolute field numbers */
if (frame->numfields == 2 && fieldnum != 0)
frame->numfields--;
/* if we already have both fields on the current videoindex, advance */
else if (frame->numfields >= 2)
{
ldcore->videoindex = (ldcore->videoindex + 1) % ARRAY_LENGTH(ldcore->frame);
frame = &ldcore->frame[ldcore->videoindex];
frame->numfields = 0;
}
}
/* if we're squelched, reset the frame counter */
if (ldcore->videosquelch)
frame->numfields = 0;
/* remember the last field number */
frame->lastfield = tracknum * 2 + fieldnum;
/* set the video target information */
ldcore->videotarget = *frame->bitmap;
ldcore->videotarget.base = BITMAP_ADDR16(&ldcore->videotarget, fieldnum, 0);
ldcore->videotarget.height /= 2;
ldcore->videotarget.rowpixels *= 2;
ldcore->avconfig.video = &ldcore->videotarget;
/* set the audio target information */
if (ldcore->audiobufin + ldcore->audiomaxsamples <= ldcore->audiobufsize)
{
/* if we can fit without wrapping, just read the data directly */
ldcore->avconfig.audio[0] = &ldcore->audiobuffer[0][ldcore->audiobufin];
ldcore->avconfig.audio[1] = &ldcore->audiobuffer[1][ldcore->audiobufin];
}
else
{
/* otherwise, read to the beginning of the buffer */
ldcore->avconfig.audio[0] = &ldcore->audiobuffer[0][0];
ldcore->avconfig.audio[1] = &ldcore->audiobuffer[1][0];
}
/* override if we're not decoding */
ldcore->avconfig.maxsamples = ldcore->audiomaxsamples;
ldcore->avconfig.actsamples = &ldcore->audiocursamples;
ldcore->audiocursamples = 0;
/* compute the chdhunk number we are going to read */
chdtrack = tracknum - 1 - VIRTUAL_LEAD_IN_TRACKS;
chdtrack = MAX(chdtrack, 0);
chdtrack = MIN(chdtrack, ldcore->chdtracks - 1);
readhunk = chdtrack * 2 + fieldnum;
/* set the VBI data for the new field from our precomputed data */
if (ldcore->vbidata != NULL)
vbi_metadata_unpack(&ldcore->metadata[fieldnum], &vbiframe, &ldcore->vbidata[readhunk * VBI_PACKED_BYTES]);
/* configure the codec and then read */
ldcore->readresult = CHDERR_FILE_NOT_FOUND;
if (ldcore->disc != NULL && !ldcore->videosquelch)
{
ldcore->readresult = chd_codec_config(ldcore->disc, AV_CODEC_DECOMPRESS_CONFIG, &ldcore->avconfig);
if (ldcore->readresult == CHDERR_NONE)
ldcore->readresult = chd_read_async(ldcore->disc, readhunk, NULL);
}
}
/*-------------------------------------------------
process_track_data - process data from a
track after it has been read
-------------------------------------------------*/
static void process_track_data(const device_config *device)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
/* wait for the async operation to complete */
if (ldcore->readresult == CHDERR_OPERATION_PENDING)
ldcore->readresult = chd_async_complete(ldcore->disc);
/* remove the video if we had an error */
if (ldcore->readresult != CHDERR_NONE)
ldcore->avconfig.video = NULL;
/* count the field as read if we are successful */
if (ldcore->avconfig.video != NULL)
ldcore->frame[ldcore->videoindex].numfields++;
/* render the display if present */
// if (ldcore->display && ldcore->avconfig.video != NULL)
// render_display((UINT16 *)ldcore->avconfig.video->base, ldcore->avconfig.video->rowpixels, ldcore->avconfig.video->width, ldcore->last_frame);
/* pass the audio to the callback */
if (ldcore->config.audio != NULL)
(*ldcore->config.audio)(device, ldcore->samplerate, ldcore->audiocursamples, ldcore->avconfig.audio[0], ldcore->avconfig.audio[1]);
/* shift audio data if we read it into the beginning of the buffer */
if (ldcore->audiocursamples != 0 && ldcore->audiobufin != 0)
{
int chnum;
/* iterate over channels */
for (chnum = 0; chnum < 2; chnum++)
if (ldcore->avconfig.audio[chnum] == &ldcore->audiobuffer[chnum][0])
{
int samplesleft;
/* move data to the end */
samplesleft = ldcore->audiobufsize - ldcore->audiobufin;
samplesleft = MIN(samplesleft, ldcore->audiocursamples);
memmove(&ldcore->audiobuffer[chnum][ldcore->audiobufin], &ldcore->audiobuffer[chnum][0], samplesleft * 2);
/* shift data at the beginning */
if (samplesleft < ldcore->audiocursamples)
memmove(&ldcore->audiobuffer[chnum][0], &ldcore->audiobuffer[chnum][samplesleft], (ldcore->audiocursamples - samplesleft) * 2);
}
}
/* update the input buffer pointer */
ldcore->audiobufin = (ldcore->audiobufin + ldcore->audiocursamples) % ldcore->audiobufsize;
}
/*-------------------------------------------------
render_display - draw the frame display
-------------------------------------------------*/
#ifdef UNUSED_FUNCTION
static void render_display(UINT16 *videodata, UINT32 rowpixels, UINT32 width, int frame)
{
const int xscale = 4, yscale = 2;
char buffer[10];
int x = width / 10;
int y = 50;
int ch;
int delta;
/* do nothing if no data */
if (videodata == NULL)
return;
/* convert to a character string and render */
sprintf(buffer, "%5d", frame);
/* iterate over 5 positions: (-1,-1), (-1,1), (1,-1), (1,1) and (0,0) */
/* render all in black except the last one */
for (delta = 0; delta < 5; delta++)
{
int color = (delta < 4) ? 0x0080 : 0xff80;
int dx = (delta < 4) ? ((delta & 1) ? -1 : 1) : 0;
int dy = (delta < 4) ? ((delta & 2) ? -1 : 1) : 0;
/* iterate over 5 characters */
for (ch = 0; ch < 5; ch++)
if (buffer[ch] >= '0' && buffer[ch] <= '9')
{
const UINT8 *fontdata = &numberfont[buffer[ch] - '0'][0];
int cy, cx;
/* iterate over the rows of the character */
for (cy = 0; cy < 8; cy++)
{
UINT8 bits = *fontdata++;
/* and over the columns */
for (cx = 0; cx < 8; cx++)
if (bits & (0x80 >> cx))
{
int ix, iy;
/* fill in an xscale x yscale pixel */
for (iy = 0; iy < yscale; iy++)
for (ix = 0; ix < xscale; ix++)
videodata[(y + cy * yscale + iy + dy) * rowpixels + (x + (ch * 9 + cx) * xscale + ix + dx)] = color;
}
}
}
}
}
#endif
/*-------------------------------------------------
custom_start - custom audio start
for laserdiscs
-------------------------------------------------*/
static void *custom_start(int clock, const custom_sound_interface *config)
{
sound_token *token = auto_malloc(sizeof(*token));
token->stream = stream_create(0, 2, 48000, token, custom_stream_callback);
token->ld = NULL;
return token;
}
/*-------------------------------------------------
custom_stream_callback - audio streamer
for laserdiscs
-------------------------------------------------*/
static void custom_stream_callback(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
sound_token *token = param;
laserdisc_state *ld = token->ld;
ldcore_data *ldcore = ld->core;
stream_sample_t *dst0 = outputs[0];
stream_sample_t *dst1 = outputs[1];
INT16 leftand, rightand;
int samples_avail = 0;
/* compute AND values based on the squelch */
leftand = (ldcore->audiosquelch & 1) ? 0x0000 : 0xffff;
rightand = (ldcore->audiosquelch & 2) ? 0x0000 : 0xffff;
/* see if we have enough samples to fill the buffer; if not, drop out */
if (ld != NULL)
{
samples_avail = ldcore->audiobufin - ldcore->audiobufout;
if (samples_avail < 0)
samples_avail += ldcore->audiobufsize;
}
/* if no attached ld, just clear the buffers */
if (samples_avail < samples)
{
memset(dst0, 0, samples * sizeof(dst0[0]));
memset(dst1, 0, samples * sizeof(dst1[0]));
}
/* otherwise, stream from our buffer */
else
{
INT16 *buffer0 = ldcore->audiobuffer[0];
INT16 *buffer1 = ldcore->audiobuffer[1];
int sampout = ldcore->audiobufout;
/* copy samples, clearing behind us as we go */
while (sampout != ldcore->audiobufin && samples-- > 0)
{
*dst0++ = buffer0[sampout] & leftand;
*dst1++ = buffer1[sampout] & rightand;
buffer0[sampout] = 0;
buffer1[sampout] = 0;
sampout++;
if (sampout >= ldcore->audiobufsize)
sampout = 0;
}
ldcore->audiobufout = sampout;
/* clear out the rest of the buffer */
if (samples > 0)
{
int sampout = (ldcore->audiobufout == 0) ? ldcore->audiobufsize - 1 : ldcore->audiobufout - 1;
stream_sample_t fill0 = buffer0[sampout] & leftand;
stream_sample_t fill1 = buffer1[sampout] & rightand;
while (samples-- > 0)
{
*dst0++ = fill0;
*dst1++ = fill1;
}
}
}
}
/***************************************************************************
CONFIG SETTINGS ACCESS
***************************************************************************/
/*-------------------------------------------------
configuration_load - read and apply data from
the configuration file
-------------------------------------------------*/
static void configuration_load(running_machine *machine, int config_type, xml_data_node *parentnode)
{
xml_data_node *overnode;
xml_data_node *ldnode;
/* we only care about game files */
if (config_type != CONFIG_TYPE_GAME)
return;
/* might not have any data */
if (parentnode == NULL)
return;
/* iterate over overlay nodes */
for (ldnode = xml_get_sibling(parentnode->child, "device"); ldnode != NULL; ldnode = xml_get_sibling(ldnode->next, "device"))
{
const char *devtag = xml_get_attribute_string(ldnode, "tag", "");
const device_config *device = device_list_find_by_tag(machine->config->devicelist, LASERDISC, devtag);
if (device != NULL)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
/* handle the overlay node */
overnode = xml_get_sibling(ldnode->child, "overlay");
if (overnode != NULL)
{
/* fetch positioning controls */
ldcore->config.overposx = xml_get_attribute_float(overnode, "hoffset", ldcore->config.overposx);
ldcore->config.overscalex = xml_get_attribute_float(overnode, "hstretch", ldcore->config.overscalex);
ldcore->config.overposy = xml_get_attribute_float(overnode, "voffset", ldcore->config.overposy);
ldcore->config.overscaley = xml_get_attribute_float(overnode, "vstretch", ldcore->config.overscaley);
}
}
}
}
/*-------------------------------------------------
configuration_save - save data to the
configuration file
-------------------------------------------------*/
static void configuration_save(running_machine *machine, int config_type, xml_data_node *parentnode)
{
const device_config *device;
/* we only care about game files */
if (config_type != CONFIG_TYPE_GAME)
return;
/* iterate over disc devices */
for (device = device_list_first(machine->config->devicelist, LASERDISC); device != NULL; device = device_list_next(device, LASERDISC))
{
laserdisc_config *origconfig = device->inline_config;
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
xml_data_node *overnode;
xml_data_node *ldnode;
/* create a node */
ldnode = xml_add_child(parentnode, "device", NULL);
if (ldnode != NULL)
{
int changed = FALSE;
/* output the basics */
xml_set_attribute(ldnode, "tag", device->tag);
/* add an overlay node */
overnode = xml_add_child(ldnode, "overlay", NULL);
if (overnode != NULL)
{
/* output the positioning controls */
if (ldcore->config.overposx != origconfig->overposx)
{
xml_set_attribute_float(overnode, "hoffset", ldcore->config.overposx);
changed = TRUE;
}
if (ldcore->config.overscalex != origconfig->overscalex)
{
xml_set_attribute_float(overnode, "hstretch", ldcore->config.overscalex);
changed = TRUE;
}
if (ldcore->config.overposy != origconfig->overposy)
{
xml_set_attribute_float(overnode, "voffset", ldcore->config.overposy);
changed = TRUE;
}
if (ldcore->config.overscaley != origconfig->overscaley)
{
xml_set_attribute_float(overnode, "vstretch", ldcore->config.overscaley);
changed = TRUE;
}
}
/* if nothing changed, kill the node */
if (!changed)
xml_delete_node(ldnode);
}
}
}
/***************************************************************************
VIDEO INTERFACE
***************************************************************************/
/*-------------------------------------------------
laserdisc_video_enable - enable/disable the
video
-------------------------------------------------*/
void laserdisc_video_enable(const device_config *device, int enable)
{
laserdisc_state *ld = get_safe_token(device);
ld->core->videoenable = enable;
}
/*-------------------------------------------------
laserdisc_video_enable - enable/disable the
video
-------------------------------------------------*/
void laserdisc_overlay_enable(const device_config *device, int enable)
{
laserdisc_state *ld = get_safe_token(device);
ld->core->overenable = enable;
}
/*-------------------------------------------------
video update callback
-------------------------------------------------*/
VIDEO_UPDATE( laserdisc )
{
const device_config *laserdisc = device_list_first(screen->machine->config->devicelist, LASERDISC);
if (laserdisc != NULL)
{
const rectangle *visarea = video_screen_get_visible_area(screen);
laserdisc_state *ld = laserdisc->token;
ldcore_data *ldcore = ld->core;
bitmap_t *overbitmap = ldcore->overbitmap[ldcore->overindex];
bitmap_t *vidbitmap = NULL;
/* handle the overlay if present */
if (overbitmap != NULL && ldcore->config.overupdate != NULL)
{
rectangle clip = *cliprect;
/* scale the cliprect to the overlay size and then call the update callback */
clip.min_x = ldcore->config.overclip.min_x;
clip.max_x = ldcore->config.overclip.max_x;
clip.min_y = cliprect->min_y * overbitmap->height / bitmap->height;
if (cliprect->min_y == visarea->min_y)
clip.min_y = MIN(clip.min_y, ldcore->config.overclip.min_y);
clip.max_y = (cliprect->max_y + 1) * overbitmap->height / bitmap->height - 1;
(*ldcore->config.overupdate)(screen, overbitmap, &clip);
}
/* if this is the last update, do the rendering */
if (cliprect->max_y == visarea->max_y)
{
float x0, y0, x1, y1;
/* update the texture with the overlay contents */
if (overbitmap != NULL)
{
if (overbitmap->format == BITMAP_FORMAT_INDEXED16)
render_texture_set_bitmap(ldcore->overtex, overbitmap, &ldcore->config.overclip, 0, TEXFORMAT_PALETTEA16);
else if (overbitmap->format == BITMAP_FORMAT_RGB32)
render_texture_set_bitmap(ldcore->overtex, overbitmap, &ldcore->config.overclip, 0, TEXFORMAT_ARGB32);
}
/* get the laserdisc video */
laserdisc_get_video(laserdisc, &vidbitmap);
if (vidbitmap != NULL)
render_texture_set_bitmap(ldcore->videotex, vidbitmap, NULL, 0, TEXFORMAT_YUY16);
/* reset the screen contents */
render_container_empty(render_container_get_screen(screen));
/* add the video texture */
if (ldcore->videoenable)
render_screen_add_quad(screen, 0.0f, 0.0f, 1.0f, 1.0f, MAKE_ARGB(0xff,0xff,0xff,0xff), ldcore->videotex, PRIMFLAG_BLENDMODE(BLENDMODE_NONE) | PRIMFLAG_SCREENTEX(1));
/* add the overlay */
if (ldcore->overenable && overbitmap != NULL)
{
x0 = 0.5f - 0.5f * ldcore->config.overscalex + ldcore->config.overposx;
y0 = 0.5f - 0.5f * ldcore->config.overscaley + ldcore->config.overposy;
x1 = x0 + ldcore->config.overscalex;
y1 = y0 + ldcore->config.overscaley;
render_screen_add_quad(screen, x0, y0, x1, y1, MAKE_ARGB(0xff,0xff,0xff,0xff), ldcore->overtex, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_SCREENTEX(1));
}
/* swap to the next bitmap */
ldcore->overindex = (ldcore->overindex + 1) % ARRAY_LENGTH(ldcore->overbitmap);
}
}
return 0;
}
/***************************************************************************
CONFIGURATION
***************************************************************************/
/*-------------------------------------------------
laserdisc_get_config - return a copy of the
current live configuration settings
-------------------------------------------------*/
void laserdisc_get_config(const device_config *device, laserdisc_config *config)
{
laserdisc_state *ld = get_safe_token(device);
*config = ld->core->config;
}
/*-------------------------------------------------
laserdisc_get_config - change the current live
configuration settings
-------------------------------------------------*/
void laserdisc_set_config(const device_config *device, const laserdisc_config *config)
{
laserdisc_state *ld = get_safe_token(device);
ld->core->config = *config;
}
/***************************************************************************
INITIALIZATION
***************************************************************************/
/*-------------------------------------------------
init_disc - initialize the state of the
CHD disc
-------------------------------------------------*/
static void init_disc(const device_config *device)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
chd_error err;
/* find the disc */
ldcore->disc = get_disk_handle(device->tag);
/* set default parameters */
ldcore->width = 720;
ldcore->height = 240;
ldcore->fps_times_1million = 59940000;
ldcore->samplerate = 48000;
/* get the disc metadata and extract the ld */
ldcore->chdtracks = 0;
ldcore->maxtrack = VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS + VIRTUAL_LEAD_OUT_TRACKS;
if (ldcore->disc != NULL)
{
UINT32 totalhunks = chd_get_header(ldcore->disc)->totalhunks;
int fps, fpsfrac, interlaced, channels;
char metadata[256];
UINT32 vbilength;
/* require the A/V codec */
if (chd_get_header(ldcore->disc)->compression != CHDCOMPRESSION_AV)
fatalerror("Laserdisc video must be compressed with the A/V codec!");
/* read the metadata */
err = chd_get_metadata(ldcore->disc, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL);
if (err != CHDERR_NONE)
fatalerror("Non-A/V CHD file specified");
/* extract the metadata */
if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &ldcore->width, &ldcore->height, &interlaced, &channels, &ldcore->samplerate) != 7)
fatalerror("Invalid metadata in CHD file");
else
ldcore->fps_times_1million = fps * 1000000 + fpsfrac;
/* require interlaced video */
if (!interlaced)
fatalerror("Laserdisc video must be interlaced!");
/* determine the maximum track and allocate a frame buffer */
ldcore->chdtracks = totalhunks / 2;
/* allocate memory for the precomputed per-frame metadata */
ldcore->vbidata = auto_malloc(totalhunks * VBI_PACKED_BYTES);
err = chd_get_metadata(ldcore->disc, AV_LD_METADATA_TAG, 0, ldcore->vbidata, totalhunks * VBI_PACKED_BYTES, &vbilength, NULL);
if (err != CHDERR_NONE || vbilength != totalhunks * VBI_PACKED_BYTES)
fatalerror("Precomputed VBI metadata missing or incorrect size");
}
ldcore->maxtrack = MAX(ldcore->maxtrack, VIRTUAL_LEAD_IN_TRACKS + VIRTUAL_LEAD_OUT_TRACKS + ldcore->chdtracks);
}
/*-------------------------------------------------
init_video - initialize the state of the
video rendering
-------------------------------------------------*/
static void init_video(const device_config *device)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
int index;
/* register for VBLANK callbacks */
video_screen_register_vblank_callback(ld->screen, vblank_state_changed, (void *)device);
/* allocate video frames */
for (index = 0; index < ARRAY_LENGTH(ldcore->frame); index++)
{
frame_data *frame = &ldcore->frame[index];
/* first allocate a YUY16 bitmap at 2x the height */
frame->bitmap = auto_bitmap_alloc(ldcore->width, ldcore->height * 2, BITMAP_FORMAT_YUY16);
fillbitmap_yuy16(frame->bitmap, 40, 109, 240);
/* make a copy of the bitmap that clips out the VBI and horizontal blanking areas */
frame->visbitmap = auto_malloc(sizeof(*frame->visbitmap));
*frame->visbitmap = *frame->bitmap;
frame->visbitmap->base = BITMAP_ADDR16(frame->visbitmap, 44, frame->bitmap->width * 8 / 720);
frame->visbitmap->height -= 44;
frame->visbitmap->width -= 2 * frame->bitmap->width * 8 / 720;
}
/* allocate an empty frame of the same size */
ldcore->emptyframe = auto_bitmap_alloc(ldcore->width, ldcore->height * 2, BITMAP_FORMAT_YUY16);
fillbitmap_yuy16(ldcore->emptyframe, 0, 128, 128);
/* allocate texture for rendering */
ldcore->videoenable = TRUE;
ldcore->videotex = render_texture_alloc(NULL, NULL);
if (ldcore->videotex == NULL)
fatalerror("Out of memory allocating video texture");
/* allocate overlay */
if (ldcore->config.overwidth > 0 && ldcore->config.overheight > 0 && ldcore->config.overupdate != NULL)
{
ldcore->overenable = TRUE;
ldcore->overbitmap[0] = auto_bitmap_alloc(ldcore->config.overwidth, ldcore->config.overheight, ldcore->config.overformat);
ldcore->overbitmap[1] = auto_bitmap_alloc(ldcore->config.overwidth, ldcore->config.overheight, ldcore->config.overformat);
ldcore->overtex = render_texture_alloc(NULL, NULL);
if (ldcore->overtex == NULL)
fatalerror("Out of memory allocating overlay texture");
}
}
/*-------------------------------------------------
init_audio - initialize the state of the
audio rendering
-------------------------------------------------*/
static void init_audio(const device_config *device)
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
int sndnum;
/* find the custom audio */
ldcore->audiocustom = 0;
for (sndnum = 0; sndnum < MAX_SOUND; sndnum++)
{
if (device->machine->config->sound[sndnum].tag != NULL && strcmp(device->machine->config->sound[sndnum].tag, ldcore->config.sound) == 0)
break;
if (device->machine->config->sound[sndnum].type == SOUND_CUSTOM)
ldcore->audiocustom++;
}
if (sndnum == MAX_SOUND)
ldcore->audiocustom = -1;
/* allocate audio buffers */
ldcore->audiomaxsamples = ((UINT64)ldcore->samplerate * 1000000 + ldcore->fps_times_1million - 1) / ldcore->fps_times_1million;
ldcore->audiobufsize = ldcore->audiomaxsamples * 4;
ldcore->audiobuffer[0] = auto_malloc(ldcore->audiobufsize * sizeof(ldcore->audiobuffer[0][0]));
ldcore->audiobuffer[1] = auto_malloc(ldcore->audiobufsize * sizeof(ldcore->audiobuffer[1][0]));
}
/***************************************************************************
DEVICE INTERFACE
***************************************************************************/
/*-------------------------------------------------
device start callback
-------------------------------------------------*/
static DEVICE_START( laserdisc )
{
const laserdisc_config *config = device->inline_config;
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore;
int statesize;
int index;
/* ensure that our screen is started first */
ld->screen = devtag_get_device(device->machine, VIDEO_SCREEN, config->screen);
assert(ld->screen != NULL);
if (!ld->screen->started)
return DEVICE_START_MISSING_DEPENDENCY;
/* save a copy of the device pointer */
ld->device = device;
/* allocate memory for the core state */
ld->core = auto_malloc(sizeof(*ld->core));
memset(ld->core, 0, sizeof(*ld->core));
ldcore = ld->core;
/* determine the maximum player-specific state size and allocate it */
statesize = 0;
for (index = 0; index < ARRAY_LENGTH(player_interfaces); index++)
statesize = MAX(statesize, player_interfaces[index]->statesize);
ld->player = auto_malloc(statesize);
memset(ld->player, 0, statesize);
/* copy config data to the live state */
ldcore->config = *config;
if (ldcore->config.overclip.max_x == ldcore->config.overclip.min_x || ldcore->config.overclip.max_y == ldcore->config.overclip.min_y)
{
ldcore->config.overclip.min_x = ldcore->config.overclip.min_y = 0;
ldcore->config.overclip.max_x = ldcore->config.overwidth - 1;
ldcore->config.overclip.max_y = ldcore->config.overheight - 1;
}
if (ldcore->config.overscalex == 0)
ldcore->config.overscalex = 1.0f;
if (ldcore->config.overscaley == 0)
ldcore->config.overscaley = 1.0f;
/* initialize the various pieces */
init_disc(device);
init_video(device);
init_audio(device);
/* register callbacks */
config_register(device->machine, "laserdisc", configuration_load, configuration_save);
return DEVICE_START_OK;
}
/*-------------------------------------------------
device exit callback
-------------------------------------------------*/
static DEVICE_STOP( laserdisc )
{
laserdisc_state *ld = get_safe_token(device);
ldcore_data *ldcore = ld->core;
/* make sure all async operations have completed */
if (ldcore->disc != NULL)
chd_async_complete(ldcore->disc);
/* free any textures */
if (ldcore->videotex != NULL)
render_texture_free(ldcore->videotex);
if (ldcore->overtex != NULL)
render_texture_free(ldcore->overtex);
}
/*-------------------------------------------------
device reset callback
-------------------------------------------------*/
static DEVICE_RESET( laserdisc )
{
laserdisc_state *ld = get_safe_token(device);
attotime curtime = timer_get_time();
ldcore_data *ldcore = ld->core;
int pltype, line;
/* find our interface */
for (pltype = 0; pltype < ARRAY_LENGTH(player_interfaces); pltype++)
if (player_interfaces[pltype]->type == ldcore->config.type)
break;
if (pltype == ARRAY_LENGTH(player_interfaces))
fatalerror("No interface found for laserdisc player type %d\n", ldcore->config.type);
ldcore->intf = *player_interfaces[pltype];
/* attempt to wire up the audio */
if (ldcore->audiocustom != -1)
{
sound_token *token = custom_get_token(ldcore->audiocustom);
token->ld = ld;
stream_set_sample_rate(token->stream, ldcore->samplerate);
}
/* set up the general ld */
ldcore->audiosquelch = 3;
ldcore->videosquelch = 1;
ldcore->fieldnum = 0;
ldcore->curtrack = 1;
ldcore->attospertrack = 0;
ldcore->sliderupdate = curtime;
/* reset the I/O lines */
for (line = 0; line < LASERDISC_INPUT_LINES; line++)
ldcore->linein[line] = CLEAR_LINE;
for (line = 0; line < LASERDISC_OUTPUT_LINES; line++)
ldcore->lineout[line] = CLEAR_LINE;
/* call the initialization */
if (ldcore->intf.init != NULL)
(*ldcore->intf.init)(ld);
}
/*-------------------------------------------------
device set info callback
-------------------------------------------------*/
static DEVICE_SET_INFO( laserdisc )
{
laserdisc_state *ld = get_safe_token(device);
switch (state)
{
/* --- the following bits of info are set as 64-bit signed integers --- */
case LDINFO_INT_TYPE:
if (ld != NULL && ld->core != NULL && ld->core->config.type != info->i)
{
ld->core->config.type = info->i;
device_reset(device);
}
break;
}
}
/*-------------------------------------------------
device get info callback
-------------------------------------------------*/
DEVICE_GET_INFO( laserdisc )
{
const laserdisc_config *config = NULL;
const ldplayer_interface *intf = NULL;
int pltype;
/* if we have a device, figure out where our config lives */
if (device != NULL)
{
laserdisc_state *ld = device->token;
config = device->inline_config;
if (ld != NULL && ld->core != NULL)
{
config = &ld->core->config;
intf = &ld->core->intf;
}
}
/* if we don't have an interface, but we have a config, look up the interface */
if (intf == NULL && config != NULL)
for (pltype = 0; pltype < ARRAY_LENGTH(player_interfaces); pltype++)
if (player_interfaces[pltype]->type == config->type)
{
intf = player_interfaces[pltype];
break;
}
switch (state)
{
/* --- the following bits of info are returned as 64-bit signed integers --- */
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(laserdisc_state); break;
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(laserdisc_config); break;
case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_VIDEO; break;
case LDINFO_INT_TYPE: info->i = config->type; break;
/* --- the following bits of info are returned as pointers --- */
case DEVINFO_PTR_ROM_REGION: info->romregion = (intf != NULL) ? intf->romregion : NULL; break;
case DEVINFO_PTR_MACHINE_CONFIG: info->machine_config = (intf != NULL) ? intf->machine_config : NULL; break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case DEVINFO_FCT_SET_INFO: info->set_info = DEVICE_SET_INFO_NAME(laserdisc); break;
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(laserdisc); break;
case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME(laserdisc); break;
case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(laserdisc); break;
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: info->s = (intf != NULL) ? intf->name : "Unknown Laserdisc Player"; break;
case DEVINFO_STR_FAMILY: info->s = "Laserdisc Player"; break;
case DEVINFO_STR_VERSION: info->s = "1.0"; break;
case DEVINFO_STR_SOURCE_FILE: info->s = __FILE__; break;
case DEVINFO_STR_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break;
}
}