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