home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
569a.lha
/
MultiPlayer_v1.01
/
Formats.doc.pp
/
Formats.doc
Wrap
Text File
|
1991-11-10
|
26KB
|
518 lines
General Module Format (GMOD) Description
Copyright (C) 1991 Bryan Ford
All Rights Reserved
Revision date 27-Jul-91
The GMOD format is a format which can interface any music module with
embedded player code with any program that wants to play the music. It has
many powerful features, but is designed to be as simple as you want it to
be. The 'least common denominator' support is extremely small, so the
module can be small and quick. In many cases, all that is needed to write
a GMOD module is to tack on a short constant header before a normal module.
If you still have doubts, I'll back my words with an offer: If you are
the author of a music-maker program (or any program that creates music
modules), I will personally write whatever code is necessary to extend your
program to load and save GMOD modules as well as your own format. Just
contact me on the network, telephone, or by mail. (My addresses are at the
end of this document.) I'm tired of so many incompatible module formats
around. The Soundtracker format is not powerful enough, but every other
module format requires its own nonstandard player. The GMOD format, then,
is my proposed solution to this problem, and I'm willing to back it up.
If GMOD catches on, we may start to see music maker programs that write
full-featured GMOD modules that are not only playable with most any simple
player program, but also support many specialized features such as external
control (volume, speed, fast-forward, rewind, etc.), real-time sound
effects added to the music (for games or other sound effects), and whatever
else you can imagine.
This document is intended especially for distribution with my music
player MultiPlayer (which of course supports this format). Wherever
MultiPlayer goes, this document goes. However, since the GMOD format isn't
really tied to MultiPlayer, MultiPlayer doesn't have to go wherever this
document goes. You may also distribute this document apart from
MultiPlayer, put it on public bulletin board systems, or even include it
along with commercial programs if you are so inclined, as long as it
remains unchanged. (If you have suggestions or additions to this format,
contact me and I'll make every effort to accomodate your needs as quickly
as possible.)
At the end of this document are described two other module formats,
XMOD and AMOD, which are very simple module formats that I used before the
GMOD format was developed. GMOD replaces both previous formats, so please
don't write programs that create XMOD or AMOD modules. Although
MultiPlayer can still play them, and you may still create them if you want
something extremely simple and quick, for general purpose use you should
always use GMODs.
In this document, when I refer to the 'player', I am referring to the
program that uses the module (the caller), such as MultiPlayer, not the
actual player code in the module itself. I refer to the module, both
specific music-playing code and song data, as just the 'module'.
Format Specification
~~~~~~~~~~~~~~~~~~~~
Now for the actual definition. A GMOD file always begins with four
specific longwords, then a jump table with an arbitrary length, then the
rest of the file can be anything you want. The format follows:
module+$00 'GMOD' ($474D4F44)
module+$04 4-byte ID of program that created this module
module+$08 Memory address where this module MUST be loaded, 0 if relocatable
module+$0c Offset of end of jump table (=numentries*4+$10)
module+$10 Start of jump table (arbitrary length)
The first longword in the file is simply used to identify GMOD modules.
The ID of the creator (module+$04) is used to identify the program that
created this module. It can be ignored by any program that simply wants to
play the module. A music composer program might check this longword when
trying to re-load a module to make sure the module is the correct type.
When selecting an ID for your program to use, think of it as an IFF chunk
ID: Try to select something that is somewhat readable ASCII, but is
unlikely to collide with some other program. (For example, don't use
'SONG' or 'TRAK' or something simple like that.)
The load address is the absolute location in memory where the module
must be loaded. Although hopefully programmers will have enough sense not
to create non-relocatable modules in the future, the feature is there for
current module types that use absolute addresses in the code and/or data.
In the MultiPlayer implementation using this feature is not dangerous to
the system and will never cause innocent memory to be stomped on, but it
may cause the module to be unable to load even when there's plenty of
memory. If the required memory area is already occupied (even if it's just
a few bytes somewhere in the required range), MultiPlayer will refuse to
load the module. Therefore, this feature should never be used except when
absolutely necessary, since these modules may not be able to load even when
there is plenty memory available. MultiPlayer properly allocates the
needed memory with AllocAbs, rather than just overwriting memory as many
European demos do, so multitasking will never cause the module to get
overwritten. I wish this feature wasn't necessary, but sometimes we just
have to clean up other people's messes, don't we?
The offset of the end of the jump table is the offset (from the start
of the module) pointing to just past the last valid jump table entry. It
can be calculated using the equation (numvecs*4)+16, where numvecs is the
number of vectors in the jump table. Any vectors beyond this offset are
assumed by the player (caller) to be 'do-nothing' routines - i.e. just an
RTS.
The actual jump table entries are usually branches or PC-relative jump
instructions, but can be any code that fits in four bytes. In particular,
if a function simply wants to return a constant value (such as
GetNumSongs), a MOVEQ and an RTS could fit right into the jump table. The
player must not try to interpret the jump table entries (and by no means
modify it!). The one exception to this rule is that the player may look
into the jump table to see if the first word of the entrypoint is $4E75
(RTS), in which case the player knows that the entry is not used. This
way, the player can avoid bogging down the system with unused interrupts
and such.
The calling conventions are always standard Amiga conventions: The
routines must save registers D2-D7 and A2-A6, and must return with an RTS.
Parameters are passed in various registers and returned in D0, the same way
as standard Amiga libraries.
Note that, since any of the entries may be omitted, special care must
be taken by the caller when calling the module. First, before calling a
particular entrypoint, the offset must be tested against the MaxVecOfs in
the module. If the offset is greater than OR EQUAL to MaxVecOfs, the
routine does not exist, and behaves the same way as if the entrypoint was a
do-nothing entrypoint (first word is RTS). Second, when the caller is
expecting a return code of some kind in D0, before calling the module it
must initialize D0 to some default value, or an invalid value it can
recognize, in case the routine is a do-nothing or nonexistent entrypoint.
For example, before calling the GetNumSongs function, D0 should be
initialized to 1. If GetNumSongs doesn't exist or does nothing, the
default value of one song will be assumed.
I will not specify whether or not operating system routines (such as
Exec's memory allocation routines) may be called from within the module.
For a general-purpose player program like MultiPlayer, this should be
allowed. However, I expect that the majority of music modules for a long
time to come will stay away from the operating system in order to support
games and demos and other programs that take over the machine. If the
operating system is to be used normally, it is always the caller's
(player's) responsibility to allocate the audio.device's channels and make
sure the module fits into the multitasking system. All the module needs to
do is stick to the standard audio hardware registers (and audio DMA
control) and keep its nose out of forbidden areas. Modules should generally
not need to muck around in interrupts, since the format defines some very
general-purpose interrupt routines, but I won't forbid it either. If a module
wants to do its own interrupt processing, then all the module's interrupt
entrypoints (VBlank50, VBlank60, and TimerTick) should be do-nothing, and
the module should properly allocate the interrupts using system calls.
The currently defined jump table entries are described below:
module+$10 InitMusic
Called to initialize the module. This is generally the first routine
called by a player program after loading the module. It should be called
only once, and the module may be started and stopped as many times as
needed after a single call to InitMusic. D0 must be set to NULL before
calling the routine, and the initialization routine should return NULL in
D0 if the initialization succeeded, or a pointer to a string (in D0)
describing the error if the initialization failed. No other routines in
the module may be called before initialization. InitMusic should not mess
with the audio hardware or start playing music, as the audio channels may
not be allocated by this time. This entrypoint would generally be used to
perform any relocation or other one-time initialization of the module, or
for allocating memory or other resources through the operating system if
necessary.
module+$14 StartMusic
Called to start playing the music. If the module contains several
songs, then D0 will hold the song number (0..n-1) to start playing,
otherwise D0 will be 0. StartMusic always starts a given song at the
beginning. To pause and restart in the middle of a song, use PauseMusic
and RestartMusic.
Any given module is always guaranteed to have a song number 0. Before
calling StartMusic with a song number greater than zero, it must call
GetNumSongs to make sure that song actually exists. There is not
necessarily any protection in the module against playing songs that do not
exist.
The StartMusic entrypoint may be called again after StopMusic to
restart the music after it was stopped. This allows the module to be
're-used' without reloading. Also, it allows switching songs in the module
if the module contains more than one song. However, StartMusic should
never be called after another StartMusic, without a StopMusic in between.
module+$18 StopMusic
Called to stop playing the music. Often all this does is turn off
audio DMA and return. StopMusic may be called any number of times, with or
without a corresponding StartMusic call.
Note that no interrupt entrypoints in the module are called by the
player anytime except when the music is actually playing, i.e. after
StartMusic and before StopMusic. Therefore, the player program should
enable audio interrupts AFTER StartMusic, and disable interrupts BEFORE
StopMusic.
module+$1c EndMusic
Called to shut down the module and prepare to be unloaded. The caller
MUST call this entry before unloading the module, even if the call to
InitMusic failed. If the music was playing, StopMusic must be called
before calling EndMusic. This may be called more than once successively,
but no other routines in the module may be called after EndMusic has been
called. A module may not be re-used after EndMusic. EndMusic should not
mess with audio hardware or DMA registers - that is done in StopMusic.
This entrypoint is provided mainly to free any allocated memory or do other
operating-system related cleanup. As such, for most current modules this
entrypoint would do nothing.
module+$20 Reserved (was PauseMusic, now obsolete)
module+$24 ContinueMusic
After the player has called StopMusic (but before it calls EndMusic),
it can call ContinueMusic to try and continue the music at the position
where it was stopped. The player calls this function with D0=0, and if
the module is able to restart the music, it should return D0=1; otherwise
it should leave D0 at 0. The player must ONLY start sending interrupts
again if the module returned nonzero in D0 - otherwise, the module
doesn't support this feature and turning interrupts on at this point
might cause trouble. The player can always call StartMusic again to
restart the module at the beginning.
module+$28 VBlank50
module+$2c VBlank60
The player program must call whichever of these vectors are supplied,
at the appropriate frequency. The VBlank50 routine will be called 50 times
per second, the VBlank60 routine will be called 60 times per second. The
module may use either one of these routines (not both). If the player
calls these routines from a vertical blank interrupt (as is usually the
case, but not necessarily), it is its responsibility to adjust the
frequency appropriately. Note that a more general timing system is also
available, described below.
module+$30 Channel0
module+$34 Channel1
module+$38 Channel2
module+$3c Channel3
These four entrypoints, if supplied, are called by the player whenever
the corresponding audio channels are reloaded. They correspond to Exec's
AUD0-AUD3 interrupts (levels 7-10), CPU level 4.
module+$40 GetNumSongs
Some music modules have more than one song in the same module. This is
useful when you want several separate musical scores that all use the same
set of instruments, for memory efficiency. If this entrypoint is supplied,
the player program may call this routine to find out how many different
songs are contained in this module. The routine must be called with D0 = 1
(the default number of songs), and the module should return the actual
number of songs in D0. Once the player knows how many songs are in the
module, it may select the song to play in the call to StartMusic.
module+$44 GetSongName
If a module contains several songs, GetSongName provides a method for
the player to find the name of each song. This may be simply displayed to
the user while the song is playing, or a list of songs may be built for the
user to select from by name. This routine is called with D0 holding NULL
and D1 holding the song number (which must be in the proper range). If the
module supplies this routine, it must return a pointer to the song name in
D0. Otherwise, D0 will remain NULL and the song will remain nameless.
module+$48 GetSongAuthor
This routine works the same way as GetSongName, except the name of the
song's author is returned. If all the songs in the module are by the same
person (or if the module contains only one song), D1 can be ignored.
module+$4c GetFrequency
module+$50 TimerTick
These routines can be used by modules for more general-purpose timing,
or for odd timing values not based on the vertical blank interrupt. If the
module uses this method of timing, both of these functions must be
implemented, and neither VBlank50 or VBlank60 may be. The GetFrequency
function must simply return the number of ticks per second the module
requires in d0. The TimerTick function will then be called (probably from
an interrupt) at the specified frequency while the module is playing, just
like the VBlank functions.
module+$54 GetMakerName
This entrypoint must be called with D0=0, and if it is implemented,
must return the name of the program that was used to create this module.
Although music maker programs should use the ID field at offset 4 to
recognize specific module types, this string can be used by player programs
and such to identify the module's origin for the user.
module+$58 Hook
This function is called by the player with D0=0, D1=hook flags, and A0
pointing to a standard Hook structure as defined in the 2.0 header files.
(This does not mean that modules or players that use this feature must run
on 2.0 - this structure is just used for convenience.) The hook flags work
very much like Intuition's IDCMP flags and tell the module what kinds of
events the player wants to hear about. If the module supports this call,
it must save the pointer to the Hook (it will remain valid until after
EndMusic is called) and return in D0 the hook flags indicate what hooks the
module supports.
During playing, the module will call the player's Hook for the events
that the player specified (in D1 in the Hook call) AND the module supports
(returned in D0 from the Hook call). Standard Hook calling conventions
must be used: A0 points to the Hook structure that the player originally
passed to the module, and A1 points to an appropriate 'message' or parameter
packet (see below). The 'object' passed in A2 is currently unused, but may
be used in future message types.
The message that the module passes to the player's Hook function depends
on the type of event. The first longword of the message always contains
a single Hook Flags bit set, indicating the type of event (just like IDCMP
messages). Note that the message is NOT a standard Exec message. After
the first longword, more data can be passed depending on the event.
The player may call the module's Hook entrypoint more than once.
However, the Hook pointer passed to the module must ALWAYS be the same!
Therefore, re-calling this entrypoint can only be used to change the
'active' event bits (ala ModifyIDCMP()), not to change the Hook pointer
itself.
Note that the module's entrypoints are not necessarily reentrant, so
the player's Hook function must NEVER call any of the module's entrypoints,
or make any calls that might indirectly cause one of the entrypoints to be
called.
Currently the following event types are defined:
BITDEF GMODH,REPEAT,0
If supported and requested, the module calls the Hook function as
soon as the music is about to wrap around and repeat itself. If the
player's intent is to stop the module when it repeats, it must set a flag
or signal a parent Task - it must not directly call the module's StopMusic
entrypoints (see above). The message passed to the player's Hook must, of
course, have the first longword set to GMODHF_REPEAT (1<<0), but other than
that the message is unused.
BITDEF GMODH,SEQUENCE,1
If supported and requested, the module calls the player's Hook whenever
the music's sequence number changes. This event type is specifically
designed for music formats with Soundtracker-like sequences: if the music
format doesn't use such sequences, then this event type should be left
unimplemented by the module. The sequence number starts at 0 and goes to a
maximum of length-1 (length is the number of sequences in the module). The
message passed to the player's Hook must have GMODHF_SEQUENCE (1<<1) in the
first longword, the new sequence number in the second longword (offset 4),
and the total number of sequences in the third longword (offset 8). The
module should also call this Hook once during the PlayMusic call, or very
soon afterwards, so the player knows immediately how long the module is.
module+$5c Jump
Instructs the module to jump to a particular position within the
current song. This is extremely module-dependent. The player passes D0=0
and the position to jump to (a longword) in D1. For Soundtracker-like
modules, this position is probably the sequence number. For other modules,
it may be a time value or whatever. If the module successfully jumps to
the requested position (it should do range checking and such first), it
returns D0=1, otherwise it leaves or sets D0=0. This entrypoint exists to
allow users and programmers to do nifty tricks with specific modules - the
player can't and shouldn't interpret or use this entrypoint by itself.
I have many other possible entrypoints and hooks in mind, but haven't
gotten them standardized yet (much less implemented in MultiPlayer), so
I'll leave them out for now. If there's something you want, drop me a line
and I'll go ahead and add it to the specification, whether or not
MultiPlayer can support it. (It probably can.) The important thing is to
keep the interface powerful, easy to use, and standardized.
To use GMOD modules in your own programs, such as games or demos, all
you have to do is call the appropriate entrypoints at the appropriate
times. If your program will only be using one module, or a limited number
of modules all made with the same program, you can make assumptions about
the module, i.e., which interrupt timing routine to call, etc. A minimum
implementation might be as simple as two calls: one to the initialization
code, and one during each VBlank interrupt. (Many modules do nothing more
than turn audio DMA off in their exit code.) Of course, if you want to be
compatible with more modules, you'll have to put in a little more effort.
If you are the author of a music-composer program, I hope you will
seriously consider supporting GMOD modules. As I said at the top of this
document, I'll do everything I can to make life easy for you. A minimal
implementation can be done very quickly and with very little overhead.
Modules with built-in players are becoming more and more popular, and I
believe a standard, extensible caller interface could benefit both
musicians and programmers.
The XMOD format (obsolete)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Besides the standard module formats that MultiPlayer understands, there
is also a new format you can use to turn just about anything into a
multitasking module. Basically, you go into the program you want to rip
the music out of with some kind of debugger (I use S.I.M.), find the player
and module (with luck, they'll be right next to each other), and add an
XMOD header at the beginning so that MultiPlayer (or another player that
supports XMOD format) knows what to do with it.
The XMOD format is very simple:
module+$00 'XMOD'
module+$04 InitEntry
module+$08 PlayEntry
module+$0c StopEntry
When MultiPlayer loads a module and finds an 'XMOD' in the beginning of
it, it first calls module+$04 to let the player initialize itself and the
module, then it calls module+$08 50 times per second (the normal vertical
blanking interrupt speed on PAL systems), and finally calls module+$0c when
the user stops the module. The player must be PC-relative, or it must do
its own relocation in the initialization code. The entire file is always
loaded into chip memory in one continuous block.
All three entrypoints must use standard Amiga calling conventions and
save registers D2-D7/A2-A6. This includes the PlayEntry - it may not
modify A5/A6 even though it's normally called from an interrupt routine.
They may use D0-D1/A0-A1 without restoring them. No parameters are passed
to any of the entries, and no return codes are checked for. All three
routines must return with an RTS, not with an RTE.
If you need to debug an XMOD module (i.e. it doesn't work the first
time), you can just set the first instruction at module+$04 to 'illegal'
($4AFC), activate a good debugger, and load the module from MultiPlayer.
As soon as MultiPlayer calls the module's init routine, it should kick into
the debugger.
The AMOD format (obsolete)
~~~~~~~~~~~~~~~~~~~~~~~~~~
The AMOD format is another special-purpose format MultiPlayer
understands. It is almost identical to the XMOD format, except in one
respect: AMOD modules always get loaded at a specific address. For those
few oddball music composer/player programs that like to write modules with
lots of absolute addresses in them, this format may be the only good way to
make the modules multitask.
The AMOD format is a simple extension to the XMOD format:
address+$00 'AMOD'
address+$04 InitEntry
address+$08 PlayEntry
address+$0c StopEntry
address+$10 Load address
When MultiPlayer encounters an AMOD module, it allocates memory for the
module starting at the load address specified in the module. The load
address points to the location where the 'AMOD' identifier should go. The
module's code does not have to be PC-relative, since the load address is
known. Other than that, an AMOD module is loaded and executed the same way
as an XMOD module.
If the required memory area is already occupied (even if it's just a
few bytes somewhere in the required range), the module will not load.
Therefore, the AMOD format should never be used except when absolutely
necessary, since these modules may not be able to load even when there is
enough memory available. The memory does get allocated (the space is not
just stomped on as with most European demos), so it will not cause the
machine to crash; it just might not load sometimes when it should be able
to. I wish this module type wasn't necessary, but sometimes we just have
to clean up somebody else's messes, don't we?
Contact Address
~~~~~~~~~~~~~~~
The most reliable way of contacting me is at my parents' address. It
may take a while for me to get something sent there, but it WILL get to me.
I tend to move around a great deal, so mail sent directly to me sometimes
has a hard time catching up. My parents' address is:
Bryan Ford
8749 Alta Hills Circle
Sandy, UT 84093
I can be reached more quickly (for the time being anyway) on the phone
or through one of the electronic mail addresses below:
(801) 585-4619
bryan.ford@m.cc.utah.edu
baf0863@cc.utah.edu
baf0863@utahcca.bitnet
If you want to mail me something quickly, FIRST call or E-mail me
to make sure I'm still here, then send it to this address:
Bryan Ford
27104 Ballif Hall
University of Utah
Salt Lake City, UT 84112