Wednesday, October 31, 2012

Sounding Off

I'm currently implementing sound in Cannonball (the name for the cross platform OutRun engine). Much like my approach to the video hardware, the sound hardware will be emulated, whilst the actual Z80 program code is converted to readable C++.

I've implemented the SDL sound layer to output the audio, hooked up emulation of the Sega PCM chip and created the interface between the main program code and sound code. I've successfully converted enough of the Z80 code to trigger basic PCM samples. The entire Z80 rom is decompiled and commented, so progress from here should be steady.

There will (eventually) be two approaches to audio in Cannonball. The first approach will purely use the original ROMs for music and sound as discussed above. The second approach will allow players to configure audio files as replacement music tracks. This will allow you to play the game with the various remixes that have been produced over the years.

Once sound is implemented, I'll open up the source code repository to public access. This will allow everyone to play with and port the source code to new platforms for non-commercial purposes.

Saturday, October 20, 2012

Z80 Program Code - Part 2

Let's take a look at the structured format of sound information. Here's a relatively straightforward example demonstrating the setup of the Checkpoint PCM sample.

; Voice 1, Checkpoint
ROM:6F91 data_voice1:    dw data_voice1_c   ; Offset to channel setup below

The sample is played through two channels simultaneously, presumably to boost its volume. 

; Voice 1, Channel Setup
ROM:6F99 data_voice1_c:  db 2               ; Number of channels
ROM:6F9A                 dw data_voice1_c1  ; Address of Channel 1 Setup
ROM:6F9C                 dw data_voice1_c2  ; Address of Channel 2 Setup

This block represents the default setup for the 32 byte block mentioned in the previous post. It's not actually 32 bytes, but the remainder of the space is padding to zero by the program code. I've cut the second entry short in the interests of space as it's very similar.

; Voice 1, Checkpoint (PCM Samples: Channel 1) - Default 0x20 area setup
ROM:6F9E data_voice1_c1: db 80h             ; Flags: Enable
ROM:6F9F                 db 1000110b        ; Flags: Mute & Channel Index
ROM:6FA0                 db 2               ; End Marker
ROM:6FA1                 dw 0
ROM:6FA3                 dw 1
ROM:6FA5                 dw data_voice1_c1p; Address of commands
ROM:6FA7                 db 0
ROM:6FA8                 db 20h            ; Offset for positioning information
ROM:6FA9                 db 0
ROM:6FAA                 db 0
ROM:6FAB                 db 0

; Voice 1, Checkpoint (PCM Samples: Channel 2)
ROM:6FAC data_voice1_c2: db 80h
; Snip: Similar to the above block
ROM:6FB9                 db 0

Here's where things gets a little interesting; what follows is a series of commands that correspond to a particular z80 routine, along with their arguments.

; Voice 1, Checkpoint (PCM Properties)
ROM:6FBA data_voice1_c1p:db 93             ; 93 = Command: PCM Set Pitch
ROM:6FBB                 db 48h            ;    value = pitch
ROM:6FBC                 db 82h            ; 82 = Command: PCM Sample Volumes
ROM:6FBD                 db 17h            ;    value = left channel vol
ROM:6FBE                 db 2Eh            ;    value = right channel vol
ROM:6FBF                 db 0DCh           ; DC = Command: Sample Index
ROM:6FC0                 db 28h            ;    value = checkpoint
ROM:6FC1                 db 99h            ; 99 = Command: PCM Finalize

ROM:6FC2 data_voice1_c2p:db 93h            ; 93 = Command: PCM Set Pitch
ROM:6FC3                 db 48h            ;    value = pitch
ROM:6FC4                 db 82h            ; 82 = Command: PCM Sample Volumes
ROM:6FC5                 db 2Eh            ;    value = left channel vol
ROM:6FC6                 db 17h            ;    value = right channel vol
ROM:6FC7                 db 0DCh           ; DC = Command: Sample Index
ROM:6FC8                 db 28h            ;    value = checkpoint
ROM:6FC9                 db 99h            ; 99 = Command: PCM Finalize

These commands index a table of routines which is as follows. Not all of these routines are used, as I imagine this area of the code is used across other Sega titles. I've highlighted in red the entries used by the above sample.

