Xeen Wiki
Advertisement

Xeen supports a variety of audio drivers to output wave sound and MIDI music. Each of these drivers is stored in the game's primary resource CC file. These sound drivers are also responsible for providing timing functions to the game engine.

Driver Filenames[]

Each driver file is named according to a standard: the filename begins with a prefix which indicates the kind of sound card the driver is for, and the filename ends with the letters "MUS" for a music (MIDI) driver or "SND" for a wave sound driver.

Internally, the game uses the terms "speak", "sound", and "music" somewhat interchangeably. "Sound" and "music" generally refer to the MIDI music driver, while "speak" commands refer to the wave sound driver.

Card Driver Name Details
Adlib ADxxx
Sound Blaster BLASTxxx
Roland Canvas / GS CANxxx
Covox Sound Master 2 COVXSND Speech only
PC Speaker IBMxxx
Sound Blaster Pro PROxxx
Roland LAPC-1 / MT-32 ROLxxx
Pro-Audio Spectrum SPECxxx
Wave Blaster WAVEMUS MIDI only
None NULLxxx

These driver files consist entirely of standard x86 executable code, and work by exposing "jump" commands at specified locations. The drivers are loaded in to memory when the game starts, and the game jumps to a specific offset in the driver to perform any given function. For example, to initialize the music driver, the game will jump to seg:0000 where "seg" is the segment the driver has been loaded to.

The entry points listed below exist in all drivers. The initialization routines use the following variables, all of which (except seg_music) are read directly from the configuration file:

  • soundAddr is the address of the sound card (default is 0x220)
  • musicAddr is the address of the music card (default is 0x388)
  • irq is the sound card's IRQ (default is 7)
  • volumeSet is a boolean indicating whether or not the game may set the sound card's default volume levels (only used by some drivers)
  • seg_music is the segment address that the driver has been loaded in to
  • soundDMA is the DMA setting of the sound card (default is 1)

Music Driver Functions[]

Offset Function Details
seg:0000 Initialize
seg:0003 Terminate
seg:0006 Song
seg:0009 FX
seg:000C Interrupt
seg:000F nop
seg:0011 Set Instrument

"Initialize" Function[]

This function takes four uint16 arguments: musicAddr, volumeSet, initString, soundAddr.

initString is a pointer to a theoretical string of MIDI commands that will be passed to the MPU on startup. In practice, it is always 0, and should be safely ignored.

"Terminate" Function[]

Shuts down the driver.

"Song" Function[]

The "song" function is called with a uint16 command argument, and any number of further arguments depending on the command being executed.

Stop Music[]

Command: 0000h
Argument: none

Stops the music. Called when the music flag is flipped off in the control panel.

Restart[]

Command: 0001h
Argument: none

Restarts the currently playing music to the beginning of the file

SetVolume[]

Command: 0100h
Argument: Volume Level (uint16)

This function is called with two volume levels: 48 and 95, which indicate the probable range of accepted values is 0 to 100. This function is called in the in-game cutscenes, specifically the Sphinx, Golem, Reaper, and Dwarf events.

Set "Something" (Song playing?)[]

Command: >0001h but <=00FFh (00CF used everywhere, apparently)
Argument: none

Sets the "Something" returned by command FFE0h to the command value.

Return "Something" (Song playing?)[]

Command: FFE0h
Argument: none

Returns a byte variable of as yet unknown use. Suspected to be set true when a sound is playing. Can be set by the above Set command.

Play Song[]

Command: >0100h
Argument: ignored (uint16), stop (uint16)

The command value is actually the segment the song has been loaded to in memory. It abuses the segmented nature of real mode by starting the music right at the start of a segment, so the offset 0 is the start of the file. Takes two arguments. The first is ignored, the second is a boolean value which tells the music player that it needs to stop the current note on a channel before starting the next one. Both arguments are always set to 0 anyway.

"FX" Function[]

Unexamined.

"Interrupt" Function[]

This is an external link to the music interrupt function which is triggered 72.8 times per second. There should never be a need to call this directly.

"nop"[]

Two zero bytes that do nothing.

"Set Instrument" Function[]

An external link to a "Set Instrument" function similar to the one called in the course of music playback. Two uint16 arguments are channel and instrument number. This is not called anywhere in any known code, and may be a holdover from some debugging functions.

Playing Music and Sound Effects[]

Execution[]

In order to play music, the "Song" function must be called with the segment the .M file has been loaded into. The function will set the offset to 0, set the "musicPlaying" boolean to true, and count down timer to 0. Similar in the call to "FX" function, setting the "fxPlaying" flag to true, and the respective count down timer to 0.

At the start of every interrupt, first check boolean variable "musicPlaying". If true, there's currently a .M file loaded, and the interrupt will attempt to process it.

Assuming music is playing, it then decrements and checks the music delay timer. If it is zero, it proceeds to read the next command from the file and execute it. It will then loop back to the top, reading and executing commands until it encounters command 0x1, which sets the delay timer, and exits the interrupt.

If the "musicPlaying" flag is false, or the music delay timer is not yet zero, it proceeds to the FX execution code.

In the FX execution code, it checks "fxPlaying" flag. If false, it simply exits the interrupt as there is no music and no FX commands to execute.

If the "fxPlaying" flag is true, it decrements and checks the FX delay timer. Similar to the music delay timer, if it is zero, it proceeds to read and execute commands, starting from where it last left off.

If the FX delay timer is not yet zero, it exits the interrupt.

