diff options
author | 2008-09-04 08:48:57 +0000 | |
---|---|---|
committer | 2008-09-04 08:48:57 +0000 | |
commit | c1f0164f5d02bf60dce10b58503bd2fea2cec951 (patch) | |
tree | 0eaf4f47edbf064bd53df0359274e409a09d9b45 /src | |
parent | b660cc0f7024b0ed063fd4b6d5e928e12ec08a81 (diff) |
Split core laserdisc functionality into separate file ldcore.c.
Each player now gets its own source file, along with new hooks
which enable more precise control over the behavior. Updated the
PR-8210 and LD-V1000 implementations to the new spec. Other
players will come online shortly.
Changed scan behavior so that it requires a constant stream of
signals to continue scanning. Updated ldplayer accordingly.
Diffstat (limited to 'src')
-rw-r--r-- | src/emu/emu.mak | 4 | ||||
-rw-r--r-- | src/emu/machine/laserdsc.h | 3 | ||||
-rw-r--r-- | src/emu/machine/ldcore.c | 1576 | ||||
-rw-r--r-- | src/emu/machine/ldcore.h | 247 | ||||
-rw-r--r-- | src/emu/machine/ldpr8210.c | 722 | ||||
-rw-r--r-- | src/emu/machine/ldv1000.c | 742 | ||||
-rw-r--r-- | src/ldplayer/ldplayer.c | 66 | ||||
-rw-r--r-- | src/mame/drivers/esh.c | 4 | ||||
-rw-r--r-- | src/mame/drivers/gpworld.c | 3 | ||||
-rw-r--r-- | src/mame/drivers/istellar.c | 3 | ||||
-rw-r--r-- | src/mame/drivers/lgp.c | 3 | ||||
-rw-r--r-- | src/mame/drivers/segald.c | 3 | ||||
-rw-r--r-- | src/mame/drivers/superdq.c | 3 |
13 files changed, 3314 insertions, 65 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; +} diff --git a/src/ldplayer/ldplayer.c b/src/ldplayer/ldplayer.c index 1144dc41777..ed83ac7cc93 100644 --- a/src/ldplayer/ldplayer.c +++ b/src/ldplayer/ldplayer.c @@ -29,10 +29,8 @@ enum { CMD_SCAN_REVERSE, - CMD_SCAN_REVERSE_END, CMD_STEP_REVERSE, CMD_SCAN_FORWARD, - CMD_SCAN_FORWARD_END, CMD_STEP_FORWARD, CMD_PLAY, CMD_PAUSE, @@ -96,31 +94,27 @@ static void process_commands(const device_config *laserdisc) int number; /* scan/step backwards */ - if (!(last_controls & 0x01) && (controls & 0x01)) + if (playing) { - if (playing) + if (controls & 0x01) (*execute_command)(laserdisc, CMD_SCAN_REVERSE); - else - (*execute_command)(laserdisc, CMD_STEP_REVERSE); } - else if ((last_controls & 0x01) && !(controls & 0x01)) + else { - if (playing) - (*execute_command)(laserdisc, CMD_SCAN_REVERSE_END); + if (!(last_controls & 0x01) && (controls & 0x01)) + (*execute_command)(laserdisc, CMD_STEP_REVERSE); } /* scan/step forwards */ - if (!(last_controls & 0x02) && (controls & 0x02)) + if (playing) { - if (playing) + if (controls & 0x02) (*execute_command)(laserdisc, CMD_SCAN_FORWARD); - else - (*execute_command)(laserdisc, CMD_STEP_FORWARD); } - else if ((last_controls & 0x02) && !(controls & 0x02)) + else { - if (playing) - (*execute_command)(laserdisc, CMD_SCAN_FORWARD_END); + if (!(last_controls & 0x02) && (controls & 0x02)) + (*execute_command)(laserdisc, CMD_STEP_FORWARD); } /* play/pause */ @@ -158,10 +152,12 @@ static TIMER_CALLBACK( vsync_update ) /* handle commands */ if (!param) + { process_commands(laserdisc); - /* update the laserdisc */ - laserdisc_vsync(laserdisc); + /* update the laserdisc */ + laserdisc_vsync(laserdisc); + } /* set a timer to go off on the next VBLANK */ vblank_scanline = video_screen_get_visible_area(machine->primary_screen)->max_y + 1; @@ -279,13 +275,11 @@ static void pr8210_execute(const device_config *laserdisc, int command) switch (command) { case CMD_SCAN_REVERSE: - pr8210_add_command(0x1c); - playing = TRUE; - break; - - case CMD_SCAN_REVERSE_END: - pr8210_add_command(0x14); - playing = TRUE; + if (pr8210_command_buffer_in == pr8210_command_buffer_out) + { + pr8210_add_command(0x1c); + playing = TRUE; + } break; case CMD_STEP_REVERSE: @@ -294,13 +288,11 @@ static void pr8210_execute(const device_config *laserdisc, int command) break; case CMD_SCAN_FORWARD: - pr8210_add_command(0x08); - playing = TRUE; - break; - - case CMD_SCAN_FORWARD_END: - pr8210_add_command(0x14); - playing = TRUE; + if (pr8210_command_buffer_in == pr8210_command_buffer_out) + { + pr8210_add_command(0x08); + playing = TRUE; + } break; case CMD_STEP_FORWARD: @@ -369,11 +361,6 @@ static void ldv1000_execute(const device_config *laserdisc, int command) playing = TRUE; break; - case CMD_SCAN_REVERSE_END: - laserdisc_data_w(laserdisc, 0xfd); - playing = TRUE; - break; - case CMD_STEP_REVERSE: laserdisc_data_w(laserdisc, 0xfe); playing = FALSE; @@ -384,11 +371,6 @@ static void ldv1000_execute(const device_config *laserdisc, int command) playing = TRUE; break; - case CMD_SCAN_FORWARD_END: - laserdisc_data_w(laserdisc, 0xfd); - playing = TRUE; - break; - case CMD_STEP_FORWARD: laserdisc_data_w(laserdisc, 0xf6); playing = FALSE; diff --git a/src/mame/drivers/esh.c b/src/mame/drivers/esh.c index 7d980d2f6a7..e70dbe8c2a3 100644 --- a/src/mame/drivers/esh.c +++ b/src/mame/drivers/esh.c @@ -47,10 +47,6 @@ static VIDEO_UPDATE( esh ) /* clear */ fillbitmap(bitmap, 0, cliprect); - /* display disc information */ - if (ld_video_visible) - popmessage("%s", laserdisc_describe_state(laserdisc)); - /* Draw tiles */ for (charx = 0; charx < 32; charx++) { diff --git a/src/mame/drivers/gpworld.c b/src/mame/drivers/gpworld.c index d86f5b1c81e..1c4b2a401d7 100644 --- a/src/mame/drivers/gpworld.c +++ b/src/mame/drivers/gpworld.c @@ -210,9 +210,6 @@ static VIDEO_UPDATE( gpworld ) gpworld_draw_tiles(screen->machine, bitmap, cliprect); gpworld_draw_sprites(screen->machine, bitmap, cliprect); - /* display disc information */ - popmessage("%s", laserdisc_describe_state(laserdisc)); - return 0; } diff --git a/src/mame/drivers/istellar.c b/src/mame/drivers/istellar.c index 06c5c2281a0..287b6ddbf40 100644 --- a/src/mame/drivers/istellar.c +++ b/src/mame/drivers/istellar.c @@ -49,9 +49,6 @@ static VIDEO_UPDATE( istellar ) /* clear */ fillbitmap(bitmap, 0, cliprect); - /* display disc information */ - popmessage("%s", laserdisc_describe_state(laserdisc)); - /* DEBUG */ /* for (charx = 0; charx < 0x400; charx ++) diff --git a/src/mame/drivers/lgp.c b/src/mame/drivers/lgp.c index 5d238a77d07..e900ab501d0 100644 --- a/src/mame/drivers/lgp.c +++ b/src/mame/drivers/lgp.c @@ -88,9 +88,6 @@ static VIDEO_UPDATE( lgp ) /* clear */ fillbitmap(bitmap, 0, cliprect); - /* display disc information */ - popmessage("%s", laserdisc_describe_state(laserdisc)); - /* Draw tiles */ for (charx = 0; charx < 32; charx++) { diff --git a/src/mame/drivers/segald.c b/src/mame/drivers/segald.c index f51ca6523f0..f621b1d447c 100644 --- a/src/mame/drivers/segald.c +++ b/src/mame/drivers/segald.c @@ -87,9 +87,6 @@ static VIDEO_UPDATE( astron ) astron_draw_characters(screen->machine, bitmap, cliprect); astron_draw_sprites(bitmap, cliprect); - /* display disc information */ - popmessage("%s", laserdisc_describe_state(laserdisc)); - return 0; } diff --git a/src/mame/drivers/superdq.c b/src/mame/drivers/superdq.c index 790fec43e99..ec4bf734c09 100644 --- a/src/mame/drivers/superdq.c +++ b/src/mame/drivers/superdq.c @@ -52,9 +52,6 @@ static VIDEO_UPDATE( superdq ) tilemap_draw(bitmap,cliprect,superdq_tilemap,0,0); - /* display disc information */ - popmessage("%s", laserdisc_describe_state(laserdisc)); - return 0; } |