summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/machine/laserdsc.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/machine/laserdsc.c')
-rw-r--r--trunk/src/emu/machine/laserdsc.c1248
1 files changed, 1248 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/laserdsc.c b/trunk/src/emu/machine/laserdsc.c
new file mode 100644
index 00000000000..f5b6c2e0520
--- /dev/null
+++ b/trunk/src/emu/machine/laserdsc.c
@@ -0,0 +1,1248 @@
+/*************************************************************************
+
+ laserdsc.c
+
+ Core laserdisc player implementation.
+
+****************************************************************************
+
+ Copyright Aaron Giles
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name 'MAME' nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+*************************************************************************/
+
+#include "emu.h"
+#include "laserdsc.h"
+#include "avhuff.h"
+#include "vbiparse.h"
+#include "config.h"
+#include "render.h"
+#include "chd.h"
+
+
+
+//**************************************************************************
+// DEBUGGING
+//**************************************************************************
+
+#define LOG_SLIDER 0
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// these specs code from IEC 60857, for NTSC players
+const UINT32 LEAD_IN_MIN_RADIUS_IN_UM = 53500; // 53.5 mm
+const UINT32 PROGRAM_MIN_RADIUS_IN_UM = 55000; // 55 mm
+const UINT32 PROGRAM_MAX_RADIUS_IN_UM = 145000; // 145 mm
+const UINT32 LEAD_OUT_MIN_SIZE_IN_UM = 2000; // 2 mm
+
+// the track pitch is defined as a range; we pick a nominal pitch
+// that ensures we can fit 54,000 tracks
+const UINT32 MIN_TRACK_PITCH_IN_NM = 1400; // 1.4 um
+const UINT32 MAX_TRACK_PITCH_IN_NM = 2000; // 2 um
+const UINT32 NOMINAL_TRACK_PITCH_IN_NM = (PROGRAM_MAX_RADIUS_IN_UM - PROGRAM_MIN_RADIUS_IN_UM) * 1000 / 54000;
+
+// we simulate extra lead-in and lead-out tracks
+const UINT32 VIRTUAL_LEAD_IN_TRACKS = (PROGRAM_MIN_RADIUS_IN_UM - LEAD_IN_MIN_RADIUS_IN_UM) * 1000 / NOMINAL_TRACK_PITCH_IN_NM;
+const UINT32 MAX_TOTAL_TRACKS = 54000;
+const UINT32 VIRTUAL_LEAD_OUT_TRACKS = LEAD_OUT_MIN_SIZE_IN_UM * 1000 / NOMINAL_TRACK_PITCH_IN_NM;
+
+
+
+//**************************************************************************
+// CORE IMPLEMENTATION
+//**************************************************************************
+
+//-------------------------------------------------
+// laserdisc_device - constructor
+//-------------------------------------------------
+
+laserdisc_device::laserdisc_device(const machine_config &mconfig, device_type type, const char *name, const char *shortname, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, type, name, shortname, tag, owner, clock),
+ device_sound_interface(mconfig, *this),
+ m_screen_name(NULL),
+ m_overwidth(0),
+ m_overheight(0),
+ m_overclip(0, -1, 0, -1),
+ m_disc(NULL),
+ m_width(0),
+ m_height(0),
+ m_fps_times_1million(0),
+ m_samplerate(0),
+ m_readresult(CHDERR_NONE),
+ m_chdtracks(0),
+ m_work_queue(osd_work_queue_alloc(WORK_QUEUE_FLAG_IO)),
+ m_audiosquelch(0),
+ m_videosquelch(0),
+ m_fieldnum(0),
+ m_curtrack(0),
+ m_maxtrack(0),
+ m_attospertrack(0),
+ m_sliderupdate(attotime::zero),
+ m_videoindex(0),
+ m_stream(NULL),
+ m_audiobufsize(0),
+ m_audiobufin(0),
+ m_audiobufout(0),
+ m_audiocursamples(0),
+ m_audiomaxsamples(0),
+ m_videoenable(false),
+ m_videotex(NULL),
+ m_videopalette(NULL),
+ m_overenable(false),
+ m_overindex(0),
+ m_overtex(NULL)
+{
+ // initialize overlay_config
+ m_orig_config.m_overposx = m_orig_config.m_overposy = 0.0f;
+ m_orig_config.m_overscalex = m_orig_config.m_overscaley = 1.0f;
+ *static_cast<laserdisc_overlay_config *>(this) = m_orig_config;
+}
+
+
+//-------------------------------------------------
+// ~laserdisc_device - destructor
+//-------------------------------------------------
+
+laserdisc_device::~laserdisc_device()
+{
+ osd_work_queue_free(m_work_queue);
+}
+
+
+
+//**************************************************************************
+// PUBLIC INTERFACES
+//**************************************************************************
+
+//-------------------------------------------------
+// get_field_code - return raw field information
+// read from the disc
+//-------------------------------------------------
+
+UINT32 laserdisc_device::get_field_code(laserdisc_field_code code, bool zero_if_squelched)
+{
+ // return nothing if the video is off (external devices can't sense)
+ if (zero_if_squelched && m_videosquelch)
+ return 0;
+
+ switch (code)
+ {
+ case LASERDISC_CODE_WHITE_FLAG:
+ return m_metadata[m_fieldnum].white;
+
+ case LASERDISC_CODE_LINE16:
+ return m_metadata[m_fieldnum].line16;
+
+ case LASERDISC_CODE_LINE17:
+ return m_metadata[m_fieldnum].line17;
+
+ case LASERDISC_CODE_LINE18:
+ return m_metadata[m_fieldnum].line18;
+
+ case LASERDISC_CODE_LINE1718:
+ return m_metadata[m_fieldnum].line1718;
+ }
+ return 0;
+}
+
+
+//-------------------------------------------------
+// screen_update - handle updating the screen
+//-------------------------------------------------
+
+UINT32 laserdisc_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
+{
+ // handle the overlay if present
+ screen_bitmap &overbitmap = m_overbitmap[m_overindex];
+ if (overbitmap.valid() && (!m_overupdate_ind16.isnull() || !m_overupdate_rgb32.isnull()))
+ {
+ // scale the cliprect to the overlay size
+ rectangle clip(m_overclip);
+ clip.min_y = cliprect.min_y * overbitmap.height() / bitmap.height();
+ if (cliprect.min_y == screen.visible_area().min_y)
+ clip.min_y = MIN(clip.min_y, m_overclip.min_y);
+ clip.max_y = (cliprect.max_y + 1) * overbitmap.height() / bitmap.height() - 1;
+
+ // call the update callback
+ if (!m_overupdate_ind16.isnull())
+ m_overupdate_ind16(screen, overbitmap.as_ind16(), clip);
+ else
+ m_overupdate_rgb32(screen, overbitmap.as_rgb32(), clip);
+ }
+
+ // if this is the last update, do the rendering
+ if (cliprect.max_y == screen.visible_area().max_y)
+ {
+ // update the texture with the overlay contents
+ if (overbitmap.valid())
+ m_overtex->set_bitmap(overbitmap, m_overclip, overbitmap.texformat());
+
+ // get the laserdisc video
+ bitmap_yuy16 &vidbitmap = get_video();
+ m_videotex->set_bitmap(vidbitmap, vidbitmap.cliprect(), TEXFORMAT_YUY16);
+
+ // reset the screen contents
+ screen.container().empty();
+
+ // add the video texture
+ if (m_videoenable)
+ screen.container().add_quad(0.0f, 0.0f, 1.0f, 1.0f, MAKE_ARGB(0xff,0xff,0xff,0xff), m_videotex, PRIMFLAG_BLENDMODE(BLENDMODE_NONE) | PRIMFLAG_SCREENTEX(1));
+
+ // add the overlay
+ if (m_overenable && overbitmap.valid())
+ {
+ float x0 = 0.5f - 0.5f * m_overscalex + m_overposx;
+ float y0 = 0.5f - 0.5f * m_overscaley + m_overposy;
+ float x1 = x0 + m_overscalex;
+ float y1 = y0 + m_overscaley;
+ screen.container().add_quad(x0, y0, x1, y1, MAKE_ARGB(0xff,0xff,0xff,0xff), m_overtex, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_SCREENTEX(1));
+ }
+
+ // swap to the next bitmap
+ m_overindex = (m_overindex + 1) % ARRAY_LENGTH(m_overbitmap);
+ }
+ return 0;
+}
+
+
+//-------------------------------------------------
+// static_set_screen - set the screen name
+//-------------------------------------------------
+
+void laserdisc_device::static_set_screen(device_t &device, const char *screen)
+{
+ downcast<laserdisc_device &>(device).m_screen_name = screen;
+}
+
+
+//-------------------------------------------------
+// static_set_get_disc - set the get disc
+// delegate
+//-------------------------------------------------
+
+void laserdisc_device::static_set_get_disc(device_t &device, laserdisc_get_disc_delegate callback)
+{
+ downcast<laserdisc_device &>(device).m_getdisc_callback = callback;
+}
+
+
+//-------------------------------------------------
+// static_set_get_disc - set the audio interceptor
+// delegate
+//-------------------------------------------------
+
+void laserdisc_device::static_set_audio(device_t &device, laserdisc_audio_delegate callback)
+{
+ downcast<laserdisc_device &>(device).m_audio_callback = callback;
+}
+
+
+//-------------------------------------------------
+// static_set_overlay - set the overlay parameters
+//-------------------------------------------------
+
+void laserdisc_device::static_set_overlay(device_t &device, UINT32 width, UINT32 height, screen_update_ind16_delegate update)
+{
+ laserdisc_device &ld = downcast<laserdisc_device &>(device);
+ ld.m_overwidth = width;
+ ld.m_overheight = height;
+ ld.m_overclip.set(0, width - 1, 0, height - 1);
+ ld.m_overupdate_ind16 = update;
+ ld.m_overupdate_rgb32 = screen_update_rgb32_delegate();
+}
+
+void laserdisc_device::static_set_overlay(device_t &device, UINT32 width, UINT32 height, screen_update_rgb32_delegate update)
+{
+ laserdisc_device &ld = downcast<laserdisc_device &>(device);
+ ld.m_overwidth = width;
+ ld.m_overheight = height;
+ ld.m_overclip.set(0, width - 1, 0, height - 1);
+ ld.m_overupdate_ind16 = screen_update_ind16_delegate();
+ ld.m_overupdate_rgb32 = update;
+}
+
+
+//-------------------------------------------------
+// static_set_overlay - set the overlay visible
+// memregion
+//-------------------------------------------------
+
+void laserdisc_device::static_set_overlay_clip(device_t &device, INT32 minx, INT32 maxx, INT32 miny, INT32 maxy)
+{
+ downcast<laserdisc_device &>(device).m_overclip.set(minx, maxx, miny, maxy);
+}
+
+
+//-------------------------------------------------
+// static_set_overlay_position - set the overlay
+// position parameters
+//-------------------------------------------------
+
+void laserdisc_device::static_set_overlay_position(device_t &device, float posx, float posy)
+{
+ laserdisc_device &ld = downcast<laserdisc_device &>(device);
+ ld.m_orig_config.m_overposx = ld.m_overposx = posx;
+ ld.m_orig_config.m_overposy = ld.m_overposy = posy;
+}
+
+
+//-------------------------------------------------
+// static_set_overlay_scale - set the overlay
+// scale parameters
+//-------------------------------------------------
+
+void laserdisc_device::static_set_overlay_scale(device_t &device, float scalex, float scaley)
+{
+ laserdisc_device &ld = downcast<laserdisc_device &>(device);
+ ld.m_orig_config.m_overscalex = ld.m_overscalex = scalex;
+ ld.m_orig_config.m_overscaley = ld.m_overscaley = scaley;
+
+}
+
+
+//**************************************************************************
+// DEVICE INTERFACE
+//**************************************************************************
+
+//-------------------------------------------------
+// device start callback
+//-------------------------------------------------
+
+void laserdisc_device::device_start()
+{
+ // ensure that our screen is started first
+ m_screen = machine().device<screen_device>(m_screen_name);
+ assert(m_screen != NULL);
+ if (!m_screen->started())
+ throw device_missing_dependencies();
+
+ // initialize the various pieces
+ init_disc();
+ init_video();
+ init_audio();
+
+ // register callbacks
+ config_register(machine(), "laserdisc", config_saveload_delegate(FUNC(laserdisc_device::config_load), this), config_saveload_delegate(FUNC(laserdisc_device::config_save), this));
+}
+
+
+//-------------------------------------------------
+// device stop callback
+//-------------------------------------------------
+
+void laserdisc_device::device_stop()
+{
+ // make sure all async operations have completed
+ if (m_disc != NULL)
+ osd_work_queue_wait(m_work_queue, osd_ticks_per_second() * 10);
+
+ // free any textures and palettes
+ if (m_videotex != NULL)
+ machine().render().texture_free(m_videotex);
+ if (m_videopalette != NULL)
+ palette_deref(m_videopalette);
+ if (m_overtex != NULL)
+ machine().render().texture_free(m_overtex);
+}
+
+
+//-------------------------------------------------
+// device reset callback
+//-------------------------------------------------
+
+void laserdisc_device::device_reset()
+{
+ // attempt to wire up the audio
+ m_stream->set_sample_rate(m_samplerate);
+
+ // set up the general ld
+ m_audiosquelch = 3;
+ m_videosquelch = 1;
+ m_fieldnum = 0;
+ m_curtrack = 1;
+ m_attospertrack = 0;
+ m_sliderupdate = machine().time();
+}
+
+
+//-------------------------------------------------
+// device_timer - handle timers set by this
+// device
+//-------------------------------------------------
+
+void laserdisc_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ switch (id)
+ {
+ case TID_VBI_FETCH:
+ {
+ // wait for previous read and decode to finish
+ process_track_data();
+
+ // update current track based on slider speed
+ update_slider_pos();
+
+ // update the state
+ add_and_clamp_track(player_update(m_metadata[m_fieldnum], m_fieldnum, machine().time()));
+
+ // flush any audio before we read more
+ m_stream->update();
+
+ // start reading the track data for the next round
+ m_fieldnum ^= 1;
+ read_track_data();
+ break;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - audio streamer for
+// laserdiscs
+//-------------------------------------------------
+
+void laserdisc_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ // compute AND values based on the squelch
+ INT16 leftand = (m_audiosquelch & 1) ? 0x0000 : 0xffff;
+ INT16 rightand = (m_audiosquelch & 2) ? 0x0000 : 0xffff;
+
+ // see if we have enough samples to fill the buffer; if not, drop out
+ int samples_avail = m_audiobufin - m_audiobufout;
+ if (samples_avail < 0)
+ samples_avail += m_audiobufsize;
+
+ // if no attached ld, just clear the buffers
+ stream_sample_t *dst0 = outputs[0];
+ stream_sample_t *dst1 = outputs[1];
+ 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 = m_audiobuffer[0];
+ INT16 *buffer1 = m_audiobuffer[1];
+ int sampout = m_audiobufout;
+
+ // copy samples, clearing behind us as we go
+ while (sampout != m_audiobufin && samples-- > 0)
+ {
+ *dst0++ = buffer0[sampout] & leftand;
+ *dst1++ = buffer1[sampout] & rightand;
+ buffer0[sampout] = 0;
+ buffer1[sampout] = 0;
+ sampout++;
+ if (sampout >= m_audiobufsize)
+ sampout = 0;
+ }
+ m_audiobufout = sampout;
+
+ // clear out the rest of the buffer
+ if (samples > 0)
+ {
+ sampout = (m_audiobufout == 0) ? m_audiobufsize - 1 : m_audiobufout - 1;
+ stream_sample_t fill0 = buffer0[sampout] & leftand;
+ stream_sample_t fill1 = buffer1[sampout] & rightand;
+
+ while (samples-- > 0)
+ {
+ *dst0++ = fill0;
+ *dst1++ = fill1;
+ }
+ }
+ }
+}
+
+
+//**************************************************************************
+// SUBCLASS HELPERS
+//**************************************************************************
+
+//-------------------------------------------------
+// set_slider_speed - dynamically change the
+// slider speed
+//-------------------------------------------------
+
+void laserdisc_device::set_slider_speed(INT32 tracks_per_vsync)
+{
+ // update to the current time
+ update_slider_pos();
+
+ // if 0, set the time to 0
+ attotime vsyncperiod = m_screen->frame_period();
+ if (tracks_per_vsync == 0)
+ m_attospertrack = 0;
+
+ // positive values store positive times
+ else if (tracks_per_vsync > 0)
+ m_attospertrack = (vsyncperiod / tracks_per_vsync).as_attoseconds();
+
+ // negative values store negative times
+ else
+ m_attospertrack = -(vsyncperiod / -tracks_per_vsync).as_attoseconds();
+
+ if (LOG_SLIDER)
+ printf("Slider speed = %d\n", tracks_per_vsync);
+}
+
+
+//-------------------------------------------------
+// advance_slider - advance the slider by
+// a certain number of tracks
+//-------------------------------------------------
+
+void laserdisc_device::advance_slider(INT32 numtracks)
+{
+ // first update to the current time
+ update_slider_pos();
+
+ // then update the track position
+ add_and_clamp_track(numtracks);
+ if (LOG_SLIDER)
+ printf("Advance by %d\n", numtracks);
+}
+
+
+//-------------------------------------------------
+// get_slider_position - get the current
+// slider position
+//-------------------------------------------------
+
+laserdisc_device::slider_position laserdisc_device::get_slider_position()
+{
+ // update the slider position first
+ update_slider_pos();
+
+ // return the status
+ if (m_curtrack == 1)
+ return SLIDER_MINIMUM;
+ else if (m_curtrack < VIRTUAL_LEAD_IN_TRACKS)
+ return SLIDER_VIRTUAL_LEADIN;
+ else if (m_curtrack < VIRTUAL_LEAD_IN_TRACKS + m_chdtracks)
+ return SLIDER_CHD;
+ else if (m_curtrack < VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS)
+ return SLIDER_OUTSIDE_CHD;
+ else if (m_curtrack < m_maxtrack - 1)
+ return SLIDER_VIRTUAL_LEADOUT;
+ else
+ return SLIDER_MAXIMUM;
+}
+
+
+//-------------------------------------------------
+// generic_update - generically update in a way
+// that works for most situations
+//-------------------------------------------------
+
+INT32 laserdisc_device::generic_update(const vbi_metadata &vbi, int fieldnum, attotime curtime, player_state_info &newstate)
+{
+ INT32 advanceby = 0;
+ int frame;
+
+ // start by assuming the state doesn't change
+ newstate = m_player_state;
+
+ // handle things based on the state
+ switch (m_player_state.m_state)
+ {
+ case LDSTATE_EJECTING:
+ // when time expires, switch to the ejected state
+ if (curtime >= m_player_state.m_endtime)
+ newstate.m_state = LDSTATE_EJECTED;
+ break;
+
+ case LDSTATE_EJECTED:
+ // do nothing
+ break;
+
+ case LDSTATE_PARKED:
+ // do nothing
+ break;
+
+ case LDSTATE_LOADING:
+ // when time expires, switch to the spinup state
+ if (curtime >= m_player_state.m_endtime)
+ newstate.m_state = LDSTATE_SPINUP;
+ advanceby = -GENERIC_SEARCH_SPEED;
+ break;
+
+ case LDSTATE_SPINUP:
+ // when time expires, switch to the playing state
+ if (curtime >= m_player_state.m_endtime)
+ newstate.m_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.m_state = LDSTATE_PAUSED;
+ newstate.m_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 (m_player_state.m_param == 1)
+ advanceby = (fieldnum == 1) ? 1 : -1;
+ break;
+
+ case LDSTATE_PLAYING:
+ // if we hit the target frame, switch to the paused state
+ if (m_player_state.m_param > 0 && is_start_of_frame(vbi) && frame_from_metadata(vbi) == m_player_state.m_param)
+ {
+ newstate.m_state = LDSTATE_PAUSED;
+ newstate.m_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 && ++m_player_state.m_substate > m_player_state.m_param)
+ {
+ advanceby = -1;
+ m_player_state.m_substate = 0;
+ }
+ break;
+
+ case LDSTATE_PLAYING_SLOW_FORWARD:
+ // after the second field of each frame, see if we need to advance
+ if (fieldnum == 1 && ++m_player_state.m_substate > m_player_state.m_param)
+ {
+ advanceby = 1;
+ m_player_state.m_substate = 0;
+ }
+ break;
+
+ case LDSTATE_PLAYING_FAST_REVERSE:
+ // advance after the second field of each frame
+ if (fieldnum == 1)
+ advanceby = -m_player_state.m_param;
+ break;
+
+ case LDSTATE_PLAYING_FAST_FORWARD:
+ // advance after the second field of each frame
+ if (fieldnum == 1)
+ advanceby = m_player_state.m_param;
+ break;
+
+ case LDSTATE_SCANNING:
+ // advance after the second field of each frame
+ if (fieldnum == 1)
+ advanceby = m_player_state.m_param >> 8;
+
+ // after we run out of vsyncs, revert to the saved state
+ if (++m_player_state.m_substate >= (m_player_state.m_param & 0xff))
+ newstate = m_saved_state;
+ 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.m_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.m_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 (m_player_state.m_substate == 1 && is_start_of_frame(vbi) && frame == m_player_state.m_param)
+ {
+ newstate.m_state = LDSTATE_PAUSED;
+ newstate.m_param = fieldnum;
+ }
+
+ // otherwise, if we got frame data from the VBI, update our seeking logic
+ else if (m_player_state.m_substate == 0 && frame != FRAME_NOT_PRESENT)
+ {
+ INT32 delta = (m_player_state.m_param - 2) - frame;
+
+ // if we're within a couple of frames, just play until we hit it
+ if (delta >= 0 && delta <= 2)
+ m_player_state.m_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;
+
+ default:
+ // do nothing
+ break;
+ }
+
+ return advanceby;
+}
+
+
+//**************************************************************************
+// INITIALIZATION
+//**************************************************************************
+
+//-------------------------------------------------
+// init_disc - initialize the state of the
+// CHD disc
+//-------------------------------------------------
+
+void laserdisc_device::init_disc()
+{
+ // get a handle to the disc to play
+ if (!m_getdisc_callback.isnull())
+ m_disc = m_getdisc_callback(*this);
+ else
+ m_disc = get_disk_handle(machine(), tag());
+
+ // set default parameters
+ m_width = 720;
+ m_height = 240;
+ m_fps_times_1million = 59940000;
+ m_samplerate = 48000;
+
+ // get the disc metadata and extract the ld
+ m_chdtracks = 0;
+ m_maxtrack = VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS + VIRTUAL_LEAD_OUT_TRACKS;
+ if (m_disc != NULL)
+ {
+ // require the A/V codec and nothing else
+ if (m_disc->compression(0) != CHD_CODEC_AVHUFF || m_disc->compression(1) != CHD_CODEC_NONE)
+ throw emu_fatalerror("Laserdisc video must be compressed with the A/V codec!");
+
+ // read the metadata
+ astring metadata;
+ chd_error err = m_disc->read_metadata(AV_METADATA_TAG, 0, metadata);
+ if (err != CHDERR_NONE)
+ throw emu_fatalerror("Non-A/V CHD file specified");
+
+ // extract the metadata
+ int fps, fpsfrac, interlaced, channels;
+ if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &m_width, &m_height, &interlaced, &channels, &m_samplerate) != 7)
+ throw emu_fatalerror("Invalid metadata in CHD file");
+ else
+ m_fps_times_1million = fps * 1000000 + fpsfrac;
+
+ // require interlaced video
+ if (!interlaced)
+ throw emu_fatalerror("Laserdisc video must be interlaced!");
+
+ // determine the maximum track and allocate a frame buffer
+ UINT32 totalhunks = m_disc->hunk_count();
+ m_chdtracks = totalhunks / 2;
+
+ // allocate memory for the precomputed per-frame metadata
+ err = m_disc->read_metadata(AV_LD_METADATA_TAG, 0, m_vbidata);
+ if (err != CHDERR_NONE || m_vbidata.count() != totalhunks * VBI_PACKED_BYTES)
+ throw emu_fatalerror("Precomputed VBI metadata missing or incorrect size");
+ }
+ m_maxtrack = MAX(m_maxtrack, VIRTUAL_LEAD_IN_TRACKS + VIRTUAL_LEAD_OUT_TRACKS + m_chdtracks);
+}
+
+
+//-------------------------------------------------
+// init_video - initialize the state of the
+// video rendering
+//-------------------------------------------------
+
+void laserdisc_device::init_video()
+{
+ // register for VBLANK callbacks
+ m_screen->register_vblank_callback(vblank_state_delegate(FUNC(laserdisc_device::vblank_state_changed), this));
+
+ // allocate palette for applying brightness/contrast/gamma
+ m_videopalette = palette_alloc(256, 1);
+ if (m_videopalette == NULL)
+ throw emu_fatalerror("Out of memory allocating video palette");
+ for (int index = 0; index < 256; index++)
+ palette_entry_set_color(m_videopalette, index, MAKE_RGB(index, index, index));
+
+ // allocate video frames
+ for (int index = 0; index < ARRAY_LENGTH(m_frame); index++)
+ {
+ // first allocate a YUY16 bitmap at 2x the height
+ frame_data &frame = m_frame[index];
+ frame.m_bitmap.allocate(m_width, m_height * 2);
+ frame.m_bitmap.set_palette(m_videopalette);
+ fillbitmap_yuy16(frame.m_bitmap, 40, 109, 240);
+
+ // make a copy of the bitmap that clips out the VBI and horizontal blanking areas
+ frame.m_visbitmap.wrap(&frame.m_bitmap.pix16(44, frame.m_bitmap.width() * 8 / 720),
+ frame.m_bitmap.width() - 2 * frame.m_bitmap.width() * 8 / 720,
+ frame.m_bitmap.height() - 44,
+ frame.m_bitmap.rowpixels());
+ frame.m_visbitmap.set_palette(m_videopalette);
+ }
+
+ // allocate an empty frame of the same size
+ m_emptyframe.allocate(m_width, m_height * 2);
+ m_emptyframe.set_palette(m_videopalette);
+ fillbitmap_yuy16(m_emptyframe, 0, 128, 128);
+
+ // allocate texture for rendering
+ m_videoenable = true;
+ m_videotex = machine().render().texture_alloc();
+ if (m_videotex == NULL)
+ fatalerror("Out of memory allocating video texture");
+
+ // allocate overlay
+ m_overenable = overlay_configured();
+ if (m_overenable)
+ {
+ // bind our handlers
+ m_overupdate_ind16.bind_relative_to(*owner());
+ m_overupdate_rgb32.bind_relative_to(*owner());
+
+ // configure bitmap formats
+ bitmap_format format = !m_overupdate_ind16.isnull() ? BITMAP_FORMAT_IND16 : BITMAP_FORMAT_RGB32;
+ texture_format texformat = !m_overupdate_ind16.isnull() ? TEXFORMAT_PALETTEA16 : TEXFORMAT_ARGB32;
+
+ // allocate overlay bitmaps
+ for (int index = 0; index < ARRAY_LENGTH(m_overbitmap); index++)
+ {
+ m_overbitmap[index].set_format(format, texformat);
+ m_overbitmap[index].set_palette(machine().palette);
+ m_overbitmap[index].resize(m_overwidth, m_overheight);
+ }
+
+ // allocate overlay texture
+ m_overtex = machine().render().texture_alloc();
+ if (m_overtex == NULL)
+ fatalerror("Out of memory allocating overlay texture");
+ }
+}
+
+
+//-------------------------------------------------
+// init_audio - initialize the state of the
+// audio rendering
+//-------------------------------------------------
+
+void laserdisc_device::init_audio()
+{
+ // allocate a stream
+ m_stream = stream_alloc(0, 2, 48000);
+
+ // allocate audio buffers
+ m_audiomaxsamples = ((UINT64)m_samplerate * 1000000 + m_fps_times_1million - 1) / m_fps_times_1million;
+ m_audiobufsize = m_audiomaxsamples * 4;
+ m_audiobuffer[0] = auto_alloc_array(machine(), INT16, m_audiobufsize);
+ m_audiobuffer[1] = auto_alloc_array(machine(), INT16, m_audiobufsize);
+}
+
+
+//**************************************************************************
+// INTERNAL HELPERS
+//**************************************************************************
+
+//-------------------------------------------------
+// fillbitmap_yuy16 - fill a YUY16 bitmap with a
+// given color pattern
+//-------------------------------------------------
+
+void laserdisc_device::fillbitmap_yuy16(bitmap_yuy16 &bitmap, UINT8 yval, UINT8 cr, UINT8 cb)
+{
+ UINT16 color0 = (yval << 8) | cb;
+ UINT16 color1 = (yval << 8) | cr;
+
+ // write 32 bits of color (2 pixels at a time)
+ for (int y = 0; y < bitmap.height(); y++)
+ {
+ UINT16 *dest = &bitmap.pix16(y);
+ for (int x = 0; x < bitmap.width() / 2; x++)
+ {
+ *dest++ = color0;
+ *dest++ = color1;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// update_slider_pos - based on the current
+// speed and elapsed time, update the current
+// track position
+//-------------------------------------------------
+
+void laserdisc_device::update_slider_pos()
+{
+ attotime curtime = machine().time();
+
+ // if not moving, update to now
+ if (m_attospertrack == 0)
+ m_sliderupdate = curtime;
+
+ // otherwise, compute the number of tracks covered
+ else
+ {
+ attoseconds_t delta = (curtime - m_sliderupdate).as_attoseconds();
+
+ // determine how many tracks we covered and advance
+ if (m_attospertrack >= 0)
+ {
+ INT32 tracks_covered = delta / m_attospertrack;
+ add_and_clamp_track(tracks_covered);
+ if (tracks_covered != 0)
+ m_sliderupdate += attotime(0, tracks_covered * m_attospertrack);
+ }
+ else
+ {
+ INT32 tracks_covered = delta / -m_attospertrack;
+ add_and_clamp_track(-tracks_covered);
+ if (tracks_covered != 0)
+ m_sliderupdate += attotime(0, tracks_covered * -m_attospertrack);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// vblank_state_changed - called on each state
+// change of the VBLANK signal
+//-------------------------------------------------
+
+void laserdisc_device::vblank_state_changed(screen_device &screen, bool vblank_state)
+{
+ // update current track based on slider speed
+ update_slider_pos();
+
+ // on rising edge, process previously-read frame and inform the player
+ if (vblank_state)
+ {
+ // call the player's VSYNC callback
+ player_vsync(m_metadata[m_fieldnum], m_fieldnum, machine().time());
+
+ // set a timer to begin fetching the next frame just before the VBI data would be fetched
+ timer_set(screen.time_until_pos(16*2), TID_VBI_FETCH);
+ }
+}
+
+
+//-------------------------------------------------
+// current_frame - return a reference to the
+// currently visible frame
+//-------------------------------------------------
+
+laserdisc_device::frame_data &laserdisc_device::current_frame()
+{
+ // determine the most recent live set of frames
+ frame_data *frame = &m_frame[m_videoindex];
+ if (frame->m_numfields < 2)
+ frame = &m_frame[(m_videoindex + ARRAY_LENGTH(m_frame) - 1) % ARRAY_LENGTH(m_frame)];
+ return *frame;
+}
+
+
+//-------------------------------------------------
+// read_track_data - read and process data for
+// a particular video track
+//-------------------------------------------------
+
+void laserdisc_device::read_track_data()
+{
+ // compute the chdhunk number we are going to read
+ INT32 chdtrack = m_curtrack - 1 - VIRTUAL_LEAD_IN_TRACKS;
+ chdtrack = MAX(chdtrack, 0);
+ chdtrack = MIN(chdtrack, m_chdtracks - 1);
+ UINT32 readhunk = chdtrack * 2 + m_fieldnum;
+
+ // cheat and look up the metadata we are about to retrieve
+ vbi_metadata vbidata = { 0 };
+ if (m_vbidata.count() != 0)
+ vbi_metadata_unpack(&vbidata, NULL, &m_vbidata[readhunk * VBI_PACKED_BYTES]);
+
+ // if we're in the lead-in area, force the VBI data to be standard lead-in
+ if (m_curtrack - 1 < VIRTUAL_LEAD_IN_TRACKS)
+ {
+ vbidata.line16 = 0;
+ vbidata.line17 = vbidata.line18 = vbidata.line1718 = VBI_CODE_LEADIN;
+ }
+//printf("track %5d.%d: %06X %06X %06X\n", m_curtrack, m_fieldnum, vbidata.line16, vbidata.line17, vbidata.line18);
+
+ // if we're about to read the first field in a frame, advance
+ frame_data *frame = &m_frame[m_videoindex];
+ if ((vbidata.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
+ {
+ if (frame->m_numfields >= 2)
+ m_videoindex = (m_videoindex + 1) % ARRAY_LENGTH(m_frame);
+ frame = &m_frame[m_videoindex];
+ frame->m_numfields = 0;
+ }
+
+ // if we're squelched, reset the frame counter
+ if (m_videosquelch)
+ frame->m_numfields = 0;
+
+ // remember the last field number
+ frame->m_lastfield = m_curtrack * 2 + m_fieldnum;
+
+ // set the video target information
+ m_avhuff_config.video.wrap(&frame->m_bitmap.pix16(m_fieldnum), frame->m_bitmap.width(), frame->m_bitmap.height() / 2, frame->m_bitmap.rowpixels() * 2);
+
+ // set the audio target information
+ if (m_audiobufin + m_audiomaxsamples <= m_audiobufsize)
+ {
+ // if we can fit without wrapping, just read the data directly
+ m_avhuff_config.audio[0] = &m_audiobuffer[0][m_audiobufin];
+ m_avhuff_config.audio[1] = &m_audiobuffer[1][m_audiobufin];
+ }
+ else
+ {
+ // otherwise, read to the beginning of the buffer
+ m_avhuff_config.audio[0] = &m_audiobuffer[0][0];
+ m_avhuff_config.audio[1] = &m_audiobuffer[1][0];
+ }
+
+ // override if we're not decoding
+ m_avhuff_config.maxsamples = m_audiomaxsamples;
+ m_avhuff_config.actsamples = &m_audiocursamples;
+ m_audiocursamples = 0;
+
+ // set the VBI data for the new field from our precomputed data
+ if (m_vbidata.count() != 0)
+ {
+ UINT32 vbiframe;
+ vbi_metadata_unpack(&m_metadata[m_fieldnum], &vbiframe, &m_vbidata[readhunk * VBI_PACKED_BYTES]);
+ }
+
+ // if we're in the lead-in area, force the VBI data to be standard lead-in
+ if (m_curtrack - 1 < VIRTUAL_LEAD_IN_TRACKS)
+ {
+ m_metadata[m_fieldnum].line16 = 0;
+ m_metadata[m_fieldnum].line17 = m_metadata[m_fieldnum].line18 = m_metadata[m_fieldnum].line1718 = VBI_CODE_LEADIN;
+ }
+
+ // configure the codec and then read
+ m_readresult = CHDERR_FILE_NOT_FOUND;
+ if (m_disc != NULL && !m_videosquelch)
+ {
+ m_readresult = m_disc->codec_configure(CHD_CODEC_AVHUFF, AVHUFF_CODEC_DECOMPRESS_CONFIG, &m_avhuff_config);
+ if (m_readresult == CHDERR_NONE)
+ {
+ m_queued_hunknum = readhunk;
+ m_readresult = CHDERR_OPERATION_PENDING;
+ osd_work_item_queue(m_work_queue, read_async_static, this, WORK_ITEM_FLAG_AUTO_RELEASE);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// read_async_static - work item callback for
+// asynchronous reads
+//-------------------------------------------------
+
+void *laserdisc_device::read_async_static(void *param, int threadid)
+{
+ laserdisc_device &ld = *reinterpret_cast<laserdisc_device *>(param);
+ ld.m_readresult = ld.m_disc->read_hunk(ld.m_queued_hunknum, NULL);
+ return NULL;
+}
+
+
+//-------------------------------------------------
+// process_track_data - process data from a
+// track after it has been read
+//-------------------------------------------------
+
+void laserdisc_device::process_track_data()
+{
+ // wait for the async operation to complete
+ if (m_readresult == CHDERR_OPERATION_PENDING)
+ osd_work_queue_wait(m_work_queue, osd_ticks_per_second() * 10);
+ assert(m_readresult != CHDERR_OPERATION_PENDING);
+
+ // remove the video if we had an error
+ if (m_readresult != CHDERR_NONE)
+ m_avhuff_config.video.reset();
+
+ // count the field as read if we are successful
+ if (m_avhuff_config.video.valid())
+ {
+ m_frame[m_videoindex].m_numfields++;
+ player_overlay(m_avhuff_config.video);
+ }
+
+ // pass the audio to the callback
+ if (!m_audio_callback.isnull())
+ m_audio_callback(*this, m_samplerate, m_audiocursamples, m_avhuff_config.audio[0], m_avhuff_config.audio[1]);
+
+ // shift audio data if we read it into the beginning of the buffer
+ if (m_audiocursamples != 0 && m_audiobufin != 0)
+ for (int chnum = 0; chnum < 2; chnum++)
+ if (m_avhuff_config.audio[chnum] == &m_audiobuffer[chnum][0])
+ {
+ // move data to the end
+ int samplesleft = m_audiobufsize - m_audiobufin;
+ samplesleft = MIN(samplesleft, m_audiocursamples);
+ memmove(&m_audiobuffer[chnum][m_audiobufin], &m_audiobuffer[chnum][0], samplesleft * 2);
+
+ // shift data at the beginning
+ if (samplesleft < m_audiocursamples)
+ memmove(&m_audiobuffer[chnum][0], &m_audiobuffer[chnum][samplesleft], (m_audiocursamples - samplesleft) * 2);
+ }
+
+ // update the input buffer pointer
+ m_audiobufin = (m_audiobufin + m_audiocursamples) % m_audiobufsize;
+}
+
+
+
+//**************************************************************************
+// CONFIG SETTINGS ACCESS
+//**************************************************************************
+
+//-------------------------------------------------
+// config_load - read and apply data from the
+// configuration file
+//-------------------------------------------------
+
+void laserdisc_device::config_load(int config_type, xml_data_node *parentnode)
+{
+ // 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 (xml_data_node *ldnode = xml_get_sibling(parentnode->child, "device"); ldnode != NULL; ldnode = xml_get_sibling(ldnode->next, "device"))
+ {
+ const char *devtag = xml_get_attribute_string(ldnode, "tag", "");
+ if (strcmp(devtag, tag()) == 0)
+ {
+ // handle the overlay node
+ xml_data_node *overnode = xml_get_sibling(ldnode->child, "overlay");
+ if (overnode != NULL)
+ {
+ // fetch positioning controls
+ m_overposx = xml_get_attribute_float(overnode, "hoffset", m_overposx);
+ m_overscalex = xml_get_attribute_float(overnode, "hstretch", m_overscalex);
+ m_overposy = xml_get_attribute_float(overnode, "voffset", m_overposy);
+ m_overscaley = xml_get_attribute_float(overnode, "vstretch", m_overscaley);
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// config_save - save data to the configuration
+// file
+//-------------------------------------------------
+
+void laserdisc_device::config_save(int config_type, xml_data_node *parentnode)
+{
+ // we only care about game files
+ if (config_type != CONFIG_TYPE_GAME)
+ return;
+
+ // create a node
+ xml_data_node *ldnode = xml_add_child(parentnode, "device", NULL);
+ if (ldnode != NULL)
+ {
+ // output the basics
+ xml_set_attribute(ldnode, "tag", tag());
+
+ // add an overlay node
+ xml_data_node *overnode = xml_add_child(ldnode, "overlay", NULL);
+ bool changed = false;
+ if (overnode != NULL)
+ {
+ // output the positioning controls
+ if (m_overposx != m_orig_config.m_overposx)
+ {
+ xml_set_attribute_float(overnode, "hoffset", m_overposx);
+ changed = true;
+ }
+
+ if (m_overscalex != m_orig_config.m_overscalex)
+ {
+ xml_set_attribute_float(overnode, "hstretch", m_overscalex);
+ changed = true;
+ }
+
+ if (m_overposy != m_orig_config.m_overposy)
+ {
+ xml_set_attribute_float(overnode, "voffset", m_overposy);
+ changed = true;
+ }
+
+ if (m_overscaley != m_orig_config.m_overscaley)
+ {
+ xml_set_attribute_float(overnode, "vstretch", m_overscaley);
+ changed = true;
+ }
+ }
+
+ // if nothing changed, kill the node
+ if (!changed)
+ xml_delete_node(ldnode);
+ }
+}
+