35 LaserdiscPlayer::Command::Command(
36 CommandController& commandController_,
37 StateChangeDistributor& stateChangeDistributor_,
39 : RecordedCommand(commandController_, stateChangeDistributor_,
40 scheduler_,
"laserdiscplayer")
44 void LaserdiscPlayer::Command::execute(
48 if (tokens.
size() == 1) {
52 laserdiscPlayer.getImageName().getResolved());
53 }
else if (tokens[1] ==
"eject") {
54 checkNumArgs(tokens, 2, Prefix{2},
nullptr);
55 result =
"Ejecting laserdisc.";
56 laserdiscPlayer.eject(time);
57 }
else if (tokens[1] ==
"insert") {
58 checkNumArgs(tokens, 3,
"filename");
60 result =
"Changing laserdisc.";
61 laserdiscPlayer.setImageName(
string(tokens[2].getString()), time);
62 }
catch (MSXException& e) {
63 throw CommandException(std::move(e).getMessage());
70 string LaserdiscPlayer::Command::help(
const vector<string>& tokens)
const
72 if (tokens.size() >= 2) {
73 if (tokens[1] ==
"insert") {
74 return "Inserts the specified laserdisc image into "
75 "the laserdisc player.";
76 }
else if (tokens[1] ==
"eject") {
77 return "Eject the laserdisc.";
80 return "laserdiscplayer insert <filename> "
81 ": insert a (different) laserdisc image\n"
82 "laserdiscplayer eject "
83 ": eject the laserdisc\n";
86 void LaserdiscPlayer::Command::tabCompletion(vector<string>& tokens)
const
88 if (tokens.size() == 2) {
89 static constexpr
const char*
const extra[] = {
"eject",
"insert" };
90 completeString(tokens, extra);
91 }
else if (tokens.size() == 3 && tokens[1] ==
"insert") {
103 xml.addChild(
"sound").addChild(
"volume",
"30000");
111 , syncAck (hwConf.getMotherBoard().getScheduler())
112 , syncOdd (hwConf.getMotherBoard().getScheduler())
113 , syncEven(hwConf.getMotherBoard().getScheduler())
114 , motherBoard(hwConf.getMotherBoard())
115 , ldControl(ldControl_)
116 , laserdiscCommand(motherBoard.getCommandController(),
117 motherBoard.getStateChangeDistributor(),
118 motherBoard.getScheduler())
119 , sampleClock(EmuTime::zero())
120 , start(EmuTime::zero())
123 , remoteState(REMOTE_IDLE)
124 , remoteLastEdge(EmuTime::zero())
125 , remoteLastBit(false)
126 , remoteProtocol(IR_NONE)
129 , playerState(PLAYER_STOPPED)
131 motherBoard.getCommandController(),
"autorunlaserdisc",
132 "automatically try to run Laserdisc", true)
134 motherBoard.getReactor().getGlobalSettings().getThrottleManager())
144 scheduleDisplayStart(getCurrentTime());
158 void LaserdiscPlayer::scheduleDisplayStart(EmuTime::param time)
163 syncOdd .setSyncPoint(frameClock + 1);
164 syncEven.setSyncPoint(frameClock + 2);
180 if (remoteLastBit == bit)
return;
185 remoteLastEdge = time;
188 switch (remoteState) {
191 remoteBits = remoteBitNr = 0;
196 if (5800 <= usec && usec < 11200) {
203 if (3400 <= usec && usec < 6200) {
210 if (usec >= 380 && usec < 1070) {
217 if (1260 <= usec && usec < 4720) {
219 remoteBits |= 1 << remoteBitNr;
220 }
else if (usec < 300 || usec >= 1065) {
229 if (++remoteBitNr == 32) {
230 byte custom = ( remoteBits >> 0) & 0xff;
231 byte customCompl = (~remoteBits >> 8) & 0xff;
232 byte code = ( remoteBits >> 16) & 0xff;
233 byte codeCompl = (~remoteBits >> 24) & 0xff;
234 if (custom == customCompl &&
237 submitRemote(
IR_NEC, code);
248 void LaserdiscPlayer::submitRemote(RemoteProtocol protocol,
unsigned code)
252 if (protocol != remoteProtocol || code != remoteCode ||
253 (protocol ==
IR_NEC && (code ==
one_of(0x42u, 0x17u)))) {
254 remoteProtocol = protocol;
256 remoteVblanksBack = 0;
257 remoteExecuteDelayed =
true;
260 remoteVblanksBack = 0;
261 remoteExecuteDelayed =
false;
267 return renderer->getRawFrame();
270 void LaserdiscPlayer::setAck(EmuTime::param time,
int wait)
273 syncAck.removeSyncPoint();
278 void LaserdiscPlayer::remoteButtonNEC(
unsigned code, EmuTime::param time)
283 case 0x47: f =
"C+";
break;
284 case 0x46: f =
"C-";
break;
285 case 0x43: f =
"D+";
break;
286 case 0x4b: f =
"L+";
break;
287 case 0x49: f =
"L-";
break;
288 case 0x4a: f =
"L@";
break;
289 case 0x58: f =
"M+";
break;
290 case 0x55: f =
"M-";
break;
291 case 0x17: f =
"P+";
break;
292 case 0x16: f =
"P@";
break;
293 case 0x18: f =
"P/";
break;
294 case 0x54: f =
"S+";
break;
295 case 0x50: f =
"S-";
break;
296 case 0x45: f =
"X+";
break;
297 case 0x41: f =
'F';
break;
298 case 0x40: f =
'C';
break;
299 case 0x42: f =
"END";
break;
300 case 0x00: f =
'0';
break;
301 case 0x01: f =
'1';
break;
302 case 0x02: f =
'2';
break;
303 case 0x03: f =
'3';
break;
304 case 0x04: f =
'4';
break;
305 case 0x05: f =
'5';
break;
306 case 0x06: f =
'6';
break;
307 case 0x07: f =
'7';
break;
308 case 0x08: f =
'8';
break;
309 case 0x09: f =
'9';
break;
310 case 0x5f: f =
"WAIT FRAME";
break;
318 std::cerr <<
"LaserdiscPlayer::remote " << f <<
'\n';
320 std::cerr <<
"LaserdiscPlayer::remote unknown " << std::hex << code <<
'\n';
329 if (code ==
one_of(0x49u, 0x4au, 0x4bu)) {
349 "ejecting laserdisc");
361 bool nonseekack =
true;
367 stillOnWaitFrame =
false;
377 nonseekack = video->getChapter(0) != 0;
389 seekNum = seekNum * 10 + code;
395 seekFrame(seekNum % 100000, time);
400 seekChapter(seekNum % 100, time);
405 waitFrame = seekNum % 100000;
406 if (waitFrame >= 101 && waitFrame < 200) {
407 auto frame = video->getChapter(
408 int(waitFrame - 100));
409 if (frame) waitFrame = frame;
418 if (seekState !=
SEEK_NONE && seekNum != 0) {
444 stillOnWaitFrame =
true;
455 "The Laserdisc player received a command to "
456 "play backwards (M-). This is currently not "
465 if (playingSpeed >= SPEED_STEP1) {
471 if (playingSpeed <= SPEED_X2) {
478 "The Laserdisc player received an unknown "
479 "command 0x", hex_string<2>(code));
492 void LaserdiscPlayer::execSyncAck(EmuTime::param time)
504 void LaserdiscPlayer::execSyncFrame(EmuTime::param time,
bool odd)
508 if (!odd || (video && video->getFrameRate() == 60)) {
510 (currentFrame > video->getFrames())) {
514 if (
auto* rawFrame = renderer->getRawFrame()) {
515 renderer->frameStart(time);
517 if (isVideoOutputAvailable(time)) {
518 auto frame = currentFrame;
519 if (video->getFrameRate() == 60) {
524 video->getFrameNo(*rawFrame, frame);
530 renderer->drawBlank(0, 128, 196);
532 renderer->frameEnd();
536 loadingIndicator.
update(seeking || sampleReads > 500);
540 scheduleDisplayStart(time);
546 if (remoteProtocol ==
IR_NEC) {
547 if (remoteExecuteDelayed) {
548 remoteButtonNEC(remoteCode, time);
551 if (++remoteVblanksBack > 6) {
555 remoteExecuteDelayed =
false;
558 void LaserdiscPlayer::setFrameStep()
560 switch (playingSpeed) {
587 void LaserdiscPlayer::nextFrame(EmuTime::param time)
589 if (waitFrame && waitFrame == currentFrame) {
594 if (stillOnWaitFrame) {
595 playingFromSample = getCurrentSample(time);
597 stillOnWaitFrame =
false;
606 switch (playingSpeed) {
624 && video->stopFrame(currentFrame)) {
626 playingFromSample = getCurrentSample(time);
631 void LaserdiscPlayer::setImageName(
string newImage, EmuTime::param time)
635 video = std::make_unique<OggReader>(oggImage, motherBoard.
getMSXCliComm());
637 unsigned inputRate = video->getSampleRate();
638 sampleClock.
setFreq(inputRate);
645 int LaserdiscPlayer::signalEvent(
const std::shared_ptr<const Event>& event)
653 void LaserdiscPlayer::autoRun()
661 string var =
"::auto_run_ld_counter";
663 "if ![info exists ", var,
"] { set ", var,
" 0 }\n"
665 "after time 2 \"if $", var,
"==\\$", var,
" { "
666 "type_via_keyboard 1CALLLD\\\\r }\"");
669 }
catch (CommandException& e) {
671 "Error executing loading instruction for AutoRun: ",
672 e.getMessage(),
"\n Please report a bug.");
676 void LaserdiscPlayer::generateChannels(
float** buffers,
unsigned num)
679 if (playerState !=
PLAYER_PLAYING || seeking || (muteLeft && muteRight)) {
680 buffers[0] =
nullptr;
685 size_t currentSample;
689 EmuDuration duration = sampleClock.
getTime() - start;
690 unsigned len = duration.getTicksAt(video->getSampleRate());
692 buffers[0] =
nullptr;
696 for (; pos < len; ++pos) {
697 buffers[0][pos * 2 + 0] = 0.0f;
698 buffers[0][pos * 2 + 1] = 0.0f;
701 currentSample = playingFromSample;
703 currentSample = getCurrentSample(start);
706 unsigned drift = video->getSampleRate() / 30;
708 if (currentSample > (lastPlayedSample + drift) ||
709 (currentSample + drift) < lastPlayedSample) {
711 lastPlayedSample = currentSample;
714 int left = stereoMode ==
RIGHT ? 1 : 0;
715 int right = stereoMode ==
LEFT ? 0 : 1;
718 const AudioFragment* audio = video->getAudio(lastPlayedSample);
722 buffers[0] =
nullptr;
725 for (; pos < num; ++pos) {
726 buffers[0][pos * 2 + 0] = 0.0f;
727 buffers[0][pos * 2 + 1] = 0.0f;
731 auto offset = unsigned(lastPlayedSample - audio->position);
732 unsigned len =
std::min(audio->length - offset, num - pos);
735 for (
unsigned i = 0; i < len; ++i, ++pos) {
736 buffers[0][pos * 2 + 0] = muteLeft ? 0.0f :
737 audio->pcm[left][offset + i];
738 buffers[0][pos * 2 + 1] = muteRight ? 0.0f :
739 audio->pcm[right][offset + i];
742 lastPlayedSample += len;
747 float LaserdiscPlayer::getAmplificationFactorImpl()
const
752 bool LaserdiscPlayer::updateBuffer(
unsigned length,
float* buffer,
767 void LaserdiscPlayer::play(EmuTime::param time)
779 lastPlayedSample = 0;
780 playingFromSample = 0;
789 playingSpeed = SPEED_1IN4;
797 playingFromSample = (currentFrame - 1ll) * 1001ll *
798 video->getSampleRate() / 30000ll;
810 size_t LaserdiscPlayer::getCurrentSample(EmuTime::param time)
812 switch(playerState) {
815 return playingFromSample;
817 return playingFromSample + sampleClock.
getTicksTill(time);
821 void LaserdiscPlayer::pause(EmuTime::param time)
827 playingFromSample = getCurrentSample(time);
829 playingFromSample = (currentFrame - 1ll) * 1001ll *
830 video->getSampleRate() / 30000ll;
839 void LaserdiscPlayer::stop(EmuTime::param time)
847 void LaserdiscPlayer::eject(EmuTime::param time)
856 void LaserdiscPlayer::stepFrame(
bool forwards)
858 bool needseek =
false;
864 if (currentFrame < video->getFrames()) {
868 if (currentFrame > 1) {
876 int64_t samplePos = (currentFrame - 1ll) * 1001ll *
877 video->getSampleRate() / 30000ll;
878 playingFromSample = samplePos;
881 if (video->getFrameRate() == 60)
882 video->seek(currentFrame * 2, samplePos);
884 video->seek(currentFrame, samplePos);
888 void LaserdiscPlayer::seekFrame(
size_t toFrame, EmuTime::param time)
893 if (toFrame <= 0) toFrame = 1;
894 if (toFrame > video->getFrames()) toFrame = video->getFrames();
903 auto dist = std::abs(int64_t(toFrame) - int64_t(currentFrame));
904 int seektime = (dist < 1000)
906 : (1800 + dist / 12);
908 int64_t samplePos = (toFrame - 1ll) * 1001ll *
909 video->getSampleRate() / 30000ll;
911 if (video->getFrameRate() == 60) {
912 video->seek(toFrame * 2, samplePos);
914 video->seek(toFrame, samplePos);
917 playingFromSample = samplePos;
918 currentFrame = toFrame;
924 setAck(time, seektime);
928 void LaserdiscPlayer::seekChapter(
int chapter, EmuTime::param time)
931 auto frameNo = video->getChapter(chapter);
932 if (!frameNo)
return;
933 seekFrame(frameNo, time);
944 auto sample = getCurrentSample(time);
947 int channel = stereoMode ==
LEFT ? 0 : 1;
948 return int(audio->pcm[channel][sample - audio->position]
955 bool LaserdiscPlayer::isVideoOutputAvailable(EmuTime::param time)
959 bool videoOut = [&] {
960 switch (playerState) {
974 void LaserdiscPlayer::preVideoSystemChange()
979 void LaserdiscPlayer::postVideoSystemChange()
984 void LaserdiscPlayer::createRenderer()
990 static std::initializer_list<enum_string<LaserdiscPlayer::RemoteState>> RemoteStateInfo = {
999 static std::initializer_list<enum_string<LaserdiscPlayer::PlayerState>> PlayerStateInfo = {
1008 static std::initializer_list<enum_string<LaserdiscPlayer::SeekState>> SeekStateInfo = {
1016 static std::initializer_list<enum_string<LaserdiscPlayer::StereoMode>> StereoModeInfo = {
1023 static std::initializer_list<enum_string<LaserdiscPlayer::RemoteProtocol>> RemoteProtocolInfo = {
1033 template<
typename Archive>
1037 ar.serialize(
"RemoteState", remoteState);
1039 ar.serialize(
"RemoteBitNr", remoteBitNr,
1040 "RemoteBits", remoteBits);
1041 if (ar.versionBelow(version, 3)) {
1042 assert(ar.isLoader());
1046 ar.serialize(
"RemoteLastBit", remoteLastBit,
1047 "RemoteLastEdge", remoteLastEdge,
1048 "RemoteProtocol", remoteProtocol);
1049 if (remoteProtocol !=
IR_NONE) {
1050 ar.serialize(
"RemoteCode", remoteCode);
1051 if (ar.versionBelow(version, 3)) {
1052 assert(ar.isLoader());
1055 ar.serialize(
"RemoteExecuteDelayed", remoteExecuteDelayed,
1056 "RemoteVblanksBack", remoteVblanksBack);
1060 ar.serialize(
"OggImage", oggImage);
1061 if (ar.isLoader()) {
1063 if (!oggImage.
empty()) {
1064 setImageName(oggImage.
getResolved(), getCurrentTime());
1069 ar.serialize(
"PlayerState", playerState);
1073 ar.serialize(
"SeekState", seekState);
1075 ar.serialize(
"SeekNum", seekNum);
1077 ar.serialize(
"seeking", seeking);
1080 ar.serialize(
"WaitFrame", waitFrame);
1083 if (ar.versionAtLeast(version, 2)) {
1084 ar.serialize(
"StillOnWaitFrame", stillOnWaitFrame);
1087 ar.serialize(
"ACK", ack,
1088 "PlayingSpeed", playingSpeed);
1091 ar.serialize(
"CurrentFrame", currentFrame);
1093 ar.serialize(
"FrameStep", frameStep);
1097 ar.serialize(
"StereoMode", stereoMode,
1098 "FromSample", playingFromSample,
1099 "SampleClock", sampleClock);
1101 if (ar.isLoader()) {
1103 if (video->getSampleRate() != sampleClock.
getFreq()) {
1104 uint64_t pos = playingFromSample;
1106 pos *= video->getSampleRate();
1109 playingFromSample = pos;
1110 sampleClock.
setFreq(video->getSampleRate());
1113 auto sample = getCurrentSample(getCurrentTime());
1114 if (video->getFrameRate() == 60)
1115 video->seek(currentFrame * 2, sample);
1117 video->seek(currentFrame, sample);
1118 lastPlayedSample = sample;
1122 if (ar.versionAtLeast(version, 4)) {
1123 ar.serialize(
"syncEven", syncEven,
1125 "syncAck", syncAck);
1130 if (ar.isLoader()) {
1131 (void)isVideoOutputAvailable(getCurrentTime());