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:
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 
294 void LaserdiscPlayer::remoteButtonNEC(unsigned code, EmuTime::param time)
295 {
296 #ifdef DEBUG
297  string f;
298  switch (code) {
299  case 0x47: f = "C+"; break; // Increase playing speed
300  case 0x46: f = "C-"; break; // Decrease playing speed
301  case 0x43: f = "D+"; break; // Show Frame# & Chapter# OSD
302  case 0x4b: f = "L+"; break; // right
303  case 0x49: f = "L-"; break; // left
304  case 0x4a: f = "L@"; break; // stereo
305  case 0x58: f = "M+"; break; // multi speed forwards
306  case 0x55: f = "M-"; break; // multi speed backwards
307  case 0x17: f = "P+"; break; // play
308  case 0x16: f = "P@"; break; // stop
309  case 0x18: f = "P/"; break; // pause
310  case 0x54: f = "S+"; break; // frame step forward
311  case 0x50: f = "S-"; break; // frame step backwards
312  case 0x45: f = "X+"; break; // clear
313  case 0x41: f = 'F'; break; // seek frame
314  case 0x40: f = 'C'; break; // seek chapter
315  case 0x42: f = "END"; break; // done seek frame/chapter
316  case 0x00: f = '0'; break;
317  case 0x01: f = '1'; break;
318  case 0x02: f = '2'; break;
319  case 0x03: f = '3'; break;
320  case 0x04: f = '4'; break;
321  case 0x05: f = '5'; break;
322  case 0x06: f = '6'; break;
323  case 0x07: f = '7'; break;
324  case 0x08: f = '8'; break;
325  case 0x09: f = '9'; break;
326  case 0x5f: f = "WAIT FRAME"; break;
327 
328  case 0x53: // previous chapter
329  case 0x52: // next chapter
330  default: break;
331  }
332 
333  if (!f.empty()) {
334  PRT_DEBUG("LaserdiscPlayer::remote " << f);
335  } else {
336  PRT_DEBUG("LaserdiscPlayer::remote unknown " << std::hex << code);
337  }
338 #endif
339  // When not playing the following buttons work
340  // 0x17: start playing (ack sent)
341  // 0x16: eject (no ack)
342  // 0x49, 0x4a, 0x4b (ack sent)
343  // if 0x49 is a repeat then no ACK is sent
344  // if 0x49 is followed by 0x4a then ACK is sent
345  if (code == 0x49 || code == 0x4a || code == 0x4b) {
346  updateStream(time);
347 
348  switch (code) {
349  case 0x4b: // L+ (both channels play the left channel)
350  stereoMode = LEFT;
351  break;
352  case 0x49: // L- (both channels play the right channel)
353  stereoMode = RIGHT;
354  break;
355  case 0x4a: // L@ (normal stereo)
356  stereoMode = STEREO;
357  break;
358  }
359 
360  setAck(time, 46);
361  } else if (playerState == PLAYER_STOPPED) {
362  switch (code) {
363  case 0x16: // P@
364  motherBoard.getMSXCliComm().printWarning(
365  "ejecting laserdisc");
366  eject(time);
367  break;
368  case 0x17: // P+
369  play(time);
370  break;
371  }
372 
373  // During playing, playing will be acked if not repeated
374  // within less than 115ms
375  } else {
376  // FIXME: while seeking, only a small subset of buttons work
377  bool nonseekack = true;
378 
379  switch (code) {
380  case 0x5f:
381  seekState = SEEK_WAIT;
382  seekNum = 0;
383  stillOnWaitFrame = false;
384  nonseekack = false;
385  break;
386  case 0x41:
387  seekState = SEEK_FRAME;
388  seekNum = 0;
389  break;
390  case 0x40:
391  seekState = SEEK_CHAPTER;
392  seekNum = 0;
393  nonseekack = video->chapter(0) != 0;
394  break;
395  case 0x00:
396  case 0x01:
397  case 0x02:
398  case 0x03:
399  case 0x04:
400  case 0x05:
401  case 0x06:
402  case 0x07:
403  case 0x08:
404  case 0x09:
405  seekNum = seekNum * 10 + code;
406  break;
407  case 0x42:
408  switch (seekState) {
409  case SEEK_FRAME:
410  seekState = SEEK_NONE;
411  seekFrame(seekNum % 100000, time);
412  nonseekack = false;
413  break;
414  case SEEK_CHAPTER:
415  seekState = SEEK_NONE;
416  seekChapter(seekNum % 100, time);
417  nonseekack = false;
418  break;
419  case SEEK_WAIT:
420  seekState = SEEK_NONE;
421  waitFrame = seekNum % 100000;
422  if (waitFrame >= 101 && waitFrame < 200) {
423  auto frame = video->chapter(
424  int(waitFrame - 100));
425  if (frame) waitFrame = frame;
426  }
427  PRT_DEBUG("Wait frame set to " << std::dec <<
428  waitFrame);
429  break;
430  default:
431  seekState = SEEK_NONE;
432  break;
433  }
434  break;
435  case 0x45: // Clear "X+"
436  if (seekState != SEEK_NONE && seekNum != 0) {
437  seekNum = 0;
438  } else {
439  seekState = SEEK_NONE;
440  seekNum = 0;
441  }
442  waitFrame = 0;
443  break;
444  case 0x18: // P/
445  pause(time);
446  nonseekack = false;
447  break;
448  case 0x17: // P+
449  play(time);
450  nonseekack = false;
451  break;
452  case 0x16: // P@ (stop/eject)
453  stop(time);
454  nonseekack = false;
455  break;
456  case 0xff:
457  nonseekack = false;
458  seekState = SEEK_NONE;
459  break;
460  case 0x54: // S+ (frame step forward)
461  if (seekState == SEEK_WAIT) {
462  stillOnWaitFrame = true;
463  } else {
464  stepFrame(true);
465  }
466  break;
467  case 0x50: // S- (frame step backwards)
468  stepFrame(false);
469  break;
470  case 0x55: // M- (multispeed backwards)
471  // Not supported
472  motherBoard.getMSXCliComm().printWarning(
473  "The Laserdisc player received a command to "
474  "play backwards (M-). This is currently not "
475  "supported.");
476  nonseekack = false;
477  break;
478  case 0x58: // M+ (multispeed forwards)
479  playerState = PLAYER_MULTISPEED;
480  setFrameStep();
481  break;
482  case 0x46: // C- (play slower)
483  if (playingSpeed >= SPEED_STEP1) {
484  playingSpeed--;
485  frameStep = 1; // FIXME: is this correct?
486  }
487  break;
488  case 0x47: // C+ (play faster)
489  if (playingSpeed <= SPEED_X2) {
490  playingSpeed++;
491  frameStep = 1; // FIXME: is this correct?
492  }
493  break;
494  default:
495  motherBoard.getMSXCliComm().printWarning(
496  "The Laserdisc player received an unknown "
497  "command 0x" + StringOp::toHexString(code, 2));
498  nonseekack = false;
499  break;
500  }
501 
502  if (nonseekack) {
503  // All ACKs for operations which do not
504  // require seeking
505  setAck(time, 46);
506  }
507  }
508 }
509 
510 void LaserdiscPlayer::executeUntil(EmuTime::param time, int userdata)
511 {
512  updateStream(time);
513 
514  switch (userdata) {
515  case ACK:
516  if (seeking && playerState == PLAYER_PLAYING) {
517  sampleClock.advance(time);
518  }
519 
520  if (seeking) {
521  PRT_DEBUG("Laserdisc: seek complete");
522  }
523 
524  ack = false;
525  seeking = false;
526  PRT_DEBUG("Laserdisc: ACK cleared");
527  break;
528  case ODD_FRAME:
529  if (!video || video->getFrameRate() != 60)
530  break;
531 
532  case EVEN_FRAME:
533  if ((playerState != PLAYER_STOPPED) &&
534  (currentFrame > video->getFrames())) {
535  playerState = PLAYER_STOPPED;
536  }
537 
538  if (RawFrame* rawFrame = renderer->getRawFrame()) {
539  renderer->frameStart(time);
540 
541  if (isVideoOutputAvailable(time)) {
542  auto frame = currentFrame;
543  if (video->getFrameRate() == 60) {
544  frame *= 2;
545  if (userdata == ODD_FRAME) {
546  frame--;
547  }
548  }
549 
550  video->getFrameNo(*rawFrame, frame);
551 
552  if (userdata == EVEN_FRAME) {
553  nextFrame(time);
554  }
555  } else {
556  renderer->drawBlank(0, 128, 196);
557  }
558  renderer->frameEnd();
559  }
560 
561  // Update throttling
562  loadingIndicator->update(seeking || sampleReads > 500);
563  sampleReads = 0;
564 
565  if (userdata == EVEN_FRAME) {
566  scheduleDisplayStart(time);
567  }
568  }
569 
570  if (userdata == EVEN_FRAME || userdata == ODD_FRAME) {
571  // Processing of the remote control happens at each frame
572  // (even and odd, so at 59.94Hz)
573  if (remoteProtocol == IR_NEC) {
574  if (remoteExecuteDelayed) {
575  remoteButtonNEC(remoteCode, time);
576  }
577 
578  if (++remoteVblanksBack > 6) {
579  remoteProtocol = IR_NONE;
580  }
581  }
582  remoteExecuteDelayed = false;
583  }
584 }
585 
586 void LaserdiscPlayer::setFrameStep()
587 {
588  switch (playingSpeed) {
589  case SPEED_X3:
590  case SPEED_X2:
591  case SPEED_X1:
592  frameStep = 1;
593  break;
594  case SPEED_1IN2:
595  frameStep = 2;
596  break;
597  case SPEED_1IN4:
598  frameStep = 4;
599  break;
600  case SPEED_1IN8:
601  frameStep = 8;
602  break;
603  case SPEED_1IN16:
604  frameStep = 16;
605  break;
606  case SPEED_STEP1:
607  frameStep = 30;
608  break;
609  case SPEED_STEP3:
610  frameStep = 90;
611  break;
612  }
613 }
614 
615 void LaserdiscPlayer::nextFrame(EmuTime::param time)
616 {
617  if (waitFrame && waitFrame == currentFrame) {
618  PRT_DEBUG("LaserdiscPlayer: wait frame " << std::dec <<
619  waitFrame << " reached");
620 
621  // Leave ACK raised until the next command
622  ack = true;
623  waitFrame = 0;
624 
625  if (stillOnWaitFrame) {
626  playingFromSample = getCurrentSample(time);
627  playerState = PLAYER_STILL;
628  stillOnWaitFrame = false;
629  }
630  }
631 
632  if (playerState == PLAYER_MULTISPEED) {
633  if (--frameStep) {
634  return;
635  }
636 
637  switch (playingSpeed) {
638  case SPEED_X3:
639  currentFrame += 3;
640  break;
641  case SPEED_X2:
642  currentFrame += 2;
643  break;
644  default:
645  currentFrame += 1;
646  break;
647  }
648  setFrameStep();
649  } else if (playerState == PLAYER_PLAYING) {
650  currentFrame++;
651  }
652 
653  // freeze if stop frame
654  if ((playerState == PLAYER_PLAYING || playerState == PLAYER_MULTISPEED)
655  && video->stopFrame(currentFrame)) {
656  PRT_DEBUG("LaserdiscPlayer: stopFrame " << std::dec <<
657  currentFrame << " reached");
658 
659  playingFromSample = getCurrentSample(time);
660  playerState = PLAYER_STILL;
661  }
662 }
663 
664 void LaserdiscPlayer::setImageName(string newImage, EmuTime::param time)
665 {
666  stop(time);
667  oggImage = Filename(std::move(newImage), UserFileContext());
668  video = make_unique<OggReader>(oggImage, motherBoard.getMSXCliComm());
669 
670  unsigned inputRate = video->getSampleRate();
671  sampleClock.setFreq(inputRate);
672  if (inputRate != getInputRate()) {
673  setInputRate(inputRate);
674  createResampler();
675  }
676 }
677 
678 int LaserdiscPlayer::signalEvent(const std::shared_ptr<const Event>& event)
679 {
680  if (event->getType() == OPENMSX_BOOT_EVENT && video) {
681  autoRun();
682  }
683  return 0;
684 }
685 
686 void LaserdiscPlayer::autoRun()
687 {
688  if (!autoRunSetting->getBoolean()) {
689  return;
690  }
691 
692  string var = "::auto_run_ld_counter";
693  string command =
694  "if ![info exists " + var + "] { set " + var + " 0 }\n"
695  "incr " + var + "\n"
696  "after time 2 \"if $" + var + "==\\$" + var + " { "
697  "type 1CALLLD\\\\r }\"";
698 
699  try {
700  motherBoard.getCommandController().executeCommand(command);
701  } catch (CommandException& e) {
702  motherBoard.getMSXCliComm().printWarning(
703  "Error executing loading instruction for AutoRun: " +
704  e.getMessage() + "\n Please report a bug.");
705  }
706 }
707 
708 void LaserdiscPlayer::generateChannels(int** buffers, unsigned num)
709 {
710  // Single channel device: replace content of buffers[0] (not add to it).
711  if (playerState != PLAYER_PLAYING || seeking || (muteLeft && muteRight)) {
712  buffers[0] = nullptr;
713  return;
714  }
715 
716  unsigned pos = 0;
717  size_t currentSample;
718 
719  if (unlikely(!sampleClock.before(start))) {
720  // Before playing of sounds begins
721  EmuDuration duration = sampleClock.getTime() - start;
722  unsigned len = duration.getTicksAt(video->getSampleRate());
723  if (len >= num) {
724  buffers[0] = nullptr;
725  return;
726  }
727 
728  for (; pos < len; ++pos) {
729  buffers[0][pos * 2 + 0] = 0;
730  buffers[0][pos * 2 + 1] = 0;
731  }
732 
733  currentSample = playingFromSample;
734  } else {
735  currentSample = getCurrentSample(start);
736  }
737 
738  unsigned drift = video->getSampleRate() / 30;
739 
740  if (currentSample > (lastPlayedSample + drift) ||
741  (currentSample + drift) < lastPlayedSample) {
742  PRT_DEBUG("Laserdisc audio drift: " << std::dec <<
743  lastPlayedSample << ' ' << currentSample);
744  lastPlayedSample = currentSample;
745  }
746 
747  int left = stereoMode == RIGHT ? 1 : 0;
748  int right = stereoMode == LEFT ? 0 : 1;
749 
750  while (pos < num) {
751  const AudioFragment* audio = video->getAudio(lastPlayedSample);
752 
753  if (!audio) {
754  if (pos == 0) {
755  buffers[0] = nullptr;
756  break;
757  } else for (; pos < num; ++pos) {
758  buffers[0][pos * 2 + 0] = 0;
759  buffers[0][pos * 2 + 1] = 0;
760  }
761  } else {
762  auto offset = unsigned(lastPlayedSample - audio->position);
763  unsigned len = std::min(audio->length - offset, num - pos);
764 
765  // maybe muting should be moved out of the loop?
766  for (unsigned i = 0; i < len; ++i, ++pos) {
767  buffers[0][pos * 2 + 0] = muteLeft ? 0 :
768  int(audio->pcm[left][offset + i] * 65536.f);
769  buffers[0][pos * 2 + 1] = muteRight ? 0 :
770  int(audio->pcm[right][offset + i] * 65536.f);
771  }
772 
773  lastPlayedSample += len;
774  }
775  }
776 }
777 
778 bool LaserdiscPlayer::updateBuffer(unsigned length, int* buffer,
779  EmuTime::param time)
780 {
781  bool result = ResampledSoundDevice::updateBuffer(length, buffer, time);
782  start = time; // current end-time is next start-time
783  return result;
784 }
785 
786 void LaserdiscPlayer::setMuting(bool left, bool right, EmuTime::param time)
787 {
788  updateStream(time);
789  PRT_DEBUG("Laserdisc::setMuting L:" << (left ? "on" : "off")
790  << " R:" << (right ? "on" : "off"));
791  muteLeft = left;
792  muteRight = right;
793 }
794 
795 void LaserdiscPlayer::play(EmuTime::param time)
796 {
797  PRT_DEBUG("Laserdisc::Play");
798 
799  if (video) {
800  updateStream(time);
801 
802  if (seeking) {
803  // Do not ACK
804  PRT_DEBUG("play while seeking");
805  } else if (playerState == PLAYER_STOPPED) {
806  // Disk needs to spin up, which takes 9.6s on
807  // my Pioneer LD-92000. Also always seek to
808  // beginning (confirmed on real MSX and LD)
809  video->seek(1, 0);
810  lastPlayedSample = 0;
811  playingFromSample = 0;
812  currentFrame = 1;
813  // Note that with "fullspeedwhenloading" this
814  // should be reduced to.
815  setAck(time, 9600);
816  seekState = SEEK_NONE;
817  seeking = true;
818  waitFrame = 0;
819  stereoMode = STEREO;
820  playingSpeed = SPEED_1IN4;
821  } else if (playerState == PLAYER_PLAYING) {
822  // If Play command is issued while the player
823  // is already playing, then if no ACK is sent then
824  // Astron Belt will send LD1100 commands
825  setAck(time, 46);
826  } else if (playerState == PLAYER_MULTISPEED) {
827  // Should be hearing stuff again
828  playingFromSample = (currentFrame - 1ll) * 1001ll *
829  video->getSampleRate() / 30000ll;
830  sampleClock.advance(time);
831  setAck(time, 46);
832  } else {
833  // STILL or PAUSED
834  sampleClock.advance(time);
835  setAck(time, 46);
836  }
837  playerState = PLAYER_PLAYING;
838  }
839 }
840 
841 size_t LaserdiscPlayer::getCurrentSample(EmuTime::param time)
842 {
843  switch(playerState) {
844  case PLAYER_PAUSED:
845  case PLAYER_STILL:
846  return playingFromSample;
847  default:
848  return playingFromSample + sampleClock.getTicksTill(time);
849  }
850 }
851 
852 void LaserdiscPlayer::pause(EmuTime::param time)
853 {
854  if (playerState != PLAYER_STOPPED) {
855  PRT_DEBUG("Laserdisc::Pause");
856 
857  updateStream(time);
858 
859  if (playerState == PLAYER_PLAYING) {
860  playingFromSample = getCurrentSample(time);
861  } else if (playerState == PLAYER_MULTISPEED) {
862  playingFromSample = (currentFrame - 1ll) * 1001ll *
863  video->getSampleRate() / 30000ll;
864  sampleClock.advance(time);
865  }
866 
867  playerState = PLAYER_PAUSED;
868  setAck(time, 46);
869  }
870 }
871 
872 void LaserdiscPlayer::stop(EmuTime::param time)
873 {
874  if (playerState != PLAYER_STOPPED) {
875  PRT_DEBUG("Laserdisc::Stop");
876 
877  updateStream(time);
878 
879  playerState = PLAYER_STOPPED;
880  }
881 }
882 
883 void LaserdiscPlayer::eject(EmuTime::param time)
884 {
885  stop(time);
886  video.reset();
887 }
888 
889 // Step one frame forwards or backwards. The frame will be visible and
890 // we won't be playing afterwards
891 void LaserdiscPlayer::stepFrame(bool forwards)
892 {
893  bool needseek = false;
894 
895  // Note that on real hardware, the screen goes dark momentarily
896  // if you try to step before the first frame or after the last one
897  if (playerState == PLAYER_STILL) {
898  if (forwards) {
899  if (currentFrame < video->getFrames()) {
900  currentFrame++;
901  }
902  } else {
903  if (currentFrame > 1) {
904  currentFrame--;
905  needseek = true;
906  }
907  }
908  }
909 
910  playerState = PLAYER_STILL;
911  int64_t samplePos = (currentFrame - 1ll) * 1001ll *
912  video->getSampleRate() / 30000ll;
913  playingFromSample = samplePos;
914 
915  if (needseek) {
916  if (video->getFrameRate() == 60)
917  video->seek(currentFrame * 2, samplePos);
918  else
919  video->seek(currentFrame, samplePos);
920  }
921 }
922 
923 void LaserdiscPlayer::seekFrame(size_t toframe, EmuTime::param time)
924 {
925  if (playerState != PLAYER_STOPPED) {
926  PRT_DEBUG("Laserdisc::SeekFrame " << std::dec << toframe);
927 
928  if (seeking) {
929  PRT_DEBUG("FIXME: seek command while still seeking");
930  }
931 
932  if (video) {
933  updateStream(time);
934 
935  if (toframe <= 0) {
936  toframe = 1;
937  }
938 
939  if (toframe > video->getFrames()) {
940  toframe = video->getFrames();
941  }
942 
943  // Seek time needs to be emulated correctly since
944  // e.g. Astron Belt does not wait for the seek
945  // to complete, it simply assumes a certain
946  // delay.
947  //
948  // This calculation is based on measurements on
949  // a Pioneer LD-92000.
950  auto dist = abs(int64_t(toframe) - int64_t(currentFrame));
951  int seektime; // time in ms
952 
953  if (dist < 1000) {
954  seektime = dist + 300;
955  } else {
956  seektime = 1800 + dist / 12;
957  }
958 
959  int64_t samplePos = (toframe - 1ll) * 1001ll *
960  video->getSampleRate() / 30000ll;
961 
962  if (video->getFrameRate() == 60)
963  video->seek(toframe * 2, samplePos);
964  else
965  video->seek(toframe, samplePos);
966 
967  playerState = PLAYER_STILL;
968  playingFromSample = samplePos;
969  currentFrame = toframe;
970 
971  // Seeking clears the frame to wait for
972  waitFrame = 0;
973 
974  seeking = true;
975  setAck(time, seektime);
976  }
977  }
978 }
979 
980 void LaserdiscPlayer::seekChapter(int chapter, EmuTime::param time)
981 {
982  if (playerState != PLAYER_STOPPED) {
983  if (video) {
984  auto frameno = video->chapter(chapter);
985  if (!frameno) {
986  return;
987  }
988  seekFrame(frameno, time);
989  }
990  }
991 }
992 
994 {
995  // Here we should return the value of the sample on the
996  // right audio channel, ignoring muting (this is done in the MSX)
997  // but honouring the stereo mode as this is done in the
998  // Laserdisc player
999  if (playerState == PLAYER_PLAYING && !seeking) {
1000  auto sample = getCurrentSample(time);
1001  if (const AudioFragment* audio = video->getAudio(sample)) {
1002  ++sampleReads;
1003  int channel = stereoMode == LEFT ? 0 : 1;
1004  return int(audio->pcm[channel][sample - audio->position]
1005  * 32767.f);
1006  }
1007  }
1008  return 0;
1009 }
1010 
1011 bool LaserdiscPlayer::isVideoOutputAvailable(EmuTime::param time)
1012 {
1013  updateStream(time);
1014 
1015  bool videoOut;
1016  switch (playerState) {
1017  case PLAYER_PLAYING:
1018  case PLAYER_MULTISPEED:
1019  case PLAYER_STILL:
1020  videoOut = !seeking;
1021  break;
1022  default:
1023  videoOut = false;
1024  break;
1025  }
1026  ldcontrol.videoIn(videoOut);
1027 
1028  return videoOut;
1029 }
1030 
1031 void LaserdiscPlayer::preVideoSystemChange()
1032 {
1033  renderer.reset();
1034 }
1035 
1036 void LaserdiscPlayer::postVideoSystemChange()
1037 {
1038  createRenderer();
1039 }
1040 
1041 void LaserdiscPlayer::createRenderer()
1042 {
1043  Display& display = getMotherBoard().getReactor().getDisplay();
1044  renderer = RendererFactory::createLDRenderer(*this, display);
1045 }
1046 
1047 static enum_string<LaserdiscPlayer::RemoteState> RemoteStateInfo[] = {
1048  { "IDLE", LaserdiscPlayer::REMOTE_IDLE },
1049  { "HEADER_PULSE", LaserdiscPlayer::REMOTE_HEADER_PULSE },
1050  { "NEC_HEADER_SPACE", LaserdiscPlayer::NEC_HEADER_SPACE },
1051  { "NEC_BITS_PULSE", LaserdiscPlayer::NEC_BITS_PULSE },
1052  { "NEC_BITS_SPACE", LaserdiscPlayer::NEC_BITS_SPACE },
1053 };
1055 
1056 static enum_string<LaserdiscPlayer::PlayerState> PlayerStateInfo[] = {
1057  { "STOPPED", LaserdiscPlayer::PLAYER_STOPPED },
1058  { "PLAYING", LaserdiscPlayer::PLAYER_PLAYING },
1059  { "MULTISPEED", LaserdiscPlayer::PLAYER_MULTISPEED },
1060  { "PAUSED", LaserdiscPlayer::PLAYER_PAUSED },
1061  { "STILL", LaserdiscPlayer::PLAYER_STILL }
1062 };
1064 
1065 static enum_string<LaserdiscPlayer::SeekState> SeekStateInfo[] = {
1066  { "NONE", LaserdiscPlayer::SEEK_NONE },
1067  { "CHAPTER", LaserdiscPlayer::SEEK_CHAPTER },
1068  { "FRAME", LaserdiscPlayer::SEEK_FRAME },
1069  { "WAIT", LaserdiscPlayer::SEEK_WAIT }
1070 };
1072 
1073 static enum_string<LaserdiscPlayer::StereoMode> StereoModeInfo[] = {
1074  { "LEFT", LaserdiscPlayer::LEFT },
1075  { "RIGHT", LaserdiscPlayer::RIGHT },
1076  { "STEREO", LaserdiscPlayer::STEREO }
1077 };
1079 
1080 static enum_string<LaserdiscPlayer::RemoteProtocol> RemoteProtocolInfo[] = {
1081  { "NONE", LaserdiscPlayer::IR_NONE },
1082  { "NEC", LaserdiscPlayer::IR_NEC },
1083 };
1084 SERIALIZE_ENUM(LaserdiscPlayer::RemoteProtocol, RemoteProtocolInfo);
1085 
1086 // version 1: initial version
1087 // version 2: added 'stillOnWaitFrame'
1088 // version 3: reversed bit order of 'remoteBits' and 'remoteCode'
1089 template<typename Archive>
1090 void LaserdiscPlayer::serialize(Archive& ar, unsigned version)
1091 {
1092  // Serialize remote control
1093  ar.serialize("RemoteState", remoteState);
1094  if (remoteState != REMOTE_IDLE) {
1095  ar.serialize("RemoteBitNr", remoteBitNr);
1096  ar.serialize("RemoteBits", remoteBits);
1097  if (ar.versionBelow(version, 3)) {
1098  assert(ar.isLoader());
1099  remoteBits = Math::reverseNBits(remoteBits, remoteBitNr);
1100  }
1101  }
1102  ar.serialize("RemoteLastBit", remoteLastBit);
1103  ar.serialize("RemoteLastEdge", remoteLastEdge);
1104  ar.serialize("RemoteProtocol", remoteProtocol);
1105  if (remoteProtocol != IR_NONE) {
1106  ar.serialize("RemoteCode", remoteCode);
1107  if (ar.versionBelow(version, 3)) {
1108  assert(ar.isLoader());
1109  remoteCode = Math::reverseByte(remoteCode);
1110  }
1111  ar.serialize("RemoteExecuteDelayed", remoteExecuteDelayed);
1112  ar.serialize("RemoteVblanksBack", remoteVblanksBack);
1113  }
1114 
1115  // Serialize filename
1116  ar.serialize("OggImage", oggImage);
1117  if (ar.isLoader()) {
1118  sampleReads = 0;
1119  if (!oggImage.empty()) {
1120  setImageName(oggImage.getResolved(), getCurrentTime());
1121  } else {
1122  video.reset();
1123  }
1124  }
1125  ar.serialize("PlayerState", playerState);
1126 
1127  if (playerState != PLAYER_STOPPED) {
1128  // Serialize seek state
1129  ar.serialize("SeekState", seekState);
1130  if (seekState != SEEK_NONE) {
1131  ar.serialize("SeekNum", seekNum);
1132  }
1133  ar.serialize("seeking", seeking);
1134 
1135  // Playing state
1136  ar.serialize("WaitFrame", waitFrame);
1137 
1138  // This was not yet implemented in openmsx 0.8.1 and earlier
1139  if (ar.versionAtLeast(version, 2)) {
1140  ar.serialize("StillOnWaitFrame", stillOnWaitFrame);
1141  }
1142 
1143  ar.serialize("ACK", ack);
1144  ar.serialize("PlayingSpeed", playingSpeed);
1145 
1146  // Frame position
1147  ar.serialize("CurrentFrame", currentFrame);
1148  if (playerState == PLAYER_MULTISPEED) {
1149  ar.serialize("FrameStep", frameStep);
1150  }
1151 
1152  // Audio position
1153  ar.serialize("StereoMode", stereoMode);
1154  ar.serialize("FromSample", playingFromSample);
1155  ar.serialize("SampleClock", sampleClock);
1156 
1157  if (ar.isLoader()) {
1158  // If the samplerate differs, adjust accordingly
1159  if (video->getSampleRate() != sampleClock.getFreq()) {
1160  uint64_t pos = playingFromSample;
1161 
1162  pos *= video->getSampleRate();
1163  pos /= sampleClock.getFreq();
1164 
1165  playingFromSample = pos;
1166  sampleClock.setFreq(video->getSampleRate());
1167  }
1168 
1169  auto sample = getCurrentSample(getCurrentTime());
1170  if (video->getFrameRate() == 60)
1171  video->seek(currentFrame * 2, sample);
1172  else
1173  video->seek(currentFrame, sample);
1174  lastPlayedSample = sample;
1175  }
1176  }
1177 
1178  ar.template serializeBase<Schedulable>(*this);
1179 
1180  if (ar.isLoader()) {
1181  isVideoOutputAvailable(getCurrentTime());
1182  }
1183 }
1184 
1186 
1187 } // 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.
const std::string & getName() const
Definition: Completer.hh:17
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:123
bool before(EmuTime::param e) const
Checks whether this clock's last tick is or is not before the given time stamp.
Definition: DynamicClock.hh:44
MSXMotherBoard & getMotherBoard()
unsigned char byte
8 bit unsigned integer
Definition: openmsx.hh:33
void printWarning(string_ref message)
Definition: CliComm.cc:28
virtual std::string executeCommand(const std::string &command, CliConnection *connection=nullptr)=0
Execute the given command.
Represents a clock with a fixed frequency.
Definition: Clock.hh:18
Commands that directly influence the MSX state should send and events so that they can be recorded by...
void setSyncPoint(EmuTime::param timestamp, int userData=0)
Definition: Schedulable.cc:25
unsigned reverseNBits(unsigned x, unsigned bits)
Reverse the lower N bits of a given value.
Definition: Math.hh:89
void extControl(bool bit, EmuTime::param time)
unsigned getFreq() const
Returns the frequency (in Hz) at which this clock ticks.
CassettePortInterface & getCassettePort()
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
EventDistributor & getEventDistributor()
Definition: Reactor.hh:79
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:102
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:128
bool removeSyncPoint(int userData=0)
Definition: Schedulable.cc:30
LaserdiscPlayer(const HardwareConfig &hwConf, PioneerLDControl &ldcontrol)
const RawFrame * getRawFrame() const
void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:16
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:25
virtual void setLaserdiscPlayer(LaserdiscPlayer *laserdisc)=0
Set the Laserdisc Player; when the motor control is off, sound is read from the laserdisc.
This class implements a subset of the proposal for std::array_ref (proposed for the next c++ standard...
Definition: array_ref.hh:19
void detach(VideoSystemChangeListener &listener)
Definition: Display.cc:169
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: DynamicClock.hh:51
unsigned getTicksAt(unsigned freq) const
Definition: EmuDuration.hh:106
void setInputRate(unsigned sampleRate)
Definition: SoundDevice.hh:79
unsigned getInputRate() const
Definition: SoundDevice.hh:80
const std::string & getMessage() const
Definition: MSXException.hh:14
void setMuting(bool left, bool right, EmuTime::param time)
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
Definition: DynamicClock.hh:86
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:51
const EmuTime & param
Definition: EmuTime.hh:20
unique_ptr< LDRenderer > createLDRenderer(LaserdiscPlayer &ld, Display &display)
Create the Laserdisc Renderer.
CommandController & getCommandController()
LaserdiscCommand(CommandController &commandController, StateChangeDistributor &stateChangeDistributor, Scheduler &scheduler, LaserdiscPlayer &laserdiscPlayer)
void serialize(Archive &ar, unsigned version)
void addListElement(string_ref element)
Definition: TclObject.cc:110
static void completeString(std::vector< std::string > &tokens, const RANGE &possibleValues, bool caseSensitive=true)
Definition: Completer.hh:88
void attach(VideoSystemChangeListener &listener)
Definition: Display.cc:163
void videoIn(bool enabled)
commandController(commandController_)
void setString(string_ref value)
Definition: TclObject.cc:55
Used by a device to indicate when it is loading.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:802
Display & getDisplay()
Definition: Reactor.hh:83
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
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:81
#define PRT_DEBUG(mes)
Definition: openmsx.hh:69