Cakewalk Plug-In Development Kit

DXi Software Synthesizers
DirectShow Plug-Ins
MIDI Effects
Filters

 January 10, 2002

Copyright © 1998- by Twelve Tone Systems, Inc.  All rights reserved.


Cakewalk and SONAR are registered trademarks of Twelve Tone Systems, Inc.
Microsoft, Windows, DirectX, DirectShow, Visual Studio and Visual C++ are registered trademarks of Microsoft, Inc.

Quick Start

If you are an experienced DirectShow, MFX or DXi developer, or otherwise seasoned and battle-scarred, this section will help you cut to the chase.

The bare minimum you probably need to read in this document are the chapters named DXi 2.0 Structures and Types and DXi 2.0 Base Classes.  Armed with that information, you should next explore the TWONAR sample DXi synthesizer, particularly Twonar.cpp.  This should give you a feel for the basic concepts required in building a DXi 2.0 synthesizer.

You should also read about IDeferZeroFill, because knowing how this works can significantly improve the performance of your plug-in.

What’s New in DXi 2.0

DXi 2.0 has been extensively enhanced and reworked, both to provide new capabilities to SONAR 2 and other host applications, and to simplify the development of DXi software synthesizers and DirectX plug-ins.

New Capabilities

Multiple audio output streams from a DXi

MIDI output from an MFX or DXi, to the host

Raw MIDI event processing (e.g. for MTC)

Flexible, high-resolution time-conversion

Per-channel patch, note, RPN and NRPN  names

Efficient processing of silent audio buffers

Pitch marker access

Simplified Development

DXi 2.0 includes an overhauled App Wizard, for use with Microsoft Visual C++.  With this wizard, the coding and deployment of plug-ins is easier than ever.

Fully editable source code

Single self-contained DLL

Simpler DXi coding model

End User Overview

This section describes the various kinds of audio and MIDI plug-ins available in Cakewalk products, and how they behave in those products.  Other host applications may behave differently.

This section focuses on the real-time usage of various kinds of plug-ins, e.g., whether they are used as a “track insert” style effect, whether they can respond to live input “just in time”, etc.  All of the effects listed below are also available for use as destructive effects in Cakewalk products, as well as in other host applications.

MFX: MIDI Effects Filters

An MFX effect processes MIDI data in one of several ways.

MIDI events are sent to MFX processes in buffers, implying that processing happens in “near real-time.”  The host application will work-ahead by some small amount of time, and this workaround time will determine the size of the MIDI data buffers fed to an MFX.

The following diagram shows how MFX are utilized in Cakewalk products.  Each MIDI track has a patch point control, into which one or more MFX may be inserted into an MFX chain.  Events from the track are periodically packaged into a buffer and fed to the first MFX in the track’s chain.  The MFX produces then produces output buffer, usually based on the contents of the input buffer.  This output buffer becomes the input buffer for the next MFX in the chain, and the process continues for all MFX.  The output of the last MFX in the chain is sent to the MIDI output port

An MFX processor which generates MIDI data does so via a periodic signal from the host application.  No data needs to be on the track for this to happen – the song doesn’t even need to be playing.

In contrast to audio effects, Cakewalk products currently support only track inserts for MIDI effects, not “aux busses” or “master effects”.  During playback, MIDI events from each track are run through the chain of effects (if any) for the track.


DirectX Plug-Ins

A DirectX plug-in processes audio data in one of two ways.

The preferred data format for audio processing is 32-bit IEEE floating point.  As a practical matter, every commercially available DirectX plug-in supports this format.

The signal flow model for audio in Cakewalk products varies product by product, and is beyond the scope of this document.  Please refer to the user’s guide for your host application for the specifics.

For detailed information about how to develop DirectX audio plug-ins, please refer to the Microsoft DirectShow SDK documentation.

DXi Software Synthesizers

A DXi has the characteristics of both an MFX effect and a DirectX plug-in.  As such, a DXi can behave either as software synthesizer, or as a MIDI controllable audio plug-in.

As an MFX effect, a DXi receives buffers of MIDI data, both streamed and just-in-time.  For format of these data buffers is identical to the buffers provided to MFX plug-ins.

As a DirectX plug-in, a DXi can exist anywhere in a project where you would patch an audio effect.  Furthermore, a DXi is fed audio data that may have been processed by upstream effects, or data coming out of an audio track.  Thus a DXi some has flexibility in choosing how to process audio data:

Developer Overview

MFX and DXi plug-ins are COM objects implemented as in-process servers (in DLLs).

You should make as few assumptions as possible about the behavior of the host application. If you find some area to be ambiguous, rather than code a specific product’s behavior, please alert Cakewalk so that the issue can be resolved and/or better documented.

COM

This document can’t provide you with an introduction to COM, but it can recommend some books that you should own:

·         Rogerson, Dale, Inside COM. Microsoft Press 1997. This is a good introduction to the fundamentals of COM.

·         Kruglinski, David J.; Shepherd, George; and Wingo, Scott.  Programming Microsoft Visual C++. Microsoft Press 1998.

Most functions in this document return an HRESULT, as is the COM convention.  Rather than listing possible return values with the documentation of each function, we will list several common return values here.  Please consult the Windows Platform SDK Documentation for a complete list of values.

S_OK

Method succeeded; or “Yes”, if the method returns a yes/no answer.

S_FALSE

Method did not succeed; or “No”, if the method returns a yes/no answer.

E_INVALIDARG

An invalid argument was supplied to the method.

E_POINTER

An invalid pointer value was supplied to the method.

E_OUTOFMEMORY

A memory allocation failed.

E_FAIL

An unspecified error occurred.

DXi and MFX Registry Keys

All code for object (de)registration is automatically generated by the App Wizard.  What follows is a summary of the registry keys that are actually written.

MFX Filters

When they are installed, MFX filters must modify the Registry to make themselves available to applications.

  1. The usual COM object registration:

HKEY_CLASS_ROOT\CLSID\{clsid} = <friendly name>
HKEY_CLASS_ROOT\CLSID\{clsid}\InProcServer32 = <pathname to DLL>

 

  1. A sub key under a key that lists available MFX objects.

HKEY_LOCAL_MACHINE\Software\Cakewalk Music Software\MIDI Filters\{clsid} = <>

 

Note:    The MidiFilter.h file has a #define for the root key name:
#define SZ_MIDI_FILTER_REGKEY "Software\\Cakewalk Music Software\\MIDI Filters"

DXi’s

Because DXi’s are DirectShow filters, they already perform whatever registration is required by DirectShow.  In order to be made available for use as DXi’s, there is additional registry key that must be written.

HKEY_LOCAL_MACHINE\Software\Classes\MfxSoftSynths\{clsid} = <>

 

Note:    The MidiFilter.h file has a #define for the root key name:
#define SZ_SOFT_SYNTH_REGKEY "Software\\Classes\\MfxSoftSynths"

MFX_VERSION

The declarations in MidiFilter.h have evolved over time, as newer capabilities have been added alongside new Cakewalk products.  For backwards compatibility with Cakewalk products, the following table lists which Cakewalk products correspond to particular versions of MFX.

MFX_VERSION

Supported Cakewalk Product(s)

< 8

Pro Audio, Home Studio and Professional, versions 6.x and 7.x; Guitar Studio 1.0

8

Pro Audio, Home Studio and Professional, version 8.x; Guitar Studio 2.0

9

Pro Audio and Home Studio, version 9.x

10

SONAR and SONAR XL, version 1.x; Home Studio 01; Plasma; Club Tracks

11

SONAR and SONAR Xl, version 2.x

Using AFX_MANAGE_STATE

The App Wizard creates a DLL that is dynamically linked against the MFC DLLs, so any functions exported from this DLL which call into MFC must have the AFX_MANAGE_STATE macro added at the very beginning of the function. For example:

extern "C" BOOL PASCAL EXPORT ExportedFunction()

{

   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   // normal function body here

}

 

It is very important that this macro appear in each function, prior to any calls into MFC.  This means that it must appear as the first statement within the function, even before any object variable declarations (because their constructors may generate calls into the MFC DLL).  Failure to conform to this requirement can cause access violations in the host application.  Please see MFC Technical Notes 33 and 58 for additional details.

Time Critical Threads

Several methods in the DXi 2.0 SDK may be called in the context of the time critical thread:

IMfxEventFilter::OnInput
IMfxEventFilter::OnEvents
IMfxSoftSynth::OnInput
IMfxSoftSynth::OnEvents
CDXi::Process
CDXi::InitializeNoteEvent
CDXI::ExpireNoteEvent

These methods should be written to execute as quickly as possible.

MFX, DXi 2.0 and DirectX objects may be used in real-time during playback.  In this case with Cakewalk products, the methods listed above are called from a dedicated thread.  The thread may be running at time critical thread priority.  Such threads must be careful about which APIs they call, to ensure smooth system performance.

To give one specific example, do not call functions that paint the screen, like InvalidateRect or SetWindowText. These are particularly bad in Windows 9x and ME, because such functions acquire the Win16Mutex.  As a result, this time-critical thread can block on some other thread painting the screen.  Instead, if you need to update your UI based on something happening in one of these methods, then a better approach would be to post yourself a message using PostMessage (not SendMessage!).  This way, you will avoid causing the thread to block, and the UI update can occur at a more appropriate time.

Thread synchronization

As explained above, methods can be called from different threads.  The App Wizard will generate code that uses critical section objects to synchronize access to data objects, as required.  For more information about critical section objects, read about them in the Microsoft Platform SDK.   Also we recommend a good book like Jeffrey Richter’s Advanced Windows. Finally, consult the sample code.

