openMSX
LaserdiscPlayer.cc
Go to the documentation of this file.
1 #include "LaserdiscPlayer.hh"
2 #include "CommandException.hh"
3 #include "CommandController.hh"
4 #include "EventDistributor.hh"
5 #include "FileContext.hh"
6 #include "DeviceConfig.hh"
7 #include "HardwareConfig.hh"
8 #include "XMLElement.hh"
9 #include "CassettePort.hh"
10 #include "CliComm.hh"
11 #include "Display.hh"
12 #include "GlobalSettings.hh"
13 #include "Reactor.hh"
14 #include "ReverseManager.hh"
15 #include "MSXMotherBoard.hh"
16 #include "PioneerLDControl.hh"
17 #include "OggReader.hh"
18 #include "LDRenderer.hh"
19 #include "RendererFactory.hh"
20 #include "Math.hh"
21 #include "likely.hh"
22 #include "one_of.hh"
23 #include <cstdint>
24 #include <cstdlib>
25 #include <iostream>
26 #include <memory>
27 
28 using std::string;
29 using std::vector;
30 
31 namespace openmsx {
32 
33 // Command
34 
35 LaserdiscPlayer::Command::Command(
36  CommandController& commandController_,
37  StateChangeDistributor& stateChangeDistributor_,
38  Scheduler& scheduler_)
39  : RecordedCommand(commandController_, stateChangeDistributor_,
40  scheduler_, "laserdiscplayer")
41 {
42 }
43 
44 void LaserdiscPlayer::Command::execute(
45  span<const TclObject> tokens, TclObject& result, EmuTime::param time)
46 {
47  auto& laserdiscPlayer = OUTER(LaserdiscPlayer, laserdiscCommand);
48  if (tokens.size() == 1) {
49  // Returning Tcl lists here, similar to the disk commands in
50  // DiskChanger
51  result.addListElement(getName() + ':',
52  laserdiscPlayer.getImageName().getResolved());
53  } else if (tokens[1] == "eject") {
54  checkNumArgs(tokens, 2, Prefix{2}, nullptr);
55  result = "Ejecting laserdisc.";
56  laserdiscPlayer.eject(time);
57  } else if (tokens[1] == "insert") {
58  checkNumArgs(tokens, 3, "filename");
59  try {
60  result = "Changing laserdisc.";
61  laserdiscPlayer.setImageName(string(tokens[2].getString()), time);
62  } catch (MSXException& e) {
63  throw CommandException(std::move(e).getMessage());
64  }
65  } else {
66  throw SyntaxError();
67  }
68 }
69 
70 string LaserdiscPlayer::Command::help(const vector<string>& tokens) const
71 {
72  if (tokens.size() >= 2) {
73  if (tokens[1] == "insert") {
74  return "Inserts the specified laserdisc image into "
75  "the laserdisc player.";
76  } else if (tokens[1] == "eject") {
77  return "Eject the laserdisc.";
78  }
79  }
80  return "laserdiscplayer insert <filename> "
81  ": insert a (different) laserdisc image\n"
82  "laserdiscplayer eject "
83  ": eject the laserdisc\n";
84 }
85 
86 void LaserdiscPlayer::Command::tabCompletion(vector<string>& tokens) const
87 {
88  if (tokens.size() == 2) {
89  static constexpr const char* const extra[] = { "eject", "insert" };
90  completeString(tokens, extra);
91  } else if (tokens.size() == 3 && tokens[1] == "insert") {
92  completeFileName(tokens, userFileContext());
93  }
94 }
95 
96 // LaserdiscPlayer
97 
98 constexpr unsigned DUMMY_INPUT_RATE = 44100; // actual rate depends on .ogg file
99 
100 static XMLElement createXML()
101 {
102  XMLElement xml("laserdiscplayer");
103  xml.addChild("sound").addChild("volume", "30000");
104  return xml;
105 }
106 
108  const HardwareConfig& hwConf, PioneerLDControl& ldcontrol_)
109  : ResampledSoundDevice(hwConf.getMotherBoard(), "laserdiscplayer",
110  "Laserdisc Player", 1, DUMMY_INPUT_RATE, true)
111  , syncAck (hwConf.getMotherBoard().getScheduler())
112  , syncOdd (hwConf.getMotherBoard().getScheduler())
113  , syncEven(hwConf.getMotherBoard().getScheduler())
114  , motherBoard(hwConf.getMotherBoard())
115  , ldcontrol(ldcontrol_)
116  , laserdiscCommand(motherBoard.getCommandController(),
117  motherBoard.getStateChangeDistributor(),
118  motherBoard.getScheduler())
119  , sampleClock(EmuTime::zero())
120  , start(EmuTime::zero())
121  , muteLeft(false)
122  , muteRight(false)
123  , remoteState(REMOTE_IDLE)
124  , remoteLastEdge(EmuTime::zero())
125  , remoteLastBit(false)
126  , remoteProtocol(IR_NONE)
127  , ack(false)
128  , seeking(false)
129  , playerState(PLAYER_STOPPED)
130  , autoRunSetting(
131  motherBoard.getCommandController(), "autorunlaserdisc",
132  "automatically try to run Laserdisc", true)
133  , loadingIndicator(
134  motherBoard.getReactor().getGlobalSettings().getThrottleManager())
135  , sampleReads(0)
136 {
137  motherBoard.getCassettePort().setLaserdiscPlayer(this);
138 
139  Reactor& reactor = motherBoard.getReactor();
140  reactor.getDisplay().attach(*this);
141 
142  createRenderer();
144  scheduleDisplayStart(getCurrentTime());
145 
146  static XMLElement xml = createXML();
147  registerSound(DeviceConfig(hwConf, xml));
148 }
149 
151 {
152  unregisterSound();
153  Reactor& reactor = motherBoard.getReactor();
154  reactor.getDisplay().detach(*this);
156 }
157 
158 void LaserdiscPlayer::scheduleDisplayStart(EmuTime::param time)
159 {
160  Clock<60000, 1001> frameClock(time);
161  // The video is 29.97Hz, however we need to do vblank processing
162  // at the full 59.94Hz
163  syncOdd .setSyncPoint(frameClock + 1);
164  syncEven.setSyncPoint(frameClock + 2);
165 }
166 
167 // The protocol used to communicate over the cable for commands to the
168 // laserdisc player is the NEC infrared protocol with minor deviations:
169 // 1) The leader pulse and space is a little shorter.
170 // 2) The remote does not send NEC repeats; full NEC codes are repeated
171 // after 20ms. The main unit does not understand NEC repeats.
172 // 3) No carrier modulation is done over the ext protocol.
173 //
174 // My Laserdisc player is an Pioneer LD-700 which has a remote called
175 // the CU-700. This is much like the CU-CLD106 which is described
176 // here: http://lirc.sourceforge.net/remotes/pioneer/CU-CLD106
177 // The codes and protocol are exactly the same.
178 void LaserdiscPlayer::extControl(bool bit, EmuTime::param time)
179 {
180  if (remoteLastBit == bit) return;
181  remoteLastBit = bit;
182 
183  // The tolerance here is based on actual measurements of an LD-700
184  EmuDuration duration = time - remoteLastEdge;
185  remoteLastEdge = time;
186  unsigned usec = duration.getTicksAt(1000000); // microseconds
187 
188  switch (remoteState) {
189  case REMOTE_IDLE:
190  if (bit) {
191  remoteBits = remoteBitNr = 0;
192  remoteState = REMOTE_HEADER_PULSE;
193  }
194  break;
195  case REMOTE_HEADER_PULSE:
196  if (5800 <= usec && usec < 11200) {
197  remoteState = NEC_HEADER_SPACE;
198  } else {
199  remoteState = REMOTE_IDLE;
200  }
201  break;
202  case NEC_HEADER_SPACE:
203  if (3400 <= usec && usec < 6200) {
204  remoteState = NEC_BITS_PULSE;
205  } else {
206  remoteState = REMOTE_IDLE;
207  }
208  break;
209  case NEC_BITS_PULSE:
210  if (usec >= 380 && usec < 1070) {
211  remoteState = NEC_BITS_SPACE;
212  } else {
213  remoteState = REMOTE_IDLE;
214  }
215  break;
216  case NEC_BITS_SPACE:
217  if (1260 <= usec && usec < 4720) {
218  // bit 1
219  remoteBits |= 1 << remoteBitNr;
220  } else if (usec < 300 || usec >= 1065) {
221  // error
222  remoteState = REMOTE_IDLE;
223  break;
224  }
225 
226  // since it does not matter how long the trailing pulse
227  // is, we process the button here. Note that real hardware
228  // needs the trailing pulse to be at least 200┬Ás
229  if (++remoteBitNr == 32) {
230  byte custom = ( remoteBits >> 0) & 0xff;
231  byte customCompl = (~remoteBits >> 8) & 0xff;
232  byte code = ( remoteBits >> 16) & 0xff;
233  byte codeCompl = (~remoteBits >> 24) & 0xff;
234  if (custom == customCompl &&
235  custom == 0xa8 &&
236  code == codeCompl) {
237  submitRemote(IR_NEC, code);
238  }
239  remoteState = REMOTE_IDLE;
240  } else {
241  remoteState = NEC_BITS_PULSE;
242  }
243 
244  break;
245  }
246 }
247 
248 void LaserdiscPlayer::submitRemote(RemoteProtocol protocol, unsigned code)
249 {
250  // The END command for seeking/waiting acknowledges repeats,
251  // Esh's Aurunmilla needs play as well.
252  if (protocol != remoteProtocol || code != remoteCode ||
253  (protocol == IR_NEC && (code == one_of(0x42u, 0x17u)))) {
254  remoteProtocol = protocol;
255  remoteCode = code;
256  remoteVblanksBack = 0;
257  remoteExecuteDelayed = true;
258  } else {
259  // remote ignored
260  remoteVblanksBack = 0;
261  remoteExecuteDelayed = false;
262  }
263 }
264 
266 {
267  return renderer->getRawFrame();
268 }
269 
270 void LaserdiscPlayer::setAck(EmuTime::param time, int wait)
271 {
272  // activate ACK for 'wait' milliseconds
273  syncAck.removeSyncPoint();
274  syncAck.setSyncPoint(time + EmuDuration::msec(wait));
275  ack = true;
276 }
277 
278 void LaserdiscPlayer::remoteButtonNEC(unsigned code, EmuTime::param time)
279 {
280 #ifdef DEBUG
281  string f;
282  switch (code) {
283  case 0x47: f = "C+"; break; // Increase playing speed
284  case 0x46: f = "C-"; break; // Decrease playing speed
285  case 0x43: f = "D+"; break; // Show Frame# & Chapter# OSD
286  case 0x4b: f = "L+"; break; // right
287  case 0x49: f = "L-"; break; // left
288  case 0x4a: f = "L@"; break; // stereo
289  case 0x58: f = "M+"; break; // multi speed forwards
290  case 0x55: f = "M-"; break; // multi speed backwards
291  case 0x17: f = "P+"; break; // play
292  case 0x16: f = "P@"; break; // stop
293  case 0x18: f = "P/"; break; // pause
294  case 0x54: f = "S+"; break; // frame step forward
295  case 0x50: f = "S-"; break; // frame step backwards
296  case 0x45: f = "X+"; break; // clear
297  case 0x41: f = 'F'; break; // seek frame
298  case 0x40: f = 'C'; break; // seek chapter
299  case 0x42: f = "END"; break; // done seek frame/chapter
300  case 0x00: f = '0'; break;
301  case 0x01: f = '1'; break;
302  case 0x02: f = '2'; break;
303  case 0x03: f = '3'; break;
304  case 0x04: f = '4'; break;
305  case 0x05: f = '5'; break;
306  case 0x06: f = '6'; break;
307  case 0x07: f = '7'; break;
308  case 0x08: f = '8'; break;
309  case 0x09: f = '9'; break;
310  case 0x5f: f = "WAIT FRAME"; break;
311 
312  case 0x53: // previous chapter
313  case 0x52: // next chapter
314  default: break;
315  }
316 
317  if (!f.empty()) {
318  std::cerr << "LaserdiscPlayer::remote " << f << '\n';
319  } else {
320  std::cerr << "LaserdiscPlayer::remote unknown " << std::hex << code << '\n';
321  }
322 #endif
323  // When not playing the following buttons work
324  // 0x17: start playing (ack sent)
325  // 0x16: eject (no ack)
326  // 0x49, 0x4a, 0x4b (ack sent)
327  // if 0x49 is a repeat then no ACK is sent
328  // if 0x49 is followed by 0x4a then ACK is sent
329  if (code == one_of(0x49u, 0x4au, 0x4bu)) {
330  updateStream(time);
331 
332  switch (code) {
333  case 0x4b: // L+ (both channels play the left channel)
334  stereoMode = LEFT;
335  break;
336  case 0x49: // L- (both channels play the right channel)
337  stereoMode = RIGHT;
338  break;
339  case 0x4a: // L@ (normal stereo)
340  stereoMode = STEREO;
341  break;
342  }
343 
344  setAck(time, 46);
345  } else if (playerState == PLAYER_STOPPED) {
346  switch (code) {
347  case 0x16: // P@
348  motherBoard.getMSXCliComm().printWarning(
349  "ejecting laserdisc");
350  eject(time);
351  break;
352  case 0x17: // P+
353  play(time);
354  break;
355  }
356 
357  // During playing, playing will be acked if not repeated
358  // within less than 115ms
359  } else {
360  // FIXME: while seeking, only a small subset of buttons work
361  bool nonseekack = true;
362 
363  switch (code) {
364  case 0x5f:
365  seekState = SEEK_WAIT;
366  seekNum = 0;
367  stillOnWaitFrame = false;
368  nonseekack = false;
369  break;
370  case 0x41:
371  seekState = SEEK_FRAME;
372  seekNum = 0;
373  break;
374  case 0x40:
375  seekState = SEEK_CHAPTER;
376  seekNum = 0;
377  nonseekack = video->getChapter(0) != 0;
378  break;
379  case 0x00:
380  case 0x01:
381  case 0x02:
382  case 0x03:
383  case 0x04:
384  case 0x05:
385  case 0x06:
386  case 0x07:
387  case 0x08:
388  case 0x09:
389  seekNum = seekNum * 10 + code;
390  break;
391  case 0x42:
392  switch (seekState) {
393  case SEEK_FRAME:
394  seekState = SEEK_NONE;
395  seekFrame(seekNum % 100000, time);
396  nonseekack = false;
397  break;
398  case SEEK_CHAPTER:
399  seekState = SEEK_NONE;
400  seekChapter(seekNum % 100, time);
401  nonseekack = false;
402  break;
403  case SEEK_WAIT:
404  seekState = SEEK_NONE;
405  waitFrame = seekNum % 100000;
406  if (waitFrame >= 101 && waitFrame < 200) {
407  auto frame = video->getChapter(
408  int(waitFrame - 100));
409  if (frame) waitFrame = frame;
410  }
411  break;
412  default:
413  seekState = SEEK_NONE;
414  break;
415  }
416  break;
417  case 0x45: // Clear "X+"
418  if (seekState != SEEK_NONE && seekNum != 0) {
419  seekNum = 0;
420  } else {
421  seekState = SEEK_NONE;
422  seekNum = 0;
423  }
424  waitFrame = 0;
425  break;
426  case 0x18: // P/
427  pause(time);
428  nonseekack = false;
429  break;
430  case 0x17: // P+
431  play(time);
432  nonseekack = false;
433  break;
434  case 0x16: // P@ (stop/eject)
435  stop(time);
436  nonseekack = false;
437  break;
438  case 0xff:
439  nonseekack = false;
440  seekState = SEEK_NONE;
441  break;
442  case 0x54: // S+ (frame step forward)
443  if (seekState == SEEK_WAIT) {
444  stillOnWaitFrame = true;
445  } else {
446  stepFrame(true);
447  }
448  break;
449  case 0x50: // S- (frame step backwards)
450  stepFrame(false);
451  break;
452  case 0x55: // M- (multispeed backwards)
453  // Not supported
454  motherBoard.getMSXCliComm().printWarning(
455  "The Laserdisc player received a command to "
456  "play backwards (M-). This is currently not "
457  "supported.");
458  nonseekack = false;
459  break;
460  case 0x58: // M+ (multispeed forwards)
461  playerState = PLAYER_MULTISPEED;
462  setFrameStep();
463  break;
464  case 0x46: // C- (play slower)
465  if (playingSpeed >= SPEED_STEP1) {
466  playingSpeed--;
467  frameStep = 1; // FIXME: is this correct?
468  }
469  break;
470  case 0x47: // C+ (play faster)
471  if (playingSpeed <= SPEED_X2) {
472  playingSpeed++;
473  frameStep = 1; // FIXME: is this correct?
474  }
475  break;
476  default:
477  motherBoard.getMSXCliComm().printWarning(
478  "The Laserdisc player received an unknown "
479  "command 0x", hex_string<2>(code));
480  nonseekack = false;
481  break;
482  }
483 
484  if (nonseekack) {
485  // All ACKs for operations which do not
486  // require seeking
487  setAck(time, 46);
488  }
489  }
490 }
491 
492 void LaserdiscPlayer::execSyncAck(EmuTime::param time)
493 {
494  updateStream(time);
495 
496  if (seeking && playerState == PLAYER_PLAYING) {
497  sampleClock.advance(time);
498  }
499 
500  ack = false;
501  seeking = false;
502 }
503 
504 void LaserdiscPlayer::execSyncFrame(EmuTime::param time, bool odd)
505 {
506  updateStream(time);
507 
508  if (!odd || (video && video->getFrameRate() == 60)) {
509  if ((playerState != PLAYER_STOPPED) &&
510  (currentFrame > video->getFrames())) {
511  playerState = PLAYER_STOPPED;
512  }
513 
514  if (auto* rawFrame = renderer->getRawFrame()) {
515  renderer->frameStart(time);
516 
517  if (isVideoOutputAvailable(time)) {
518  auto frame = currentFrame;
519  if (video->getFrameRate() == 60) {
520  frame *= 2;
521  if (odd) frame--;
522  }
523 
524  video->getFrameNo(*rawFrame, frame);
525 
526  if (!odd) {
527  nextFrame(time);
528  }
529  } else {
530  renderer->drawBlank(0, 128, 196);
531  }
532  renderer->frameEnd();
533  }
534 
535  // Update throttling
536  loadingIndicator.update(seeking || sampleReads > 500);
537  sampleReads = 0;
538 
539  if (!odd) {
540  scheduleDisplayStart(time);
541  }
542  }
543 
544  // Processing of the remote control happens at each frame
545  // (even and odd, so at 59.94Hz)
546  if (remoteProtocol == IR_NEC) {
547  if (remoteExecuteDelayed) {
548  remoteButtonNEC(remoteCode, time);
549  }
550 
551  if (++remoteVblanksBack > 6) {
552  remoteProtocol = IR_NONE;
553  }
554  }
555  remoteExecuteDelayed = false;
556 }
557 
558 void LaserdiscPlayer::setFrameStep()
559 {
560  switch (playingSpeed) {
561  case SPEED_X3:
562  case SPEED_X2:
563  case SPEED_X1:
564  frameStep = 1;
565  break;
566  case SPEED_1IN2:
567  frameStep = 2;
568  break;
569  case SPEED_1IN4:
570  frameStep = 4;
571  break;
572  case SPEED_1IN8:
573  frameStep = 8;
574  break;
575  case SPEED_1IN16:
576  frameStep = 16;
577  break;
578  case SPEED_STEP1:
579  frameStep = 30;
580  break;
581  case SPEED_STEP3:
582  frameStep = 90;
583  break;
584  }
585 }
586 
587 void LaserdiscPlayer::nextFrame(EmuTime::param time)
588 {
589  if (waitFrame && waitFrame == currentFrame) {
590  // Leave ACK raised until the next command
591  ack = true;
592  waitFrame = 0;
593 
594  if (stillOnWaitFrame) {
595  playingFromSample = getCurrentSample(time);
596  playerState = PLAYER_STILL;
597  stillOnWaitFrame = false;
598  }
599  }
600 
601  if (playerState == PLAYER_MULTISPEED) {
602  if (--frameStep) {
603  return;
604  }
605 
606  switch (playingSpeed) {
607  case SPEED_X3:
608  currentFrame += 3;
609  break;
610  case SPEED_X2:
611  currentFrame += 2;
612  break;
613  default:
614  currentFrame += 1;
615  break;
616  }
617  setFrameStep();
618  } else if (playerState == PLAYER_PLAYING) {
619  currentFrame++;
620  }
621 
622  // freeze if stop frame
623  if ((playerState == one_of(PLAYER_PLAYING, PLAYER_MULTISPEED))
624  && video->stopFrame(currentFrame)) {
625  // stop frame reached
626  playingFromSample = getCurrentSample(time);
627  playerState = PLAYER_STILL;
628  }
629 }
630 
631 void LaserdiscPlayer::setImageName(string newImage, EmuTime::param time)
632 {
633  stop(time);
634  oggImage = Filename(std::move(newImage), userFileContext());
635  video = std::make_unique<OggReader>(oggImage, motherBoard.getMSXCliComm());
636 
637  unsigned inputRate = video->getSampleRate();
638  sampleClock.setFreq(inputRate);
639  if (inputRate != getInputRate()) {
640  setInputRate(inputRate);
641  createResampler();
642  }
643 }
644 
645 int LaserdiscPlayer::signalEvent(const std::shared_ptr<const Event>& event)
646 {
647  if (event->getType() == OPENMSX_BOOT_EVENT && video) {
648  autoRun();
649  }
650  return 0;
651 }
652 
653 void LaserdiscPlayer::autoRun()
654 {
655  if (!autoRunSetting.getBoolean()) return;
656  if (motherBoard.getReverseManager().isReplaying()) {
657  // See comments in CassettePlayer::autoRun()
658  return;
659  }
660 
661  string var = "::auto_run_ld_counter";
662  string command = strCat(
663  "if ![info exists ", var, "] { set ", var, " 0 }\n"
664  "incr ", var, "\n"
665  "after time 2 \"if $", var, "==\\$", var, " { "
666  "type_via_keyboard 1CALLLD\\\\r }\"");
667  try {
668  motherBoard.getCommandController().executeCommand(command);
669  } catch (CommandException& e) {
670  motherBoard.getMSXCliComm().printWarning(
671  "Error executing loading instruction for AutoRun: ",
672  e.getMessage(), "\n Please report a bug.");
673  }
674 }
675 
676 void LaserdiscPlayer::generateChannels(float** buffers, unsigned num)
677 {
678  // Single channel device: replace content of buffers[0] (not add to it).
679  if (playerState != PLAYER_PLAYING || seeking || (muteLeft && muteRight)) {
680  buffers[0] = nullptr;
681  return;
682  }
683 
684  unsigned pos = 0;
685  size_t currentSample;
686 
687  if (unlikely(!sampleClock.before(start))) {
688  // Before playing of sounds begins
689  EmuDuration duration = sampleClock.getTime() - start;
690  unsigned len = duration.getTicksAt(video->getSampleRate());
691  if (len >= num) {
692  buffers[0] = nullptr;
693  return;
694  }
695 
696  for (; pos < len; ++pos) {
697  buffers[0][pos * 2 + 0] = 0.0f;
698  buffers[0][pos * 2 + 1] = 0.0f;
699  }
700 
701  currentSample = playingFromSample;
702  } else {
703  currentSample = getCurrentSample(start);
704  }
705 
706  unsigned drift = video->getSampleRate() / 30;
707 
708  if (currentSample > (lastPlayedSample + drift) ||
709  (currentSample + drift) < lastPlayedSample) {
710  // audio drift
711  lastPlayedSample = currentSample;
712  }
713 
714  int left = stereoMode == RIGHT ? 1 : 0;
715  int right = stereoMode == LEFT ? 0 : 1;
716 
717  while (pos < num) {
718  const AudioFragment* audio = video->getAudio(lastPlayedSample);
719 
720  if (!audio) {
721  if (pos == 0) {
722  buffers[0] = nullptr;
723  break;
724  } else {
725  for (; pos < num; ++pos) {
726  buffers[0][pos * 2 + 0] = 0.0f;
727  buffers[0][pos * 2 + 1] = 0.0f;
728  }
729  }
730  } else {
731  auto offset = unsigned(lastPlayedSample - audio->position);
732  unsigned len = std::min(audio->length - offset, num - pos);
733 
734  // maybe muting should be moved out of the loop?
735  for (unsigned i = 0; i < len; ++i, ++pos) {
736  buffers[0][pos * 2 + 0] = muteLeft ? 0.0f :
737  audio->pcm[left][offset + i];
738  buffers[0][pos * 2 + 1] = muteRight ? 0.0f :
739  audio->pcm[right][offset + i];
740  }
741 
742  lastPlayedSample += len;
743  }
744  }
745 }
746 
747 float LaserdiscPlayer::getAmplificationFactorImpl() const
748 {
749  return 2.0f;
750 }
751 
752 bool LaserdiscPlayer::updateBuffer(unsigned length, float* buffer,
753  EmuTime::param time)
754 {
755  bool result = ResampledSoundDevice::updateBuffer(length, buffer, time);
756  start = time; // current end-time is next start-time
757  return result;
758 }
759 
760 void LaserdiscPlayer::setMuting(bool left, bool right, EmuTime::param time)
761 {
762  updateStream(time);
763  muteLeft = left;
764  muteRight = right;
765 }
766 
767 void LaserdiscPlayer::play(EmuTime::param time)
768 {
769  if (video) {
770  updateStream(time);
771 
772  if (seeking) {
773  // Do not ACK, play while seeking
774  } else if (playerState == PLAYER_STOPPED) {
775  // Disk needs to spin up, which takes 9.6s on
776  // my Pioneer LD-92000. Also always seek to
777  // beginning (confirmed on real MSX and LD)
778  video->seek(1, 0);
779  lastPlayedSample = 0;
780  playingFromSample = 0;
781  currentFrame = 1;
782  // Note that with "fullspeedwhenloading" this
783  // should be reduced to.
784  setAck(time, 9600);
785  seekState = SEEK_NONE;
786  seeking = true;
787  waitFrame = 0;
788  stereoMode = STEREO;
789  playingSpeed = SPEED_1IN4;
790  } else if (playerState == PLAYER_PLAYING) {
791  // If Play command is issued while the player
792  // is already playing, then if no ACK is sent then
793  // Astron Belt will send LD1100 commands
794  setAck(time, 46);
795  } else if (playerState == PLAYER_MULTISPEED) {
796  // Should be hearing stuff again
797  playingFromSample = (currentFrame - 1ll) * 1001ll *
798  video->getSampleRate() / 30000ll;
799  sampleClock.advance(time);
800  setAck(time, 46);
801  } else {
802  // STILL or PAUSED
803  sampleClock.advance(time);
804  setAck(time, 46);
805  }
806  playerState = PLAYER_PLAYING;
807  }
808 }
809 
810 size_t LaserdiscPlayer::getCurrentSample(EmuTime::param time)
811 {
812  switch(playerState) {
813  case PLAYER_PAUSED:
814  case PLAYER_STILL:
815  return playingFromSample;
816  default:
817  return playingFromSample + sampleClock.getTicksTill(time);
818  }
819 }
820 
821 void LaserdiscPlayer::pause(EmuTime::param time)
822 {
823  if (playerState != PLAYER_STOPPED) {
824  updateStream(time);
825 
826  if (playerState == PLAYER_PLAYING) {
827  playingFromSample = getCurrentSample(time);
828  } else if (playerState == PLAYER_MULTISPEED) {
829  playingFromSample = (currentFrame - 1ll) * 1001ll *
830  video->getSampleRate() / 30000ll;
831  sampleClock.advance(time);
832  }
833 
834  playerState = PLAYER_PAUSED;
835  setAck(time, 46);
836  }
837 }
838 
839 void LaserdiscPlayer::stop(EmuTime::param time)
840 {
841  if (playerState != PLAYER_STOPPED) {
842  updateStream(time);
843  playerState = PLAYER_STOPPED;
844  }
845 }
846 
847 void LaserdiscPlayer::eject(EmuTime::param time)
848 {
849  stop(time);
850  oggImage = {};
851  video.reset();
852 }
853 
854 // Step one frame forwards or backwards. The frame will be visible and
855 // we won't be playing afterwards
856 void LaserdiscPlayer::stepFrame(bool forwards)
857 {
858  bool needseek = false;
859 
860  // Note that on real hardware, the screen goes dark momentarily
861  // if you try to step before the first frame or after the last one
862  if (playerState == PLAYER_STILL) {
863  if (forwards) {
864  if (currentFrame < video->getFrames()) {
865  currentFrame++;
866  }
867  } else {
868  if (currentFrame > 1) {
869  currentFrame--;
870  needseek = true;
871  }
872  }
873  }
874 
875  playerState = PLAYER_STILL;
876  int64_t samplePos = (currentFrame - 1ll) * 1001ll *
877  video->getSampleRate() / 30000ll;
878  playingFromSample = samplePos;
879 
880  if (needseek) {
881  if (video->getFrameRate() == 60)
882  video->seek(currentFrame * 2, samplePos);
883  else
884  video->seek(currentFrame, samplePos);
885  }
886 }
887 
888 void LaserdiscPlayer::seekFrame(size_t toframe, EmuTime::param time)
889 {
890  if ((playerState != PLAYER_STOPPED) && video) {
891  updateStream(time);
892 
893  if (toframe <= 0) toframe = 1;
894  if (toframe > video->getFrames()) toframe = video->getFrames();
895 
896  // Seek time needs to be emulated correctly since
897  // e.g. Astron Belt does not wait for the seek
898  // to complete, it simply assumes a certain
899  // delay.
900  //
901  // This calculation is based on measurements on
902  // a Pioneer LD-92000.
903  auto dist = std::abs(int64_t(toframe) - int64_t(currentFrame));
904  int seektime; // time in ms
905 
906  if (dist < 1000) {
907  seektime = dist + 300;
908  } else {
909  seektime = 1800 + dist / 12;
910  }
911 
912  int64_t samplePos = (toframe - 1ll) * 1001ll *
913  video->getSampleRate() / 30000ll;
914 
915  if (video->getFrameRate() == 60) {
916  video->seek(toframe * 2, samplePos);
917  } else {
918  video->seek(toframe, samplePos);
919  }
920  playerState = PLAYER_STILL;
921  playingFromSample = samplePos;
922  currentFrame = toframe;
923 
924  // Seeking clears the frame to wait for
925  waitFrame = 0;
926 
927  seeking = true;
928  setAck(time, seektime);
929  }
930 }
931 
932 void LaserdiscPlayer::seekChapter(int chapter, EmuTime::param time)
933 {
934  if ((playerState != PLAYER_STOPPED) && video) {
935  auto frameno = video->getChapter(chapter);
936  if (!frameno) return;
937  seekFrame(frameno, time);
938  }
939 }
940 
941 int16_t LaserdiscPlayer::readSample(EmuTime::param time)
942 {
943  // Here we should return the value of the sample on the
944  // right audio channel, ignoring muting (this is done in the MSX)
945  // but honouring the stereo mode as this is done in the
946  // Laserdisc player
947  if (playerState == PLAYER_PLAYING && !seeking) {
948  auto sample = getCurrentSample(time);
949  if (const AudioFragment* audio = video->getAudio(sample)) {
950  ++sampleReads;
951  int channel = stereoMode == LEFT ? 0 : 1;
952  return int(audio->pcm[channel][sample - audio->position]
953  * 32767.f);
954  }
955  }
956  return 0;
957 }
958 
959 bool LaserdiscPlayer::isVideoOutputAvailable(EmuTime::param time)
960 {
961  updateStream(time);
962 
963  bool videoOut;
964  switch (playerState) {
965  case PLAYER_PLAYING:
966  case PLAYER_MULTISPEED:
967  case PLAYER_STILL:
968  videoOut = !seeking;
969  break;
970  default:
971  videoOut = false;
972  break;
973  }
974  ldcontrol.videoIn(videoOut);
975 
976  return videoOut;
977 }
978 
979 void LaserdiscPlayer::preVideoSystemChange()
980 {
981  renderer.reset();
982 }
983 
984 void LaserdiscPlayer::postVideoSystemChange()
985 {
986  createRenderer();
987 }
988 
989 void LaserdiscPlayer::createRenderer()
990 {
991  Display& display = getMotherBoard().getReactor().getDisplay();
992  renderer = RendererFactory::createLDRenderer(*this, display);
993 }
994 
995 static std::initializer_list<enum_string<LaserdiscPlayer::RemoteState>> RemoteStateInfo = {
996  { "IDLE", LaserdiscPlayer::REMOTE_IDLE },
997  { "HEADER_PULSE", LaserdiscPlayer::REMOTE_HEADER_PULSE },
998  { "NEC_HEADER_SPACE", LaserdiscPlayer::NEC_HEADER_SPACE },
999  { "NEC_BITS_PULSE", LaserdiscPlayer::NEC_BITS_PULSE },
1000  { "NEC_BITS_SPACE", LaserdiscPlayer::NEC_BITS_SPACE },
1001 };
1003 
1004 static std::initializer_list<enum_string<LaserdiscPlayer::PlayerState>> PlayerStateInfo = {
1005  { "STOPPED", LaserdiscPlayer::PLAYER_STOPPED },
1006  { "PLAYING", LaserdiscPlayer::PLAYER_PLAYING },
1007  { "MULTISPEED", LaserdiscPlayer::PLAYER_MULTISPEED },
1008  { "PAUSED", LaserdiscPlayer::PLAYER_PAUSED },
1009  { "STILL", LaserdiscPlayer::PLAYER_STILL }
1010 };
1012 
1013 static std::initializer_list<enum_string<LaserdiscPlayer::SeekState>> SeekStateInfo = {
1014  { "NONE", LaserdiscPlayer::SEEK_NONE },
1015  { "CHAPTER", LaserdiscPlayer::SEEK_CHAPTER },
1016  { "FRAME", LaserdiscPlayer::SEEK_FRAME },
1017  { "WAIT", LaserdiscPlayer::SEEK_WAIT }
1018 };
1020 
1021 static std::initializer_list<enum_string<LaserdiscPlayer::StereoMode>> StereoModeInfo = {
1022  { "LEFT", LaserdiscPlayer::LEFT },
1023  { "RIGHT", LaserdiscPlayer::RIGHT },
1024  { "STEREO", LaserdiscPlayer::STEREO }
1025 };
1027 
1028 static std::initializer_list<enum_string<LaserdiscPlayer::RemoteProtocol>> RemoteProtocolInfo = {
1029  { "NONE", LaserdiscPlayer::IR_NONE },
1030  { "NEC", LaserdiscPlayer::IR_NEC },
1031 };
1033 
1034 // version 1: initial version
1035 // version 2: added 'stillOnWaitFrame'
1036 // version 3: reversed bit order of 'remoteBits' and 'remoteCode'
1037 // version 4: removed 'userData' from Schedulable
1038 template<typename Archive>
1039 void LaserdiscPlayer::serialize(Archive& ar, unsigned version)
1040 {
1041  // Serialize remote control
1042  ar.serialize("RemoteState", remoteState);
1043  if (remoteState != REMOTE_IDLE) {
1044  ar.serialize("RemoteBitNr", remoteBitNr,
1045  "RemoteBits", remoteBits);
1046  if (ar.versionBelow(version, 3)) {
1047  assert(ar.isLoader());
1048  remoteBits = Math::reverseNBits(remoteBits, remoteBitNr);
1049  }
1050  }
1051  ar.serialize("RemoteLastBit", remoteLastBit,
1052  "RemoteLastEdge", remoteLastEdge,
1053  "RemoteProtocol", remoteProtocol);
1054  if (remoteProtocol != IR_NONE) {
1055  ar.serialize("RemoteCode", remoteCode);
1056  if (ar.versionBelow(version, 3)) {
1057  assert(ar.isLoader());
1058  remoteCode = Math::reverseByte(remoteCode);
1059  }
1060  ar.serialize("RemoteExecuteDelayed", remoteExecuteDelayed,
1061  "RemoteVblanksBack", remoteVblanksBack);
1062  }
1063 
1064  // Serialize filename
1065  ar.serialize("OggImage", oggImage);
1066  if (ar.isLoader()) {
1067  sampleReads = 0;
1068  if (!oggImage.empty()) {
1069  setImageName(oggImage.getResolved(), getCurrentTime());
1070  } else {
1071  video.reset();
1072  }
1073  }
1074  ar.serialize("PlayerState", playerState);
1075 
1076  if (playerState != PLAYER_STOPPED) {
1077  // Serialize seek state
1078  ar.serialize("SeekState", seekState);
1079  if (seekState != SEEK_NONE) {
1080  ar.serialize("SeekNum", seekNum);
1081  }
1082  ar.serialize("seeking", seeking);
1083 
1084  // Playing state
1085  ar.serialize("WaitFrame", waitFrame);
1086 
1087  // This was not yet implemented in openmsx 0.8.1 and earlier
1088  if (ar.versionAtLeast(version, 2)) {
1089  ar.serialize("StillOnWaitFrame", stillOnWaitFrame);
1090  }
1091 
1092  ar.serialize("ACK", ack,
1093  "PlayingSpeed", playingSpeed);
1094 
1095  // Frame position
1096  ar.serialize("CurrentFrame", currentFrame);
1097  if (playerState == PLAYER_MULTISPEED) {
1098  ar.serialize("FrameStep", frameStep);
1099  }
1100 
1101  // Audio position
1102  ar.serialize("StereoMode", stereoMode,
1103  "FromSample", playingFromSample,
1104  "SampleClock", sampleClock);
1105 
1106  if (ar.isLoader()) {
1107  // If the samplerate differs, adjust accordingly
1108  if (video->getSampleRate() != sampleClock.getFreq()) {
1109  uint64_t pos = playingFromSample;
1110 
1111  pos *= video->getSampleRate();
1112  pos /= sampleClock.getFreq();
1113 
1114  playingFromSample = pos;
1115  sampleClock.setFreq(video->getSampleRate());
1116  }
1117 
1118  auto sample = getCurrentSample(getCurrentTime());
1119  if (video->getFrameRate() == 60)
1120  video->seek(currentFrame * 2, sample);
1121  else
1122  video->seek(currentFrame, sample);
1123  lastPlayedSample = sample;
1124  }
1125  }
1126 
1127  if (ar.versionAtLeast(version, 4)) {
1128  ar.serialize("syncEven", syncEven,
1129  "syncOdd", syncOdd,
1130  "syncAck", syncAck);
1131  } else {
1132  Schedulable::restoreOld(ar, {&syncEven, &syncOdd, &syncAck});
1133  }
1134 
1135  if (ar.isLoader()) {
1136  isVideoOutputAvailable(getCurrentTime());
1137  }
1138 }
1139 
1141 
1142 } // namespace openmsx
openmsx::Schedulable::restoreOld
static void restoreOld(Archive &ar, std::vector< Schedulable * > schedulables)
Definition: Schedulable.hh:77
openmsx::DynamicClock::advance
void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
Definition: DynamicClock.hh:151
openmsx::MSXMotherBoard::getReactor
Reactor & getReactor()
Definition: MSXMotherBoard.hh:136
openmsx::LaserdiscPlayer::setMuting
void setMuting(bool left, bool right, EmuTime::param time)
Definition: LaserdiscPlayer.cc:760
one_of.hh
openmsx::LaserdiscPlayer::RemoteProtocol
RemoteProtocol
Definition: LaserdiscPlayer.hh:78
OggReader.hh
HardwareConfig.hh
openmsx::SoundDevice::setInputRate
void setInputRate(unsigned sampleRate)
Definition: SoundDevice.hh:109
openmsx::DynamicClock::setFreq
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
Definition: DynamicClock.hh:105
openmsx::LaserdiscPlayer::SEEK_FRAME
@ SEEK_FRAME
Definition: LaserdiscPlayer.hh:68
openmsx::ResampledSoundDevice
Definition: ResampledSoundDevice.hh:17
openmsx::LaserdiscPlayer::SEEK_NONE
@ SEEK_NONE
Definition: LaserdiscPlayer.hh:66
gl::min
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
unlikely
#define unlikely(x)
Definition: likely.hh:15
Display.hh
openmsx::OPENMSX_BOOT_EVENT
@ OPENMSX_BOOT_EVENT
Definition: Event.hh:36
openmsx::CassettePortInterface::setLaserdiscPlayer
virtual void setLaserdiscPlayer(LaserdiscPlayer *laserdisc)=0
Set the Laserdisc Player; when the motor control is off, sound is read from the laserdisc.
openmsx::Reactor::getDisplay
Display & getDisplay()
Definition: Reactor.hh:85
openmsx::EmuDuration
Definition: EmuDuration.hh:19
openmsx::LaserdiscPlayer::PLAYER_PAUSED
@ PLAYER_PAUSED
Definition: LaserdiscPlayer.hh:61
openmsx::AudioFragment
Definition: OggReader.hh:21
openmsx::DeviceConfig
Definition: DeviceConfig.hh:20
openmsx::PioneerLDControl
Definition: PioneerLDControl.hh:17
openmsx::LaserdiscPlayer::STEREO
@ STEREO
Definition: LaserdiscPlayer.hh:75
openmsx::LaserdiscPlayer::~LaserdiscPlayer
~LaserdiscPlayer()
Definition: LaserdiscPlayer.cc:150
openmsx::SERIALIZE_ENUM
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
openmsx::LaserdiscPlayer::SeekState
SeekState
Definition: LaserdiscPlayer.hh:65
openmsx::Display::attach
void attach(VideoSystemChangeListener &listener)
Definition: Display.cc:136
openmsx::LaserdiscPlayer::serialize
void serialize(Archive &ar, unsigned version)
Definition: LaserdiscPlayer.cc:1039
openmsx::DynamicClock::getFreq
unsigned getFreq() const
Returns the frequency (in Hz) at which this clock ticks.
Definition: DynamicClock.hh:124
openmsx::XMLElement
XMLElement
Definition: XMLElement.cc:350
gl::length
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:348
openmsx::DUMMY_INPUT_RATE
constexpr unsigned DUMMY_INPUT_RATE
Definition: CassettePlayer.cc:58
openmsx::Filename::getResolved
const std::string & getResolved() const
Definition: Filename.hh:27
openmsx::LaserdiscPlayer::StereoMode
StereoMode
Definition: LaserdiscPlayer.hh:72
openmsx::Reactor::getEventDistributor
EventDistributor & getEventDistributor()
Definition: Reactor.hh:81
openmsx::ResampledSoundDevice::updateBuffer
bool updateBuffer(unsigned length, float *buffer, EmuTime::param time) override
Generate sample data.
Definition: ResampledSoundDevice.cc:39
openmsx::userFileContext
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:164
XMLElement.hh
RendererFactory.hh
openmsx::LaserdiscPlayer
Definition: LaserdiscPlayer.hh:28
openmsx::MSXMotherBoard::getCommandController
CommandController & getCommandController()
Definition: MSXMotherBoard.cc:493
PioneerLDControl.hh
openmsx::DynamicClock::before
bool before(EmuTime::param e) const
Checks whether this clock's last tick is or is not before the given time stamp.
Definition: DynamicClock.hh:45
LDRenderer.hh
openmsx::LaserdiscPlayer::NEC_BITS_SPACE
@ NEC_BITS_SPACE
Definition: LaserdiscPlayer.hh:54
openmsx::CommandController::executeCommand
virtual TclObject executeCommand(const std::string &command, CliConnection *connection=nullptr)=0
Execute the given command.
openmsx::LaserdiscPlayer::RIGHT
@ RIGHT
Definition: LaserdiscPlayer.hh:74
span
Definition: span.hh:126
likely.hh
openmsx::Reactor
Contains the main loop of openMSX.
Definition: Reactor.hh:67
openmsx::LaserdiscPlayer::PLAYER_STOPPED
@ PLAYER_STOPPED
Definition: LaserdiscPlayer.hh:58
openmsx::LaserdiscPlayer::PlayerState
PlayerState
Definition: LaserdiscPlayer.hh:57
Reactor.hh
CommandController.hh
OUTER
#define OUTER(type, member)
Definition: outer.hh:41
openmsx::CliComm::printWarning
void printWarning(std::string_view message)
Definition: CliComm.cc:10
openmsx::SoundDevice::getInputRate
unsigned getInputRate() const
Definition: SoundDevice.hh:110
ReverseManager.hh
openmsx::XMLElement
Definition: XMLElement.hh:16
CassettePort.hh
openmsx::Keys::getName
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:740
openmsx::LaserdiscPlayer::NEC_BITS_PULSE
@ NEC_BITS_PULSE
Definition: LaserdiscPlayer.hh:53
openmsx::LaserdiscPlayer::extControl
void extControl(bool bit, EmuTime::param time)
Definition: LaserdiscPlayer.cc:178
openmsx::EmuDuration::getTicksAt
constexpr unsigned getTicksAt(unsigned freq) const
Definition: EmuDuration.hh:105
openmsx::MSXMotherBoard::getReverseManager
ReverseManager & getReverseManager()
Definition: MSXMotherBoard.hh:135
openmsx::SoundDevice::registerSound
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:92
one_of
Definition: one_of.hh:7
EventDistributor.hh
openmsx::LaserdiscPlayer
LaserdiscPlayer
Definition: LaserdiscPlayer.cc:1140
openmsx::LaserdiscPlayer::RemoteState
RemoteState
Definition: LaserdiscPlayer.hh:49
openmsx::LaserdiscPlayer::IR_NONE
@ IR_NONE
Definition: LaserdiscPlayer.hh:79
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
LaserdiscPlayer.hh
openmsx::Filename::empty
bool empty() const
Convenience method to test for empty filename.
Definition: Filename.cc:38
openmsx::LaserdiscPlayer::LEFT
@ LEFT
Definition: LaserdiscPlayer.hh:73
openmsx::RawFrame
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:26
openmsx::SoundDevice::unregisterSound
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:133
GlobalSettings.hh
FileContext.hh
Math::reverseNBits
unsigned reverseNBits(unsigned x, unsigned bits)
Reverse the lower N bits of a given value.
Definition: Math.hh:119
openmsx::RendererFactory::createLDRenderer
unique_ptr< LDRenderer > createLDRenderer(LaserdiscPlayer &ld, Display &display)
Create the Laserdisc Renderer.
Definition: RendererFactory.cc:70
openmsx::LaserdiscPlayer::IR_NEC
@ IR_NEC
Definition: LaserdiscPlayer.hh:80
openmsx::LoadingIndicator::update
void update(bool newState)
Called by the device to indicate its loading state may have changed.
Definition: ThrottleManager.cc:66
span::size
constexpr index_type size() const noexcept
Definition: span.hh:296
openmsx::Filename
Filename
Definition: Filename.cc:50
openmsx::LaserdiscPlayer::PLAYER_PLAYING
@ PLAYER_PLAYING
Definition: LaserdiscPlayer.hh:59
openmsx::ReverseManager::isReplaying
bool isReplaying() const override
Definition: ReverseManager.cc:163
openmsx::EmuDuration::msec
static constexpr EmuDuration msec(unsigned x)
Definition: EmuDuration.hh:41
openmsx::LaserdiscPlayer::NEC_HEADER_SPACE
@ NEC_HEADER_SPACE
Definition: LaserdiscPlayer.hh:52
openmsx::DynamicClock::getTime
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: DynamicClock.hh:38
openmsx::LaserdiscPlayer::SEEK_WAIT
@ SEEK_WAIT
Definition: LaserdiscPlayer.hh:69
openmsx::HardwareConfig
Definition: HardwareConfig.hh:22
openmsx::Scheduler
Scheduler
Definition: Scheduler.cc:132
openmsx::LaserdiscPlayer::REMOTE_HEADER_PULSE
@ REMOTE_HEADER_PULSE
Definition: LaserdiscPlayer.hh:51
openmsx::MSXMotherBoard::getCassettePort
CassettePortInterface & getCassettePort()
Definition: MSXMotherBoard.cc:426
openmsx::LaserdiscPlayer::readSample
int16_t readSample(EmuTime::param time)
Definition: LaserdiscPlayer.cc:941
Math::reverseByte
uint8_t reverseByte(uint8_t a)
Reverse the bits in a byte.
Definition: Math.hh:169
openmsx::EventDistributor::registerEventListener
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
Definition: EventDistributor.cc:23
openmsx::LaserdiscPlayer::REMOTE_IDLE
@ REMOTE_IDLE
Definition: LaserdiscPlayer.hh:50
CliComm.hh
openmsx::LaserdiscPlayer::SEEK_CHAPTER
@ SEEK_CHAPTER
Definition: LaserdiscPlayer.hh:67
DeviceConfig.hh
openmsx::LaserdiscPlayer::getMotherBoard
MSXMotherBoard & getMotherBoard()
Definition: LaserdiscPlayer.hh:47
Math.hh
openmsx::DynamicClock::getTicksTill
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: DynamicClock.hh:52
openmsx::EventDistributor::unregisterEventListener
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
Definition: EventDistributor.cc:35
openmsx::LaserdiscPlayer::getRawFrame
const RawFrame * getRawFrame() const
Definition: LaserdiscPlayer.cc:265
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
CommandException.hh
openmsx::Clock
Represents a clock with a fixed frequency.
Definition: Clock.hh:19
openmsx::LaserdiscPlayer::PLAYER_MULTISPEED
@ PLAYER_MULTISPEED
Definition: LaserdiscPlayer.hh:60
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::PioneerLDControl::videoIn
void videoIn(bool enabled)
Definition: PioneerLDControl.cc:153
MSXMotherBoard.hh
openmsx::MSXMotherBoard::getMSXCliComm
CliComm & getMSXCliComm()
Definition: MSXMotherBoard.cc:383
openmsx::BooleanSetting::getBoolean
bool getBoolean() const noexcept
Definition: BooleanSetting.hh:17
openmsx::SoundDevice::updateStream
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:138
openmsx::ResampledSoundDevice::createResampler
void createResampler()
Definition: ResampledSoundDevice.cc:58
openmsx::LaserdiscPlayer::PLAYER_STILL
@ PLAYER_STILL
Definition: LaserdiscPlayer.hh:62
openmsx::LaserdiscPlayer::LaserdiscPlayer
LaserdiscPlayer(const HardwareConfig &hwConf, PioneerLDControl &ldcontrol)
Definition: LaserdiscPlayer.cc:107
openmsx::Display::detach
void detach(VideoSystemChangeListener &listener)
Definition: Display.cc:142