Friday, March 2, 2012

Part Ten - The final push

The last few months of the project were pretty unexciting. There's a certain mode that you get in, if you can see a project coming to an end. It's basically becoming a bit of a grind, where you're trying to finish off as many of the lose ends that are still left. After all, you want to get this over with, and there's no easy way of doing that.


Having been through this a few times before, I knew that the best way for me is to make a TODO list now, of every thing that still needs to be done, which had about 100 items in the end. Ticking things off that list helps in seeing the light at the end of the tunnel...

    ...
    [x] Add title screen graphics and logic
    [x] Add cheat keys
    [x] Fix Jaffar positioning in level 13
    [x] Fix shadow not running fully out of screen
    [x] Make strength indicator blink if only one left
    [x] Fix little drawing glitch when C section is overlapping top block area
    ...

One of the remaining things was the frame of the game: The title screen along with the text cards inspired by the silent movie era. I knew that I wouldn't have to reverse-engineer that code, as it would be much easier to achieve the same thing and faster using custom code. This was one of the easiest parts and very satisfying work.

I started out with STE's initial work on the background for the title screens. I did take the liberty to change it a bit to make it look more like the graphics in the PC version (mostly the border of the image).

Original title background from DOS version (by Avril Harrison)
C64 multi-color background without ornaments (by Steve Day)
For the little symbols in the corners, I decided to go with hires sprites, because the 160x200 multicolor mode used by the picture made them look really bad, and I wanted to be pixel precise where possible. STE had the idea to add his own signature in the lower left corner, instead of Avril Harrison's "ah".
With hi-res sprite overlays in the corners
At this point I was wondering if I should put my own name and copyright notice into the title screen. But doing that felt wrong somehow. So I decided to go with what Brøderbund would've made back in the days: The same logo and "a game by Jordan Mechner", which seemed appropriate.
I added versions of the background image with black outlines for white hi-res sprite overlays. Although the outlines are not pixel precise (since they're in 160x200 resolution), they're good enough when covered with the sprites to make the final image look a lot like the PC screens.


Black outlines in background image for Brøderbund logo

Sprite overlay for Brøderbund logo

The composited result

For the title cards containing the text which explains the game's story, I had a similar plan. I also wanted to make sure to get the full 320x200 graphics ported over, since any 160x200 experiment I made was looking awful.
The trick was simple, I just used raster interrupts to switch from lo-res to hi-res bitmap mode in the area where the text needs to be displayed. Since that would also affect the border on the left and right edge of the screen I simply put a multiplexed column of multi-color sprites there. It doesn't get much easier than that.

One of the text screens, without the sprites used for the border

I added the necessary logic and drawing code to flip through the different screens. That wasn't hard because I used the two screen buffers which were already set up for the game, but it needed some care to hide drawing glitches that were be visible, by making sure that the color RAM updates happen at the right times during the screen refresh.

To complete the flow of the title screen, I had to add the intro cutscene now. It's certainly the most complicated of all the cutscenes in the game, but fortunately I learned enough about that already, so it didn't really pose a challenge anymore. Here's the code for that cutscene:

;----------------------------------
cutsceneBeforeLevel1:
            jsr triggerVizierStanding
            jsr saveKid
            jsr triggerPrincessStanding
            jsr saveShad
            lda #$02
            jsr runCutsceneLoop
            
            ; princess waiting (just music)
            lda #$07
            ldx #$08
            jsr runCutsceneLoopWithMusic
            lda #$05
            jsr runCutsceneLoop
            
            ; princess turns around
            lda #$62
            jsr triggerShadSequence
            lda #$09
            jsr runCutsceneLoop

            ; door opens (music used as sound effect)
            lda #$08
            ldx #$00
            jsr runCutsceneLoopWithMusic
            lda #$07
            sta CutsceneDelay
            lda #$05
            jsr runCutsceneLoop
            
            ; jaffar enters
            lda #$60
            jsr triggerKidSequence
            lda #$06
            jsr runCutsceneLoop
            lda #$61
            jsr triggerKidSequence
            lda #$04
            jsr runCutsceneLoop
            
            ; jaffar stands (just music)
            lda #$09
            ldx #$0c
            jsr runCutsceneLoopWithMusic
            lda #$04
            jsr runCutsceneLoop
            
            ; jaffar approaches
            lda #$60
            jsr triggerKidSequence
            lda #$1e
            jsr runCutsceneLoop
            lda #$61
            jsr triggerKidSequence
            lda #$04
            jsr runCutsceneLoop
            
            ; jaffar stands in front of princess (tension building music)
            lda #$0a
            ldx #$19
            jsr runCutsceneLoopWithMusic
            
            ; jaffar reaching for princess
            lda #$66
            jsr triggerKidSequence
            lda #$01
            jsr runCutsceneLoop
            
            ; princess retreats
            lda #$63
            jsr triggerShadSequence
            lda #$0d
            jsr runCutsceneLoop
            
            ; jaffar spawns hour glass
            ldx #$00
            jsr initHourglass
            lda #$05
            sta ScreenFlashLength
            lda #$ff
            sta ScreenFlashFillByte
            lda #$0c
            sta CutsceneDelay
            lda #$05
            jsr runCutsceneLoop
            lda #$00
            sta CutsceneSandCounter
            lda #$0b
            ldx #$08
            jsr runCutsceneLoopWithMusic
            lda #$07
            sta CutsceneDelay
            
            ; jaffar turns around and leaves (sand starts falling)
            lda #$64
            jsr triggerKidSequence
            lda #$11
            jsr runCutsceneLoop
            ldx #$01
            jsr initHourglass
            lda #$0c
            jsr runCutsceneLoop
            
            ; princess is sad
            lda #$71
            jsr triggerShadSequence
            lda #$1c
            jsr runCutsceneLoop
            
            ; jaffar has left the screen
            lda #$0c
            sta CutsceneDelay
            lda #$0c
            ldx #$14
            jmp runCutsceneLoopWithMusic
;----------------------------------

Like in the other cutscenes, the princess uses the Shad slot, and in this case Jaffar is using the Kid slot. Since the Apple II is challenged when it comes to sound and music, you can't have animation and music at the same time. So they alternate each other. A bit of animation, then a bit of music and so on.
The timing is either determined by the number of frames passed to runCutsceneLoop (in case of an animated part) or the length of the music (in case of runCutsceneLoopWithMusic). If music is disabled, then runCutsceneLoopWithMusic just calls runCutsceneLoop and uses the X register contents as the number of frames.
For now I had no music system in place, so all I got was that alternative timing:


After I got this intro cutscene working initially, I spent some time to tweak the character positioning to match the background graphics as good as possible and to make sure that the distance between princess, vizier and the hourglass is proper, since the general layout of the screen differs a bit from the Apple II version.

Oh, but what about the music? Turns out that is a longer story...
I asked a few different musicians while working on the game, and even though they showed some interest, it quickly became apparent to me that the idea of converting music is not as appealing to a composer as writing something original is.
So after trying to get someone else to do it for some time, I hit a point where getting some music into the game became a high priority, and that's when I decided to give it a go myself.
Now I have to add that even though I know lots of things about the SID, ripped many tunes during my adolescent years and generally am a huge fan of C64 music, one thing I've never done myself is write a SID music routine. Mostly because I'm not a composer, and writing a player also typically means that you have to write an editor for it, which feels like a lot of tedious work.

So having decided to make my own music conversion (mostly as a test, because back then I was still hoping that a real musician would come forward with some work) I first did some reverse-engineering into the music player of the Apple II version, out of interest. Well, let's just say that that was funny and scary at the same time and I quickly stopped doing that. :)

I then took a closer look at the MIDI files that I had extracted from the data of the PC version. I wrote a little Python script to parse the MIDI files so I could examine the tracks and the note data they contain. One thing that was important to me was to see how many tracks the music had and if it was feasible to play it using just the three voices of the SID.
Turns out that I was really lucky there, because even though the MIDI files had more than 3 tracks, those were generally not essential to the music, e.g. most of the additional tracks were just there to make a certain instrument fatter by playing the same sequence of notes one octave down or up. Those tracks were labelled "Bass" and "Bass dbl-v" or "melody" and "melody dbl". Removing the dbl tracks from the MIDI files made each file contain just 3 tracks, and the music was still complete and easily recognizable, i.e. it didn't miss large chunks or whole instruments.
It seemed like someone had already planned it like this.



So the plan was clear. I needed to get the data out of the MIDI file and into shape for a C64 music player. And I had to write one. The first problem that came up was the issue of timing. MIDI files can have various different tempi, and they're usually specified in ticks per quarter note, and ticks can be very precise. Also they typically don't nicely line up with the 50Hz or 60Hz ticks of SID players which are driven by the screen refresh.
But since the Apple II is so restricted in the music department, it meant that I didn't have to clock my music player that way. Whenever music is supposed to play in the Apple II version, the game pauses and waits until the music is done. In the PC version there's some music overlapping the actual game play, but it's not really required. The game still works if game and music are mutually exclusive.
That meant that I could use a timer interrupt to drive the music, and the music player could change the timer count rate to have true variable tempo with precise timing, something which is usually never done with SID music.

So I started by adding code to the Python script to save out the note data in a binary format suitable for C64 use. I then implemented a simple playback engine that reads this data and plays it on the SID. I added some simple effects for the instruments, such as vibrato and pulse/filter sweeps. Nothing too fancy, but enough to make it sound better than a music program in BASIC. Luckily the music didn't use drums.

Since at this point I had no RAM left, I fit the whole player and all the music data into a single 8KB ROM bank, so it can be banked in and executed easily. The data is quite large, since there are no patterns to be reused, it is basically a stream of notes and instrument changes, with timing information.
You can listen to the result here:


Putting it all together, I still had to add code to sync the title screen updates with the music timing. I hope the result is respectable, considering it's my first SID music player... :)



I guess this kinda concludes this story. I wouldn't want to bore you with details about the three months of final testing and bug fixing that I did. Or the endless amount of pixel fine-tuning on some of the graphics (Thanks, Mikael!). Or how Conrad jumped in at the very end to provide some awesome sound effects in practically no time, using custom code.

Once again, you can download the Apple II memory dump disassembly that I built up during this project, which might be helpful to port Prince of Persia to other platforms (not necessarily just 6502-based ones). I hope you had a good time reading this, and maybe I'll have some more interesting stuff to talk about in the future. Until then, take care, and keep the C64 spirit alive.

21 comments:

  1. You're insane. That's brilliant. Thank you for writing about it!

    ReplyDelete
  2. Fantastic. Now I feel it's time to get it loaded onto my micro SD card and Ultimate 1541-II and start playing your very impressive work!

    ReplyDelete
  3. Yes, I agree, thank you so much for sharing all this! Great reading, I've been eagerly awaiting this final chapter of your pop-blog. Impressive that you landed this project fully completed - and at such a high level of thoroughness in regards the source. As on old C64 fan, this whole project makes me kindof warm inside. Hats off! :)

    ReplyDelete
  4. Fantastic work Mr Sid! And thank you for this blog explaining all the challenges you faced in the process!

    ReplyDelete
  5. Yes, fantastic work! Thanks for writing this blog, it's a great read!

    ReplyDelete
  6. Thanks so much for this series and your effort. Bravo!

    ReplyDelete
  7. "It seemed like someone had already planned it like this." Yes, it was common to make sure PC music could downgrade gracefully to support only three voices, to support the Tandy 1000 series which had a simple 3-voice soundchip.

    Congratulations on your achievement, and on documenting it for all of us to read. Outstanding!

    ReplyDelete
  8. Hi, did you contacted Jordan Mechner? I wonder what he would say...

    ReplyDelete
    Replies
    1. See the comments on the first post on this blog...

      Delete
  9. Thank you for this nice journey mr.sid. It was a real pleasure to read all these behind the scenes stuff.

    ReplyDelete
  10. Hey, have you seen that Jordan Mechner actually found the original source code of Prince of Persia?!! http://jordanmechner.com/blog/2012/03/prince-of-persia-source-code-found/
    Wow!

    ReplyDelete
  11. I just came here to talk about the source code being found as well! Are you going to grab a copy to help you with finishing touches?

    ReplyDelete
  12. Bravo. I suppose you've read the Prince of Persia author found back his sources just a couple of days ago ... I guess you're eager to have an eye on them as soon as he managed to get them out the floppies ...

    ReplyDelete
  13. https://github.com/jmechner/Prince-of-Persia-Apple-II :)

    ReplyDelete
  14. Amazing port, especially considering you were working off a reverse-engineered version. I've made a trainer for PoP, based on the Apple // version running in emulation. http://java-ace.svn.sourceforge.net/viewvc/java-ace/jace/src/jace/cheat/PrinceOfPersiaCheats.java?revision=188&view=markup

    Please use as much of this as you want. :-D

    ReplyDelete
  15. It was fascinating and I really appreciate you taking the time to document your journey.


    Martin Fensome
    Surrey, B.C.
    Canada

    ReplyDelete
  16. This comment has been removed by the author.

    ReplyDelete
  17. Why dont publish the c64 pop source code?

    ReplyDelete
  18. Too bad you didn't release the source code. Any chance of getting all the fish? This would definitively be the real /final push/ :-)

    Great job and post, anyway.

    --J

    ReplyDelete
  19. Thanks for sharing your whole Prince Of Persia story...was an awesome read mate, well done!! :)

    ReplyDelete
  20. Fantastic read. So many challenges along the journey I can identify with being a C64 6510 veteran myself - especially the "Final Push" and the To Do list approach. I've also just discovered you've ported DK Jr from the Atari - is there a similar blog for that one?

    Great Result. Great Blog. Big Thanks!

    G

    ReplyDelete