DXi 2.0 Structures and Types

AudioBuffer

This class encapsulates a DirectShow media sample (IMediaSample), providing automatic support for deferred zero filling if the host application supports it.  For DXi 2.0 synthesizers, AudioBuffer also contains the association of the buffer to the synthesizers output pin.

long cSamp;

The number of samples in the audio buffer.  Note that for stereo buffers, each pair of left/right values is counted as one sample.

DWORD idPin;

(This data member is generated by the wizard only for DXi synthesizers.)  The output pin assigned to this audio buffer, suitable for use in CDXiSynthContext methods.

IMediaSample* pms;

The media sample underlying this audio data buffer.  A DXi or plug-in will seldom need to refer to this value.

float* GetPointer();

Returns a pointer to the audio data as a pointer to a 32-bit IEEE float.  This call may also result in the data buffer being explicitly filled with zeroes, if a previous call to SetZerofill(TRUE) was made.

BOOL GetZerofill();

Test to see if this buffer was previous tagged as being zero-filled.  If TRUE, then the buffer is all zeroes, and processing can be optimized as a result.  Your plug-in code should take care to call this method first, before calling GetPointer, to avoid unnecessary memory fills.

void SetZerofill( BOOL bZerofill );

Flag this buffer as being zero-filled, or not.  This call doesn’t actually fill the buffer with any zeros.  It simply sets a flag (using IDeferZeroFill if possible), to trigger a “lazy” or deferred memory fill.  Use this method whenever your DSP code is producing an output buffer that is known to be fully zeroes.

DXiEvent

This structure encapsulates a MIDI event that is due to be processed by a DXi synthesizer.  It extends the constructs of MfxEvent by providing a sample accurate timestamp (for rendering), and hooks for plug-in specific data such as the state of a voice’s oscillator.

MfxEvent me;

The MIDI event fed to the DXi from the host application.

MFX_CHANNEL mfxChannel;

The virtual MFX_CHANNEL where the event is to be rendered.   In SONAR 2.x, this corresponds to the track number in the project, but this may change in future products or other host applications.

LONGLONG llSampTimestamp;

The start time of the event, on a free-running synthesizer sample clock.

From the DXi 2.0 synthesizer perspective, all MIDI events are rendered on an ever increasing, sample-accurate timeline.  The DXi 2.0 class framework handles the ugly details of the event’s origins, be it live MIDI input or track streaming, as well as looping playback.

void* pvData;

Synthesizer specific data for the event.  Your DXi code initializes this value via the CDXi::InitializeNoteEvent override, and frees it via the CDXi::ExpireNoteEvent override.

DXi 2.0 Base Classes

CDXi

CDXi is generated by the DXi 2.0 App Wizard.  It consists of a small number of “boilerplate” methods which are filled in to provide the exact details of your DirectX plug-in or DXi synthesizer.  In code generated by the wizard, you fill find these functions in the module named {PROJECT}.cpp, where {PROJECT} is the name you provided when first creating your plug-in with the wizard.

HRESULT Initialize();

This method is called by framework exactly once on the DXi object, when it first comes to life.  Any and all non-trivial initialization of your plug-in object should be placed here, especially initializations which may fail (such as memory allocation.)

Never put initializations which could possibly fail into your plug-in’s constructor!

HRESULT IsValidInputFormat( const WAVEFORMATEX* pwfx ) const;

Ask the plug-in or DXi if the specified wave format is acceptable as an input format.   Note that the framework code will have already validated pwfx to ensure that it is 16-bit PCM or 32-bit float, 1 or 2 channels.  The default implementation of this function, generated by the framework, will require that your plug-in accept only 32-bit floats.  (This is not a very restrictive requirement, given that all modern DirectX host applications support floating point processing.)

Return S_OK if the input format is acceptable, or VFW_E_TYPE_NOT_ACCEPTED if unacceptable.

HRESULT IsValidOutputFormat( const WAVEFORMATEX* pwfx ) const;

Ask the plug-in or DXi if the specified wave format is acceptable as an output format.   Note that the framework code will have already validated pwfx to ensure that it is 16-bit PCM or 32-bit float, 1 or 2 channels.  The default implementation of this function, generated by the framework, will require that your plug-in accept only 32-bit floats.

Return S_OK if the output format is acceptable, or VFW_E_TYPE_NOT_ACCEPTED if unacceptable.

HRESULT IsValidTransform( const WAVEFORMATEX* pwfxI, const WAVEFORMATEX* pwfxO ) const;

Ask the plug-in or DXi if the conversion from input to output wave format is acceptable.   Note that the framework code will have already validated pwfx to ensure that it is 16-bit PCM or 32-bit float, 1 or 2 channels, and that the input and output formats have the same sample rate.   The default implementation of this function, generated by the framework, will require that your plug-in accept only 32-bit floats.

Return S_OK if the transform is acceptable, or VFW_E_TYPE_NOT_ACCEPTED if unacceptable.

HRESULT SuggestOutputFormat( WAVEFORMATEX* pwfx ) const;

Ask the plug-in or DXi to recommend a preferred output format, given a particular input format.  Note that the framework code will have already validated pwfx to ensure that it is 16-bit PCM or 32-bit float, 1 or 2 channels.  The default implementation of this function, generated by framework, simply returns S_OK.  This means that suggested output format will be the same as the current input format.

HRESULT Process( LONGLONG llSampTimestamp, AudioBuffer* pbufIn,
                           AudioBuffer* pbufOut );

This method is where your DirectX plug-in will be doing most of its work.  Note that the wizard will declare Process differently, depending on whether you have chosen to create a DirectX plug-in or a DXi synthesizer.  The form shown here is for a DirectX plug-in.

A DirectX plug-in may process its data in-place, or buffer-to-buffer.  To detect whether your plug-in indeed operating in one mode versus the other, you can simply compare pbufIn and pbufOut.  (The source code created by the App Wizard does this for you already.)

llSampTimestamp corresponds to the DirectShow “media time”, or time-stamp of the incoming media sample.  Plug-ins which implement DirectX 8.0 automation (IMediaParams et al) would inspect this value to determine the timeline position for automation data.

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

HRESULT Process( LONGLONG llSampTimestamp, AudioBuffer* pbufIn,
                           AudioBuffer* abufOut, long cBufOut,
                           LONGLONG llSampMidiClock, deque<DXiEvent>& qMidi );

This method is where your DXi software synthesizer will be doing most of its work.  Note that the wizard will declare Process differently, depending on whether you have chosen to create a DirectX plug-in or a DXi synthesizer.  The form shown here is for a DXi software synthesizer.

llSampTimestamp is corresponds to the DirectShow “media time”, or time-stamp of the incoming media sample.  Plug-ins which implement DirectX 8.0 automation (IMediaParams et al) would inspect this value to determine the timeline position for automation data.

llSampMidiClock is the current value of the free-running sample clock for use by the synthesizer.  Its value is the time base for all MIDI events in the qMidi queue.

A typical DXi implementation of Process would take the following steps.  (Please see the TWONAR sample for a working example.)

  1. Pass input buffer through to output pins, as appropriate
  2. Iterate over each event in qMidi:
    1. If it’s a note event, produce audio in the appropriate output buffer
    2. If it’s a controller or other event, record the state change
  3. Perform any buffer mixing, as appropriate

HRESULT IsValidEvent( DXiEvent& de );

This method allows a DXi synthesizer to filter out MIDI events that don’t participate in synthesis.  It basically “stands guard” between the host applications large MIDI data buffers and just-in-time rendering done by Process, to ensure that Process isn’t burdened with MIDI events that it can’t render.

For example, if a user’s project contains a dense passage of NRPN events, and these events aren’t recognized by the DXi, then the DXi’s Process method would be used to ensure that Process would never be asked to process a NRPN message.

HRESULT InitializeNoteEvent( DXiEvent& de );

This method allows a DXi synthesizer to initialize any state information pertaining to a MIDI note event.  Most synthesizers are designed with a kind of “voice architecture”, and this method allows the synthesizer to initialize its “voice” for a newly sounding note.  (Please see the TWONAR sample for a working example.)

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

HRESULT ExpireNoteEvent( DXiEvent& de, BOOL bForce );

This method is called by the framework, to allow the DXi to test whether a note event has played to completion, and/or to free all memory allocated for the note event by InitializeNoteEvent.

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

HRESULT AllocateResources();

This method is called at the start of streaming, specifically, when the DirectShow filter graph is transitioned from a “stopped” state into a “paused” state.  A DirectX plug-in or DXi will usually want to allocate all audio format-dependent resources here, such as delay lines that are sized based on the current sample rate, etc.

HRESULT FreeResources();

This method is called at the end of streaming, specifically, when the DirectShow filter graph is transitioned from a “running” state into a “paused” state.  A DirectX plug-in or DXi will usually want to release all audio format-dependent resources here, such as delay lines that are sized based on the current sample rate, etc.

int PersistGetSize() const;

Returns number of bytes needs by the plug-in or DXi to persist (save or load) its state.

The code generated by the App Wizard will package all presets into a member variable struct named m_p, and the implementation of PersistGetSize will return the size of this struct.

HRESULT PersistLoad( IStream* pStream );

Load the DXi state from pStream.  The number of bytes read must equal the value returned from PersistGetSize.

The code generated by the App Wizard will read bytes from pStream directly into m_p.

HRESULT PersistSave( IStream* pStream );

Save the DXi state into pStream.  The number of bytes written must equal the value returned from PersistGetSize.

