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