summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/machine/ldcore.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/machine/ldcore.c')
-rw-r--r--trunk/src/emu/machine/ldcore.c1716
1 files changed, 1716 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/ldcore.c b/trunk/src/emu/machine/ldcore.c
new file mode 100644
index 00000000000..58aade8c076
--- /dev/null
+++ b/trunk/src/emu/machine/ldcore.c
@@ -0,0 +1,1716 @@
+/*************************************************************************
+
+ ldcore.c
+
+ Private core laserdisc player implementation.
+
+ Copyright Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+*************************************************************************/
+
+#include "emu.h"
+#include "ldcore.h"
+#include "avcomp.h"
+#include "vbiparse.h"
+#include "config.h"
+#include "render.h"
+
+
+
+/***************************************************************************
+ DEBUGGING
+***************************************************************************/
+
+#define LOG_SLIDER 0
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+/* these specs code from IEC 60857, for NTSC players */
+#define LEAD_IN_MIN_RADIUS_IN_UM 53500 /* 53.5 mm */
+#define PROGRAM_MIN_RADIUS_IN_UM 55000 /* 55 mm */
+#define PROGRAM_MAX_RADIUS_IN_UM 145000 /* 145 mm */
+#define LEAD_OUT_MIN_SIZE_IN_UM 2000 /* 2 mm */
+
+/* the track pitch is defined as a range; we pick a nominal pitch
+ that ensures we can fit 54,000 tracks */
+#define MIN_TRACK_PITCH_IN_NM 1400 /* 1.4 um */
+#define MAX_TRACK_PITCH_IN_NM 2000 /* 2 um */
+#define NOMINAL_TRACK_PITCH_IN_NM ((PROGRAM_MAX_RADIUS_IN_UM - PROGRAM_MIN_RADIUS_IN_UM) * 1000 / 54000)
+
+/* we simulate extra lead-in and lead-out tracks */
+#define VIRTUAL_LEAD_IN_TRACKS ((PROGRAM_MIN_RADIUS_IN_UM - LEAD_IN_MIN_RADIUS_IN_UM) * 1000 / NOMINAL_TRACK_PITCH_IN_NM)
+#define MAX_TOTAL_TRACKS 54000
+#define VIRTUAL_LEAD_OUT_TRACKS (LEAD_OUT_MIN_SIZE_IN_UM * 1000 / NOMINAL_TRACK_PITCH_IN_NM)
+
+
+
+/***************************************************************************
+ 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 */
+ device_t *audiocustom; /* custom sound device */
+
+ /* 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 */
+ palette_t * videopalette; /* palette 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(device_t *device);
+static DEVICE_START( laserdisc_sound );
+static STREAM_UPDATE( custom_stream_callback );
+static void configuration_load(device_t *device, int config_type, xml_data_node *parentnode);
+static void configuration_save(device_t *device, int config_type, xml_data_node *parentnode);
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ get_safe_token - makes sure that the passed
+ in device is, in fact, a laserdisc device
+-------------------------------------------------*/
+
+INLINE laserdisc_state *get_safe_token(device_t *device)
+{
+ assert(device != NULL);
+ assert(device_is_laserdisc(device));
+
+ return (laserdisc_state *)downcast<legacy_device_base *>(device)->token();
+}
+
+
+/*-------------------------------------------------
+ update_audio - update the audio stream to the
+ current time
+-------------------------------------------------*/
+
+INLINE void update_audio(laserdisc_state *ld)
+{
+ ldcore_data *ldcore = ld->core;
+ if (ldcore->audiocustom != NULL)
+ {
+ sound_token *token = (sound_token *)downcast<legacy_device_base *>(ldcore->audiocustom)->token();
+ token->stream->update();
+ }
+}
+
+
+/*-------------------------------------------------
+ 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 = (curtime - ldcore->sliderupdate).as_attoseconds();
+ 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(0, tracks_covered * ldcore->attospertrack);
+ }
+ else
+ {
+ tracks_covered = delta / -ldcore->attospertrack;
+ add_and_clamp_track(ldcore, -tracks_covered);
+ if (tracks_covered != 0)
+ ldcore->sliderupdate += attotime(0, tracks_covered * -ldcore->attospertrack);
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ vblank_state_changed - called on each state
+ change of the VBLANK signal
+-------------------------------------------------*/
+
+static void vblank_state_changed(device_t *device, screen_device &screen, bool vblank_state)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+ attotime curtime = screen.machine().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 */
+ screen.machine().scheduler().timer_set(screen.time_until_pos(16*2), FUNC(perform_player_update), 0, ld);
+ }
+}
+
+
+/*-------------------------------------------------
+ vblank_state_changed - called on each state
+ change of the VBLANK signal
+-------------------------------------------------*/
+
+static TIMER_CALLBACK( perform_player_update )
+{
+ laserdisc_state *ld = (laserdisc_state *)ptr;
+ ldcore_data *ldcore = ld->core;
+ attotime curtime = machine.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(ld);
+
+ /* 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(device_t *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(device_t *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(device_t *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(device_t *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(device_t *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(device_t *device, UINT32 code, UINT8 zero_if_squelched)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+ int field = ldcore->fieldnum;
+
+ /* return nothing if the video is off (external devices can't sense) */
+ if (zero_if_squelched && ldcore->videosquelch)
+ return 0;
+
+ 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(device_t *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);
+ 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 = ld->screen->frame_period();
+
+ update_slider_pos(ldcore, ld->device->machine().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 = (vsyncperiod / tracks_per_vsync).as_attoseconds();
+
+ /* negative values store negative times */
+ else
+ ldcore->attospertrack = -(vsyncperiod / -tracks_per_vsync).as_attoseconds();
+
+ 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, ld->device->machine().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, ld->device->machine().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 (curtime >= ld->state.endtime)
+ 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 (curtime >= ld->state.endtime)
+ newstate->state = LDSTATE_SPINUP;
+ advanceby = -GENERIC_SEARCH_SPEED;
+ break;
+
+ case LDSTATE_SPINUP:
+ /* when time expires, switch to the playing state */
+ if (curtime >= ld->state.endtime)
+ 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;
+ vbi_metadata vbidata = { 0 };
+ frame_data *frame;
+ UINT32 vbiframe;
+ UINT32 readhunk;
+ INT32 chdtrack;
+
+ /* 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;
+
+ /* cheat and look up the metadata we are about to retrieve */
+ if (ldcore->vbidata != NULL)
+ vbi_metadata_unpack(&vbidata, NULL, &ldcore->vbidata[readhunk * VBI_PACKED_BYTES]);
+
+ /* if we're in the lead-in area, force the VBI data to be standard lead-in */
+ if (tracknum - 1 < VIRTUAL_LEAD_IN_TRACKS)
+ {
+ vbidata.line16 = 0;
+ vbidata.line17 = vbidata.line18 = vbidata.line1718 = VBI_CODE_LEADIN;
+ }
+//printf("track %5d.%d: %06X %06X %06X\n", tracknum, fieldnum, vbidata.line16, vbidata.line17, vbidata.line18);
+
+ /* if we're about to read the first field in a frame, advance */
+ frame = &ldcore->frame[ldcore->videoindex];
+ if ((vbidata.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
+ {
+ 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.alloc = NULL;
+ ldcore->videotarget.base = BITMAP_ADDR16(frame->bitmap, fieldnum, 0);
+ ldcore->videotarget.rowpixels = frame->bitmap->rowpixels * 2;
+ ldcore->videotarget.width = frame->bitmap->width;
+ ldcore->videotarget.height = frame->bitmap->height / 2;
+ ldcore->videotarget.format = frame->bitmap->format;
+ ldcore->videotarget.bpp = frame->bitmap->bpp;
+ ldcore->videotarget.palette = frame->bitmap->palette;
+ ldcore->videotarget.cliprect = frame->bitmap->cliprect;
+ 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;
+
+ /* 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]);
+
+ /* if we're in the lead-in area, force the VBI data to be standard lead-in */
+ if (tracknum - 1 < VIRTUAL_LEAD_IN_TRACKS)
+ {
+ ldcore->metadata[fieldnum].line16 = 0;
+ ldcore->metadata[fieldnum].line17 = ldcore->metadata[fieldnum].line18 = ldcore->metadata[fieldnum].line1718 = VBI_CODE_LEADIN;
+ }
+
+ /* 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(device_t *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->avconfig.video != NULL && ldcore->intf.overlay != NULL)
+ (*ldcore->intf.overlay)(ld, ldcore->avconfig.video);
+
+ /* 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;
+}
+
+
+/*-------------------------------------------------
+ laserdisc_sound_start - custom audio start
+ for laserdiscs
+-------------------------------------------------*/
+
+static DEVICE_START( laserdisc_sound )
+{
+ sound_token *token = (sound_token *)downcast<legacy_device_base *>(device)->token();
+ token->stream = device->machine().sound().stream_alloc(*device, 0, 2, 48000, token, custom_stream_callback);
+ token->ld = NULL;
+}
+
+
+/*-------------------------------------------------
+ laserdisc_sound_get_info - information
+ callback for laserdisc audio
+-------------------------------------------------*/
+
+DEVICE_GET_INFO( laserdisc_sound )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as 64-bit signed integers --- */
+ case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(sound_token); break;
+
+ /* --- the following bits of info are returned as pointers to data or functions --- */
+ case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(laserdisc_sound);break;
+
+ /* --- the following bits of info are returned as NULL-terminated strings --- */
+ case DEVINFO_STR_NAME: strcpy(info->s, "Laserdisc Analog"); break;
+ case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
+ }
+}
+
+
+/*-------------------------------------------------
+ custom_stream_callback - audio streamer
+ for laserdiscs
+-------------------------------------------------*/
+
+static STREAM_UPDATE( custom_stream_callback )
+{
+ sound_token *token = (sound_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 */
+ 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)
+ {
+ 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(device_t *device, 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", "");
+ if (strcmp(devtag,device->tag()) == 0)
+ {
+ 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(device_t *device, int config_type, xml_data_node *parentnode)
+{
+ /* we only care about game files */
+ if (config_type != CONFIG_TYPE_GAME)
+ return;
+
+ laserdisc_config *origconfig = (laserdisc_config *)downcast<const legacy_device_base *>(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(device_t *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(device_t *device, int enable)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ld->core->overenable = enable;
+}
+
+
+/*-------------------------------------------------
+ video update callback
+-------------------------------------------------*/
+
+SCREEN_UPDATE( laserdisc )
+{
+ device_t *laserdisc = screen->machine().device("laserdisc"); // TODO: allow more than one laserdisc
+ if (laserdisc != NULL)
+ {
+ const rectangle &visarea = screen->visible_area();
+ laserdisc_state *ld = (laserdisc_state *)downcast<legacy_device_base *>(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)
+ {
+ /* update the texture with the overlay contents */
+ if (overbitmap != NULL)
+ {
+ if (overbitmap->format == BITMAP_FORMAT_INDEXED16)
+ ldcore->overtex->set_bitmap(overbitmap, &ldcore->config.overclip, TEXFORMAT_PALETTEA16, laserdisc->machine().palette);
+ else if (overbitmap->format == BITMAP_FORMAT_RGB32)
+ ldcore->overtex->set_bitmap(overbitmap, &ldcore->config.overclip, TEXFORMAT_ARGB32);
+ }
+
+ /* get the laserdisc video */
+ laserdisc_get_video(laserdisc, &vidbitmap);
+ if (vidbitmap != NULL)
+ ldcore->videotex->set_bitmap(vidbitmap, NULL, TEXFORMAT_YUY16, ldcore->videopalette);
+
+ /* reset the screen contents */
+ screen->container().empty();
+
+ /* add the video texture */
+ if (ldcore->videoenable)
+ screen->container().add_quad(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)
+ {
+ float x0 = 0.5f - 0.5f * ldcore->config.overscalex + ldcore->config.overposx;
+ float y0 = 0.5f - 0.5f * ldcore->config.overscaley + ldcore->config.overposy;
+ float x1 = x0 + ldcore->config.overscalex;
+ float y1 = y0 + ldcore->config.overscaley;
+ screen->container().add_quad(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(device_t *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(device_t *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(device_t *device)
+{
+ const laserdisc_config *config = (const laserdisc_config *)downcast<const legacy_device_base *>(device)->inline_config();
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+ chd_error err;
+
+ /* get a handle to the disc to play */
+ if (config->getdisc != NULL)
+ ldcore->disc = (*config->getdisc)(device);
+ else
+ ldcore->disc = get_disk_handle(device->machine(), 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, 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_alloc_array(device->machine(), UINT8, totalhunks * VBI_PACKED_BYTES);
+ err = chd_get_metadata(ldcore->disc, AV_LD_METADATA_TAG, 0, ldcore->vbidata, totalhunks * VBI_PACKED_BYTES, &vbilength, NULL, 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(device_t *device)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+ int index;
+
+ /* register for VBLANK callbacks */
+ ld->screen->register_vblank_callback(vblank_state_delegate(FUNC(vblank_state_changed), 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_alloc(device->machine(), bitmap_t(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_alloc(device->machine(), bitmap_t(BITMAP_ADDR16(frame->bitmap, 44, frame->bitmap->width * 8 / 720),
+ frame->bitmap->width - 2 * frame->bitmap->width * 8 / 720,
+ frame->bitmap->height - 44,
+ frame->bitmap->rowpixels, frame->bitmap->format));
+ }
+
+ /* allocate an empty frame of the same size */
+ ldcore->emptyframe = auto_bitmap_alloc(device->machine(), ldcore->width, ldcore->height * 2, BITMAP_FORMAT_YUY16);
+ fillbitmap_yuy16(ldcore->emptyframe, 0, 128, 128);
+
+ /* allocate texture for rendering */
+ ldcore->videoenable = TRUE;
+ ldcore->videotex = device->machine().render().texture_alloc();
+ if (ldcore->videotex == NULL)
+ fatalerror("Out of memory allocating video texture");
+
+ /* allocate palette for applying brightness/contrast/gamma */
+ ldcore->videopalette = palette_alloc(256, 1);
+ if (ldcore->videopalette == NULL)
+ fatalerror("Out of memory allocating video palette");
+ for (index = 0; index < 256; index++)
+ palette_entry_set_color(ldcore->videopalette, index, MAKE_RGB(index, index, index));
+
+ /* allocate overlay */
+ if (ldcore->config.overwidth > 0 && ldcore->config.overheight > 0 && ldcore->config.overupdate != NULL)
+ {
+ ldcore->overenable = TRUE;
+ ldcore->overbitmap[0] = auto_bitmap_alloc(device->machine(), ldcore->config.overwidth, ldcore->config.overheight, (bitmap_format)ldcore->config.overformat);
+ ldcore->overbitmap[1] = auto_bitmap_alloc(device->machine(), ldcore->config.overwidth, ldcore->config.overheight, (bitmap_format)ldcore->config.overformat);
+ ldcore->overtex = device->machine().render().texture_alloc();
+ if (ldcore->overtex == NULL)
+ fatalerror("Out of memory allocating overlay texture");
+ }
+}
+
+
+/*-------------------------------------------------
+ init_audio - initialize the state of the
+ audio rendering
+-------------------------------------------------*/
+
+static void init_audio(device_t *device)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+
+ /* find the custom audio */
+ ldcore->audiocustom = device->machine().device(ldcore->config.sound);
+
+ /* 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_alloc_array(device->machine(), INT16, ldcore->audiobufsize);
+ ldcore->audiobuffer[1] = auto_alloc_array(device->machine(), INT16, ldcore->audiobufsize);
+}
+
+
+
+/***************************************************************************
+ DEVICE INTERFACE
+***************************************************************************/
+
+/*-------------------------------------------------
+ device start callback
+-------------------------------------------------*/
+
+static DEVICE_START( laserdisc )
+{
+ const laserdisc_config *config = (const laserdisc_config *)downcast<const legacy_device_base *>(device)->inline_config();
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore;
+
+ /* ensure that our screen is started first */
+ ld->screen = downcast<screen_device *>(device->machine().device(config->screen));
+ assert(ld->screen != NULL);
+ if (!ld->screen->started())
+ throw device_missing_dependencies();
+
+ /* save a copy of the device pointer */
+ ld->device = device;
+
+ /* allocate memory for the core state */
+ ld->core = auto_alloc_clear(device->machine(), ldcore_data);
+ ldcore = ld->core;
+
+ if (device->type() == PIONEER_PR8210)
+ ldcore->intf = pr8210_interface;
+ else if (device->type() == SIMUTREK_SPECIAL)
+ ldcore->intf = simutrek_interface;
+ else if (device->type() == PIONEER_LDV1000)
+ ldcore->intf = ldv1000_interface;
+ else if (device->type() == PHILLIPS_22VP931)
+ ldcore->intf = vp931_interface;
+
+ ld->player = (ldplayer_data *)auto_alloc_array_clear(device->machine(), UINT8, ldcore->intf.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", config_saveload_delegate(FUNC(configuration_load), device), config_saveload_delegate(FUNC(configuration_save), device));
+}
+
+
+/*-------------------------------------------------
+ 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 and palettes */
+ device->machine().render().texture_free(ldcore->videotex);
+ if (ldcore->videopalette != NULL)
+ palette_deref(ldcore->videopalette);
+ device->machine().render().texture_free(ldcore->overtex);
+}
+
+
+/*-------------------------------------------------
+ device reset callback
+-------------------------------------------------*/
+
+static DEVICE_RESET( laserdisc )
+{
+ laserdisc_state *ld = get_safe_token(device);
+ attotime curtime = device->machine().time();
+ ldcore_data *ldcore = ld->core;
+ int line;
+
+ /* attempt to wire up the audio */
+ if (ldcore->audiocustom != NULL)
+ {
+ sound_token *token = (sound_token *)downcast<legacy_device_base *>(ldcore->audiocustom)->token();
+ token->ld = ld;
+ token->stream->set_sample_rate(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
+-------------------------------------------------*/
+
+int laserdisc_get_type(device_t *device)
+{
+ if (device->type() == PIONEER_PR7820)
+ return LASERDISC_TYPE_PIONEER_PR7820;
+ if (device->type() == PIONEER_PR8210)
+ return LASERDISC_TYPE_PIONEER_PR8210;
+ if (device->type() == SIMUTREK_SPECIAL)
+ return LASERDISC_TYPE_SIMUTREK_SPECIAL;
+ if (device->type() == PIONEER_LDV1000)
+ return LASERDISC_TYPE_PIONEER_LDV1000;
+ if (device->type() == PHILLIPS_22VP931)
+ return LASERDISC_TYPE_PHILLIPS_22VP931;
+ if (device->type() == PHILLIPS_22VP932)
+ return LASERDISC_TYPE_PHILLIPS_22VP932;
+ if (device->type() == SONY_LDP1450)
+ return LASERDISC_TYPE_SONY_LDP1450;
+ return LASERDISC_TYPE_UNKNOWN;
+}
+
+void laserdisc_set_type(device_t *device, int type)
+{
+ // this is no longer supported
+}
+
+
+DEVICE_GET_INFO( laserdisc )
+{
+ 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;
+
+ /* --- the following bits of info are returned as pointers to data or functions --- */
+ 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_FAMILY: strcpy(info->s, "Laserdisc Player"); 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;
+ }
+}
+
+DEVICE_GET_INFO( unkldplay )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as pointers --- */
+ case DEVINFO_PTR_ROM_REGION: info->romregion = NULL; return;
+ case DEVINFO_PTR_MACHINE_CONFIG: info->machine_config = NULL; return;
+ /* --- the following bits of info are returned as NULL-terminated strings --- */
+ case DEVINFO_STR_NAME: strcpy(info->s,"Unknown Laserdisc Player"); return;
+ case DEVINFO_STR_SHORTNAME: strcpy(info->s,"unkldplay"); return;
+ }
+
+ DEVICE_GET_INFO_CALL( laserdisc );
+}
+
+DEVICE_GET_INFO( pioneer_pr7820 )
+{
+ DEVICE_GET_INFO_CALL( unkldplay );
+}
+
+DEVICE_GET_INFO( pioneer_pr8210 )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as pointers --- */
+ case DEVINFO_PTR_ROM_REGION: info->romregion = pr8210_interface.romregion; return;
+ case DEVINFO_PTR_MACHINE_CONFIG: info->machine_config = pr8210_interface.machine_config; return;
+
+ /* --- the following bits of info are returned as NULL-terminated strings -- */
+ case DEVINFO_STR_NAME: strcpy(info->s, pr8210_interface.name); return;
+ case DEVINFO_STR_SHORTNAME: strcpy(info->s, pr8210_interface.shortname); return;
+ }
+
+ DEVICE_GET_INFO_CALL( laserdisc );
+}
+
+DEVICE_GET_INFO( simutrek_special )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as pointers --- */
+ case DEVINFO_PTR_ROM_REGION: info->romregion = simutrek_interface.romregion; return;
+ case DEVINFO_PTR_MACHINE_CONFIG: info->machine_config = simutrek_interface.machine_config; return;
+
+ /* --- the following bits of info are returned as NULL-terminated strings -- */
+ case DEVINFO_STR_NAME: strcpy(info->s, simutrek_interface.name); return;
+ case DEVINFO_STR_SHORTNAME: strcpy(info->s, simutrek_interface.shortname); return;
+ }
+
+ DEVICE_GET_INFO_CALL( laserdisc );
+}
+
+DEVICE_GET_INFO( pioneer_ldv1000 )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as pointers --- */
+ case DEVINFO_PTR_ROM_REGION: info->romregion = ldv1000_interface.romregion; return;
+ case DEVINFO_PTR_MACHINE_CONFIG: info->machine_config = ldv1000_interface.machine_config; return;
+
+ /* --- the following bits of info are returned as NULL-terminated strings -- */
+ case DEVINFO_STR_NAME: strcpy(info->s, ldv1000_interface.name); return;
+ case DEVINFO_STR_SHORTNAME: strcpy(info->s, ldv1000_interface.shortname); return;
+ }
+
+ DEVICE_GET_INFO_CALL( laserdisc );
+}
+
+DEVICE_GET_INFO( phillips_22vp931 )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as pointers --- */
+ case DEVINFO_PTR_ROM_REGION: info->romregion = vp931_interface.romregion; return;
+ case DEVINFO_PTR_MACHINE_CONFIG: info->machine_config = vp931_interface.machine_config; return;
+
+ /* --- the following bits of info are returned as NULL-terminated strings -- */
+ case DEVINFO_STR_NAME: strcpy(info->s, vp931_interface.name); return;
+ case DEVINFO_STR_SHORTNAME: strcpy(info->s, vp931_interface.shortname); return;
+ }
+
+ DEVICE_GET_INFO_CALL( laserdisc );
+}
+
+DEVICE_GET_INFO( phillips_22vp932 )
+{
+ DEVICE_GET_INFO_CALL( unkldplay );
+}
+
+DEVICE_GET_INFO( sony_ldp1450 )
+{
+ DEVICE_GET_INFO_CALL( unkldplay );
+}
+
+DEFINE_LEGACY_DEVICE(PIONEER_PR7820,pioneer_pr7820);
+DEFINE_LEGACY_DEVICE(PIONEER_PR8210,pioneer_pr8210);
+DEFINE_LEGACY_DEVICE(SIMUTREK_SPECIAL,simutrek_special);
+DEFINE_LEGACY_DEVICE(PIONEER_LDV1000,pioneer_ldv1000);
+DEFINE_LEGACY_DEVICE(PHILLIPS_22VP931,phillips_22vp931);
+DEFINE_LEGACY_DEVICE(PHILLIPS_22VP932,phillips_22vp932);
+DEFINE_LEGACY_DEVICE(SONY_LDP1450,sony_ldp1450);
+DEFINE_LEGACY_SOUND_DEVICE(LASERDISC_SOUND, laserdisc_sound);