Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmake/libremidi.examples.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ if(LIBREMIDI_HAS_WINMIDI)
add_backend_example(midi2_out_winmidi)
endif()

if(LIBREMIDI_HAS_KDMAPI)
add_backend_example(midi1_out_kdmapi)
endif()

if(LIBREMIDI_HAS_NETWORK)
add_example(network)
endif()
Expand Down
18 changes: 18 additions & 0 deletions cmake/libremidi.kdmapi.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
if(LIBREMIDI_NO_KDMAPI)
return()
endif()

if(NOT WIN32)
return()
endif()

if(${CMAKE_SYSTEM_NAME} MATCHES WindowsStore)
return()
endif()

message(STATUS "libremidi: using KDMAPI (OmniMIDI)")
set(LIBREMIDI_HAS_KDMAPI 1)
target_compile_definitions(libremidi
${_public}
LIBREMIDI_KDMAPI
)
7 changes: 7 additions & 0 deletions cmake/libremidi.sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ target_sources(libremidi PRIVATE
include/libremidi/backends/jack_ump/observer.hpp
include/libremidi/backends/jack_ump.hpp

include/libremidi/backends/kdmapi/config.hpp
include/libremidi/backends/kdmapi/helpers.hpp
include/libremidi/backends/kdmapi/midi_in.hpp
include/libremidi/backends/kdmapi/midi_out.hpp
include/libremidi/backends/kdmapi/observer.hpp
include/libremidi/backends/kdmapi.hpp

include/libremidi/backends/keyboard/config.hpp
include/libremidi/backends/keyboard/midi_in.hpp

Expand Down
1 change: 1 addition & 0 deletions cmake/libremidi.win32.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ if(NOT WIN32)
endif()

include(libremidi.winmm)
include(libremidi.kdmapi)

if(NOT LIBREMIDI_NO_WINMIDI OR NOT LIBREMIDI_NO_WINUWP)
include(libremidi.cppwinrt)
Expand Down
190 changes: 190 additions & 0 deletions examples/backends/midi1_out_kdmapi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//*****************************************//
// midi1_out_kdmapi.cpp
//
// Example demonstrating KDMAPI (OmniMIDI) output.
// Shows high-throughput MIDI output with KDMAPI's
// low-latency direct data path.
//
//*****************************************//

#include <libremidi/backends/kdmapi.hpp>
#include <libremidi/libremidi.hpp>

#include <chrono>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <random>
#include <thread>

int main(int argc, char** argv)
{
using namespace std::chrono_literals;

// Check if KDMAPI is available
if (!libremidi::kdmapi::kdmapi_loader::instance().is_available())
{
std::cerr << "KDMAPI is not available. Please install OmniMIDI.\n";
std::cerr << "Download from: https://github.com/KeppySoftware/OmniMIDI\n";
return 1;
}

std::cout << "KDMAPI is available!\n";

// Get KDMAPI version
auto& loader = libremidi::kdmapi::kdmapi_loader::instance();
if (loader.ReturnKDMAPIVer)
{
DWORD major{}, minor{}, build{}, rev{};
if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev))
{
std::cout << "KDMAPI Version: " << major << "." << minor << "." << build << " Rev. " << rev
<< "\n";
}
}

// Parse arguments
bool no_buffer = false;
bool stress_test = false;
int note_count = 100;

for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
if (arg == "-n" || arg == "--no-buffer")
{
no_buffer = true;
}
else if (arg == "-s" || arg == "--stress")
{
stress_test = true;
}
else if (arg == "-c" && i + 1 < argc)
{
note_count = std::stoi(argv[++i]);
}
}

// Configure KDMAPI output
libremidi::kdmapi::output_configuration kdm_conf;
kdm_conf.use_no_buffer = no_buffer;

std::cout << "Mode: " << (no_buffer ? "No-buffer (lowest latency)" : "Buffered") << "\n";

// Create MIDI output with KDMAPI configuration
libremidi::midi_out midiout{{}, kdm_conf};

// Get available ports
libremidi::observer obs{{}, libremidi::kdmapi::observer_configuration{}};
auto ports = obs.get_output_ports();

if (ports.empty())
{
std::cerr << "No KDMAPI output ports found!\n";
return 1;
}

std::cout << "Output port: " << ports[0].display_name << "\n\n";

// Open the port
auto err = midiout.open_port(ports[0]);
if (err != stdx::error{})
{
std::cerr << "Failed to open KDMAPI port!\n";
return 1;
}

