1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /*************************************************************************
4 
5     laserdsc.c
6 
7     Core laserdisc player implementation.
8 
9 *************************************************************************/
10 
11 #include "emu.h"
12 #include "laserdsc.h"
13 #include "avhuff.h"
14 #include "vbiparse.h"
15 #include "config.h"
16 #include "render.h"
17 #include "romload.h"
18 #include "chd.h"
19 
20 
21 
22 //**************************************************************************
23 //  DEBUGGING
24 //**************************************************************************
25 
26 #define LOG_SLIDER                  0
27 
28 
29 
30 //**************************************************************************
31 //  CONSTANTS
32 //**************************************************************************
33 
34 // these specs code from IEC 60857, for NTSC players
35 const uint32_t LEAD_IN_MIN_RADIUS_IN_UM = 53500;      // 53.5 mm
36 const uint32_t PROGRAM_MIN_RADIUS_IN_UM = 55000;      // 55 mm
37 const uint32_t PROGRAM_MAX_RADIUS_IN_UM = 145000;     // 145 mm
38 const uint32_t LEAD_OUT_MIN_SIZE_IN_UM = 2000;        // 2 mm
39 
40 // the track pitch is defined as a range; we pick a nominal pitch
41 // that ensures we can fit 54,000 tracks
42 //const uint32_t MIN_TRACK_PITCH_IN_NM = 1400;          // 1.4 um
43 //const uint32_t MAX_TRACK_PITCH_IN_NM = 2000;          // 2 um
44 const uint32_t NOMINAL_TRACK_PITCH_IN_NM = (PROGRAM_MAX_RADIUS_IN_UM - PROGRAM_MIN_RADIUS_IN_UM) * 1000 / 54000;
45 
46 // we simulate extra lead-in and lead-out tracks
47 const uint32_t VIRTUAL_LEAD_IN_TRACKS = (PROGRAM_MIN_RADIUS_IN_UM - LEAD_IN_MIN_RADIUS_IN_UM) * 1000 / NOMINAL_TRACK_PITCH_IN_NM;
48 const uint32_t MAX_TOTAL_TRACKS = 54000;
49 const uint32_t VIRTUAL_LEAD_OUT_TRACKS = LEAD_OUT_MIN_SIZE_IN_UM * 1000 / NOMINAL_TRACK_PITCH_IN_NM;
50 
51 
52 
53 //**************************************************************************
54 //  CORE IMPLEMENTATION
55 //**************************************************************************
56 
57 //-------------------------------------------------
58 //  laserdisc_device - constructor
59 //-------------------------------------------------
60 
laserdisc_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)61 laserdisc_device::laserdisc_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
62 	: device_t(mconfig, type, tag, owner, clock),
63 		device_sound_interface(mconfig, *this),
64 		device_video_interface(mconfig, *this),
65 		m_getdisc_callback(*this),
66 		m_audio_callback(*this),
67 		m_overwidth(0),
68 		m_overheight(0),
69 		m_overclip(0, -1, 0, -1),
70 		m_overupdate_rgb32(*this),
71 		m_disc(nullptr),
72 		m_width(0),
73 		m_height(0),
74 		m_fps_times_1million(0),
75 		m_samplerate(0),
76 		m_readresult(CHDERR_NONE),
77 		m_chdtracks(0),
78 		m_work_queue(osd_work_queue_alloc(WORK_QUEUE_FLAG_IO)),
79 		m_audiosquelch(0),
80 		m_videosquelch(0),
81 		m_fieldnum(0),
82 		m_curtrack(0),
83 		m_maxtrack(0),
84 		m_attospertrack(0),
85 		m_sliderupdate(attotime::zero),
86 		m_videoindex(0),
87 		m_stream(nullptr),
88 		m_audiobufsize(0),
89 		m_audiobufin(0),
90 		m_audiobufout(0),
91 		m_audiocursamples(0),
92 		m_audiomaxsamples(0),
93 		m_videoenable(false),
94 		m_videotex(nullptr),
95 		m_videopalette(nullptr),
96 		m_overenable(false),
97 		m_overindex(0),
98 		m_overtex(nullptr)
99 {
100 	// initialize overlay_config
101 	m_orig_config.m_overposx = m_orig_config.m_overposy = 0.0f;
102 	m_orig_config.m_overscalex = m_orig_config.m_overscaley = 1.0f;
103 	*static_cast<laserdisc_overlay_config *>(this) = m_orig_config;
104 }
105 
106 
107 //-------------------------------------------------
108 //  ~laserdisc_device - destructor
109 //-------------------------------------------------
110 
~laserdisc_device()111 laserdisc_device::~laserdisc_device()
112 {
113 	osd_work_queue_free(m_work_queue);
114 }
115 
116 
117 
118 //**************************************************************************
119 //  PUBLIC INTERFACES
120 //**************************************************************************
121 
122 //-------------------------------------------------
123 //  get_field_code - return raw field information
124 //  read from the disc
125 //-------------------------------------------------
126 
get_field_code(laserdisc_field_code code,bool zero_if_squelched)127 uint32_t laserdisc_device::get_field_code(laserdisc_field_code code, bool zero_if_squelched)
128 {
129 	// return nothing if the video is off (external devices can't sense)
130 	if (zero_if_squelched && m_videosquelch)
131 		return 0;
132 
133 	switch (code)
134 	{
135 		case LASERDISC_CODE_WHITE_FLAG:
136 			return m_metadata[m_fieldnum].white;
137 
138 		case LASERDISC_CODE_LINE16:
139 			return m_metadata[m_fieldnum].line16;
140 
141 		case LASERDISC_CODE_LINE17:
142 			return m_metadata[m_fieldnum].line17;
143 
144 		case LASERDISC_CODE_LINE18:
145 			return m_metadata[m_fieldnum].line18;
146 
147 		case LASERDISC_CODE_LINE1718:
148 			return m_metadata[m_fieldnum].line1718;
149 	}
150 	return 0;
151 }
152 
153 
154 //-------------------------------------------------
155 //  screen_update - handle updating the screen
156 //-------------------------------------------------
157 
screen_update(screen_device & screen,bitmap_rgb32 & bitmap,const rectangle & cliprect)158 uint32_t laserdisc_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
159 {
160 	// handle the overlay if present
161 	screen_bitmap &overbitmap = m_overbitmap[m_overindex];
162 	if (overbitmap.valid() && !m_overupdate_rgb32.isnull())
163 	{
164 		// scale the cliprect to the overlay size
165 		rectangle clip(m_overclip);
166 		clip.min_y = cliprect.min_y * overbitmap.height() / bitmap.height();
167 		if (cliprect.min_y == screen.visible_area().min_y)
168 			clip.min_y = std::min(clip.min_y, m_overclip.min_y);
169 		clip.max_y = (cliprect.max_y + 1) * overbitmap.height() / bitmap.height() - 1;
170 
171 		// call the update callback
172 		m_overupdate_rgb32(screen, overbitmap.as_rgb32(), clip);
173 	}
174 
175 	// if this is the last update, do the rendering
176 	if (cliprect.max_y == screen.visible_area().max_y)
177 	{
178 		// update the texture with the overlay contents
179 		if (overbitmap.valid())
180 			m_overtex->set_bitmap(overbitmap, m_overclip, overbitmap.texformat());
181 
182 		// get the laserdisc video
183 		bitmap_yuy16 &vidbitmap = get_video();
184 		m_videotex->set_bitmap(vidbitmap, vidbitmap.cliprect(), TEXFORMAT_YUY16);
185 
186 		// reset the screen contents
187 		screen.container().empty();
188 
189 		// add the video texture
190 		if (m_videoenable)
191 			screen.container().add_quad(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0xff,0xff,0xff), m_videotex, PRIMFLAG_BLENDMODE(BLENDMODE_NONE) | PRIMFLAG_SCREENTEX(1));
192 
193 		// add the overlay
194 		if (m_overenable && overbitmap.valid())
195 		{
196 			float x0 = 0.5f - 0.5f * m_overscalex + m_overposx;
197 			float y0 = 0.5f - 0.5f * m_overscaley + m_overposy;
198 			float x1 = x0 + m_overscalex;
199 			float y1 = y0 + m_overscaley;
200 			screen.container().add_quad(x0, y0, x1, y1, rgb_t(0xff,0xff,0xff,0xff), m_overtex, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_SCREENTEX(1));
201 		}
202 
203 		// swap to the next bitmap
204 		m_overindex = (m_overindex + 1) % ARRAY_LENGTH(m_overbitmap);
205 	}
206 	return 0;
207 }
208 
209 
210 //**************************************************************************
211 //  DEVICE INTERFACE
212 //**************************************************************************
213 
214 //-------------------------------------------------
215 //  device start callback
216 //-------------------------------------------------
217 
device_start()218 void laserdisc_device::device_start()
219 {
220 	// initialize the various pieces
221 	init_disc();
222 	init_video();
223 	init_audio();
224 
225 	// register callbacks
226 	machine().configuration().config_register("laserdisc", config_load_delegate(&laserdisc_device::config_load, this), config_save_delegate(&laserdisc_device::config_save, this));
227 }
228 
229 
230 //-------------------------------------------------
231 //  device stop callback
232 //-------------------------------------------------
233 
device_stop()234 void laserdisc_device::device_stop()
235 {
236 	// make sure all async operations have completed
237 	if (m_disc != nullptr)
238 		osd_work_queue_wait(m_work_queue, osd_ticks_per_second() * 10);
239 
240 	// free any textures and palettes
241 	if (m_videotex != nullptr)
242 		machine().render().texture_free(m_videotex);
243 	if (m_videopalette != nullptr)
244 		m_videopalette->deref();
245 	if (m_overtex != nullptr)
246 		machine().render().texture_free(m_overtex);
247 }
248 
249 
250 //-------------------------------------------------
251 //  device reset callback
252 //-------------------------------------------------
253 
device_reset()254 void laserdisc_device::device_reset()
255 {
256 	// attempt to wire up the audio
257 	m_stream->set_sample_rate(m_samplerate);
258 
259 	// set up the general ld
260 	m_audiosquelch = 3;
261 	m_videosquelch = 1;
262 	m_fieldnum = 0;
263 	m_curtrack = 1;
264 	m_attospertrack = 0;
265 	m_sliderupdate = machine().time();
266 }
267 
268 
269 //-------------------------------------------------
270 //  device_validity_check - verify device
271 //  configuration
272 //-------------------------------------------------
273 
device_validity_check(validity_checker & valid) const274 void laserdisc_device::device_validity_check(validity_checker &valid) const
275 {
276 }
277 
278 //-------------------------------------------------
279 //  device_timer - handle timers set by this
280 //  device
281 //-------------------------------------------------
282 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)283 void laserdisc_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
284 {
285 	switch (id)
286 	{
287 		case TID_VBI_FETCH:
288 		{
289 			// wait for previous read and decode to finish
290 			process_track_data();
291 
292 			// update current track based on slider speed
293 			update_slider_pos();
294 
295 			// update the state
296 			add_and_clamp_track(player_update(m_metadata[m_fieldnum], m_fieldnum, machine().time()));
297 
298 			// flush any audio before we read more
299 			m_stream->update();
300 
301 			// start reading the track data for the next round
302 			m_fieldnum ^= 1;
303 			read_track_data();
304 			break;
305 		}
306 	}
307 }
308 
309 
310 //-------------------------------------------------
311 //  sound_stream_update - audio streamer for
312 //  laserdiscs
313 //-------------------------------------------------
314 
sound_stream_update(sound_stream & stream,std::vector<read_stream_view> const & inputs,std::vector<write_stream_view> & outputs)315 void laserdisc_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
316 {
317 	// compute AND values based on the squelch
318 	int16_t leftand = (m_audiosquelch & 1) ? 0x0000 : 0xffff;
319 	int16_t rightand = (m_audiosquelch & 2) ? 0x0000 : 0xffff;
320 
321 	// see if we have enough samples to fill the buffer; if not, drop out
322 	int samples_avail = m_audiobufin - m_audiobufout;
323 	if (samples_avail < 0)
324 		samples_avail += m_audiobufsize;
325 
326 	// if no attached ld, just clear the buffers
327 	auto &dst0 = outputs[0];
328 	auto &dst1 = outputs[1];
329 	if (samples_avail < outputs[0].samples())
330 	{
331 		dst0.fill(0);
332 		dst1.fill(0);
333 	}
334 
335 	// otherwise, stream from our buffer
336 	else
337 	{
338 		int16_t *buffer0 = &m_audiobuffer[0][0];
339 		int16_t *buffer1 = &m_audiobuffer[1][0];
340 		int sampout = m_audiobufout;
341 
342 		// copy samples, clearing behind us as we go
343 		int sampindex;
344 		for (sampindex = 0; sampout != m_audiobufin && sampindex < outputs[0].samples(); sampindex++)
345 		{
346 			dst0.put_int(sampindex, buffer0[sampout] & leftand, 32768);
347 			dst1.put_int(sampindex, buffer1[sampout] & rightand, 32768);
348 			buffer0[sampout] = 0;
349 			buffer1[sampout] = 0;
350 			sampout++;
351 			if (sampout >= m_audiobufsize)
352 				sampout = 0;
353 		}
354 		m_audiobufout = sampout;
355 
356 		// clear out the rest of the buffer
357 		if (sampindex < outputs[0].samples())
358 		{
359 			sampout = (m_audiobufout == 0) ? m_audiobufsize - 1 : m_audiobufout - 1;
360 			s32 fill0 = buffer0[sampout] & leftand;
361 			s32 fill1 = buffer1[sampout] & rightand;
362 
363 			for ( ; sampindex < outputs[0].samples(); sampindex++)
364 			{
365 				dst0.put_int(sampindex, fill0, 32768);
366 				dst1.put_int(sampindex, fill1, 32768);
367 			}
368 		}
369 	}
370 }
371 
372 
373 //**************************************************************************
374 //  SUBCLASS HELPERS
375 //**************************************************************************
376 
377 //-------------------------------------------------
378 //  set_slider_speed - dynamically change the
379 //  slider speed
380 //-------------------------------------------------
381 
set_slider_speed(int32_t tracks_per_vsync)382 void laserdisc_device::set_slider_speed(int32_t tracks_per_vsync)
383 {
384 	// update to the current time
385 	update_slider_pos();
386 
387 	// if 0, set the time to 0
388 	attotime vsyncperiod = screen().frame_period();
389 	if (tracks_per_vsync == 0)
390 		m_attospertrack = 0;
391 
392 	// positive values store positive times
393 	else if (tracks_per_vsync > 0)
394 		m_attospertrack = (vsyncperiod / tracks_per_vsync).as_attoseconds();
395 
396 	// negative values store negative times
397 	else
398 		m_attospertrack = -(vsyncperiod / -tracks_per_vsync).as_attoseconds();
399 
400 	if (LOG_SLIDER)
401 		printf("Slider speed = %d\n", tracks_per_vsync);
402 }
403 
404 
405 //-------------------------------------------------
406 //  advance_slider - advance the slider by
407 //  a certain number of tracks
408 //-------------------------------------------------
409 
advance_slider(int32_t numtracks)410 void laserdisc_device::advance_slider(int32_t numtracks)
411 {
412 	// first update to the current time
413 	update_slider_pos();
414 
415 	// then update the track position
416 	add_and_clamp_track(numtracks);
417 	if (LOG_SLIDER)
418 		printf("Advance by %d\n", numtracks);
419 }
420 
421 
422 //-------------------------------------------------
423 //  get_slider_position - get the current
424 //  slider position
425 //-------------------------------------------------
426 
get_slider_position()427 laserdisc_device::slider_position laserdisc_device::get_slider_position()
428 {
429 	// update the slider position first
430 	update_slider_pos();
431 
432 	// return the status
433 	if (m_curtrack == 1)
434 		return SLIDER_MINIMUM;
435 	else if (m_curtrack < VIRTUAL_LEAD_IN_TRACKS)
436 		return SLIDER_VIRTUAL_LEADIN;
437 	else if (m_curtrack < VIRTUAL_LEAD_IN_TRACKS + m_chdtracks)
438 		return SLIDER_CHD;
439 	else if (m_curtrack < VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS)
440 		return SLIDER_OUTSIDE_CHD;
441 	else if (m_curtrack < m_maxtrack - 1)
442 		return SLIDER_VIRTUAL_LEADOUT;
443 	else
444 		return SLIDER_MAXIMUM;
445 }
446 
447 
448 //-------------------------------------------------
449 //  generic_update - generically update in a way
450 //  that works for most situations
451 //-------------------------------------------------
452 
generic_update(const vbi_metadata & vbi,int fieldnum,const attotime & curtime,player_state_info & newstate)453 int32_t laserdisc_device::generic_update(const vbi_metadata &vbi, int fieldnum, const attotime &curtime, player_state_info &newstate)
454 {
455 	int32_t advanceby = 0;
456 	int frame;
457 
458 	// start by assuming the state doesn't change
459 	newstate = m_player_state;
460 
461 	// handle things based on the state
462 	switch (m_player_state.m_state)
463 	{
464 		case LDSTATE_EJECTING:
465 			// when time expires, switch to the ejected state
466 			if (curtime >= m_player_state.m_endtime)
467 				newstate.m_state = LDSTATE_EJECTED;
468 			break;
469 
470 		case LDSTATE_EJECTED:
471 			// do nothing
472 			break;
473 
474 		case LDSTATE_PARKED:
475 			// do nothing
476 			break;
477 
478 		case LDSTATE_LOADING:
479 			// when time expires, switch to the spinup state
480 			if (curtime >= m_player_state.m_endtime)
481 				newstate.m_state = LDSTATE_SPINUP;
482 			advanceby = -GENERIC_SEARCH_SPEED;
483 			break;
484 
485 		case LDSTATE_SPINUP:
486 			// when time expires, switch to the playing state
487 			if (curtime >= m_player_state.m_endtime)
488 				newstate.m_state = LDSTATE_PLAYING;
489 			advanceby = -GENERIC_SEARCH_SPEED;
490 			break;
491 
492 		case LDSTATE_PAUSING:
493 			// if he hit the start of a frame, switch to paused state
494 			if (is_start_of_frame(vbi))
495 			{
496 				newstate.m_state = LDSTATE_PAUSED;
497 				newstate.m_param = fieldnum;
498 			}
499 
500 			// else advance until we hit it
501 			else if (fieldnum == 1)
502 				advanceby = 1;
503 			break;
504 
505 		case LDSTATE_PAUSED:
506 			// if we paused on field 1, we must flip back and forth
507 			if (m_player_state.m_param == 1)
508 				advanceby = (fieldnum == 1) ? 1 : -1;
509 			break;
510 
511 		case LDSTATE_PLAYING:
512 			// if we hit the target frame, switch to the paused state
513 			if (m_player_state.m_param > 0 && is_start_of_frame(vbi) && frame_from_metadata(vbi) == m_player_state.m_param)
514 			{
515 				newstate.m_state = LDSTATE_PAUSED;
516 				newstate.m_param = fieldnum;
517 			}
518 
519 			// otherwise after the second field of each frame
520 			else if (fieldnum == 1)
521 				advanceby = 1;
522 			break;
523 
524 		case LDSTATE_PLAYING_SLOW_REVERSE:
525 			// after the second field of each frame, see if we need to advance
526 			if (fieldnum == 1 && ++m_player_state.m_substate > m_player_state.m_param)
527 			{
528 				advanceby = -1;
529 				m_player_state.m_substate = 0;
530 			}
531 			break;
532 
533 		case LDSTATE_PLAYING_SLOW_FORWARD:
534 			// after the second field of each frame, see if we need to advance
535 			if (fieldnum == 1 && ++m_player_state.m_substate > m_player_state.m_param)
536 			{
537 				advanceby = 1;
538 				m_player_state.m_substate = 0;
539 			}
540 			break;
541 
542 		case LDSTATE_PLAYING_FAST_REVERSE:
543 			// advance after the second field of each frame
544 			if (fieldnum == 1)
545 				advanceby = -m_player_state.m_param;
546 			break;
547 
548 		case LDSTATE_PLAYING_FAST_FORWARD:
549 			// advance after the second field of each frame
550 			if (fieldnum == 1)
551 				advanceby = m_player_state.m_param;
552 			break;
553 
554 		case LDSTATE_SCANNING:
555 			// advance after the second field of each frame
556 			if (fieldnum == 1)
557 				advanceby = m_player_state.m_param >> 8;
558 
559 			// after we run out of vsyncs, revert to the saved state
560 			if (++m_player_state.m_substate >= (m_player_state.m_param & 0xff))
561 				newstate = m_saved_state;
562 			break;
563 
564 		case LDSTATE_STEPPING_REVERSE:
565 			// wait for the first field of the frame and then leap backwards
566 			if (is_start_of_frame(vbi))
567 			{
568 				advanceby = (fieldnum == 1) ? -1 : -2;
569 				newstate.m_state = LDSTATE_PAUSING;
570 			}
571 			break;
572 
573 		case LDSTATE_STEPPING_FORWARD:
574 			// wait for the first field of the frame and then switch to pausing state
575 			if (is_start_of_frame(vbi))
576 				newstate.m_state = LDSTATE_PAUSING;
577 			break;
578 
579 		case LDSTATE_SEEKING:
580 			// if we're in the final state, look for a matching frame and pause there
581 			frame = frame_from_metadata(vbi);
582 			if (m_player_state.m_substate == 1 && is_start_of_frame(vbi) && frame == m_player_state.m_param)
583 			{
584 				newstate.m_state = LDSTATE_PAUSED;
585 				newstate.m_param = fieldnum;
586 			}
587 
588 			// otherwise, if we got frame data from the VBI, update our seeking logic
589 			else if (m_player_state.m_substate == 0 && frame != FRAME_NOT_PRESENT)
590 			{
591 				int32_t delta = (m_player_state.m_param - 2) - frame;
592 
593 				// if we're within a couple of frames, just play until we hit it
594 				if (delta >= 0 && delta <= 2)
595 					m_player_state.m_substate++;
596 
597 				// otherwise, compute the delta assuming 1:1 track to frame; this will correct eventually
598 				else
599 				{
600 					if (delta < 0)
601 						delta--;
602 					advanceby = delta;
603 					advanceby = std::min(advanceby, GENERIC_SEARCH_SPEED);
604 					advanceby = std::max(advanceby, -GENERIC_SEARCH_SPEED);
605 				}
606 			}
607 
608 			// otherwise, keep advancing until we know what's up
609 			else
610 			{
611 				if (fieldnum == 1)
612 					advanceby = 1;
613 			}
614 			break;
615 
616 		default:
617 			// do nothing
618 			break;
619 	}
620 
621 	return advanceby;
622 }
623 
624 
625 //**************************************************************************
626 //  INITIALIZATION
627 //**************************************************************************
628 
629 //-------------------------------------------------
630 //  init_disc - initialize the state of the
631 //  CHD disc
632 //-------------------------------------------------
633 
init_disc()634 void laserdisc_device::init_disc()
635 {
636 	m_getdisc_callback.resolve();
637 
638 	// get a handle to the disc to play
639 	if (!m_getdisc_callback.isnull())
640 		m_disc = m_getdisc_callback();
641 	else
642 		m_disc = machine().rom_load().get_disk_handle(tag());
643 
644 	// set default parameters
645 	m_width = 720;
646 	m_height = 240;
647 	m_fps_times_1million = 59940000;
648 	m_samplerate = 48000;
649 
650 	// get the disc metadata and extract the ld
651 	m_chdtracks = 0;
652 	m_maxtrack = VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS + VIRTUAL_LEAD_OUT_TRACKS;
653 	if (m_disc != nullptr)
654 	{
655 		// require the A/V codec and nothing else
656 		if (m_disc->compression(0) != CHD_CODEC_AVHUFF || m_disc->compression(1) != CHD_CODEC_NONE)
657 			throw emu_fatalerror("Laserdisc video must be compressed with the A/V codec!");
658 
659 		// read the metadata
660 		std::string metadata;
661 		chd_error err = m_disc->read_metadata(AV_METADATA_TAG, 0, metadata);
662 		if (err != CHDERR_NONE)
663 			throw emu_fatalerror("Non-A/V CHD file specified");
664 
665 		// extract the metadata
666 		int fps, fpsfrac, interlaced, channels;
667 		if (sscanf(metadata.c_str(), AV_METADATA_FORMAT, &fps, &fpsfrac, &m_width, &m_height, &interlaced, &channels, &m_samplerate) != 7)
668 			throw emu_fatalerror("Invalid metadata in CHD file");
669 		else
670 			m_fps_times_1million = fps * 1000000 + fpsfrac;
671 
672 		// require interlaced video
673 		if (!interlaced)
674 			throw emu_fatalerror("Laserdisc video must be interlaced!");
675 
676 		// determine the maximum track and allocate a frame buffer
677 		uint32_t totalhunks = m_disc->hunk_count();
678 		m_chdtracks = totalhunks / 2;
679 
680 		// allocate memory for the precomputed per-frame metadata
681 		err = m_disc->read_metadata(AV_LD_METADATA_TAG, 0, m_vbidata);
682 		if (err != CHDERR_NONE || m_vbidata.size() != totalhunks * VBI_PACKED_BYTES)
683 			throw emu_fatalerror("Precomputed VBI metadata missing or incorrect size");
684 	}
685 	m_maxtrack = std::max(m_maxtrack, VIRTUAL_LEAD_IN_TRACKS + VIRTUAL_LEAD_OUT_TRACKS + m_chdtracks);
686 }
687 
688 
689 //-------------------------------------------------
690 //  init_video - initialize the state of the
691 //  video rendering
692 //-------------------------------------------------
693 
init_video()694 void laserdisc_device::init_video()
695 {
696 	// register for VBLANK callbacks
697 	screen().register_vblank_callback(vblank_state_delegate(&laserdisc_device::vblank_state_changed, this));
698 
699 	// allocate palette for applying brightness/contrast/gamma
700 	m_videopalette = palette_t::alloc(256);
701 	if (m_videopalette == nullptr)
702 		throw emu_fatalerror("Out of memory allocating video palette");
703 	for (int index = 0; index < 256; index++)
704 		m_videopalette->entry_set_color(index, rgb_t(index, index, index));
705 
706 	// allocate video frames
707 	for (auto & frame : m_frame)
708 	{
709 		// first allocate a YUY16 bitmap at 2x the height
710 
711 		frame.m_bitmap.allocate(m_width, m_height * 2);
712 		frame.m_bitmap.set_palette(m_videopalette);
713 		fillbitmap_yuy16(frame.m_bitmap, 40, 109, 240);
714 
715 		// make a copy of the bitmap that clips out the VBI and horizontal blanking areas
716 		frame.m_visbitmap.wrap(&frame.m_bitmap.pix(
717 					44, frame.m_bitmap.width() * 8 / 720),
718 					frame.m_bitmap.width() - 2 * frame.m_bitmap.width() * 8 / 720, frame.m_bitmap.height() - 44,
719 					frame.m_bitmap.rowpixels());
720 		frame.m_visbitmap.set_palette(m_videopalette);
721 	}
722 
723 	// allocate an empty frame of the same size
724 	m_emptyframe.allocate(m_width, m_height * 2);
725 	m_emptyframe.set_palette(m_videopalette);
726 	fillbitmap_yuy16(m_emptyframe, 0, 128, 128);
727 
728 	// allocate texture for rendering
729 	m_videoenable = true;
730 	m_videotex = machine().render().texture_alloc();
731 	if (m_videotex == nullptr)
732 		fatalerror("Out of memory allocating video texture\n");
733 
734 	// allocate overlay
735 	m_overenable = overlay_configured();
736 	if (m_overenable)
737 	{
738 		// bind our handlers
739 		m_overupdate_rgb32.resolve();
740 
741 		// allocate overlay bitmaps
742 		for (auto & elem : m_overbitmap)
743 		{
744 			elem.set_format(BITMAP_FORMAT_RGB32, TEXFORMAT_ARGB32);
745 			elem.resize(m_overwidth, m_overheight);
746 		}
747 
748 		// allocate overlay texture
749 		m_overtex = machine().render().texture_alloc();
750 		if (m_overtex == nullptr)
751 			fatalerror("Out of memory allocating overlay texture\n");
752 	}
753 }
754 
755 
756 //-------------------------------------------------
757 //  init_audio - initialize the state of the
758 //  audio rendering
759 //-------------------------------------------------
760 
init_audio()761 void laserdisc_device::init_audio()
762 {
763 	m_audio_callback.resolve();
764 
765 	// allocate a stream
766 	m_stream = stream_alloc(0, 2, 48000);
767 
768 	// allocate audio buffers
769 	m_audiomaxsamples = ((uint64_t)m_samplerate * 1000000 + m_fps_times_1million - 1) / m_fps_times_1million;
770 	m_audiobufsize = m_audiomaxsamples * 4;
771 	m_audiobuffer[0].resize(m_audiobufsize);
772 	m_audiobuffer[1].resize(m_audiobufsize);
773 }
774 
775 
776 //**************************************************************************
777 //  INTERNAL HELPERS
778 //**************************************************************************
779 
780 //-------------------------------------------------
781 //  fillbitmap_yuy16 - fill a YUY16 bitmap with a
782 //  given color pattern
783 //-------------------------------------------------
784 
fillbitmap_yuy16(bitmap_yuy16 & bitmap,uint8_t yval,uint8_t cr,uint8_t cb)785 void laserdisc_device::fillbitmap_yuy16(bitmap_yuy16 &bitmap, uint8_t yval, uint8_t cr, uint8_t cb)
786 {
787 	uint16_t color0 = (yval << 8) | cb;
788 	uint16_t color1 = (yval << 8) | cr;
789 
790 	// write 32 bits of color (2 pixels at a time)
791 	for (int y = 0; y < bitmap.height(); y++)
792 	{
793 		uint16_t *dest = &bitmap.pix(y);
794 		for (int x = 0; x < bitmap.width() / 2; x++)
795 		{
796 			*dest++ = color0;
797 			*dest++ = color1;
798 		}
799 	}
800 }
801 
802 
803 //-------------------------------------------------
804 //  update_slider_pos - based on the current
805 //  speed and elapsed time, update the current
806 //  track position
807 //-------------------------------------------------
808 
update_slider_pos()809 void laserdisc_device::update_slider_pos()
810 {
811 	attotime curtime = machine().time();
812 
813 	// if not moving, update to now
814 	if (m_attospertrack == 0)
815 		m_sliderupdate = curtime;
816 
817 	// otherwise, compute the number of tracks covered
818 	else
819 	{
820 		attoseconds_t delta = (curtime - m_sliderupdate).as_attoseconds();
821 
822 		// determine how many tracks we covered and advance
823 		if (m_attospertrack >= 0)
824 		{
825 			int32_t tracks_covered = delta / m_attospertrack;
826 			add_and_clamp_track(tracks_covered);
827 			if (tracks_covered != 0)
828 				m_sliderupdate += attotime(0, tracks_covered * m_attospertrack);
829 		}
830 		else
831 		{
832 			int32_t tracks_covered = delta / -m_attospertrack;
833 			add_and_clamp_track(-tracks_covered);
834 			if (tracks_covered != 0)
835 				m_sliderupdate += attotime(0, tracks_covered * -m_attospertrack);
836 		}
837 	}
838 }
839 
840 
841 //-------------------------------------------------
842 //  vblank_state_changed - called on each state
843 //  change of the VBLANK signal
844 //-------------------------------------------------
845 
vblank_state_changed(screen_device & screen,bool vblank_state)846 void laserdisc_device::vblank_state_changed(screen_device &screen, bool vblank_state)
847 {
848 	// update current track based on slider speed
849 	update_slider_pos();
850 
851 	// on rising edge, process previously-read frame and inform the player
852 	if (vblank_state)
853 	{
854 		// call the player's VSYNC callback
855 		player_vsync(m_metadata[m_fieldnum], m_fieldnum, machine().time());
856 
857 		// set a timer to begin fetching the next frame just before the VBI data would be fetched
858 		timer_set(screen.time_until_pos(16*2), TID_VBI_FETCH);
859 	}
860 }
861 
862 
863 //-------------------------------------------------
864 //  current_frame - return a reference to the
865 //  currently visible frame
866 //-------------------------------------------------
867 
current_frame()868 laserdisc_device::frame_data &laserdisc_device::current_frame()
869 {
870 	// determine the most recent live set of frames
871 	frame_data *frame = &m_frame[m_videoindex];
872 	if (frame->m_numfields < 2)
873 		frame = &m_frame[(m_videoindex + ARRAY_LENGTH(m_frame) - 1) % ARRAY_LENGTH(m_frame)];
874 	return *frame;
875 }
876 
877 
878 //-------------------------------------------------
879 //  read_track_data - read and process data for
880 //  a particular video track
881 //-------------------------------------------------
882 
read_track_data()883 void laserdisc_device::read_track_data()
884 {
885 	// compute the chdhunk number we are going to read
886 	int32_t chdtrack = m_curtrack - 1 - VIRTUAL_LEAD_IN_TRACKS;
887 	chdtrack = (std::max<int32_t>)(chdtrack, 0);
888 	chdtrack = (std::min<uint32_t>)(chdtrack, m_chdtracks - 1);
889 	uint32_t readhunk = chdtrack * 2 + m_fieldnum;
890 
891 	// cheat and look up the metadata we are about to retrieve
892 	vbi_metadata vbidata = { 0 };
893 	if (!m_vbidata.empty())
894 		vbi_metadata_unpack(&vbidata, nullptr, &m_vbidata[readhunk * VBI_PACKED_BYTES]);
895 
896 	// if we're in the lead-in area, force the VBI data to be standard lead-in
897 	if (m_curtrack - 1 < VIRTUAL_LEAD_IN_TRACKS)
898 	{
899 		vbidata.line16 = 0;
900 		vbidata.line17 = vbidata.line18 = vbidata.line1718 = VBI_CODE_LEADIN;
901 	}
902 //printf("track %5d.%d: %06X %06X %06X\n", m_curtrack, m_fieldnum, vbidata.line16, vbidata.line17, vbidata.line18);
903 
904 	// if we're about to read the first field in a frame, advance
905 	frame_data *frame = &m_frame[m_videoindex];
906 	if ((vbidata.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
907 	{
908 		if (frame->m_numfields >= 2)
909 			m_videoindex = (m_videoindex + 1) % ARRAY_LENGTH(m_frame);
910 		frame = &m_frame[m_videoindex];
911 		frame->m_numfields = 0;
912 	}
913 
914 	// if we're squelched, reset the frame counter
915 	if (m_videosquelch)
916 		frame->m_numfields = 0;
917 
918 	// remember the last field number
919 	frame->m_lastfield = m_curtrack * 2 + m_fieldnum;
920 
921 	// set the video target information
922 	m_avhuff_video.wrap(&frame->m_bitmap.pix(m_fieldnum), frame->m_bitmap.width(), frame->m_bitmap.height() / 2, frame->m_bitmap.rowpixels() * 2);
923 	m_avhuff_config.video = &m_avhuff_video;
924 
925 	// set the audio target information
926 	if (m_audiobufin + m_audiomaxsamples <= m_audiobufsize)
927 	{
928 		// if we can fit without wrapping, just read the data directly
929 		m_avhuff_config.audio[0] = &m_audiobuffer[0][m_audiobufin];
930 		m_avhuff_config.audio[1] = &m_audiobuffer[1][m_audiobufin];
931 	}
932 	else
933 	{
934 		// otherwise, read to the beginning of the buffer
935 		m_avhuff_config.audio[0] = &m_audiobuffer[0][0];
936 		m_avhuff_config.audio[1] = &m_audiobuffer[1][0];
937 	}
938 
939 	// override if we're not decoding
940 	m_avhuff_config.maxsamples = m_audiomaxsamples;
941 	m_avhuff_config.actsamples = &m_audiocursamples;
942 	m_audiocursamples = 0;
943 
944 	// set the VBI data for the new field from our precomputed data
945 	if (!m_vbidata.empty())
946 	{
947 		uint32_t vbiframe;
948 		vbi_metadata_unpack(&m_metadata[m_fieldnum], &vbiframe, &m_vbidata[readhunk * VBI_PACKED_BYTES]);
949 	}
950 
951 	// if we're in the lead-in area, force the VBI data to be standard lead-in
952 	if (m_curtrack - 1 < VIRTUAL_LEAD_IN_TRACKS)
953 	{
954 		m_metadata[m_fieldnum].line16 = 0;
955 		m_metadata[m_fieldnum].line17 = m_metadata[m_fieldnum].line18 = m_metadata[m_fieldnum].line1718 = VBI_CODE_LEADIN;
956 	}
957 
958 	// configure the codec and then read
959 	m_readresult = CHDERR_FILE_NOT_FOUND;
960 	if (m_disc != nullptr && !m_videosquelch)
961 	{
962 		m_readresult = m_disc->codec_configure(CHD_CODEC_AVHUFF, AVHUFF_CODEC_DECOMPRESS_CONFIG, &m_avhuff_config);
963 		if (m_readresult == CHDERR_NONE)
964 		{
965 			m_queued_hunknum = readhunk;
966 			m_readresult = CHDERR_OPERATION_PENDING;
967 			osd_work_item_queue(m_work_queue, read_async_static, this, WORK_ITEM_FLAG_AUTO_RELEASE);
968 		}
969 	}
970 }
971 
972 
973 //-------------------------------------------------
974 //  read_async_static - work item callback for
975 //  asynchronous reads
976 //-------------------------------------------------
977 
read_async_static(void * param,int threadid)978 void *laserdisc_device::read_async_static(void *param, int threadid)
979 {
980 	laserdisc_device &ld = *reinterpret_cast<laserdisc_device *>(param);
981 	ld.m_readresult = ld.m_disc->read_hunk(ld.m_queued_hunknum, nullptr);
982 	return nullptr;
983 }
984 
985 
986 //-------------------------------------------------
987 //  process_track_data - process data from a
988 //  track after it has been read
989 //-------------------------------------------------
990 
process_track_data()991 void laserdisc_device::process_track_data()
992 {
993 	// wait for the async operation to complete
994 	if (m_readresult == CHDERR_OPERATION_PENDING)
995 		osd_work_queue_wait(m_work_queue, osd_ticks_per_second() * 10);
996 	assert(m_readresult != CHDERR_OPERATION_PENDING);
997 
998 	// remove the video if we had an error
999 	if (m_readresult != CHDERR_NONE)
1000 		m_avhuff_video.reset();
1001 
1002 	// count the field as read if we are successful
1003 	if (m_avhuff_video.valid())
1004 	{
1005 		m_frame[m_videoindex].m_numfields++;
1006 		player_overlay(m_avhuff_video);
1007 	}
1008 
1009 	// pass the audio to the callback
1010 	if (!m_audio_callback.isnull())
1011 		m_audio_callback(m_samplerate, m_audiocursamples, m_avhuff_config.audio[0], m_avhuff_config.audio[1]);
1012 
1013 	// shift audio data if we read it into the beginning of the buffer
1014 	if (m_audiocursamples != 0 && m_audiobufin != 0)
1015 		for (int chnum = 0; chnum < 2; chnum++)
1016 			if (m_avhuff_config.audio[chnum] == &m_audiobuffer[chnum][0])
1017 			{
1018 				// move data to the end
1019 				uint32_t samplesleft = m_audiobufsize - m_audiobufin;
1020 				samplesleft = std::min(samplesleft, m_audiocursamples);
1021 				memmove(&m_audiobuffer[chnum][m_audiobufin], &m_audiobuffer[chnum][0], samplesleft * 2);
1022 
1023 				// shift data at the beginning
1024 				if (samplesleft < m_audiocursamples)
1025 					memmove(&m_audiobuffer[chnum][0], &m_audiobuffer[chnum][samplesleft], (m_audiocursamples - samplesleft) * 2);
1026 			}
1027 
1028 	// update the input buffer pointer
1029 	m_audiobufin = (m_audiobufin + m_audiocursamples) % m_audiobufsize;
1030 }
1031 
1032 
1033 
1034 //**************************************************************************
1035 //  CONFIG SETTINGS ACCESS
1036 //**************************************************************************
1037 
1038 //-------------------------------------------------
1039 //  config_load - read and apply data from the
1040 //  configuration file
1041 //-------------------------------------------------
1042 
config_load(config_type cfg_type,util::xml::data_node const * parentnode)1043 void laserdisc_device::config_load(config_type cfg_type, util::xml::data_node const *parentnode)
1044 {
1045 	// we only care about game files
1046 	if (cfg_type != config_type::GAME)
1047 		return;
1048 
1049 	// might not have any data
1050 	if (parentnode == nullptr)
1051 		return;
1052 
1053 	// iterate over overlay nodes
1054 	for (util::xml::data_node const *ldnode = parentnode->get_child("device"); ldnode != nullptr; ldnode = ldnode->get_next_sibling("device"))
1055 	{
1056 		const char *devtag = ldnode->get_attribute_string("tag", "");
1057 		if (strcmp(devtag, tag()) == 0)
1058 		{
1059 			// handle the overlay node
1060 			util::xml::data_node const *const overnode = ldnode->get_child("overlay");
1061 			if (overnode != nullptr)
1062 			{
1063 				// fetch positioning controls
1064 				m_overposx = overnode->get_attribute_float("hoffset", m_overposx);
1065 				m_overscalex = overnode->get_attribute_float("hstretch", m_overscalex);
1066 				m_overposy = overnode->get_attribute_float("voffset", m_overposy);
1067 				m_overscaley = overnode->get_attribute_float("vstretch", m_overscaley);
1068 			}
1069 		}
1070 	}
1071 }
1072 
1073 
1074 //-------------------------------------------------
1075 //  config_save - save data to the configuration
1076 //  file
1077 //-------------------------------------------------
1078 
config_save(config_type cfg_type,util::xml::data_node * parentnode)1079 void laserdisc_device::config_save(config_type cfg_type, util::xml::data_node *parentnode)
1080 {
1081 	// we only care about game files
1082 	if (cfg_type != config_type::GAME)
1083 		return;
1084 
1085 	// create a node
1086 	util::xml::data_node *const ldnode = parentnode->add_child("device", nullptr);
1087 	if (ldnode != nullptr)
1088 	{
1089 		// output the basics
1090 		ldnode->set_attribute("tag", tag());
1091 
1092 		// add an overlay node
1093 		util::xml::data_node *const overnode = ldnode->add_child("overlay", nullptr);
1094 		bool changed = false;
1095 		if (overnode != nullptr)
1096 		{
1097 			// output the positioning controls
1098 			if (m_overposx != m_orig_config.m_overposx)
1099 			{
1100 				overnode->set_attribute_float("hoffset", m_overposx);
1101 				changed = true;
1102 			}
1103 
1104 			if (m_overscalex != m_orig_config.m_overscalex)
1105 			{
1106 				overnode->set_attribute_float("hstretch", m_overscalex);
1107 				changed = true;
1108 			}
1109 
1110 			if (m_overposy != m_orig_config.m_overposy)
1111 			{
1112 				overnode->set_attribute_float("voffset", m_overposy);
1113 				changed = true;
1114 			}
1115 
1116 			if (m_overscaley != m_orig_config.m_overscaley)
1117 			{
1118 				overnode->set_attribute_float("vstretch", m_overscaley);
1119 				changed = true;
1120 			}
1121 		}
1122 
1123 		// if nothing changed, kill the node
1124 		if (!changed)
1125 			ldnode->delete_node();
1126 	}
1127 }
1128 
add_ntsc_screen(machine_config & config,const char * _tag)1129 void laserdisc_device::add_ntsc_screen(machine_config &config, const char *_tag)
1130 {
1131 	set_screen(_tag);
1132 	screen_device &screen(SCREEN(config, _tag, SCREEN_TYPE_RASTER));
1133 	screen.set_video_attributes(VIDEO_SELF_RENDER);
1134 	screen.set_raw(XTAL(14'318'181)*2, 910, 0, 704, 525, 44, 524);
1135 	screen.set_screen_update(tag(), FUNC(laserdisc_device::screen_update));
1136 }
1137 
add_pal_screen(machine_config & config,const char * _tag)1138 void laserdisc_device::add_pal_screen(machine_config &config, const char *_tag)
1139 {
1140 	set_screen(_tag);
1141 	screen_device &screen(SCREEN(config, _tag, SCREEN_TYPE_RASTER));
1142 	screen.set_video_attributes(VIDEO_SELF_RENDER);
1143 	screen.set_raw(XTAL(17'734'470)*2, 1135, 0, 768, 625, 48, 624);
1144 	screen.set_screen_update(tag(), FUNC(laserdisc_device::screen_update));
1145 }
1146