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