if (stress_test)
{
// Stress test: blast as many notes as possible
std::cout << "Running stress test with " << note_count << " notes...\n";

std::random_device rd;
std::mt19937 gen(rd());
for(int i = 0; i < 1000; i++)
{
std::uniform_int_distribution<> note_dist(36, 96);
std::uniform_int_distribution<> vel_dist(60, 127);
std::uniform_int_distribution<> ch_dist(0, 15);

auto start = std::chrono::steady_clock::now();

for (int i = 0; i < note_count; ++i)
{
int note = note_dist(gen);
int vel = vel_dist(gen);
int ch = ch_dist(gen);

// Note On
midiout.send_message(0x90 | ch, note, vel);

// Brief delay to let some notes sound
if (i % 100 == 0)
{
std::this_thread::sleep_for(1ms);
}
}

auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration<double, std::milli>(end - start).count();

std::cout << "Sent " << note_count << " notes in " << std::fixed << std::setprecision(2)
<< duration << " ms\n";
std::cout << "Rate: " << std::fixed << std::setprecision(0)
<< (note_count / duration * 1000.0) << " notes/sec\n";

// Wait a bit then silence all
std::this_thread::sleep_for(1ms);

// All notes off on all channels
for (int ch = 0; ch < 16; ++ch)
{
midiout.send_message(0xB0 | ch, 123, 0);
midiout.send_message(0xB0 | ch, 120, 0);
}
}
}
else
{
// Simple demo: play a chord progression
std::cout << "Playing a simple chord progression...\n\n";

// Set piano on all channels
for (int ch = 0; ch < 16; ++ch)
{
midiout.send_message(0xC0 | ch, 0); // Program change: Piano
}

// Define some chords (C major, F major, G major, C major)
std::vector<std::vector<int>> chords = {
{60, 64, 67}, // C major
{60, 65, 69}, // F major
{59, 62, 67}, // G major
{60, 64, 67, 72}, // C major (with octave)
};

for (const auto& chord : chords)
{
// Note On for all notes in chord
for (int note : chord)
{
midiout.send_message(0x90, note, 100);
std::this_thread::sleep_for(5ms); // Slight strum effect
}

std::this_thread::sleep_for(500ms);

// Note Off for all notes
for (int note : chord)
{
midiout.send_message(0x80, note, 0);
}

std::this_thread::sleep_for(100ms);
}

std::cout << "Done!\n";
}

return 0;
}
1 change: 1 addition & 0 deletions include/libremidi/api-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef enum libremidi_api
KEYBOARD, /*!< Computer keyboard input */
NETWORK, /*!< MIDI over IP */
ANDROID_AMIDI, /*!< Android AMidi API */
KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */

// MIDI 2.0 APIs
ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */
Expand Down
8 changes: 8 additions & 0 deletions include/libremidi/backends.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
#include <libremidi/backends/winmm.hpp>
#endif

#if defined(LIBREMIDI_KDMAPI)
#include <libremidi/backends/kdmapi.hpp>
#endif

#if defined(LIBREMIDI_WINUWP)
#include <libremidi/backends/winuwp.hpp>
#endif
Expand Down Expand Up @@ -107,6 +111,10 @@ static constexpr auto available_backends = make_tl(
,
winmm_backend{}
#endif
#if defined(LIBREMIDI_KDMAPI)
,
kdmapi_backend{}
#endif
#if defined(LIBREMIDI_WINUWP)
,
winuwp_backend{}
Expand Down
43 changes: 43 additions & 0 deletions include/libremidi/backends/kdmapi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once
#include <libremidi/backends/kdmapi/midi_in.hpp>
#include <libremidi/backends/kdmapi/midi_out.hpp>
#include <libremidi/backends/kdmapi/observer.hpp>

#include <string_view>

//*********************************************************************//
// API: OmniMIDI KDMAPI (Keppy's Direct MIDI API)
//*********************************************************************//

// OmniMIDI is a low-latency MIDI synthesizer driver for Windows.
// KDMAPI provides a direct interface to OmniMIDI, bypassing the
// Windows Multimedia API for lower latency.
//
// Note: KDMAPI is output-only. The midi_in class is a placeholder
// that always fails - use a different backend for MIDI input.
//
// https://github.com/KeppySoftware/OmniMIDI

namespace libremidi
{

struct kdmapi_backend
{
using midi_in = kdmapi::midi_in;
using midi_out = kdmapi::midi_out;
using midi_observer = kdmapi::observer;
using midi_in_configuration = kdmapi::input_configuration;
using midi_out_configuration = kdmapi::output_configuration;
using midi_observer_configuration = kdmapi::observer_configuration;
static const constexpr auto API = libremidi::API::KDMAPI;
static const constexpr std::string_view name = "kdmapi";
static const constexpr std::string_view display_name = "OmniMIDI (KDMAPI)";

static inline bool available() noexcept
{
return kdmapi::kdmapi_loader::instance().is_available();
}
};

}

23 changes: 23 additions & 0 deletions include/libremidi/backends/kdmapi/config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once
#include <libremidi/config.hpp>

namespace libremidi::kdmapi
{
// KDMAPI does not support MIDI input
struct input_configuration
{
};

struct output_configuration
{
// Use the no-buffer variant of SendDirectData for lowest latency
// This bypasses OmniMIDI's internal buffer
bool use_no_buffer = false;
};

struct observer_configuration
{
};

}

Loading