UltraMID Programmer's Reference Guide Revision 1.00.00 1 July 1993 Forte Technologies 1555 East Henrietta Rd. Rochester, N.Y. 14623 FAX (716) 292-6353 BBS (716) 427-2921 5 NOTICE The information contained in this manual is believed to be correct. The manual is subject to change without notice and does not represent a commitment on the part of FORTE or Advanced Gravis. Neither FORTE nor Advanced Gravis make a warranty of any kind with regard to this material, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. Neither FORTE nor Advanced Gravis shall be liable for errors contained herein or for incidental or consequential damages in connection with the furnishing, performance or use of this material. This document contains proprietary information which is protected by copyright. This manual is Copyright (C) 1992,1993 by FORTE and Advanced Gravis. All rights are reserved. No part of this document may be reproduced, transmitted, transcribed, stored in a retrieval system, or translated into any human or computer language, in any form or by any means; electronic, mechanical, magnetic, optical, chemical, manual or otherwise, without the expressed written Forte Technologies UltraMID Programmer's Reference Guide Page 2 Copyright Notice permission of FORTE and Advanced Gravis. Any copying, duplication, selling, or otherwise distributing the program or support files described in this manual, other than for the limited purposes of system backup and loading the program into the computer as part of executing the program, is a violation of the software license agreement and the law. Willful violation of the copyright law of the United States can result in statutory damages of up to $50,000 in addition to actual damages, plus criminal penalties of imprisonment for up to one year and/or a $10,000 fine. Forte Technologies UltraMID Programmer's Reference Guide Page 3 Chapter 1 - Introduction UltraMID is a TSR program that has approximately 20 functions to support general midi music synthesis, smart patch loading, and DMA-driven digital audio playback. UltraMID is an alternative to the low-level SDK. It provides very high level access to the UltraSound card so that a programmer can write software to synthesize General Midi music with minimal effort. In fact, the UltraMID TSR is easier to use than other popular midi processors. UltraMID also makes it very easy for programmers to produce digital audio sound effects with a minimal amount of additional code and memory. This document is a technical document with the following information: A brief overview of the UltraSound card; An overview of how the digital audio section works; An overview of how the midi section works; And Complete documentation on each of the functions in UltraMID. Also included with this documentation are some programming examples. The following files should be of some interest to you: * umwav.c: A .WAV file player in C. * aumwav.c: A .WAV file player written in assembly. * ummidi.c: a .MID file player written in C. This example consists of 4 .c files. The file totsr.c contains the UltraMID interface. This example is based on the source code used for playmidi.exe which comes with the UltraSound card. ____________________________________________________________ Forte Technologies UltraMID Programmer's Reference Guide Page 4 Chapter 2 - The UltraSound audio card The UltraSound audio card ************************************************************ Block Diagram: +---------------------------------------------------------------------+ | +---------------+ | | |MEMORY MEMORY| Amplified Out (st) o | | | +-------+ Line Out (stereo) o | |MEMORY MEMORY|----| GF1 | / | | | | ASIC |---I/O Ports/DMA | | |MEMORY MEMORY| +-------+ / \ Midi/Joystick port | | | | / \ | | |MEMORY MEMORY| / \ \ | +---------------+ / \ Mic input (stereo) o | (Memory hold all sounds) / \ Line input (stereo) o |_________________________ / ___\ ___| |___16bit___________| |__8bit_____________| | | The heart of the UltraSound card is based on an ASIC chip called the GF1. The GF1 has no general purpose processing capabilites. It is "hardwired" pipelined processor which performs a very specific task. Simply put, it interpolates sound data, applies attenuation to produce pan effects and volume, sums data, and produces two digital signals which are passed on to the analog circuitry to produce sound. The low-level SDK provides routines to write to all of the registers in the GF1 chip and to control each of these operations. UltraMID requires no knowledge of the GF1 internal process or registers, but it is useful to understand a little bit about how this works so that you can visualize what's happening "underneath the hood." The GF1 has a single speed at which it operates internally. Unlike other digital processors which can play sound data at varying speeds, the GF1 uses a completely different method for frequency control. If a sample is recorded at 44.1Khz, the GF1 plays that sample by sending each byte in order to the DACs. If you want to play back the sound at 22Khz, the GF1 will interpolate values between the sound data points in memory to achieve the slower rate. Forte Technologies UltraMID Programmer's Reference Guide Page 5 Chapter 2 - The UltraSound audio card The interpolated sample values are then multipled by values in the pan register to produce to sample values, one for the left ear, and one for the right. The GF1 then applies volume attenuation based on the value of the volume register. The two values are finally converted to and analog signal. The GF1 can play up to 32 voices by repeating the steps listed above. The GF1 has 32 seperate sets of registers to control volume, pan, and position. UltraMID uses 24 voices. Each voice is independant from the other voices. They can point to different memory, play at different rates, volumes, pan positions, and data types. The voices can play data forward, backward, loop forward, loop backward, loop bidirectionally. The volume of each voice can be modified in a similar fasion. Given a start volume, end volume, and a rate, the GF1 can automatically make the volume louder, softer, or loop from soft to loud, loud to soft, or bidirectionally. All of these features are used in the midi engine, however, the UltraMID programmer does not have to write any additional code to make use of these features. d Forte Technologies UltraMID Programmer's Reference Guide Page 6 Chapter 3 - Voice Allocation using UltraMID Voice allocation using UltraMID. ************************************************************ The UltraSound card can have up to 32 mono sounds playing at one time, or 16 stereo, or combinations. UltraMID runs the UltraSound card in a mode where 24 of those voices are available. UltraMID will allow you to allocate voices for digital audio using a prioritized scheme. A priority of 0 is the highest priority. If you request a voice with a 0 priority, you will get one, and it will remain yours until you give it up. Any other priority means that your voice can get stolen from you if another request is made at a higher priority. Voices are also aged. If you allocate 25 voices with a priority of 1, then the 25th voice will be obtained by stealing the first voice that was allocated. This is because the first voice has been allocated the longest and because UltraMID uses 24 voices. If a voice was allocated as part of a group of voices, e.g. a stereo sample, and the voice gets stolen, then the other voice will get stolen also. MIDI voices are allocated dynamically and automatically. Each voice is allocated initially with a priority of 1. The voices priority is lowered as it plays through its envelope. So, a midi note that is fading out has a better chance of getting stolen then a new note that is sustaining. For example, if notes A and B start at the same time, and note A sustains while note B starts to taper off, then note B will be stolen before note A. N | O B| **********---------- T A| ******************** E +---+--------------------------------------------------------------- Time --> ^- start release for note B. Voices for digital audio playback are assigned a priority by your application. Forte Technologies UltraMID Programmer's Reference Guide Page 7 Chapter 4 - Digital Audio playback with UltraMID. Digital Audio playback with UltraMID. ************************************************************ The UltraSound card has its own DRAM seperate from that of the PC which it uses to store data for playback. Each of the voices points to memory locations on the UltraSound card. The UltraSound adds up all of the data at these memory locations and sends it to the left and right stereo outputs for playback. In order to play an 8K sample, for example, you must allocate memory on the card, transfer the sound data to this memory, and start the voice. You must also set the initial pan position (balance), volume, sample rate, data type (8, 16, signed, unsigned, stereo, mono). A question that might come to your mind is, what if I want to play a sample bigger than 1Mb, let's say, 2Mb. UltraMID was designed especially to handle the case where you want to play large samples with limited memory. UltraMID will allow you to allocate a small buffer, lets say 8K for example, and then split up your 2MB sample into many smaller chunks. | | |-------------|-------------| 0K 4K 8K Initially, there is no data in the 8K buffer. You start by reading 4K of data from the PC hard disk, and sending it to UltraMID. |** S | |^------------|-------------| 0K 4K 8K UltraMID will start to DMA the data (*) into the UltraSound card at the fastest rate supported by the UltraSound which can be up to 680Kb/s. At the same time, the UltraMID starts the voice playing (^). A moment later: Forte Technologies UltraMID Programmer's Reference Guide Page 8 Chapter 4 - Digital Audio playback with UltraMID. |*************S | |--^----------|-------------| 0K 4K 8K Notice how the DMA transfer has completed much earlier than the voice. A 44.1Khz voice is 15 times slower than the optimal DMA transfer rate. At this point UltraMID will call your application through a far address that you provide and ask you for MORE_DATA. If your application has another buffer ready, it will return it to UltraMID, and the next DMA transfer will occur. |***************************S |---------^---|-------------| 0K 4K 8K As time goes by, the voice will finally reach your original first 4K mark and then UltraMID will call you back telling you that it no longer needs access to that buffer (BUFFER_DONE). | **************S |-------------^-------------| 0K 4K 8K Immediately following the BUFFER_DONE callback, you will receive a MORE_DATA callback. If you respond with more data, than things will look like this: |*************S*************Loop |-------------|^------------| 0K 4K 8K In this example your application passed UltraMID 4K buffers, however, UltraMID can take any size buffer and it will manage the data internally in the 8K buffer in the same fashion for you. If you had 256K of data on the PC that you wanted to play, then you would pass the whole thing to UltraMID. When UltraMID got to the last 4K of the data, it would make a callback to you asking for MORE_DATA. You could than respond with another buffer of any size. Recommendations: * The UltraSound card can only DMA from PC memory into 32 byte boundaries in the card. If you present a buffer that is not a multiple of 32 bytes, then UltraMID will have to do direct I/O to the card which takes more CPU time than a typical DMA. If you make sure your buffer sizes are a multiple of 32 bytes, than the polled I/O to the card will be avoided. Forte Technologies UltraMID Programmer's Reference Guide Page 9 Chapter 4 - Digital Audio playback with UltraMID. * If the sample you want to play is not entirely in PC memory, but you are reading it from disk, then there are several things to be aware of. All hard disks have a higher transfer rate then the fastest sound you will play on your UltraSound. However, the transfer rate is not the only variable to pay attention to. It takes a relatively long time for the data on the spinning disk to find its way underneath the read/write head of the drive. It takes much longer than that for the disk arm to move from track to track. It is sometimes better to use many small buffers than it is to use 2 very large buffers. In the .WAV player example (aumwav.asm or umwav.c), the number and size of the buffers can be changed, try adjusting the numbers and try playing different .WAV files on different drives. (The assembly version must have a buffer size which is a power of 2). There is an example program that you should have received with this document that will play a .WAV formatted file using UltraMID. If you are interested in the C version, look at umwav.c. If you are interested in the assembly version look at aumwav.asm. Neither example requires a C library. You should be able to compile the examples use the Borland C++ 3.10 compiler or the Borland assembler (tasm). The structure shown below is used to start a digital audio playback. It can be found in the files ultramid.h (c), ultramid.inc (masm), and ultmidi.inc (tasm ideal). um_sound_struct struc um_sound_data label dword um_sound_data_off dw ? um_sound_data_seg dw ? um_stereo_mem label dword um_stereo_mem_off dw ? um_stereo_mem_seg dw ? um_sound_len dd ? um_gf1mem dd ? um_pan db ? um_volume dw ? um_sample_rate dw ? label um_voice word um_priority dw ? um_data_type db ? um_callback_addr label dword um_callback_addr_off dw ? um_callback_addr_seg dw ? um_sound_struct ENDS Forte Technologies UltraMID Programmer's Reference Guide Page 10 Chapter 4 - Digital Audio playback with UltraMID. Here is a brief description of each of the fields of this structure: * um_sound_data label dword * um_sound_data_off dw ? * um_sound_data_seg dw ? A far pointer to your sound data to start playback. There is no requirements about alignment of this data in PC memory. You can put it wherever you want, and it can even cross a 64K segment boundary. * um_stereo_mem label dword * um_stereo_mem_off dw ? * um_stereo_mem_seg dw ? The UltraSound card cannot play interleaved stereo data without also jumping through hoops and doing magic tricks when running with 24 active voices. UltraMID will split up interleaved stereo data into smaller buffers before it DMA's it into the UltraSound card. This field should be a far pointer to PC memory which contains as much memory as 1/4 of the buffer used on the UltraSound card. In the above example, the UltraSound buffer was divided in half for mono playback. In a stereo situation, the buffer is divided 4 ways. UltraMID will use this memory for DMA transfers. The example used an 8K buffer, so um_stereo_mem should point to a 2K PC buffer for that example. |left |right | |------|------|------|------| 0K 2K 4K 6K 8K * um_sound_len dd ? The length of the sample to be played in bytes. Be careful not to split up a sample when dividing your sound data into multiple buffers. For example, one 16-bit-stereo sample is 4 bytes. Therefore um_sound_len should be a multiple of 4 for that data type. * um_gf1mem dd ? The address of the memory allocated on the UltraSound card. This is the address where data will be transferred for playback. You can allocate memory with the function ETSR_ALLOCATE_MEMORY, Fand free it with E TSR_FREE_MEMORY. F * um_pan db ? The balance control for the voice. 0 is all the way to the left, and 15 is all the way to the right. * um_volume dw ? A value from 0 to 4095. This is a logarithmic volume scale. To (approximately) convert from a linear scale Forte Technologies UltraMID Programmer's Reference Guide Page 11 Chapter 4 - Digital Audio playback with UltraMID. (0 - 65535) to a log scale: / 256 * log(65536/linear_volume) \ log_volume = 4096 - | ------------------------------- | \ log(2.0) / or from a log scale (0 - 4095) to a linear scale (0 - 65536): 65536 linear_volume = --------------------------------------------------- (4096-log_volume) * ( ln(2) / 256 ) e The file vol.inc has a short table which can be used to implement a linear volume scale. * um_sample_rate dw ? The sample rate that the sound was recorded at. * label um_voice word * um_priority dw ? On entry to ETSR_START_DIGITAL, Fthis contains the priority of the voice. 0 is the highest priority. If you use a priority of 0, the voice will remain allocated until you have completed playback of your sample. If you use a lower priority, such as 1, then the voice may be stolen when another request is made to UltraMID for another voice. Voices are allocated to turn on midi notes and when calls to ETSR_START_DIGITAL F are made. If the operation is successful, the voice number is returned. Otherwise a -1 is returned. The voice number is used in following calls to change volume, pan, or stop the voice. * um_data_type db ? um_data_type is a mask containing the following values: UM_8BIT EQU 1 ; 1 use 8 bit data ; 0 use 16 bit data UM_PRELOAD EQU 2 ; preload data (not implemented yet) UM_INVERT_MSB EQU 4 ; invert most significant bit during dma UM_STEREO EQU 8 ; 1 for stereo data The GF1 always plays unsigned data. If you want the GF1 to play signed data, then set the UM_INVERT_MSB flag. This will cause the data to get converted in hardware during the DMA transfer. If you are playing 8 bit mono signed data, then you would pass to UltraMID: (UM_8BIT OR UM_INVERT_MSB). If you are playing 16 bit unsigned stereo data, then you would pass to UltraMID: (UM_STEREO). These are the most common data types. Forte Technologies UltraMID Programmer's Reference Guide Page 12 Chapter 4 - Digital Audio playback with UltraMID. * um_callback_addr label dword * um_callback_addr_off dw ? * um_callback_addr_seg dw ? This should be a far pointer to a routine in your code to handle callbacks from UltraMID. UltraMID will call your code to tell you when a buffer has completed playback, when it needs more data, and when it is done playing and has deallocated the voice. If you fill this field with 0:0, then you won't get a callback. The voice will remain playing until the sample has completed. If you plan on calling UltraMID with no callback address, then you should use a non-zero priority so that voice-stealing can take place. An example of how digital audio works is in the file umwav.c (c code) and in aumwav.asm (assembly). This example will play a .WAV file. Forte Technologies UltraMID Programmer's Reference Guide Page 13 Chapter 5 - MIDI playback with UltraMID. MIDI playback with UltraMID. ************************************************************ UltraMID will accept a general MIDI data stream and synthesize music from that data. The data is passed to UltraMID through two functions. If you want to pass UltraMID a byte at a time, you would call ETSR_MIDI_OUT. FIf you want to send a string of midi data, then you would call E TSR_MIDI_OUT_STR. F The UltraSound card has no build in patches (instruments). There is no ROM or FM synthesizer. All patches must be loaded before you start your MIDI sequence. The patches can be loaded when UltraMID starts with the -c parameter, or your application can load patches with little effort using UltraMID. UltraMID has two modes of operations. If UltraMID is started with the "-c" parameter, then a subset of the general midi instrument set will be downloaded into the card. The programmer will have to do no additional work to get sounds into the card. This is probably the most appropriate mode of operation for people who already have existing drivers for other cards and can't find a way to load instruments without changing the other drivers. The other mode of operation requires that the programmer load the correct patches before playback starts. UltraMID has several functions to help load the patches. ETSR_LOAD_MIDI_PATCHES Ftakes a far pointer to the beginning of a track from a standard midi file and loads all of the patches that will be needed for that track. ETSR_LOAD_XMIDI_PATCHES Ftakes a far pointer to the beginning of the event list in an xmidi file and loads all of the patches that will be needed for that track. ETSR_LOAD_PATCH Fwill load a patch from a program change. If you know you are going to be doing program changes to 0 (piano 1), then you would call ETSR_LOAD_PATCH Fwith 0. ETSR_UNLOAD_PATCH Fwill clear a patch from the UltraSounds memory. Take a look at the ummidi.exe example to see how to play standard midi files with UltraMID. Ummidi.c is the main program which calls all of the other parts. midifile.c reads the midi file into memory. Midiout.c interprets the midi data and makes calls to totsr.c which contains all of Forte Technologies UltraMID Programmer's Reference Guide Page 14 Chapter 5 - MIDI playback with UltraMID. the calls to UltraMID. Totsr.c also handles the system timer. P Forte Technologies UltraMID Programmer's Reference Guide Page 15 Chapter 6 - List of functions supported by UltraMID ************************************************************ E TSR_START_DIGITAL F equ 0 This function will play continuous digital data of any length. It supports the following data types: * 8 bit signed mono data * 16 bit signed mono data * 8 bit signed interleaved (l/r) stereo data * 16 bit signed interleaved (l/r) stereo data * 8 bit unsigned mono data * 16 bit unsigned mono data * 8 bit unsigned interleaved (l/r) stereo data * 16 bit unsigned interleaved (l/r) stereo data This function will start a playing digital data by transferring the data described in the um_sound_struct into the UltraSound card at the specified address. The callback address (um_callback_addr, in the um_sound_struct) should point to a function that you supply. This function will get for the following reasons: 1) UM_STOP_SOUND: the voice has been stopped or stolen, and must no longer be used by your application. If the voice was stolen, and you have more data to play, than another voice must be allocated and started. 2) UM_MORE_DATA: UltraMID needs more data so that there is a smooth playback without clicks. This callback does NOT mean that the last buffer is done playing. It just means UltraMID Forte Technologies UltraMID Programmer's Reference Guide Page 16 Chapter 6 - List of functions supported by UltraMID is ready for the next buffer. When you get this callback, UltraMID will look at the return value to figure out what to do next. The list of return values is described below. 3) UM_BUFFER_DONE: UltraMID is done with a buffer. This means that a buffer has completed playing, and you can re-use that PC memory. When you receive the UM_MORE_DATA callback, you can take one of the following actions: * UM_STOP_SOUND: This will tell UltraMID that you have no more data available and it is OK to deallocate the voice when the data is done playing. If you have more data that you want to play, but it is not available (it is still on the hard disk), you can use the next option. You may receive more than one UM_MORE_DATA request even after you return UM_STOP_SOUND. UltraMID will stop sending callbacks only after UltraMID calls YOUR application with UM_STOP_SOUND. * UM_PAUSE: This will tell UltraMID to pause playback. This return value may be ignored until UltraMID is done playing all of the data it has shipped to the card. At this point, the UM_MORE_DATA callback will occur again. If you return UM_PAUSE again, then it will finally pause. When eventually you do have more data to play, you can call the function E TSR_PLAY_NEXT_BUFFER Fto restart the voice with new data. UM_PAUSE should only be used to prevent UltraMID from deallocating the voice. It is different from the ETSR_PAUSE_DIGITAL F command, which pauses playback immediately. * UM_MORE_DATA: This is the value you should return if you actually have more data for UltraMID to start playing. UltraMID passes your callback routine several parameters on the stack. If you are using C, you can use the following prototype: int far um_callback(int reason, int voice, unsigned char far * far *buf, unsigned long far *size, unsigned short far *rate Forte Technologies UltraMID Programmer's Reference Guide Page 17 Chapter 6 - List of functions supported by UltraMID If you are using assembly, you can access these parameters by creating a stack frame: push bp; mov bp, sp; In large memory model: * reason:word [bp+6] * voice:word [bp+8] * buf:dword ptr [bp+10] * size:dword ptr [bp+14] * rate:word ptr [bp+18] Descriptions of the callback parameters: * Ereason: FOne of the reasons listed above. * Evoice: FThe same number as the voice returned from ETSR_START_DIGITAL. F * Ebuff: FIf you are responding to UM_MORE_DATA with UM_MORE_DATA, then this address should be filled in with the address of your next buffer. * Esize: FIf you are responding to UM_MORE_DATA with UM_MORE_DATA, then this address should be filled in with the size of your next buffer. * Erate: FIf you are responding to UM_MORE_DATA with UM_MORE_DATA, then this address should be filled in with the sample rate of your next buffer. This option was added because DIGPAK required double buffering with varying rates for playback. The rate will not change until the new buffer starts playback. If you need to modify the rate immediately, call ETSR_SET_RATE. F input: es:di - far pointer to sound structure output: AX will have the voice that was allocated or -1 if no voice was available. The voice field in the structure will also contain the value. E TSR_PLAY_NEXT_BUFFER F equ 1 If the callback routine is called and you pause the Forte Technologies UltraMID Programmer's Reference Guide Page 18 Chapter 6 - List of functions supported by UltraMID voice, this routine can be called to send UltraMID the next buffer and also continue playback. input: es:di - far pointer to sound structure output: none E TSR_SET_PAN F equ 2 This function will be ignored for stereo data. For a mono piece, it will let you set the position of the sound from 0 left to 15 right. The total power of the left and right line level outputs is maintained as you pan from one side to the other. input: cx - voice bx - pan output: none E TSR_SET_VOLUME F equ 3 This function will let you change the output volume for a specific voice. The range of volumes is from 0 to 4095. This call actually does a volume ramp from the old volume to the new volume. It will actually take about 1ms (maximum) to reach the desired volume. This will prevent any clicks or pops in the audio. VERY TECHNICAL NOTE: If you split up the 12 bit volume number, the most significant four bits are the exponent, and the least significant 8 bits are the mantissa. EEEEMMMMMMMM. Ignoring pan, for a peak-to-peak 16-bit sine wave, the line level output voltage (RMS) is: Lvrms = 1.7 * (256+M) / (512 * (2 ^ (15-E))) which gives you a line level output range from 0 to 1.7 volts. This means for a volume of 0, you will get -91.7dbv, and for a volume of 4095, you will get +4.6dbv. This means the UltraSound has a range of 96dB!. 96dB is extremly loud and can hurt your ears. More information on volume control: Since the UltraSound card is always summing 24 voices, you should keep the volumes below the maximum so that clipping doesn't occur. The highest midi volume is 4016. The digital volume can go up to 4095, but it might cause noise in the output if there are other sounds playing. You will get good results if you limit your audible digital output to the range between 2048 and 4016. If it turns out your MIDI music is too loud, the user can lower the midi volume by running UltraMID with the -m parameter. For example, UltraMID -m100. Input: Forte Technologies UltraMID Programmer's Reference Guide Page 19 Chapter 6 - List of functions supported by UltraMID cx - voice bx - volume output: none E TSR_SET_RATE F equ 4 not implemented yet E TSR_PAUSE_DIGITAL F equ 5 This function will pause a voice immediately. It can be restarted with ETSR_RESTART_DIGITAL. FNo volume ramp occurs with this function. If you want to pause a playing voice and make sure there are no 'clicks' in the audio, then set the volume to 0 before pausing. When restarting, set the volume back to where it was. input: cx - voice output: none E TSR_RESTART_DIGITAL F equ 6 This function will restart a voice that was paused with E TSR_PAUSE_DIGITAL. FTake a look at ETSR_PAUSE_DIGITAL F above for more information. input: cx - voice output: none E TSR_STOP_DIGITAL F equ 7 This function will stop a playing voice. The callback routine will get called immediately to alert your application that the voice has been deallocated. input: cx - voice output: none E TSR_GET_DIGITAL_POSITION Fequ 8 This function will give you an approximate location of the sample data that is being played for a particular voice. The location will be a far pointer to somewhere in your PC buffer. input: cx - voice output: ax:dx far pointer to your PC buffer. The pointer may be normalized and not have the same segment that you passed in. E TSR_VOICE_STATUS F equ 9 not implemented yet ; MIDI functions E TSR_LOAD_MIDI_PATCHES F equ 10 This function will automatically load the patches that will be needed to play a midi track from a standard midi file. Forte Technologies UltraMID Programmer's Reference Guide Page 20 Chapter 6 - List of functions supported by UltraMID input: es:di is far pointer to the midi track just after the Mtrk header and length. bx:dx is the length of the midi track output: none E TSR_LOAD_XMIDI_PATCHES F equ 11 This function will automatically load the patches that will be needed to play an xmidi track. input: es:di is far pointer to the event buffer including the EVNT header output: none E TSR_LOAD_PATCH F equ 12 This function will load a specific general midi patch. input: cx is the midi number of the patch to load. (0-127 is a melodic patch) (128-255 is a percussion patch) output: none E TSR_UNLOAD_PATCH F equ 13 This function will remove a patch loaded with E TSR_LOAD_PATCH. F input: cx is the midi number of the patch to unload. (0-127 is a melodic patch) (128-255 is a percussion patch) output: none E TSR_START_SEQUENCE F equ 14 No parameters. This function alerts UltraMID that on the next LOAD_XMIDI_PATCHES or LOAD_MIDI_PATCHES, that it is ok to clear the UltraSound's memory before loading new patches. E TSR_UNLOAD_ALL_PATCHES F equ 15 No parameters. Clears all of the patches from the UltraSound's memory. E TSR_MIDI_OUT F equ 16 Pass one byte to the midi interpreter in UltraMID. input: cx - midi byte output: none E TSR_MIDI_OUT_STR F equ 17 Play one or more bytes of midi data (immediately. No timing information should be in this data) input: es:di far pointer to string of midi data cx is the number of bytes. Forte Technologies UltraMID Programmer's Reference Guide Page 21 Chapter 6 - List of functions supported by UltraMID output: none E TSR_ALL_NOTES_OFF F equ 18 start the release for any playing MIDI notes ; resource functions E TSR_ALLOCATE_MEMORY F equ 19 Allocate memory for digital audio playback. If patches are preloaded using the "-c" command, there will be at least 8K of memory in all memory configurations. input: bx:dx amount of memory to allocate from UltraSound's memory output: address in ax:dx, or 0:0 in ax:dx if memory not available. E TSR_FREE_MEMORY F equ 20 input: bx:dx is the address of the memory on the UltraSound card to free output: none E TSR_FREE_TSR F equ 21 Your application shouldn't call this. This will cause the TSR to stop all playing sounds, and free it's memory back to DOS. inputs: none outputs: none E TSR_SEM_ENTER F equ 22 returns non-zero value if ok to call midi functions from an interrupt E TSR_SEM_LEAVE F equ 23 must call this after successful call to ETSR_SEM_ENTER. F E TSR_ADD_EXTERNAL_SEMAPHORE Fequ 24 This call is provided if you are going to use write your own TSR around UltraMID, and it must be compatible with previous versions of that TSR, and, that TSR has a semaphore, AND, that TSR can report the address of it's own semaphore to another caller, AND it has been compiled in a small memory model. This call will let you provide UltraMID with a far pointer to a memory location which UltraMID will use to let your TSR know that it is in a critical region of code and cannot be re-entered from an interrupt. input: bx:dx is a far pointer to your applications semaphore output: none E TSR_CLEAR_EXTERNAL_SEMAPHORE Fequ 25 Tells UltraMID to stop wasting time setting and Forte Technologies UltraMID Programmer's Reference Guide Page 22 Chapter 6 - List of functions supported by UltraMID restoring an external semaphore set with E TSR_ADD_EXTERNAL_SEMAPHORE. F input: bx:dx is a far pointer to your applications semaphore. output: none. E TSR_APP_START F equ 26 This function tells UltraMID that an application will be using the UltraMID vector. E TSR_APP_END F equ 27 This function tells UltraMID that an application is done using the UltraMID vector. Forte Technologies UltraMID Programmer's Reference Guide Page 23 Chapter 7 - How to locate the TSR in memory: How to locate the TSR in memory: ************************************************************ UltraMID tries to use an interrupt vector not in use between interrupt vectors 0x78 and 0x7F inclusive. This vector can be overriden with the -v command line parameter. The segment of the vector which UltraMID uses will have the string "ULTRAMID" located at an offset of 0x103. In pseudo-code: for vector = 0x78 to 0x7f do if far_pointer(seg(vector), 0x103) == "ULTRAMID" then tsr_entry_point = far_pointer(seg(vector), off(vector)) end if end for In masm style assembly, here is a code segment that will find the TSR. ; in your data segment chk_hook_str db 'ULTRAMID' ultramid_hook label dword hookoff dw ? hookseg dw ? ; in your code segment push ds mov al,078h UltraMID is a TSR program that has approximately 20 functions to support mov cx,6 next_vector: mov ah,035h int 21h ; es is segment of gf1 driver. ; di is offset into ULTRAMID.EXE's vector stamp. mov di,0103H mov si,offset chk_hook_str push cx mov cx,8 cld repe cmpsb jcxz vector_valid ; Setup for next vector check. Forte Technologies UltraMID Programmer's Reference Guide Page 24 Chapter 7 - How to locate the TSR in memory: pop cx inc al loop next_vector jmp vector_not_found vector_valid: pop cx pop ds mov hookoff, bx mov hookseg, es ;OK YOU GOT IT - NOW RETURN mov ax, 1 ret vector_not_found: pop ds ;Couldn't find it xor ax, ax ret Forte Technologies 0 E FILE: UMWAV.CF #include #include "ultramid.h" /* The first half of this file contains the interface functions to */ /* UltraMID, and the second half contains the file i/o, wav i/o, and */ /* user interface */ #pragma inline #define MK_LONG (long)((void _seg *)(_DX) + (void near *)(_AX)) void (far *um_hook)(void) = 0L; short volume = 4095; short pan = 7; short voice; unsigned short frequency; int still_playing; int user_request_stop = 0; #define NBUFFS 10 int play_index; /* pointer to buffer which is being played */ int dma_index; /* pointer to buffer which is being xferred */ int fill_index; /* pinter to buffer which should be filled next */ #define BS_EMPTY 0 #define BS_FILLED 1 #define BS_XFERRED 2 #define BS_PLAYING 3 unsigned char buff_status[NBUFFS]; unsigned long buff_len[NBUFFS]; #define BSIZE 4096U #define GF1BSIZE 8192L unsigned char buffs[NBUFFS][BSIZE]; unsigned char stbuff[GF1BSIZE/4]; #pragma warn -par int far um_callback(int reason, int voice, unsigned char far * far*buf, unsigned long far *size, unsigned short far *rate) { /* this function is called from an interrupt */ /* restore our DS register */ asm push ds asm mov ax, cs asm mov ds, ax switch (reason) { case UM_STOP_SOUND: still_playing = 0; break; case UM_MORE_DATA: if (buff_status[dma_index] == BS_FILLED) { *buf = buffs[dma_index]; *size = buff_len[dma_index]; *rate = frequency; buff_status[dma_index] = BS_XFERRED; dma_index = (dma_index + 1) % NBUFFS; asm pop ds return(1); /* return 1 when there is more data */ /* return 0 when done playing */ } break; case UM_VOICE_DONE: buff_status[play_index] = BS_EMPTY; play_index = (play_index + 1) % NBUFFS; if (buff_status[play_index] == BS_XFERRED) { buff_status[play_index] = BS_PLAYING; } break; } asm pop ds return(0); } #pragma warn .par int um_start_digital(struct um_sound_struct *umss) { _ES = FP_SEG(umss); _DI = FP_OFF(umss); _AX = TSR_START_DIGITAL; (*um_hook)(); return(_AX); E Page #1F E FILE: UMWAV.CF } void um_stop_digital(int voice) { _CX = voice; _AX = TSR_STOP_DIGITAL; (*um_hook)(); } void um_restart_digital(int voice) { _CX = voice; _AX = TSR_RESTART_DIGITAL; (*um_hook)(); } void um_pause_digital(int voice) { _CX = voice; _AX = TSR_PAUSE_DIGITAL; (*um_hook)(); } void um_set_pan(int voice, int pan) { _CX = voice; _BX = pan; _AX = TSR_SET_PAN; (*um_hook)(); } void um_set_volume(int voice, int volume) { _CX = voice; _BX = volume; _AX = TSR_SET_VOLUME; (*um_hook)(); } long um_allocate_memory(long mem) { asm mov dx, word ptr mem; asm mov bx, word ptr mem+2; _AX = TSR_ALLOCATE_MEMORY; (*um_hook)(); mem = MK_LONG; return(mem); } void um_free_memory(long mem) { asm mov bx, word ptr mem+2; asm mov dx, word ptr mem; _AX = TSR_FREE_MEMORY; (*um_hook)(); } struct um_sound_struct umss; /**********************************************************************/ /* file i/o functions, console functions, and other library funcs */ /**********************************************************************/ #define CARRY_FLAG 1 void interrupt (*getvect(int int_number))() { _AX = 0x3500; _AL = (char)int_number; geninterrupt(0x21); return(MK_FP(_ES, _BX)); } int my_strncmp(char far *str1, char far *str2, int len) { asm mov dx, ds asm cld E Page #2F E FILE: UMWAV.CF asm les di, str2 asm mov si, di asm mov ax, len asm mov cx, ax asm jcxz done asm mov bx, ax asm xor al, al asm repne scasb asm sub bx, cx asm mov cx, bx asm mov di, si asm lds si, str1 asm repe cmpsb asm mov al, [si-1] asm mov bl, es:[di-1] asm xor ah, ah asm mov bh, ah asm sub ax, bx done: asm mov ds, dx return(_AX); } void myputs(char *s) { while (*s) { _BH = 0; _AL = *s; _AH = 0x0e; geninterrupt(0x10); s++; } } mygetch(void) { _AX = 0x0700; geninterrupt(0x21); _AH = 0x0; return(_AL); } my_open(char *name) { int save_ax, hold_ds; hold_ds = FP_SEG( name ); _DX = FP_OFF( name ); _AH = 0x3d; /* open file */ _AL = 0x0; /* read only */ asm push ds; _DS = hold_ds; geninterrupt(0x21); asm pop ds; save_ax = _AX; if( _FLAGS & CARRY_FLAG ) { return( -1 ); } return(save_ax); } void my_close(int handle) { _BX = handle; _AH = 0x3e; geninterrupt(0x21); } my_read(int handle, unsigned char *buff, unsigned long lsize) { E Page #3F E FILE: UMWAV.CF int hold_ds; unsigned int size; unsigned short seg, offset; unsigned long addr; while (lsize > 0) { size = (lsize > 32768U) ? 32768U : lsize; lsize -= size; hold_ds = FP_SEG(buff); _DX = FP_OFF(buff); _BX = handle; _CX = size; _AH = 0x3f; /* read file */ asm push ds; _DS = hold_ds; geninterrupt(0x21); asm pop ds; if (_FLAGS & CARRY_FLAG) { return(0); } buff += size; } return(1); } /**********************************************************************/ /* Application Functions */ /**********************************************************************/ struct riff_header { char RIFF[4]; unsigned long file_size; char WAVE[4]; char fmt[4]; unsigned long format_size; } riff_header; struct format_header { unsigned int wFormatTag; unsigned int nChannels; unsigned long nSamplesPerSec; unsigned long nAvgBytesPerSec; unsigned int nBlockAlign; unsigned int nBitsPerSample; } format_header; struct data_header { unsigned char DATA[4]; unsigned long size; } data_header; #define MIN(a,b) ((a) < (b) ? (a) : (b)) int far *head_keys = (int far *)0x0040001aL; int far *tail_keys = (int far *)0x0040001cL; #define fast_kbhit() (*head_keys != *tail_keys) void check_buffers(int fp, unsigned long *length) { unsigned long size; static int paused=0; int c; if (fast_kbhit()) { if ((c=mygetch()) == 27) { user_request_stop = 1; um_stop_digital(voice); return; } else if (c == 'P' || c == 'p') { if (paused) { um_restart_digital(voice); } else { um_pause_digital(voice); } paused ^= 1; E Page #4F E FILE: UMWAV.CF } else if (c == '>') { if (pan > 0) { um_set_pan(voice, --pan); } } else if (c == '<') { if (pan < 15) { um_set_pan(voice, ++pan); } } else if (c == ',') { volume -= 50; if (volume < 0) volume = 0; um_set_volume(voice, volume); } else if (c == '.') { volume += 50; if (volume > 4095) volume = 4095; um_set_volume(voice, volume); } } while (buff_status[fill_index] == BS_EMPTY) { /* check to see if there is any more data to read */ if (*length == 0) break; /* if no more data in file - return */ /* fill next buffer */ size = MIN(BSIZE, *length); my_read(fp, buffs[fill_index], size); *length -= size; buff_len[fill_index] = size; buff_status[fill_index] = BS_FILLED; fill_index = (fill_index + 1) % NBUFFS; } } void play(char *filename, unsigned long gf1mem) { int fp; int i; unsigned long length; unsigned short type=0; fp = my_open(filename); if (fp == -1) { myputs("Can't open file "); myputs(filename); myputs(" for playback"); return; } my_read(fp, (unsigned char *)&riff_header, sizeof(riff_header)); if (my_strncmp(riff_header.RIFF, "RIFF", 4) == 0 && my_strncmp(riff_header.WAVE, "WAVE", 4) == 0 && my_strncmp(riff_header.fmt, "fmt ", 4) == 0) { /* read header */ if (my_read(fp, (unsigned char *)&format_header, riff_header.format_size) != 1) { myputs("File "); myputs(filename); myputs(" isn't a .WAV format file"); my_close(fp); return; } if (format_header.wFormatTag != 1) { myputs("This player can't play "); myputs(filename); my_close(fp); return; } if (format_header.nChannels != 1) { type |= UM_STEREO; } if (format_header.nBitsPerSample == 8) { type |= UM_8BIT; type |= UM_INVERT_MSB; } else if (format_header.nBitsPerSample != 16) { myputs("Can't play "); myputs(filename); myputs(". Must be 8 or 16 bits"); my_close(fp); return; } E Page #5F E FILE: UMWAV.CF frequency = format_header.nSamplesPerSec; my_read(fp, (unsigned char *)&data_header, sizeof(data_header)); if (my_strncmp(data_header.DATA, "data", 4)) { myputs("This player can't play "); myputs(filename); myputs(". Can't find data"); my_close(fp); return; } length = data_header.size; user_request_stop = 0; umss.stereo_mem = stbuff; umss.gf1mem = gf1mem; umss.pan = pan; umss.volume = volume; umss.sample_rate = frequency; umss.priority = 0; umss.data_type = type; umss.callback = um_callback; for (i=0; i < NBUFFS; i++) buff_status[i] = BS_EMPTY; fill_index = 0; play_index = 0; dma_index = 0; while (user_request_stop == 0 && length != 0) { check_buffers(fp,&length); buff_status[play_index] = BS_PLAYING; umss.sound_data = buffs[play_index]; umss.sound_len = buff_len[play_index]; dma_index = (dma_index + 1) % NBUFFS; still_playing = 1; voice = um_start_digital(&umss); if (voice == -1) break; while (still_playing) { check_buffers(fp,&length); } } } my_close(fp); } int main(void) { unsigned long mem; int ret, i; int vector; char far *stamp; char filename[80], *cp; char far *command; unsigned char far *command_len; command_len = MK_FP(_psp, 0x80); command = MK_FP(_psp,0x82); if (*command_len <= 1) return(0); command[*command_len-1] = '\0'; cp = filename; while ((*cp++ = *command++) != 0) ; for (vector=0x78; vector <= 0x7f; vector++) { um_hook = (void (far *)())getvect(vector); stamp = (char far *)MK_FP(FP_SEG(um_hook), 0x103); if (my_strncmp(stamp, "ULTRAMID", 8) == 0) break; } if (vector > 0x7f) { myputs("Couldn't find UltraMID\r\n"); return(3); } mem = um_allocate_memory(0x2000L); if (mem == 0L) { myputs("UltraSound card - out of memory"); } else { play(filename, mem); um_free_memory(mem); } return(0); } E Page #6F 0 E FILE: AUMWAV.ASMF LOCALS @@ _TEXT segment byte public 'CODE' _TEXT ends assume cs:_TEXT, ds:_TEXT include ultramid.inc riff_header struc rh_RIFF dd ? rh_file_size dd ? rh_WAVE dd ? rh_fmt dd ? rh_format_size dd ? riff_header ends format_header struc fh_wFormatTag dw ? fh_nChannels dw ? fh_nSamplesPerSec dd ? fh_nAvgBytesPerSec dd ? fh_nBlockAlign dw ? fh_nBitsPerSample dw ? format_header ends data_header struc dh_DATA dd ? dh_size dd ? data_header ends _TEXT segment byte public 'CODE' org 100h start: call _main mov ah,4ch int 21h __psp dw ? _um_hook label dword _hookoff dw 0 _hookseg dw 0 _volume dw 4095 _pan dw 7 _paused db 0 _user_request_stop db 0 _head_keys label dword dw 1ah dw 40h _tail_keys label dword dw 1ch dw 40h _umss um_sound_struct <> chk_hook_str db 'ULTRAMID',0 riffheader riff_header <> formatheader format_header <> dataheader data_header <> BS_EMPTY equ 0 BS_FILLED equ 1 BS_XFERRED equ 2 BS_PLAYING equ 3 NBUFFS equ 10 BSIZE equ 4096 BSIZE_BITS equ 12 _um_callback proc C far,reason,voice,buff:FAR PTR,bufflen:FAR PTR, bufrate:FAR PTR uses ds, si, di mov ax, cs mov ds, ax mov ax,reason cmp ax,UM_STOP_SOUND je short @@stop cmp ax,UM_MORE_DATA je short @@more_data cmp ax,UM_VOICE_DONE E Page #1F E FILE: AUMWAV.ASMF je short @@voice_done mov ax, 0 jmp @@ret @@stop: mov _still_playing,0 jmp @@ret @@more_data: mov bx,_dma_index mov ax, 0 cmp _buff_status[bx],BS_FILLED jne short @@ret mov cl,BSIZE_BITS ; find addr of buffer (_dma_index * BSIZE + offset(buffs)) shl bx,cl add bx,offset _buffs ; fill in buff with next buffer to play les si,buff mov word ptr es:[si+2],ds mov word ptr es:[si],bx ; fill in bufflen with size of next buffer to play mov bx,_dma_index mov cl,2 shl bx,cl mov ax,_buff_len[bx+2] mov dx,_buff_len[bx] les bx,bufflen mov word ptr es:[bx+2],ax mov word ptr es:[bx],dx ; fill in rate with frequency of next buffer les bx,bufrate mov ax,_frequency mov word ptr es:[bx],ax ; change status of _buff_status to BS_XFERRED mov bx,_dma_index mov byte ptr _buff_status[bx],BS_XFERRED ; bump _dma_index mov ax,bx inc ax mov bx,NBUFFS cwd idiv bx mov word ptr _dma_index,dx mov ax,1 jmp @@ret @@voice_done: mov bx,_play_index mov _buff_status[bx],BS_EMPTY ; bump _play_index mov ax,_play_index inc ax mov dx, 0 mov cx,NBUFFS idiv cx mov _play_index,dx ; if buffer was xferred already change status to BS_PLAY mov bx, dx cmp _buff_status[bx],BS_XFERRED jne short @@ret mov _buff_status[bx],BS_PLAYING @@ret: ret _um_callback endp _my_strncmp proc C near, str1:NEAR PTR, str2:NEAR PTR, len:word uses ds, si, di cld mov ax, cs mov es, ax mov di, str2 mov si, di mov ax, len mov cx, ax jcxz short @@done mov bx, ax xor al, al repne scasb sub bx, cx E Page #2F E FILE: AUMWAV.ASMF mov cx, bx mov di, si mov si, str1 repe cmpsb mov al, [si-1] mov bl, es:[di-1] xor ah, ah mov bh, ah sub ax, bx @@done: ret _my_strncmp endp _myputs proc C near string:NEAR PTR uses si mov si,string jmp short @@end_while @@while: mov bh,0 mov al, [si] mov ah,14 int 10h inc si @@end_while: cmp byte ptr [si],0 jne short @@while ret _myputs endp _mygetch proc C near mov ax,700h int 21h xor ah,ah ret _mygetch endp _my_open proc C near fname:NEAR PTR mov dx,fname mov ah,03dh mov al,0 int 21h jnc short @@ret mov ax,-1 @@ret: ret _my_open endp _my_close proc C near handle mov bx, handle mov ah,3eh int 21h ret _my_close endp _my_read proc C near handle, buff, bsize mov dx,buff mov bx,handle mov cx,bsize mov ah,3fh int 21h jc short @@ret mov ax, 1 jmp short @@ret1 @@ret: xor ax, ax @@ret1: ret _my_read endp _check_buffers proc C near fp, blength:NEAR ptr local bsize, c uses si, di ; check the keyboard les bx,_head_keys mov ax, word ptr es:[bx] E Page #3F E FILE: AUMWAV.ASMF les bx,_tail_keys cmp ax,word ptr es:[bx] jne @@check_key jmp @@check_buffers @@check_key: call _mygetch C mov si,ax cmp ax,27 ; check esc key jne short @@check_key_1 mov _user_request_stop,1 mov ax, TSR_STOP_DIGITAL mov cx, _voice call dword ptr _um_hook jmp @@ret @@check_key_1: cmp si,'P' je short @@p_pressed cmp si,'p' jne short @@check_key_2 @@p_pressed: cmp _paused,0 je @@pause mov ax, TSR_RESTART_DIGITAL mov cx, _voice call dword ptr _um_hook jmp short @@flip_paused @@pause: mov ax, TSR_PAUSE_DIGITAL mov cx, _voice call dword ptr _um_hook @@flip_paused: xor _paused,1 jmp @@check_buffers @@check_key_2: cmp si,'>' jne short @@check_key_3 cmp _pan,0 jle short @@check_key_3 dec _pan @@set_pan: mov bx,_pan mov cx,_voice mov ax, TSR_SET_PAN call dword ptr _um_hook jmp short @@check_buffers @@check_key_3: cmp si,'<' jne short @@check_key_4 cmp _pan,15 jge short @@check_key_4 inc _pan jmp short @@set_pan @@check_key_4: cmp si,',' jne short @@check_key_5 sub _volume,50 jnc @@set_volume mov _volume,0 @@set_volume: mov bx, _volume mov cx, _voice mov ax, TSR_SET_VOLUME call dword ptr _um_hook jmp short @@check_buffers @@check_key_5: cmp si,'.' jne short @@check_buffers add _volume,50 cmp _volume,4095 jle short @@set_volume mov _volume,4095 jmp short @@set_volume @@check_buffers: mov di, word ptr [blength] jmp @@end_while @@while: E Page #4F E FILE: AUMWAV.ASMF ; check if next buffer is empty mov ax,word ptr [di] or ax,word ptr [di+2] jne short @@fill_buffer jmp @@ret @@fill_buffer: ; bsize = MIN(BSIZE, blength) cmp word ptr [di+2],0 ja short @@use_BSIZE cmp word ptr [di],BSIZE jbe short @@use_size @@use_BSIZE: mov si,BSIZE jmp short @@do_read @@use_size: mov si,word ptr [di] @@do_read: mov ax,_fill_index mov cl,BSIZE_BITS shl ax,cl add ax,offset _buffs call _my_read C, fp, ax, si sub word ptr [di],si sbb word ptr [di+2],0 mov bx,_fill_index mov cl,2 shl bx,cl mov word ptr _buff_len[bx+2],0 mov word ptr _buff_len[bx],si mov bx,_fill_index mov _buff_status[bx],BS_FILLED mov ax,bx inc ax mov bx,NBUFFS cwd idiv bx mov _fill_index,dx @@end_while: mov bx,_fill_index cmp _buff_status[bx],BS_EMPTY jne @@ret jmp @@while @@ret: ret _check_buffers endp _play proc C near filename:NEAR PTR, gf1mem:dword local fp, i, blength:dword, btype uses si, di mov btype, 0 call _my_open C,[filename] mov fp, ax cmp ax, -1 jne short @@get_riff_header mov ax,offset msg_cant_open call _myputs C,ax call _myputs C,[filename] mov ax,offset msg_cant_open1 call _myputs C,ax jmp @@ret @@get_riff_header: mov ax, offset riffheader mov cx, size riffheader call _my_read C, fp, ax, cx mov bx, offset riffheader.rh_RIFF mov cx, offset msg_RIFF call _my_strncmp C, bx, cx, 4 or ax,ax je @@2 jmp @@not_wav @@2: lea bx, riffheader.rh_WAVE lea cx, msg_WAVE mov ax,4 call _my_strncmp C, bx, cx, 4 or ax,ax E Page #5F E FILE: AUMWAV.ASMF je @@3 jmp @@not_wav @@3: lea bx, riffheader.rh_fmt lea cx, msg_fmt mov ax,4 call _my_strncmp C, bx, cx, 4 or ax,ax je @@4 jmp @@not_wav @@4: mov bx, word ptr riffheader.rh_format_size mov ax, offset formatheader call _my_read C, fp, ax, bx cmp ax,1 je short @@check_tag @@not_wav: mov ax, offset msg_File call _myputs C, ax call _myputs C, filename mov ax, offset msg_isnt_wave call _myputs C, ax @@close: call _my_close C, fp jmp @@ret @@check_tag: cmp word ptr formatheader.fh_wFormatTag,1 je short @@check_nchannels mov ax, offset msg_cant_play call _myputs C, ax call _myputs C, filename jmp short @@close @@check_nchannels: cmp word ptr formatheader.fh_nChannels,1 je @@check_nbits or btype, UM_STEREO @@check_nbits: cmp word ptr formatheader.fh_nBitsPerSample,8 jne short @@check_16bits or btype, (UM_8BIT OR UM_INVERT_MSB) jmp short @@get_frequency @@check_16bits: cmp word ptr formatheader.fh_nBitsPerSample,16 je short @@get_frequency mov ax, offset msg_cant_play call _myputs C, ax call _myputs C, filename mov ax, offset msg_bad_type call _myputs C, ax jmp short @@close @@get_frequency: mov ax,word ptr formatheader.fh_nSamplesPerSec mov word ptr _frequency,ax mov ax, offset dataheader mov bx, size dataheader call _my_read C, fp, ax, bx lea bx, dataheader.dh_DATA mov ax, offset msg_data call _my_strncmp C, bx, ax, 4 or ax,ax je short @@get_length mov ax, offset msg_cant_play call _myputs C, ax call _myputs C, filename mov ax, offset msg_no_data call _myputs C, ax jmp @@close @@get_length: mov ax,word ptr dataheader.dh_size+2 mov dx,word ptr dataheader.dh_size mov word ptr [blength+2],ax mov word ptr [blength],dx mov _user_request_stop,0 mov _umss.um_stereo_mem_seg,ds mov _umss.um_stereo_mem_off,offset _stbuff mov ax,word ptr [gf1mem+2] E Page #6F E FILE: AUMWAV.ASMF mov dx,word ptr [gf1mem] mov word ptr _umss.um_gf1mem+2,ax mov word ptr _umss.um_gf1mem,dx mov al,byte ptr _pan mov byte ptr _umss.um_pan,al mov ax,word ptr _volume mov word ptr _umss.um_volume,ax mov ax,word ptr _frequency mov word ptr _umss.um_sample_rate,ax mov word ptr _umss.um_priority,0 mov al,byte ptr btype mov byte ptr _umss.um_data_type,al mov word ptr _umss.um_callback_addr_seg,cs mov word ptr _umss.um_callback_addr_off,offset _um_callback mov bx,0 @@empty_buffs: mov _buff_status[bx],BS_EMPTY inc bx cmp bx, NBUFFS jne short @@empty_buffs mov word ptr _fill_index,0 mov word ptr _play_index,0 mov word ptr _dma_index,0 jmp @@end_while @@while: lea ax,blength call _check_buffers C, fp, ax mov bx,_play_index mov _buff_status[bx],BS_PLAYING mov ax,bx mov cl,BSIZE_BITS shl ax,cl add ax,offset _buffs mov word ptr _umss.um_sound_data_seg,ds mov word ptr _umss.um_sound_data_off,ax mov cl,2 shl bx,cl mov ax,word ptr _buff_len[bx+2] mov dx,word ptr _buff_len[bx] mov word ptr _umss.um_sound_len+2,ax mov word ptr _umss.um_sound_len,dx mov ax,_dma_index inc ax mov bx,NBUFFS cwd idiv bx mov _dma_index,dx mov word ptr _still_playing,1 mov ax, cs mov es, ax mov di, offset _umss mov ax, TSR_START_DIGITAL call _um_hook mov word ptr _voice,ax cmp ax,-1 je short @@done jmp @@check_playing @@keep_on_checking: lea ax, blength call _check_buffers C, fp, ax @@check_playing: cmp word ptr _still_playing,0 jne short @@keep_on_checking @@end_while: cmp _user_request_stop,0 jne short @@done mov ax,word ptr [blength] or ax,word ptr [blength+2] je @@done jmp @@while @@done: jmp @@close @@ret: ret _play endp _main proc C near E Page #7F E FILE: AUMWAV.ASMF local gf1mem:dword mov ax, cs mov ds, ax mov ax, es mov __psp, ax mov bh, 0 mov di, 80h mov bl, byte ptr es:[di] cmp bl, 1 jg @@find_filename jmp @@ret @@find_filename: dec bx mov si, 82h mov byte ptr es:[bx+si], 0 ; copy file name into this segment push ds push ds mov ax, es mov ds, ax pop es mov di, offset _filename mov cx, 80 cld rep movsb pop ds ; find ultramid's entry point mov al,078h mov cx,6 @@next_vector: mov ah,035h int 21h ; es is segment of gf1 driver. ; di is Offset into UltraMID's vector stamp. mov di,0103H mov si,offset chk_hook_str push cx mov cx,8 cld repe cmpsb jcxz @@vector_valid ; Setup for next vector check. pop cx inc al loop @@next_vector jmp @@vector_not_found @@vector_valid: pop cx mov ah,035h int 21h mov [_hookoff], bx mov [_hookseg], es mov ax, TSR_APP_START call dword ptr _um_hook xor bx,bx mov dx,8192 mov ax, TSR_ALLOCATE_MEMORY call dword ptr _um_hook mov word ptr [gf1mem+2],dx mov word ptr [gf1mem],ax or ax,dx je short @@no_memory mov ax, offset _filename call _play C, ax, [gf1mem] mov dx, word ptr [gf1mem] mov bx, word ptr [gf1mem+2] mov ax, TSR_FREE_MEMORY call dword ptr _um_hook mov ax, TSR_APP_END E Page #8F E FILE: AUMWAV.ASMF call dword ptr _um_hook jmp short @@ret @@no_memory: mov ax, offset msg_no_mem call _myputs C, ax jmp short @@ret @@vector_not_found: mov ax, offset msg_no_um call _myputs C, ax jmp short @@ret @@ret: ret _main endp msg_cant_open db "Can't open file ", 0 msg_cant_open1 db ' for playback',0 msg_RIFF db 'RIFF',0 msg_WAVE db 'WAVE',0 msg_fmt db 'fmt ',0 msg_File db 'File ',0 msg_isnt_wave db " isn't a .WAV format file",0 msg_cant_play db "This player can't play ",0 msg_bad_type db '. Must be 8 or 16 bits',0 msg_data db 'data',0 msg_no_data db ". Can't find data",0 msg_no_um db "Couldn't find UltraMID",13,10,0 msg_no_mem db 'UltraSound card - out of memory',13,10,0 _filename db 80 dup (?) _stbuff db 2048 dup (?) _buffs db BSIZE*NBUFFS dup (?) _buff_len label word db 40 dup (?) _buff_status label byte db NBUFFS dup (?) _fill_index dw ? _dma_index dw ? _play_index dw ? _still_playing dw ? _frequency dw ? _voice dw ? _TEXT ends end start E Page #9F 0 E FILE: TOTSR.CF /* ** Copyright (C) 1993 Forte. All rights reserved ** Written late one night 6/27/93. */ #include #include #include #include #include #include "midi.h" #include "totsr.h" #include "ultramid.h" extern int control_break; /* interrupt clock stuff */ static void interrupt (*orig_clock)(void) = 0L; static void interrupt clock(void); static unsigned long clock_interval = 8334L; /* microseconds */ #define CLOCK_DIVISOR 9944U #define ORIG_CLOCK_DIVISOR 0U /* the timer interrupt is on vector 0x08 */ #define TIMER 0x08 void (far *um_hook)(void) = 0L; int um_data_out( int byte ) { if (um_hook) { _AX = TSR_MIDI_OUT; _CX = byte; (*um_hook)(); } return(0); } void um_pitch_bend( int channel, int msb, int lsb ) { um_data_out( (pitch_wheel+channel) ); um_data_out( msb ); um_data_out( lsb ); } void um_program( int channel, int prog ) { um_data_out( (program_chng+channel) ); um_data_out( prog ); } void um_chanpressure( int channel, int pitch, int pressure ) { um_data_out( (channel_aftertouch+channel) ); um_data_out( pitch ); um_data_out( pressure ); } void um_pressure( int channel, int pitch, int pressure ) { um_data_out( (poly_aftertouch+channel) ); um_data_out( pitch ); um_data_out( pressure ); } void um_parameter( int channel, int control, int value ) { um_data_out( (control_change+channel) ); um_data_out( control ); um_data_out( value ); } int um_note_on( int channel, int note, int velocity ) { int error; E Page #1F E FILE: TOTSR.CF error = um_data_out( (note_on+channel) ); error += um_data_out( note ); error += um_data_out( velocity ); return(error); } void um_note_off( int channel, int note, int velocity ) { um_data_out( (note_off+channel) ); um_data_out( note ); um_data_out( velocity ); } int reset_um( ) { int i; _AX = TSR_ALL_NOTES_OFF; if (um_hook) (*um_hook)(); for (i=0; i < 16; i++) { um_parameter(i, 0x78, 0); /* all sounds off */ um_parameter(i, 0x79, 0); /* reset all controllers */ um_parameter(i, 100, 0); /* RPN MSB 0 */ um_parameter(i, 101, 0); /* RPN LSB 0 */ um_parameter(i, 6, 2); /* Pitch Bend Sensitivity MSB */ um_parameter(i, 38, 0); /* Pitch Bend Sensitivity LSB */ } return(1); } static unsigned long us_timer = 0L; static unsigned long timer = 0L; static unsigned long tempo = 4800L; static void interrupt clock() { short t; us_timer += clock_interval; if (us_timer > tempo) { t = us_timer / tempo; us_timer -= (t * tempo); timer += t; } if (orig_clock) (*orig_clock)(); } void um_set_tempo(us, divisor) unsigned long us; short divisor; { /* ** the tempo and divisor values are described in the Standard Midi ** File specification 1.0 */ int smpte_format, smpte_resolution; if (divisor > 0) { tempo = us / divisor; } else { smpte_format = (-divisor >> 8) & 0x7f; smpte_resolution = divisor & 0x7f; tempo = 1000000 / (smpte_format * smpte_resolution); } if (tempo <= 0) tempo = 1; } void um_reset_tick_counter() { timer = 0L; us_timer = 0L; } int um_wait_for ( unsigned long some_time ) { if (kbhit() || control_break) return(1); E Page #2F E FILE: TOTSR.CF while (some_time > timer) if (kbhit() || control_break) return(1); return(0); } #pragma warn -par int um_init_hardware(struct MIDILIB *ml) { int vector, i; char far *stamp; struct track *track; unsigned long length; for (vector=0x78; vector <= 0x7f; vector++) { um_hook = (void (far *)())getvect(vector); stamp = (char far *)MK_FP( FP_SEG(um_hook), 0x103 ); if (strncmp(stamp, "ULTRAMID", 8) == 0) break; } if (vector <= 0x7f) { midi_message("UltraMID is now loading instrument patches"); if (um_hook) { _AX = TSR_APP_START; (*um_hook)(); } reset_um(); if (um_hook) { for (track=ml->Mp_tracks; track; track = track->next_track) { _ES = FP_SEG(track->data); asm push es _DI = FP_OFF(track->data); asm push di length = track->length; asm mov bx, word ptr length+2 asm mov dx, word ptr length asm pop di asm pop es _AX = TSR_LOAD_MIDI_PATCHES; (*um_hook)(); } _AX = TSR_START_SEQUENCE; (*um_hook)(); } } else { um_hook = 0L; midi_error("Couldn't find UltraMID"); return(1); } return(0); } #pragma warn .par void um_cleanup( void ) { reset_um(); if (um_hook) { _AX = TSR_APP_END; (*um_hook)(); } } void um_init_timers(void) { orig_clock = getvect(TIMER); setvect(TIMER, clock); outportb(0x43, 0x36); outportb(0x40, CLOCK_DIVISOR & 0xff); outportb(0x40, (CLOCK_DIVISOR & 0xff00) >> 8); } void um_cleanup_timers(void) { outportb(0x43, 0x36); outportb(0x40, ORIG_CLOCK_DIVISOR & 0xff); outportb(0x40, (ORIG_CLOCK_DIVISOR & 0xff00) >> 8); if (orig_clock) setvect(TIMER, orig_clock); } E Page #3F