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