31static std::string_view getLaserDiscPlayerName()
33 return "laserdiscplayer";
38LaserdiscPlayer::Command::Command(
39 CommandController& commandController_,
40 StateChangeDistributor& stateChangeDistributor_,
41 Scheduler& scheduler_)
42 : RecordedCommand(commandController_, stateChangeDistributor_,
43 scheduler_, getLaserDiscPlayerName())
47void LaserdiscPlayer::Command::execute(
48 std::span<const TclObject> tokens, TclObject& result, EmuTime::param time)
50 auto& laserdiscPlayer =
OUTER(LaserdiscPlayer, laserdiscCommand);
51 if (tokens.size() == 1) {
54 result.addListElement(
tmpStrCat(getName(),
':'),
55 laserdiscPlayer.getImageName().getResolved());
56 }
else if (tokens[1] ==
"eject") {
57 checkNumArgs(tokens, 2, Prefix{2},
nullptr);
58 result =
"Ejecting laserdisc.";
59 laserdiscPlayer.eject(time);
60 }
else if (tokens[1] ==
"insert") {
61 checkNumArgs(tokens, 3,
"filename");
63 result =
"Changing laserdisc.";
64 laserdiscPlayer.setImageName(
string(tokens[2].getString()), time);
65 }
catch (MSXException& e) {
66 throw CommandException(std::move(e).getMessage());
73string LaserdiscPlayer::Command::help(std::span<const TclObject> tokens)
const
75 if (tokens.size() >= 2) {
76 if (tokens[1] ==
"insert") {
77 return "Inserts the specified laserdisc image into "
78 "the laserdisc player.";
79 }
else if (tokens[1] ==
"eject") {
80 return "Eject the laserdisc.";
83 return "laserdiscplayer insert <filename> "
84 ": insert a (different) laserdisc image\n"
85 "laserdiscplayer eject "
86 ": eject the laserdisc\n";
89void LaserdiscPlayer::Command::tabCompletion(std::vector<string>& tokens)
const
91 if (tokens.size() == 2) {
92 using namespace std::literals;
93 static constexpr std::array extra = {
"eject"sv,
"insert"sv};
94 completeString(tokens, extra);
95 }
else if (tokens.size() == 3 && tokens[1] ==
"insert") {
102static constexpr unsigned DUMMY_INPUT_RATE = 44100;
104LaserdiscPlayer::LaserdiscPlayer(
107 "Laserdisc Player", 1, DUMMY_INPUT_RATE, true)
108 , syncAck (hwConf.getMotherBoard().getScheduler())
109 , syncOdd (hwConf.getMotherBoard().getScheduler())
110 , syncEven(hwConf.getMotherBoard().getScheduler())
111 , motherBoard(hwConf.getMotherBoard())
112 , ldControl(ldControl_)
113 , laserdiscCommand(motherBoard.getCommandController(),
114 motherBoard.getStateChangeDistributor(),
115 motherBoard.getScheduler())
117 motherBoard.getCommandController(),
"autorunlaserdisc",
118 "automatically try to run Laserdisc", true)
120 motherBoard.getReactor().getGlobalSettings().getThrottleManager())
129 scheduleDisplayStart(getCurrentTime());
133 auto* result = doc.allocateElement(
string(getLaserDiscPlayerName()).c_str());
134 result->setFirstChild(doc.allocateElement(
"sound"))
135 ->setFirstChild(doc.allocateElement(
"volume",
"30000"));
154string LaserdiscPlayer::getStateString()
const
156 switch (playerState) {
158 case STOPPED:
return "stopped";
159 case PLAYING:
return "playing";
161 case PAUSED:
return "paused";
162 case STILL:
return "still";
170 "state", getStateString());
173void LaserdiscPlayer::scheduleDisplayStart(EmuTime::param time)
178 syncOdd .setSyncPoint(frameClock + 1);
179 syncEven.setSyncPoint(frameClock + 2);
195 if (remoteLastBit == bit)
return;
200 remoteLastEdge = time;
203 switch (remoteState) {
207 remoteBits = remoteBitNr = 0;
212 if (5800 <= usec && usec < 11200) {
219 if (3400 <= usec && usec < 6200) {
226 if (usec >= 380 && usec < 1070) {
233 if (1260 <= usec && usec < 4720) {
235 remoteBits |= 1 << remoteBitNr;
236 }
else if (usec < 300 || usec >= 1065) {
245 if (++remoteBitNr == 32) {
246 auto custom = narrow_cast<byte>(( remoteBits >> 0) & 0xff);
247 auto customCompl = narrow_cast<byte>((~remoteBits >> 8) & 0xff);
248 auto code = narrow_cast<byte>(( remoteBits >> 16) & 0xff);
249 auto codeCompl = narrow_cast<byte>((~remoteBits >> 24) & 0xff);
250 if (custom == customCompl &&
264void LaserdiscPlayer::submitRemote(RemoteProtocol protocol, uint8_t code)
268 if (protocol != remoteProtocol || code != remoteCode ||
270 remoteProtocol = protocol;
272 remoteVblanksBack = 0;
273 remoteExecuteDelayed =
true;
276 remoteVblanksBack = 0;
277 remoteExecuteDelayed =
false;
283 return renderer->getRawFrame();
286void LaserdiscPlayer::setAck(EmuTime::param time,
int wait)
289 syncAck.removeSyncPoint();
294void LaserdiscPlayer::remoteButtonNEC(uint8_t code, EmuTime::param time)
299 case 0x47: f =
"C+";
break;
300 case 0x46: f =
"C-";
break;
301 case 0x43: f =
"D+";
break;
302 case 0x4b: f =
"L+";
break;
303 case 0x49: f =
"L-";
break;
304 case 0x4a: f =
"L@";
break;
305 case 0x58: f =
"M+";
break;
306 case 0x55: f =
"M-";
break;
307 case 0x17: f =
"P+";
break;
308 case 0x16: f =
"P@";
break;
309 case 0x18: f =
"P/";
break;
310 case 0x54: f =
"S+";
break;
311 case 0x50: f =
"S-";
break;
312 case 0x45: f =
"X+";
break;
313 case 0x41: f =
'F';
break;
314 case 0x40: f =
'C';
break;
315 case 0x42: f =
"END";
break;
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;
334 std::cerr <<
"LaserdiscPlayer::remote " << f <<
'\n';
336 std::cerr <<
"LaserdiscPlayer::remote unknown " << std::hex << code <<
'\n';
345 if (code ==
one_of(0x49u, 0x4au, 0x4bu)) {
365 "ejecting laserdisc");
377 bool nonSeekAck =
true;
384 stillOnWaitFrame =
false;
394 nonSeekAck = video->getChapter(0) != 0;
406 seekNum = seekNum * 10 + code;
412 seekFrame(seekNum % 100000, time);
417 seekChapter(seekNum % 100, time);
422 waitFrame = seekNum % 100000;
423 if (waitFrame >= 101 && waitFrame < 200) {
424 auto frame = video->getChapter(
425 int(waitFrame - 100));
426 if (frame) waitFrame = frame;
435 if (seekState !=
NONE && seekNum != 0) {
460 if (seekState ==
WAIT) {
461 stillOnWaitFrame =
true;
472 "The Laserdisc player received a command to "
473 "play backwards (M-). This is currently not "
482 if (playingSpeed >= SPEED_STEP1) {
488 if (playingSpeed <= SPEED_X2) {
495 "The Laserdisc player received an unknown "
496 "command 0x", hex_string<2>(code));
509void LaserdiscPlayer::execSyncAck(EmuTime::param time)
521void LaserdiscPlayer::execSyncFrame(EmuTime::param time,
bool odd)
525 if (!odd || (video && video->getFrameRate() == 60)) {
527 (currentFrame > video->getFrames())) {
531 if (
auto* rawFrame = renderer->getRawFrame()) {
532 renderer->frameStart(time);
534 if (isVideoOutputAvailable(time)) {
536 auto frame = currentFrame;
537 if (video->getFrameRate() == 60) {
542 video->getFrameNo(*rawFrame, frame);
548 renderer->drawBlank(0, 128, 196);
550 renderer->frameEnd();
554 loadingIndicator.
update(seeking || sampleReads > 500);
558 scheduleDisplayStart(time);
565 if (remoteExecuteDelayed) {
566 remoteButtonNEC(remoteCode, time);
569 if (++remoteVblanksBack > 6) {
573 remoteExecuteDelayed =
false;
576void LaserdiscPlayer::setFrameStep()
578 switch (playingSpeed) {
605void LaserdiscPlayer::nextFrame(EmuTime::param time)
608 if (waitFrame && waitFrame == currentFrame) {
613 if (stillOnWaitFrame) {
614 playingFromSample = getCurrentSample(time);
616 stillOnWaitFrame =
false;
625 switch (playingSpeed) {
637 }
else if (playerState ==
PLAYING) {
644 if (video->stopFrame(currentFrame)) {
646 playingFromSample = getCurrentSample(time);
652void LaserdiscPlayer::setImageName(
string newImage, EmuTime::param time)
658 unsigned inputRate = video->getSampleRate();
659 sampleClock.
setFreq(inputRate);
666bool LaserdiscPlayer::signalEvent(
const Event& event)
674void LaserdiscPlayer::autoRun()
682 string var =
"::auto_run_ld_counter";
684 "if ![info exists ", var,
"] { set ", var,
" 0 }\n"
686 "after time 2 \"if $", var,
"==\\$", var,
" { "
687 "type_via_keyboard 1CALLLD\\\\r }\"");
690 }
catch (CommandException& e) {
692 "Error executing loading instruction for AutoRun: ",
693 e.getMessage(),
"\n Please report a bug.");
697void LaserdiscPlayer::generateChannels(std::span<float*> buffers,
unsigned num)
700 assert(buffers.size() == 1);
702 buffers[0] =
nullptr;
708 size_t currentSample;
710 if (!sampleClock.
before(start)) [[unlikely]] {
712 EmuDuration duration = sampleClock.
getTime() - start;
713 unsigned len = duration.getTicksAt(video->getSampleRate());
715 buffers[0] =
nullptr;
719 for (; pos < len; ++pos) {
720 buffers[0][pos * 2 + 0] = 0.0f;
721 buffers[0][pos * 2 + 1] = 0.0f;
724 currentSample = playingFromSample;
726 currentSample = getCurrentSample(start);
729 unsigned drift = video->getSampleRate() / 30;
731 if (currentSample > (lastPlayedSample + drift) ||
732 (currentSample + drift) < lastPlayedSample) {
734 lastPlayedSample = currentSample;
741 const AudioFragment* audio = video->getAudio(lastPlayedSample);
745 buffers[0] =
nullptr;
748 for (; pos < num; ++pos) {
749 buffers[0][pos * 2 + 0] = 0.0f;
750 buffers[0][pos * 2 + 1] = 0.0f;
754 auto offset = unsigned(lastPlayedSample - audio->position);
755 unsigned len = std::min(audio->length - offset, num - pos);
758 for (
unsigned i = 0; i < len; ++i, ++pos) {
759 buffers[0][pos * 2 + 0] = muteLeft ? 0.0f :
760 audio->pcm[left][offset + i];
761 buffers[0][pos * 2 + 1] = muteRight ? 0.0f :
762 audio->pcm[right][offset + i];
765 lastPlayedSample += len;
770float LaserdiscPlayer::getAmplificationFactorImpl()
const
775bool LaserdiscPlayer::updateBuffer(
size_t length,
float* buffer,
790void LaserdiscPlayer::play(EmuTime::param time)
799 }
else if (playerState ==
STOPPED) {
804 lastPlayedSample = 0;
805 playingFromSample = 0;
814 playingSpeed = SPEED_1IN4;
815 }
else if (playerState ==
PLAYING) {
822 playingFromSample = (currentFrame - 1LL) * 1001LL *
823 video->getSampleRate() / 30000LL;
834size_t LaserdiscPlayer::getCurrentSample(EmuTime::param time)
836 switch(playerState) {
839 return playingFromSample;
841 return playingFromSample + sampleClock.
getTicksTill(time);
845void LaserdiscPlayer::pause(EmuTime::param time)
848 if (playerState ==
STOPPED)
return;
853 playingFromSample = getCurrentSample(time);
855 playingFromSample = (currentFrame - 1LL) * 1001LL *
856 video->getSampleRate() / 30000LL;
864void LaserdiscPlayer::stop(EmuTime::param time)
872void LaserdiscPlayer::eject(EmuTime::param time)
881void LaserdiscPlayer::stepFrame(
bool forwards)
884 bool needSeek =
false;
891 if (currentFrame < video->getFrames()) {
895 if (currentFrame > 1) {
903 auto samplePos = (currentFrame - 1LL) * 1001LL *
904 video->getSampleRate() / 30000LL;
905 playingFromSample = samplePos;
908 if (video->getFrameRate() == 60)
909 video->seek(currentFrame * 2, samplePos);
911 video->seek(currentFrame, samplePos);
915void LaserdiscPlayer::seekFrame(
size_t toFrame, EmuTime::param time)
921 if (toFrame <= 0) toFrame = 1;
922 if (toFrame > video->getFrames()) toFrame = video->getFrames();
931 auto dist = std::abs(int64_t(toFrame) - int64_t(currentFrame));
932 int seekTime = (dist < 1000)
933 ? narrow<int>(dist + 300)
934 : narrow<int>(1800 + dist / 12);
936 auto samplePos = (toFrame - 1LL) * 1001LL *
937 video->getSampleRate() / 30000LL;
939 if (video->getFrameRate() == 60) {
940 video->seek(toFrame * 2, samplePos);
942 video->seek(toFrame, samplePos);
945 playingFromSample = samplePos;
946 currentFrame = toFrame;
952 setAck(time, seekTime);
955void LaserdiscPlayer::seekChapter(
int chapter, EmuTime::param time)
959 auto frameNo = video->getChapter(chapter);
960 if (!frameNo)
return;
961 seekFrame(frameNo, time);
972 auto sample = getCurrentSample(time);
976 return narrow_cast<int16_t>
977 (audio->pcm[channel][sample - audio->position]
984bool LaserdiscPlayer::isVideoOutputAvailable(EmuTime::param time)
988 bool videoOut = [&] {
989 switch (playerState) {
1003void LaserdiscPlayer::preVideoSystemChange() noexcept
1008void LaserdiscPlayer::postVideoSystemChange() noexcept
1013void LaserdiscPlayer::createRenderer()
1016 renderer = RendererFactory::createLDRenderer(*
this, display);
1019static constexpr std::initializer_list<enum_string<LaserdiscPlayer::RemoteState>> RemoteStateInfo = {
1028static constexpr std::initializer_list<enum_string<LaserdiscPlayer::PlayerState>> PlayerStateInfo = {
1037static constexpr std::initializer_list<enum_string<LaserdiscPlayer::SeekState>> SeekStateInfo = {
1045static constexpr std::initializer_list<enum_string<LaserdiscPlayer::StereoMode>> StereoModeInfo = {
1052static constexpr std::initializer_list<enum_string<LaserdiscPlayer::RemoteProtocol>> RemoteProtocolInfo = {
1062template<
typename Archive>
1066 ar.serialize(
"RemoteState", remoteState);
1068 ar.serialize(
"RemoteBitNr", remoteBitNr,
1069 "RemoteBits", remoteBits);
1070 if (ar.versionBelow(version, 3)) {
1071 assert(Archive::IS_LOADER);
1075 ar.serialize(
"RemoteLastBit", remoteLastBit,
1076 "RemoteLastEdge", remoteLastEdge,
1077 "RemoteProtocol", remoteProtocol);
1079 ar.serialize(
"RemoteCode", remoteCode);
1080 if (ar.versionBelow(version, 3)) {
1081 assert(Archive::IS_LOADER);
1084 ar.serialize(
"RemoteExecuteDelayed", remoteExecuteDelayed,
1085 "RemoteVblanksBack", remoteVblanksBack);
1089 ar.serialize(
"OggImage", oggImage);
1090 if constexpr (Archive::IS_LOADER) {
1092 if (!oggImage.
empty()) {
1093 setImageName(oggImage.
getResolved(), getCurrentTime());
1098 ar.serialize(
"PlayerState", playerState);
1102 ar.serialize(
"SeekState", seekState);
1104 ar.serialize(
"SeekNum", seekNum);
1106 ar.serialize(
"seeking", seeking);
1109 ar.serialize(
"WaitFrame", waitFrame);
1112 if (ar.versionAtLeast(version, 2)) {
1113 ar.serialize(
"StillOnWaitFrame", stillOnWaitFrame);
1116 ar.serialize(
"ACK", ack,
1117 "PlayingSpeed", playingSpeed);
1120 ar.serialize(
"CurrentFrame", currentFrame);
1122 ar.serialize(
"FrameStep", frameStep);
1126 ar.serialize(
"StereoMode", stereoMode,
1127 "FromSample", playingFromSample,
1128 "SampleClock", sampleClock);
1130 if constexpr (Archive::IS_LOADER) {
1132 if (video->getSampleRate() != sampleClock.
getFreq()) {
1133 uint64_t pos = playingFromSample;
1135 pos *= video->getSampleRate();
1138 playingFromSample = pos;
1139 sampleClock.
setFreq(video->getSampleRate());
1142 auto sample = getCurrentSample(getCurrentTime());
1143 if (video->getFrameRate() == 60)
1144 video->seek(currentFrame * 2, sample);
1146 video->seek(currentFrame, sample);
1147 lastPlayedSample = sample;
1151 if (ar.versionAtLeast(version, 4)) {
1152 ar.serialize(
"syncEven", syncEven,
1154 "syncAck", syncAck);
1159 if constexpr (Archive::IS_LOADER) {
1160 (void)isVideoOutputAvailable(getCurrentTime());
bool getBoolean() const noexcept
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=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 getMediaInfo(TclObject &result) override
This method gets called when information is required on the media inserted in the media slot of the p...
void serialize(Archive &ar, unsigned version)
void update(bool newState)
Called by the device to indicate its loading state may have changed.
void update(UpdateType type, std::string_view name, std::string_view value) override
void registerMediaInfo(std::string_view name, MediaInfoProvider &provider)
Register and unregister providers of media info, for the media info topic.
CommandController & getCommandController()
void unregisterMediaInfo(MediaInfoProvider &provider)
MSXCliComm & getMSXCliComm()
ReverseManager & getReverseManager()
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(size_t 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.
void addDictKeyValues(Args &&... args)
static XMLDocument & getStaticDocument()
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.
This file implemented 3 utility functions:
EventType getType(const Event &event)
const FileContext & userFileContext()
FileContext userFileContext(string_view savePath)
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
TemporaryString tmpStrCat(Ts &&... ts)