ROM:0B93 BigRoutineTable:
ROM:0B93    dw YM_Dec_Pos           ; YM: Decrement Position In Sequence (80)
ROM:0B95    dw YM_SetEndMarker      ; YM: Set End Marker. 
ROM:0B97    dw PCM_SetVol           ; PCM: Set Volume (Left & Right Channels) (82)
ROM:0B99    dw YM_Dec_Pos           ; YM: Decrement Position In Sequence (80)
ROM:0B9B    dw YM_Finalize          ; YM: End (84)
ROM:0B9D    dw YM_SetNoise          ; YM: Enable Noise Channel (85)
ROM:0B9F    dw loc_409              ; Unused?
ROM:0BA1    dw YM_SetModTab         ; YM: Enable/Disable Modulation table
ROM:0BA3    dw WriteSeqAddr
ROM:0BA5    dw SetSeqAddr           ; Set Next Sequence Address
ROM:0BA7    dw YM_GetLoopAdr        ; de = new YM loop address
ROM:0BA9    dw YM_SetNoteOffset     ; YM: Set Note/Octave Offset (8B)
ROM:0BAB    dw YM_DoLoop            ; YM: Loop Sequence Of Commands (8C)
ROM:0BAD    dw loc_46B              ; Unused?
ROM:0BAF    dw loc_471              ; Unused?
ROM:0BB1    dw YM_Enable_Correspnd  ; YM: (Unused) Enable corresponding channel (8F)
ROM:0BB3    dw YM_Disable_Correspnd ; YM: (Unused) Disable corresponding channel (90)
ROM:0BB5    dw YM_SetBlock          ; YM: Set Block - Called First When Setting Up (91)
ROM:0BB7    dw YM_DisableNoise      ; YM: Disable Noise Channel (92)
ROM:0BB9    dw PCM_SetPitch         ; PCM: Set Pitch (93)
ROM:0BBB    dw YM_MarkerData        ; FM: End Marker - Do not calculate, use value from data (94)
ROM:0BBD    dw YM_MarkerHigh        ; FM: End Marker - Set High Byte From Data (95)
ROM:0BBF    dw YM_ConnectRight      ; FM: Connect Channel to Right Speaker (96)
ROM:0BC1    dw YM_ConnectLeft       ; FM: Connect Channel to Left Speaker (97)
ROM:0BC3    dw YM_ConnectCentre     ; FM: Connect Channel to Both Speaker (98)
ROM:0BC5    dw PCM_Finalize         ; Write Commands to PCM Channel (99)

In case you were wondering the 0xDC command which is not shown in the table above triggers a separate piece of code, which also triggers drum samples and so forth in the music tracks. Anyway,  let's take a look at a simple routine - setting the pitch of a PCM sample.

ROM:03C4 PCM_SetPitch:
ROM:03C4   bit     6, (ix+1)       ; If channel is muted, don't set pitch
ROM:03C8   jp      nz, set_pitch
ROM:03CB   ld      a, a
ROM:03CC   ret
ROM:03CD set_pitch:
ROM:03CD   ld      a, (de)         ; a = New pitch (read from setup table in rom)
ROM:03CE   ld      (ix+16h), a     ; Set relevant area in 32 byte block that controls pitch
ROM:03D1   ret

The music tracks work in a similar way, but with a much longer and more complex series of commands.

Originally, I thought the Z80 might be used in a 'dumb' manner and simply stream preformatted audio data to the various chips. But its usage is much more sophisticated as demonstrated above.

Thursday, October 18, 2012

Z80 Program Code - Part 1

I've almost finished decompiling OutRun's Z80 program code, so I'll be providing high level information regarding its workings over a series of posts.

The Z80 processor controls two pieces of sound hardware; a custom Sega PCM controller and a Yamaha YM2151 FM sound chip. This was a fairly standard configuration for Sega boardsets at the time. As you'd expect, some of the Z80 program code is in fact shared with other games of the era. However, most of the code is unique and written solely with OutRun in mind.

Commands are sent to the Z80 from the master 68000 program code. The Z80's interrupt routine reads from port 0x40 and places the values received into a sequential set of locations in RAM. Commands are high level and consist of a byte corresponding to a Z80 routine. So sending 0x81 plays the 'Passing Breeze' music, whereas 0x9d triggers the 'Checkpoint' PCM sample. From the 68000's point of view, playing a sound is simple and the complexity is nicely masked.

In addition to these commands, the 68000 sends data relating to the volume and pitch of the Ferrari's engine tone. It also sends volume and panning information relating to the passing traffic. This ensures that when you drive past a vehicle, the volume of its engine is proportional to the y distance from your Ferrari and the stereo panning corresponds to the x difference.

The core loop to achieve everything is as follows:

ROM:0039 main_loop:
ROM:0039   call    DoFMTimerA      ; Wait for timer on YM2151 chip 
ROM:003C   call    ProcessCommand  ; Process Command sent by 68000
ROM:003F   call    ProcessChannels ; Run logic on individual sound channel (both YM & PCM channels)
ROM:0042   call    ProcessEngines  ; Ferrari Engine Tone & Traffic Noise
ROM:0045   call    ProcessTraffic  ; Traffic Volume, Panning, Pitch
ROM:0048   jp      main_loop

