
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 ]
Chris Rutter, chris@collegium.co.uk