The code generated by the App Wizard will write bytes directly from m_p into pStream.

CDXiSynthContext

This class is created by the App Wizard only for DXi 2.0 synthesizers.  It exposes services to the DXi for creating, querying and destroying audio output pins.  (For an explanation “pins,” “filters”, and other such terminology, please refer to the Microsoft DirectShow SDK.)  It also exposes helper functions for conversion between musical time and sample time.  Most DXi 2.0 implementations will not ever need to change this class.

HRESULT CreateOutputPin( const char* pszPinName = NULL, DWORD* pidCreated = NULL );

Creates a new audio output pin for the DXi synthesizer.  If pszName is NULL, then the framework will create a name for the pin for you.  If pidCreated is not NULL, then the ID of the new pin will be returned to you.

Important:  The framework will negotiate with host application to determine whether a new pin may be created.  This negotiation can easily fail, so be very careful about checking the return value from this call.  Some host applications may not support filters having multiple output pins.  Also, some host applications may deny a request to create new pins if the application is “busy” or in an otherwise unacceptable state.

HRESULT DestroyOutputPin( DWORD id );

Removes the specified audio output pin from the DXi synthesizer.

Important:  The framework will negotiate with host application to determine whether the pin may be destroyed.  This negotiation can easily fail, so be very careful about checking the return value from this call.  Some host applications may not support filters having multiple pins, and could reject a call to destroy a pin outright.  Also, some host applications may deny a request to destroy a pin if the application is “busy” or in an otherwise unacceptable state.

HRESULT DestroyAllOutputPins();

Removes all output pins from the DXi synthesizer.  A DXi would seldom need to do this, because one would expect always needing to keep at least a single audio output pin.

Important:  The framework will negotiate with host application to determine whether the pins may be destroyed.  This negotiation can easily fail, so be very careful about checking the return value from this call.  Some host applications may require that a filter have at least one pin.  Also, some host applications may deny a request to destroy pins if the application is “busy” or in an otherwise unacceptable state.

CBasePin* FindOutputPin( DWORD id ) const;

Get the DirectShow CBasePin object for the specified pin ID.  Most DXi’s will not ever need to manipulate output pins directly, but this method is provided just in case.

int GetOutputPinCount() const;

Returns the number of audio pins currently allocated for the DXi.

DWORD  GetOutputPinId( int nIndex ) const;

Returns the ID for the Nth output pin, as given by nIndex.

LONGLONG TicksToSamples( LONG lTicks ) const;

Converts a musical tick time (in units of 960 PPQN) to samples.

This function is provided to simplify backwards compatibility with DXi 1.0 host applications.  DXi 1.0 did not provide IMfxTimeConverter, which gives sample-accurate conversions between ticks and samples.  The implementation of this function will attempt to use IMfxTimeConverter if one is available, otherwise it will fall back to the older (msec accurate) IMfxTempoMap.

LONG SamplesToTicks( LONGLONG llSamples ) const;

Converts a sample time to a musical tick time (in units of 960 PPQN).

This function is provided to simplify backwards compatibility with DXi 1.0 host applications.  DXi 1.0 did not provide IMfxTimeConverter, which gives sample-accurate conversions between ticks and samples.  The implementation of this function will attempt to use IMfxTimeConverterif one is available, otherwise it will fall back to the older (msec accurate) IMfxTempoMap.

CInstrument

This class is created by the App Wizard only for DXi 2.0 synthesizers.  It provides a boilerplate implementation of IMfxInstrument which you can fill in to provide the specifics for your synth.  Please refer to the TWONAR sample to see how you might modify CInstrument to suit your needs.

MFX Structures and Types

MFX_CHANNEL

An MFX_CHANNEL identifies the origin of a MIDI data stream in the host application.  For example, in SONAR 1.0, the MFX_CHANNEL is always the same as the track number where the MIDI data resides.  In future products, the MFX_CHANNEL may identify the stream or “clip” where the data resides.

The purpose of this data type is to allow certain MIDI event properties to be changed “just in time.”  For example, Cakewalk products allow all of the MIDI data on a track to be transposed by modifying a “Key+” entry in the track’s UI.  These changes would be encoded in an MfxNotifyMsg structure (below) having an MFX_CHANNEL equal to the track number where the Key+ was changed.

MFX_TIME

The MFX_TIME struct is used to represent the position of events on a song’s timeline.  MFX_TIME supports a large variety of time formats.  MFX_TIME values can be converted from one unit to another via IMfxTimeConverter.

typedef enum MFX_TIME_FORMAT

{

       TF_NULL,      // no time value (used to clear a time value)

       TF_SECONDS,   // time in absolute seconds from song top (double)

       TF_SAMPLES,   // time in absolute samples from song top (LONGLONG)

       TF_TICKS,     // time in MIDI ticks @ 960 ppqn, from song top (LONGLONG)

       TF_UTICKS,    // time in hi-res MIDI ticks @ 960*(2^16) ppqn, from song top (LONGLONG)

       TF_MBT,              // time in measure:beat:ticks, from song top

       TF_FRAMES,    // time in SMPTE frames, including the projects SMPTE offset

       TF_FRAMES_REL,       // time in SMPTE frames, relative to song top (no offset applied)

       TF_SMPTE,     // time in SMPTE HMSF format, including the projects SMPTE offset

       TF_SMPTE_REL  // time in SMPTE HMSF format, relative to song top (no offset applied)

}

MFX_TIME_FORMAT;

 

typedef enum MFX_FPS

{

       FPS_24,              // 24 fps

       FPS_25,              // 25 fps

       FPS_2997,     // 29.97 fps

       FPS_2997_DROP,       // 29.97 fps, drop-frame

       FPS_30,              // 30 fps

       FPS_30_DROP   // 30 fps, drop-frame

}

MFX_FPS;

 

typedef struct MFX_TIME

{

       MFX_TIME_FORMAT      timeFormat;

       union

       {

              double        dSeconds;     // timeFormat == TF_SECONDS

              LONGLONG      llSamples;    // timeFormat == TF_SAMPLES

              LONG          lTicks;              // timeFormat == TF_TICKS

              LONGLONG      llUTicks;     // timeFormat == TF_UTICKS

 

              struct

              {

                     int    nMeas;

                     short  nBeat;

                     short  nTick;

              }

              mbt;                       // timeFormat == TF_MBT

 

              struct

              {

                     short  fps;          // one of enum MFX_FPS

                     short  nSub400;      // units = 1/400 of a frame

                     char   nFrame;

                     char   nSec;

                     char   nMin;

                     char   nHour;

              }

              smpte;                     // timeFormat == TF_SMPTE, TF_SMPTE_REL

 

              struct

              {

                     short  fps;

                     LONG   lFrame;

              }

              frames;                           timeFormat == TF_FRAMES, TF_FRAMES_REL

       };

}

MFX_TIME;

MfxData

The MfxData struct is used to describe MIDI events at a low level.  This is seen in the member function OnInput.

struct MfxData

{

       // Musical ticks. For MfxData items passed to IMfxEventFilter::OnInput(),

       // this will correspond to the current time. If the application isn't playing,

       // the current time will not change.

       LONG   m_lTime;

 

       union

       {

              DWORD  m_dwData;     // this is in midiOutShortMsg() format...

              struct

              {

                     BYTE   m_byStatus;

                     BYTE   m_byData1;

                     BYTE   m_byData2;

              };

       };

};

MfxEvent

The MfxEvent struct is used to describe a single MIDI event.

This is a slightly higher-level concept than raw MIDI voice messages. For example, Note, Patch, RPN, and NRPN MfxEvents consolidate multiple MIDI voice messages into a single logical MfxEvent, for easier processing.

The m_eType field identifies the type of event. New types of events may be defined in the future. As a result, in your IMfxEventFilter::OnEvents member you should be prepared for m_eType values that you do not recognize. The action you should take depends on your filter. Usually you should pass unknown events to IMfxEventQueue::Add. However, if the purpose of your filter were to delete all events except for those of a certain type, it might be reasonable to delete the unknown events (not pass them to Add).

The m_lTime field may only hold non-negative values. So why is it a signed LONG? Because time math often is convenient to do with signed integers, and this allows you to avoid excessive casting. But again, only values greater than or equal to zero are permitted for MfxEvent start times.

struct MfxEvent

{

       // This describes a MIDI event.

       // This is a bit higher-level than raw MIDI voice messages. For example,

       // Note, Patch, RPN and NRPN MfxEvents consolidate multiple MIDI voice

       // messages into a single logical MfxEvent, for easier processing.

 

       enum Type

       {

              Note,

              KeyAft,

              Control,

              Patch,

              ChanAft,

              Wheel,

              RPN,

              NRPN

#if (MFX_VERSION > 8)

              Sysx,

              Text,

              Lyric,

#endif // (MFX_VERSION > 8)

 

#if (MFX_VERSION > 9)

              MuteMask,

              VelOfs,

              VelTrim,

              KeyOfs,

              KeyTrim,

#endif // (MFX_VERSION > 9)

 

#if (MFX_VERSION > 10)

              ShortMsg,

#endif // (MFX_VERSION > 10)

       }      m_eType;

 

       LONG   m_lTime;      // in musical ticks; must be >= 0L

 

       union

       {

              struct

              {

                     BYTE   m_byPort;     // MIDI port for this event

                     BYTE   m_byChan;     // MIDI channel for this event

              };

              struct

              {

                     MfxMuteMask   m_maskSet;    // for MuteMask, bits to set in the mask

                     MfxMuteMask   m_maskClear;  // for MuteMask, bits to clear from the mask

              };

