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