Tuesday, August 14, 2018

Commanding Laserdiscs

Coming soon...
For the past couple evenings, I've been poking at controlling video playback programmatically... I was inspired by Kevin Savetz' recent project to resurrect the Rollercoaster Apple ][ Laserdisc game.  He got it working, fixed it, enhanced it to work for the DVD version of the game, and was showing it off at KansasFest.

My first quick hack, as seen before on this blog, was to screen-scrape an Infocom game (Hitchhiker's Guide to the Galaxy), and based on text from that, force VLC to play back certain frames or tracks of video from the Hitchhiker's Guide TV show.

I've been poking at actually making Rollercoster be playable inside the browser for my next trick. I'm basing it all on the source code, which Kevin has made available to us, and using a HD transfer of the movie, which hopefully is the same edit of the film as the Discovision Laserdisc.

There will be more on this later, but the short version is that I'm using jsbasic to run Applesoft Basic right in the browser (no emulation of an Apple ][ ), simulating the Super Serial port (6551 ACIA chip), capturing the BASIC game's laserdisc commands being sent to said serial port, and then using that information to control a fakeo-laserdisc player, which is just an HTML 5 video player.  I'm still working through all of this.  The cool thing is that when I'm done with this, it could be a great test system for people wanting to make more LD-AppleII games.  As long as you use video files that can be properly frame-associated with real discs, it should be a drop-in kind of thing.

NOTE: if I learn more information, I'll note it in this post.  I'm really green when it comes to Apple II hardware, and really Apple II programming in general. This is really my first deep-dive into Apple II serial hardware and specifics of Applesoft BASIC!

Super Serial card

The Super Serial for this game is in slot 2, and is fitted with a 6551 ACIA serial interface chip. Writes to the ACIA get sent out immediately, they are not buffered.  The ACIA has one bit on its status port 0x08 or b00001000 that goes high when there's a byte to read.  The LDP will send a 'R' when its current task is complete.

The Super Serial card, being in slot 2 means that its four registers are at decimal locations:

  • 49320 - Data 
    • Read from here to read characters sent to the Apple
    • Write to this to send out characters to the LDP
  • 49321 - Status
    • Read from here to get error codes, 
    • Read and mask off 0x08 to get the "is a character ready to be read from the LDP?
  • 49322 - Command
  • 49323 - Control
    • These two are used to configure the port, for baud rate, parity, etc.  For this project, I'm simulating the behavior of the device at a very high level, so i basically ignore all of this stuff.
    • The real hardware runs at 4800 baud, 8-N-1
This means that to write a character (newline) out to the LDP:
  • POKE 49320,13

LDP Simulation

Anyway. there's been some interest in others wanting to simulate the Laserdisc player (LDP) using other methods (using VLC for example), so I thought I'd just do a quick post to explain my current understanding of the specific commands used by the game. You can look up specifics of the protocol in the LD-V4400 User's Manual, or in the DVD-V8000 User's Manual  All commands are two-letter sequences, uppercase (although the manual says that it accepts lowercase as well.) and can have a number BEFORE the opcode.

There are a lot of commands that these can handle, but these are the commands that we care about for this game:

Go into "frame mode".  This means that any addresses will be in number of frames from the start of the disc.  Frame 0 is the first frame on the disc, the final frame is dependent on the duration of the disc.  CAV discs are about 30 minutes of content. at 29.97FPS (NTSC), that is roughly 54,000 frames.

(address) SE
Seek to the address (frame number) and go into still-frame mode.  Reading from the ACIA, 

(address) PL
Play, and go to still-frame mode when the address (frame number) is reached.

Additionally, the game's command strings use '/' to indicate a carriage return (CR) character, which is sent as decimal value 13.  The player will then execute the command, and will send back an "R" character (decimal 82) when it completes.

Let's go through a couple real examples...

  • Switch the LDP into frame mode
  • Seek to frame 2818, and still-frame there
  • LDP will send "R" when it has completed.
AKA: Still frame on frame 2818, will send "R" when done.

  • Switch the LDP into frame mode
  • From the current position, play until the frame number reaches 2134, and still-frame there
  • LDP will send an "R" when it has completed
AKA: Play until frame 2134, will send "R" when it reaches there.

  • Switch the LDP into frame mode
  • Seek to frame 6726, and stillframe
  • LDP will send "R" when ready
  • (switch to frame mode)
  • Play until frame 6959, and then stillframe
  • LDP will send "R" when ready
AKA: Play from frame 6726 through 6959, send "R" when done.

Game implementations

The serial port configuration and setup is at line 31000.  The sequence it does is that it POKEs 11 to 49322, and 28 to 49323, which sets up the serial port configuration (baud, parity, etc).  It also does something with CHR$(4); "PR#2" or "PR#0".  I'm honestly not entirely sure what those are for, something with sending data to the serial card.

The BASIC code to send the command strings is at line 40000, and the command string is stored in VC$.  So in the program you'll see stuff like:
34000 VC$ = "FR2818SE/": GOSUB 40000 
34011 VC$ = "FR6726SE/FR6959PL/": GOSUB 40000
You already know what these do from the above examples!

The LDP communication code at 40000 is:
40020  FOR I = 1 TO  LEN (VC$)
40030  IF  MID$ (VC$,I,1) = "/" THEN  POKE 49320,13: WAIT 49321,8:J =  PEEK (49320): GOTO 40060
40040  POKE 49320, ASC ( MID$ (VC$,I,1))
40060  NEXT I
40070  RETURN
Or, in human readable terms:
  • if "DISC" is set 0, then there's no VDP, so just return
  • For each character of the command string:
    • if the character is a '/', then send the VDP a carriage return, and wait for "R" response
    • otherwise just send the current command string character to the VDP

The code also uses the "WAIT" command, which was not implemented in the javascript library I was using.  This command essentially blocks, and waits until the conditions are met.  In the above code it essentially is PEEKing at 49321 (ACIA status) for bit 8 to be set; for a character to be ready to read in.. It then reads the character to variable J, which is then ignored. ;)  But it knows the LDP is done with the task.

No comments:

Post a Comment