              char   m_nOfs;                    // for VelOfs and KeyOfs

              char   m_nTrim;             // for VelTrim and KeyTrim

       };

 

       Type   m_eType;

 

       union

       {

              struct // m_eType == Note

              {

                     BYTE   m_byKey;      // 0..127; key number

                     BYTE   m_byVel;      // 0..127; attack velocity

                     BYTE   m_byVelOff;   // 0..127 release velocity (64 = default)

                     DWORD  m_dwDuration; // 0..ULONG_MAX; in musical ticks

              };

              struct // m_eType == KeyAft

              {

                     BYTE   m_byKey;      // 0..127

                     BYTE   m_byAmt;      // 0..127

              };

              struct // m_eType == Control

              {

                     BYTE   m_byNum;      // 0..127; controller number

                     BYTE   m_byVal;      // 0..127; controller value

              };

              struct // m_eType == Patch

              {

                     BYTE   m_byPatch;           // 0..127

                     BYTE   m_byBankSelMethod;   // 0==Normal, 1==Ctrl0, 2==Ctrl32, 3==Patch100

                     short  m_nBank;             // -1..16383 (-1 == "none")

              };

              struct // m_eType == ChanAft

              {

                     BYTE   m_nAmt;              // 0..127

              };

              struct // m_eType == Wheel

              {

                     short  m_nVal;              // -8191..+8191 (0 == center)

              };

              struct // m_eType == RPN or NRPN

              {

                     WORD   m_wNum;              // 0..16383

                     WORD   m_wVal;              // 0..16383

              };

#if (MFX_VERSION > 8)

              struct // m_eType == Sysx, Text, or Lyric

              {

                     // Pass the MFX_HBUFFER to the IMfxFactory::GetPointer() method

                     // to cash it in for a pointer and length.

                     // Note: Text and Lyric events are wchar_t pointers -- i.e. Unicode

                     // strings -- following COM convention.

                     MFX_HBUFFER   m_hBuffer;

              };

#endif // (MFX_VERSION > 8)

 

#if (MFX_VERSION > 9)

              struct // m_eType == Mute, VelOfs, VelTrim, KeyOfs, KeyTrim

              {

                     MFX_CHANNEL m_mfxChannel;

              };

#endif // (MFX_VERSION > 9)

 

#if (MFX_VERSION > 10)

              struct // m_eType == ShortMsg

              {

                     DWORD m_dwShortMsg;

              };

#endif // (MFX_VERSION > 9) };

};

 

MfxMuteMask

The MfxMuteMask class is used to describe why a particular channel has been muted.  Specifically, it encapsulates a bit-field, each bit of which represents one possible reason for a mute to occur.

struct MfxMuteMask

{

       MfxMuteMask() { byte = 0; }

       MfxMuteMask(int b) { byte = BYTE(b); }

       BOOL AnySet() const { return byte; }

       operator BYTE() const { return byte; }

       BYTE operator=(int n) { byte = BYTE(n); return byte; }

       union

       {

               BYTE    byte;

               struct

               {

                       BYTE    Manual:1;

                       BYTE    Auto:1;

                       BYTE    Solo:1;

                       BYTE    Record:1;

                       BYTE    Scrub:1;

               };

       };

};

 

Mask Bit

Description

Manual

The user has clicked on a mute button, either in the application’s UI on an external control surface.

Auto

A mute automation envelope has changed the mute state of the channel.

Solo

This channel is being (un)muted because some other channel is being (un)soloed.

Record

The application has begun an “overwrite” recording pass on this channel.  Any data being streamed out of the channel should be muted.

Scrub

Some other channel is being scrubbed.

 

MfxNotifyMsg

This struct is used by IMfxNotify.  It encapsulates “just in time” changes sent from the host app to a DXi.

struct MfxNotifyMsg

{

       enum Type

       {

              ChannelMidiChannel,

              ChannelMuteMask,

              ChannelVelOfs,

              ChannelVelTrim,

              ChannelKeyOfs,

              ChannelKeyTrim,

       };

 

       Type                       m_type;      

       union

       {

              struct // ChannelMidiChannel

              {

                     MFX_CHANNEL   m_mfxChannel;

                     char          m_nMidiChannel;

              };

              struct // ChannelMuteMask

              {

                     MFX_CHANNEL   m_mfxChannel;

                     MfxMuteMask   m_maskSet;

                     MfxMuteMask   m_maskClear;

              };

              struct // ChannelVelOfs, ChannelKeyOfs

              {

                     MFX_CHANNEL   m_mfxChannel;

                     char          m_nOfs;

              };

              struct // ChannelVelTrim, ChannelKeyTrim

              {

                     MFX_CHANNEL   m_mfxChannel;

