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