Digital Sound & Music Interface for OS/2

Written by Julien Pierre

Introduction
Typically, the audio capabilities of multimedia applications are restricted by hardware. The Digital Sound & Music Interface (DSMI) for OS/2 changes all that.

What DSMI Offers
DSMI was created to overcome the limitations of the sound hardware and let programmers access a greater number of channels. DSMI allows you to use up to 32 PCM channels simultaneously, even if your hardware has only one channel.

DSMI also offers APIs to load the music "module" files. Modules are conceptually similar to Musical Instrument Digital Interface (MIDI) files, in that they contain a full song with the list of its notes, duration, effects, and other information. But unlike MIDI files, which are limited to the sounds your MIDI synthesizer offers, module files also contain information about each "instrument", stored as PCM samples. That means you can use any sound you record from your sound card as an instrument in the song -- such as your voice. For this reason, and because of the limitations of the MIDI hardware found on most PC sound cards, modules typically sound much better than MIDI.

How DSMI Works
DSMI has several layers:
 * Application
 * Advanced Module Player
 * Channel Distributor Interface
 * Multi Channel Player
 * Dynamic Driver System
 * Sound Device Interface

This section briefly describes all of these layers except for the application layer. A typical DSMI application uses the Advanced Module Player and/or the Channel Distributor Interface.

Advanced Module Player
The Advanced Module Player (AMP) is a set of APIs that lets you manipulate the loading and playback of module files. It can handle a variety of module formats like *.MOD, *.NST, *.STM, *.S3M, *.MTM, *.FAR, *.669, and *.AMF. If you just want to play music using modules, you do not need to use the other layers.

AMP also provides functions to pause and resume the music, and to get the position in the music, so that you can synchronize your program with the music. You can also change the global tempo and volume of the song, go forward or backward in the song, enable or disable certain channels in the song, or set the panning of a channel.

The panning is the position of a channel on a stereo system. DSMI supports panning values from -63 for full-left channels to +63 for full-right channels. Intermediate values mean that the channel is played on both left and right channels, with proportional volumes. A panning of 0 means that the channel is set to the middle (ie, it is played at full volume on both left and right speakers). In addition, DSMI supports Dolby Prologic Surround. If you set the panning of a channel to 100, the channel will be played on the surround speaker. If you do not have a Dolby Prologic Surround system, you will still hear the channel on both speakers, like a middle channel. Internally, the AMP uses Channel Distributor Interface (CDI) calls to play the music.

Channel Distributor Interface
The Channel Distributor Interface (CDI) is a low-level interface. It does not control music like the AMP, but instead gives you full control of individual channels. You will want to use CDI for sound-effects playback.

The CDI lets you independently access the DSMI channels. On each channel you can set the digital instrument that will be used; control the playback frequency; get and set the panning, position and volume; get a status; as well as mute, unmute, play, and stop a note (sound). CDI also lets you globally apply digital effects, like reverberation, filters, or chorus. You can even write your own digital effects.

Multi Channel Player
The Multi Channel Player (MCP) is essentially a software digital mixer. For devices without hardware mixing, MCP is registered as the mixing system that CDI will use. Devices with hardware mixing support bypass the MCP and provide their own mixing system.

Conceptually, mixing in MCP is accomplished simply by adding the digital waveforms and shifting them so they do not clip. However, the implementation of MCP is in reality much more complex, because MCP also allows you to control the playback sampling rate, volume, panning, and many other effects, on each channel. If you consider the high-data rates of audio -- 176,400 bytes per second at 16 bits, 44 kHz stereo (CD quality) -- and the fact that MCP has to mix each of the 32 possible channels at that data rate, you can see why it can keep your computer very busy. For that reason, the MCP and several other DSMI components were written in 32-bit assembly language.

Dynamic Driver System
The Dynamic Driver System (DDS) is a means of passing functions between different parts of DSMI. It is especially used to register and get access to the Sound Device Interface (SDI) functions when you use them.

Sound Device Interface
DSMI provides device independence by means of the Sound Device Interface (SDI). CDI makes calls to the mixing system - usually MCP - which in turn calls the SDI to actually reach the sound hardware.

DSMI has four built-in SDIs: Nosound, File, DART, and MMPM/2. The Nosound SDI does not use a sound card. This SDI mostly helps you debug your programs. The other three SDIs all register MCP as their mixing system because they do not inherently support mixing. The DART and MMPM/2 SDIs provide true device independence and allow DSMI to work with all sound cards that are supported by OS/2; you do not need an SDI for each device.

If you have a sound card with hardware mixing support, like the MWave, you might want to write a special SDI for it that would not register the MCP for mixing and would offload the mixing from the main CPU to the sound card.

A Practical Program Example
This multimedia sample program is written in text mode to be simple, so that you can concentrate on the DSMI APIs you are interested in.

This program is called Simple Module Player, and the full source is available in DSMI\PROGRAMS\SOURCE\SMP\SIMPLE.CPP after you have installed DSMI. It demonstrates the initialization of DSMI and loading and playback of module files. You can use it to play the DSMI\PROGRAMS\EXAMPLE.AMF module file that comes with DSMI, or thousands of other module files that you can get from ftp://ftp.cdrom.com/pub/demos/music/songs/ or other Internet sites.

First, you need to include the DSMI headers:
 * 1) include "dsmi.h"    // headers for DSMI.DLL
 * 2) include "init.h"    // headers for DRIVERS.DLL

Then, declare a pointer to a MODULE structure to hold the information about the module music file: MODULE *mod;