                     char          m_nTrim;

              };

       };

};

 

Notification

Description

ChannelMidiChannel

All of the data which came from channel m_mfxChannel should be rendered out a different synthesizer channel, m_nMidiChannel.

ChannelMuteMask

The user has muted/unmuted channel m_mfxChannel.  Mutes are specified via 2 mute mask variables, one which specifies bits to clear, and another which specifies bits to set.  In other words, Mute = (Mute | m_maskSet) & (~m_maskClear), and the channel is muted if “Mute” is nonzero.

For example, a DXi will typically initialize every channel’s mute mask to 0x00.  If a mute notification arrives with Set=0x01/Clear=0x00, then Mute=(0x00|0x01)&(~0x00)=0x01.

ChannelVelOfs

Add m_nOfs to the velocity of every MIDI note event which came from m_mfxChannel.

ChannelVelTrim

Add m_nTrim to the velocity of every MIDI note-on event which came from m_mfxChannel.

ChannnelKeyOfs

Add m_nOfs to the key (pitch) of every MIDI note-on event which came from m_mfxChannel.

ChannelKeyTrim

Add m_nTrim to the key (pitch) of every MIDI note-on event which came from m_mfxChannel.

 

MFX Interface Reference

In the event of any discrepancy, the final authority is the header file MidiFilter.h from the SDK. Also, be sure to study the sample code provided.

Interface Overview

Interfaces implemented by an MFX MIDI effect

A MFX filter must implement a COM interface defined for MFX:

¨       IMfxEventFilter

Also, it must implement a standard COM interface that allows the effect to be persisted (allows its CLSID and its properties to be stored in files):

¨       IPersistStream, to support saving effects in project files, and to support the “Presets” feature.

If a MFX filter has any user interface (most will), it should also implement some standard COM interfaces:

¨       ISpecifyPropertyPages, to provide a user interface in the form of one or more IPropertyPage objects.

These last two interfaces are also implemented by DirectShow audio filters.  In other words, for both MIDI and audio effects, PA handles user interface, persistence, and presets using the same mechanism.

Interfaces implemented by a DXi 2 software synthesizer

A software synthesizer must implement a COM interface defined for software synthesizers:

¨       IMfxSoftSynth2

It should implement the COM interface to facilitate just-in-processing of mutes, key-transposition, etc.

¨       IMfxNotify

Also, it must implement a standard COM interface that allows the effect to be persisted (allows its CLSID and its properties to be stored in files):

¨       IPersistStream, to support saving effects in project files, and to support the “Presets” feature.

If the DXi has any user interface (most will), it should also implement some standard COM interfaces:

¨       ISpecifyPropertyPages, to provide a user interface in the form of one or more IPropertyPage objects.

If the DXi wants to send MIDI data to the host “just in time,” for example, to record automation as MIDI from the DXi’s user interface, it should also implement

¨       IMfxInputPort, to accept an IMfxInputCallback from the host, through which MIDI messages can be sent.

Interfaces implemented by the host application

The host application of your MFX filter, will implement some other special interfaces:

¨       IMfxEventQueue and IMfxEventQueue2, so that your IMfxEventFilter::OnEvents member can pass on MfxEvents that it modifies or creates.

¨       IMfxBufferFactory, so that you may work with MFX_HBUFFER handles in MfxEvents (for example, System Exclusive events).

¨       IMfxDataQueue, so that your IMfxEventFilter::OnInput member can pass on MfxDatas that it modifies or creates.

¨       IMfxTempoMap, so that your effect can convert between musical ticks and milliseconds, and get read-only access to the tempo map.

¨       IMfxMarkerMap and IMfxMarkerMap2, so that your effect can access any markers and pitch markers in the project.

¨       IMfxMeterMap, so that your effect can convert between musical ticks and Measure:Beat:Tick format, and get read-only access to the meter map.

¨       IMfxKeySigMap, so that your effect can query the key signature in effect at any given time.

¨       IMfxInstruments, IMfxNameListSet, and IMfxNameList, so that your application can access the instrument definitions and lists of names for patches, notes, and controllers.

¨       IMfxSelection, so that your effect can find out if it’s being used as an offline edit command, and if so, what is the time span of the selection.

¨       IMfxInputPulse, so that certain types of effects may have their IMfxEventFilter::OnInput member called periodically, regardless of whether there’s any MIDI input.

¨       IMfxInputCallback, to allow the MFX or DXi to send MIDI data back to the host application for recording, UI automation, etc.

¨       IMfxTimeConverter, to perform high-resolution time conversions between various time formats.

¨       IDeferZeroFill, to allow the plug-in to efficiently manage silent audio buffer.

IDeferZeroFill

The purpose of IDeferZeroFill is to improve DSP performance by avoiding unnecessary computation and/or memory access.

IDeferZeroFill is “discovered” by a plug-in via a query interface on a DirectShow media sample.  What follows is an example of how a simple DSP routine might detect and use this capability.

HRESULT ScaleBuffer( IMediaSample* pms, double dGain )

{

       // Check for possible silent buffer optimizations

IDeferZeroFill* pdzf = NULL;

if (S_OK == pms->QueryInterface( IID_IDeferZeroFill, (void**)&pdzf ))

{

              if (pdzf->get_NeedsZerofill())    // silent buffer?

              {

                     // Silent buffers are always silent, even after being scaled.

                     // Nothing more to do.

                     pdzf->Release();

                     return S_OK;

              }

              else if (0.0 == dGain)                   // multiply by zero?

              {

                     // Scaling by zero yields a silent buffer.  Do this efficiently

                     // without touching memory or CPU.

                     pdzf->put_NeedsZerofill( TRUE );

                     pdzf->Release();

                     return S_OK;

              }

              pdzf->Release();

}

 

// No optimizations possible.  Process away . . .

}

 

Why go through all this work?  In a word, performance.  Modern CPUs are very efficient at floating point math.  Yet in highly optimized DSP algorithms, the DSP code itself is often not the bottleneck!   Instead, bandwidth between the CPU and RAM is the limiting factor.  Even with a L1 and L2 processor caches, there is great benefit in avoiding unnecessary memory accesses.  (The introduction of this IDeferZeroFill literally doubled the performance of Cakewalk’s audio engine!)

Be conservative about calling IMediaSample::GetPointer

This is because of a key implementation detail that allows IDeferZeroFill to be effective, even when used with plug-ins that don’t know how to recognize it.  Visualize the call to put_NeedsZerofill(TRUE) as putting a kind of tamper-proof seal on the data buffer.  The seal gets “broken” when somebody calls IMediaSample::GetPointer – necessarily so, because this function gives the caller full access the audio data.  Since there is not way to guess the caller’s intent in calling IMediaSample::GetPointer, any reasonable implementation of IDeferZeroFill must be pessimistic and assume that the buffer is about to populated with non zero data.

So the moral of the story is, always check for deferred zero filling before you attempt to obtain a media sample’s pointer.  (Fortunately, the AudioBuffer class provided with the DXi 2.0 App Wizard takes care of this for you automatically.)

BOOL get_NeedsZerofill();

Reports if the associated media sample has been flagged as being fully silent.

void put_NeedsZerofill( BOOL bZerofill );

Flags a media sample as being fully silent.

HRESULT GetRawPointer( BYTE** ppBuffer );

Returns the data buffer pointed to by the IMediaSample, bypassing the usual pessimistic side-effect of calling IMediaSample::GetPointer.

IMfxBufferFactory

MFX buffers are used to support variable-length data associated with certain MfxEvent types like System Exclusive, Text, and Lyric. The MfxEvent doesn't contain a raw pointer to the extra memory. Instead it contains an MFX_HBUFFER handle. You use this IMfxBufferFactory interface to cash in the handle for a pointer to the data as well as its length. In addition if you are modifying or creating new MfxEvents of this type, the IMfxBufferFactory interface lets you create new buffers.

MFX buffers are valid only during the call to your IMfxEventFilter::OnEvents member. You should not retain the IMfxBufferFactory pointer or any MFX_HBUFFER handles across such calls and expect them to be valid. This enables the host application can do automatic garbage collection. This means that effects aren't responsible for freeing memory to prevent leaks, and by the same token effects can't corrupt the heap by freeing memory twice. Finally, because effects should check the return value of GetPointer using the SUCCEEDED macro, they should not get into trouble by using bad pointers: If the MFX_HBUFFER is no longer valid, GetPointer will return E_INVALIDARG.

For efficiency, MFX buffers may not be resized. If you need to resize a buffer, you must call CreateCopy to create a new buffer of the desired length and copy the contents of the old buffer to the new one.  By contrast, if you need only to modify the buffer in-place (for example to modify the value of one byte in a System Exclusive message) you may do so efficiently with the read/write pointer you get from GetPointer.

HRESULT Create( DWORD dwLength, MFX_HBUFFER* phbufNew );

This creates a new MFX buffer with the capacity to store dwLength bytes.

Return S_OK on success, or E_OUTOFMEMORY if there was not enough memory to create the buffer.

HRESULT CreateCopy( MFX_HBUFFER hbufOld, DWORD dwLength, MFX_HBUFFER* phbufNew );

This creates a new MFX buffer with the capacity to store dwLength bytes.  In addition, the contents of hbufOld will be copied to hbufNew (up to but not exceeding the length of the new buffer).

Return S_OK on success, E_INVALIDARG if hbufOld is an invalid handle, or E_OUTOFMEMORY if there was not enough memory to create the buffer.

HRESULT GetPointer( MFX_HBUFFER hbuf, void** ppvData, DWORD* pdwLength );

This returns a read/write pointer to the data in ppvData.  If pdwLength is not NULL, this return the number of bytes that the buffer contains.

Return S_OK on success or E_INVALIDARG if hbuf is an invalid handle.

IMfxDataQueue

IMfxDataQueue represents a collection of MfxEvents.

HRESULT Add( const MfxData& data ) ;

Some of your IMfxEventFilter members are called with a pointer to an IMfxDataQueue. This allows you to output zero or more MfxData.

HRESULT GetCount( int* pnCount );

Return the number of MfxData in the queue

HRESULT GetAt( int ix, MfxData* pData );

Provides the MfxData at index 'ix'.

IMfxEventFilter

HRESULT Connect( IUnknown* pContext );

This method is called before any other. It gives your filter a chance to obtain interfaces provided by the host application. To do so, QueryInterface the pContext pointer.

Your filter may require a certain interface like IMfxTempoMap. If QueryInterface fails, you should return E_FAIL to indicate that your filter cannot work at all.

Return NOERROR (or S_OK) to indicate that your filter found what was required and that it can work.

Note:    Your filter may want to use an interface if available, but be able to work without it (perhaps in some limited fashion).  This is an important COM concept:  An object can find out at runtime what capabilities another object supports. For example, the host application might add a new “ISuperDuperMap” interface.  Subsequently, you might write a new filter for which ISuperDuperMap might be handy but not required.  Your future filter could QueryInterface for ISuperDuperMap, and proceed accordingly.  This way, your filter could take advantage of a specific host application, but remain compatible with all other host applications that don’t supply ISuperDuperMap.

If you do not retain a copy of pContext, be sure to call Release on it before returning.

HRESULT Disconnect();

This method is called before the final Release of your filter. At this time, you should Release any interface pointers that you obtained in Connect.

HRESULT OnStart( LONG lTime, IMfxEventQueue* pqOut );

Called when playback (or other streaming session) starts, with the start time.

The IMfxEventQueue* pqOut is valid only for this call; do not retain a copy of it and do not call AddRef or Release.

HRESULT OnLoop( LONG lTimeRestart, LONG lTimeStop, IMfxEventQueue* pqOut );

Called when playback (or other streaming session) loops back to an earlier time without stopping and restarting.

The IMfxEventQueue* pqOut is valid only for this call; do not retain a copy of it and do not call AddRef or Release.

HRESULT OnStop( LONG lTime, IMfxEventQueue* pqOut );

Called when playback (or other streaming session) stops, with the stop time.

The IMfxEventQueue* pqOut is valid only for this call; do not retain a copy of it and do not call AddRef or Release.

HRESULT OnEvents( LONG timeFrom, LONG timeThru,
                              IMfxEventQueue* pqIn, IMfxEventQueue* pqOut );

This is called when a period of time is being processed. The period is represented by timeFrom through timeThru.

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

This member will be called even if there are no events during the time period for you to process. This allows you to implement purely generative effects, or ones which need may need to generate output periodically even if no input is occurring (for example, an arpeggiator).

If there are any events to process, they are in the IMfxEventQueue* pqIn.

Both pqIn and pqOut are valid only for this call; do not retain a copy of them and do not call AddRef or Release.

For each event in pqIn:

¨       To pass it on unchanged, pass it to pqOut->AddRef().

¨       To modify it, pass a modified MfxEvent to pqOut->AddRef().

¨       To delete it, simply do not call pqOut->AddRef() at all.

¨       To create one or more additional events, call pqOut->AddRef() for each one.

Port and channel

The MfxEvent structure has members for the MIDI port and channel: m_byPort and m_byChan. The meaning depends on how the filter is being used.

¨       Playback. Each event has a MIDI channel stored with it. In addition, tracks have a forced channel property, which can be “none” or a specific channel from 1 to 16. If the forced channel is other than “none”, then each event is stamped with that channel during playback. This stamping process takes place before you get the MfxEvent in OnEvents.  You may modify m_byPort and m_byChan to change where events are output during playback. For example, you could write an echo filter that would send the duplicated notes to a port and channel specified by the user, regardless of the original port and channel.

¨       Offline. When your filter is used as an offline edit command, the m_byPort field is meaningless and is ignored. The m_byChan field is the actual channel stored with the event (the track forced channel is not used). If you modify m_byChan, this will modify the channel when the event is stored back in the track.

HRESULT OnInput( IMfxDataQueue* pqIn, IMfxDataQueue* pqOut );

This member is called to allow you to modify live input at the MIDI input ports, “on the fly”. Whenever a MIDI voice message is received, it is passed to this member.

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

Both pqIn and pqOut are valid only for this call; do not retain a copy of them and do not call AddRef or Release.

For each event in pqIn:

¨       To pass it on unchanged, pass it to pqOut->AddRef().

¨       To modify it, pass a modified MfxData to pqOut->AddRef().

¨       To delete it, simply do not call pqOut->AddRef() at all.

¨       To create one or more additional events, call pqOut->AddRef() for each one.

Using IMfxInputPulse to make OnInput be called periodically

A limited class of effects needs to be able to execute periodically, even in the absence of MIDI input. For example, an arpeggiator effect might want to generate notes indefinitely. The user presses some keys on the MIDI keyboard: OnInput is called. The user releases all the keys: OnInput is called again. But in between, an indefinite amount of time may pass, during which the arpeggiator wants to generate output notes.

To meet this need, IMfxInputPulse may be used. You may obtain this interface by calling QueryInterface on the IUnknown* passed to your Connect member.  Call IMfxInputPulse::BeginPulse to begin the periodic calls to OnInput. Call IMfxInputPulse::EndPulse to end them.

IMfxInputPulse::BeginPulse returns the period of the callbacks (how frequently your OnInput member will be called). Use this to generate notes with future timestamps, to cover that period of time, until the next call to OnInput.

Because this pulse consumes valuable CPU resources, it is a very good idea to have the pulse enabled only when you absolutely need it. In the arpeggiator example, you might call BeginPulse when the first key is pressed, and call EndPulse when the last note is released.

See IMfxInputPulse for additional information.

Keep in mind that most types of MIDI effects do not need to use IMfxInputPulse. A simple echo/delay, for instance, can go ahead and create all of the echoed notes when the original is received.  This does not need, and should not use, IMfxBeginPulse. Instead it should use the capability to stamp output data with times in the future. That is the most efficient way to create data when the amount of data to be created is known in advance.

IMfxEventQueue

IMfxEventQueue represents a collection of MfxEvents.

HRESULT Add( const MfxEvent& event ) ;

Some of your IMfxEventFilter members are called with a pointer to an IMfxEventQueue. This allows you to output zero or more MfxEvents.

HRESULT GetCount( int* pnCount );

Return the number of MfxEvents in the queue

HRESULT GetAt( int ix, MfxEvent* pEvent );

Provides the MfxEvent  at index 'ix'.

IMfxEventQueue2

IMfxEventQueue2 represents a collection of MfxEvents. It extends the original IMfxEventQueue interface by adding a GetBufferFactory method.

HRESULT Add( const MfxEvent& event ) ;

Some of your IMfxEventFilter members are called with a pointer to an IMfxEventQueue. This allows you to output zero or more MfxEvents.

HRESULT GetCount( int* pnCount );

Return the number of MfxEvents in the queue

HRESULT GetAt( int ix, MfxEvent* pEvent );

Provides the MfxEvent at index 'ix'.

HRESULT GetBufferFactory( IMfxBufferFactory** ppIMfxBufferFactory );

Use this to obtain the IMfxBufferFactory associated with this IMfxEventQueue.

Returns S_OK on success, or E_POINTER if the pointer argument is bad.

You must call Release on the IMfxBufferFactory pointer it returns, when you are done with it. Typically you will obtain this pointer in your IMfxEventFilter::OnEvents method. Note that for compatibility, OnEvents takes IMfxEventQueue pointers -- not IMfxEventQueue2.  Therefore you should:

1.       QueryInterface the IMfxEventQueue pointer for the IMfxEventQueue2 interface.

2.       Call IMfxEventQueue2::GetBufferFactory() to get the IMfxBufferFactory pointer.

3.       Call IMfxBufferFactory::GetPointer() to cash in the MFX_HBUFFER handle for a pointer and length.

4.       Release the IMfxBufferFactory pointer.

5.       Release the IMfxEventQueue2 pointer.


Here is a sample code fragment. In production code, you might prefer to obtain and hold the IMfxEventQueue2 and IMfxBufferFactory interface pointers outside the event iteration loop. Just be sure you call Release on both pointers when you are done.

HRESULT CMidiFilter::OnEvents( LONG timeFrom, LONG timeThru,

 IMfxEventQueue* pqIn, IMfxEventQueue* pqOut )

{

       HRESULT hr;

 

       // Determine the number of events to be processed

       int nCount;

       hr = pqIn->GetCount( &nCount );

       if (FAILED( hr ))

              return hr;

 

       for (int ix = 0; ix < nCount; ++ix)

       {

              // Get the next event

              MfxEvent event;

              hr = pqIn->GetAt( ix, &event );

              if (FAILED( hr ))

                     return hr;

 

              if (MfxEvent::Sysx == event.m_eType)

              {

              IMfxEventQueue2* pq2;

                     hr = pqIn->QueryInterface( IID_IMfxEventQueue2, (void**)&pq2 );

                     if (FAILED( hr ))

                           return hr;

 

                     IMfxBufferFactory* pBufFact;

                     hr = pq2->GetBufferFactory( &pBufFact );

                     if (SUCCEEDED( hr ))

                     {

                           void* pv;

                           DWORD cb;

                           hr = pBufFact->GetPointer( event.m_hBuffer, &pv, cb );

                           if (SUCCEEDED( hr ))

                           {

                                  BYTE* pby = static_cast<BYTE*>(pv);

                                  for ( ; cb--; ++pby)

                                  {

                                         TRACE( "%x ", *pby );

                                  }

                                  TRACE0( "\n" );

                           }

                           pBufFact->Release();

                     }

                     pq2->Release();

              }

       }

       return hr;

}

IMfxInputCallback

This is implemented by the host application, working with IMfxInputPort to allow your filter to send MIDI data back to the host application.

HRESULT OnEvent( IMfxInputPort* pPort, const MfxEvent& mfxEvent );

Call this to “push” a MIDI event to the host application, i.e., when the user has struck a note on your plug-ins virtual keyboard, or moved a control in your UI which the host application should record.


IMfxInputPort

This allows your filter to send MIDI data back to the host application.  This is useful if you are building a DXi that presents some kind of virtual keyboard interface.  It is also useful for DXi or MFX filters having parameters that are controllable both from a UI and from MIDI.  In this latter usage, IMfxInputCallback would facilitate the recording of user interactions on the plug-in, as MIDI in the host application.

HRESULT SetInputCallback(IMfxInputCallback* pCallback );

This is called by the host application to establish a connection between it and your plug-in.  Your plug-in should make calls on pCallback whenever it needs to send MIDI data to the host.

pCallback may be NULL, meaning that the host no longer can accept MIDI data from your plug-in.

If your plug-in intends to send MIDI, and will therefore be storing pCallback, it should obey standard COM rules and AddRef the pointer before storing it.  Similarly, when your plug-in destroys itself, it should Release this pointer.

HRESULT GetInputCallback( IMfxInputCallback** ppCallback );

This is called by the host application to obtain a pointer to any callback pointer stored in your plug-in.

If your stored callback pointer is not NULL, this method should obey standard COM rules and AddRef the pointer before storing it in ppCallback.

IMfxInputPulse

This allows your filter to request that its IMfxEventFilter::OnInput member be called periodically, even if there is no actual MIDI input.

Please see the documentation for IMfxEventFilter::OnInput for important information about how to use IMfxInputPulse correctly.

HRESULT BeginPulse( LONG* plIntervalMsec )

Call this to begin having your IMfxEventFilter::OnInput member called at a periodic interval. The interval time is not under your control. BeginPulse passes out the interval using the plIntervalMsec argument.

HRESULT EndPulse()

Call this to stop having your IMfxEventFilter::OnInput member called at a periodic interval.

IMfxInstrument

This interface is how a DXi 2.0 software synthesizer exposes different sets of names (notes, controllers, etc.) per MIDI channel.  Using IMfxInstrument a synth can define a specially named drum instrument solely for channel 10, for example.

HRESULT  GetInstrumentName( char* pszName, int cbName );

Returns the name of this instrument as a DBCS string.

HRESULT GetBanksForPatchNames( int** panBank, int* pcBank );

Returns an array of bank numbers, each of which provides patch names.  Each bank number filled in panBank is guaranteed to provide a non-empty list of patch names when passed into GetPatchNames.

The caller is responsible for freeing the returned array panBank, by calling CoTaskMemFree.

HRESULT GetIsDrumPatch( int nBank, int nPatch );

If the specified patch in the specified bank is a drum patch, returns S_OK, otherwise returns S_FALSE.

Returns E_INVALIDARG is nBank is outside the range 0 through 16383 or if nPatch is outside the range 0 through 127.

HRESULT GetIsDiatonicNoteNames( int nBank, int nPatch );

Diatonic note names are names of musical notes like “C#0”, “Gb5”, and so on. Note names for a drum patch will typically not be diatonic note names, but rather names like “Kick”, “Snare”, and so on.

If the specified note name list is the list of diatonic note names, returns S_OK, otherwise returns S_FALSE.

Returns E_INVALIDARG is nBank is outside the range 0 through 16383 or if nPatch is outside the range 0 through 127.

HRESULT GetPatchNames(int nBank, IMfxNameList2** ppMap );

For a specified bank, provides an IMfxNameList2*, which represents the names of the patches within that bank.

Returns S_OK, else returns E_INVALIDARG if nBank is outside the range 0 through 16383 or IMfxNameList2 is a bad pointer.

HRESULT GetNoteNames( int nBank, int nPatch, IMfxNameList2** ppMap );

For a specified bank and patch, provides an IMfxNameList2*, which represents the names of the patches within that bank.

Returns S_OK, else returns E_INVALIDARG if nBank is outside the range 0 through 16383, if nPatch is outside the range 0 through 127, or if IMfxNameList2 is a bad pointer.

HRESULT GetControllerNames( IMfxNameList2** ppMap );

Provides an IMfxNameList2*, which represents the names of controllers. Returns S_OK.

HRESULT GetRpnNames( IMfxNameList2** ppMap );

Provides an IMfxNameList2*, which represents the names of RPNs (Registered Parameter Numbers).

HRESULT GetNrpnNames( IMfxNameList2** ppMap );

Provides an IMfxNameList2*, which represents the names of NRPNs (Non-Registered Parameter Numbers).

IMfxInstruments

IMfxInstruments represents all instruments currently known by the host application.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

void GetThePatchNameLists( IMfxNameListSet** ppIMfxNameListSet  );

Get the IMfxNameListSet containing all the IMfxNameLists for patch names.

Important: You must call Release on the IMfxNameListSet * when you're done.

void GetTheNoteNameLists( IMfxNameListSet** ppIMfxNameListSet );

Get the IMfxNameListSet containing all the IMfxNameLists for note names.

Important: You must call Release on the IMfxNameListSet * when you're done.

void GetTheControllerNameLists( IMfxNameListSet** ppIMfxNameListSet );

Get the IMfxNameListSet containing all the IMfxNameLists for controller names.

Important: You must call Release on the IMfxNameListSet * when you're done.

IMfxKeySigMap

IMfxKeySigMap represents the key signature map of the host application.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

int GetKeySigIndexForTime( LONG lTicks );

Get the index of the key signature change in effect for time 'lTicks'.

int GetKeySigCount();

Get the number of key signature changes in the map.

HRESULT GetKeySigAt( int ix, int* pnMeasure, int* pnKeySig );

Get a meter change. Returns:

pnMeasure:       the measure number of the key signature change .

pnKeySig:          the key signature (-7 = 7 flats, 0 = C Major, +7 = 7 sharps).

IMfxMarkerMap

IMfxMarkerMap represents the list of markers of the host application.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

int GetMarkerIndexForTime( LONG lTicks );

Get the index of the marker in effect for time lTicks.

int GetMarkerCount();

Get the number of markers in the map.

enum EUnits { Ticks, Frames, Samples };

Specifies the possible units returned by the GetMarkerAt method.

HRESULT GetMarkerAt( int ix, LONG* plTime, EUnits* peUnits, wchar_t** ppwszName );

Get the marker at the specified index.  Upon return, *plTime contains the time of the marker, in the units specified by *peUnits, and *ppwszName contains the name of the marker.

IMfxMarkerMap2

IMfxMarkerMap2 extends (inherits from) IMfxMarkerMap, adding access to “pitch markers.”  Pitch markers were added in SONAR 1.0 to represent project key changes, for the rendering of SONAR Groove Clips.

HRESULT GetPitchAtTime( MFX_TIME* pTime, int* pnPitch );

Determine the pitch in effect at the project timeline, at the time given by MFX_TIME.

IMfxMeterMap

IMfxMeterMap represents the meter (time signature) map of the host application.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

HRESULT TicksToMBT( LONG lTicks, int* pnMeasure, int* pnBeat, int* pnTicks );

Convert between musical ticks and Measure:Beat:Ticks format.

LONG MBTToTicks( int nMeasure, int nBeat, int nTicks );

Convert between Measure:Beat:Ticks and musical ticks format.

int GetMeterIndexForTime( LONG lTicks );

Get the index of the meter change in effect for time 'lTicks'.

int GetMeterCount();

Get the number of meter changes in the map.

int GetMeterAt( int ix, int* pnMeasure, int* pnTop, EBeatValue* eBottom );

Get a meter change. Returns:

*pnMeasure:   the measure number of the meter change.

*pnTop:           the number of beats per measure.

*eBottom:       the beat value (shift 1 left by this amount to get the beat value; e.g., 2 means 1 << 2 or 4).

IMfxNameList

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

void GetTitle( char* pszTitle, int cbTitle );

Get the title of this name list (e.g. "General MIDI").

void GetMaxNames( int* pnCount );

Get the maximum number of names that this list can contain, e.g.. 128 for note and controller names, 16384 for patch names.  This number is one greater than the maximum valid value for the ix  argument to GetAt.

void GetAt( int ixList, char* pszName, int cbName );

Get the specified name.

IMfxNameList2

This interface is used by soft synths to expose lists of banks, patches, controllers, RPNs and NRPNs.  It uses IMfxNameList as a base class, so all of those methods (listed above) must be implemented by an object which exposes IMfxNameList2.

IMfxNameList2 extends IMfxNameList by allowing names to stored (and iterated) as sparse arrays.  For example, the MIDI specification allows up to 16k RPNs, though few synthesizers expose that many.  By using IMfxNameList2, a soft synth can provide names for only those RPN numbers that are valid, thereby conserving memory.

HRESULT GetCount( int* pnCount );

Fills pnCount with the number of names in this map.  Return S_OK on success or E_POINTER if pnCount is a bad pointer.

HRESULT GetStartPosition( MFX_POSITION* pPos );

This function, along with GetNextAssoc, allow a client of IMfxNameList2 to iterate over all elements in the map.  (This function has the same behavior as in the MFC container classes.)

HRESULT GetNextAssoc( MFX_POSITION* pPos, int* pnKey, char* pszString, int cbString );

This function, along with GetStartPosition, allow a client of IMfxNameList2 to iterate over all elements in the map.  (This function has the same behavior as in the MFC container classes.)

The following code fragment shows how to iterate over elements in an IMfxNameList2:

MFX_POSITION  pos;

char          sz;

int           nKey;

HRESULT              hr = pMfxMap->GetStartPosition( &pos );

if (SUCCEEDED( hr ))

{

       while (S_OK == pMfxMap->GetNextAssoc( &pos, &nKey, sz, sizeof(sz) ))

       {

              // do something with nKey, sz

       }

}

HRESULT Lookup( int nKey, char* pszString, int cbString );

Retrieves the string associated with the given key.  Returns S_OK if an entry was found, S_FALSE if an entry was not found, or E_POINTER is pszString is a bad pointer.

IMfxNameListSet

IMfxNameListSet represents all list of names of a particular type.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

void GetCount( int* pnCount  );

Get the number of IMfxNameLists available.

void GetAt( int ixList, IMfxNameListSet** ppIMfxNameListSet );

Get the indicated IMfxNameList.

Important: You must call Release on the IMfxNameListSet* when you're done.

IMfxNotify

IMfxNotify is exposed by the soft synth to allow about mute, solo, etc, to be applied "just in time".

When data is fed to the soft ware synth via IMfxSoftSynth::OnEvents or IMfxSoftSynth::OnInput, the supplied data will not have certain properties, such as a key offset or velocity offset, subject to last minute changes.  IMfxNotify is the means for applying these last minute changes to the data stream.

HRESULT OnMfxNotify( MfxNotifyMsg* pMsg );

Tell the soft synth about the last minute change to data.

pMsg->m_type specifies which property has been changed (such as channel velocity offset).

pMsg->m_mfxChannel specifies the channel for which the property has changed.

Additional information about the property change is encoded within the remainder of the MfxNotifyMsg structure.

IMfxNotifyHost

This interface is exposed by the host application, to allow a DXi or MFX plug-in to tell the host about important changes that may have occurred.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect.   It is not exposed by SONAR 1.x or earlier Cakewalk products.

HRESULT OnMfxNotifyHost( UINT uMessage, IUnknown* pUnkFrom, LPARAM lParam );

This method is called by the DXi or MFX when it needs to notify the host about some changes.  The following is a list of valid values for uMessage, as defined in MidiFilter.h

MH_SYNTH_AUDIO_PORTS_CHANGE_BEGIN

The caller is a multi-output DXi, and is notifying the host about a change in its audio output count.  If the host returns an error code from this notification, the DXi must leave its output count in the state it was in before making this request.

In this notification, lParam should be set to the new (desired) number of synthesizer audio outputs.

MH_SYNTH_AUDIO_PORTS_CHANGE_END

The caller is a multi-output DXi, and is notifying the host that it has completed changing its audio output count.  If the host returns an error code from this notification, the DXi restore its output count in the state it was in before making this request.

In this notification, lParam should be set to the new (desired) number of synthesizer audio outputs.

IMfxSelection

This provides information about the selection, when a filter is being used as an offline edit command.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is provided by Cakewalk products when your filter is being used as an offline edit command. Otherwise QueryInterface will fail, indicating that your filter is being used e.g. during playback or in some other situation.

LONG GetFrom()

Returns the selection start time.

LONG GetThru()

Returns the selection end time. This is inclusive (“through”, not “up to”) -- the selection extends up through and including this tick.

LONG GetFirstEventStartTime()

Returns the start time of the first selected event.

This may be a later time than what GetFrom returns. The reason is that the application may allow the user to indicate a time selection that begins before the first event.

LONG GetLastEventStartTime()

Returns the start time of the last selected event.

This may be an earlier time than what GetThru returns. The reason is that the application may allow the user to indicate a time selection that extends after the last event.

LONG GetLastEventEndTime()

Returns the end time of the last selected event. For a note event, this will be the note’s start time plus its duration. For other types of events, this will be the same as the start time.

This may be an earlier time than what GetThru returns. The reason is that the application may allow the user to indicate a time selection that extends after the last event.

IMfxSoftSynth

This interface must be implemented by a software synthesis filter.  It is very similar to the IMfxEventFilter interface intended for MIDI effects.

HRESULT Connect( IUnknown* pContext );

This method is called before any other. It gives your filter a chance to obtain interfaces provided by the host application. To do so, QueryInterface the pContext pointer.

Your filter may require a certain interface like IMfxTempoMap. If QueryInterface fails, you should return E_FAIL to indicate that your filter cannot work at all.

Return NOERROR (or S_OK) to indicate that your filter found what was required and that it can work.

Note:    Your filter may want to use an interface if available, but be able to work without it (perhaps in some limited fashion). This is an important COM concept: An object can find out at runtime what capabilities another object supports. For example, the host application might add a new “ISuperDuperMap” interface. Subsequently, you might write a new filter for which ISuperDuperMap might be handy but not required. Your future filter could QueryInterface for ISuperDuperMap, and proceed accordingly. This way, your filter could take advantage of a specific host application, but remain compatible with all other host applications that don’t supply ISuperDuperMap.

If you do not retain a copy of pContext, be sure to call Release on it before returning.

HRESULT Disconnect();

This method is called before the final Release of your filter. At this time, you should Release any interface pointers that you obtained in Connect.

HRESULT OnStart( LONG lTime );

Called when playback (or other streaming session) starts, with the start time.

HRESULT OnLoop( LONG lTimeRestart, LONG lTimeStop );

Called when playback (or other streaming session) loops back to an earlier time without stopping and restarting.

A typical DXi will require some kind of internal cache or queue of events which have been sent via OnEvents (below), but not yet rendered into the audio output stream.  To prevent this queue from growing on each loop repetition, a DXi should be sure to respond to this by method by flushing any queued events that lie outside the loop boundary. 

Also, note that loops can be very short – even as short as a handful of MIDI ticks, even shorter than a single audio output buffer.  This implies that a DXi must take care to remember which loop iteration “owns” a particular MIDI event.  When it comes time to render that event into audio, the DXi will have repeated copies of the same event (from multiple loop repetitions) queued for the same time.  Unless the DXi knows the loop iteration associated with each event, it may mistakenly play them all at once.

HRESULT OnStop( LONG lTime );

Called when playback (or other streaming session) stops, with the stop time.

HRESULT OnEvents( LONG timeFrom, LONG timeThru, MFX_CHANNEL mch, IMfxEventQueue* pqIn );

This is called when a period of time is being processed. The period is represented by timeFrom through timeThru.

If there are any events to process, they are in the IMfxEventQueue* pqIn.

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

Your synth is free to remove elements from pqIn if desired, but this is not required, since the synth is always the last processing element in the chain.

Note that OnEvents is always called ahead of real-time – the events in pqIn will actually be rendered at a short time in the future.  In order to allow certain output changes to occur at the last minute, “just in time”, the mch parameter is provided.  This parameter can be used in the conjunction with the IMfxNotify interface to determine the actual state of the MIDI output stream the instant before the note is rendered.

Also, note that if the application is performing looping playback, that timeThru may actually extend beyond the loop end time.  This behavior is necessary in order to allow DXi’s to quantize their MIDI data – which might mean moving “future” data back in time.  For this reason, OnLoop should take care to flush any cached MIDI events outside the loop boundary.

Port and channel

The MfxEvent structure has members for the MIDI port and channel: m_byPort and m_byChan. Each event has a MIDI channel stored with it. In addition, tracks have a forced channel property, which can be “none” or a specific channel from 1 to 16. If the forced channel is other than “none”, then each event is stamped with that channel during playback. This stamping process takes place before you get the MfxEvent in OnEvents.  If the user changes the track’s MIDI channel in the middle of playback, the DXi will be notified of the change via the IMfxNotify interface.

HRESULT OnInput( MFX_CHANNEL mch, IMfxDataQueue* pqIn );

This is called to give you an opportunity to process MIDI input.

This method may be running on a time-critical thread, and should be written to execute as quickly as possible.

Your synth is free to remove elements from pqIn if desired, but this is not required, since the synth is always the last processing element in the chain.

In order to allow certain output changes to occur at the last minute, “just in time”, the mch parameter is provided.  This parameter can be used in the conjunction with the IMfxNotify interface to determine the actual state of the MIDI output stream the instant before the note is rendered.

HRESULT GetBanksForPatchNames( int** panBank, int* pcBank );

Returns an array of bank numbers, each of which provides patch names.  Each bank number filled in panBank is guaranteed to provide a non-empty list of patch names when passed into GetPatchNames.

The caller is responsible for freeing the returned array panBank, by calling CoTaskMemFree.

HRESULT GetIsDrumPatch( int nBank, int nPatch );

If the specified patch in the specified bank is a drum patch, returns S_OK, else return S_FALSE.

Returns E_INVALIDARG is nBank is outside the range 0 through 16383 or if nPatch is outside the range 0 through 127.

HRESULT GetIsDiatonicNoteNames( int nBank, int nPatch );

Diatonic note names are names of musical notes like “C#0”, “Gb5”, and so on. Note names for a drum patch will typically not be diatonic note names, but rather names like “Kick”, “Snare”, and so on.

If the specified note name list is the list of diatonic note names, returns S_OK, else return S_FALSE.

Returns E_INVALIDARG is nBank is outside the range 0 through 16383 or if nPatch is outside the range 0 through 127.

HRESULT GetPatchNames(int nBank, IMfxNameList2** ppMap );

For a specified bank, provides an IMfxNameList2*, which represents the names of the patches within that bank.

Returns S_OK, else returns E_INVALIDARG if nBank is outside the range 0 through 16383 or IMfxNameList2 is a bad pointer.

HRESULT GetNoteNames( int nBank, int nPatch, IMfxNameList2** ppMap );

For a specified bank and patch, provides an IMfxNameList2*, which represents the names of the patches within that bank.

Returns S_OK, else returns E_INVALIDARG if nBank is outside the range 0 through 16383, if nPatch is outside the range 0 through 127, or if IMfxNameList2 is a bad pointer.

HRESULT GetControllerNames( IMfxNameList2** ppMap );

Provides an IMfxNameList2*, which represents the names of controllers. Returns S_OK.

HRESULT GetRpnNames( IMfxNameList2** ppMap );

Provides an IMfxNameList2*, which represents the names of RPNs (Registered Parameter Numbers). Returns S_OK.

HRESULT GetNrpnNames( IMfxNameList2** ppMap );

Provides an IMfxNameList2*, which represents the names of NRPNs (Non-Registered Parameter Numbers). Returns S_OK.

IMfxSoftSynth2

This interface extends (inherits from) IMfxSoftSynth, to allow a DXi to expose a different instrument per MIDI channel.

HRESULT GetInstrument( int nChannel, IMfxInstrument** ppInstrument );

Return the instrument definition for the specified MIDI channel.  As per standard COM rules, the pointer returned in ppInstrument should be AddRef’d before returning.

IMfxTempoMap

IMfxTempoMap represents the tempo map of the host application.

You may try to obtain this interface by using QueryInterface on the IUnknown* passed to Connect. This interface is always provided by Cakewalk products.

LONG TicksToMsecs( LONG lTicks );

Convert between musical ticks and absolute milliseconds

LONG MsecsToTicks( LONG lMsecs );

Convert between absolute milliseconds and musical ticks

int GetTicksPerQuarterNote();

Get the number of ticks per quarter-note (the "PPQ" or "time base").

int GetTempoIndexForTime( LONG lTicks );

Get the index of the tempo change in effect for time 'lTicks'.

int GetTempoCount();

Get the number of tempo changes in the map.

int GetTempoAt( int ix, LONG* plTicks, int* pnBPM100 );

Get a tempo change.  Returns the time of the tempo change in *plTicks, and the value of the tempo in 100ths of beats per minute in *plBPM100.  (For example, 100.05 BPM is returned as 10005.)

IMfxTimeConverter

This interface is provided by the host application, to perform a variety of time conversions for time values on the project time-line.  It is especially useful for DXi synthesizers, who need to convert between musical time and absolute audio sample times, with high precision.

HRESULT ConvertMfxTime( MFX_TIME* pTime, MFX_TIME_FORMAT newFormat );

Convert the supplied time value pTime into a different format, specified by newFormat.  See MFX_TIME for a description of the available time formats for conversion.

Sample Code

DXi 2.0 Sample Code

The sample code is compatible with Microsoft® Visual C++ 6.0.  There is one sample included, a multi-output DXi 2.0 synthesizer called “TONAR”, which can synthesize sine, square or saw tooth waveforms.

MFX Sample Code

This sample code is compatible with Microsoft Visual C++ 5.0.  There are two samples included, one developed using MFC and another using the Windows API.  The sample is a simple MIDI echo effect.

Using the App Wizards

This SDK includes 2 App Wizards for Visual C++.  The first can be used to create a new MFX plug-in.  The other can be used to create a new DXi or DirectX plug-in.  Theses wizards are designed to automate much of the busy-work associated with creating new plug-ins, and thus to significantly decrease production time.

Tasks automated by the wizard include:

1.       Generation of CLSIDs.

2.       Creation of workspace and project files.

3.       Creation of major classes.

4.       Helpful commenting of the source files.

To use the wizard:

1.       Copy *.awx to the template directory of your Developer Studio installation, typically:    
C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Template

2.       Select File->New… and the Projects tab of the dialog box that will appear.

3.       Select Cakewalk MFX Plug-in Wizard or DXi2 / DirectX Plug-In from the list of templates, type a name for you project, and click OK.

4.       Follow the wizard prompts.  Note that the DXi 2.0 wizard requires you have already installed the Microsoft DirectX 8.0 SDK.

5.       Modify the generated source-files, filling in the “TODO” comments.  For the DXi 2.0 App Wizard, most of your coding will occur in {Project}.cpp and {Project}PropPage.cpp, where {Project} is the name you supplied to the App Wizard.

6.       Note:  A Readme.txt file that contains additional information on each of the files will be included with the project created by the wizard.  Refer to the sample code examples.