diff options
Diffstat (limited to 'src/emu')
-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 |
6 files changed, 3290 insertions, 4 deletions
diff --git a/src/emu/emu.mak b/src/emu/emu.mak index e1d5cc79733..b4d2cdecf30 100644 --- a/src/emu/emu.mak +++ b/src/emu/emu.mak @@ -139,7 +139,9 @@ EMUMACHINEOBJS = \ $(EMUMACHINE)/idectrl.o \ $(EMUMACHINE)/intelfsh.o \ $(EMUMACHINE)/latch8.o \ - $(EMUMACHINE)/laserdsc.o \ + $(EMUMACHINE)/ldcore.o \ + $(EMUMACHINE)/ldpr8210.o \ + $(EMUMACHINE)/ldv1000.o \ $(EMUMACHINE)/mb3773.o \ $(EMUMACHINE)/mb87078.o \ $(EMUMACHINE)/mc146818.o \ diff --git a/src/emu/machine/laserdsc.h b/src/emu/machine/laserdsc.h index 6ec9ef25141..6c4c50923d0 100644 --- a/src/emu/machine/laserdsc.h +++ b/src/emu/machine/laserdsc.h @@ -161,9 +161,6 @@ extern const custom_sound_interface laserdisc_custom_interface; /* call this once per field (i.e., 59.94 times/second for NTSC) */ void laserdisc_vsync(const device_config *device); -/* return a textual description of the current state (for debugging) */ -const char *laserdisc_describe_state(const device_config *device); - /* get a bitmap for the current frame (and the frame number) */ UINT32 laserdisc_get_video(const device_config *device, bitmap_t **bitmap); diff --git a/src/emu/machine/ldcore.c b/src/emu/machine/ldcore.c new file mode 100644 index 00000000000..05b6f512858 --- /dev/null +++ b/src/emu/machine/ldcore.c @@ -0,0 +1,1576 @@ +/************************************************************************* + + ldcore.c + + Private core laserdisc player implementation. + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*************************************************************************/ + +#include "driver.h" +#include "ldcore.h" +#include "avcomp.h" +#include "streams.h" +#include "vbiparse.h" +#include "config.h" +#include "sound/custom.h" + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define LOG_POSITION(x) /*printf x*/ + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* we simulate extra lead-in and lead-out tracks */ +#define VIRTUAL_LEAD_IN_TRACKS 200 +#define VIRTUAL_LEAD_OUT_TRACKS 200 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* core-specific data */ +struct _ldcore_data +{ + /* general config */ + laserdisc_config config; /* copy of the inline config */ + ldplayer_interface intf; /* interface to the player */ + + /* disc parameters */ + chd_file * disc; /* handle to the disc itself */ + av_codec_decompress_config avconfig; /* decompression configuration */ + UINT8 readpending; /* true if a read is pending */ + UINT32 maxtrack; /* maximum track number */ + + /* core states */ + UINT8 audiosquelch; /* audio squelch state: bit 0 = audio 1, bit 1 = audio 2 */ + UINT8 videosquelch; /* video squelch state: bit 0 = on/off */ + UINT8 fieldnum; /* field number (0 or 1) */ + INT32 curtrack; /* current track */ + + /* video data */ + bitmap_t * videoframe[3]; /* currently cached frames */ + bitmap_t * videovisframe[3]; /* wrapper around videoframe with only visible lines */ + UINT8 videofields[3]; /* number of fields in each frame */ + UINT32 videoframenum[3]; /* frame number contained in each frame */ + UINT8 videoindex; /* index of the current video buffer */ + bitmap_t videotarget; /* fake target bitmap for decompression */ + bitmap_t * emptyframe; /* blank frame */ + + /* audio data */ + INT16 * audiobuffer[2]; /* buffer for audio samples */ + UINT32 audiobufsize; /* size of buffer */ + UINT32 audiobufin; /* input index */ + UINT32 audiobufout; /* output index */ + UINT32 audiocursamples; /* current samples this track */ + UINT32 audiomaxsamples; /* maximum samples per track */ + int audiocustom; /* custom sound index */ + int samplerate; /* playback samplerate */ + + /* metadata */ + vbi_metadata metadata[2]; /* metadata parsed from the stream, for each field */ + int last_frame; /* last seen frame number */ + int last_chapter; /* last seen chapter number */ + + /* I/O data */ + UINT8 datain; /* current input data value */ + UINT8 linein[LASERDISC_INPUT_LINES]; /* current input line state */ + UINT8 dataout; /* current output data value */ + UINT8 lineout[LASERDISC_OUTPUT_LINES]; /* current output line state */ + + /* video updating */ + UINT8 videoenable; /* is video enabled? */ + render_texture * videotex; /* texture for the video */ + UINT8 overenable; /* is the overlay enabled? */ + bitmap_t * overbitmap[2]; /* overlay bitmaps */ + int overindex; /* index of the overlay bitmap */ + render_texture * overtex; /* texture for the overlay */ +}; + + +/* sound callback info */ +typedef struct _sound_token sound_token; +struct _sound_token +{ + sound_stream * stream; + laserdisc_state * ld; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* generic helper functions */ +static void read_track_data(laserdisc_state *ld); +static void process_track_data(const device_config *device); +static void fake_metadata(UINT32 track, UINT8 which, vbi_metadata *metadata); +//static void render_display(UINT16 *videodata, UINT32 rowpixels, UINT32 width, int frame); +static void *custom_start(int clock, const custom_sound_interface *config); +static void custom_stream_callback(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int samples); +static void configuration_load(running_machine *machine, int config_type, xml_data_node *parentnode); +static void configuration_save(running_machine *machine, int config_type, xml_data_node *parentnode); + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +static const ldplayer_interface *player_interfaces[] = +{ +// &pr7820_interface, + &pr8210_interface, + &simutrek_interface, + &ldv1000_interface, +// &ldp1450_interface, +// &vp932_interface +}; + +const custom_sound_interface laserdisc_custom_interface = +{ + custom_start +}; + +static const UINT8 numberfont[10][8] = +{ + { + 0x30, // ..xx.... + 0x48, // .x..x... + 0x84, // x....x.. + 0x84, // x....x.. + 0x84, // x....x.. + 0x48, // .x..x... + 0x30 // ..xx.... + }, + { + 0x10, // ...x.... + 0x30, // ..xx.... + 0x50, // .x.x.... + 0x10, // ...x.... + 0x10, // ...x.... + 0x10, // ...x.... + 0x7c // .xxxxx.. + }, + { + 0x78, // .xxxx... + 0x84, // x....x.. + 0x04, // .....x.. + 0x38, // ..xxx... + 0x40, // .x...... + 0x80, // x....... + 0xfc // xxxxxx.. + }, + { + 0x78, // .xxxx... + 0x84, // x....x.. + 0x04, // .....x.. + 0x38, // ..xxx... + 0x04, // .....x.. + 0x84, // x....x.. + 0x78 // .xxxx... + }, + { + 0x08, // ....x... + 0x18, // ...xx... + 0x28, // ..x.x... + 0x48, // .x..x... + 0xfc, // xxxxxx.. + 0x08, // ....x... + 0x08 // ....x... + }, + { + 0xfc, // xxxxxx.. + 0x80, // x....... + 0x80, // x....... + 0xf8, // xxxxx... + 0x04, // .....x.. + 0x84, // x....x.. + 0x78 // .xxxx... + }, + { + 0x38, // ..xxx... + 0x40, // .x...... + 0x80, // x....... + 0xf8, // xxxxx... + 0x84, // x....x.. + 0x84, // x....x.. + 0x78 // .xxxx... + }, + { + 0xfc, // xxxxxx.. + 0x04, // .....x.. + 0x08, // ....x... + 0x10, // ...x.... + 0x20, // ..x..... + 0x20, // ..x..... + 0x20 // ..x..... + }, + { + 0x78, // .xxxx... + 0x84, // x....x.. + 0x84, // x....x.. + 0x78, // .xxxx... + 0x84, // x....x.. + 0x84, // x....x.. + 0x78 // .xxxx... + }, + { + 0x78, // .xxxx.. + 0x84, // x....x. + 0x84, // x....x. + 0x7c, // .xxxxx. + 0x04, // .....x. + 0x08, // ....x.. + 0x70, // .xxx... + } +}; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + get_safe_token - makes sure that the passed + in device is, in fact, a laserdisc device +-------------------------------------------------*/ + +INLINE laserdisc_state *get_safe_token(const device_config *device) +{ + assert(device != NULL); + assert(device->token != NULL); + assert(device->type == LASERDISC); + + return (laserdisc_state *)device->token; +} + + +/*------------------------------------------------- + audio_channel_active - return TRUE if the + given audio channel should be output +-------------------------------------------------*/ + +INLINE int audio_channel_active(ldcore_data *ldcore, int channel) +{ + return (~ldcore->audiosquelch >> channel) & 1; +} + + +/*------------------------------------------------- + video_active - return TRUE if the video should + be output +-------------------------------------------------*/ + +INLINE int video_active(ldcore_data *ldcore) +{ + return !ldcore->videosquelch; +} + + +/*------------------------------------------------- + add_to_current_track - add a value to the + current track, stopping if we hit the min or + max +-------------------------------------------------*/ + +INLINE int add_to_current_track(ldcore_data *ldcore, INT32 delta) +{ + ldcore->curtrack += delta; + if (ldcore->curtrack < 1) + { + ldcore->curtrack = 1; + return TRUE; + } + else if (ldcore->curtrack >= ldcore->maxtrack - 1) + { + ldcore->curtrack = ldcore->maxtrack - 1; + return TRUE; + } + return FALSE; +} + + +/*------------------------------------------------- + fillbitmap_yuy16 - fill a YUY16 bitmap with a + given color pattern +-------------------------------------------------*/ + +INLINE void fillbitmap_yuy16(bitmap_t *bitmap, UINT8 yval, UINT8 cr, UINT8 cb) +{ + UINT16 color0 = (yval << 8) | cb; + UINT16 color1 = (yval << 8) | cr; + int x, y; + + /* write 32 bits of color (2 pixels at a time) */ + for (y = 0; y < bitmap->height; y++) + { + UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels; + for (x = 0; x < bitmap->width / 2; x++) + { + *dest++ = color0; + *dest++ = color1; + } + } +} + + + +/*************************************************************************** + GENERIC IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + laserdisc_vsync - call this once per field + on the VSYNC signal +-------------------------------------------------*/ + +void laserdisc_vsync(const device_config *device) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + + /* wait for previous read and decode to finish */ + process_track_data(device); + + /* update the state */ + if (ldcore->intf.update != NULL) + { + INT32 advanceby = (*ldcore->intf.update)(ld, &ldcore->metadata[ldcore->fieldnum], ldcore->fieldnum, timer_get_time()); + add_to_current_track(ldcore, advanceby); + } + + /* flush any audio before we read more */ + if (ldcore->audiocustom != -1) + { + sound_token *token = custom_get_token(ldcore->audiocustom); + stream_update(token->stream); + } + + /* start reading the track data for the next round */ + ldcore->fieldnum ^= 1; + read_track_data(ld); +} + + +/*------------------------------------------------- + laserdisc_data_w - write data to the given + laserdisc player +-------------------------------------------------*/ + +void laserdisc_data_w(const device_config *device, UINT8 data) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + UINT8 prev = ldcore->datain; + ldcore->datain = data; + + /* call through to the player-specific write handler */ + if (ldcore->intf.writedata != NULL) + (*ldcore->intf.writedata)(ld, prev, data); +} + + +/*------------------------------------------------- + laserdisc_line_w - control an input line +-------------------------------------------------*/ + +void laserdisc_line_w(const device_config *device, UINT8 line, UINT8 newstate) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + + assert(line < LASERDISC_INPUT_LINES); + assert(newstate == ASSERT_LINE || newstate == CLEAR_LINE || newstate == PULSE_LINE); + + /* assert */ + if (newstate == ASSERT_LINE || newstate == PULSE_LINE) + { + if (ldcore->linein[line] != ASSERT_LINE) + { + /* call through to the player-specific line handler */ + if (ldcore->intf.writeline[line] != NULL) + (*ldcore->intf.writeline[line])(ld, CLEAR_LINE, ASSERT_LINE); + } + ldcore->linein[line] = ASSERT_LINE; + } + + /* deassert */ + if (newstate == CLEAR_LINE || newstate == PULSE_LINE) + { + if (ldcore->linein[line] != CLEAR_LINE) + { + /* call through to the player-specific line handler */ + if (ldcore->intf.writeline[line] != NULL) + (*ldcore->intf.writeline[line])(ld, ASSERT_LINE, CLEAR_LINE); + } + ldcore->linein[line] = CLEAR_LINE; + } +} + + +/*------------------------------------------------- + laserdisc_data_r - return the current + data byte +-------------------------------------------------*/ + +UINT8 laserdisc_data_r(const device_config *device) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + UINT8 result = ldcore->dataout; + + /* call through to the player-specific data handler */ + if (ldcore->intf.readdata != NULL) + result = (*ldcore->intf.readdata)(ld); + + return result; +} + + +/*------------------------------------------------- + laserdisc_line_r - return the current state + of an output line +-------------------------------------------------*/ + +UINT8 laserdisc_line_r(const device_config *device, UINT8 line) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + UINT8 result; + + assert(line < LASERDISC_OUTPUT_LINES); + result = ldcore->lineout[line]; + + /* call through to the player-specific data handler */ + if (ldcore->intf.readline[line] != NULL) + result = (*ldcore->intf.readline[line])(ld); + + return result; +} + + +/*------------------------------------------------- + laserdisc_get_video - return the current + video frame +-------------------------------------------------*/ + +UINT32 laserdisc_get_video(const device_config *device, bitmap_t **bitmap) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + int frameindex; + + /* determine the most recent live set of frames */ + frameindex = ldcore->videoindex; + if (ldcore->videofields[frameindex] < 2) + frameindex = (frameindex + ARRAY_LENGTH(ldcore->videofields) - 1) % ARRAY_LENGTH(ldcore->videofields); + + /* if no video present, return the empty frame */ + if (!video_active(ldcore) || ldcore->videofields[frameindex] < 2) + { + *bitmap = ldcore->emptyframe; + return 0; + } + else + { + *bitmap = ldcore->videovisframe[frameindex]; + return ldcore->videoframenum[frameindex]; + } +} + + +/*------------------------------------------------- + laserdisc_get_field_code - return raw field + information read from the disc +-------------------------------------------------*/ + +UINT32 laserdisc_get_field_code(const device_config *device, UINT8 code) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + int field = ldcore->fieldnum ^ 1; + + /* if no video present, return */ + if (!video_active(ldcore)) + return 0; + + switch (code) + { + case LASERDISC_CODE_WHITE_FLAG: + return ldcore->metadata[field].white; + + case LASERDISC_CODE_LINE16: + return ldcore->metadata[field].line16; + + case LASERDISC_CODE_LINE17: + return ldcore->metadata[field].line17; + + case LASERDISC_CODE_LINE18: + return ldcore->metadata[field].line18; + } + + return 0; +} + + + +/*************************************************************************** + PLAYER-TO-CORE INTERFACES +***************************************************************************/ + +/*------------------------------------------------- + ldcore_get_safe_token - return a token with + type checking from a device +-------------------------------------------------*/ + +laserdisc_state *ldcore_get_safe_token(const device_config *device) +{ + return get_safe_token(device); +} + + +/*------------------------------------------------- + ldcore_set_audio_squelch - set the left/right + audio squelch states +-------------------------------------------------*/ + +void ldcore_set_audio_squelch(laserdisc_state *ld, UINT8 squelchleft, UINT8 squelchright) +{ + ld->core->audiosquelch = (squelchleft ? 1 : 0) | (squelchright ? 2 : 0); +} + + +/*------------------------------------------------- + ldcore_set_video_squelch - set the video + squelch state +-------------------------------------------------*/ + +void ldcore_set_video_squelch(laserdisc_state *ld, UINT8 squelch) +{ + ld->core->videosquelch = squelch; +} + + + +/*************************************************************************** + GENERIC HELPER FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + ldcore_generic_update - generically update in + a way that works for most situations +-------------------------------------------------*/ + +INT32 ldcore_generic_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime, ldplayer_state *newstate) +{ + INT32 advanceby = 0; + int frame; + + /* start by assuming the state doesn't change */ + *newstate = ld->state; + + /* handle things based on the state */ + switch (ld->state.state) + { + case LDSTATE_EJECTING: + /* when time expires, switch to the ejected state */ + if (attotime_compare(curtime, ld->state.endtime) >= 0) + newstate->state = LDSTATE_EJECTED; + break; + + case LDSTATE_EJECTED: + /* do nothing */ + break; + + case LDSTATE_PARKED: + /* do nothing */ + break; + + case LDSTATE_LOADING: + /* when time expires, switch to the spinup state */ + if (attotime_compare(curtime, ld->state.endtime) >= 0) + newstate->state = LDSTATE_SPINUP; + advanceby = -GENERIC_SEARCH_SPEED; + break; + + case LDSTATE_SPINUP: + /* when time expires, switch to the playing state */ + if (attotime_compare(curtime, ld->state.endtime) >= 0) + newstate->state = LDSTATE_PLAYING; + advanceby = -GENERIC_SEARCH_SPEED; + break; + + case LDSTATE_PAUSING: + /* if he hit the start of a frame, switch to paused state */ + if (is_start_of_frame(vbi)) + { + newstate->state = LDSTATE_PAUSED; + newstate->param = fieldnum; + } + + /* else advance until we hit it */ + else if (fieldnum == 1) + advanceby = 1; + break; + + case LDSTATE_PAUSED: + /* if we paused on field 1, we must flip back and forth */ + if (ld->state.param == 1) + advanceby = (fieldnum == 1) ? 1 : -1; + break; + + case LDSTATE_PLAYING: + /* if we hit the target frame, switch to the paused state */ + if (ld->state.param > 0 && is_start_of_frame(vbi) && frame_from_metadata(vbi) == ld->state.param) + { + newstate->state = LDSTATE_PAUSED; + newstate->param = fieldnum; + } + + /* otherwise after the second field of each frame */ + else if (fieldnum == 1) + advanceby = 1; + break; + + case LDSTATE_PLAYING_SLOW_REVERSE: + /* after the second field of each frame, see if we need to advance */ + if (fieldnum == 1 && ++ld->state.substate > ld->state.param) + { + advanceby = -1; + ld->state.substate = 0; + } + break; + + case LDSTATE_PLAYING_SLOW_FORWARD: + /* after the second field of each frame, see if we need to advance */ + if (fieldnum == 1 && ++ld->state.substate > ld->state.param) + { + advanceby = 1; + ld->state.substate = 0; + } + break; + + case LDSTATE_PLAYING_FAST_REVERSE: + /* advance after the second field of each frame */ + if (fieldnum == 1) + advanceby = -ld->state.param; + break; + + case LDSTATE_PLAYING_FAST_FORWARD: + /* advance after the second field of each frame */ + if (fieldnum == 1) + advanceby = ld->state.param; + break; + + case LDSTATE_SCANNING: + /* advance after the second field of each frame */ + if (fieldnum == 1) + advanceby = ld->state.param >> 8; + + /* after we run out of vsyncs, revert to the saved state */ + if (++ld->state.substate >= (ld->state.param & 0xff)) + *newstate = ld->savestate; + break; + + case LDSTATE_STEPPING_REVERSE: + /* wait for the first field of the frame and then leap backwards */ + if (is_start_of_frame(vbi)) + { + advanceby = (fieldnum == 1) ? -1 : -2; + newstate->state = LDSTATE_PAUSING; + } + break; + + case LDSTATE_STEPPING_FORWARD: + /* wait for the first field of the frame and then switch to pausing state */ + if (is_start_of_frame(vbi)) + newstate->state = LDSTATE_PAUSING; + break; + + case LDSTATE_SEEKING: + /* if we're in the final state, look for a matching frame and pause there */ + frame = frame_from_metadata(vbi); + if (ld->state.substate == 1 && is_start_of_frame(vbi) && frame == ld->state.param) + { + newstate->state = LDSTATE_PAUSED; + newstate->param = fieldnum; + } + + /* otherwise, if we got frame data from the VBI, update our seeking logic */ + else if (ld->state.substate == 0 && frame != FRAME_NOT_PRESENT) + { + INT32 delta = (ld->state.param - 2) - frame; + + /* if we're within a couple of frames, just play until we hit it */ + if (delta >= 0 && delta <= 2) + ld->state.substate++; + + /* otherwise, compute the delta assuming 1:1 track to frame; this will correct eventually */ + else + { + if (delta < 0) + delta--; + advanceby = delta; + advanceby = MIN(advanceby, GENERIC_SEARCH_SPEED); + advanceby = MAX(advanceby, -GENERIC_SEARCH_SPEED); + } + } + + /* otherwise, keep advancing until we know what's up */ + else + { + if (fieldnum == 1) + advanceby = 1; + } + break; + } + + return advanceby; +} + + +/*------------------------------------------------- + read_track_data - read and process data for + a particular video track +-------------------------------------------------*/ + +static void read_track_data(laserdisc_state *ld) +{ + ldcore_data *ldcore = ld->core; + UINT32 tracknum = ldcore->curtrack; + UINT32 fieldnum = ldcore->fieldnum; + chd_error err; + + /* if the previous field had the white flag, force the new field to pair with it */ + if (ldcore->metadata[fieldnum ^ 1].white) + ldcore->videofields[ldcore->videoindex] = 1; + + /* if we already have both fields on the current videoindex, advance */ + if (ldcore->videofields[ldcore->videoindex] >= 2) + { + ldcore->videoindex = (ldcore->videoindex + 1) % ARRAY_LENGTH(ldcore->videofields); + ldcore->videofields[ldcore->videoindex] = 0; + } + + /* set the video target information */ + ldcore->videotarget = *ldcore->videoframe[ldcore->videoindex]; + ldcore->videotarget.base = BITMAP_ADDR16(&ldcore->videotarget, fieldnum, 0); + ldcore->videotarget.height /= 2; + ldcore->videotarget.rowpixels *= 2; + ldcore->avconfig.video = &ldcore->videotarget; + + /* set the audio target information */ + if (ldcore->audiobufin + ldcore->audiomaxsamples <= ldcore->audiobufsize) + { + /* if we can fit without wrapping, just read the data directly */ + ldcore->avconfig.audio[0] = &ldcore->audiobuffer[0][ldcore->audiobufin]; + ldcore->avconfig.audio[1] = &ldcore->audiobuffer[1][ldcore->audiobufin]; + } + else + { + /* otherwise, read to the beginning of the buffer */ + ldcore->avconfig.audio[0] = &ldcore->audiobuffer[0][0]; + ldcore->avconfig.audio[1] = &ldcore->audiobuffer[1][0]; + } + + /* override if we're not decoding */ + ldcore->avconfig.maxsamples = ldcore->audiomaxsamples; + ldcore->avconfig.actsamples = &ldcore->audiocursamples; + ldcore->audiocursamples = 0; + if (!audio_channel_active(ldcore, 0)) + ldcore->avconfig.audio[0] = NULL; + if (!audio_channel_active(ldcore, 1)) + ldcore->avconfig.audio[1] = NULL; + + /* configure the codec and then read */ + if (ldcore->disc != NULL) + { + err = chd_codec_config(ldcore->disc, AV_CODEC_DECOMPRESS_CONFIG, &ldcore->avconfig); + if (err == CHDERR_NONE) + { + INT32 maxtrack = chd_get_header(ldcore->disc)->totalhunks / 2; + INT32 chdtrack = tracknum - 1 - VIRTUAL_LEAD_IN_TRACKS; + + /* clamp the track */ + chdtrack = MAX(chdtrack, 0); + chdtrack = MIN(chdtrack, maxtrack - 1); + err = chd_read_async(ldcore->disc, chdtrack * 2 + fieldnum, NULL); + ldcore->readpending = TRUE; + } + } +} + + +/*------------------------------------------------- + process_track_data - process data from a + track after it has been read +-------------------------------------------------*/ + +static void process_track_data(const device_config *device) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + UINT32 tracknum = ldcore->curtrack; + UINT32 fieldnum = ldcore->fieldnum; + int frame, chapter; + chd_error chderr; + + /* wait for the async operation to complete */ + if (ldcore->disc != NULL && ldcore->readpending) + { + /* complete the async operation */ + chderr = chd_async_complete(ldcore->disc); + if (chderr != CHDERR_NONE && chderr != CHDERR_NO_ASYNC_OPERATION) + ldcore->avconfig.video = NULL; + } + ldcore->readpending = FALSE; + + /* parse the metadata */ + if (ldcore->disc != NULL && ldcore->avconfig.video != NULL) + vbi_parse_all((const UINT16 *)ldcore->avconfig.video->base, ldcore->avconfig.video->rowpixels, ldcore->avconfig.video->width, 8, &ldcore->metadata[fieldnum]); + else + fake_metadata(tracknum, fieldnum, &ldcore->metadata[fieldnum]); +// printf("Track %5d: Metadata = %d %08X %08X %08X %08X\n", tracknum, ldcore->metadata[fieldnum].white, ldcore->metadata[fieldnum].line16, ldcore->metadata[fieldnum].line17, ldcore->metadata[fieldnum].line18, ldcore->metadata[fieldnum].line1718); + + /* update the last seen frame and chapter */ + frame = frame_from_metadata(&ldcore->metadata[fieldnum]); + if (frame >= 1 && frame < 99999) + ldcore->last_frame = frame; + chapter = chapter_from_metadata(&ldcore->metadata[fieldnum]); + if (chapter != -1) + ldcore->last_chapter = chapter; + + /* render the display if present */ +// if (ldcore->display && ldcore->avconfig.video != NULL) +// render_display((UINT16 *)ldcore->avconfig.video->base, ldcore->avconfig.video->rowpixels, ldcore->avconfig.video->width, ldcore->last_frame); + + /* update video field */ + if (ldcore->avconfig.video != NULL) + { + ldcore->videofields[ldcore->videoindex]++; + ldcore->videoframenum[ldcore->videoindex] = ldcore->last_frame; + } + + /* pass the audio to the callback */ + if (ldcore->config.audio != NULL) + (*ldcore->config.audio)(device, ldcore->samplerate, ldcore->audiocursamples, ldcore->avconfig.audio[0], ldcore->avconfig.audio[1]); + + /* shift audio data if we read it into the beginning of the buffer */ + if (ldcore->audiocursamples != 0 && ldcore->audiobufin != 0) + { + int chnum; + + /* iterate over channels */ + for (chnum = 0; chnum < 2; chnum++) + if (ldcore->avconfig.audio[chnum] == &ldcore->audiobuffer[chnum][0]) + { + int samplesleft; + + /* move data to the end */ + samplesleft = ldcore->audiobufsize - ldcore->audiobufin; + samplesleft = MIN(samplesleft, ldcore->audiocursamples); + memmove(&ldcore->audiobuffer[chnum][ldcore->audiobufin], &ldcore->audiobuffer[chnum][0], samplesleft * 2); + + /* shift data at the beginning */ + if (samplesleft < ldcore->audiocursamples) + memmove(&ldcore->audiobuffer[chnum][0], &ldcore->audiobuffer[chnum][samplesleft], (ldcore->audiocursamples - samplesleft) * 2); + } + } + + /* update the input buffer pointer */ + ldcore->audiobufin = (ldcore->audiobufin + ldcore->audiocursamples) % ldcore->audiobufsize; +} + + +/*------------------------------------------------- + fake_metadata - fake metadata when there's + no disc present +-------------------------------------------------*/ + +static void fake_metadata(UINT32 track, UINT8 which, vbi_metadata *metadata) +{ + if (which == 0) + { + metadata->white = 1; + metadata->line16 = 0; + metadata->line17 = metadata->line18 = 0xf80000 | + (((track / 10000) % 10) << 16) | + (((track / 1000) % 10) << 12) | + (((track / 100) % 10) << 8) | + (((track / 10) % 10) << 4) | + (((track / 1) % 10) << 0); + } + else + memset(metadata, 0, sizeof(*metadata)); +} + + +/*------------------------------------------------- + render_display - draw the frame display +-------------------------------------------------*/ + +#ifdef UNUSED_FUNCTION +static void render_display(UINT16 *videodata, UINT32 rowpixels, UINT32 width, int frame) +{ + const int xscale = 4, yscale = 2; + char buffer[10]; + int x = width / 10; + int y = 50; + int ch; + int delta; + + /* do nothing if no data */ + if (videodata == NULL) + return; + + /* convert to a character string and render */ + sprintf(buffer, "%5d", frame); + + /* iterate over 5 positions: (-1,-1), (-1,1), (1,-1), (1,1) and (0,0) */ + /* render all in black except the last one */ + for (delta = 0; delta < 5; delta++) + { + int color = (delta < 4) ? 0x0080 : 0xff80; + int dx = (delta < 4) ? ((delta & 1) ? -1 : 1) : 0; + int dy = (delta < 4) ? ((delta & 2) ? -1 : 1) : 0; + + /* iterate over 5 characters */ + for (ch = 0; ch < 5; ch++) + if (buffer[ch] >= '0' && buffer[ch] <= '9') + { + const UINT8 *fontdata = &numberfont[buffer[ch] - '0'][0]; + int cy, cx; + + /* iterate over the rows of the character */ + for (cy = 0; cy < 8; cy++) + { + UINT8 bits = *fontdata++; + + /* and over the columns */ + for (cx = 0; cx < 8; cx++) + if (bits & (0x80 >> cx)) + { + int ix, iy; + + /* fill in an xscale x yscale pixel */ + for (iy = 0; iy < yscale; iy++) + for (ix = 0; ix < xscale; ix++) + videodata[(y + cy * yscale + iy + dy) * rowpixels + (x + (ch * 9 + cx) * xscale + ix + dx)] = color; + } + } + } + } +} +#endif + + +/*------------------------------------------------- + custom_start - custom audio start + for laserdiscs +-------------------------------------------------*/ + +static void *custom_start(int clock, const custom_sound_interface *config) +{ + sound_token *token = auto_malloc(sizeof(*token)); + token->stream = stream_create(0, 2, 48000, token, custom_stream_callback); + token->ld = NULL; + return token; +} + + +/*------------------------------------------------- + custom_stream_callback - audio streamer + for laserdiscs +-------------------------------------------------*/ + +static void custom_stream_callback(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +{ + sound_token *token = param; + laserdisc_state *ld = token->ld; + ldcore_data *ldcore = ld->core; + stream_sample_t *dst0 = outputs[0]; + stream_sample_t *dst1 = outputs[1]; + int samples_avail = 0; + + /* see if we have enough samples to fill the buffer; if not, drop out */ + if (ld != NULL) + { + samples_avail = ldcore->audiobufin - ldcore->audiobufout; + if (samples_avail < 0) + samples_avail += ldcore->audiobufsize; + } + + /* if no attached ld, just clear the buffers */ + if (samples_avail < samples) + { + memset(dst0, 0, samples * sizeof(dst0[0])); + memset(dst1, 0, samples * sizeof(dst1[0])); + } + + /* otherwise, stream from our buffer */ + else + { + INT16 *buffer0 = ldcore->audiobuffer[0]; + INT16 *buffer1 = ldcore->audiobuffer[1]; + int sampout = ldcore->audiobufout; + + /* copy samples, clearing behind us as we go */ + while (sampout != ldcore->audiobufin && samples-- > 0) + { + *dst0++ = buffer0[sampout]; + *dst1++ = buffer1[sampout]; + buffer0[sampout] = 0; + buffer1[sampout] = 0; + sampout++; + if (sampout >= ldcore->audiobufsize) + sampout = 0; + } + ldcore->audiobufout = sampout; + + /* clear out the rest of the buffer */ + if (samples > 0) + { + int sampout = (ldcore->audiobufout == 0) ? ldcore->audiobufsize - 1 : ldcore->audiobufout - 1; + stream_sample_t fill0 = buffer0[sampout]; + stream_sample_t fill1 = buffer1[sampout]; + + while (samples-- > 0) + { + *dst0++ = fill0; + *dst1++ = fill1; + } + } + } +} + + + +/*************************************************************************** + CONFIG SETTINGS ACCESS +***************************************************************************/ + +/*------------------------------------------------- + configuration_load - read and apply data from + the configuration file +-------------------------------------------------*/ + +static void configuration_load(running_machine *machine, int config_type, xml_data_node *parentnode) +{ + xml_data_node *overnode; + xml_data_node *ldnode; + + /* we only care about game files */ + if (config_type != CONFIG_TYPE_GAME) + return; + + /* might not have any data */ + if (parentnode == NULL) + return; + + /* iterate over overlay nodes */ + for (ldnode = xml_get_sibling(parentnode->child, "device"); ldnode != NULL; ldnode = xml_get_sibling(ldnode->next, "device")) + { + const char *devtag = xml_get_attribute_string(ldnode, "tag", ""); + const device_config *device = device_list_find_by_tag(machine->config->devicelist, LASERDISC, devtag); + if (device != NULL) + { + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + + /* handle the overlay node */ + overnode = xml_get_sibling(ldnode->child, "overlay"); + if (overnode != NULL) + { + /* fetch positioning controls */ + ldcore->config.overposx = xml_get_attribute_float(overnode, "hoffset", ldcore->config.overposx); + ldcore->config.overscalex = xml_get_attribute_float(overnode, "hstretch", ldcore->config.overscalex); + ldcore->config.overposy = xml_get_attribute_float(overnode, "voffset", ldcore->config.overposy); + ldcore->config.overscaley = xml_get_attribute_float(overnode, "vstretch", ldcore->config.overscaley); + } + } + } +} + + +/*------------------------------------------------- + configuration_save - save data to the + configuration file +-------------------------------------------------*/ + +static void configuration_save(running_machine *machine, int config_type, xml_data_node *parentnode) +{ + const device_config *device; + + /* we only care about game files */ + if (config_type != CONFIG_TYPE_GAME) + return; + + /* iterate over disc devices */ + for (device = device_list_first(machine->config->devicelist, LASERDISC); device != NULL; device = device_list_next(device, LASERDISC)) + { + laserdisc_config *origconfig = device->inline_config; + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + xml_data_node *overnode; + xml_data_node *ldnode; + + /* create a node */ + ldnode = xml_add_child(parentnode, "device", NULL); + if (ldnode != NULL) + { + int changed = FALSE; + + /* output the basics */ + xml_set_attribute(ldnode, "tag", device->tag); + + /* add an overlay node */ + overnode = xml_add_child(ldnode, "overlay", NULL); + if (overnode != NULL) + { + /* output the positioning controls */ + if (ldcore->config.overposx != origconfig->overposx) + { + xml_set_attribute_float(overnode, "hoffset", ldcore->config.overposx); + changed = TRUE; + } + + if (ldcore->config.overscalex != origconfig->overscalex) + { + xml_set_attribute_float(overnode, "hstretch", ldcore->config.overscalex); + changed = TRUE; + } + + if (ldcore->config.overposy != origconfig->overposy) + { + xml_set_attribute_float(overnode, "voffset", ldcore->config.overposy); + changed = TRUE; + } + + if (ldcore->config.overscaley != origconfig->overscaley) + { + xml_set_attribute_float(overnode, "vstretch", ldcore->config.overscaley); + changed = TRUE; + } + } + + /* if nothing changed, kill the node */ + if (!changed) + xml_delete_node(ldnode); + } + } +} + + + +/*************************************************************************** + VIDEO INTERFACE +***************************************************************************/ + +/*------------------------------------------------- + laserdisc_video_enable - enable/disable the + video +-------------------------------------------------*/ + +void laserdisc_video_enable(const device_config *device, int enable) +{ + laserdisc_state *ld = get_safe_token(device); + ld->core->videoenable = enable; +} + + +/*------------------------------------------------- + laserdisc_video_enable - enable/disable the + video +-------------------------------------------------*/ + +void laserdisc_overlay_enable(const device_config *device, int enable) +{ + laserdisc_state *ld = get_safe_token(device); + ld->core->overenable = enable; +} + + +/*------------------------------------------------- + video update callback +-------------------------------------------------*/ + +VIDEO_UPDATE( laserdisc ) +{ + const device_config *laserdisc = device_list_first(screen->machine->config->devicelist, LASERDISC); + if (laserdisc != NULL) + { + laserdisc_state *ld = laserdisc->token; + ldcore_data *ldcore = ld->core; + bitmap_t *overbitmap = ldcore->overbitmap[ldcore->overindex]; + bitmap_t *vidbitmap = NULL; + + /* handle the overlay if present */ + if (overbitmap != NULL && ldcore->config.overupdate != NULL) + { + rectangle clip = *cliprect; + + /* scale the cliprect to the overlay size and then call the update callback */ + clip.min_x = 0; + clip.max_x = ldcore->config.overwidth - 1; + clip.min_y = cliprect->min_y * overbitmap->height / bitmap->height; + clip.max_y = (cliprect->max_y + 1) * overbitmap->height / bitmap->height - 1; + (*ldcore->config.overupdate)(screen, overbitmap, &clip); + } + + /* if this is the last update, do the rendering */ + if (cliprect->max_y == video_screen_get_visible_area(screen)->max_y) + { + float x0, y0, x1, y1; + + /* update the texture with the overlay contents */ + if (overbitmap != NULL) + { + if (overbitmap->format == BITMAP_FORMAT_INDEXED16) + render_texture_set_bitmap(ldcore->overtex, overbitmap, &ldcore->config.overclip, 0, TEXFORMAT_PALETTEA16); + else if (overbitmap->format == BITMAP_FORMAT_RGB32) + render_texture_set_bitmap(ldcore->overtex, overbitmap, &ldcore->config.overclip, 0, TEXFORMAT_ARGB32); + } + + /* get the laserdisc video */ + laserdisc_get_video(laserdisc, &vidbitmap); + if (vidbitmap != NULL) + render_texture_set_bitmap(ldcore->videotex, vidbitmap, NULL, 0, TEXFORMAT_YUY16); + + /* reset the screen contents */ + render_container_empty(render_container_get_screen(screen)); + + /* add the video texture */ + if (ldcore->videoenable) + render_screen_add_quad(screen, 0.0f, 0.0f, 1.0f, 1.0f, MAKE_ARGB(0xff,0xff,0xff,0xff), ldcore->videotex, PRIMFLAG_BLENDMODE(BLENDMODE_NONE) | PRIMFLAG_SCREENTEX(1)); + + /* add the overlay */ + if (ldcore->overenable && overbitmap != NULL) + { + x0 = 0.5f - 0.5f * ldcore->config.overscalex + ldcore->config.overposx; + y0 = 0.5f - 0.5f * ldcore->config.overscaley + ldcore->config.overposy; + x1 = x0 + ldcore->config.overscalex; + y1 = y0 + ldcore->config.overscaley; + render_screen_add_quad(screen, x0, y0, x1, y1, MAKE_ARGB(0xff,0xff,0xff,0xff), ldcore->overtex, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_SCREENTEX(1)); + } + + /* swap to the next bitmap */ + ldcore->overindex = (ldcore->overindex + 1) % ARRAY_LENGTH(ldcore->overbitmap); + } + } + + return 0; +} + + + +/*************************************************************************** + CONFIGURATION +***************************************************************************/ + +/*------------------------------------------------- + laserdisc_get_config - return a copy of the + current live configuration settings +-------------------------------------------------*/ + +void laserdisc_get_config(const device_config *device, laserdisc_config *config) +{ + laserdisc_state *ld = get_safe_token(device); + *config = ld->core->config; +} + + +/*------------------------------------------------- + laserdisc_get_config - change the current live + configuration settings +-------------------------------------------------*/ + +void laserdisc_set_config(const device_config *device, const laserdisc_config *config) +{ + laserdisc_state *ld = get_safe_token(device); + ld->core->config = *config; +} + + + +/*************************************************************************** + DEVICE INTERFACE +***************************************************************************/ + +/*------------------------------------------------- + device start callback +-------------------------------------------------*/ + +static DEVICE_START( laserdisc ) +{ + int fps = 30, fpsfrac = 0, width = 720, height = 240, interlaced = 1, channels = 2, rate = 44100; + const laserdisc_config *config = device->inline_config; + laserdisc_state *ld = get_safe_token(device); + UINT32 fps_times_1million; + ldcore_data *ldcore; + char metadata[256]; + int sndnum, index; + int statesize; + chd_error err; + + /* save a copy of the device pointer */ + ld->device = device; + + /* allocate memory for the core state */ + ld->core = auto_malloc(sizeof(*ld->core)); + memset(ld->core, 0, sizeof(*ld->core)); + ldcore = ld->core; + + /* determine the maximum player-specific state size and allocate it */ + statesize = 0; + for (index = 0; index < ARRAY_LENGTH(player_interfaces); index++) + statesize = MAX(statesize, player_interfaces[index]->statesize); + ld->player = auto_malloc(statesize); + memset(ld->player, 0, statesize); + + /* copy config data to the live state */ + ldcore->config = *config; + if (ldcore->config.overclip.max_x == ldcore->config.overclip.min_x || ldcore->config.overclip.max_y == ldcore->config.overclip.min_y) + { + ldcore->config.overclip.min_x = ldcore->config.overclip.min_y = 0; + ldcore->config.overclip.max_x = ldcore->config.overwidth - 1; + ldcore->config.overclip.max_y = ldcore->config.overheight - 1; + } + if (ldcore->config.overscalex == 0) + ldcore->config.overscalex = 1.0f; + if (ldcore->config.overscaley == 0) + ldcore->config.overscaley = 1.0f; + + /* find the disc */ + ldcore->disc = get_disk_handle(device->tag); + ldcore->audiocustom = 0; + for (sndnum = 0; sndnum < MAX_SOUND; sndnum++) + { + if (device->machine->config->sound[sndnum].tag != NULL && strcmp(device->machine->config->sound[sndnum].tag, device->tag) == 0) + break; + if (device->machine->config->sound[sndnum].type == SOUND_CUSTOM) + ldcore->audiocustom++; + } + if (sndnum == MAX_SOUND) + ldcore->audiocustom = -1; + + /* get the disc metadata and extract the ld */ + if (ldcore->disc != NULL) + { + /* require the A/V codec */ + if (chd_get_header(ldcore->disc)->compression != CHDCOMPRESSION_AV) + fatalerror("Laserdisc video must be compressed with the A/V codec!"); + + /* read and extract the metadata */ + err = chd_get_metadata(ldcore->disc, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL); + if (err != CHDERR_NONE) + fatalerror("Non-A/V CHD file specified"); + if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) + fatalerror("Invalid metadata in CHD file"); + + /* require interlaced video */ + if (!interlaced) + fatalerror("Laserdisc video must be interlaced!"); + + /* determine the maximum track and allocate a frame buffer */ + ldcore->maxtrack = chd_get_header(ldcore->disc)->totalhunks / 2; + } + else + ldcore->maxtrack = 54000; + ldcore->maxtrack += VIRTUAL_LEAD_IN_TRACKS + VIRTUAL_LEAD_OUT_TRACKS; + + /* allocate video frames */ + for (index = 0; index < ARRAY_LENGTH(ldcore->videofields); index++) + { + /* first allocate a YUY16 bitmap at 2x the height */ + ldcore->videoframe[index] = auto_bitmap_alloc(width, height * 2, BITMAP_FORMAT_YUY16); + fillbitmap_yuy16(ldcore->videoframe[index], 40, 109, 240); + + /* make a copy of the bitmap that clips out the VBI and horizontal blanking areas */ + ldcore->videovisframe[index] = auto_malloc(sizeof(*ldcore->videovisframe[index])); + *ldcore->videovisframe[index] = *ldcore->videoframe[index]; + ldcore->videovisframe[index]->base = BITMAP_ADDR16(ldcore->videovisframe[index], 44, ldcore->videoframe[index]->width * 8 / 720); + ldcore->videovisframe[index]->height -= 44; + ldcore->videovisframe[index]->width -= 2 * ldcore->videoframe[index]->width * 8 / 720; + } + + /* allocate an empty frame of the same size */ + ldcore->emptyframe = auto_bitmap_alloc(width, height * 2, BITMAP_FORMAT_YUY16); + fillbitmap_yuy16(ldcore->emptyframe, 0, 128, 128); + + /* allocate audio buffers */ + fps_times_1million = fps * 1000000 + fpsfrac; + ldcore->audiomaxsamples = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; + ldcore->audiobufsize = ldcore->audiomaxsamples * 4; + ldcore->audiobuffer[0] = auto_malloc(ldcore->audiobufsize * sizeof(ldcore->audiobuffer[0][0])); + ldcore->audiobuffer[1] = auto_malloc(ldcore->audiobufsize * sizeof(ldcore->audiobuffer[1][0])); + ldcore->samplerate = rate; + + /* allocate texture for rendering */ + ldcore->videoenable = TRUE; + ldcore->videotex = render_texture_alloc(NULL, NULL); + if (ldcore->videotex == NULL) + fatalerror("Out of memory allocating video texture"); + + /* allocate overlay */ + if (ldcore->config.overwidth > 0 && ldcore->config.overheight > 0 && ldcore->config.overupdate != NULL) + { + ldcore->overenable = TRUE; + ldcore->overbitmap[0] = auto_bitmap_alloc(ldcore->config.overwidth, ldcore->config.overheight, ldcore->config.overformat); + ldcore->overbitmap[1] = auto_bitmap_alloc(ldcore->config.overwidth, ldcore->config.overheight, ldcore->config.overformat); + ldcore->overtex = render_texture_alloc(NULL, NULL); + if (ldcore->overtex == NULL) + fatalerror("Out of memory allocating overlay texture"); + } + + /* register callbacks */ + config_register(device->machine, "laserdisc", configuration_load, configuration_save); +} + + +/*------------------------------------------------- + device exit callback +-------------------------------------------------*/ + +static DEVICE_STOP( laserdisc ) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + + /* make sure all async operations have completed */ + if (ldcore->disc != NULL) + chd_async_complete(ldcore->disc); + + /* free any textures */ + if (ldcore->videotex != NULL) + render_texture_free(ldcore->videotex); + if (ldcore->overtex != NULL) + render_texture_free(ldcore->overtex); +} + + +/*------------------------------------------------- + device reset callback +-------------------------------------------------*/ + +static DEVICE_RESET( laserdisc ) +{ + laserdisc_state *ld = get_safe_token(device); + ldcore_data *ldcore = ld->core; + int pltype, line; + + /* find our interface */ + for (pltype = 0; pltype < ARRAY_LENGTH(player_interfaces); pltype++) + if (player_interfaces[pltype]->type == ldcore->config.type) + break; + if (pltype == ARRAY_LENGTH(player_interfaces)) + fatalerror("No interface found for laserdisc player type %d\n", ldcore->config.type); + ldcore->intf = *player_interfaces[pltype]; + + /* attempt to wire up the audio */ + if (ldcore->audiocustom != -1) + { + sound_token *token = custom_get_token(ldcore->audiocustom); + token->ld = ld; + stream_set_sample_rate(token->stream, ldcore->samplerate); + } + + /* set up the general ld */ + ldcore->videosquelch = 1; + ldcore->audiosquelch = 3; + + /* default to track 1 */ + ldcore->curtrack = 1; + ldcore->last_frame = 0; + ldcore->last_chapter = 0; + + /* reset the I/O lines */ + for (line = 0; line < LASERDISC_INPUT_LINES; line++) + ldcore->linein[line] = CLEAR_LINE; + for (line = 0; line < LASERDISC_OUTPUT_LINES; line++) + ldcore->lineout[line] = CLEAR_LINE; + + /* call the initialization */ + if (ldcore->intf.init != NULL) + (*ldcore->intf.init)(ld); +} + + +/*------------------------------------------------- + device set info callback +-------------------------------------------------*/ + +static DEVICE_SET_INFO( laserdisc ) +{ + laserdisc_state *ld = get_safe_token(device); + switch (state) + { + /* --- the following bits of info are set as 64-bit signed integers --- */ + case LDINFO_INT_TYPE: + if (ld != NULL && ld->core != NULL && ld->core->config.type != info->i) + { + ld->core->config.type = info->i; + device_reset(device); + } + break; + } +} + + +/*------------------------------------------------- + device get info callback +-------------------------------------------------*/ + +DEVICE_GET_INFO( laserdisc ) +{ + const laserdisc_config *config = NULL; + int pltype; + + /* if we have a device, figure out where our config lives */ + if (device != NULL) + { + laserdisc_state *ld = device->token; + config = (ld == NULL || ld->core == NULL) ? device->inline_config : &ld->core->config; + } + + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(laserdisc_state); break; + case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(laserdisc_config); break; + case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_VIDEO; break; + case LDINFO_INT_TYPE: info->i = config->type; break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_SET_INFO: info->set_info = DEVICE_SET_INFO_NAME(laserdisc); break; + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(laserdisc); break; + case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME(laserdisc); break; + case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(laserdisc); break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: + info->s = "Unknown Laserdisc Player"; + for (pltype = 0; pltype < ARRAY_LENGTH(player_interfaces); pltype++) + if (player_interfaces[pltype]->type == config->type) + info->s = player_interfaces[pltype]->name; + break; + case DEVINFO_STR_FAMILY: info->s = "Laserdisc Player"; break; + case DEVINFO_STR_VERSION: info->s = "1.0"; break; + case DEVINFO_STR_SOURCE_FILE: info->s = __FILE__; break; + case DEVINFO_STR_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break; + } +} diff --git a/src/emu/machine/ldcore.h b/src/emu/machine/ldcore.h new file mode 100644 index 00000000000..74b912212ea --- /dev/null +++ b/src/emu/machine/ldcore.h @@ -0,0 +1,247 @@ +/************************************************************************* + + ldcore.h + + Private core laserdisc player implementation. + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*************************************************************************/ + +#pragma once + +#ifndef __LDCORE_H__ +#define __LDCORE_H__ + +#include "driver.h" +#include "laserdsc.h" +#include "vbiparse.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* common laserdisc states */ +enum +{ + LDSTATE_NONE, /* unspecified state */ + LDSTATE_EJECTING, /* in the process of ejecting */ + LDSTATE_EJECTED, /* fully ejected */ + LDSTATE_PARKED, /* head parked in lead-in */ + LDSTATE_LOADING, /* loading from ejected state */ + LDSTATE_SPINUP, /* spinning up */ + LDSTATE_PAUSING, /* looking for a frame boundary to pause */ + LDSTATE_PAUSED, /* found a frame boundary; now paused */ + /* parameter specifies the fieldnum of the first frame */ + LDSTATE_PLAYING, /* playing forward normally, with audio */ + /* parameter specifies the target frame, or 0 if none */ + LDSTATE_PLAYING_SLOW_REVERSE, /* playing slow in the reverse direction, with no audio */ + /* parameter specifies the number of times to repeat each track */ + LDSTATE_PLAYING_SLOW_FORWARD, /* playing slow in the forward direction, with no audio */ + /* parameter specifies the number of times to repeat each track */ + LDSTATE_PLAYING_FAST_REVERSE, /* playing fast in the reverse direction, with no audio */ + /* parameter specifies the number of frames to skip backwards after each frame */ + LDSTATE_PLAYING_FAST_FORWARD, /* playing fast in the forward direction, with no audio */ + /* parameter specifies the number of frames to skip forwards after each frame */ + LDSTATE_STEPPING_REVERSE, /* single frame stepping in the reverse direction */ + LDSTATE_STEPPING_FORWARD, /* single frame stepping in the forward direction */ + LDSTATE_SCANNING, /* scanning in the forward or reverse direction */ + /* parameter(0:7) controls how many vsyncs until revert to savestate */ + /* parameter(8:31) specifies the speed */ + LDSTATE_SEEKING, /* seeking to a specific frame */ + /* parameter specifies the target frame */ + LDSTATE_OTHER /* other states start here */ +}; + +/* special frame and chapter numbers from VBI conversion */ +#define FRAME_NOT_PRESENT -2 /* no frame number information present */ +#define FRAME_LEAD_IN -1 /* lead-in code detected */ +#define FRAME_LEAD_OUT 99999 /* lead-out code detected */ +#define CHAPTER_NOT_PRESENT -2 /* no chapter number information present */ +#define CHAPTER_LEAD_IN -1 /* lead-in code detected */ +#define CHAPTER_LEAD_OUT 100 /* lead-out code detected */ + +/* generic head movement speeds; use player-specific information where appropriate */ +#define GENERIC_SLOW_SPEED (5) /* 1/5 normal speed */ +#define GENERIC_FAST_SPEED (3) /* 3x normal speed */ +#define GENERIC_SCAN_SPEED (50) /* 50x normal speed */ +#define GENERIC_SEARCH_SPEED (5000) /* 5000x normal speed */ + +/* generic timings; use player-specific information where appropriate */ +#define GENERIC_EJECT_TIME (ATTOTIME_IN_SEC(5)) +#define GENERIC_SPINUP_TIME (ATTOTIME_IN_SEC(2)) +#define GENERIC_LOAD_TIME (ATTOTIME_IN_SEC(5)) + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +#define SCANNING_PARAM(speed,duration) (((speed) << 8) | ((duration) & 0xff)) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* core-specific and player-specific data */ +typedef struct _ldplayer_data ldplayer_data; +typedef struct _ldcore_data ldcore_data; + + +/* player state */ +typedef struct _ldplayer_state ldplayer_state; +struct _ldplayer_state +{ + UINT8 state; /* current state */ + INT32 substate; /* internal sub-state; starts at 0 on any state change */ + INT32 param; /* parameter for current state */ + attotime endtime; /* minimum ending time for current state */ +}; + + +/* generic data */ +typedef struct _laserdisc_state laserdisc_state; +struct _laserdisc_state +{ + const device_config * device; /* pointer to owning device */ + ldcore_data * core; /* private core data */ + ldplayer_data * player; /* private player data */ + + ldplayer_state state; /* active state */ + ldplayer_state savestate; /* saved state during temporary operations */ +}; + + +/* player-specific callbacks */ +typedef void (*laserdisc_init_func)(laserdisc_state *ld); +typedef INT32 (*laserdisc_update_func)(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime); +typedef void (*laserdisc_w_func)(laserdisc_state *ld, UINT8 prev, UINT8 new); +typedef UINT8 (*laserdisc_r_func)(laserdisc_state *ld); + + +/* player configuration */ +typedef struct _ldplayer_interface ldplayer_interface; +struct _ldplayer_interface +{ + int type; /* type of the player */ + size_t statesize; /* size of the state */ + const char * name; /* name of the player */ + laserdisc_init_func init; /* initialization callback */ + laserdisc_update_func update; /* update callback */ + laserdisc_w_func writedata; /* parallel data write */ + laserdisc_w_func writeline[LASERDISC_INPUT_LINES]; /* single line write */ + laserdisc_r_func readdata; /* parallel data read */ + laserdisc_r_func readline[LASERDISC_OUTPUT_LINES]; /* single line read */ +}; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +/* defined by each player */ +extern const ldplayer_interface pr7820_interface; +extern const ldplayer_interface pr8210_interface; +extern const ldplayer_interface simutrek_interface; +extern const ldplayer_interface ldv1000_interface; +extern const ldplayer_interface ldp1450_interface; +extern const ldplayer_interface vp932_interface; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- player interface ----- */ + +/* return a token with type checking from a device */ +laserdisc_state *ldcore_get_safe_token(const device_config *device); + +/* set the left/right audio squelch states */ +void ldcore_set_audio_squelch(laserdisc_state *ld, UINT8 squelchleft, UINT8 squelchright); + +/* set the video squelch state */ +void ldcore_set_video_squelch(laserdisc_state *ld, UINT8 squelch); + + + +/* ----- generic implementations ----- */ + +/* generically update in a way that works for most situations */ +INT32 ldcore_generic_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime, ldplayer_state *curstate); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + is_start_of_frame - return TRUE if this is + the start of a frame +-------------------------------------------------*/ + +INLINE int is_start_of_frame(const vbi_metadata *vbi) +{ + /* is it not known if the white flag or the presence of a frame code + determines the start of frame; the former seems to be the "official" + way, but the latter seems to be the practical implementation */ + return (vbi->white || (vbi->line1718 & 0xf80000) == 0xf80000); +} + + +/*------------------------------------------------- + frame_from_metadata - return the frame number + encoded in the metadata, if present, or + FRAME_NOT_PRESENT +-------------------------------------------------*/ + +INLINE int frame_from_metadata(const vbi_metadata *metadata) +{ + UINT32 data; + + if ((metadata->line1718 & 0xf80000) == 0xf80000) + data = metadata->line1718 & 0x7ffff; + else if (metadata->line1718 == 0x88ffff) + return FRAME_LEAD_IN; + else if (metadata->line1718 == 0x80eeee) + return FRAME_LEAD_OUT; + else + return FRAME_NOT_PRESENT; + + return (((data >> 16) & 0x0f) * 10000) + (((data >> 12) & 0x0f) * 1000) + (((data >> 8) & 0x0f) * 100) + (((data >> 4) & 0x0f) * 10) + (data & 0x0f); +} + + +/*------------------------------------------------- + chapter_from_metadata - return the chapter + number encoded in the metadata, if present, + or CHAPTER_NOT_PRESENT +-------------------------------------------------*/ + +INLINE int chapter_from_metadata(const vbi_metadata *metadata) +{ + UINT32 data; + + if ((metadata->line1718 & 0xf00fff) == 0x800ddd) + data = metadata->line1718 & 0x7f000; + else if (metadata->line1718 == 0x88ffff) + return CHAPTER_LEAD_IN; + else if (metadata->line1718 == 0x80eeee) + return CHAPTER_LEAD_OUT; + else + return CHAPTER_NOT_PRESENT; + + return (((data >> 16) & 0x0f) * 10) + ((data >> 12) & 0x0f); +} + + +#endif diff --git a/src/emu/machine/ldpr8210.c b/src/emu/machine/ldpr8210.c new file mode 100644 index 00000000000..de6cea47286 --- /dev/null +++ b/src/emu/machine/ldpr8210.c @@ -0,0 +1,722 @@ +/************************************************************************* + + ldpr8210.c + + Pioneer PR-8210 laserdisc emulation. + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*************************************************************************/ + +#include "ldcore.h" + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define PRINTF_COMMANDS 1 +#define CMDPRINTF(x) do { if (PRINTF_COMMANDS) mame_printf_debug x; } while (0) + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* Player-specific states */ +enum +{ + LDSTATE_STEPPING_BY_PARAMETER = LDSTATE_OTHER +}; + +/* Pioneer PR-8210 specific information */ +#define PR8210_SCAN_SPEED (2000 / 30) /* 2000 frames/second */ +#define PR8210_SCAN_DURATION (10) /* scan for 10 vsyncs each time */ +#define PR8210_SLOW_SPEED (5) /* 1/5x normal speed */ +#define PR8210_FAST_SPEED (3) /* 3x normal speed */ +#define PR8210_JUMP_REV_SPEED (15) /* 15x normal speed */ +#define PR8210_SEEK_FAST_SPEED (1000) /* 1000x normal speed */ + +/* Us vs. Them requires at least this much "non-video" time in order to work */ +#define PR8210_MINIMUM_SEEK_TIME ATTOTIME_IN_MSEC(150) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* player-specific data */ +struct _ldplayer_data +{ + /* general modes and states */ + UINT8 audio1disable; /* explicit disable for audio 1 */ + UINT8 audio2disable; /* explicit disable for audio 2 */ + UINT8 framedisplay; /* frame display */ + INT32 parameter; /* parameter for numbers */ + + /* serial/command interface processing */ + UINT8 lastcommand; /* last command byte received */ + UINT8 seekstate; /* state of the seek command */ + UINT16 accumulator; /* bit accumulator */ + attotime lastbittime; /* time of last bit received */ + attotime firstbittime; /* time of first bit in command */ + + /* Simutrek-specific data */ + UINT8 cmdcnt; /* counter for multi-byte command */ + UINT8 cmdbytes[3]; /* storage for multi-byte command */ + void (*cmd_ack_callback)(void); /* callback to clear game command write flag */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static void pr8210_init(laserdisc_state *ld); +static void pr8210_soft_reset(laserdisc_state *ld); +static void pr8210_update_squelch(laserdisc_state *ld); +static int pr8210_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 stateparam); +static INT32 pr8210_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime); +static void pr8210_command(laserdisc_state *ld); +static void pr8210_control_w(laserdisc_state *ld, UINT8 prev, UINT8 data); + +static void simutrek_init(laserdisc_state *ld); +static UINT8 simutrek_status_r(laserdisc_state *ld); +static void simutrek_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data); + + + +/*************************************************************************** + INTERFACES +***************************************************************************/ + +const ldplayer_interface pr8210_interface = +{ + LASERDISC_TYPE_PIONEER_PR8210, /* type of the player */ + sizeof(ldplayer_data), /* size of the state */ + "Pioneer PR-8210", /* name of the player */ + pr8210_init, /* initialization callback */ + pr8210_update, /* update callback */ + NULL, /* parallel data write */ + { /* single line write: */ + NULL, /* LASERDISC_LINE_ENTER */ + pr8210_control_w /* LASERDISC_LINE_CONTROL */ + }, + NULL, /* parallel data read */ + { /* single line read: */ + NULL, /* LASERDISC_LINE_READY */ + NULL, /* LASERDISC_LINE_STATUS */ + NULL, /* LASERDISC_LINE_COMMAND */ + NULL, /* LASERDISC_LINE_DATA_AVAIL */ + } +}; + + +const ldplayer_interface simutrek_interface = +{ + LASERDISC_TYPE_SIMUTREK_SPECIAL, /* type of the player */ + sizeof(ldplayer_data), /* size of the state */ + "Simutrek Modified PR-8210", /* name of the player */ + simutrek_init, /* initialization callback */ + pr8210_update, /* update callback */ + simutrek_data_w, /* parallel data write */ + { /* single line write: */ + NULL, /* LASERDISC_LINE_ENTER */ + NULL /* LASERDISC_LINE_CONTROL */ + }, + NULL, /* parallel data read */ + { /* single line read: */ + NULL, /* LASERDISC_LINE_READY */ + simutrek_status_r, /* LASERDISC_LINE_STATUS */ + NULL, /* LASERDISC_LINE_COMMAND */ + NULL, /* LASERDISC_LINE_DATA_AVAIL */ + } +}; + + + +/*************************************************************************** + PIONEER PR-8210 IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + requires_state_save - returns TRUE if the + given state will return to the previous + state when done +-------------------------------------------------*/ + +INLINE int requires_state_save(UINT8 state) +{ + return (state == LDSTATE_SCANNING); +} + + + +/*************************************************************************** + PIONEER PR-8210 IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + pr8210_init - Pioneer PR-8210-specific + initialization +-------------------------------------------------*/ + +static void pr8210_init(laserdisc_state *ld) +{ + /* do a soft reset */ + pr8210_soft_reset(ld); +} + + +/*------------------------------------------------- + pr8210_soft_reset - Pioneer PR-8210-specific + soft reset +-------------------------------------------------*/ + +static void pr8210_soft_reset(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + attotime curtime = timer_get_time(); + + ld->state.state = LDSTATE_NONE; + ld->state.substate = 0; + ld->state.param = 0; + ld->state.endtime = curtime; + + player->audio1disable = 0; + player->audio2disable = 0; + player->parameter = 0; + + player->lastcommand = 0; + player->seekstate = 0; + player->accumulator = 0; + player->firstbittime = curtime; + player->lastbittime = curtime; + + pr8210_switch_state(ld, LDSTATE_PARKED, 0); +} + + +/*------------------------------------------------- + pr8210_update_squelch - update the squelch + settings based on the current state +-------------------------------------------------*/ + +static void pr8210_update_squelch(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + + switch (ld->state.state) + { + /* video on, audio on */ + case LDSTATE_PLAYING: + ldcore_set_audio_squelch(ld, player->audio1disable, player->audio2disable); + ldcore_set_video_squelch(ld, FALSE); + break; + + /* video on, audio off */ + case LDSTATE_PAUSING: + case LDSTATE_PAUSED: + case LDSTATE_PLAYING_SLOW_REVERSE: + case LDSTATE_PLAYING_SLOW_FORWARD: + case LDSTATE_PLAYING_FAST_REVERSE: + case LDSTATE_PLAYING_FAST_FORWARD: + case LDSTATE_STEPPING_REVERSE: + case LDSTATE_STEPPING_FORWARD: + case LDSTATE_SCANNING: + ldcore_set_audio_squelch(ld, TRUE, TRUE); + ldcore_set_video_squelch(ld, FALSE); + break; + + /* video off, audio off */ + case LDSTATE_NONE: + case LDSTATE_EJECTED: + case LDSTATE_EJECTING: + case LDSTATE_PARKED: + case LDSTATE_LOADING: + case LDSTATE_SPINUP: + case LDSTATE_SEEKING: + ldcore_set_audio_squelch(ld, TRUE, TRUE); + ldcore_set_video_squelch(ld, TRUE); + break; + + /* Simutrek: stepping by parameter with explicit audio squelch controls */ + case LDSTATE_STEPPING_BY_PARAMETER: + ldcore_set_video_squelch(ld, FALSE); + break; + } +} + + +/*------------------------------------------------- + pr8210_switch_state - attempt to switch states + if appropriate, and ensure the squelch values + are correct +-------------------------------------------------*/ + +static int pr8210_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 parameter) +{ + attotime curtime = timer_get_time(); + + /* if this is the same state, ignore */ + if (ld->state.state == newstate) + return TRUE; + + /* if we're in a timed state, we ignore changes until the time elapses */ + if (attotime_compare(curtime, ld->state.endtime) < 0) + return FALSE; + + /* if moving to a state that requires it, save the old state */ + if (requires_state_save(newstate) && !requires_state_save(ld->state.state)) + ld->savestate = ld->state; + + /* set up appropriate squelch and timing */ + ld->state.state = newstate; + ld->state.substate = 0; + ld->state.param = parameter; + ld->state.endtime = curtime; + pr8210_update_squelch(ld); + + /* if we're switching to a timed state, set the appropriate timer */ + switch (ld->state.state) + { + case LDSTATE_EJECTING: + ld->state.endtime = attotime_add(curtime, GENERIC_EJECT_TIME); + break; + + case LDSTATE_LOADING: + ld->state.endtime = attotime_add(curtime, GENERIC_LOAD_TIME); + break; + + case LDSTATE_SPINUP: + ld->state.endtime = attotime_add(curtime, GENERIC_SPINUP_TIME); + break; + + case LDSTATE_SEEKING: + ld->state.endtime = attotime_add(curtime, PR8210_MINIMUM_SEEK_TIME); + break; + } + return TRUE; +} + + +/*------------------------------------------------- + pr8210_update - Pioneer PR-8210-specific + update callback +-------------------------------------------------*/ + +static INT32 pr8210_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime) +{ + ldplayer_state newstate; + INT32 advanceby = 0; + int frame; + + /* handle things based on the state */ + switch (ld->state.state) + { + case LDSTATE_EJECTING: + case LDSTATE_EJECTED: + case LDSTATE_PARKED: + case LDSTATE_LOADING: + case LDSTATE_SPINUP: + case LDSTATE_PAUSING: + case LDSTATE_PAUSED: + case LDSTATE_PLAYING: + case LDSTATE_PLAYING_SLOW_REVERSE: + case LDSTATE_PLAYING_SLOW_FORWARD: + case LDSTATE_PLAYING_FAST_REVERSE: + case LDSTATE_PLAYING_FAST_FORWARD: + case LDSTATE_STEPPING_REVERSE: + case LDSTATE_STEPPING_FORWARD: + case LDSTATE_SCANNING: + /* generic behaviors are appropriate for all of these commands */ + advanceby = ldcore_generic_update(ld, vbi, fieldnum, curtime, &newstate); + pr8210_switch_state(ld, newstate.state, newstate.param); + break; + + case LDSTATE_SEEKING: + /* if we're in the final state, look for a matching frame and pause there */ + frame = frame_from_metadata(vbi); + if (ld->state.substate == 3 && is_start_of_frame(vbi) && frame == ld->state.param) + pr8210_switch_state(ld, LDSTATE_PAUSED, fieldnum); + + /* otherwise, if we got frame data from the VBI, update our seeking logic */ + else if (ld->state.substate < 3 && frame != FRAME_NOT_PRESENT) + { + static const INT32 seekspeed[] = { -PR8210_SEEK_FAST_SPEED, PR8210_SEEK_FAST_SPEED, -PR8210_JUMP_REV_SPEED }; + INT32 curseekspeed = seekspeed[ld->state.substate]; + INT32 delta = (ld->state.param - 1) - frame; + + if ((curseekspeed < 0 && delta <= 0) || (curseekspeed > 0 && delta >= 0)) + advanceby = curseekspeed; + else + ld->state.substate++; + } + + /* otherwise, keep advancing until we know what's up */ + else if (fieldnum == 1) + advanceby = 1; + break; + + case LDSTATE_STEPPING_BY_PARAMETER: + /* advance after the second field of each frame */ + if (fieldnum == 1) + { + /* note that we switch directly to PAUSED as we are not looking for frames */ + advanceby = ld->state.param; + pr8210_switch_state(ld, LDSTATE_PAUSED, 0); + } + break; + } + + return advanceby; +} + + +/*------------------------------------------------- + pr8210_command - Pioneer PR-8210-specific + command processing +-------------------------------------------------*/ + +static void pr8210_command(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + UINT8 cmd = player->lastcommand; + + switch (cmd) + { + case 0x00: CMDPRINTF(("pr8210: EOC\n")); + /* EOC marker - can be safely ignored */ + break; + + case 0x01: CMDPRINTF(("pr8210: 0\n")); + player->parameter = (player->parameter == -1) ? 0 : (player->parameter * 10 + 0); + break; + + case 0x02: CMDPRINTF(("pr8210: Slow reverse\n")); + pr8210_switch_state(ld, LDSTATE_PLAYING_SLOW_REVERSE, PR8210_SLOW_SPEED); + break; + + case 0x03: CMDPRINTF(("pr8210: 8\n")); + player->parameter = (player->parameter == -1) ? 8 : (player->parameter * 10 + 8); + break; + + case 0x04: CMDPRINTF(("pr8210: Step forward\n")); + pr8210_switch_state(ld, LDSTATE_STEPPING_FORWARD, 0); + break; + + case 0x05: CMDPRINTF(("pr8210: 4\n")); + player->parameter = (player->parameter == -1) ? 4 : (player->parameter * 10 + 4); + break; + + case 0x06 : CMDPRINTF(("pr8210: Chapter\n")); + /* chapter -- not implemented */ + break; + + case 0x08: CMDPRINTF(("pr8210: Scan forward\n")); + if (ld->state.state != LDSTATE_SCANNING) + pr8210_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(PR8210_SCAN_SPEED, PR8210_SCAN_DURATION)); + else + ld->state.substate = 0; + break; + + case 0x09: CMDPRINTF(("pr8210: 2\n")); + player->parameter = (player->parameter == -1) ? 2 : (player->parameter * 10 + 2); + break; + + case 0x0a: CMDPRINTF(("pr8210: Pause\n")); + pr8210_switch_state(ld, LDSTATE_PAUSING, 0); + break; + + case 0x0b : CMDPRINTF(("pr8210: Frame\n")); + /* frame -- not implemented */ + break; + + case 0x0c: CMDPRINTF(("pr8210: Fast reverse\n")); + pr8210_switch_state(ld, LDSTATE_PLAYING_FAST_REVERSE, PR8210_FAST_SPEED); + break; + + case 0x0d: CMDPRINTF(("pr8210: 6\n")); + player->parameter = (player->parameter == -1) ? 6 : (player->parameter * 10 + 6); + break; + + case 0x0e: CMDPRINTF(("pr8210: Ch1 toggle\n")); + player->audio1disable = !player->audio1disable; + pr8210_update_squelch(ld); + break; + + case 0x10: CMDPRINTF(("pr8210: Fast forward\n")); + pr8210_switch_state(ld, LDSTATE_PLAYING_FAST_FORWARD, PR8210_FAST_SPEED); + break; + + case 0x11: CMDPRINTF(("pr8210: 1\n")); + player->parameter = (player->parameter == -1) ? 1 : (player->parameter * 10 + 1); + break; + + case 0x12: CMDPRINTF(("pr8210: Step reverse\n")); + pr8210_switch_state(ld, LDSTATE_STEPPING_REVERSE, 0); + break; + + case 0x13: CMDPRINTF(("pr8210: 9\n")); + player->parameter = (player->parameter == -1) ? 9 : (player->parameter * 10 + 9); + break; + + case 0x14: CMDPRINTF(("pr8210: Play\n")); + if (ld->state.state == LDSTATE_EJECTED) + pr8210_switch_state(ld, LDSTATE_LOADING, 0); + else if (ld->state.state == LDSTATE_PARKED) + pr8210_switch_state(ld, LDSTATE_SPINUP, 0); + else + pr8210_switch_state(ld, LDSTATE_PLAYING, 0); + break; + + case 0x15: CMDPRINTF(("pr8210: 5\n")); + player->parameter = (player->parameter == -1) ? 5 : (player->parameter * 10 + 5); + break; + + case 0x16: CMDPRINTF(("pr8210: Ch2 toggle\n")); + player->audio2disable = !player->audio2disable; + pr8210_update_squelch(ld); + break; + + case 0x18: CMDPRINTF(("pr8210: Slow forward\n")); + pr8210_switch_state(ld, LDSTATE_PLAYING_SLOW_FORWARD, PR8210_SLOW_SPEED); + break; + + case 0x19: CMDPRINTF(("pr8210: 3\n")); + player->parameter = (player->parameter == -1) ? 3 : (player->parameter * 10 + 3); + break; + + case 0x1a: CMDPRINTF(("pr8210: Seek\n")); + if (player->seekstate) + { + /* we're ready to seek */ + CMDPRINTF(("pr8210: Seeking to frame %d\n", player->parameter)); + pr8210_switch_state(ld, LDSTATE_SEEKING, player->parameter); + } + else + { + /* waiting for digits indicating position */ + player->parameter = 0; + } + player->seekstate ^= 1; + break; + + case 0x1c: CMDPRINTF(("pr8210: Scan reverse\n")); + if (ld->state.state != LDSTATE_SCANNING) + pr8210_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(-PR8210_SCAN_SPEED, PR8210_SCAN_DURATION)); + else + ld->state.substate = 0; + break; + + case 0x1d: CMDPRINTF(("pr8210: 7\n")); + player->parameter = (player->parameter == -1) ? 7 : (player->parameter * 10 + 7); + break; + + case 0x1e: CMDPRINTF(("pr8210: Reject\n")); + pr8210_switch_state(ld, LDSTATE_EJECTING, 0); + break; + + default: CMDPRINTF(("pr8210: Unknown command %02X\n", cmd)); + break; + } +} + + +/*------------------------------------------------- + pr8210_control_w - write callback when the + CONTROL line is toggled +-------------------------------------------------*/ + +static void pr8210_control_w(laserdisc_state *ld, UINT8 prev, UINT8 data) +{ + ldplayer_data *player = ld->player; + + if (data == ASSERT_LINE) + { + attotime curtime = timer_get_time(); + attotime delta; + int longpulse; + + /* if we timed out, reset the accumulator */ + delta = attotime_sub(curtime, player->firstbittime); + if (delta.attoseconds > ATTOTIME_IN_USEC(25320).attoseconds) + { + player->firstbittime = curtime; + player->accumulator = 0x5555; +// printf("Reset accumulator\n"); + } + + /* get the time difference from the last assert */ + /* and update our internal command time */ + delta = attotime_sub(curtime, player->lastbittime); + player->lastbittime = curtime; + + /* 0 bit delta is 1.05 msec, 1 bit delta is 2.11 msec */ + longpulse = (delta.attoseconds < ATTOTIME_IN_USEC(1500).attoseconds) ? 0 : 1; + player->accumulator = (player->accumulator << 1) | longpulse; + +#if 0 + { + int usecdiff = (int)(delta.attoseconds / ATTOSECONDS_IN_USEC(1)); + printf("bitdelta = %5d (%d) - accum = %04X\n", usecdiff, longpulse, player->accumulator); + } +#endif + + /* if we have a complete command, signal it */ + /* a complete command is 0,0,1 followed by 5 bits, followed by 0,0 */ + if ((player->accumulator & 0x383) == 0x80) + { + UINT8 newcommand = (player->accumulator >> 2) & 0x1f; + +//printf("New command = %02X (last=%02X)\n", newcommand, player->lastcommand); + + /* if we got a double command, act on it */ + if (newcommand == player->lastcommand) + { + pr8210_command(ld); + player->lastcommand = 0; + } + else + player->lastcommand = newcommand; + } + } +} + + + +/*************************************************************************** + SIMUTREK MODIFIED PR-8210 PLAYER IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + + Command Set: + + FX XX XX : Seek to frame XXXXX + 01-19 : Skip forward 1-19 frames + 99-81 : Skip back 1-19 frames + 5a : Toggle frame display + +-------------------------------------------------*/ + +/*------------------------------------------------- + simutrek_init - Simutrek-specific + initialization +-------------------------------------------------*/ + +static void simutrek_init(laserdisc_state *ld) +{ + /* do a soft reset */ + pr8210_soft_reset(ld); +} + + +/*------------------------------------------------- + simutrek_status_r - Simutrek-specific + command processing +-------------------------------------------------*/ + +static UINT8 simutrek_status_r(laserdisc_state *ld) +{ + return (ld->state.state != LDSTATE_SEEKING) ? ASSERT_LINE : CLEAR_LINE; +} + + +/*------------------------------------------------- + simutrek_data_w - Simutrek-specific + data processing +-------------------------------------------------*/ + +static void simutrek_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data) +{ + ldplayer_data *player = ld->player; + + /* Acknowledge every command byte */ + if (player->cmd_ack_callback != NULL) + (*player->cmd_ack_callback)(); + + /* Is this byte part of a multi-byte seek command? */ + if (player->cmdcnt > 0) + { + CMDPRINTF(("Simutrek: Seek to frame byte %d of 3\n", player->cmdcnt + 1)); + player->cmdbytes[player->cmdcnt++] = data; + + if (player->cmdcnt == 3) + { + player->parameter = ((player->cmdbytes[0] & 0xf) * 10000) + + ((player->cmdbytes[1] >> 4) * 1000) + + ((player->cmdbytes[1] & 0xf) * 100) + + ((player->cmdbytes[2] >> 4) * 10) + + (player->cmdbytes[2] & 0xf); + + CMDPRINTF(("Simutrek: Seek to frame %d\n", player->parameter)); + + pr8210_switch_state(ld, LDSTATE_SEEKING, player->parameter); + player->cmdcnt = 0; + } + } + else if (data == 0) + { + CMDPRINTF(("Simutrek: 0 ?\n")); + } + else if ((data & 0xf0) == 0xf0) + { + CMDPRINTF(("Simutrek: Seek to frame byte 1 of 3\n")); + player->cmdbytes[player->cmdcnt++] = data; + } + else if (data >= 1 && data <= 0x19) + { + int count = ((data >> 4) * 10) + (data & 0xf); + CMDPRINTF(("Simutrek: Step forwards by %d frame(s)\n", count)); + pr8210_switch_state(ld, LDSTATE_STEPPING_BY_PARAMETER, count); + } + else if (data >= 0x81 && data <= 0x99) + { + int count = (((data >> 4) * 10) + (data & 0xf)) - 100; + CMDPRINTF(("Simutrek: Step backwards by %d frame(s)\n", count)); + pr8210_switch_state(ld, LDSTATE_STEPPING_BY_PARAMETER, count); + } + else if (data == 0x5a) + { + CMDPRINTF(("Simutrek: Frame window toggle\n")); + player->framedisplay ^= 1; + } + else + { + CMDPRINTF(("Simutrek: Unknown command (%.2x)\n", data)); + } +} + + +/*------------------------------------------------- + simutrek_set_audio_squelch - Simutrek-specific + command to enable/disable audio squelch +-------------------------------------------------*/ + +void simutrek_set_audio_squelch(const device_config *device, int state) +{ + laserdisc_state *ld = ldcore_get_safe_token(device); + int squelch = (state != ASSERT_LINE); + ldcore_set_audio_squelch(ld, squelch, squelch); +} + + +/*------------------------------------------------- + simutrek_set_audio_squelch - Simutrek-specific + command to set callback function for + player/interface command acknowledge +-------------------------------------------------*/ + +void simutrek_set_cmd_ack_callback(const device_config *device, void (*callback)(void)) +{ + laserdisc_state *ld = ldcore_get_safe_token(device); + ldplayer_data *player = ld->player; + + player->cmd_ack_callback = callback; +} diff --git a/src/emu/machine/ldv1000.c b/src/emu/machine/ldv1000.c new file mode 100644 index 00000000000..36f00853714 --- /dev/null +++ b/src/emu/machine/ldv1000.c @@ -0,0 +1,742 @@ +/************************************************************************* + + ldv1000.c + + Pioneer LD-V1000 laserdisc emulation. + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*************************************************************************/ + +#include "ldcore.h" + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define PRINTF_COMMANDS 1 +#define CMDPRINTF(x) do { if (PRINTF_COMMANDS) mame_printf_debug x; } while (0) + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* Player-specific states */ +enum +{ + LDSTATE_SKIPPING = LDSTATE_OTHER, + LDSTATE_WAITING +}; + +/* Pioneer LD-V1000 specific information */ +enum +{ + LDV1000_MODE_STATUS, + LDV1000_MODE_GET_FRAME, + LDV1000_MODE_GET_1ST, + LDV1000_MODE_GET_2ND, + LDV1000_MODE_GET_RAM +}; + +#define LDV1000_SCAN_SPEED (2000 / 30) /* 2000 frames/second */ +#define LDV1000_SCAN_DURATION (4) /* scan for 4 vsyncs each time */ + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* player-specific data */ +struct _ldplayer_data +{ + /* general modes and states */ + UINT8 audio1disable; /* explicit disable for audio 1 */ + UINT8 audio2disable; /* explicit disable for audio 2 */ + UINT8 framedisplay; /* frame display */ + INT32 parameter; /* parameter for numbers */ + + UINT8 mode; /* current mode */ + UINT8 lastchapter; /* last chapter seen */ + UINT16 lastframe; /* last frame seen */ + UINT32 activereg; /* active register index */ + UINT8 ram[1024]; /* RAM */ + UINT32 readpos; /* current read position */ + UINT32 readtotal; /* current read position */ + UINT8 readbuf[256]; /* temporary read buffer */ + attotime lastvsynctime; /* time of last update/vsync */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static void ldv1000_init(laserdisc_state *ld); +static void ldv1000_soft_reset(laserdisc_state *ld); +static void ldv1000_update_squelch(laserdisc_state *ld); +static int ldv1000_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 stateparam); +static INT32 ldv1000_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime); +static void ldv1000_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data); +static UINT8 ldv1000_status_strobe_r(laserdisc_state *ld); +static UINT8 ldv1000_command_strobe_r(laserdisc_state *ld); +static UINT8 ldv1000_status_r(laserdisc_state *ld); + + + +/*************************************************************************** + INTERFACES +***************************************************************************/ + +const ldplayer_interface ldv1000_interface = +{ + LASERDISC_TYPE_PIONEER_LDV1000, /* type of the player */ + sizeof(ldplayer_data), /* size of the state */ + "Pioneer LD-V1000", /* name of the player */ + ldv1000_init, /* initialization callback */ + ldv1000_update, /* update callback */ + ldv1000_data_w, /* parallel data write */ + { /* single line write: */ + NULL, /* LASERDISC_LINE_ENTER */ + NULL /* LASERDISC_LINE_CONTROL */ + }, + ldv1000_status_r, /* parallel data read */ + { /* single line read: */ + NULL, /* LASERDISC_LINE_READY */ + ldv1000_status_strobe_r, /* LASERDISC_LINE_STATUS */ + ldv1000_command_strobe_r, /* LASERDISC_LINE_COMMAND */ + NULL, /* LASERDISC_LINE_DATA_AVAIL */ + } +}; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + requires_state_save - returns TRUE if the + given state will return to the previous + state when done +-------------------------------------------------*/ + +INLINE int requires_state_save(UINT8 state) +{ + return (state == LDSTATE_SCANNING || + state == LDSTATE_SKIPPING || + state == LDSTATE_WAITING); +} + + +/*------------------------------------------------- + read_16bits_from_ram_be - read 16 bits from + player RAM in big-endian format +-------------------------------------------------*/ + +INLINE UINT16 read_16bits_from_ram_be(UINT8 *ram, UINT32 offset) +{ + return (ram[offset + 0] << 8) | ram[offset + 1]; +} + + +/*------------------------------------------------- + write_16bits_to_ram_be - write 16 bits to + player RAM in big endian format +-------------------------------------------------*/ + +INLINE void write_16bits_to_ram_be(UINT8 *ram, UINT32 offset, UINT16 data) +{ + ram[offset + 0] = data >> 8; + ram[offset + 1] = data >> 0; +} + + + +/*************************************************************************** + PIONEER LD-V1000 IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + ldv1000_init - Pioneer PR-8210-specific + initialization +-------------------------------------------------*/ + +static void ldv1000_init(laserdisc_state *ld) +{ + /* do a soft reset */ + ldv1000_soft_reset(ld); +} + + +/*------------------------------------------------- + ldv1000_soft_reset - Pioneer LDV-1000-specific + soft reset +-------------------------------------------------*/ + +static void ldv1000_soft_reset(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + attotime curtime = timer_get_time(); + + ld->state.state = LDSTATE_NONE; + ld->state.substate = 0; + ld->state.param = 0; + ld->state.endtime = curtime; + + player->audio1disable = 0; + player->audio2disable = 0; + player->parameter = 0; + + player->mode = 0; + player->activereg = 0; + memset(player->ram, 0, sizeof(player->ram)); + player->readpos = 0; + player->readtotal = 0; + memset(player->readbuf, 0, sizeof(player->readbuf)); + player->lastvsynctime = curtime; + + ldv1000_switch_state(ld, LDSTATE_PARKED, 0); +} + + +/*------------------------------------------------- + ldv1000_update_squelch - update the squelch + settings based on the current state +-------------------------------------------------*/ + +static void ldv1000_update_squelch(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + + switch (ld->state.state) + { + /* video on, audio on */ + case LDSTATE_PLAYING: + ldcore_set_audio_squelch(ld, player->audio1disable, player->audio2disable); + ldcore_set_video_squelch(ld, FALSE); + break; + + /* video on, audio off */ + case LDSTATE_PAUSING: + case LDSTATE_PAUSED: + case LDSTATE_PLAYING_SLOW_REVERSE: + case LDSTATE_PLAYING_SLOW_FORWARD: + case LDSTATE_PLAYING_FAST_REVERSE: + case LDSTATE_PLAYING_FAST_FORWARD: + case LDSTATE_STEPPING_REVERSE: + case LDSTATE_STEPPING_FORWARD: + case LDSTATE_SCANNING: + case LDSTATE_SKIPPING: + case LDSTATE_WAITING: + ldcore_set_audio_squelch(ld, TRUE, TRUE); + ldcore_set_video_squelch(ld, FALSE); + break; + + /* video off, audio off */ + case LDSTATE_NONE: + case LDSTATE_EJECTING: + case LDSTATE_EJECTED: + case LDSTATE_PARKED: + case LDSTATE_LOADING: + case LDSTATE_SPINUP: + case LDSTATE_SEEKING: + ldcore_set_audio_squelch(ld, TRUE, TRUE); + ldcore_set_video_squelch(ld, TRUE); + break; + } +} + + +/*------------------------------------------------- + ldv1000_switch_state - attempt to switch states + if appropriate, and ensure the squelch values + are correct +-------------------------------------------------*/ + +static int ldv1000_switch_state(laserdisc_state *ld, UINT8 newstate, INT32 parameter) +{ + attotime curtime = timer_get_time(); + + /* if this is the same state, ignore */ + if (ld->state.state == newstate) + return TRUE; + + /* if we're in a timed state, we ignore changes until the time elapses */ + if (attotime_compare(curtime, ld->state.endtime) < 0) + return FALSE; + + /* if moving to a state that requires it, save the old state */ + if (requires_state_save(newstate) && !requires_state_save(ld->state.state)) + ld->savestate = ld->state; + + /* set up appropriate squelch and timing */ + ld->state.state = newstate; + ld->state.substate = 0; + ld->state.param = parameter; + ld->state.endtime = curtime; + ldv1000_update_squelch(ld); + + /* if we're switching to a timed state, set the appropriate timer */ + switch (ld->state.state) + { + case LDSTATE_EJECTING: + ld->state.endtime = attotime_add(curtime, GENERIC_EJECT_TIME); + break; + + case LDSTATE_LOADING: + ld->state.endtime = attotime_add(curtime, GENERIC_LOAD_TIME); + break; + + case LDSTATE_SPINUP: + ld->state.endtime = attotime_add(curtime, GENERIC_SPINUP_TIME); + break; + + case LDSTATE_WAITING: + ld->state.endtime = attotime_add(curtime, attotime_mul(ATTOTIME_IN_MSEC(100), ld->state.param)); + break; + } + return TRUE; +} + + +/*------------------------------------------------- + ldv1000_update - Pioneer PR-8210-specific + update callback +-------------------------------------------------*/ + +static INT32 ldv1000_update(laserdisc_state *ld, const vbi_metadata *vbi, int fieldnum, attotime curtime) +{ + ldplayer_data *player = ld->player; + ldplayer_state newstate; + INT32 advanceby = 0; + int frame, chapter; + + /* remember the last frame and chapter */ + player->lastvsynctime = curtime; + frame = frame_from_metadata(vbi); + if (frame != FRAME_NOT_PRESENT) + player->lastframe = frame; + chapter = chapter_from_metadata(vbi); + if (chapter != CHAPTER_NOT_PRESENT) + player->lastchapter = chapter; + + /* handle things based on the state */ + switch (ld->state.state) + { + case LDSTATE_EJECTING: + case LDSTATE_EJECTED: + case LDSTATE_PARKED: + case LDSTATE_LOADING: + case LDSTATE_SPINUP: + case LDSTATE_PAUSING: + case LDSTATE_PAUSED: + case LDSTATE_PLAYING: + case LDSTATE_PLAYING_SLOW_REVERSE: + case LDSTATE_PLAYING_SLOW_FORWARD: + case LDSTATE_PLAYING_FAST_REVERSE: + case LDSTATE_PLAYING_FAST_FORWARD: + case LDSTATE_STEPPING_REVERSE: + case LDSTATE_STEPPING_FORWARD: + case LDSTATE_SCANNING: + case LDSTATE_SEEKING: + /* generic behaviors are appropriate for all of these commands */ + advanceby = ldcore_generic_update(ld, vbi, fieldnum, curtime, &newstate); + ldv1000_switch_state(ld, newstate.state, newstate.param); + break; + + case LDSTATE_SKIPPING: + /* just advance and return to previous state */ + advanceby = ld->state.param; + ldv1000_switch_state(ld, ld->savestate.state, ld->savestate.param); + break; + + case LDSTATE_WAITING: + /* keep trying to switch to the previous state until we succeed */ + ldv1000_switch_state(ld, ld->savestate.state, ld->savestate.param); + break; + } + + return advanceby; +} + + +/*------------------------------------------------- + ldv1000_data_w - write callback when the + ENTER state is written +-------------------------------------------------*/ + +static void ldv1000_data_w(laserdisc_state *ld, UINT8 prev, UINT8 data) +{ + ldplayer_data *player = ld->player; + int target; + + /* 0xff bytes are used for synchronization */ + if (data == 0xff) + return; + + /* handle commands */ + switch (data) + { + case 0x3f: CMDPRINTF(("ldv1000: 0\n")); + player->parameter = (player->parameter == -1) ? 0 : (player->parameter * 10 + 0); + return; + + case 0x0f: CMDPRINTF(("ldv1000: 1\n")); + player->parameter = (player->parameter == -1) ? 1 : (player->parameter * 10 + 1); + return; + + case 0x8f: CMDPRINTF(("ldv1000: 2\n")); + player->parameter = (player->parameter == -1) ? 2 : (player->parameter * 10 + 2); + return; + + case 0x4f: CMDPRINTF(("ldv1000: 3\n")); + player->parameter = (player->parameter == -1) ? 3 : (player->parameter * 10 + 3); + return; + + case 0x2f: CMDPRINTF(("ldv1000: 4\n")); + player->parameter = (player->parameter == -1) ? 4 : (player->parameter * 10 + 4); + return; + + case 0xaf: CMDPRINTF(("ldv1000: 5\n")); + player->parameter = (player->parameter == -1) ? 5 : (player->parameter * 10 + 5); + return; + + case 0x6f: CMDPRINTF(("ldv1000: 6\n")); + player->parameter = (player->parameter == -1) ? 6 : (player->parameter * 10 + 6); + return; + + case 0x1f: CMDPRINTF(("ldv1000: 7\n")); + player->parameter = (player->parameter == -1) ? 7 : (player->parameter * 10 + 7); + return; + + case 0x9f: CMDPRINTF(("ldv1000: 8\n")); + player->parameter = (player->parameter == -1) ? 8 : (player->parameter * 10 + 8); + return; + + case 0x5f: CMDPRINTF(("ldv1000: 9\n")); + player->parameter = (player->parameter == -1) ? 9 : (player->parameter * 10 + 9); + return; + + case 0x7f: CMDPRINTF(("ldv1000: %d Recall\n", player->parameter)); + /* set the active register */ + player->activereg = player->parameter; + /* should also display the register value */ + break; + + case 0x20: CMDPRINTF(("ldv1000: x0 reverse (stop) - Badlands special\n")); + ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_REVERSE, 0); + break; + + case 0x21: CMDPRINTF(("ldv1000: x1/4 reverse - Badlands special\n")); + ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_REVERSE, 4); + break; + + case 0x22: CMDPRINTF(("ldv1000: x1/2 reverse - Badlands special\n")); + ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_REVERSE, 2); + break; + + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: CMDPRINTF(("ldv1000: x%d reverse - Badlands special\n", (data & 0x07) - 2)); + ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_REVERSE, (data & 0x07) - 2); + break; + + case 0xa0: CMDPRINTF(("ldv1000: x0 forward (stop)\n")); + ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_FORWARD, 0); + break; + + case 0xa1: CMDPRINTF(("ldv1000: x1/4 forward\n")); + ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_FORWARD, 4); + break; + + case 0xa2: CMDPRINTF(("ldv1000: x1/2 forward\n")); + ldv1000_switch_state(ld, LDSTATE_PLAYING_SLOW_FORWARD, 2); + break; + + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: CMDPRINTF(("ldv1000: x%d forward\n", (data & 0x07) - 2)); + ldv1000_switch_state(ld, LDSTATE_PLAYING_FAST_FORWARD, (data & 0x07) - 2); + break; + + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + case 0xb8: + case 0xb9: + case 0xba: CMDPRINTF(("ldv1000: Skip forward %d0\n", data & 0x0f)); + /* note that this skips tracks, not frames */ + ldv1000_switch_state(ld, LDSTATE_SKIPPING, 10 * (data & 0x0f)); + break; + + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3a: CMDPRINTF(("ldv1000: Skip backwards %d0 - Badlands special\n", data & 0x0f)); + /* note that this skips tracks, not frames */ + ldv1000_switch_state(ld, LDSTATE_SKIPPING, -10 * (data & 0x0f)); + break; + + case 0xbf: CMDPRINTF(("ldv1000: %d Clear\n", player->parameter)); + /* clears register display and removes pending arguments */ + player->parameter = -1; + break; + + case 0xc2: CMDPRINTF(("ldv1000: Get frame no.\n")); + /* returns the current frame number */ + player->mode = LDV1000_MODE_GET_FRAME; + player->readpos = 0; + player->readtotal = 5; + sprintf((char *)player->readbuf, "%05d", player->lastframe); + break; + + case 0xc3: CMDPRINTF(("ldv1000: Get 2nd display\n")); + /* returns the data from the 2nd display line */ + player->mode = LDV1000_MODE_GET_2ND; + player->readpos = 0; + player->readtotal = 8; + sprintf((char *)player->readbuf, " \x1c\x1c\x1c"); + break; + + case 0xc4: CMDPRINTF(("ldv1000: Get 1st display\n")); + /* returns the data from the 1st display line */ + player->mode = LDV1000_MODE_GET_1ST; + player->readpos = 0; + player->readtotal = 8; + sprintf((char *)player->readbuf, " \x1c\x1c\x1c"); + break; + + case 0xc8: CMDPRINTF(("ldv1000: Transfer memory\n")); + /* returns the data from RAM */ + player->mode = LDV1000_MODE_GET_RAM; + player->readpos = 0; + player->readtotal = 1024; + break; + + case 0xcc: CMDPRINTF(("ldv1000: Load\n")); + /* load program from disc -- not implemented */ + break; + + case 0xcd: CMDPRINTF(("ldv1000: Display disable\n")); + /* disables the display of current command -- not implemented */ + break; + + case 0xce: CMDPRINTF(("ldv1000: Display enable\n")); + /* enables the display of current command -- not implemented */ + break; + + case 0xf0: CMDPRINTF(("ldv1000: Scan forward\n")); + if (ld->state.state != LDSTATE_SCANNING) + ldv1000_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(LDV1000_SCAN_SPEED, LDV1000_SCAN_DURATION)); + else + ld->state.substate = 0; + break; + + case 0xf1: CMDPRINTF(("ldv1000: %d Display\n", player->parameter)); + player->framedisplay = (player->parameter == -1) ? !player->framedisplay : (player->parameter & 1); + break; + + case 0xf3: CMDPRINTF(("ldv1000: %d Autostop\n", player->parameter)); + target = (player->parameter != -1) ? player->parameter : read_16bits_from_ram_be(player->ram, (player->activereg++ * 2) % 1024); + if (target > player->lastframe) + ldv1000_switch_state(ld, LDSTATE_PLAYING, target); + else + ldv1000_switch_state(ld, LDSTATE_SEEKING, target); + break; + + case 0xf4: CMDPRINTF(("ldv1000: %d Audio track 1\n", player->parameter)); + if (player->parameter == -1) + player->audio1disable ^= 1; + else + player->audio1disable = ~player->parameter & 1; + break; + + case 0xf5: CMDPRINTF(("ldv1000: %d Store\n", player->parameter)); + /* store either the current frame number or an explicit value into the active register */ + if (player->parameter == -1) + write_16bits_to_ram_be(player->ram, (player->activereg * 2) % 1024, player->lastframe); + else + write_16bits_to_ram_be(player->ram, (player->activereg * 2) % 1024, player->parameter); + player->activereg++; + break; + + case 0xf6: CMDPRINTF(("ldv1000: Step forward\n")); + ldv1000_switch_state(ld, LDSTATE_STEPPING_FORWARD, 0); + break; + + case 0xf7: CMDPRINTF(("ldv1000: %d Search\n", player->parameter)); + target = (player->parameter != -1) ? player->parameter : read_16bits_from_ram_be(player->ram, (player->activereg * 2) % 1024); + player->activereg++; + ldv1000_switch_state(ld, LDSTATE_SEEKING, target); + break; + + case 0xf8: CMDPRINTF(("ldv1000: Scan reverse\n")); + if (ld->state.state != LDSTATE_SCANNING) + ldv1000_switch_state(ld, LDSTATE_SCANNING, SCANNING_PARAM(-LDV1000_SCAN_SPEED, LDV1000_SCAN_DURATION)); + else + ld->state.substate = 0; + break; + + case 0xf9: CMDPRINTF(("ldv1000: Reject\n")); + ldv1000_switch_state(ld, LDSTATE_EJECTING, 0); + break; + + case 0xfb: CMDPRINTF(("ldv1000: %d Stop/Wait\n", player->parameter)); + ldv1000_switch_state(ld, LDSTATE_WAITING, player->parameter); + break; + + case 0xfc: CMDPRINTF(("ldv1000: %d Audio track 2\n", player->parameter)); + if (player->parameter == -1) + player->audio2disable ^= 1; + else + player->audio2disable = ~player->parameter & 1; + break; + + case 0xfd: CMDPRINTF(("ldv1000: Play\n")); + if (ld->state.state == LDSTATE_EJECTED) + ldv1000_switch_state(ld, LDSTATE_LOADING, 0); + else if (ld->state.state == LDSTATE_PARKED) + ldv1000_switch_state(ld, LDSTATE_SPINUP, 0); + else + ldv1000_switch_state(ld, LDSTATE_PLAYING, -1); + break; + + case 0xfe: CMDPRINTF(("ldv1000: Step reverse\n")); + ldv1000_switch_state(ld, LDSTATE_STEPPING_REVERSE, 0); + break; + + default: CMDPRINTF(("ldv1000: %d Unknown command %02X\n", player->parameter, data)); + /* unknown command */ + break; + } + + /* reset the parameter after executing a command */ + player->parameter = -1; +} + + +/*------------------------------------------------- + ldv1000_status_strobe_r - return state of the + status strobe +-------------------------------------------------*/ + +static UINT8 ldv1000_status_strobe_r(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + + /* the status strobe is asserted (active low) 500-650usec after VSYNC */ + /* for a duration of 26usec; we pick 600-626usec */ + attotime delta = attotime_sub(timer_get_time(), player->lastvsynctime); + if (delta.attoseconds >= ATTOTIME_IN_USEC(600).attoseconds && delta.attoseconds < ATTOTIME_IN_USEC(626).attoseconds) + return ASSERT_LINE; + + return CLEAR_LINE; +} + + +/*------------------------------------------------- + ldv1000_command_strobe_r - return state of the + command strobe +-------------------------------------------------*/ + +static UINT8 ldv1000_command_strobe_r(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + + /* the command strobe is asserted (active low) 54 or 84usec after the status */ + /* strobe for a duration of 25usec; we pick 600+84 = 684-709usec */ + /* for a duration of 26usec; we pick 600-626usec */ + attotime delta = attotime_sub(timer_get_time(), player->lastvsynctime); + if (delta.attoseconds >= ATTOTIME_IN_USEC(684).attoseconds && delta.attoseconds < ATTOTIME_IN_USEC(709).attoseconds) + return ASSERT_LINE; + + return CLEAR_LINE; +} + + +/*------------------------------------------------- + ldv1000_status_r - return state of the ready + line +-------------------------------------------------*/ + +static UINT8 ldv1000_status_r(laserdisc_state *ld) +{ + ldplayer_data *player = ld->player; + UINT8 status = 0xff; + + /* switch off the current mode */ + switch (player->mode) + { + /* reading frame number returns 5 characters */ + /* reading display lines returns 8 characters */ + case LDV1000_MODE_GET_FRAME: + case LDV1000_MODE_GET_1ST: + case LDV1000_MODE_GET_2ND: + assert(player->readpos < player->readtotal); + status = player->readbuf[player->readpos++]; + if (player->readpos == player->readtotal) + player->mode = LDV1000_MODE_STATUS; + break; + + /* reading RAM returns 1024 bytes */ + case LDV1000_MODE_GET_RAM: + assert(player->readpos < player->readtotal); + status = player->ram[1023 - player->readpos++]; + if (player->readpos == player->readtotal) + player->mode = LDV1000_MODE_STATUS; + break; + + /* otherwise, we just compute a status code */ + default: + case LDV1000_MODE_STATUS: + switch (ld->state.state) + { + case LDSTATE_EJECTING: status = 0x60; break; + case LDSTATE_EJECTED: status = 0xe0; break; + case LDSTATE_PARKED: status = 0xfc; break; + case LDSTATE_LOADING: status = 0x48; break; + case LDSTATE_SPINUP: status = 0x48; break; + case LDSTATE_PAUSING: status = 0xe5; break; + case LDSTATE_PAUSED: status = 0xe5; break; + case LDSTATE_PLAYING: status = 0xe4; break; + case LDSTATE_PLAYING_SLOW_REVERSE: status = 0xae; break; + case LDSTATE_PLAYING_SLOW_FORWARD: status = 0xae; break; + case LDSTATE_PLAYING_FAST_REVERSE: status = 0xae; break; + case LDSTATE_PLAYING_FAST_FORWARD: status = 0xae; break; + case LDSTATE_STEPPING_FORWARD: status = 0xe5; break; + case LDSTATE_STEPPING_REVERSE: status = 0xe5; break; + case LDSTATE_SCANNING: status = 0x4c; break; + case LDSTATE_SEEKING: status = 0x50; break; +// case LASERDISC_SEARCH_FINISHED: status = 0xd0; break; +// case LASERDISC_AUTOSTOPPED: status = 0x54; break; + default: + fatalerror("Unexpected disc state in ldv1000_status_r\n"); + break; + } + break; + } + + /* bit 7 indicates our busy status */ + return status; +} |