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
Text File  |  1991-11-10  |  26KB  |  518 lines

  1.                  General Module Format (GMOD) Description
  2.                        Copyright (C) 1991 Bryan Ford
  3.                             All Rights Reserved
  4.                           Revision date 27-Jul-91
  5.  
  6.  
  7.  
  8.     The GMOD format is a format which can interface any music module with
  9. embedded player code with any program that wants to play the music.  It has
  10. many powerful features, but is designed to be as simple as you want it to
  11. be.  The 'least common denominator' support is extremely small, so the
  12. module can be small and quick.  In many cases, all that is needed to write
  13. a GMOD module is to tack on a short constant header before a normal module.
  14.  
  15.     If you still have doubts, I'll back my words with an offer:  If you are
  16. the author of a music-maker program (or any program that creates music
  17. modules), I will personally write whatever code is necessary to extend your
  18. program to load and save GMOD modules as well as your own format.  Just
  19. contact me on the network, telephone, or by mail.  (My addresses are at the
  20. end of this document.) I'm tired of so many incompatible module formats
  21. around.  The Soundtracker format is not powerful enough, but every other
  22. module format requires its own nonstandard player.  The GMOD format, then,
  23. is my proposed solution to this problem, and I'm willing to back it up.
  24.  
  25.     If GMOD catches on, we may start to see music maker programs that write
  26. full-featured GMOD modules that are not only playable with most any simple
  27. player program, but also support many specialized features such as external
  28. control (volume, speed, fast-forward, rewind, etc.), real-time sound
  29. effects added to the music (for games or other sound effects), and whatever
  30. else you can imagine.
  31.  
  32.     This document is intended especially for distribution with my music
  33. player MultiPlayer (which of course supports this format).  Wherever
  34. MultiPlayer goes, this document goes.  However, since the GMOD format isn't
  35. really tied to MultiPlayer, MultiPlayer doesn't have to go wherever this
  36. document goes.  You may also distribute this document apart from
  37. MultiPlayer, put it on public bulletin board systems, or even include it
  38. along with commercial programs if you are so inclined, as long as it
  39. remains unchanged.  (If you have suggestions or additions to this format,
  40. contact me and I'll make every effort to accomodate your needs as quickly
  41. as possible.)
  42.  
  43.     At the end of this document are described two other module formats,
  44. XMOD and AMOD, which are very simple module formats that I used before the
  45. GMOD format was developed.  GMOD replaces both previous formats, so please
  46. don't write programs that create XMOD or AMOD modules.  Although
  47. MultiPlayer can still play them, and you may still create them if you want
  48. something extremely simple and quick, for general purpose use you should
  49. always use GMODs.
  50.  
  51.     In this document, when I refer to the 'player', I am referring to the
  52. program that uses the module (the caller), such as MultiPlayer, not the
  53. actual player code in the module itself.  I refer to the module, both
  54. specific music-playing code and song data, as just the 'module'.
  55.  
  56.  
  57.  
  58.                            Format Specification
  59.                            ~~~~~~~~~~~~~~~~~~~~
  60.  
  61.     Now for the actual definition.  A GMOD file always begins with four
  62. specific longwords, then a jump table with an arbitrary length, then the
  63. rest of the file can be anything you want.  The format follows:
  64.  
  65. module+$00    'GMOD' ($474D4F44)
  66. module+$04    4-byte ID of program that created this module
  67. module+$08    Memory address where this module MUST be loaded, 0 if relocatable
  68. module+$0c    Offset of end of jump table (=numentries*4+$10)
  69. module+$10    Start of jump table (arbitrary length)
  70.  
  71.     The first longword in the file is simply used to identify GMOD modules.
  72.  
  73.     The ID of the creator (module+$04) is used to identify the program that
  74. created this module.  It can be ignored by any program that simply wants to
  75. play the module.  A music composer program might check this longword when
  76. trying to re-load a module to make sure the module is the correct type.
  77. When selecting an ID for your program to use, think of it as an IFF chunk
  78. ID:  Try to select something that is somewhat readable ASCII, but is
  79. unlikely to collide with some other program.  (For example, don't use
  80. 'SONG' or 'TRAK' or something simple like that.)
  81.  
  82.     The load address is the absolute location in memory where the module
  83. must be loaded.  Although hopefully programmers will have enough sense not
  84. to create non-relocatable modules in the future, the feature is there for
  85. current module types that use absolute addresses in the code and/or data.
  86. In the MultiPlayer implementation using this feature is not dangerous to
  87. the system and will never cause innocent memory to be stomped on, but it
  88. may cause the module to be unable to load even when there's plenty of
  89. memory.  If the required memory area is already occupied (even if it's just
  90. a few bytes somewhere in the required range), MultiPlayer will refuse to
  91. load the module.  Therefore, this feature should never be used except when
  92. absolutely necessary, since these modules may not be able to load even when
  93. there is plenty memory available.  MultiPlayer properly allocates the
  94. needed memory with AllocAbs, rather than just overwriting memory as many
  95. European demos do, so multitasking will never cause the module to get
  96. overwritten.  I wish this feature wasn't necessary, but sometimes we just
  97. have to clean up other people's messes, don't we?
  98.  
  99.     The offset of the end of the jump table is the offset (from the start
  100. of the module) pointing to just past the last valid jump table entry.  It
  101. can be calculated using the equation (numvecs*4)+16, where numvecs is the
  102. number of vectors in the jump table.  Any vectors beyond this offset are
  103. assumed by the player (caller) to be 'do-nothing' routines - i.e.  just an
  104. RTS.
  105.  
  106.     The actual jump table entries are usually branches or PC-relative jump
  107. instructions, but can be any code that fits in four bytes.  In particular,
  108. if a function simply wants to return a constant value (such as
  109. GetNumSongs), a MOVEQ and an RTS could fit right into the jump table.  The
  110. player must not try to interpret the jump table entries (and by no means
  111. modify it!).  The one exception to this rule is that the player may look
  112. into the jump table to see if the first word of the entrypoint is $4E75
  113. (RTS), in which case the player knows that the entry is not used.  This
  114. way, the player can avoid bogging down the system with unused interrupts
  115. and such.
  116.  
  117.     The calling conventions are always standard Amiga conventions:  The
  118. routines must save registers D2-D7 and A2-A6, and must return with an RTS.
  119. Parameters are passed in various registers and returned in D0, the same way
  120. as standard Amiga libraries.
  121.  
  122.     Note that, since any of the entries may be omitted, special care must
  123. be taken by the caller when calling the module.  First, before calling a
  124. particular entrypoint, the offset must be tested against the MaxVecOfs in
  125. the module.  If the offset is greater than OR EQUAL to MaxVecOfs, the
  126. routine does not exist, and behaves the same way as if the entrypoint was a
  127. do-nothing entrypoint (first word is RTS).  Second, when the caller is
  128. expecting a return code of some kind in D0, before calling the module it
  129. must initialize D0 to some default value, or an invalid value it can
  130. recognize, in case the routine is a do-nothing or nonexistent entrypoint.
  131. For example, before calling the GetNumSongs function, D0 should be
  132. initialized to 1.  If GetNumSongs doesn't exist or does nothing, the
  133. default value of one song will be assumed.
  134.  
  135.     I will not specify whether or not operating system routines (such as
  136. Exec's memory allocation routines) may be called from within the module.
  137. For a general-purpose player program like MultiPlayer, this should be
  138. allowed.  However, I expect that the majority of music modules for a long
  139. time to come will stay away from the operating system in order to support
  140. games and demos and other programs that take over the machine.  If the
  141. operating system is to be used normally, it is always the caller's
  142. (player's) responsibility to allocate the audio.device's channels and make
  143. sure the module fits into the multitasking system.  All the module needs to
  144. do is stick to the standard audio hardware registers (and audio DMA
  145. control) and keep its nose out of forbidden areas.  Modules should generally
  146. not need to muck around in interrupts, since the format defines some very
  147. general-purpose interrupt routines, but I won't forbid it either.  If a module
  148. wants to do its own interrupt processing, then all the module's interrupt
  149. entrypoints (VBlank50, VBlank60, and TimerTick) should be do-nothing, and
  150. the module should properly allocate the interrupts using system calls.
  151.  
  152.     The currently defined jump table entries are described below:
  153.  
  154.  
  155. module+$10    InitMusic
  156.  
  157.     Called to initialize the module.  This is generally the first routine
  158. called by a player program after loading the module.  It should be called
  159. only once, and the module may be started and stopped as many times as
  160. needed after a single call to InitMusic.  D0 must be set to NULL before
  161. calling the routine, and the initialization routine should return NULL in
  162. D0 if the initialization succeeded, or a pointer to a string (in D0)
  163. describing the error if the initialization failed.  No other routines in
  164. the module may be called before initialization.  InitMusic should not mess
  165. with the audio hardware or start playing music, as the audio channels may
  166. not be allocated by this time.  This entrypoint would generally be used to
  167. perform any relocation or other one-time initialization of the module, or
  168. for allocating memory or other resources through the operating system if
  169. necessary.
  170.  
  171.  
  172. module+$14    StartMusic
  173.  
  174.     Called to start playing the music.  If the module contains several
  175. songs, then D0 will hold the song number (0..n-1) to start playing,
  176. otherwise D0 will be 0.  StartMusic always starts a given song at the
  177. beginning.  To pause and restart in the middle of a song, use PauseMusic
  178. and RestartMusic.
  179.  
  180.     Any given module is always guaranteed to have a song number 0.  Before
  181. calling StartMusic with a song number greater than zero, it must call
  182. GetNumSongs to make sure that song actually exists.  There is not
  183. necessarily any protection in the module against playing songs that do not
  184. exist.
  185.  
  186.     The StartMusic entrypoint may be called again after StopMusic to
  187. restart the music after it was stopped.  This allows the module to be
  188. 're-used' without reloading.  Also, it allows switching songs in the module
  189. if the module contains more than one song.  However, StartMusic should
  190. never be called after another StartMusic, without a StopMusic in between.
  191.  
  192.  
  193. module+$18    StopMusic
  194.  
  195.     Called to stop playing the music.  Often all this does is turn off
  196. audio DMA and return.  StopMusic may be called any number of times, with or
  197. without a corresponding StartMusic call.
  198.  
  199.     Note that no interrupt entrypoints in the module are called by the
  200. player anytime except when the music is actually playing, i.e.  after
  201. StartMusic and before StopMusic.  Therefore, the player program should
  202. enable audio interrupts AFTER StartMusic, and disable interrupts BEFORE
  203. StopMusic.
  204.  
  205.  
  206. module+$1c    EndMusic
  207.  
  208.     Called to shut down the module and prepare to be unloaded.  The caller
  209. MUST call this entry before unloading the module, even if the call to
  210. InitMusic failed.  If the music was playing, StopMusic must be called
  211. before calling EndMusic.  This may be called more than once successively,
  212. but no other routines in the module may be called after EndMusic has been
  213. called.  A module may not be re-used after EndMusic.  EndMusic should not
  214. mess with audio hardware or DMA registers - that is done in StopMusic.
  215. This entrypoint is provided mainly to free any allocated memory or do other
  216. operating-system related cleanup.  As such, for most current modules this
  217. entrypoint would do nothing.
  218.  
  219.  
  220. module+$20    Reserved (was PauseMusic, now obsolete)
  221.  
  222.  
  223. module+$24    ContinueMusic
  224.  
  225.     After the player has called StopMusic (but before it calls EndMusic),
  226. it can call ContinueMusic to try and continue the music at the position
  227. where it was stopped.  The player calls this function with D0=0, and if
  228. the module is able to restart the music, it should return D0=1; otherwise
  229. it should leave D0 at 0.  The player must ONLY start sending interrupts
  230. again if the module returned nonzero in D0 - otherwise, the module
  231. doesn't support this feature and turning interrupts on at this point
  232. might cause trouble.  The player can always call StartMusic again to
  233. restart the module at the beginning.
  234.  
  235.  
  236. module+$28    VBlank50
  237. module+$2c    VBlank60
  238.  
  239.     The player program must call whichever of these vectors are supplied,
  240. at the appropriate frequency.  The VBlank50 routine will be called 50 times
  241. per second, the VBlank60 routine will be called 60 times per second.  The
  242. module may use either one of these routines (not both).  If the player
  243. calls these routines from a vertical blank interrupt (as is usually the
  244. case, but not necessarily), it is its responsibility to adjust the
  245. frequency appropriately.  Note that a more general timing system is also
  246. available, described below.
  247.  
  248.  
  249. module+$30    Channel0
  250. module+$34    Channel1
  251. module+$38    Channel2
  252. module+$3c    Channel3
  253.  
  254.     These four entrypoints, if supplied, are called by the player whenever
  255. the corresponding audio channels are reloaded.  They correspond to Exec's
  256. AUD0-AUD3 interrupts (levels 7-10), CPU level 4.
  257.  
  258.  
  259. module+$40    GetNumSongs
  260.  
  261.     Some music modules have more than one song in the same module.  This is
  262. useful when you want several separate musical scores that all use the same
  263. set of instruments, for memory efficiency.  If this entrypoint is supplied,
  264. the player program may call this routine to find out how many different
  265. songs are contained in this module.  The routine must be called with D0 = 1
  266. (the default number of songs), and the module should return the actual
  267. number of songs in D0.  Once the player knows how many songs are in the
  268. module, it may select the song to play in the call to StartMusic.
  269.  
  270.  
  271. module+$44    GetSongName
  272.  
  273.     If a module contains several songs, GetSongName provides a method for
  274. the player to find the name of each song.  This may be simply displayed to
  275. the user while the song is playing, or a list of songs may be built for the
  276. user to select from by name.  This routine is called with D0 holding NULL
  277. and D1 holding the song number (which must be in the proper range).  If the
  278. module supplies this routine, it must return a pointer to the song name in
  279. D0.  Otherwise, D0 will remain NULL and the song will remain nameless.
  280.  
  281.  
  282. module+$48    GetSongAuthor
  283.  
  284.     This routine works the same way as GetSongName, except the name of the
  285. song's author is returned.  If all the songs in the module are by the same
  286. person (or if the module contains only one song), D1 can be ignored.
  287.  
  288.  
  289. module+$4c    GetFrequency
  290. module+$50    TimerTick
  291.  
  292.     These routines can be used by modules for more general-purpose timing,
  293. or for odd timing values not based on the vertical blank interrupt.  If the
  294. module uses this method of timing, both of these functions must be
  295. implemented, and neither VBlank50 or VBlank60 may be.  The GetFrequency
  296. function must simply return the number of ticks per second the module
  297. requires in d0.  The TimerTick function will then be called (probably from
  298. an interrupt) at the specified frequency while the module is playing, just
  299. like the VBlank functions.
  300.  
  301.  
  302. module+$54    GetMakerName
  303.  
  304.     This entrypoint must be called with D0=0, and if it is implemented,
  305. must return the name of the program that was used to create this module.
  306. Although music maker programs should use the ID field at offset 4 to
  307. recognize specific module types, this string can be used by player programs
  308. and such to identify the module's origin for the user.
  309.  
  310.  
  311. module+$58    Hook
  312.  
  313.     This function is called by the player with D0=0, D1=hook flags, and A0
  314. pointing to a standard Hook structure as defined in the 2.0 header files.
  315. (This does not mean that modules or players that use this feature must run
  316. on 2.0 - this structure is just used for convenience.) The hook flags work
  317. very much like Intuition's IDCMP flags and tell the module what kinds of
  318. events the player wants to hear about.  If the module supports this call,
  319. it must save the pointer to the Hook (it will remain valid until after
  320. EndMusic is called) and return in D0 the hook flags indicate what hooks the
  321. module supports.
  322.  
  323.     During playing, the module will call the player's Hook for the events
  324. that the player specified (in D1 in the Hook call) AND the module supports
  325. (returned in D0 from the Hook call).  Standard Hook calling conventions
  326. must be used:  A0 points to the Hook structure that the player originally
  327. passed to the module, and A1 points to an appropriate 'message' or parameter
  328. packet (see below).  The 'object' passed in A2 is currently unused, but may
  329. be used in future message types.
  330.  
  331.     The message that the module passes to the player's Hook function depends
  332. on the type of event.  The first longword of the message always contains
  333. a single Hook Flags bit set, indicating the type of event (just like IDCMP
  334. messages).  Note that the message is NOT a standard Exec message.  After
  335. the first longword, more data can be passed depending on the event.
  336.  
  337.     The player may call the module's Hook entrypoint more than once.
  338. However, the Hook pointer passed to the module must ALWAYS be the same!
  339. Therefore, re-calling this entrypoint can only be used to change the
  340. 'active' event bits (ala ModifyIDCMP()), not to change the Hook pointer
  341. itself.
  342.  
  343.     Note that the module's entrypoints are not necessarily reentrant, so
  344. the player's Hook function must NEVER call any of the module's entrypoints,
  345. or make any calls that might indirectly cause one of the entrypoints to be
  346. called.
  347.  
  348.     Currently the following event types are defined:
  349.  
  350.  
  351. BITDEF    GMODH,REPEAT,0
  352.  
  353.     If supported and requested, the module calls the Hook function as
  354. soon as the music is about to wrap around and repeat itself.  If the
  355. player's intent is to stop the module when it repeats, it must set a flag
  356. or signal a parent Task - it must not directly call the module's StopMusic
  357. entrypoints (see above).  The message passed to the player's Hook must, of
  358. course, have the first longword set to GMODHF_REPEAT (1<<0), but other than
  359. that the message is unused.
  360.  
  361.  
  362. BITDEF    GMODH,SEQUENCE,1
  363.  
  364.     If supported and requested, the module calls the player's Hook whenever
  365. the music's sequence number changes.  This event type is specifically
  366. designed for music formats with Soundtracker-like sequences:  if the music
  367. format doesn't use such sequences, then this event type should be left
  368. unimplemented by the module.  The sequence number starts at 0 and goes to a
  369. maximum of length-1 (length is the number of sequences in the module).  The
  370. message passed to the player's Hook must have GMODHF_SEQUENCE (1<<1) in the
  371. first longword, the new sequence number in the second longword (offset 4),
  372. and the total number of sequences in the third longword (offset 8).  The
  373. module should also call this Hook once during the PlayMusic call, or very
  374. soon afterwards, so the player knows immediately how long the module is.
  375.  
  376.  
  377. module+$5c    Jump
  378.  
  379.     Instructs the module to jump to a particular position within the
  380. current song.  This is extremely module-dependent.  The player passes D0=0
  381. and the position to jump to (a longword) in D1.  For Soundtracker-like
  382. modules, this position is probably the sequence number.  For other modules,
  383. it may be a time value or whatever.  If the module successfully jumps to
  384. the requested position (it should do range checking and such first), it
  385. returns D0=1, otherwise it leaves or sets D0=0.  This entrypoint exists to
  386. allow users and programmers to do nifty tricks with specific modules - the
  387. player can't and shouldn't interpret or use this entrypoint by itself.
  388.  
  389.  
  390.     I have many other possible entrypoints and hooks in mind, but haven't
  391. gotten them standardized yet (much less implemented in MultiPlayer), so
  392. I'll leave them out for now.  If there's something you want, drop me a line
  393. and I'll go ahead and add it to the specification, whether or not
  394. MultiPlayer can support it.  (It probably can.)  The important thing is to
  395. keep the interface powerful, easy to use, and standardized.
  396.  
  397.     To use GMOD modules in your own programs, such as games or demos, all
  398. you have to do is call the appropriate entrypoints at the appropriate
  399. times.  If your program will only be using one module, or a limited number
  400. of modules all made with the same program, you can make assumptions about
  401. the module, i.e., which interrupt timing routine to call, etc.  A minimum
  402. implementation might be as simple as two calls:  one to the initialization
  403. code, and one during each VBlank interrupt.  (Many modules do nothing more
  404. than turn audio DMA off in their exit code.) Of course, if you want to be
  405. compatible with more modules, you'll have to put in a little more effort.
  406.  
  407.     If you are the author of a music-composer program, I hope you will
  408. seriously consider supporting GMOD modules.  As I said at the top of this
  409. document, I'll do everything I can to make life easy for you.  A minimal
  410. implementation can be done very quickly and with very little overhead.
  411. Modules with built-in players are becoming more and more popular, and I
  412. believe a standard, extensible caller interface could benefit both
  413. musicians and programmers.
  414.  
  415.  
  416.  
  417.                         The XMOD format (obsolete)
  418.                         ~~~~~~~~~~~~~~~~~~~~~~~~~~
  419.  
  420.     Besides the standard module formats that MultiPlayer understands, there
  421. is also a new format you can use to turn just about anything into a
  422. multitasking module.  Basically, you go into the program you want to rip
  423. the music out of with some kind of debugger (I use S.I.M.), find the player
  424. and module (with luck, they'll be right next to each other), and add an
  425. XMOD header at the beginning so that MultiPlayer (or another player that
  426. supports XMOD format) knows what to do with it.
  427.     The XMOD format is very simple:
  428.  
  429. module+$00    'XMOD'
  430. module+$04    InitEntry
  431. module+$08    PlayEntry
  432. module+$0c    StopEntry
  433.  
  434.     When MultiPlayer loads a module and finds an 'XMOD' in the beginning of
  435. it, it first calls module+$04 to let the player initialize itself and the
  436. module, then it calls module+$08 50 times per second (the normal vertical
  437. blanking interrupt speed on PAL systems), and finally calls module+$0c when
  438. the user stops the module.  The player must be PC-relative, or it must do
  439. its own relocation in the initialization code.  The entire file is always
  440. loaded into chip memory in one continuous block.
  441.     All three entrypoints must use standard Amiga calling conventions and
  442. save registers D2-D7/A2-A6.  This includes the PlayEntry - it may not
  443. modify A5/A6 even though it's normally called from an interrupt routine.
  444. They may use D0-D1/A0-A1 without restoring them.  No parameters are passed
  445. to any of the entries, and no return codes are checked for.  All three
  446. routines must return with an RTS, not with an RTE.
  447.     If you need to debug an XMOD module (i.e.  it doesn't work the first
  448. time), you can just set the first instruction at module+$04 to 'illegal'
  449. ($4AFC), activate a good debugger, and load the module from MultiPlayer.
  450. As soon as MultiPlayer calls the module's init routine, it should kick into
  451. the debugger.
  452.  
  453.  
  454.  
  455.                         The AMOD format (obsolete)
  456.                         ~~~~~~~~~~~~~~~~~~~~~~~~~~
  457.  
  458.     The AMOD format is another special-purpose format MultiPlayer
  459. understands.  It is almost identical to the XMOD format, except in one
  460. respect:  AMOD modules always get loaded at a specific address.  For those
  461. few oddball music composer/player programs that like to write modules with
  462. lots of absolute addresses in them, this format may be the only good way to
  463. make the modules multitask.
  464.     The AMOD format is a simple extension to the XMOD format:
  465.  
  466. address+$00    'AMOD'
  467. address+$04    InitEntry
  468. address+$08    PlayEntry
  469. address+$0c    StopEntry
  470. address+$10    Load address
  471.  
  472.     When MultiPlayer encounters an AMOD module, it allocates memory for the
  473. module starting at the load address specified in the module.  The load
  474. address points to the location where the 'AMOD' identifier should go.  The
  475. module's code does not have to be PC-relative, since the load address is
  476. known.  Other than that, an AMOD module is loaded and executed the same way
  477. as an XMOD module.
  478.     If the required memory area is already occupied (even if it's just a
  479. few bytes somewhere in the required range), the module will not load.
  480. Therefore, the AMOD format should never be used except when absolutely
  481. necessary, since these modules may not be able to load even when there is
  482. enough memory available.  The memory does get allocated (the space is not
  483. just stomped on as with most European demos), so it will not cause the
  484. machine to crash; it just might not load sometimes when it should be able
  485. to.  I wish this module type wasn't necessary, but sometimes we just have
  486. to clean up somebody else's messes, don't we?
  487.  
  488.  
  489.  
  490.                               Contact Address
  491.                               ~~~~~~~~~~~~~~~
  492.  
  493.     The most reliable way of contacting me is at my parents' address.  It
  494. may take a while for me to get something sent there, but it WILL get to me.
  495. I tend to move around a great deal, so mail sent directly to me sometimes
  496. has a hard time catching up.  My parents' address is:
  497.  
  498.     Bryan Ford
  499.     8749 Alta Hills Circle
  500.     Sandy, UT 84093
  501.  
  502.     I can be reached more quickly (for the time being anyway) on the phone
  503. or through one of the electronic mail addresses below:
  504.  
  505.     (801) 585-4619
  506.     bryan.ford@m.cc.utah.edu
  507.     baf0863@cc.utah.edu
  508.     baf0863@utahcca.bitnet
  509.  
  510.     If you want to mail me something quickly, FIRST call or E-mail me
  511. to make sure I'm still here, then send it to this address:
  512.  
  513.     Bryan Ford
  514.     27104 Ballif Hall
  515.     University of Utah
  516.     Salt Lake City, UT 84112
  517.  
  518.