Chapter 21

Using MIDI with a Background Plug-In


CONTENTS


Introduction

The release of Netscape Navigator 3.x gives plug-ins the capability to run in background mode. Background mode is denoted by NP_BACKGROUND during the NPP_New API. This mode has no plug-in window and is for plug-ins that can perform their duties without the need for user interaction through the Navigator.

The Musical Instrument Device Interface (MIDI) allows for recording musical sequences in very small files. MIDI records the actual note value rather than sampling audio with a waveform digitizer. For example, a middle C on a piano can be represented with a single byte of data. MIDI does have its limitations. You can't record voice, and the audio can sound tinny and unrealistic.

For the purposes of a Web page, MIDI can provide a great enhancement with little increase in system and network resources. Popular games such as DOOM use MIDI as background music with great results. MIDI can also be mixed in real-time with waveform data on most audio cards such as the Sound Blaster. DOOM uses this technique for shotgun blasts and monster roars mixed in with eerie background music.

You can easily mix MIDI with waveform also. To prove this, just bring up the MCIWnd sample with a page that has both a .WAV and .MID file embedded in it. You can then play both at the same time! Try making a plug-in that provides for background MIDI with short waveform riffs intermixed. Use this background MIDI player as a base and add waveform support.

Where to Find the Files

All the necessary files for building NPMIDI.DLL are located on the CD-ROM. These files are in the \CODE\MIDI, \CODE\INC and \CODE\COMMON directories. You will need the files shown in the following lists.

For the directory \CODE\MIDI, you need these files:

For the directory \CODE\INC, you need these files:

For the directory \CODE\COMMON, you need this file:

The Design of the Background MIDI Plug-In

Because the Background MIDI plug-in runs in the background, no plug-in window is needed. This makes the code much simpler-no subclassing, no window class, and no user interaction. The plug-in APIs are routed directly to a CMidi class, which in turn opens the MIDI MCI driver with MCI_OPEN and plays the file with MCI_PLAY.

This plug-in is implemented as file-based, nonstreaming. Most MIDI files are very small and can be quickly downloaded before playing.

Embedding as Hidden

Aside from NP_HIDDEN, this sample can also be run as NP_FULL or NP_EMBED. In the case of full and embedded modes, Navigator creates the appropriate plug-in window, which the MIDI plug-in totally ignores. You probably will only run a hidden plug-in embedded in a page with the appropriate HIDDEN parameter in your HTML code. This parameter prevents Navigator from creating a plug-in window. Embed the sample like this:

<embed SRC=canyon.mid HIDDEN>

Note
The HIDDEN attribute is one of the many attributes you can use with the EMBED tag. Refer to Chapter 7, "Navigator Plug-In Design Considerations," for more information on using HIDDEN and other attributes.

Components

This sample uses only one class to implement the plug-in. The class, CMidi, is called from the standard plug-in APIs. It opens the MIDI MCI driver and plays the MIDI file. As you can see in Figure 21.1, this plug-in has very little interaction with the Navigator.

Figure 21.1 : The components of the Background MIDI Player plug-in.

Plug-In Entry Points

Like any plug-in, this one has at least stub code for all plug-in APIs.

Of the methods, only NPP_New, NPP_Destroy, and NPP_StreamAsFile do the work of this sample. The other APIs are provided as either stub code or areas for future enhancement.

NPP_New

The NPP_New method creates a CMidi object. It passes a pointer to the plug-in's instance, pInstace. After that, it saves a pointer to CMidi in the Navigator-provided instance structure and saves the mode. The parameters argc, argn, and argv, which would normally contain EMBED tag attributes, are not parsed in this example.

