Permalink
Fetching contributors…
Cannot retrieve contributors at this time
262 lines (217 sloc) 9.68 KB


A group of functions, that allows you to bypass the Windows Multimedia API, thus getting rid of the lag caused by its slow buffer system.

How can I implement it in my program?

It's quite easy actually.

You can either import the functions through the header and library files here: DeveloperContent folder
Or you can use this generic example by Sono (MarcuzD), on how to make use of the Keppy's Direct MIDI API with LoadLibrary and GetProcAddress.
It works just like WinMM would, we'll see later the differences between using WinMM as usual, and using WinMM together with Keppy's Direct MIDI:

...
#define ALLOK 0;
#define NOTOK 1;

MMRESULT(WINAPI*mmOutOpen)(LPHMIDIOUT, lphmo, UINT uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwCallbackInstance, DWORD dwFlags) = 0;
MMRESULT(WINAPI*mmOutClose)(HMIDIOUT hmo) = 0;
MMRESULT(WINAPI*mmOutShortMsg)(HMIDIOUT hmo, DWORD dwMsg) = 0;
UINT(WINAPI*mmOutGetErrorTextA)(MMRESULT mmrError, LPTSTR, lpText, UINT cchText) = 0;

MMRESULT(WINAPI*KShortMsg)(DWORD msg) = 0;
...

...
int SetupWinMM() {
    // Check if WinMM is already in memory
    HDMODULE mm= GetModuleHandle("winmm");
    
    // It's not, load it manually from the system folder
    if (!mm) mm = LoadLibrary("winmm");
    
    // Something went wrong, return the error
    if (!mm) return GetLastError();

    // Load the MIDI functions from it
    mmOutOpen = (void*)GetProcAddress(mm, "midiOutOpen");
    if (!mmOutOpen) return GetLastError();
    mmOutClose = (void*)GetProcAddress(mm, "midiOutClose");
    if (!mmOutClose) return GetLastError();
    mmOutShortMsg = (void*)GetProcAddress(mm, "midiOutShortMsg");
    if (!mmOutShortMsg) return GetLastError();
    mmOutGetErrorTextA = (void*)GetProcAddress(mm, "midiOutGetErrorTextA");
    if (!mmOutGetErrorTextA) return GetLastError();
    
    // Everything's okay, continue
    return ALLOK;
}
...

Let's initialize WinMM:

...
int mmstatus = SetupWinMM();
if (mmstatus) {
    // The program failed to initialize WinMM, close it.
    printf("Failed to initialize Windows Multimedia: %i\n", mmstatus);
    exit(0);
}

MMRESULT mmres = mmOutOpen(&hmo, 1, 0, 0, CALLBACK_NULL);
if (mmres != MMSYSERR_NOERROR) {
    // Device 1 doesn't exist or failed to initialize, let's initialize Microsoft GS instead
    mmOutGetErrorTextA(mmres, buf, sizeof(buf));
    printf("OutOpen (%1) %s\n", mmres, buf);
    mmres = mmOutOpen(&hmo, 0, 0, 0, CALLBACK_NULL);
}
if (mmres != MMSYSERR_NOERROR) {
    // Microsoft GS also failed to initialize, close the app
    mmOutGetErrorTextA(mmres, buf, sizeof(buf));
    printf("OutOpen (%1) %s\n", mmres, buf);

    return NOTOK; 
}

// Check if OmniMIDI is loaded into memory, and initialize the Keppy's Direct MIDI calls
KShortMsg = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "SendDirectData");
if (KShortMsg) {
   // Replace default WinMM function with the one from the application itself
    puts("KDMAPI initialized.");
    mmOutShortMsg = _KOutShortMsg;
}
...

And here's the function from the application itself:

MMRESULT WINAPI _KOutShortMsg(HMIDIOUT hmo, DWORD msg) {
    // Pass the MIDI event to the Keppy's Direct MIDI call, and return the WinMM result
    return KShortMsg(msg);
}

As you can see, we're basically replacing the loaded midiOutShortMsg call with our own call, _KOutShortMsg, which redirects the messages from WinMM directly to (lol) the Keppy's Direct MIDI API calls.

Is it mandatory for me to implement these features, to use OmniMIDI?

Of course not! These calls are a thing for people who care about low latency and performance.
The driver will work fine with the default WinMM => modMessage system too.
It'll be slower when playing Black MIDIs, and the latency will also be higher, but it'll work just fine.

What functions are available?

As of October 29th 2018, these are the functions available in the Keppy's Direct MIDI API.
The "NoBuf" calls bypass the built-in buffer in OmniMIDI, and directly send the events to the events processing system.

InitializeKDMAPIStream

It initializes the driver, its stream and all its required threads. There are no arguments.

void(WINAPI*KDMInit)() = 0;
KDMInit = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "InitializeKDMAPIStream");

TerminateKMDAPIStream

It tells the driver to wrap up its stuff and to leave! There are no arguments.

void(WINAPI*KDMStop)() = 0;
KDMStop = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "TerminateKDMAPIStream");

