summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu')
-rw-r--r--src/emu/emu.mak4
-rw-r--r--src/emu/machine/laserdsc.h3
-rw-r--r--src/emu/machine/ldcore.c1576
-rw-r--r--src/emu/machine/ldcore.h247
-rw-r--r--src/emu/machine/ldpr8210.c722
-rw-r--r--src/emu/machine/ldv1000.c742
6 files changed, 3290 insertions, 4 deletions
diff --git a/src/emu/emu.mak b/src/emu/emu.mak
index e1d5cc79733..b4d2cdecf30 100644
--- a/src/emu/emu.mak
+++ b/src/emu/emu.mak
@@ -139,7 +139,9 @@ EMUMACHINEOBJS = \
$(EMUMACHINE)/idectrl.o \
$(EMUMACHINE)/intelfsh.o \
$(EMUMACHINE)/latch8.o \
- $(EMUMACHINE)/laserdsc.o \
+ $(EMUMACHINE)/ldcore.o \
+ $(EMUMACHINE)/ldpr8210.o \
+ $(EMUMACHINE)/ldv1000.o \
$(EMUMACHINE)/mb3773.o \
$(EMUMACHINE)/mb87078.o \
$(EMUMACHINE)/mc146818.o \
diff --git a/src/emu/machine/laserdsc.h b/src/emu/machine/laserdsc.h
index 6ec9ef25141..6c4c50923d0 100644
--- a/src/emu/machine/laserdsc.h
+++ b/src/emu/machine/laserdsc.h
@@ -161,9 +161,6 @@ extern const custom_sound_interface laserdisc_custom_interface;
/* call this once per field (i.e., 59.94 times/second for NTSC) */
void laserdisc_vsync(const device_config *device);
-/* return a textual description of the current state (for debugging) */
-const char *laserdisc_describe_state(const device_config *device);
-
/* get a bitmap for the current frame (and the frame number) */
UINT32 laserdisc_get_video(const device_config *device, bitmap_t **bitmap);
diff --git a/src/emu/machine/ldcore.c b/src/emu/machine/ldcore.c
new file mode 100644
index 00000000000..05b6f512858
--- /dev/null
+++ b/src/emu/machine/ldcore.c
@@ -0,0 +1,1576 @@
+/*************************************************************************
+
+ 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_POSITION(x) /*printf x*/
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+/* we simulate extra lead-in and lead-out tracks */
+#define VIRTUAL_LEAD_IN_TRACKS 200
+#define VIRTUAL_LEAD_OUT_TRACKS 200
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/* 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 */
+ av_codec_decompress_config avconfig; /* decompression configuration */
+ UINT8 readpending; /* true if a read is pending */
+ UINT32 maxtrack; /* maximum track number */
+
+ /* 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 */
+
+ /* video data */
+ bitmap_t * videoframe[3]; /* currently cached frames */
+ bitmap_t * videovisframe[3]; /* wrapper around videoframe with only visible lines */
+ UINT8 videofields[3]; /* number of fields in each frame */
+ UINT32 videoframenum[3]; /* frame number contained in each frame */
+ 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 */
+ int samplerate; /* playback samplerate */
+
+ /* metadata */
+ vbi_metadata metadata[2]; /* metadata parsed from the stream, for each field */
+ int last_frame; /* last seen frame number */
+ int last_chapter; /* last seen chapter number */
+
+ /* 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 void read_track_data(laserdisc_state *ld);
+static void process_track_data(const device_config *device);
+static void fake_metadata(UINT32 track, UINT8 which, vbi_metadata *metadata);
+//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
+};
+
+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;
+}
+
+
+/*-------------------------------------------------
+ audio_channel_active - return TRUE if the
+ given audio channel should be output
+-------------------------------------------------*/
+
+INLINE int audio_channel_active(ldcore_data *ldcore, int channel)
+{
+ return (~ldcore->audiosquelch >> channel) & 1;
+}
+
+
+/*-------------------------------------------------
+ video_active - return TRUE if the video should
+ be output
+-------------------------------------------------*/
+
+INLINE int video_active(ldcore_data *ldcore)
+{
+ return !ldcore->videosquelch;
+}
+
+
+/*-------------------------------------------------
+ add_to_current_track - add a value to the
+ current track, stopping if we hit the min or
+ max
+-------------------------------------------------*/
+
+INLINE int add_to_current_track(ldcore_data *ldcore, INT32 delta)
+{
+ ldcore->curtrack += delta;
+ if (ldcore->curtrack < 1)
+ {
+ ldcore->curtrack = 1;
+ return TRUE;
+ }
+ else if (ldcore->curtrack >= ldcore->maxtrack - 1)
+ {
+ ldcore->curtrack = ldcore->maxtrack - 1;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*-------------------------------------------------
+ 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
+***************************************************************************/
+
+/*-------------------------------------------------
+ laserdisc_vsync - call this once per field
+ on the VSYNC signal
+-------------------------------------------------*/
+
+void laserdisc_vsync(const device_config *device)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+
+ /* wait for previous read and decode to finish */
+ process_track_data(device);
+
+ /* update the state */
+ if (ldcore->intf.update != NULL)
+ {
+ INT32 advanceby = (*ldcore->intf.update)(ld, &ldcore->metadata[ldcore->fieldnum], ldcore->fieldnum, timer_get_time());
+ add_to_current_track(ldcore, advanceby);
+ }
+
+ /* flush any audio before we read more */
+ if (ldcore->audiocustom != -1)
+ {
+ sound_token *token = custom_get_token(ldcore->audiocustom);
+ stream_update(token->stream);
+ }
+
+ /* 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
+-------------------------------------------------*/
+
+UINT32 laserdisc_get_video(const device_config *device, bitmap_t **bitmap)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+ int frameindex;
+
+ /* determine the most recent live set of frames */
+ frameindex = ldcore->videoindex;
+ if (ldcore->videofields[frameindex] < 2)
+ frameindex = (frameindex + ARRAY_LENGTH(ldcore->videofields) - 1) % ARRAY_LENGTH(ldcore->videofields);
+
+ /* if no video present, return the empty frame */
+ if (!video_active(ldcore) || ldcore->videofields[frameindex] < 2)
+ {
+ *bitmap = ldcore->emptyframe;
+ return 0;
+ }
+ else
+ {
+ *bitmap = ldcore->videovisframe[frameindex];
+ return ldcore->videoframenum[frameindex];
+ }
+}
+
+
+/*-------------------------------------------------
+ laserdisc_get_field_code - return raw field
+ information read from the disc
+-------------------------------------------------*/
+
+UINT32 laserdisc_get_field_code(const device_config *device, UINT8 code)
+{
+ laserdisc_state *ld = get_safe_token(device);
+ ldcore_data *ldcore = ld->core;
+ int field = ldcore->fieldnum ^ 1;
+
+ /* if no video present, return */
+ if (!video_active(ldcore))
+ 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;
+ }
+
+ 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)
+{
+ 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;
+}
+
+
+
+/***************************************************************************
+ 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;
+ chd_error err;
+
+ /* if the previous field had the white flag, force the new field to pair with it */
+ if (ldcore->metadata[fieldnum ^ 1].white)
+ ldcore->videofields[ldcore->videoindex] = 1;
+
+ /* if we already have both fields on the current videoindex, advance */
+ if (ldcore->videofields[ldcore->videoindex] >= 2)
+ {
+ ldcore->videoindex = (ldcore->videoindex + 1) % ARRAY_LENGTH(ldcore->videofields);
+ ldcore->videofields[ldcore->videoindex] = 0;
+ }
+
+ /* set the video target information */
+ ldcore->videotarget = *ldcore->videoframe[ldcore->videoindex];
+ 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;
+ if (!audio_channel_active(ldcore, 0))
+ ldcore->avconfig.audio[0] = NULL;
+ if (!audio_channel_active(ldcore, 1))
+ ldcore->avconfig.audio[1] = NULL;
+
+ /* configure the codec and then read */
+ if (ldcore->disc != NULL)
+ {
+ err = chd_codec_config(ldcore->disc, AV_CODEC_DECOMPRESS_CONFIG, &ldcore->avconfig);
+ if (err == CHDERR_NONE)
+ {
+ INT32 maxtrack = chd_get_header(ldcore->disc)->totalhunks / 2;
+ INT32 chdtrack = tracknum - 1 - VIRTUAL_LEAD_IN_TRACKS;
+
+ /* clamp the track */
+ chdtrack = MAX(chdtrack, 0);
+ chdtrack = MIN(chdtrack, maxtrack - 1);
+ err = chd_read_async(ldcore->disc, chdtrack * 2 + fieldnum, NULL);
+ ldcore->readpending = TRUE;
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ 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;
+ UINT32 tracknum = ldcore->curtrack;
+ UINT32 fieldnum = ldcore->fieldnum;
+ int frame, chapter;
+ chd_error chderr;
+
+ /* wait for the async operation to complete */
+ if (ldcore->disc != NULL && ldcore->readpending)
+ {
+ /* complete the async operation */
+ chderr = chd_async_complete(ldcore->disc);
+ if (chderr != CHDERR_NONE && chderr != CHDERR_NO_ASYNC_OPERATION)
+ ldcore->avconfig.video = NULL;
+ }
+ ldcore->readpending = FALSE;
+
+ /* parse the metadata */
+ if (ldcore->disc != NULL && ldcore->avconfig.video != NULL)
+ vbi_parse_all((const UINT16 *)ldcore->avconfig.video->base, ldcore->avconfig.video->rowpixels, ldcore->avconfig.video->width, 8, &ldcore->metadata[fieldnum]);
+ else
+ fake_metadata(tracknum, fieldnum, &ldcore->metadata[fieldnum]);
+// printf("Track %5d: Metadata = %d %08X %08X %08X %08X\n", tracknum, ldcore->metadata[fieldnum].white, ldcore->metadata[fieldnum].line16, ldcore->metadata[fieldnum].line17, ldcore->metadata[fieldnum].line18, ldcore->metadata[fieldnum].line1718);
+
+ /* update the last seen frame and chapter */
+ frame = frame_from_metadata(&ldcore->metadata[fieldnum]);
+ if (frame >= 1 && frame < 99999)
+ ldcore->last_frame = frame;
+ chapter = chapter_from_metadata(&ldcore->metadata[fieldnum]);
+ if (chapter != -1)
+ ldcore->last_chapter = chapter;
+
+ /* 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);
+
+ /* update video field */
+ if (ldcore->avconfig.video != NULL)
+ {
+ ldcore->videofields[ldcore->videoindex]++;
+ ldcore->videoframenum[ldcore->videoindex] = 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;
+}
+
+
+/*-------------------------------------------------
+ fake_metadata - fake metadata when there's
+ no disc present
+-------------------------------------------------*/
+
+static void fake_metadata(UINT32 track, UINT8 which, vbi_metadata *metadata)
+{
+ if (which == 0)
+ {
+ metadata->white = 1;
+ metadata->line16 = 0;
+ metadata->line17 = metadata->line18 = 0xf80000 |
+ (((track / 10000) % 10) << 16) |
+ (((track / 1000) % 10) << 12) |
+ (((track / 100) % 10) << 8) |
+ (((track / 10) % 10) << 4) |
+ (((track / 1) % 10) << 0);
+ }
+ else
+ memset(metadata, 0, sizeof(*metadata));
+}
+
+
+/*-------------------------------------------------
+ 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];
+ int samples_avail = 0;
+
+ /* 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];
+ *dst1++ = buffer1[sampout];
+ 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];
+ stream_sample_t fill1 = buffer1[sampout];
+
+ 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)
+ {
+ 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 = 0;
+ clip.max_x = ldcore->config.overwidth - 1;
+ clip.min_y = cliprect->min_y * overbitmap->height / bitmap->height;
+ 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 == video_screen_get_visible_area(screen)->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;
+}
+
+
+
+/***************************************************************************
+ DEVICE INTERFACE
+***************************************************************************/
+
+/*-------------------------------------------------
+ device start callback
+-------------------------------------------------*/
+
+static DEVICE_START( laserdisc )
+{
+ int fps = 30, fpsfrac = 0, width = 720, height = 240, interlaced = 1, channels = 2, rate = 44100;
+ const laserdisc_config *config = device->inline_config;
+ laserdisc_state *ld = get_safe_token(device);
+ UINT32 fps_times_1million;
+ ldcore_data *ldcore;
+ char metadata[256];
+ int sndnum, index;
+ int statesize;
+ chd_error err;
+
+ /* 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;
+
+ /* find the disc */
+ ldcore->disc = get_disk_handle(device->tag);
+ 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, device->tag) == 0)
+ break;
+ if (device->machine->config->sound[sndnum].type == SOUND_CUSTOM)
+ ldcore->audiocustom++;
+ }
+ if (sndnum == MAX_SOUND)
+ ldcore->audiocustom = -1;
+
+ /* get the disc metadata and extract the ld */
+ if (ldcore->disc != NULL)
+ {
+ /* 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 and extract 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");
+ if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7)
+ fatalerror("Invalid metadata in CHD file");
+
+ /* require interlaced video */
+ if (!interlaced)
+ fatalerror("Laserdisc video must be interlaced!");
+
+ /* determine the maximum track and allocate a frame buffer */
+ ldcore->maxtrack = chd_get_header(ldcore->disc)->totalhunks / 2;
+ }
+ else
+ ldcore->maxtrack = 54000;
+ ldcore->maxtrack += VIRTUAL_LEAD_IN_TRACKS + VIRTUAL_LEAD_OUT_TRACKS;
+
+ /* allocate video frames */
+ for (index = 0; index < ARRAY_LENGTH(ldcore->videofields); index++)
+ {
+ /* first allocate a YUY16 bitmap at 2x the height */
+ ldcore->videoframe[index] = auto_bitmap_alloc(width, height * 2, BITMAP_FORMAT_YUY16);
+ fillbitmap_yuy16(ldcore->videoframe[index], 40, 109, 240);
+
+ /* make a copy of the bitmap that clips out the VBI and horizontal blanking areas */
+ ldcore->videovisframe[index] = auto_malloc(sizeof(*ldcore->videovisframe[index]));
+ *ldcore->videovisframe[index] = *ldcore->videoframe[index];
+ ldcore->videovisframe[index]->base = BITMAP_ADDR16(ldcore->videovisframe[index], 44, ldcore->videoframe[index]->width * 8 / 720);
+ ldcore->videovisframe[index]->height -= 44;
+ ldcore->videovisframe[index]->width -= 2 * ldcore->videoframe[index]->width * 8 / 720;
+ }
+
+ /* allocate an empty frame of the same size */
+ ldcore->emptyframe = auto_bitmap_alloc(width, height * 2, BITMAP_FORMAT_YUY16);
+ fillbitmap_yuy16(ldcore->emptyframe, 0, 128, 128);
+
+ /* allocate audio buffers */
+ fps_times_1million = fps * 1000000 + fpsfrac;
+ ldcore->audiomaxsamples = ((UINT64)rate * 1000000 + fps_times_1million - 1) / 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]));
+ ldcore->samplerate = rate;
+
+ /* 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");
+ }
+
+ /* register callbacks */
+ config_register(device->machine, "laserdisc", configuration_load, configuration_save);
+}
+
+
+/*-------------------------------------------------
+ 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);
+ 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->videosquelch = 1;
+ ldcore->audiosquelch = 3;
+
+ /* default to track 1 */
+ ldcore->curtrack = 1;
+ ldcore->last_frame = 0;
+ ldcore->last_chapter = 0;
+
+ /* 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;
+ int pltype;
+
+ /* if we have a device, figure out where our config lives */
+ if (device != NULL)
+ {
+ laserdisc_state *ld = device->token;
+ config = (ld == NULL || ld->core == NULL) ? device->inline_config : &ld->core->config;
+ }
+
+ 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 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 = "Unknown Laserdisc Player";
+ for (pltype = 0; pltype < ARRAY_LENGTH(player_interfaces); pltype++)
+ if (player_interfaces[pltype]->type == config->type)
+ info->s = player_interfaces[pltype]->name;
+ 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;
+ }
+}
diff --git a/src/emu/machine/ldcore.h b/src/emu/machine/ldcore.h
new file mode 100644
index 00000000000..74b912212ea
--- /dev/null
+++ b/src/emu/machine/ldcore.h
@@ -0,0 +1,247 @@
+/*************************************************************************
+
+ ldcore.h
+
+ Private core laserdisc player implementation.
+
+ Copyright Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+*************************************************************************/
+
+#pragma once
+
+#ifndef __LDCORE_H__
+#define __LDCORE_H__
+
+#include "driver.h"
+#include "laserdsc.h"
+#include "vbiparse.h"
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+/* common laserdisc states */
+enum
+{
+ LDSTATE_NONE, /* unspecified state */
+ LDSTATE_EJECTING, /* in the process of ejecting */
+ LDSTATE_EJECTED, /* fully ejected */
+ LDSTATE_PARKED, /* head parked in lead-in */
+ LDSTATE_LOADING, /* loading from ejected state */
+ LDSTATE_SPINUP, /* spinning up */
+ LDSTATE_PAUSING, /* looking for a frame boundary to pause */
+ LDSTATE_PAUSED, /* found a frame boundary; now paused */
+ /* parameter specifies the fieldnum of the first frame */
+ LDSTATE_PLAYING, /* playing forward normally, with audio */
+ /* parameter specifies the target frame, or 0 if none */
+ LDSTATE_PLAYING_SLOW_REVERSE, /* playing slow in the reverse direction, with no audio */
+ /* parameter specifies the number of times to repeat each track */
+ LDSTATE_PLAYING_SLOW_FORWARD, /* playing slow in the forward direction, with no audio */
+ /* parameter specifies the number of times to repeat each track */
+ LDSTATE_PLAYING_FAST_REVERSE, /* playing fast in the reverse direction, with no audio */
+ /* parameter specifies the number of frames to skip backwards after each frame */
+ LDSTATE_PLAYING_FAST_FORWARD, /* playing fast in the forward direction, with no audio */
+ /* parameter specifies the number of frames to skip forwards after each frame */
+ LDSTATE_STEPPING_REVERSE, /* single frame stepping in the reverse direction */
+ LDSTATE_STEPPING_FORWARD, /* single frame stepping in the forward direction */
+ LDSTATE_SCANNING, /* scanning in the forward or reverse direction */
+ /* parameter(0:7) controls how many vsyncs until revert to savestate */
+ /* parameter(8:31) specifies the speed */
+ LDSTATE_SEEKING, /* seeking to a specific frame */
+ /* parameter specifies the target frame */
+ LDSTATE_OTHER /* other states start here */
+};
+
+/* special frame and chapter numbers from VBI conversion */
+#define FRAME_NOT_PRESENT -2 /* no frame number information present */
+#define FRAME_LEAD_IN -1 /* lead-in code detected */
+#define FRAME_LEAD_OUT 99999 /* lead-out code detected */
+#define CHAPTER_NOT_PRESENT -2 /* no chapter number information present */
+#define CHAPTER_LEAD_IN -1 /* lead-in code detected */
+#define CHAPTER_LEAD_OUT 100 /* lead-out code detected */
+
+/* generic head movement speeds; use player-specific information where appropriate */
+#define GENERIC_SLOW_SPEED (5) /* 1/5 normal speed */
+#define GENERIC_FAST_SPEED (3) /* 3x normal speed */
+#define GENERIC_SCAN_SPEED (50) /* 50x normal speed */
+#define GENERIC_SEARCH_SPEED (5000) /* 5000x normal speed */
+
+/* generic timings; use player-specific information where appropriate */
+#define GENERIC_EJECT_TIME (ATTOTIME_IN_SEC(5))
+#define GENERIC_SPINUP_TIME (ATTOTIME_IN_SEC(2))
+#define GENERIC_LOAD_TIME (ATTOTIME_IN_SEC(5))
+
+
+
+/***************************************************************************
+ MACROS
+***************************************************************************/
+
+#define SCANNING_PARAM(speed,duration) (((speed) << 8) | ((duration) & 0xff))
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/* core-specific and player-specific data */
+typedef struct _ldplayer_data ldplayer_data;
+typedef struct _ldcore_data ldcore_data;
+
+
+/* player state */
+typedef struct _ldplayer_state ldplayer_state;
+struct _ldplayer_state
+{
+ UINT8 state; /* current state */
+ INT32 substate; /* internal sub-state; starts at 0 on any state change */
+ INT32 param; /* parameter for current state */
+ attotime endtime; /* minimum ending time for current state */
+};
+
+
+/* generic data */
+typedef struct _laserdisc_state laserdisc_state;
+struct _laserdisc_state
+{
+ const device_config * device; /* pointer to owning device */
+ ldcore_data * core; /* private core data */
+ ldplayer_data * player; /* private player data */
+
+ ldplayer_state state; /* active state */
+ ldplayer_state savestate; /* saved state during temporary operations */
+};
+
+
+/* player-specific callbacks */
+typedef void (*laserdisc_init_func)(laserdisc_state *ld);
+typedef INT32 (*laserdisc_update_func)(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime);
+typedef void (*laserdisc_w_func)(laserdisc_state *ld, UINT8 prev, UINT8 new);
+typedef UINT8 (*laserdisc_r_func)(laserdisc_state *ld);
+
+
+/* player configuration */
+typedef struct _ldplayer_interface ldplayer_interface;
+struct _ldplayer_interface
+{
+ int type; /* type of the player */
+ size_t statesize; /* size of the state */
+ const char * name; /* name of the player */
+ laserdisc_init_func init; /* initialization callback */
+ laserdisc_update_func update; /* update callback */
+ laserdisc_w_func writedata; /* parallel data write */
+ laserdisc_w_func writeline[LASERDISC_INPUT_LINES]; /* single line write */
+ laserdisc_r_func readdata; /* parallel data read */
+ laserdisc_r_func readline[LASERDISC_OUTPUT_LINES]; /* single line read */
+};
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+/* defined by each player */
+extern const ldplayer_interface pr7820_interface;
+extern const ldplayer_interface pr8210_interface;
+extern const ldplayer_interface simutrek_interface;
+extern const ldplayer_interface ldv1000_interface;
+extern const ldplayer_interface ldp1450_interface;
+extern const ldplayer_interface vp932_interface;
+
+
+
+/***************************************************************************
+ FUNCTION PROTOTYPES
+***************************************************************************/
+
+
+/* ----- player interface ----- */
+
+/* return a token with type checking from a device */
+laserdisc_state *ldcore_get_safe_token(const device_config *device);
+
+/* set the left/right audio squelch states */
+void ldcore_set_audio_squelch(laserdisc_state *ld, UINT8 squelchleft, UINT8 squelchright);
+
+/* set the video squelch state */
+void ldcore_set_video_squelch(laserdisc_state *ld, UINT8 squelch);
+
+
+
+/* ----- generic implementations ----- */
+
+/* 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 *curstate);
+
+
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ is_start_of_frame - return TRUE if this is
+ the start of a frame
+-------------------------------------------------*/
+
+INLINE int is_start_of_frame(const vbi_metadata *vbi)
+{
+ /* is it not known if the white flag or the presence of a frame code
+ determines the start of frame; the former seems to be the "official"
+ way, but the latter seems to be the practical implementation */
+ return (vbi->white || (vbi->line1718 & 0xf80000) == 0xf80000);
+}
+
+
+/*-------------------------------------------------
+ frame_from_metadata - return the frame number
+ encoded in the metadata, if present, or
+ FRAME_NOT_PRESENT
+-------------------------------------------------*/
+
+INLINE int frame_from_metadata(const vbi_metadata *metadata)
+{
+ UINT32 data;
+
+ if ((metadata->line1718 & 0xf80000) == 0xf80000)
+ data = metadata->line1718 & 0x7ffff;
+ else if (metadata->line1718 == 0x88ffff)
+ return FRAME_LEAD_IN;
+ else if (metadata->line1718 == 0x80eeee)
+ return FRAME_LEAD_OUT;
+ else
+ return FRAME_NOT_PRESENT;
+
+ return (((data >> 16) & 0x0f) * 10000) + (((data >> 12) & 0x0f) * 1000) + (((data >> 8) & 0x0f) * 100) + (((data >> 4) & 0x0f) * 10) + (data & 0x0f);
+}
+
+
+/*-------------------------------------------------
+ chapter_from_metadata - return the chapter
+ number encoded in the metadata, if present,
+ or CHAPTER_NOT_PRESENT
+-------------------------------------------------*/
+
+INLINE int chapter_from_metadata(const vbi_metadata *metadata)
+{
+ UINT32 data;
+
+ if ((metadata->line1718 & 0xf00fff) == 0x800ddd)
+ data = metadata->line1718 & 0x7f000;
+ else if (metadata->line1718 == 0x88ffff)
+ return CHAPTER_LEAD_IN;
+ else if (metadata->line1718 == 0x80eeee)
+ return CHAPTER_LEAD_OUT;
+ else
+ return CHAPTER_NOT_PRESENT;
+
+ return (((data >> 16) & 0x0f) * 10) + ((data >> 12) & 0x0f);
+}
+
+
+#endif
diff --git a/src/emu/machine/ldpr8210.c b/src/emu/machine/ldpr8210.c
new file mode 100644
index 00000000000..de6cea47286
--- /dev/null
+++ b/src/emu/machine/ldpr8210.c
@@ -0,0 +1,722 @@
+/*************************************************************************
+
+ ldpr8210.c
+
+ Pioneer PR-8210 laserdisc emulation.
+
+ Copyright Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+*************************************************************************/
+
+#include "ldcore.h"
+
+
+
+/***************************************************************************
+ DEBUGGING
+***************************************************************************/
+
+#define PRINTF_COMMANDS 1
+#define CMDPRINTF(x) do { if (PRINTF_COMMANDS) mame_printf_debug x; } while (0)
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+/* Player-specific states */
+enum
+{
+ LDSTATE_STEPPING_BY_PARAMETER = LDSTATE_OTHER
+};
+
+/* Pioneer PR-8210 specific information */
+#define PR8210_SCAN_SPEED (2000 / 30) /* 2000 frames/second */
+#define PR8210_SCAN_DURATION (10) /* scan for 10 vsyncs each time */
+#define PR8210_SLOW_SPEED (5) /* 1/5x normal speed */
+#define PR8210_FAST_SPEED (3) /* 3x normal speed */
+#define PR8210_JUMP_REV_SPEED (15) /* 15x normal speed */
+#define PR8210_SEEK_FAST_SPEED (1000) /* 1000x normal speed */
+
+/* Us vs. Them requires at least this much "non-video" time in order to work */
+#define PR8210_MINIMUM_SEEK_TIME ATTOTIME_IN_MSEC(150)
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/* player-specific data */
+struct _ldplayer_data
+{
+ /* general modes and states */
+ UINT8 audio1disable; /* explicit disable for audio 1 */
+ UINT8 audio2disable; /* explicit disable for audio 2 */
+ UINT8 framedisplay; /* frame display */
+ INT32 parameter; /* parameter for numbers */
+
+ /* serial/command interface processing */
+ UINT8 lastcommand; /* last command byte received */
+ UINT8 seekstate; /* state of the seek command */
+ UINT16 accumulator; /* bit accumulator */
+ attotime lastbittime; /* time of last bit received */
+ attotime firstbittime; /* time of first bit in command */
+
+ /* Simutrek-specific data */
+ UINT8 cmdcnt; /* counter for multi-byte command */
+ UINT8 cmdbytes[3]; /* storage for multi-byte command */
+ void (*cmd_ack_callback)(void); /* callback to clear game command write flag */
+};
+
+
+
+/***************************************************************************
+ FUNCTION PROTOTYPES
+***************************************************************************/
+
+static void pr8210_init(laserdisc_state *ld);
+static void pr8210_soft_reset(laserdisc_state *ld);
+static void pr8210_update_squelch(laserdisc_state *ld);
+static int pr8210_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 stateparam);
+static INT32 pr8210_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime);
+static void pr8210_command(laserdisc_state *ld);
+static void pr8210_control_w(laserdisc_state *ld, UINT8 prev, UINT8 data);
+
+static void simutrek_init(laserdisc_state *ld);
+static UINT8 simutrek_status_r(laserdisc_state *ld);
+static void simutrek_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data);
+
+
+
+/***************************************************************************
+ INTERFACES
+***************************************************************************/
+
+const ldplayer_interface pr8210_interface =
+{
+ LASERDISC_TYPE_PIONEER_PR8210, /* type of the player */
+ sizeof(ldplayer_data), /* size of the state */
+ "Pioneer PR-8210", /* name of the player */
+ pr8210_init, /* initialization callback */
+ pr8210_update, /* update callback */
+ NULL, /* parallel data write */
+ { /* single line write: */
+ NULL, /* LASERDISC_LINE_ENTER */
+ pr8210_control_w /* LASERDISC_LINE_CONTROL */
+ },
+ NULL, /* parallel data read */
+ { /* single line read: */
+ NULL, /* LASERDISC_LINE_READY */
+ NULL, /* LASERDISC_LINE_STATUS */
+ NULL, /* LASERDISC_LINE_COMMAND */
+ NULL, /* LASERDISC_LINE_DATA_AVAIL */
+ }
+};
+
+
+const ldplayer_interface simutrek_interface =
+{
+ LASERDISC_TYPE_SIMUTREK_SPECIAL, /* type of the player */
+ sizeof(ldplayer_data), /* size of the state */
+ "Simutrek Modified PR-8210", /* name of the player */
+ simutrek_init, /* initialization callback */
+ pr8210_update, /* update callback */
+ simutrek_data_w, /* parallel data write */
+ { /* single line write: */
+ NULL, /* LASERDISC_LINE_ENTER */
+ NULL /* LASERDISC_LINE_CONTROL */
+ },
+ NULL, /* parallel data read */
+ { /* single line read: */
+ NULL, /* LASERDISC_LINE_READY */
+ simutrek_status_r, /* LASERDISC_LINE_STATUS */
+ NULL, /* LASERDISC_LINE_COMMAND */
+ NULL, /* LASERDISC_LINE_DATA_AVAIL */
+ }
+};
+
+
+
+/***************************************************************************
+ PIONEER PR-8210 IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ requires_state_save - returns TRUE if the
+ given state will return to the previous
+ state when done
+-------------------------------------------------*/
+
+INLINE int requires_state_save(UINT8 state)
+{
+ return (state == LDSTATE_SCANNING);
+}
+
+
+
+/***************************************************************************
+ PIONEER PR-8210 IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ pr8210_init - Pioneer PR-8210-specific
+ initialization
+-------------------------------------------------*/
+
+static void pr8210_init(laserdisc_state *ld)
+{
+ /* do a soft reset */
+ pr8210_soft_reset(ld);
+}
+
+
+/*-------------------------------------------------
+ pr8210_soft_reset - Pioneer PR-8210-specific
+ soft reset
+-------------------------------------------------*/
+
+static void pr8210_soft_reset(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+ attotime curtime = timer_get_time();
+
+ ld->state.state = LDSTATE_NONE;
+ ld->state.substate = 0;
+ ld->state.param = 0;
+ ld->state.endtime = curtime;
+
+ player->audio1disable = 0;
+ player->audio2disable = 0;
+ player->parameter = 0;
+
+ player->lastcommand = 0;
+ player->seekstate = 0;
+ player->accumulator = 0;
+ player->firstbittime = curtime;
+ player->lastbittime = curtime;
+
+ pr8210_switch_state(ld, LDSTATE_PARKED, 0);
+}
+
+
+/*-------------------------------------------------
+ pr8210_update_squelch - update the squelch
+ settings based on the current state
+-------------------------------------------------*/
+
+static void pr8210_update_squelch(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+
+ switch (ld->state.state)
+ {
+ /* video on, audio on */
+ case LDSTATE_PLAYING:
+ ldcore_set_audio_squelch(ld, player->audio1disable, player->audio2disable);
+ ldcore_set_video_squelch(ld, FALSE);
+ break;
+
+ /* video on, audio off */
+ case LDSTATE_PAUSING:
+ case LDSTATE_PAUSED:
+ case LDSTATE_PLAYING_SLOW_REVERSE:
+ case LDSTATE_PLAYING_SLOW_FORWARD:
+ case LDSTATE_PLAYING_FAST_REVERSE:
+ case LDSTATE_PLAYING_FAST_FORWARD:
+ case LDSTATE_STEPPING_REVERSE:
+ case LDSTATE_STEPPING_FORWARD:
+ case LDSTATE_SCANNING:
+ ldcore_set_audio_squelch(ld, TRUE, TRUE);
+ ldcore_set_video_squelch(ld, FALSE);
+ break;
+
+ /* video off, audio off */
+ case LDSTATE_NONE:
+ case LDSTATE_EJECTED:
+ case LDSTATE_EJECTING:
+ case LDSTATE_PARKED:
+ case LDSTATE_LOADING:
+ case LDSTATE_SPINUP:
+ case LDSTATE_SEEKING:
+ ldcore_set_audio_squelch(ld, TRUE, TRUE);
+ ldcore_set_video_squelch(ld, TRUE);
+ break;
+
+ /* Simutrek: stepping by parameter with explicit audio squelch controls */
+ case LDSTATE_STEPPING_BY_PARAMETER:
+ ldcore_set_video_squelch(ld, FALSE);
+ break;
+ }
+}
+
+
+/*-------------------------------------------------
+ pr8210_switch_state - attempt to switch states
+ if appropriate, and ensure the squelch values
+ are correct
+-------------------------------------------------*/
+
+static int pr8210_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 parameter)
+{
+ attotime curtime = timer_get_time();
+
+ /* if this is the same state, ignore */
+ if (ld->state.state == newstate)
+ return TRUE;
+
+ /* if we're in a timed state, we ignore changes until the time elapses */
+ if (attotime_compare(curtime, ld->state.endtime) < 0)
+ return FALSE;
+
+ /* if moving to a state that requires it, save the old state */
+ if (requires_state_save(newstate) && !requires_state_save(ld->state.state))
+ ld->savestate = ld->state;
+
+ /* set up appropriate squelch and timing */
+ ld->state.state = newstate;
+ ld->state.substate = 0;
+ ld->state.param = parameter;
+ ld->state.endtime = curtime;
+ pr8210_update_squelch(ld);
+
+ /* if we're switching to a timed state, set the appropriate timer */
+ switch (ld->state.state)
+ {
+ case LDSTATE_EJECTING:
+ ld->state.endtime = attotime_add(curtime, GENERIC_EJECT_TIME);
+ break;
+
+ case LDSTATE_LOADING:
+ ld->state.endtime = attotime_add(curtime, GENERIC_LOAD_TIME);
+ break;
+
+ case LDSTATE_SPINUP:
+ ld->state.endtime = attotime_add(curtime, GENERIC_SPINUP_TIME);
+ break;
+
+ case LDSTATE_SEEKING:
+ ld->state.endtime = attotime_add(curtime, PR8210_MINIMUM_SEEK_TIME);
+ break;
+ }
+ return TRUE;
+}
+
+
+/*-------------------------------------------------
+ pr8210_update - Pioneer PR-8210-specific
+ update callback
+-------------------------------------------------*/
+
+static INT32 pr8210_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime)
+{
+ ldplayer_state newstate;
+ INT32 advanceby = 0;
+ int frame;
+
+ /* handle things based on the state */
+ switch (ld->state.state)
+ {
+ case LDSTATE_EJECTING:
+ case LDSTATE_EJECTED:
+ case LDSTATE_PARKED:
+ case LDSTATE_LOADING:
+ case LDSTATE_SPINUP:
+ case LDSTATE_PAUSING:
+ case LDSTATE_PAUSED:
+ case LDSTATE_PLAYING:
+ case LDSTATE_PLAYING_SLOW_REVERSE:
+ case LDSTATE_PLAYING_SLOW_FORWARD:
+ case LDSTATE_PLAYING_FAST_REVERSE:
+ case LDSTATE_PLAYING_FAST_FORWARD:
+ case LDSTATE_STEPPING_REVERSE:
+ case LDSTATE_STEPPING_FORWARD:
+ case LDSTATE_SCANNING:
+ /* generic behaviors are appropriate for all of these commands */
+ advanceby = ldcore_generic_update(ld, vbi, fieldnum, curtime, &newstate);
+ pr8210_switch_state(ld, newstate.state, newstate.param);
+ 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 == 3 && is_start_of_frame(vbi) && frame == ld->state.param)
+ pr8210_switch_state(ld, LDSTATE_PAUSED, fieldnum);
+
+ /* otherwise, if we got frame data from the VBI, update our seeking logic */
+ else if (ld->state.substate < 3 && frame != FRAME_NOT_PRESENT)
+ {
+ static const INT32 seekspeed[] = { -PR8210_SEEK_FAST_SPEED, PR8210_SEEK_FAST_SPEED, -PR8210_JUMP_REV_SPEED };
+ INT32 curseekspeed = seekspeed[ld->state.substate];
+ INT32 delta = (ld->state.param - 1) - frame;
+
+ if ((curseekspeed < 0 && delta <= 0) || (curseekspeed > 0 && delta >= 0))
+ advanceby = curseekspeed;
+ else
+ ld->state.substate++;
+ }
+
+ /* otherwise, keep advancing until we know what's up */
+ else if (fieldnum == 1)
+ advanceby = 1;
+ break;
+
+ case LDSTATE_STEPPING_BY_PARAMETER:
+ /* advance after the second field of each frame */
+ if (fieldnum == 1)
+ {
+ /* note that we switch directly to PAUSED as we are not looking for frames */
+ advanceby = ld->state.param;
+ pr8210_switch_state(ld, LDSTATE_PAUSED, 0);
+ }
+ break;
+ }
+
+ return advanceby;
+}
+
+
+/*-------------------------------------------------
+ pr8210_command - Pioneer PR-8210-specific
+ command processing
+-------------------------------------------------*/
+
+static void pr8210_command(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+ UINT8 cmd = player->lastcommand;
+
+ switch (cmd)
+ {
+ case 0x00: CMDPRINTF(("pr8210: EOC\n"));
+ /* EOC marker - can be safely ignored */
+ break;
+
+ case 0x01: CMDPRINTF(("pr8210: 0\n"));
+ player->parameter = (player->parameter == -1) ? 0 : (player->parameter * 10 + 0);
+ break;
+
+ case 0x02: CMDPRINTF(("pr8210: Slow reverse\n"));
+ pr8210_switch_state(ld, LDSTATE_PLAYING_SLOW_REVERSE, PR8210_SLOW_SPEED);
+ break;
+
+ case 0x03: CMDPRINTF(("pr8210: 8\n"));
+ player->parameter = (player->parameter == -1) ? 8 : (player->parameter * 10 + 8);
+ break;
+
+ case 0x04: CMDPRINTF(("pr8210: Step forward\n"));
+ pr8210_switch_state(ld, LDSTATE_STEPPING_FORWARD, 0);
+ break;
+
+ case 0x05: CMDPRINTF(("pr8210: 4\n"));
+ player->parameter = (player->parameter == -1) ? 4 : (player->parameter * 10 + 4);
+ break;
+
+ case 0x06 : CMDPRINTF(("pr8210: Chapter\n"));
+ /* chapter -- not implemented */
+ break;
+
+ case 0x08: CMDPRINTF(("pr8210: Scan forward\n"));
+ if (ld->state.state != LDSTATE_SCANNING)
+ pr8210_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(PR8210_SCAN_SPEED, PR8210_SCAN_DURATION));
+ else
+ ld->state.substate = 0;
+ break;
+
+ case 0x09: CMDPRINTF(("pr8210: 2\n"));
+ player->parameter = (player->parameter == -1) ? 2 : (player->parameter * 10 + 2);
+ break;
+
+ case 0x0a: CMDPRINTF(("pr8210: Pause\n"));
+ pr8210_switch_state(ld, LDSTATE_PAUSING, 0);
+ break;
+
+ case 0x0b : CMDPRINTF(("pr8210: Frame\n"));
+ /* frame -- not implemented */
+ break;
+
+ case 0x0c: CMDPRINTF(("pr8210: Fast reverse\n"));
+ pr8210_switch_state(ld, LDSTATE_PLAYING_FAST_REVERSE, PR8210_FAST_SPEED);
+ break;
+
+ case 0x0d: CMDPRINTF(("pr8210: 6\n"));
+ player->parameter = (player->parameter == -1) ? 6 : (player->parameter * 10 + 6);
+ break;
+
+ case 0x0e: CMDPRINTF(("pr8210: Ch1 toggle\n"));
+ player->audio1disable = !player->audio1disable;
+ pr8210_update_squelch(ld);
+ break;
+
+ case 0x10: CMDPRINTF(("pr8210: Fast forward\n"));
+ pr8210_switch_state(ld, LDSTATE_PLAYING_FAST_FORWARD, PR8210_FAST_SPEED);
+ break;
+
+ case 0x11: CMDPRINTF(("pr8210: 1\n"));
+ player->parameter = (player->parameter == -1) ? 1 : (player->parameter * 10 + 1);
+ break;
+
+ case 0x12: CMDPRINTF(("pr8210: Step reverse\n"));
+ pr8210_switch_state(ld, LDSTATE_STEPPING_REVERSE, 0);
+ break;
+
+ case 0x13: CMDPRINTF(("pr8210: 9\n"));
+ player->parameter = (player->parameter == -1) ? 9 : (player->parameter * 10 + 9);
+ break;
+
+ case 0x14: CMDPRINTF(("pr8210: Play\n"));
+ if (ld->state.state == LDSTATE_EJECTED)
+ pr8210_switch_state(ld, LDSTATE_LOADING, 0);
+ else if (ld->state.state == LDSTATE_PARKED)
+ pr8210_switch_state(ld, LDSTATE_SPINUP, 0);
+ else
+ pr8210_switch_state(ld, LDSTATE_PLAYING, 0);
+ break;
+
+ case 0x15: CMDPRINTF(("pr8210: 5\n"));
+ player->parameter = (player->parameter == -1) ? 5 : (player->parameter * 10 + 5);
+ break;
+
+ case 0x16: CMDPRINTF(("pr8210: Ch2 toggle\n"));
+ player->audio2disable = !player->audio2disable;
+ pr8210_update_squelch(ld);
+ break;
+
+ case 0x18: CMDPRINTF(("pr8210: Slow forward\n"));
+ pr8210_switch_state(ld, LDSTATE_PLAYING_SLOW_FORWARD, PR8210_SLOW_SPEED);
+ break;
+
+ case 0x19: CMDPRINTF(("pr8210: 3\n"));
+ player->parameter = (player->parameter == -1) ? 3 : (player->parameter * 10 + 3);
+ break;
+
+ case 0x1a: CMDPRINTF(("pr8210: Seek\n"));
+ if (player->seekstate)
+ {
+ /* we're ready to seek */
+ CMDPRINTF(("pr8210: Seeking to frame %d\n", player->parameter));
+ pr8210_switch_state(ld, LDSTATE_SEEKING, player->parameter);
+ }
+ else
+ {
+ /* waiting for digits indicating position */
+ player->parameter = 0;
+ }
+ player->seekstate ^= 1;
+ break;
+
+ case 0x1c: CMDPRINTF(("pr8210: Scan reverse\n"));
+ if (ld->state.state != LDSTATE_SCANNING)
+ pr8210_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(-PR8210_SCAN_SPEED, PR8210_SCAN_DURATION));
+ else
+ ld->state.substate = 0;
+ break;
+
+ case 0x1d: CMDPRINTF(("pr8210: 7\n"));
+ player->parameter = (player->parameter == -1) ? 7 : (player->parameter * 10 + 7);
+ break;
+
+ case 0x1e: CMDPRINTF(("pr8210: Reject\n"));
+ pr8210_switch_state(ld, LDSTATE_EJECTING, 0);
+ break;
+
+ default: CMDPRINTF(("pr8210: Unknown command %02X\n", cmd));
+ break;
+ }
+}
+
+
+/*-------------------------------------------------
+ pr8210_control_w - write callback when the
+ CONTROL line is toggled
+-------------------------------------------------*/
+
+static void pr8210_control_w(laserdisc_state *ld, UINT8 prev, UINT8 data)
+{
+ ldplayer_data *player = ld->player;
+
+ if (data == ASSERT_LINE)
+ {
+ attotime curtime = timer_get_time();
+ attotime delta;
+ int longpulse;
+
+ /* if we timed out, reset the accumulator */
+ delta = attotime_sub(curtime, player->firstbittime);
+ if (delta.attoseconds > ATTOTIME_IN_USEC(25320).attoseconds)
+ {
+ player->firstbittime = curtime;
+ player->accumulator = 0x5555;
+// printf("Reset accumulator\n");
+ }
+
+ /* get the time difference from the last assert */
+ /* and update our internal command time */
+ delta = attotime_sub(curtime, player->lastbittime);
+ player->lastbittime = curtime;
+
+ /* 0 bit delta is 1.05 msec, 1 bit delta is 2.11 msec */
+ longpulse = (delta.attoseconds < ATTOTIME_IN_USEC(1500).attoseconds) ? 0 : 1;
+ player->accumulator = (player->accumulator << 1) | longpulse;
+
+#if 0
+ {
+ int usecdiff = (int)(delta.attoseconds / ATTOSECONDS_IN_USEC(1));
+ printf("bitdelta = %5d (%d) - accum = %04X\n", usecdiff, longpulse, player->accumulator);
+ }
+#endif
+
+ /* if we have a complete command, signal it */
+ /* a complete command is 0,0,1 followed by 5 bits, followed by 0,0 */
+ if ((player->accumulator & 0x383) == 0x80)
+ {
+ UINT8 newcommand = (player->accumulator >> 2) & 0x1f;
+
+//printf("New command = %02X (last=%02X)\n", newcommand, player->lastcommand);
+
+ /* if we got a double command, act on it */
+ if (newcommand == player->lastcommand)
+ {
+ pr8210_command(ld);
+ player->lastcommand = 0;
+ }
+ else
+ player->lastcommand = newcommand;
+ }
+ }
+}
+
+
+
+/***************************************************************************
+ SIMUTREK MODIFIED PR-8210 PLAYER IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+
+ Command Set:
+
+ FX XX XX : Seek to frame XXXXX
+ 01-19 : Skip forward 1-19 frames
+ 99-81 : Skip back 1-19 frames
+ 5a : Toggle frame display
+
+-------------------------------------------------*/
+
+/*-------------------------------------------------
+ simutrek_init - Simutrek-specific
+ initialization
+-------------------------------------------------*/
+
+static void simutrek_init(laserdisc_state *ld)
+{
+ /* do a soft reset */
+ pr8210_soft_reset(ld);
+}
+
+
+/*-------------------------------------------------
+ simutrek_status_r - Simutrek-specific
+ command processing
+-------------------------------------------------*/
+
+static UINT8 simutrek_status_r(laserdisc_state *ld)
+{
+ return (ld->state.state != LDSTATE_SEEKING) ? ASSERT_LINE : CLEAR_LINE;
+}
+
+
+/*-------------------------------------------------
+ simutrek_data_w - Simutrek-specific
+ data processing
+-------------------------------------------------*/
+
+static void simutrek_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data)
+{
+ ldplayer_data *player = ld->player;
+
+ /* Acknowledge every command byte */
+ if (player->cmd_ack_callback != NULL)
+ (*player->cmd_ack_callback)();
+
+ /* Is this byte part of a multi-byte seek command? */
+ if (player->cmdcnt > 0)
+ {
+ CMDPRINTF(("Simutrek: Seek to frame byte %d of 3\n", player->cmdcnt + 1));
+ player->cmdbytes[player->cmdcnt++] = data;
+
+ if (player->cmdcnt == 3)
+ {
+ player->parameter = ((player->cmdbytes[0] & 0xf) * 10000) +
+ ((player->cmdbytes[1] >> 4) * 1000) +
+ ((player->cmdbytes[1] & 0xf) * 100) +
+ ((player->cmdbytes[2] >> 4) * 10) +
+ (player->cmdbytes[2] & 0xf);
+
+ CMDPRINTF(("Simutrek: Seek to frame %d\n", player->parameter));
+
+ pr8210_switch_state(ld, LDSTATE_SEEKING, player->parameter);
+ player->cmdcnt = 0;
+ }
+ }
+ else if (data == 0)
+ {
+ CMDPRINTF(("Simutrek: 0 ?\n"));
+ }
+ else if ((data & 0xf0) == 0xf0)
+ {
+ CMDPRINTF(("Simutrek: Seek to frame byte 1 of 3\n"));
+ player->cmdbytes[player->cmdcnt++] = data;
+ }
+ else if (data >= 1 && data <= 0x19)
+ {
+ int count = ((data >> 4) * 10) + (data & 0xf);
+ CMDPRINTF(("Simutrek: Step forwards by %d frame(s)\n", count));
+ pr8210_switch_state(ld, LDSTATE_STEPPING_BY_PARAMETER, count);
+ }
+ else if (data >= 0x81 && data <= 0x99)
+ {
+ int count = (((data >> 4) * 10) + (data & 0xf)) - 100;
+ CMDPRINTF(("Simutrek: Step backwards by %d frame(s)\n", count));
+ pr8210_switch_state(ld, LDSTATE_STEPPING_BY_PARAMETER, count);
+ }
+ else if (data == 0x5a)
+ {
+ CMDPRINTF(("Simutrek: Frame window toggle\n"));
+ player->framedisplay ^= 1;
+ }
+ else
+ {
+ CMDPRINTF(("Simutrek: Unknown command (%.2x)\n", data));
+ }
+}
+
+
+/*-------------------------------------------------
+ simutrek_set_audio_squelch - Simutrek-specific
+ command to enable/disable audio squelch
+-------------------------------------------------*/
+
+void simutrek_set_audio_squelch(const device_config *device, int state)
+{
+ laserdisc_state *ld = ldcore_get_safe_token(device);
+ int squelch = (state != ASSERT_LINE);
+ ldcore_set_audio_squelch(ld, squelch, squelch);
+}
+
+
+/*-------------------------------------------------
+ simutrek_set_audio_squelch - Simutrek-specific
+ command to set callback function for
+ player/interface command acknowledge
+-------------------------------------------------*/
+
+void simutrek_set_cmd_ack_callback(const device_config *device, void (*callback)(void))
+{
+ laserdisc_state *ld = ldcore_get_safe_token(device);
+ ldplayer_data *player = ld->player;
+
+ player->cmd_ack_callback = callback;
+}
diff --git a/src/emu/machine/ldv1000.c b/src/emu/machine/ldv1000.c
new file mode 100644
index 00000000000..36f00853714
--- /dev/null
+++ b/src/emu/machine/ldv1000.c
@@ -0,0 +1,742 @@
+/*************************************************************************
+
+ ldv1000.c
+
+ Pioneer LD-V1000 laserdisc emulation.
+
+ Copyright Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+*************************************************************************/
+
+#include "ldcore.h"
+
+
+
+/***************************************************************************
+ DEBUGGING
+***************************************************************************/
+
+#define PRINTF_COMMANDS 1
+#define CMDPRINTF(x) do { if (PRINTF_COMMANDS) mame_printf_debug x; } while (0)
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+/* Player-specific states */
+enum
+{
+ LDSTATE_SKIPPING = LDSTATE_OTHER,
+ LDSTATE_WAITING
+};
+
+/* Pioneer LD-V1000 specific information */
+enum
+{
+ LDV1000_MODE_STATUS,
+ LDV1000_MODE_GET_FRAME,
+ LDV1000_MODE_GET_1ST,
+ LDV1000_MODE_GET_2ND,
+ LDV1000_MODE_GET_RAM
+};
+
+#define LDV1000_SCAN_SPEED (2000 / 30) /* 2000 frames/second */
+#define LDV1000_SCAN_DURATION (4) /* scan for 4 vsyncs each time */
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/* player-specific data */
+struct _ldplayer_data
+{
+ /* general modes and states */
+ UINT8 audio1disable; /* explicit disable for audio 1 */
+ UINT8 audio2disable; /* explicit disable for audio 2 */
+ UINT8 framedisplay; /* frame display */
+ INT32 parameter; /* parameter for numbers */
+
+ UINT8 mode; /* current mode */
+ UINT8 lastchapter; /* last chapter seen */
+ UINT16 lastframe; /* last frame seen */
+ UINT32 activereg; /* active register index */
+ UINT8 ram[1024]; /* RAM */
+ UINT32 readpos; /* current read position */
+ UINT32 readtotal; /* current read position */
+ UINT8 readbuf[256]; /* temporary read buffer */
+ attotime lastvsynctime; /* time of last update/vsync */
+};
+
+
+
+/***************************************************************************
+ FUNCTION PROTOTYPES
+***************************************************************************/
+
+static void ldv1000_init(laserdisc_state *ld);
+static void ldv1000_soft_reset(laserdisc_state *ld);
+static void ldv1000_update_squelch(laserdisc_state *ld);
+static int ldv1000_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 stateparam);
+static INT32 ldv1000_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime);
+static void ldv1000_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data);
+static UINT8 ldv1000_status_strobe_r(laserdisc_state *ld);
+static UINT8 ldv1000_command_strobe_r(laserdisc_state *ld);
+static UINT8 ldv1000_status_r(laserdisc_state *ld);
+
+
+
+/***************************************************************************
+ INTERFACES
+***************************************************************************/
+
+const ldplayer_interface ldv1000_interface =
+{
+ LASERDISC_TYPE_PIONEER_LDV1000, /* type of the player */
+ sizeof(ldplayer_data), /* size of the state */
+ "Pioneer LD-V1000", /* name of the player */
+ ldv1000_init, /* initialization callback */
+ ldv1000_update, /* update callback */
+ ldv1000_data_w, /* parallel data write */
+ { /* single line write: */
+ NULL, /* LASERDISC_LINE_ENTER */
+ NULL /* LASERDISC_LINE_CONTROL */
+ },
+ ldv1000_status_r, /* parallel data read */
+ { /* single line read: */
+ NULL, /* LASERDISC_LINE_READY */
+ ldv1000_status_strobe_r, /* LASERDISC_LINE_STATUS */
+ ldv1000_command_strobe_r, /* LASERDISC_LINE_COMMAND */
+ NULL, /* LASERDISC_LINE_DATA_AVAIL */
+ }
+};
+
+
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ requires_state_save - returns TRUE if the
+ given state will return to the previous
+ state when done
+-------------------------------------------------*/
+
+INLINE int requires_state_save(UINT8 state)
+{
+ return (state == LDSTATE_SCANNING ||
+ state == LDSTATE_SKIPPING ||
+ state == LDSTATE_WAITING);
+}
+
+
+/*-------------------------------------------------
+ read_16bits_from_ram_be - read 16 bits from
+ player RAM in big-endian format
+-------------------------------------------------*/
+
+INLINE UINT16 read_16bits_from_ram_be(UINT8 *ram, UINT32 offset)
+{
+ return (ram[offset + 0] << 8) | ram[offset + 1];
+}
+
+
+/*-------------------------------------------------
+ write_16bits_to_ram_be - write 16 bits to
+ player RAM in big endian format
+-------------------------------------------------*/
+
+INLINE void write_16bits_to_ram_be(UINT8 *ram, UINT32 offset, UINT16 data)
+{
+ ram[offset + 0] = data >> 8;
+ ram[offset + 1] = data >> 0;
+}
+
+
+
+/***************************************************************************
+ PIONEER LD-V1000 IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ ldv1000_init - Pioneer PR-8210-specific
+ initialization
+-------------------------------------------------*/
+
+static void ldv1000_init(laserdisc_state *ld)
+{
+ /* do a soft reset */
+ ldv1000_soft_reset(ld);
+}
+
+
+/*-------------------------------------------------
+ ldv1000_soft_reset - Pioneer LDV-1000-specific
+ soft reset
+-------------------------------------------------*/
+
+static void ldv1000_soft_reset(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+ attotime curtime = timer_get_time();
+
+ ld->state.state = LDSTATE_NONE;
+ ld->state.substate = 0;
+ ld->state.param = 0;
+ ld->state.endtime = curtime;
+
+ player->audio1disable = 0;
+ player->audio2disable = 0;
+ player->parameter = 0;
+
+ player->mode = 0;
+ player->activereg = 0;
+ memset(player->ram, 0, sizeof(player->ram));
+ player->readpos = 0;
+ player->readtotal = 0;
+ memset(player->readbuf, 0, sizeof(player->readbuf));
+ player->lastvsynctime = curtime;
+
+ ldv1000_switch_state(ld, LDSTATE_PARKED, 0);
+}
+
+
+/*-------------------------------------------------
+ ldv1000_update_squelch - update the squelch
+ settings based on the current state
+-------------------------------------------------*/
+
+static void ldv1000_update_squelch(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+
+ switch (ld->state.state)
+ {
+ /* video on, audio on */
+ case LDSTATE_PLAYING:
+ ldcore_set_audio_squelch(ld, player->audio1disable, player->audio2disable);
+ ldcore_set_video_squelch(ld, FALSE);
+ break;
+
+ /* video on, audio off */
+ case LDSTATE_PAUSING:
+ case LDSTATE_PAUSED:
+ case LDSTATE_PLAYING_SLOW_REVERSE:
+ case LDSTATE_PLAYING_SLOW_FORWARD:
+ case LDSTATE_PLAYING_FAST_REVERSE:
+ case LDSTATE_PLAYING_FAST_FORWARD:
+ case LDSTATE_STEPPING_REVERSE:
+ case LDSTATE_STEPPING_FORWARD:
+ case LDSTATE_SCANNING:
+ case LDSTATE_SKIPPING:
+ case LDSTATE_WAITING:
+ ldcore_set_audio_squelch(ld, TRUE, TRUE);
+ ldcore_set_video_squelch(ld, FALSE);
+ break;
+
+ /* video off, audio off */
+ case LDSTATE_NONE:
+ case LDSTATE_EJECTING:
+ case LDSTATE_EJECTED:
+ case LDSTATE_PARKED:
+ case LDSTATE_LOADING:
+ case LDSTATE_SPINUP:
+ case LDSTATE_SEEKING:
+ ldcore_set_audio_squelch(ld, TRUE, TRUE);
+ ldcore_set_video_squelch(ld, TRUE);
+ break;
+ }
+}
+
+
+/*-------------------------------------------------
+ ldv1000_switch_state - attempt to switch states
+ if appropriate, and ensure the squelch values
+ are correct
+-------------------------------------------------*/
+
+static int ldv1000_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 parameter)
+{
+ attotime curtime = timer_get_time();
+
+ /* if this is the same state, ignore */
+ if (ld->state.state == newstate)
+ return TRUE;
+
+ /* if we're in a timed state, we ignore changes until the time elapses */
+ if (attotime_compare(curtime, ld->state.endtime) < 0)
+ return FALSE;
+
+ /* if moving to a state that requires it, save the old state */
+ if (requires_state_save(newstate) && !requires_state_save(ld->state.state))
+ ld->savestate = ld->state;
+
+ /* set up appropriate squelch and timing */
+ ld->state.state = newstate;
+ ld->state.substate = 0;
+ ld->state.param = parameter;
+ ld->state.endtime = curtime;
+ ldv1000_update_squelch(ld);
+
+ /* if we're switching to a timed state, set the appropriate timer */
+ switch (ld->state.state)
+ {
+ case LDSTATE_EJECTING:
+ ld->state.endtime = attotime_add(curtime, GENERIC_EJECT_TIME);
+ break;
+
+ case LDSTATE_LOADING:
+ ld->state.endtime = attotime_add(curtime, GENERIC_LOAD_TIME);
+ break;
+
+ case LDSTATE_SPINUP:
+ ld->state.endtime = attotime_add(curtime, GENERIC_SPINUP_TIME);
+ break;
+
+ case LDSTATE_WAITING:
+ ld->state.endtime = attotime_add(curtime, attotime_mul(ATTOTIME_IN_MSEC(100), ld->state.param));
+ break;
+ }
+ return TRUE;
+}
+
+
+/*-------------------------------------------------
+ ldv1000_update - Pioneer PR-8210-specific
+ update callback
+-------------------------------------------------*/
+
+static INT32 ldv1000_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime)
+{
+ ldplayer_data *player = ld->player;
+ ldplayer_state newstate;
+ INT32 advanceby = 0;
+ int frame, chapter;
+
+ /* remember the last frame and chapter */
+ player->lastvsynctime = curtime;
+ frame = frame_from_metadata(vbi);
+ if (frame != FRAME_NOT_PRESENT)
+ player->lastframe = frame;
+ chapter = chapter_from_metadata(vbi);
+ if (chapter != CHAPTER_NOT_PRESENT)
+ player->lastchapter = chapter;
+
+ /* handle things based on the state */
+ switch (ld->state.state)
+ {
+ case LDSTATE_EJECTING:
+ case LDSTATE_EJECTED:
+ case LDSTATE_PARKED:
+ case LDSTATE_LOADING:
+ case LDSTATE_SPINUP:
+ case LDSTATE_PAUSING:
+ case LDSTATE_PAUSED:
+ case LDSTATE_PLAYING:
+ case LDSTATE_PLAYING_SLOW_REVERSE:
+ case LDSTATE_PLAYING_SLOW_FORWARD:
+ case LDSTATE_PLAYING_FAST_REVERSE:
+ case LDSTATE_PLAYING_FAST_FORWARD:
+ case LDSTATE_STEPPING_REVERSE:
+ case LDSTATE_STEPPING_FORWARD:
+ case LDSTATE_SCANNING:
+ case LDSTATE_SEEKING:
+ /* generic behaviors are appropriate for all of these commands */
+ advanceby = ldcore_generic_update(ld, vbi, fieldnum, curtime, &newstate);
+ ldv1000_switch_state(ld, newstate.state, newstate.param);
+ break;
+
+ case LDSTATE_SKIPPING:
+ /* just advance and return to previous state */
+ advanceby = ld->state.param;
+ ldv1000_switch_state(ld, ld->savestate.state, ld->savestate.param);
+ break;
+
+ case LDSTATE_WAITING:
+ /* keep trying to switch to the previous state until we succeed */
+ ldv1000_switch_state(ld, ld->savestate.state, ld->savestate.param);
+ break;
+ }
+
+ return advanceby;
+}
+
+
+/*-------------------------------------------------
+ ldv1000_data_w - write callback when the
+ ENTER state is written
+-------------------------------------------------*/
+
+static void ldv1000_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data)
+{
+ ldplayer_data *player = ld->player;
+ int target;
+
+ /* 0xff bytes are used for synchronization */
+ if (data == 0xff)
+ return;
+
+ /* handle commands */
+ switch (data)
+ {
+ case 0x3f: CMDPRINTF(("ldv1000: 0\n"));
+ player->parameter = (player->parameter == -1) ? 0 : (player->parameter * 10 + 0);
+ return;
+
+ case 0x0f: CMDPRINTF(("ldv1000: 1\n"));
+ player->parameter = (player->parameter == -1) ? 1 : (player->parameter * 10 + 1);
+ return;
+
+ case 0x8f: CMDPRINTF(("ldv1000: 2\n"));
+ player->parameter = (player->parameter == -1) ? 2 : (player->parameter * 10 + 2);
+ return;
+
+ case 0x4f: CMDPRINTF(("ldv1000: 3\n"));
+ player->parameter = (player->parameter == -1) ? 3 : (player->parameter * 10 + 3);
+ return;
+
+ case 0x2f: CMDPRINTF(("ldv1000: 4\n"));
+ player->parameter = (player->parameter == -1) ? 4 : (player->parameter * 10 + 4);
+ return;
+
+ case 0xaf: CMDPRINTF(("ldv1000: 5\n"));
+ player->parameter = (player->parameter == -1) ? 5 : (player->parameter * 10 + 5);
+ return;
+
+ case 0x6f: CMDPRINTF(("ldv1000: 6\n"));
+ player->parameter = (player->parameter == -1) ? 6 : (player->parameter * 10 + 6);
+ return;
+
+ case 0x1f: CMDPRINTF(("ldv1000: 7\n"));
+ player->parameter = (player->parameter == -1) ? 7 : (player->parameter * 10 + 7);
+ return;
+
+ case 0x9f: CMDPRINTF(("ldv1000: 8\n"));
+ player->parameter = (player->parameter == -1) ? 8 : (player->parameter * 10 + 8);
+ return;
+
+ case 0x5f: CMDPRINTF(("ldv1000: 9\n"));
+ player->parameter = (player->parameter == -1) ? 9 : (player->parameter * 10 + 9);
+ return;
+
+ case 0x7f: CMDPRINTF(("ldv1000: %d Recall\n", player->parameter));
+ /* set the active register */
+ player->activereg = player->parameter;
+ /* should also display the register value */
+ break;
+
+ case 0x20: CMDPRINTF(("ldv1000: x0 reverse (stop) - Badlands special\n"));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_REVERSE, 0);
+ break;
+
+ case 0x21: CMDPRINTF(("ldv1000: x1/4 reverse - Badlands special\n"));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_REVERSE, 4);
+ break;
+
+ case 0x22: CMDPRINTF(("ldv1000: x1/2 reverse - Badlands special\n"));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_REVERSE, 2);
+ break;
+
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27: CMDPRINTF(("ldv1000: x%d reverse - Badlands special\n", (data & 0x07) - 2));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_REVERSE, (data & 0x07) - 2);
+ break;
+
+ case 0xa0: CMDPRINTF(("ldv1000: x0 forward (stop)\n"));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_FORWARD, 0);
+ break;
+
+ case 0xa1: CMDPRINTF(("ldv1000: x1/4 forward\n"));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_FORWARD, 4);
+ break;
+
+ case 0xa2: CMDPRINTF(("ldv1000: x1/2 forward\n"));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_FORWARD, 2);
+ break;
+
+ case 0xa3:
+ case 0xa4:
+ case 0xa5:
+ case 0xa6:
+ case 0xa7: CMDPRINTF(("ldv1000: x%d forward\n", (data & 0x07) - 2));
+ ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_FORWARD, (data & 0x07) - 2);
+ break;
+
+ case 0xb1:
+ case 0xb2:
+ case 0xb3:
+ case 0xb4:
+ case 0xb5:
+ case 0xb6:
+ case 0xb7:
+ case 0xb8:
+ case 0xb9:
+ case 0xba: CMDPRINTF(("ldv1000: Skip forward %d0\n", data & 0x0f));
+ /* note that this skips tracks, not frames */
+ ldv1000_switch_state(ld, LDSTATE_SKIPPING, 10 * (data & 0x0f));
+ break;
+
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ case 0x38:
+ case 0x39:
+ case 0x3a: CMDPRINTF(("ldv1000: Skip backwards %d0 - Badlands special\n", data & 0x0f));
+ /* note that this skips tracks, not frames */
+ ldv1000_switch_state(ld, LDSTATE_SKIPPING, -10 * (data & 0x0f));
+ break;
+
+ case 0xbf: CMDPRINTF(("ldv1000: %d Clear\n", player->parameter));
+ /* clears register display and removes pending arguments */
+ player->parameter = -1;
+ break;
+
+ case 0xc2: CMDPRINTF(("ldv1000: Get frame no.\n"));
+ /* returns the current frame number */
+ player->mode = LDV1000_MODE_GET_FRAME;
+ player->readpos = 0;
+ player->readtotal = 5;
+ sprintf((char *)player->readbuf, "%05d", player->lastframe);
+ break;
+
+ case 0xc3: CMDPRINTF(("ldv1000: Get 2nd display\n"));
+ /* returns the data from the 2nd display line */
+ player->mode = LDV1000_MODE_GET_2ND;
+ player->readpos = 0;
+ player->readtotal = 8;
+ sprintf((char *)player->readbuf, " \x1c\x1c\x1c");
+ break;
+
+ case 0xc4: CMDPRINTF(("ldv1000: Get 1st display\n"));
+ /* returns the data from the 1st display line */
+ player->mode = LDV1000_MODE_GET_1ST;
+ player->readpos = 0;
+ player->readtotal = 8;
+ sprintf((char *)player->readbuf, " \x1c\x1c\x1c");
+ break;
+
+ case 0xc8: CMDPRINTF(("ldv1000: Transfer memory\n"));
+ /* returns the data from RAM */
+ player->mode = LDV1000_MODE_GET_RAM;
+ player->readpos = 0;
+ player->readtotal = 1024;
+ break;
+
+ case 0xcc: CMDPRINTF(("ldv1000: Load\n"));
+ /* load program from disc -- not implemented */
+ break;
+
+ case 0xcd: CMDPRINTF(("ldv1000: Display disable\n"));
+ /* disables the display of current command -- not implemented */
+ break;
+
+ case 0xce: CMDPRINTF(("ldv1000: Display enable\n"));
+ /* enables the display of current command -- not implemented */
+ break;
+
+ case 0xf0: CMDPRINTF(("ldv1000: Scan forward\n"));
+ if (ld->state.state != LDSTATE_SCANNING)
+ ldv1000_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(LDV1000_SCAN_SPEED, LDV1000_SCAN_DURATION));
+ else
+ ld->state.substate = 0;
+ break;
+
+ case 0xf1: CMDPRINTF(("ldv1000: %d Display\n", player->parameter));
+ player->framedisplay = (player->parameter == -1) ? !player->framedisplay : (player->parameter & 1);
+ break;
+
+ case 0xf3: CMDPRINTF(("ldv1000: %d Autostop\n", player->parameter));
+ target = (player->parameter != -1) ? player->parameter : read_16bits_from_ram_be(player->ram, (player->activereg++ * 2) % 1024);
+ if (target > player->lastframe)
+ ldv1000_switch_state(ld, LDSTATE_PLAYING, target);
+ else
+ ldv1000_switch_state(ld, LDSTATE_SEEKING, target);
+ break;
+
+ case 0xf4: CMDPRINTF(("ldv1000: %d Audio track 1\n", player->parameter));
+ if (player->parameter == -1)
+ player->audio1disable ^= 1;
+ else
+ player->audio1disable = ~player->parameter & 1;
+ break;
+
+ case 0xf5: CMDPRINTF(("ldv1000: %d Store\n", player->parameter));
+ /* store either the current frame number or an explicit value into the active register */
+ if (player->parameter == -1)
+ write_16bits_to_ram_be(player->ram, (player->activereg * 2) % 1024, player->lastframe);
+ else
+ write_16bits_to_ram_be(player->ram, (player->activereg * 2) % 1024, player->parameter);
+ player->activereg++;
+ break;
+
+ case 0xf6: CMDPRINTF(("ldv1000: Step forward\n"));
+ ldv1000_switch_state(ld, LDSTATE_STEPPING_FORWARD, 0);
+ break;
+
+ case 0xf7: CMDPRINTF(("ldv1000: %d Search\n", player->parameter));
+ target = (player->parameter != -1) ? player->parameter : read_16bits_from_ram_be(player->ram, (player->activereg * 2) % 1024);
+ player->activereg++;
+ ldv1000_switch_state(ld, LDSTATE_SEEKING, target);
+ break;
+
+ case 0xf8: CMDPRINTF(("ldv1000: Scan reverse\n"));
+ if (ld->state.state != LDSTATE_SCANNING)
+ ldv1000_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(-LDV1000_SCAN_SPEED, LDV1000_SCAN_DURATION));
+ else
+ ld->state.substate = 0;
+ break;
+
+ case 0xf9: CMDPRINTF(("ldv1000: Reject\n"));
+ ldv1000_switch_state(ld, LDSTATE_EJECTING, 0);
+ break;
+
+ case 0xfb: CMDPRINTF(("ldv1000: %d Stop/Wait\n", player->parameter));
+ ldv1000_switch_state(ld, LDSTATE_WAITING, player->parameter);
+ break;
+
+ case 0xfc: CMDPRINTF(("ldv1000: %d Audio track 2\n", player->parameter));
+ if (player->parameter == -1)
+ player->audio2disable ^= 1;
+ else
+ player->audio2disable = ~player->parameter & 1;
+ break;
+
+ case 0xfd: CMDPRINTF(("ldv1000: Play\n"));
+ if (ld->state.state == LDSTATE_EJECTED)
+ ldv1000_switch_state(ld, LDSTATE_LOADING, 0);
+ else if (ld->state.state == LDSTATE_PARKED)
+ ldv1000_switch_state(ld, LDSTATE_SPINUP, 0);
+ else
+ ldv1000_switch_state(ld, LDSTATE_PLAYING, -1);
+ break;
+
+ case 0xfe: CMDPRINTF(("ldv1000: Step reverse\n"));
+ ldv1000_switch_state(ld, LDSTATE_STEPPING_REVERSE, 0);
+ break;
+
+ default: CMDPRINTF(("ldv1000: %d Unknown command %02X\n", player->parameter, data));
+ /* unknown command */
+ break;
+ }
+
+ /* reset the parameter after executing a command */
+ player->parameter = -1;
+}
+
+
+/*-------------------------------------------------
+ ldv1000_status_strobe_r - return state of the
+ status strobe
+-------------------------------------------------*/
+
+static UINT8 ldv1000_status_strobe_r(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+
+ /* the status strobe is asserted (active low) 500-650usec after VSYNC */
+ /* for a duration of 26usec; we pick 600-626usec */
+ attotime delta = attotime_sub(timer_get_time(), player->lastvsynctime);
+ if (delta.attoseconds >= ATTOTIME_IN_USEC(600).attoseconds && delta.attoseconds < ATTOTIME_IN_USEC(626).attoseconds)
+ return ASSERT_LINE;
+
+ return CLEAR_LINE;
+}
+
+
+/*-------------------------------------------------
+ ldv1000_command_strobe_r - return state of the
+ command strobe
+-------------------------------------------------*/
+
+static UINT8 ldv1000_command_strobe_r(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+
+ /* the command strobe is asserted (active low) 54 or 84usec after the status */
+ /* strobe for a duration of 25usec; we pick 600+84 = 684-709usec */
+ /* for a duration of 26usec; we pick 600-626usec */
+ attotime delta = attotime_sub(timer_get_time(), player->lastvsynctime);
+ if (delta.attoseconds >= ATTOTIME_IN_USEC(684).attoseconds && delta.attoseconds < ATTOTIME_IN_USEC(709).attoseconds)
+ return ASSERT_LINE;
+
+ return CLEAR_LINE;
+}
+
+
+/*-------------------------------------------------
+ ldv1000_status_r - return state of the ready
+ line
+-------------------------------------------------*/
+
+static UINT8 ldv1000_status_r(laserdisc_state *ld)
+{
+ ldplayer_data *player = ld->player;
+ UINT8 status = 0xff;
+
+ /* switch off the current mode */
+ switch (player->mode)
+ {
+ /* reading frame number returns 5 characters */
+ /* reading display lines returns 8 characters */
+ case LDV1000_MODE_GET_FRAME:
+ case LDV1000_MODE_GET_1ST:
+ case LDV1000_MODE_GET_2ND:
+ assert(player->readpos < player->readtotal);
+ status = player->readbuf[player->readpos++];
+ if (player->readpos == player->readtotal)
+ player->mode = LDV1000_MODE_STATUS;
+ break;
+
+ /* reading RAM returns 1024 bytes */
+ case LDV1000_MODE_GET_RAM:
+ assert(player->readpos < player->readtotal);
+ status = player->ram[1023 - player->readpos++];
+ if (player->readpos == player->readtotal)
+ player->mode = LDV1000_MODE_STATUS;
+ break;
+
+ /* otherwise, we just compute a status code */
+ default:
+ case LDV1000_MODE_STATUS:
+ switch (ld->state.state)
+ {
+ case LDSTATE_EJECTING: status = 0x60; break;
+ case LDSTATE_EJECTED: status = 0xe0; break;
+ case LDSTATE_PARKED: status = 0xfc; break;
+ case LDSTATE_LOADING: status = 0x48; break;
+ case LDSTATE_SPINUP: status = 0x48; break;
+ case LDSTATE_PAUSING: status = 0xe5; break;
+ case LDSTATE_PAUSED: status = 0xe5; break;
+ case LDSTATE_PLAYING: status = 0xe4; break;
+ case LDSTATE_PLAYING_SLOW_REVERSE: status = 0xae; break;
+ case LDSTATE_PLAYING_SLOW_FORWARD: status = 0xae; break;
+ case LDSTATE_PLAYING_FAST_REVERSE: status = 0xae; break;
+ case LDSTATE_PLAYING_FAST_FORWARD: status = 0xae; break;
+ case LDSTATE_STEPPING_FORWARD: status = 0xe5; break;
+ case LDSTATE_STEPPING_REVERSE: status = 0xe5; break;
+ case LDSTATE_SCANNING: status = 0x4c; break;
+ case LDSTATE_SEEKING: status = 0x50; break;
+// case LASERDISC_SEARCH_FINISHED: status = 0xd0; break;
+// case LASERDISC_AUTOSTOPPED: status = 0x54; break;
+ default:
+ fatalerror("Unexpected disc state in ldv1000_status_r\n");
+ break;
+ }
+ break;
+ }
+
+ /* bit 7 indicates our busy status */
+ return status;
+}