//
// NPP_New - Create a new plug-in instance.
//
NPError NP_LOADDS NPP_New (NPMIMEType pluginType,
    NPP pInstance,
    uint16 mode,
    int16 argc,
    char* argn[],
    char* argv[],
    NPSavedData* saved)
{  
    if (pInstance == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;

    // Create a new CMidi object

    CMidi* pMidi = new CMidi (pInstance);

    // Attach it to the instance structure

    pInstance->pdata = pMidi;

    // Save our plug-in's mode

    pMidi->mode = mode;

    return NPERR_NO_ERROR;
}

NPP_Destroy

This plug-in's instance is destroyed during the NPP_Destroy API. Here, the CMidi object is deleted. Notice the check to make sure that pMidi is not NULL after retrieving it from the Navigator-provided instance structure. Also note that pInstance->pdata is set to NULL after the CMidi object is deleted. Precautions such as these can save your plug-in from trapping in the event of a Navigator calling order error.

//
// NPP_Destroy - Destroy our plug-in instance.
//
NPError NP_LOADDS NPP_Destroy (NPP pInstance, NPSavedData** save)
{
    CMidi* pMidi = (CMidi *)pInstance->pdata;

    if (pMidi)
    {
        delete pMidi;
        pInstance->pdata = NULL;
    }
    return NPERR_NO_ERROR;
}

NPP_SetWindow

A hidden plug-in is one of the few types in which you can completely ignore the NPP_SetWindow API. As you might remember, the NPP_SetWindow plug-in API allows your plug-in to associate itself with the Navigator-created plug-in window. Because a background plug-in has no window, this method can simply return the following code:

//
// NPP_SetWindow - A window was created, resized, or destroyed.
//
NPError NP_LOADDS NPP_SetWindow (NPP pInstance, NPWindow* window)
{
    return NPERR_NO_ERROR;
}

NPP_NewStream

When NPP_NewStream is called, this plug-in sets *stype to NP_ASFILE, which sets up for a file-based plug-in and a future call to NPP_StreamAsFile. Navigator 3.0 plug-ins should use NP_ASFILEONLY instead of NP_ASFILE. This flag is documented in Chapter 10, "Stream Creation and Destruction."

After the plug-in type is set, CMidi::Open is called to notify the object of a new stream.

//
// NPP_NewStream - A new stream was created.
//
NPError NP_LOADDS NPP_NewStream(NPP pInstance,
    NPMIMEType type,
    NPStream* pStream,
    NPBool seekable,
    uint16* stype)
{
    if(!pInstance)
        return NPERR_INVALID_INSTANCE_ERROR;

    CMidi* pMidi = (CMidi *)pInstance->pdata;

    *stype = NP_ASFILE;

    if (pMidi)
        pMidi->Open (pStream);

    return NPERR_NO_ERROR;
}

NPP_WriteReady and NPP_Write

Like most file-based plug-ins, NPP_WriteReady and NPP_Write are not really used. A large value is returned during NPP_WriteReady to tell Navigator that the plug-in can handle any buffer size. Calls to NPP_Write just return the length.

//
// NPP_WriteReady - Returns amount of data we can handle for the next NPP_Write
//
int32 NP_LOADDS    NPP_WriteReady (NPP pInstance, NPStream *stream)
{
    return 0x0FFFFFFF;
}

//
// NPP_Write
//
int32 NP_LOADDS NPP_Write (NPP pInstance,
    NPStream *stream,
    int32 offset,
    int32 len,
    void *buffer)
{
    return len;
}

NPP_StreamAsFile

When the file is ready for the plug-in, NPP_StreamAsFile is called with the filename. This file can be in either Navigator cache or another directory, depending on its origin. The method CMidi::GotFileName is called to start processing the file.

//
// NPP_StreamAsFile
//
void NP_LOADDS NPP_StreamAsFile (NPP pInstance,
    NPStream* stream,
    const char* fname)
{
    CMidi* pMidi = (CMidi *)pInstance->pdata;
    
     if (pMidi)  
        pMidi->GotFileName (fname);
}

NPP_DestroyStream

Although the NPP_DestroyStream plug-in doesn't need to know when the stream is destroyed, a call is made into a stub method, CMidi::EndOfStream, for any future implementation.

//
// NPP_DestroyStream
//
NPError NP_LOADDS NPP_DestroyStream (NPP pInstance,
    NPStream *stream,
    NPError reason)
{
     CMidi* pMidi = (CMidi *)pInstance->pdata;
    
     if (pMidi)  
        pMidi->EndOfStream ();
          
    return NPERR_NO_ERROR;
}

The CMidi Class

The Background MIDI Sample plug-in uses a class called CMidi to implement its main tasks. These tasks include opening, playing, and closing the MIDI file/device. MIDI playback is controlled through the use of the Windows Media Control Interface (MCI). The following are the methods of this class:

CMidi::CMidi
CMidi::~CMidi
CMidi::Open
CMidi::GotFileName
CMidi::EndofStream

Construction and Destruction

The CMidi constructor saves the plug-in instance (which is not used) and zeroes out the device ID. The destructor closes the MIDI MCI device with a call to mciSendCommand using the MCI_CLOSE flag.

//
// Constructor
//
CMidi::CMidi (NPP pInstance)
{
    this->pInstance = pInstance;
    wDeviceID = 0;
}


//
// Destructor
//
CMidi::~CMidi()
{
    MCI_GENERIC_PARMS    Close;

    memset(&Close, 0, sizeof(Close));

    MCIERROR rc = mciSendCommand (wDeviceID,
        MCI_CLOSE,
        MCI_WAIT,
        (ULONG)&Close);
}

Open

CMidi::Open saves the stream instance pointer, NPStream*. Additionally, the URL is saved. Although neither of these are used, it's nice to have them around. For example, referring to the URL can tell your plug-in where the file originated (Internet, Intranet, or local drive).

//
// Open
void CMidi::Open (NPStream* pStream)
{
//
    this->pStream = pStream;

    strcpy (this->url, pStream->url);
}

GotFileName

After the file has been fully downloaded, CMidi::GotFileName is called with the name of that file. This method opens the MIDI MCI device with an element name. The device ID is saved and used to play the MIDI file with MCI_PLAY. Notice that the MCI_PLAY uses an MCI_NOTIFY flag to allow for asynchronous playback.

Normally, you would specify a callback window handle in the MCI_OPEN_PARMS structure's dwCallback member. This window handle allows an MCI driver to post messages to a window indicating device status such as media position, media stopped, and so on. In this example, because the plug-in does not have a window, a notification window is not used. The dwCallback member is set to NULL during a call to memset.

//
// GotFileName
//
void CMidi::GotFileName (const char* fname)
{
    // Open the MIDI file

    MCI_OPEN_PARMS Open;

    memset(&Open, 0, sizeof(Open));

    Open.lpstrElementName = fname;

    MCIERROR rc = mciSendCommand (0,
        MCI_OPEN,
        MCI_WAIT | MCI_OPEN_ELEMENT,
        (ULONG)&Open);

    wDeviceID = Open.wDeviceID;

    // Play it async

    MCI_PLAY_PARMS Play;

    memset(&Play, 0, sizeof(Play));

    rc = mciSendCommand (wDeviceID, MCI_PLAY, MCI_NOTIFY, (ULONG)&Play);
}

EndOfStream

CMidi::EndOfStream does nothing and is provided for any future code.

//
// EndOfStream
//
void CMidi::EndOfStream ()
{
}

Running the Plug-In

The MIDI Hidden sample should be run embedded in a page with the HIDDEN attribute specified on the plug-in's command line. This attribute prevents Navigator from creating a plug-in window. The following HTML runs the plug-in in hidden mode:

<html>
<body>

<embed SRC=canyon.mid HIDDEN>

<h1 align=center>MIDI Plug-in</h1>

<h2 align=center>This is a hidden plug-in and has no window.</h2>

</body>
</html>

Even though this plug-in has no visible aspects, it can really add life to your pages.

Conclusion

MIDI is a great feature to add to Web pages through the use of a MIDI background plug-in. Don't forget that you can mix wave audio with MIDI for some great effects!

Background MIDI is implemented with the mciSendCommand Windows API using MCI_OPEN, MCI_PLAY, and MCI_CLOSE. The MIDI music is played asynchronously by using MCI_NOTIFY with MCI_PLAY. In the MCI_PLAY_PARMS structure, dwCallback is 0 to prevent the use of a notification window.

What's Next?

This chapter focused on a plug-in without any window, and the next chapter discusses subclassing a plug-in window without using a class library. In the next chapter, the Windows APIs SetProp and GetProp are used to save and retrieve instance data.