The Z80 maps the 16 channels of the PCM chip to various uses. 6 are reserved for the music's drum samples, 4 are used for sampled sound effects and the remainder are used for the Ferrari's engine sound and passing traffic. Each channel is allocated a 32 byte area of RAM by the Z80 program code, which stores its current state. This concept is extended to include the channels from the YM chip which are also allocated to these areas of RAM.

The usage of the 32 byte area of RAM differs dependent on whether it represents a YM or PCM channel. The area contains everything from basics including volume and pitch for PCM samples through to complex YM configuration including positional information within the current block of audio commands, section loop counters and the address of the next data block. This 32 byte block is used as a starting point to configure the separate PCM RAM area, which has a different format and to program the YM's registers.

Next time, I'll explain the interpreted language stored within the Z80 code. This is used by the Z80 to program the sound hardware. And you'll see how the music and sound effects are actually stored as an interpreted sequence of commands that call functions within the code.

Monday, September 24, 2012

Raspberry Pi Progress

Here's a screenshot of the OutRun engine running on a Raspberry Pi. The display is being output on X11 via SSH. It hasn't been tried on the native Pi display yet. 


I've decided to name the OutRun engine Cannonball, after the 1981 Cannonball Run film that Yu Suzuki was inspired by. 

Friday, September 07, 2012

Cross Platform Support

Thanks to everyone who tried the recent Visual Studio based build for Windows. The plan is to ensure this becomes a true cross platform project, not tied to a particular operating system or architecture. I like to think of it as "OutRun Everywhere", making the world a better place ;) Thanks to some work from a friend, who knows far more about this sort of thing than me, this is close to becoming a reality.

The code now successfully compiles and runs on both a Mac with XCode IDE support and the Windows MinGW compiler. Other platforms will shortly follow, including Linux and the Raspberry PI if performance allows.

We're using CMake, the cross platform, open-source build system. This automates the creation of build files for a whole range of target platforms. We're able to generate builds for a whole host of platforms and configurations.

The C++ libraries have been switched to Boost. This provides an open-source cross platform alternative to facilitate portability.

I can't take credit for the above, but I have started decompilation work on the Z80 code that controls the sound hardware (both the custom Sega PCM sample player and the Yamaha YM2151). This is my main area of focus and will take some time. So far, I've commented the code that triggers the PCM samples. Ultimately, I'd love to give players the choice of emulating the original sound hardware or streaming audio from disk, similar to the approach used by the Sega Saturn conversion.

Monday, August 27, 2012

OutRun C++ Engine Tech Demo 2

At last, here's a new demo of the OutRun engine. The entire game is ported, aside from sound and the service mode.

In terms of functionality, this release is bare bones. There is no menu system yet, all options are hardcoded and you have to play the game windowed.

It would be great if you could report bugs (and I'm sure there will be plenty) in the comments below. Please verify any subtleties against MAME for reference.


Stats:

  • Conversion time from decompiled code: 1 year 10 months.
  • Estimated ratio of time spent coding vs. debugging: 1:5
  • Road rendering code: 1500 lines
  • Ferrari handling & rendering: 1680 lines
  • Code to render level objects: 1050 lines
  • General sprite handling code: 890 lines
  • Traffic handling code: 675 lines
  • Code to handle crash routines: 1450 lines

Keys:

  • Cursors: Steering
  • Z: Accelerate
  • X: Brake
  • Space: Gear Change
  • 5: Insert Coin
  • 1: Start
  • F1: Pause
  • F2: Advance a frame when paused (useful for observing visual problems)
  • F3: Toggle/Freeze timers. (i.e. infinite time)
In a future revision, there will be options to custom the controls and the analogue sensitivity. I find MAME's default setup too twitchy, so you'll find the steering a little more heavy in comparison. 


Enhancements over original:

In a future revision, enhancements will be optional and there will be a menu toggle to enable/disable them.


Requirements:


Download:

Thursday, August 23, 2012

End Sequence Bugs

Here's another bug in the original game; the leftmost two end sequences have a palette issue which causes the male sprite to suddenly change in appearance. In the screenshot below, we're approaching the goal banner.


Then, as if by magic, the colour of the man's top changes to light blue!


But don't worry, on reaching the finish line it's back to dark blue again. 


The game code swaps to an animation playing routine at the point of the colour change. It should be seamless. But it seems as if 2 of the 5 end routines are configured incorrectly. 

Why am I noticing such trivial details? Because the engine is now complete (minus sound and test mode). Expect a test release in the next week! 

In other news, I updated the Philko bootleg page with some new pictures.