. AMOS & ASSEMBLER . by . Michael D. Cox -------------------------------------------------------------------------- Before we begin, you need to have either a commercial or PD assembler. For PD, there is A68k. For commercial, there are several. DevPac II/III, CAPE 68k, ASSEMPRO (don't go near this one), and even the AMOS Assembler. I use DevPac II (hope to have III soon) as it is easy and fast. Besides, all the great programmers use it!!! A68k is a good assembler also. Heck, it's free! It is available on the Fish disks and probably many bulletin board systems. I will even try and have it included on this issue of the AMONER disk. If not and you don't mind spending a few bucks for postage, mail me a disk or two and I will send it to you without charging return postage. You'll also need a good Amiga Programmer's book. Something like the RKMs, Amiga Programmer's Handbook Vol. I & II (SYBEX), or Amiga System Programmer's Guide (ABACUS). You will need one of these because they explain the libraries and how the work. They also tell you what the offset is for each function in a library. You need these offsets for use with the EXECALL, DOSCALL, etc. commands. Next, you need to become familiar with some of the AMOS commands that deal with the assembler routines. They are on pages 285 to 287 in the AMOS manual. - PLOAD "fielname",bank This will load your compiled assembler program into bank. No need to reserve the bank first, but make sure you don't use a reserved one (like 1 to 4). Once loaded with PLOAD, the assembler routines will be saved with your program! - CALL bank[,params] This executes your program at bank. Params are the arguments passed to your routines. Thoroughly read the manual and what it says about Params and the registers. - =AREG= and =DREG= These arrays let you set the registers used in your routines. However, for the AREG, only 0, 1, and 2 are passed when you CALL your routine. All of the DREGs are passed when CALL is executed. Timeout for a little assembler lesson: You have two types of registers: ADDRESS and DATA. ADDRESS registers hold addresses of certain things. They can hold the address of a variable, a buffer, a library, and so on. They 'point' to where the data begins. When they point to a string variable, the string must end in a NULL or CHR$(0). DATA registers hold things like values. Results of certain commands are returned in the DATA registers. For instance, when you Open a Library, using the OpenLibrary function in exec.library, D0 will either contain a 0 if the OpenLibrary failed, or the starting address of where the library is located in memory. As an example: Let's open the medplayer.library. First, we need to know what the OpenLibrary function requires in order to work. I look in my RKMs or any of the Amiga programming manuals available that discuss the EXEC library for OpenLibrary. Okay, here is the format: address = OpenLibrary(LibName,version) D0 = A1 D0 This tells me that when the OpenLibrary function is finished, it will return the results in D0. And, I need to set A1 to where (an address) the name of the library is stored (a string variable). In D0, I say which version of the library I want to use (We set it to 0, which means we will use any version). Now, in AMOS, we would do this: _OPENLIBRARY = -$228 : Rem Offset of OpenLibrary function in EXEC A$="medplayer.library"+Chr$(0) : Rem Null terminate the string NAME_POINTER = Varptr(A$) : Rem Varptr tells me the address of A$ Areg(1) = NAME_POINTER : Rem Set A1 to address of the library name Dreg(0) = 0 : Rem Set D0 to 0 for any version R=Execall(_OPENLIBRARY) Now, if R = 0, then it failed. Otherwise, save R to a variable like MED_LIB_POINTER as it contains the address of where medplayer.library is located in memory. Okay, enough rambling.... You need to also become familiar with the DOSCALL, EXECALL, GFXCALL, and INTCALL functions. And, on page 281, VARPTR is explained. Okay, we are ready to continue once you are familiar with the above commands. If not, keep your manual nearby (who doesn't?). So, we know how to open a library. How do we close it? Just as simple! We lookup CloseLibrary and find: CloseLibrary(library) A1 Which tells us?? Anyone, anyone? Right, Johnny! We put the address of where the library is located in A1. No return value is expected, so nothing else needs to be done. So, if we opened the library as above, we would do the following to close it: Rem Use _CLOSELIBRARY as AMOS reserves Close _CLOSELIBRARY = -$19E : Rem Offset of CloseLibrary in EXEC Areg(1)=MED_LIB_POINTER : Rem Set A1 to address of library R=Execall(_CLOSELIBRARY) : Rem Call the CloseLibrary function End Now, if you typed the above segments into AMOS, make sure medplayer.library is in you LIBS: directory. It is included on this AMONER disk and also disk 4. So, what does this have to do with assembler? Well, a lot, but it has nothing to do with our assembler. So, first, we need to write a little assembler routine that will get the medplayer ready and one routine to get rid of the player. The following routines were taken from the documentation for medplayer.library and have been subtly modified to work with AMOS. Load up your assembler, type in each routine below and assemble it as an executable and re-locatable. GetPlayer: move.l a6,-(sp) ; Save pointer to program on the stack pointer (SP) movea.l a2,a6 ; Move address in A2 (MED_LIB_POINTER) into A6 jsr -$1e(a6) ; Jump to the GetPlayer routine in medplayer.library movea.l (sp)+,a6 ; Return program pointer back to A6 rts ; Return control back to AMOS FreePlayer: move.l a6,-(sp) ; Save pointer to program on the stack pointer (SP) movea.l a2,a6 ; Move address in A2 (MED_LIB_POINTER) into A6 jsr -$24(a6) ; Jump to the FreePlayer routine in medplayer.library movea.l (sp)+,a6 ; Return program pointer back to A6 rts ; Return control back to AMOS Okay, have you assembled these routines into seperate executables? Good, let's load up AMOS. Load in the _OPENLIBRARY and _CLOSELIBRARY program. ESCape to Direct mode and load your two assembler routines into two separate banks. I used Bank 9 for GetPlayer and Bank 12 for FreePlayer. For example: AMOS> pload "ram:getplayer",9 AMOS> pload "ram:freeplayer",12 Go back to the editor and save the program! Remember, when dealing with unfamiliar territory, save before doing anything. Now, we need to look at what the two medplayer.library functions need in order to work. error = GetPlayer (midi) Freeplayer() D0 D0 So, GetPlayer returns a 0 to D0 if everything works fine. Before CALLing GetPlayer, we set D0 to a 0 since we don't want any MIDI. FreePlayer requires no arguments to work. Make a mental note: Even though the functions do not say they need the MED_LIB_POINTER, they always do. Since A0 to A2 registers are passed when you CALL a routine, we will put MED_LIB_POINTER into A2. You could use A0 or A1, but some functions will need to use those also. Alright, we are ready to enter our AMOS Procedures to GetPlayer then FreePlayer. Procedure _GET_PLAYER [LIB_POINTER,MIDI] Dreg(0) = MIDI Areg(2) = LIB_POINTER Call 9 FLUBBED = Dreg(0) If FLUBBED = 0 Print "Unable to GetPlayer. Press any key to exit" Wait Key End End If End Proc Procedure _FREE_PLAYER [LIB_POINTER] Areg(2) = LIB_POINTER Call 12 Print "Player Freed. Press any key to exit." Wait Key End End Proc Now, when we call the procedures, we do: _GETPLAYER[MIDI,MED_LIB_POINTER] ? "Press Any key to FreePlayer" Wait key _FREE_PLAYER [MED_LIB_POINTER] That's all there is to it, folks! What's that, Bubba? You want to load and play a MED Module? Well, just go back to the menu and select the MED player! If there is room, there should be a MED module on the disk. You readers should write a player too. Mail me the completed program and I will include them all on the next disk. Below is all the MEDPlayer docs and assembler routines. It contains all you need to know to write the routines and is the original that came with the original archive. So, mail me with your comments (good or bad)!!! Michael D. Cox 13600 EDS Drive MS A5N-B51 Herndon, VA 22071 Email: aj639@Cleveland.freenet.edu (Next issue, look for my review of Stephen Hill's book AMIGA GAME MAKER'S MANUAL (WITH AMOS BASIC) from SIGMA press.) ++++++++++++++++++++++++++++END+++++++++++++++++++++++++++++++++++++++++ ; medplrlib_stub.a: stub routines for calling medplayer.library from ; C-compilers which don't support direct library ; calling XREF _MEDPlayerBase XDEF _GetPlayer XDEF _FreePlayer XDEF _PlayModule XDEF _ContModule XDEF _StopPlayer XDEF _DimOffPlayer XDEF _SetTempo XDEF _LoadModule XDEF _UnLoadModule XDEF _GetCurrentModule XDEF _ResetMIDI CODE _GetPlayer: MOVE.L 4(SP),D0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$1E(A6) MOVEA.L (SP)+,A6 RTS _FreePlayer: MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$24(A6) MOVEA.L (SP)+,A6 RTS _PlayModule: MOVEA.L 4(SP),A0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$2A(A6) MOVEA.L (SP)+,A6 RTS _ContModule: MOVEA.L 4(SP),A0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$30(A6) MOVEA.L (SP)+,A6 RTS _StopPlayer: MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$36(A6) MOVEA.L (SP)+,A6 RTS _DimOffPlayer: MOVE.L 4(SP),D0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$3C(A6) MOVEA.L (SP)+,A6 RTS _SetTempo: MOVE.L 4(SP),D0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$42(A6) MOVEA.L (SP)+,A6 RTS _LoadModule: MOVEA.L 4(SP),A0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$48(A6) MOVEA.L (SP)+,A6 RTS _UnLoadModule: MOVEA.L 4(SP),A0 MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$4E(A6) MOVEA.L (SP)+,A6 RTS _GetCurrentModule: MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$54(A6) MOVEA.L (SP)+,A6 RTS _ResetMIDI: MOVE.L A6,-(SP) MOVEA.L _MEDPlayerBase,A6 JSR -$5A(A6) MOVEA.L (SP)+,A6 RTS END +++++++++++++++++++++++++++END+++++++++++++++++++++++++++++++++++++++++++++ Instructions for using "medplayer.library" V1.11, by Teijo Kinnunen. -------------------------------------------------------------------- "medplayer.library" is a shared library, which can be used to load and play MED modules. You can call its functions from any language which supports library calls (C, Assembler, Basic...) First you must install "medplayer.library" to your LIBS: drawer. You can also load it with ARP's "loadlib" command. The current version number of medplayer.library is V1.11. It's similar to V1.10 released with MED V3.00 (only one bug fix). "medplayer.library" supports only 4-channel MED songs!! Here's the complete list of the functions of "medplayer.library" (in RKM autodoc-style): --------------------------------------------------------------------------- --------------------------------------------------------------------------- GetPlayer NAME GetPlayer -- get and initialize the player routine SYNOPSIS error = GetPlayer(midi) D0 D0 FUNCTION This routine allocates the audio channels and CIAB timer A/B and prepares the interrupt. If "midi" is nonzero, serial port is allocated and initialized. You should call this routine when your programs starts up. INPUTS midi = 0 no midi, 1 set up midi. When you use a song that has only Amiga samples, there's no reason to allocate the serial port. Then set midi to 0. RESULT If everything is OK, GetPlayer() returns zero. If initialization failed or somebody else is currently using the library, then GetPlayer() returns nonzero value. NOTE: Even if GetPlayer() returned an error, you can still call the library functions without making harm. They just won't work (except LoadModule(), UnLoadModule() and GetCurrent- Module(), which always work). SEE ALSO FreePlayer --------------------------------------------------------------------------- --------------------------------------------------------------------------- FreePlayer NAME FreePlayer -- free the resources allocated by GetPlayer() SYNOPSIS FreePlayer() FUNCTION This routine frees all resources allocated by GetPlayer(). Remember always call this routine before your program exits. It doesn't harm to call this if GetPlayer() failed. If you don't call this function during exit, audio channels, timer etc. will remain allocated until reboot. SEE ALSO GetPlayer --------------------------------------------------------------------------- --------------------------------------------------------------------------- PlayModule NAME PlayModule -- play module from the beginning SYNOPSIS PlayModule(module) A0 FUNCTION This routine starts to play the module from the beginning. The module can be obtained by calling LoadModule() or it can be part of your program (when saved as an object file and linked with it or included with some assemblers "binary include" option). INPUTS module = pointer to module. If zero, then play the current module (module which was played last). SEE ALSO ContModule, StopPlayer, DimOffPlayer --------------------------------------------------------------------------- --------------------------------------------------------------------------- ContModule NAME ContModule -- continue playing the module from where it stopped SYNOPSIS ContModule(module) A0 FUNCTION ContModule() functions just like PlayModule() except if you have stopped playing with StopPlayer(), the playing will continue where it stopped. When you play the module first time, you should use PlayModule(), because ContModule() doesn't initialize the filter. INPUTS module = pointer to module. If zero, use the current module. SEE ALSO PlayModule, StopPlayer, DimOffPlayer --------------------------------------------------------------------------- --------------------------------------------------------------------------- StopPlayer NAME StopPlayer -- stops playing immediately SYNOPSIS StopPlayer() FUNCTION Stop. SEE ALSO PlayModule, ContModule, DimOffPlayer --------------------------------------------------------------------------- --------------------------------------------------------------------------- DimOffPlayer NAME DimOffPlayer -- fade out the volume and stop playing SYNOPSIS DimOffPlayer(dimlength) FUNCTION Fades out the volume and stops the playing. The routine returns immediately after you've called it. Then the sound will start fading. If you want to know when the player has stopped, you can examine the playstate-field of the current module. INPUTS dimlength = how slowly should the sound fade, in lines e.g. DimOffPlayer(60) fades the sound in 60 lines SEE ALSO PlayModule, ContModule, StopPlayer --------------------------------------------------------------------------- --------------------------------------------------------------------------- SetTempo NAME SetTempo -- modify the playing speed SYNOPSIS SetTempo(tempo) D0 FUNCTION If you want to modify the playback speed, you can call this one. This number should be 1 - 240. Note that tempos 1 - 10 are recognized as SoundTracker tempos. INPUTS tempo = new tempo --------------------------------------------------------------------------- --------------------------------------------------------------------------- LoadModule NAME LoadModule -- load a MED module from disk and relocate it SYNOPSIS module = LoadModule(name) D0 A0 FUNCTION When you want to load a module from disk, call this function. The function loads only MED modules (MMD0). It doesn't load Tracker-modules, MED songs or object files. Only MMD0's (MMD0 is the identification word at the beginning of the file). Because the module contains many pointers, they must be relocated. This function relocates the module automatically. If you link songs saved as object files, they will be relocated by the AmigaDOS. Only if you include the module as a binary file, then YOU must relocate it. This is an easy thing to do. You can use the "relocmod" function from "loadmod.a". INPUTS name = pointer to file name (null-terminated) RESULT module = pointer to module. If failed to load for some reason (disk error, out of memory, not a module), zero will be returned. SEE ALSO UnLoadModule --------------------------------------------------------------------------- --------------------------------------------------------------------------- UnLoadModule NAME UnLoadModule -- frees the module from memory SYNOPSIS UnLoadModule(module) A0 FUNCTION When you don't need the module anymore, you MUST free the memory it has used. Use this routine for it. Remember to stop the player before unloading the module it is playing. NOTE: unload only those modules which are loaded with LoadModule(). If you attempt to free module which is a part of the program, you will cause guru 81000009/81000005. INPUTS module = pointer to module. If zero, nothing happens. SEE ALSO LoadModule --------------------------------------------------------------------------- --------------------------------------------------------------------------- GetCurrentModule NAME GetCurrentModule -- returns the address of module currently playing SYNOPSIS module = GetCurrentModule() D0 FUNCTION Simply returns the pointer of the module, which is currently playing (or if player is stopped, which was played last). This works also if some other task is currently playing. In this case, because of multitasking, you should have no use for the value (the module can be already unloaded). You may ask what use this function has. Well, I'm not sure, but because this function takes only 2 machine language instructions (8 bytes of memory) there's no much harm of it. RESULT module = pointer to current module --------------------------------------------------------------------------- --------------------------------------------------------------------------- ResetMIDI NAME ResetMIDI -- reset all pitchbenders and modulation wheels and ask player to resend the preset values SYNOPSIS ResetMIDI() FUNCTION This function resets pitchbenders and modulation wheels on all MIDI channels. It also asks the player to send again the preset change requests for all instruments, so that the presets will be correct if the user has changed them. It performs the same function as MED's Ctrl-Space. --------------------------------------------------------------------------- ---------------------------------------------------------------------------