Command list[]

These are commands used to control the music and sound effects (not to be confused with the digital sound voices)

Music (.M) file Commands[]

Commands from the .M file are played sequentially unless otherwise stated.

Command Use Channel Data Bytes FM/MIDI Description
0x0 no none both Records current position and next instruction into some sort of stack for later use (?) and skips to a new position
0x1 yes none both Sets the count down timer delay, performs some cleanup, and exits the interrupt. If "channel" == 0, read the next byte and set the delay to that, else it uses the "channel" value.
0x2 yes 26 both Defines an instrument that can be played. The "channel" nibble is actually the instrument number, to be referenced later to assign instruments to channels. Detail below.
0x3 no none none This does nothing but return to the top for the next instruction.
0x4 yes 2 MIDI sets the pitch wheel for the given channel. Only the first data byte is relevant: 128 is center and will produce the normal tone. The second data byte is ignored. FM simply skips 2 bytes.
0x5 no 2 none This does nothing but skip two bytes and return to the top for the next instruction.
0x6 yes 1 MIDI Changes the pan (left/right) of the selected channel. FM music is mono, so simply reads the byte and returns to the top for the next instruction. 128 is center.
0x7 no none none This does nothing but return to the top for the next instruction.
0x8 yes none both This fades a note off in MIDI. May not behave exactly the same in FM.
0x9 yes 2 both Starts channel playing a specific note. The first data byte is the note, the second is the fade in rate. Note details below. Fade in rate is used only for MIDI.
0xA yes 2 both Sets volume for selected channel as a percentage of max volume. If MIDI and first byte is 0, the second byte is the volume, else this command is ignored. If FM and first byte is 5, the second byte is the volume, else this command is ignored. Each driver
0xB no X MIDI Sends an unknown number of bytes from the file directly to the MIDI port, terminating when 0xF7 is read. FM simply loops the file, ignoring bytes until 0xF7 is read.
0xC yes 1 both Sets channel to play the given instrument number, as defined in instruction 0x2 above. Data byte must not exceed 0x0F (15)
0xD yes none FM Clears (sets to zero) something. Unknown purpose: byte_111[channel]
0xE yes 3 FM Sets some things. Unknown purpose: byte_160[channel] = data[0], word_169[channel] = data[2] . data[1] (read two bytes to AX and xchang), byte_111[channel] = 1, byte_17B[channel] = 0xFF. MIDI simply advances file by 3.
0xF yes none both Depending on "channel" selected, may pop position from the stack (see instruction 0x0), or reset the file to start for looping.

FX Commands[]

FX Commands are stored in internal structures as a sequence of bytes, similar (but different) to the .M music files, though obviously much shorter. This means there are a finite number of effects that can be played, all hard-coded directly in the driver. As a result, each driver also has a slightly different set of commands, though mostly similar. More investigation may be needed for this, though it should be sufficient as a black box now.

Command Use Channel Data Bytes FM/MIDI Description
0x0 no none FM Records current position and next instruction into some sort of stack for later use and skips to a new position, as 0x0 above. FM only this time; MIDI ignores. May be useless code.
0x1 yes 0 or 1 both Sets the count down timer delay, performs some cleanup, and exits the interrupt. If "channel" == 0, read the next byte and set the delay to that, else it uses the "channel" value.
0x2 yes 1 or 11 both records a byte, but in different ways for MIDI and FM. Also skips one data byte for MIDI and 11 for FM. Needs more investigation.
0x3 yes 2 both Sets volume for selected channel as a percentage of max volume from first data byte. Same as 0xA in above music commands.
0x4 yes none MIDI Resets something. word_10E[channel] = 0. Unknown purpose. FM ignores. Needs more investigation.
0x5 yes 0 or 4 MIDI Stores next two words. FM ignores, but does not advance four data bytes. Needs more investigation.
0x6 yes 1 both Changes the pan (left/right) of the selected channel for MIDI. Does something slightly different for FM. Needs more investigation.
0x7 yes none FM Turns the selected channel off. Ignored for MIDI.
0x8 yes 1 both Does something? Appears to stop selected note. Needs more investigation
0x9 yes 1 or 2 both Starts a note playing on selected channel. First data byte is the note. For MIDI, second data byte is the fade in rate. FM doesn't count a second byte. Needs more investigation.
0xA no none none This does nothing but return to the top for the next instruction.
0xB no X MIDI Sends an unknown number of bytes from the file directly to the MIDI port, same as 0xB in above music commands. IGNORED COMPLETELY BY FM! Does not advance in file like other call for FM. Needs more investigation.
0xC yes 1 both Sets channel to play the given instrument number, as defined in instruction 0x2 above. Data byte must not exceed 0x0F (15). Same as above music command.
0xD yes none FM Clears (sets to zero) something. Unknown purpose: byte_111[channel]. Same as above music command.
0xE yes 3 FM Sets some things. Same as above music command EXCEPT MIDI IS NOT ADVANCED. MIDI simply ignores the command entirely. Needs more investigation.
0xF yes none both Changes position in file if channel == 0xF, else ends the interrupt. Needs more investigation.

Sound Driver Functions[]

Offset Function Details
seg:0000 Initialize
seg:0003 Speak
seg:0006 Terminate

"Initialize" Function[]

This function takes four uint16 arguments: soundAddr, seg_music, irq, soundDMA.

"Speak" Function[]

Unexamined.

"Terminate" Function[]

Shuts down the driver.

Advertisement