33 LaserdiscPlayer::Command::Command(
34 CommandController& commandController_,
35 StateChangeDistributor& stateChangeDistributor_,
37 : RecordedCommand(commandController_, stateChangeDistributor_,
38 scheduler_,
"laserdiscplayer")
42 void LaserdiscPlayer::Command::execute(
46 if (tokens.
size() == 1) {
50 laserdiscPlayer.getImageName().getResolved());
51 }
else if (tokens[1] ==
"eject") {
52 checkNumArgs(tokens, 2, Prefix{2},
nullptr);
53 result =
"Ejecting laserdisc.";
54 laserdiscPlayer.eject(time);
55 }
else if (tokens[1] ==
"insert") {
56 checkNumArgs(tokens, 3,
"filename");
58 result =
"Changing laserdisc.";
59 laserdiscPlayer.setImageName(
string(tokens[2].getString()), time);
60 }
catch (MSXException& e) {
61 throw CommandException(std::move(e).getMessage());
70 if (tokens.
size() >= 2) {
71 if (tokens[1] ==
"insert") {
72 return "Inserts the specified laserdisc image into "
73 "the laserdisc player.";
74 }
else if (tokens[1] ==
"eject") {
75 return "Eject the laserdisc.";
78 return "laserdiscplayer insert <filename> "
79 ": insert a (different) laserdisc image\n"
80 "laserdiscplayer eject "
81 ": eject the laserdisc\n";
84 void LaserdiscPlayer::Command::tabCompletion(std::vector<string>& tokens)
const
86 if (tokens.size() == 2) {
87 using namespace std::literals;
88 static constexpr std::array extra = {
"eject"sv,
"insert"sv};
89 completeString(tokens, extra);
90 }
else if (tokens.size() == 3 && tokens[1] ==
"insert") {
103 , syncAck (hwConf.getMotherBoard().getScheduler())
104 , syncOdd (hwConf.getMotherBoard().getScheduler())
105 , syncEven(hwConf.getMotherBoard().getScheduler())
106 , motherBoard(hwConf.getMotherBoard())
107 , ldControl(ldControl_)
108 , laserdiscCommand(motherBoard.getCommandController(),
109 motherBoard.getStateChangeDistributor(),
110 motherBoard.getScheduler())
111 , sampleClock(EmuTime::zero())
112 , start(EmuTime::zero())
115 , remoteState(REMOTE_IDLE)
116 , remoteLastEdge(EmuTime::zero())
117 , remoteLastBit(false)
118 , remoteProtocol(IR_NONE)
121 , playerState(PLAYER_STOPPED)
123 motherBoard.getCommandController(),
"autorunlaserdisc",
124 "automatically try to run Laserdisc", true)
126 motherBoard.getReactor().getGlobalSettings().getThrottleManager())
136 scheduleDisplayStart(getCurrentTime());
140 auto* result = doc.allocateElement(
"laserdiscplayer");
141 result->setFirstChild(doc.allocateElement(
"sound"))
142 ->setFirstChild(doc.allocateElement(
"volume",
"30000"));
156 void LaserdiscPlayer::scheduleDisplayStart(EmuTime::param time)
161 syncOdd .setSyncPoint(frameClock + 1);
162 syncEven.setSyncPoint(frameClock + 2);
178 if (remoteLastBit == bit)
return;
183 remoteLastEdge = time;
186 switch (remoteState) {
189 remoteBits = remoteBitNr = 0;
194 if (5800 <= usec && usec < 11200) {
201 if (3400 <= usec && usec < 6200) {
208 if (usec >= 380 && usec < 1070) {
215 if (1260 <= usec && usec < 4720) {
217 remoteBits |= 1 << remoteBitNr;
218 }
else if (usec < 300 || usec >= 1065) {
227 if (++remoteBitNr == 32) {
228 byte custom = ( remoteBits >> 0) & 0xff;
229 byte customCompl = (~remoteBits >> 8) & 0xff;
230 byte code = ( remoteBits >> 16) & 0xff;
231 byte codeCompl = (~remoteBits >> 24) & 0xff;
232 if (custom == customCompl &&
235 submitRemote(
IR_NEC, code);
246 void LaserdiscPlayer::submitRemote(RemoteProtocol protocol,
unsigned code)
250 if (protocol != remoteProtocol || code != remoteCode ||
251 (protocol ==
IR_NEC && (code ==
one_of(0x42u, 0x17u)))) {
252 remoteProtocol = protocol;
254 remoteVblanksBack = 0;
255 remoteExecuteDelayed =
true;
258 remoteVblanksBack = 0;
259 remoteExecuteDelayed =
false;
265 return renderer->getRawFrame();
268 void LaserdiscPlayer::setAck(EmuTime::param time,
int wait)
271 syncAck.removeSyncPoint();
276 void LaserdiscPlayer::remoteButtonNEC(
unsigned code, EmuTime::param time)
281 case 0x47: f =
"C+";
break;
282 case 0x46: f =
"C-";
break;
283 case 0x43: f =
"D+";
break;
284 case 0x4b: f =
"L+";
break;
285 case 0x49: f =
"L-";
break;
286 case 0x4a: f =
"L@";
break;
287 case 0x58: f =
"M+";
break;
288 case 0x55: f =
"M-";
break;
289 case 0x17: f =
"P+";
break;
290 case 0x16: f =
"P@";
break;
291 case 0x18: f =
"P/";
break;
292 case 0x54: f =
"S+";
break;
293 case 0x50: f =
"S-";
break;
294 case 0x45: f =
"X+";
break;
295 case 0x41: f =
'F';
break;
296 case 0x40: f =
'C';
break;
297 case 0x42: f =
"END";
break;
298 case 0x00: f =
'0';
break;
299 case 0x01: f =
'1';
break;
300 case 0x02: f =
'2';
break;
301 case 0x03: f =
'3';
break;
302 case 0x04: f =
'4';
break;
303 case 0x05: f =
'5';
break;
304 case 0x06: f =
'6';
break;
305 case 0x07: f =
'7';
break;
306 case 0x08: f =
'8';
break;
307 case 0x09: f =
'9';
break;
308 case 0x5f: f =
"WAIT FRAME";
break;
316 std::cerr <<
"LaserdiscPlayer::remote " << f <<
'\n';
318 std::cerr <<
"LaserdiscPlayer::remote unknown " << std::hex << code <<
'\n';
327 if (code ==
one_of(0x49u, 0x4au, 0x4bu)) {
347 "ejecting laserdisc");
359 bool nonseekack =
true;
365 stillOnWaitFrame =
false;
375 nonseekack = video->getChapter(0) != 0;
387 seekNum = seekNum * 10 + code;
393 seekFrame(seekNum % 100000, time);
398 seekChapter(seekNum % 100, time);
403 waitFrame = seekNum % 100000;
404 if (waitFrame >= 101 && waitFrame < 200) {
405 auto frame = video->getChapter(
406 int(waitFrame - 100));
407 if (frame) waitFrame = frame;
416 if (seekState !=
SEEK_NONE && seekNum != 0) {
442 stillOnWaitFrame =
true;
453 "The Laserdisc player received a command to "
454 "play backwards (M-). This is currently not "
463 if (playingSpeed >= SPEED_STEP1) {
469 if (playingSpeed <= SPEED_X2) {
476 "The Laserdisc player received an unknown "
477 "command 0x", hex_string<2>(code));
490 void LaserdiscPlayer::execSyncAck(EmuTime::param time)
502 void LaserdiscPlayer::execSyncFrame(EmuTime::param time,
bool odd)
506 if (!odd || (video && video->getFrameRate() == 60)) {
508 (currentFrame > video->getFrames())) {
512 if (
auto* rawFrame = renderer->getRawFrame()) {
513 renderer->frameStart(time);
515 if (isVideoOutputAvailable(time)) {
516 auto frame = currentFrame;
517 if (video->getFrameRate() == 60) {
522 video->getFrameNo(*rawFrame, frame);
528 renderer->drawBlank(0, 128, 196);
530 renderer->frameEnd();
534 loadingIndicator.
update(seeking || sampleReads > 500);
538 scheduleDisplayStart(time);
544 if (remoteProtocol ==
IR_NEC) {
545 if (remoteExecuteDelayed) {
546 remoteButtonNEC(remoteCode, time);
549 if (++remoteVblanksBack > 6) {
553 remoteExecuteDelayed =
false;
556 void LaserdiscPlayer::setFrameStep()
558 switch (playingSpeed) {
585 void LaserdiscPlayer::nextFrame(EmuTime::param time)
587 if (waitFrame && waitFrame == currentFrame) {
592 if (stillOnWaitFrame) {
593 playingFromSample = getCurrentSample(time);
595 stillOnWaitFrame =
false;
604 switch (playingSpeed) {
622 && video->stopFrame(currentFrame)) {
624 playingFromSample = getCurrentSample(time);
629 void LaserdiscPlayer::setImageName(
string newImage, EmuTime::param time)
635 unsigned inputRate = video->getSampleRate();
636 sampleClock.
setFreq(inputRate);
643 int LaserdiscPlayer::signalEvent(
const Event& event) noexcept
651 void LaserdiscPlayer::autoRun()
659 string var =
"::auto_run_ld_counter";
661 "if ![info exists ", var,
"] { set ", var,
" 0 }\n"
663 "after time 2 \"if $", var,
"==\\$", var,
" { "
664 "type_via_keyboard 1CALLLD\\\\r }\"");
667 }
catch (CommandException& e) {
669 "Error executing loading instruction for AutoRun: ",
670 e.getMessage(),
"\n Please report a bug.");
674 void LaserdiscPlayer::generateChannels(
float** buffers,
unsigned num)
677 if (playerState !=
PLAYER_PLAYING || seeking || (muteLeft && muteRight)) {
678 buffers[0] =
nullptr;
683 size_t currentSample;
687 EmuDuration duration = sampleClock.
getTime() - start;
688 unsigned len = duration.getTicksAt(video->getSampleRate());
690 buffers[0] =
nullptr;
694 for (; pos < len; ++pos) {
695 buffers[0][pos * 2 + 0] = 0.0f;
696 buffers[0][pos * 2 + 1] = 0.0f;
699 currentSample = playingFromSample;
701 currentSample = getCurrentSample(start);
704 unsigned drift = video->getSampleRate() / 30;
706 if (currentSample > (lastPlayedSample + drift) ||
707 (currentSample + drift) < lastPlayedSample) {
709 lastPlayedSample = currentSample;
712 int left = stereoMode ==
RIGHT ? 1 : 0;
713 int right = stereoMode ==
LEFT ? 0 : 1;
716 const AudioFragment* audio = video->getAudio(lastPlayedSample);
720 buffers[0] =
nullptr;
723 for (; pos < num; ++pos) {
724 buffers[0][pos * 2 + 0] = 0.0f;
725 buffers[0][pos * 2 + 1] = 0.0f;
729 auto offset = unsigned(lastPlayedSample - audio->position);
730 unsigned len =
std::min(audio->length - offset, num - pos);
733 for (
unsigned i = 0; i < len; ++i, ++pos) {
734 buffers[0][pos * 2 + 0] = muteLeft ? 0.0f :
735 audio->pcm[left][offset + i];
736 buffers[0][pos * 2 + 1] = muteRight ? 0.0f :
737 audio->pcm[right][offset + i];
740 lastPlayedSample += len;
745 float LaserdiscPlayer::getAmplificationFactorImpl()
const
750 bool LaserdiscPlayer::updateBuffer(
unsigned length,
float* buffer,
765 void LaserdiscPlayer::play(EmuTime::param time)
777 lastPlayedSample = 0;
778 playingFromSample = 0;
787 playingSpeed = SPEED_1IN4;
795 playingFromSample = (currentFrame - 1ll) * 1001ll *
796 video->getSampleRate() / 30000ll;
808 size_t LaserdiscPlayer::getCurrentSample(EmuTime::param time)
810 switch(playerState) {
813 return playingFromSample;
815 return playingFromSample + sampleClock.
getTicksTill(time);
819 void LaserdiscPlayer::pause(EmuTime::param time)
825 playingFromSample = getCurrentSample(time);
827 playingFromSample = (currentFrame - 1ll) * 1001ll *
828 video->getSampleRate() / 30000ll;
837 void LaserdiscPlayer::stop(EmuTime::param time)
845 void LaserdiscPlayer::eject(EmuTime::param time)
854 void LaserdiscPlayer::stepFrame(
bool forwards)
856 bool needseek =
false;
862 if (currentFrame < video->getFrames()) {
866 if (currentFrame > 1) {
874 int64_t samplePos = (currentFrame - 1ll) * 1001ll *
875 video->getSampleRate() / 30000ll;
876 playingFromSample = samplePos;
879 if (video->getFrameRate() == 60)
880 video->seek(currentFrame * 2, samplePos);
882 video->seek(currentFrame, samplePos);
886 void LaserdiscPlayer::seekFrame(
size_t toFrame, EmuTime::param time)
891 if (toFrame <= 0) toFrame = 1;
892 if (toFrame > video->getFrames()) toFrame = video->getFrames();
901 auto dist = std::abs(int64_t(toFrame) - int64_t(currentFrame));
902 int seektime = (dist < 1000)
904 : (1800 + dist / 12);
906 int64_t samplePos = (toFrame - 1ll) * 1001ll *
907 video->getSampleRate() / 30000ll;
909 if (video->getFrameRate() == 60) {
910 video->seek(toFrame * 2, samplePos);
912 video->seek(toFrame, samplePos);
915 playingFromSample = samplePos;
916 currentFrame = toFrame;
922 setAck(time, seektime);
926 void LaserdiscPlayer::seekChapter(
int chapter, EmuTime::param time)
929 auto frameNo = video->getChapter(chapter);
930 if (!frameNo)
return;
931 seekFrame(frameNo, time);
942 auto sample = getCurrentSample(time);
945 int channel = stereoMode ==
LEFT ? 0 : 1;
946 return int(audio->pcm[channel][sample - audio->position]
953 bool LaserdiscPlayer::isVideoOutputAvailable(EmuTime::param time)
957 bool videoOut = [&] {
958 switch (playerState) {
972 void LaserdiscPlayer::preVideoSystemChange() noexcept
977 void LaserdiscPlayer::postVideoSystemChange() noexcept
982 void LaserdiscPlayer::createRenderer()
988 static constexpr std::initializer_list<enum_string<LaserdiscPlayer::RemoteState>> RemoteStateInfo = {
997 static constexpr std::initializer_list<enum_string<LaserdiscPlayer::PlayerState>> PlayerStateInfo = {
1006 static constexpr std::initializer_list<enum_string<LaserdiscPlayer::SeekState>> SeekStateInfo = {
1014 static constexpr std::initializer_list<enum_string<LaserdiscPlayer::StereoMode>> StereoModeInfo = {
1021 static constexpr std::initializer_list<enum_string<LaserdiscPlayer::RemoteProtocol>> RemoteProtocolInfo = {
1031 template<
typename Archive>
1035 ar.serialize(
"RemoteState", remoteState);
1037 ar.serialize(
"RemoteBitNr", remoteBitNr,
1038 "RemoteBits", remoteBits);
1039 if (ar.versionBelow(version, 3)) {
1040 assert(Archive::IS_LOADER);
1044 ar.serialize(
"RemoteLastBit", remoteLastBit,
1045 "RemoteLastEdge", remoteLastEdge,
1046 "RemoteProtocol", remoteProtocol);
1047 if (remoteProtocol !=
IR_NONE) {
1048 ar.serialize(
"RemoteCode", remoteCode);
1049 if (ar.versionBelow(version, 3)) {
1050 assert(Archive::IS_LOADER);
1053 ar.serialize(
"RemoteExecuteDelayed", remoteExecuteDelayed,
1054 "RemoteVblanksBack", remoteVblanksBack);
1058 ar.serialize(
"OggImage", oggImage);
1059 if constexpr (Archive::IS_LOADER) {
1061 if (!oggImage.
empty()) {
1062 setImageName(oggImage.
getResolved(), getCurrentTime());
1067 ar.serialize(
"PlayerState", playerState);
1071 ar.serialize(
"SeekState", seekState);
1073 ar.serialize(
"SeekNum", seekNum);
1075 ar.serialize(
"seeking", seeking);
1078 ar.serialize(
"WaitFrame", waitFrame);
1081 if (ar.versionAtLeast(version, 2)) {
1082 ar.serialize(
"StillOnWaitFrame", stillOnWaitFrame);
1085 ar.serialize(
"ACK", ack,
1086 "PlayingSpeed", playingSpeed);
1089 ar.serialize(
"CurrentFrame", currentFrame);
1091 ar.serialize(
"FrameStep", frameStep);
1095 ar.serialize(
"StereoMode", stereoMode,
1096 "FromSample", playingFromSample,
1097 "SampleClock", sampleClock);
1099 if constexpr (Archive::IS_LOADER) {
1101 if (video->getSampleRate() != sampleClock.
getFreq()) {
1102 uint64_t pos = playingFromSample;
1104 pos *= video->getSampleRate();
1107 playingFromSample = pos;
1108 sampleClock.
setFreq(video->getSampleRate());
1111 auto sample = getCurrentSample(getCurrentTime());
1112 if (video->getFrameRate() == 60)
1113 video->seek(currentFrame * 2, sample);
1115 video->seek(currentFrame, sample);
1116 lastPlayedSample = sample;
1120 if (ar.versionAtLeast(version, 4)) {
1121 ar.serialize(
"syncEven", syncEven,
1123 "syncAck", syncAck);
1128 if constexpr (Archive::IS_LOADER) {
1129 (void)isVideoOutputAvailable(getCurrentTime());
bool getBoolean() const noexcept
virtual void setLaserdiscPlayer(LaserdiscPlayer *laserdisc)=0
Set the Laserdisc Player; when the motor control is off, sound is read from the laserdisc.
void printWarning(std::string_view message)
Represents a clock with a fixed frequency.
virtual TclObject executeCommand(zstring_view command, CliConnection *connection=nullptr)=0
Execute the given command.
void detach(VideoSystemChangeListener &listener)
void attach(VideoSystemChangeListener &listener)
bool before(EmuTime::param e) const
Checks whether this clock's last tick is or is not before the given time stamp.
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
unsigned getFreq() const
Returns the frequency (in Hz) at which this clock ticks.
void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
static constexpr EmuDuration msec(unsigned x)
constexpr unsigned getTicksAt(unsigned freq) const
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
bool empty() const
Convenience method to test for empty filename.
const std::string & getResolved() const &
void setMuting(bool left, bool right, EmuTime::param time)
int16_t readSample(EmuTime::param time)
void extControl(bool bit, EmuTime::param time)
MSXMotherBoard & getMotherBoard()
const RawFrame * getRawFrame() const
void serialize(Archive &ar, unsigned version)
void update(bool newState)
Called by the device to indicate its loading state may have changed.
ReverseManager & getReverseManager()
CliComm & getMSXCliComm()
CommandController & getCommandController()
CassettePortInterface & getCassettePort()
void videoIn(bool enabled)
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Contains the main loop of openMSX.
EventDistributor & getEventDistributor()
bool updateBuffer(unsigned length, float *buffer, EmuTime::param time) override
Generate sample data.
static void restoreOld(Archive &ar, std::vector< Schedulable * > schedulables)
void updateStream(EmuTime::param time)
unsigned getInputRate() const
void setInputRate(unsigned sampleRate)
void unregisterSound()
Unregisters this sound device with the Mixer.
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
static XMLDocument & getStaticDocument()
constexpr index_type size() const noexcept
constexpr unsigned reverseNBits(unsigned x, unsigned bits)
Reverse the lower N bits of a given value.
constexpr uint8_t reverseByte(uint8_t a)
Reverse the bits in a byte.
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
T length(const vecN< N, T > &x)
std::string getName(KeyCode keyCode)
Translate key code to key name.
std::unique_ptr< LDRenderer > createLDRenderer(LaserdiscPlayer &ld, Display &display)
Create the Laserdisc Renderer.
This file implemented 3 utility functions:
constexpr unsigned DUMMY_INPUT_RATE
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
EventType getType(const Event &event)
FileContext userFileContext(string_view savePath)
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
TemporaryString tmpStrCat(Ts &&... ts)
std::string strCat(Ts &&...ts)