Structure

To add extra functionality to QTMPlayer, simply write a veneer from QTMPlayer's SWIs to your own module's SWIs, and the QTMPlayer kernel will handle the rest. Any requests to play a file of a type you recognise will be passed on transparently to your layer.

Each layer is a directory. Inside the directory, there should be a data file called 'Code' (the veneer), and optionally there can be a file called 'Module'. If word 20 of the layer header is a pointer to a module title, QTMPlayer will try to load a file called 'Module' in the layer's directory when a song is about to be played using your layer, and kill a module of the module name pointed to by word 20 when the layer has been finished with. There can also be a Sprites file, and a Sprites22 file which will be loaded into to provide filetype sprites for the files your layer recognises.

Format of a layer code file

This description is of format 0.07. All pointers are offset from the beginning of the file. Therefore, it is advised to compile (in BASIC) like this:

         DIM code% 4096
         FOR p=4 TO 6 STEP 2 :REM offset assembly
         P%=0
         O%=code%
         [ Opt     p
           Equs    "QTML"
           Equd    7
           Equd    txt_name
         
           ...
         
         .txt_name
           Equs    "QTMPlayer"
           Equb    0
           Align
         ]
         NEXT p
         SYS "OS_File",10,"<Obey$Dir>.Code",&FFD,,code%,O%

This will ensure that Equd txt_name will obtain an offset from zero.

Header structure

Offset 0
The text "QTML"
Offset 4
Layer format version number (a word), multiplied by 100 (ie. 7)
Offset 8
Pointer to null-terminated string which is the name of layer. It is advised that you give your layer the same name as the replayer module it uses.
Offset 12
Pointer to null-terminated string which is the name of player module author.
Offset 16
Version number of player module that layer was designed for, multiplied by 100
Offset 20
Pointer to list of filetypes your layer can recognise, terminated by -1 (each filetype is a word).
Offset 24
Pointer to null-terminated string which is the title of the player module. Leave this word at zero if you do not want a module to be automatically loaded and killed appropriately.
Offset 28 onwards
Jump table and code

Jump table structure

Whenever QTMPlayer needs to enter a bit of code in your layer, it will branch to one of the words in your jump table. The words in your jump table should then be branch instructions to a routine inside the file. The jump table starts at offset 28 in the Code file, so all the offsets given here should have 28 added to them to give an offset from the beginning of the file. Here follows a summary of the functions QTMPlayer expects at each jump table word:

Offset 0
Start code
Offset 4
Initialise code
Offset 8
Finalise code
Offset 12
Recognise file code (word can be zero if you only recognise filetypes in the list pointed to by the the layer's header)
Offset 16
Examine file code (word can be zero if you are the sole claimant on a filetype)
Offset 20
Read file size code (word can be zero if you want to reserve the size of the file only)
Offset 24
Play sample code
Offset 28
Set soundquality code
Offset 32
Set transparent sound system state code
Offset 36
Read sample length code
Offset 40
Read current song position code
Offset 44
Read last song position code
Offset 48
Set current song position code
Offset 52
Read song author code
Offset 56
Read song name code
Offset 60
Read song duration code
Offset 64
Play file code
Offset 68
Pause song code
Offset 72
Restart song code
Offset 76
Stop playing code
Offset 80
Set volume code
Offset 84
Read volume code
Offset 88
Read sample name code

Code stub descriptions

On entry to each stub, R13 points to a full, descending stack. R12 contains the base address of the layer's private workspace. The null-terminated string at (R12+256) contains the pathname of the layer. The return address is stored on the stack. It can't be stored in R14, becuse all stubs are entered from within SWI routines, so if a stub called a SWI then R14_svc will be corrupted and the stub will not be able to return, without pushing and pulling the return address on entry and exit from every single stub. So, to return, use LdmFd r13!,{Pc}. No processor flags are important to the kernel, so the flags do not have to be restored from the R14 on the stack. If an error occurs in the stub, the V flag should be set on return, and R0 setup to be a pointer to a standard SWI error block. Calling a SWI will do this anyway, so the recommended code sequence is:

         .entry_point_of_stub
           Swi     "XPlayermodule_MiscOp" ;set the X bit
           LdmVsFd r13!,{Pc} ;if error occurs, pull the return address
           Mov     r1,r0,Lsr #1 ;example of post-processing
           Rsb     r0,r2,#256
           LdmFd   r13!,{Pc}
Note that the X form of SWIs must be called otherwise the error handler will be invoked and QTMPlayer will not be able to tidy up after the error. All the registers can be corrupted on return, apart from the stack pointer, which must remain intact.

Here follows a description of each stub:


Start code
(Offset 0)

Called when your layer is loaded in
On entry
R0 = pointer to null-terminated string which is path where layer code file was loaded from (ie. layer directory)
On exit
R0-R12 corrupted
Use
When your layer is first loaded in (using QTMPlayer_Load) this stub is called. You cannot rely on your module being loaded on entry to the stub. Here is an example, where depending on the OS a special flag is set in the workspace:

.start_stub
Mov r0,#129 ;reason code
Mov r1,#0
Mov r2,#&ff
Swi "XOS_Byte" ;r1 > &a4 == riscpc
LdmVsFd r13!,{Pc} ;if error exit
Cmp r1,#&a4 ;are we using a RiscPC?
MovGt r1,#1 ;yes
MovLe r1,#0 ;no
Str r1,[r12,#3] ;store the results in workspace somewhere
LdmFd r13!,{Pc} ;and exit


Initialise code
(Offset 4)

Called when a song is about to be played using your layer
On entry
No parameters passed in registers
On exit
R0-R12 corrupted
Use
This stub is called when a song using your module is about to be played, and your layer is not currently playing a song. On entry, your module will be loaded (if present in the directory).


Finalise code
(Offset 8)

Called when a song using your layer is about to be unloaded
On entry
No parameters passed in registers
On exit
R0-R12 corrupted
Use
This stub is called when a differnent layer is about to be used, or when QTMPlayer_Unload has been called. You should shut down any playing songs and release the sound system (your module is still loaded).


Recognise file code
(Offset 12)

Determines whether a layer recognises a filetype
On entry
R0 = file type of file
On exit
R0 = recognition state (0 = not recognised, 1 = this layer plays that file type)
R1-R12 corrupted
Use
This stub is called when QTMPlayer_Recognise has been called. QTMPlayer goes round all the layers, asking each one in turn if it recognises the file type. If you don't recognise this type of file, R0 should be set to zero on exit. You cannot rely on your module being loaded at this point. If your layer only recognises filetypes in the list pointed to by the layer code file's header, you can leave the jump table word set to value zero to indicate to QTMPlayer that it should just search the list of file types.


Examine file code
(Offset 16)

Called when two layers recognise the same file type
On entry
R0 = path of file
R1 = file handle of song file (file is open for you to examine)
On exit
R0 = recognition state (0 = not recognised, 1 = this layer plays that file)
R1-R12 corrupted
Use
If more than one layer recognise the same file type (eg. soundtracker and the commercial Tracker product) then this stub will be called for all the layers that recognise the file. You should examine the file bytes, if possible using the given file handle and return either zero or 1 depending on your recognition state. If you leave the branch word at zero for this stub, QTMPlayer will assume you will positively examine any file passed to you. Take this example:

.examine_stub
Mov r0,#1 ;reason code to set file pointer
;r1 already = file handle
Mov r2,#0 ;beginning of file
Swi "XOS_Args"
LdmVsFd r13!,{Pc} ;if error, exit
Mov r0,#4 ;reason code (read file)
Mov r2,r12 ;r12=workspace address (256 bytes long)
Mov r3,#4 ;read in one word
Swi "XOS_GBPB"
LdmVsFd r13!,{Pc} ;if error, exit
Ldr r0,[r12] ;load in the word we just read from file
Ldr r1,musx ;load in the value "MUSX"
Teq r0,r1 ;was the first word "MUSX"
MovEq r0,#1 ;if so, then our layer deals with this file
MovNe r0,#0 ;if not, the file is nothing to do with us
LdmFd r13!,{Pc} ;exit


Read file size code
(Offset 20)

Reads the file size (in bytes) of a file
On entry
R0 = path of file
On exit
R0 = file size in bytes
R1-R12 corrupted
Use
This stub is called to find out the amount of memory to reserve for a song file. Most layers will only want to reserve (the size of the file + 16K extra), and so they can leave the jump table word at value zero. However, Digital Symphony compresses its songs, and so this routine is enabled to allow layers like Digital Symphony to specify a much larger amount of memory to reserve. If a layer does not support loading into dynamic areas, then the layer should enable this routine, and return zero, so that QTMPlayer does not try to reserve memory in the sprite area or dynamic area only to find that the layer will not use the memory anyway. Your layer's modules will be loaded by this point, and the initialise stub will have been called. Here is an example:

.filesize_stub
Mov r1,r0
Mov r0,#5
Swi "XOS_File" ;read file's actual size
MovVc r1,#14 ;only perform following if error flag is clear
MulVc r0,r1,r4 ;r0=filesize*14
MovVc r0,r0,Lsr #3 ;r0=filesize*(14/8)
LdmFd r13!,{Pc}


Play sample code
(Offset 24)

Plays a sample
On entry
R0 = sample number
R1 = pitch (an Amiga note value ranging 1-36)
R2 = volume (linear, 0-64)
On exit
R0-R12 corrupted
Use
This stub is called to request that the layer play a sample from the current song.


Set soundquality code
(Offset 28)

Sets the sample mixing rate
On entry
R0 = new sample rate in µs
On exit
R0-R12 corrupted
Use
If possible, set the sample mixing rate to the given value in microseconds. You will never be given rates below 16 µs or above 99 µs.


Set transparent sound system state code
(Offset 32)

Sets the transparent sound system state
On entry
R0 = new transparent sound system state
On exit
R0-R12 corrupted
Use
This stub is called to change the usage of the transparent sound system feature. If your layer supports this, disable system beep sounds whilst song is playing is R0 is zero on entry, otherwise enable beep sounds whilst song is playing. (This is currently QTMTracker specific.)


Read sample length code
(Offset 36)

Reads the sample length of a given sample
On entry
R0 = sample number
On exit
R0 = sample length in bytes
R1-R12 corrupted
Use
If your layer supports this, return the sample length in bytes of the sample in the currently playing song.


Read current song position code
(Offset 40)

Reads the current song position and event
On entry
No parameters passed in registers
On exit
R0 = current song position
R1 = current event
R2-R12 corrupted
Use
It is vital that you return a value that increases as the song progresses in R0, so that the carousel functions can work. An example for a CD layer would be to return the number of minutes in R0 and the number of seconds in R1. The value in R0 should ideally not change more than every three seconds.


Read last song position code
(Offset 44)

Reads the last song position
On entry
No parameters passed in registers
On exit
R0 = maximum song position
R1-R12 corrupted
Use
This stub should return one more than the highest numbered position that can be played.


Set current song position code
(Offset 48)

Sets the current song position and event
On entry
R0 = song position
R1 = event
On exit
R0-R12 corrupted
Use
This should set the song position and event according to the same scheme that read_current_position returns them by.


Read song author code
(Offset 52)

Reads the current song's author
On entry
No parameters passed in registers
On exit
R0 = pointer to null-terminated string (zero if not supported or not found)
R1-R12 corrupted
Use
This stub returns the current song's author.


Read song name code
(Offset 56)

Reads the current song's name
On entry
No parameters passed in registers
On exit
R0 = pointer to null-terminated string (zero if not supported or not found)
R1-R12 corrupted
Use
This stub returns the current song's name.


Read song duration code
(Offset 60)

Reads a song's duration in seconds
On entry
No parameters passed in registers
On exit
R0 = number of seconds or -1 if not supported
R1-R12 corrupted
Use
This call should return the duration of the song in seconds.


Play file code
(Offset 64)

Loads a file into memory and starts it playing
On entry
R0 = pointer to filename
R1 = pointer to block to load song into
R2 = size of block in bytes
R3 = size of your source file in bytes
On exit
R0 = amount of block actually used in bytes
R1-R12 corrupted
Use
This stub is called only after calling initialise to initialise the layer. Your player module will have been loaded by this point if included in the layer directory. You should load the song into the block pointed whose address is contained in R1, and then find out how much of that block is being used, and return the correct amount in R0, or -1 if you are happy with the current block size.


Pause song code
(Offset 68)

Pauses the song
On entry
No parameters passed in registers
On exit
R0-R12 corrupted
Use
This stub is called to pause the song at the current position, so that it can be restarted from that position with restart. This stub may be called when the song is already paused, in which case it should do nothing.


Restart song code
(Offset 72)

Restarts the song from its paused position
On entry
No parameters passed in registers
On exit
R0-R12 corrupted
Use
This stub is called to restart the song from the position it was paused at, or restart it from the beginning if the song had been stopped. This stub may be called while the song is playing, in which case it should do nothing.


Stop playing code
(Offset 76)

Stops the song
On entry
No parameters passed in registers
On exit
R0-R12 corrupted
Use
This stub is called to stop the song, but not unload it from memory. It should restartable with the restart stub after this is called. You should if possible release the sound system when this stub is called, so long as it will be claimed again if necessary. This stub should be equivalent from QTMPlayer's point of view to pausing the song and setting the song position to zero.


Set volume code
(Offset 80)

Sets the volume
On entry
R0 = linearly scaled volume from 0-64 (inclusive)
On exit
R0-R12 corrupted
Use
This stub is called to set the volume to a linearly scaled one from 0-64 (inclusive). Zero is silent and 64 is maximum volume.


Read volume code
(Offset 84)

Reads current volume
On entry
No parameters passed in registers
On exit
R0 = linearly scaled volume from 0-64 (inclusive)
R1-R12 corrupted
Use
This stub is called to read the volume. Zero is silent and 64 is maximum volume.


Read sample name code
(Offset 88)

Reads a sample name
On entry
R0 = sample number
On exit
R0 = pointer to null-terminated string (or zero if not found or not supported)
Use
This stub is called to read the name of a sample in the current song.

You can download the source code to an example layer (the QTMTracker layer) to look at if you are interested.



[ QTMPlay | Overview | QTMPlayer | Downloads | Links ]