You will also want a function to report errors and quit the program: void quit(char *string, int errorcode)// function to handle errors { puts(string);                       // display error message exit(errorcode);                   // quit the program };

DSMI needs to be initialized to work properly. Here is a function to initialize DSMI: void init             // function to initialize the DSMI sound system { int result; char channels=2,        // stereo resolution=2,      // 16 bits samplingrate=SR_44K;// 44 kHz long buffer=0;          // autodetect DART buffer SDIFUNC SDIStartOutput;

result = initDSMI( &channels, &resolution,                    &samplingrate, QUALITY_ENABLED, AMP_AUTO, NULL,                     ID_DART, &buffer, AUTO_DETECT); // try to initialize DSMI with DART

if (result!=0) result = initDSMI( &channels, &resolution,                      &samplingrate, QUALITY_ENABLED, AMP_AUTO, NULL,                       ID_MMPM2, NULL, AUTO_DETECT); // try to initialize DSMI with MMPM/2

if (result!=0) quit("Error initializing DSMI.\n",2); // could not initialize DSMI with either driver

DDSAccessFunction("SDIStartOutput", // access the start output function                    (void**)&SDIStartOutput);

if (SDIStartOutput!=0)// start sound {   closeDSMI;          // close DSMI sound system quit("Error starting playback.\n",3); }; }; DSMI is initialized by way of a call to initDSMI. First, it tries to initialize it in DART mode. If this call fails, it tries to initialize DSMI using regular MMPM/2 mode. If this call fails too, the program is terminated.

The first argument to initDSMI is a pointer to the number of channels: 1 for mono, 2 for stereo. The second argument is the pointer to the resolution you want to use: 1 for 8 bits, 2 for 16 bits. The third argument is a pointer to the sampling rate. Sampling rates are defined by constants in INIT.H. A sampling rate of SR_44K is used for 44 kHz output in this sample program.

The next parameter tells DSMI if you want quality mode or not. Quality mode is required for 8-bit mode. In 16-bit mode, it improves the quality of the mixing, but at the expense of CPU cycles. Next comes the amplification mode. Constants for this mode are also defined in INIT.H.

The next parameter is only used by the File SDI to pass the name of the filename you want to direct the output to; it is set to NULL since the DART and MMPM/2 SDIs ignore it. The following parameter is very important because it specifies which SDI to use. Four SDI constants are available: ID_NOSOUND, ID_FILE, ID_DART, and ID_MMPM2. Then, you need to pass a pointer to a variable holding the DART buffer size you want. The smaller the DART buffer, the more real-time your program will be. This pointer is only useful when you initialize the DART SDI; so it is set to NULL for MMPM/2.

The last parameter specifies whether auto detection is to be used. When you pass the AUTO_DETECT parameter, the values you specify in channel, resolution, and sampling rate are ignored, and the initDSMI call will try to automatically detect the capabilities of the device and get the best possible quality on the SDI. The actual values used will be returned in the variables you passed across as pointers.

Next, you call DDSAccessFunction to get a pointer to the SDI's start output function, and you call it to actually start the sound. At this point, you have not sent any data to play to DSMI. Therefore, your speakers will remain silent, but the device will be playing even if you do not notice it.

This section of the program loads and plays the music module file: void module(char *module)// function to load and play music module file { int result; result = ampLoadModule(module,0,&mod); // load the module

if (result!=MERR_NONE) {   closeDSMI;         // close DSMI sound system quit("Error loading module",4); };

cdiSetupChannels( 0, mod->channelCount); // setup the channels

ampPlayModule(mod,0); // play the module without looping }; You just need one call to ampLoadModule to load your file with the filename, the load options (set to 0 for the default behavior), and a pointer to a pointer to a MODULE structure. It will try to load the file in every format supported by DSMI until it succeeds, and will return an error if none of them are suitable. When it succeeds, it allocates a MODULE structure, and returns a pointer to it at the address specified by the third argument.

After the module is successfully loaded, you call cdiSetupChannels to allocate the number of sound channels that you want. The first parameter specifies the number of sound devices you want to use. This parameter is ignored at present because DSMI only supports one device at a time. The second parameter specifies the number of channels that you want. This parameter must be between 0 and 32. In this code, the number of channels that the module uses is passed to the CDI layer.

You then call ampPlayModule with the MODULE structure as its argument and the playback options. In this example, the playback options are set to 0 because we do not want the module to loop. A value of PM_LOOP can be used for looping.

Here is the function that you call at the end of the program, after the music finishes playing or the user interrupts it: void stopmodule { ampStopModule;        // stop the module ampFreeModule(mod);    // free the module memory closeDSMI;           // close DSMI sound system }; Next is the main function of the program: int main(int argc,char*argv[]) { char ch=0;

puts("Simple Module Player 1.0 for OS/2, based on DSMI 3.1\n"); puts("(c) Julien Pierre, June 1996.\n");

if (argc!=2) quit("Syntax : SMP module-filename.\n",1);

init; // initialize DSMI module(argv[1]); // play the module file

puts("Press ESC to stop module.\n");

do { DosSleep(1); if (kbhit) ch = getch; } while( (ch != 27)// wait for the escape key       && (ampGetModuleStatus & MD_PLAYING) ); // or until the module is finished playing

stopmodule;// stop the module return 0; }; The program takes the module file name as its argument. If the number of arguments is correct, it tries to initialize DSMI and load the file. It then loops until the user presses the escape key, or until the module finishes playing. The loop uses the ampGetModuleStatus call in AMP to know when playback is over.

Summary
Although Simple Module Player has a very basic user interface, it provides a good program example. If you want more control and a flashier interface, get Dual Module Player for Presentation Manager.