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