ResetKDMAPIStream

Resets the MIDI channels. There are no arguments.

void(WINAPI*KDMReset)() = 0;
KDMReset = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "ResetKDMAPIStream");

ReturnKDMAPIVer

It returns the version of the Keppy's Direct MIDI API, as a string. There are no arguments available. Used by OmniMIDI Configurator.

char const*(WINAPI*KDMAPIVer)() = 0;
KDMAPIVer = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "ReturnKDMAPIVer");

IsKDMAPIAvailable

A generic check, useful for people who want to see if KDMAPI v1.2+ is available.
You NEED to call this function at least once, in order to switch the KSDAPI status value in the debug window to active.
There are no arguments available, and you have to manually catch the exception, if the function isn't available.

BOOL(WINAPI*KDMAPIStatus)() = 0;
KDMAPIStatus = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "IsKDMAPIAvailable");

ChangeDriverSettings

Allows developers to change the driver's settings from within the app, rather than asking the user to change them in the configurator.
Sending 0/nullptr will make it fallback to the settings from the registry.
The available arguments are:

  • const Settings* Struct: A pointer to your struct.
  • DWORD StructSize: The size of the struct.
VOID(WINAPI*KDMChangeSettings)(const Settings* Struct, DWORD StructSize) = 0;
KDMChangeSettings = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "ChangeDriverSettings");
...
	Settings MySettings;
	
	// Change your settings
	Settings.MaxVoices = 4;
	Settings.AudioFrequency = 22050;
	Settings.LiveChanges = TRUE;
	
	// Push the settings to the driver
	KDMChangeSettings(&MySettings, sizeof(MySettings));
	
	// Now make the driver fallback to the settings from the registry
	KDMChangeSettings(nullptr, 0);
...

You can get the code for the struct from "val.h": Click here!


GetDriverDebugInfo

Allows developers to get the driver's current rendering time and the voices that are currently active in the audio stream.

DebugInfo*(WINAPI*KDMGetDebugInfo)() = 0;
KDMGetDebugInfo = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "GetDriverDebugInfo");
...
	DebugInfo* DebugInfoFromDriver;
	DebugInfoFromDriver = KDMGetDebugInfo();
	
	//Do something with the info
	printf("Current rendering time: %d\n", DebugInfoFromDriver->RenderingTime); 
...

You can get the code for the struct from "val.h": Click here!


LoadCustomSoundFontsList

Allows developers to load their own custom SoundFonts or SoundFonts lists.
The available arguments are:

  • const TCHAR* Directory: A pointer to the unicode char array, containing the path.
VOID(WINAPI*KDMLoadCustomSFList)(const TCHAR* Directory) = 0;
KDMLoadCustomSFList = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "LoadCustomSoundFontsList");
...
	TCHAR Directory[MAX_PATH];
	wcscpy_s(Directory, MAX_PATH, _TEXT("C:\\MySF.sf2"));
	
	// Forward it to the driver
	KDMLoadCustomSFList(&Directory);
...

SendDirectData/SendDirectDataNoBuf

Allows you to send MIDI events to the driver.
The available arguments are:

  • DWORD dwMsg: The MIDI event to send to the driver.
MMRESULT(WINAPI*KShortMsg)(DWORD msg) = 0;
KShortMsg = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "SendDirectData"); // Or SendDirectDataNoBuf

SendDirectLongData/SendDirectLongDataNoBuf

Allows you to send MIDIHDR/System Exclusive events to the driver.
Both functions do the same thing. SendDirectLongDataNoBuf directly calls SendDirectLongData. I left NoBuf for retrocompatibility purpose with old patches.
You can handle the preparation of the buffer through PrepareLongData/UnprepareLongData.
The available arguments are:

  • MIDIHDR* IIMidiHdr: The pointer to the MIDIHDR.
MMRESULT(WINAPI*KLongMsg)(MIDIHDR* IIMidiHdr) = 0;
KLongMsg = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "SendDirectLongData"); // Or SendDirectLongDataNoBuf

PrepareLongData

Allows you to prepare the MIDIHDR buffer, before sending it to the driver through SendDirectLongData/SendDirectLongDataNoBuf.
Once a buffer is prepared, it becomes read-only.
The available arguments are:

  • MIDIHDR* IIMidiHdr: The pointer to the MIDIHDR.
MMRESULT(WINAPI*KPrepLongMsg)(MIDIHDR* IIMidiHdr) = 0;
KPrepLongMsg = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "PrepareLongData");

UnprepareLongData

Allows you to unprepare the MIDIHDR buffer, to make it writable again.
It is MANDATORY to unprepare a MIDIHDR before editing it, since PrepareLongData locks its data.
The available arguments are:

  • MIDIHDR* IIMidiHdr: The pointer to the MIDIHDR.
MMRESULT(WINAPI*KUnprepLongMsg)(MIDIHDR* IIMidiHdr) = 0;
KUnprepLongMsg = (void*)GetProcAddress(GetModuleHandle("OmniMIDI"), "UnprepareLongData");