Skip to content
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ option(LIBREMIDI_HEADER_ONLY "Header-only mode" OFF)

cmake_dependent_option(LIBREMIDI_NO_COREMIDI "Disable CoreMidi back-end" OFF "APPLE" OFF)
cmake_dependent_option(LIBREMIDI_NO_WINMM "Disable WinMM back-end" OFF "WIN32" OFF)
cmake_dependent_option(LIBREMIDI_NO_WINUWP "Disable UWP back-end" ON "WIN32" OFF)
cmake_dependent_option(LIBREMIDI_NO_WINMIDI "Disable WinMIDI back-end" ON "WIN32" OFF)
cmake_dependent_option(LIBREMIDI_NO_WINUWP "Disable UWP back-end" OFF "WIN32" OFF)
cmake_dependent_option(LIBREMIDI_NO_WINMIDI "Disable WinMIDI back-end" OFF "WIN32" OFF)
# if(LINUX) in CMake 3.25
cmake_dependent_option(LIBREMIDI_NO_ALSA "Disable ALSA back-end" OFF "UNIX; NOT APPLE" OFF)
cmake_dependent_option(LIBREMIDI_NO_UDEV "Disable udev support for ALSA" OFF "UNIX; NOT APPLE" OFF)
Expand Down
6 changes: 5 additions & 1 deletion bindings/python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
cmake_minimum_required(VERSION 3.28 FATAL_ERROR)
project(pylibremidi)

set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 1)

set(LIBREMIDI_FIND_BOOST 1)
set(LIBREMIDI_HEADER_ONLY 1)
set(LIBREMIDI_NEEDS_READERWRITERQUEUE 1)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 1)

add_subdirectory(../.. libremidi-src)

find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)

FetchContent_Declare(
Expand Down
18 changes: 11 additions & 7 deletions bindings/python/example.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
#!/usr/bin/env python
import pylibremidi as lm

api = lm.midi2_default_api()
print(api)
observer_config = lm.ObserverConfiguration()
observer = lm.Observer(observer_config, lm.midi2_default_api())
observer = lm.Observer(observer_config, api)

pi = observer.get_input_ports()
if len(pi) == 0:
print("No input available")
exit(1)
else:
print(f"Found input: {pi[0]}")
print(f"Found input: {pi[0].display_name}")

po = observer.get_output_ports()
if len(po) == 0:
print("No output available")
exit(1)
else:
print(f"Found output: {po[0]}")
print(f"Found output: {po[0].display_name}")

midi_out = lm.MidiOut()
out_config = lm.OutputConfiguration()
midi_out = lm.MidiOut(out_config, api)
err = midi_out.open_port(po[0])
if err:
print(err)
print("Error when opening output port: ", err)
exit(1)

in_config = lm.UmpInputConfiguration()
in_config.on_message = lambda msg: print(f"{msg}")

midi_in = lm.MidiIn(in_config)
midi_in = lm.MidiIn(in_config, api)
err = midi_in.open_port(pi[0])
if err:
print(err)
print("Error when opening input port: ", err)
exit(1)

while True:
Expand Down
4 changes: 4 additions & 0 deletions bindings/python/pylibremidi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ struct midi_out_poll_wrapper {
} // namespace libremidi

NB_MODULE(pylibremidi, m) {
#if defined(LIBREMIDI_WINMIDI) || defined(LIBREMIDI_WINUWP)
winrt::init_apartment();
#endif

namespace nb = nanobind;
nb::class_<stdx::error>(m, "Error")
.def("__bool__", [](stdx::error e) { return e != stdx::error{}; })
Expand Down
99 changes: 99 additions & 0 deletions cmake/libremidi.cppwinrt.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
if(NOT WIN32)
return()
endif()

# Adapted from vcpkg's cppwinrt portfile
if(NOT CMAKE_WINDOWS_KITS_10_DIR)
get_filename_component(CMAKE_WINDOWS_KITS_10_DIR "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0;InstallationFolder]" ABSOLUTE CACHE)
if ("${CMAKE_WINDOWS_KITS_10_DIR}" STREQUAL "/registry")
set(CMAKE_WINDOWS_KITS_10_DIR "C:/Program Files (x86)/Windows Kits/10")
endif()
endif()

set(WINSDK_PATH "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]")
# List all the SDKs manually as CMAKE_VS_blabla is only defined for VS generators
cmake_host_system_information(
RESULT WINSDK_PATH
QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/Microsoft/Windows Kits/Installed Roots"
VALUE KitsRoot10)

file(GLOB WINSDK_GLOB RELATIVE "${WINSDK_PATH}Include/" "${WINSDK_PATH}Include/*")
set(WINSDK_LATEST "0")
set(WINSDK_LIST)
foreach(dir ${WINSDK_GLOB})
if("${dir}" VERSION_GREATER "${WINSDK_LATEST}")
set(WINSDK_LATEST "${dir}")
endif()
list(PREPEND WINSDK_LIST "${CMAKE_WINDOWS_KITS_10_DIR}/Include/${dir}/cppwinrt")
endforeach()

if(NOT WINSDK_LATEST)
set(WINSDK_LATEST 10.0.26100.0)
endif()

if(NOT CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION)
set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION ${WINSDK_LATEST})
endif()

find_path(CPPWINRT_PATH "winrt/base.h"
PATHS
"${WINSDK_PATH}"
PATH_SUFFIXES
"${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/cppwinrt"
"Include/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/cppwinrt"
${WINSDK_LIST}
)

if(MSVC)
if (NOT EXISTS "${CMAKE_WINDOWS_KITS_10_DIR}/Lib/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.")
message(FATAL_ERROR "Windows SDK not found. Install a Windows SDK and pass it to CMake with e.g. -DCMAKE_GENERATOR_PLATFORM=x64,version=10.0.26100.0 -DCMAKE_SYSTEM_VERSION=10.0.26100.0")
endif()

# Download archive
if(NOT EXISTS "${CMAKE_BINARY_DIR}/cppwinrt.zip")
file(DOWNLOAD
"https://www.nuget.org/api/v2/package/Microsoft.Windows.CppWinRT"
"${CMAKE_BINARY_DIR}/cppwinrt.zip"
)
file(ARCHIVE_EXTRACT
INPUT "${CMAKE_BINARY_DIR}/cppwinrt.zip"
DESTINATION "${CMAKE_BINARY_DIR}/cppwinrt-nuget/"
)
endif()

set(CPPWINRT_TOOL "${CMAKE_BINARY_DIR}/cppwinrt-nuget/bin/cppwinrt.exe")
else()
find_path(WINRT_HEADER_PATH "winrt/Windows.Devices.Midi.h")
if(NOT WINRT_HEADER_PATH)
message(FATAL_ERROR "WinRT headers not found. On MSYS2, install the cppwinrt package")
else()
message("WinRT headers found: ${WINRT_HEADER_PATH}")
endif()

# Will be in /usr/bin/ with MSYS2
find_program(CPPWINRT_TOOL "cppwinrt")
endif()

file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/cppwinrt/")
if(CPPWINRT_TOOL)
# Enumerate winmd IDL files and store them in a response file
file(TO_CMAKE_PATH "${CMAKE_WINDOWS_KITS_10_DIR}/References/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" winsdk)
file(GLOB winmds "${winsdk}/*/*/*.winmd")

set(args "")
foreach(winmd IN LISTS winmds)
string(APPEND args "-input \"${winmd}\"\n")
endforeach()

# Recreate the sources
file(WRITE "${CMAKE_BINARY_DIR}/cppwinrt-src/cppwinrt.rsp" "${args}")

# Process the Windows API IDL files into headers
execute_process(
COMMAND "${CPPWINRT_TOOL}"
"@${CMAKE_BINARY_DIR}/cppwinrt-src/cppwinrt.rsp"
-output "${CMAKE_BINARY_DIR}/cppwinrt"
-verbose
)
endif()

4 changes: 4 additions & 0 deletions cmake/libremidi.net.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
)
endif()
endif()

if(WIN32)
target_link_libraries(libremidi ${_public} Ws2_32)
endif()
8 changes: 6 additions & 2 deletions cmake/libremidi.win32.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ if(NOT WIN32)
endif()

include(libremidi.winmm)
include(libremidi.winmidi)
include(libremidi.winuwp)

if(NOT LIBREMIDI_NO_WINMIDI OR NOT LIBREMIDI_NO_WINUWP)
include(libremidi.cppwinrt)
include(libremidi.winmidi)
include(libremidi.winuwp)
endif()
47 changes: 42 additions & 5 deletions cmake/libremidi.winmidi.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,60 @@ if(NOT EXISTS "${CMAKE_BINARY_DIR}/winmidi-headers.zip")
"${CMAKE_BINARY_DIR}/winmidi-headers.zip"
)
endif()

file(ARCHIVE_EXTRACT
INPUT "${CMAKE_BINARY_DIR}/winmidi-headers.zip"
DESTINATION "${CMAKE_BINARY_DIR}/winmidi-headers/"
)

file(MAKE_DIRECTORY
"${CMAKE_BINARY_DIR}/cppwinrt/"
"${CMAKE_BINARY_DIR}/cppwinrt/"
)
file(

file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/cppwinrt-winmidi/")
if(CPPWINRT_TOOL)
# Enumerate winmd IDL files and store them in a response file
file(TO_CMAKE_PATH "${CMAKE_WINDOWS_KITS_10_DIR}/References/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" winsdk)
file(GLOB winmds "${winsdk}/*/*/*.winmd")

set(args "")
string(APPEND args "-input \"${CMAKE_BINARY_DIR}/winmidi-headers/ref/native/Microsoft.Windows.Devices.Midi2.winmd\"\n")

foreach(winmd IN LISTS winmds)
string(APPEND args "-ref \"${winmd}\"\n")
endforeach()

# Recreate the sources
file(WRITE "${CMAKE_BINARY_DIR}/cppwinrt-src/cppwinrt-winmidi.rsp" "${args}")

execute_process(
COMMAND "${CPPWINRT_TOOL}"
"@${CMAKE_BINARY_DIR}/cppwinrt-src/cppwinrt-winmidi.rsp"
-output "${CMAKE_BINARY_DIR}/cppwinrt-winmidi"
-verbose
)

file(
COPY
"${CMAKE_BINARY_DIR}/winmidi-headers/build/native/include/winmidi/init"
DESTINATION
"${CMAKE_BINARY_DIR}/cppwinrt-winmidi/"
)
else()
# In case we don't have cppwinrt we can still try to just use the SDK headers directly
file(
COPY
"${CMAKE_BINARY_DIR}/winmidi-headers/build/native/include/winmidi"
DESTINATION
"${CMAKE_BINARY_DIR}/cppwinrt/"
)
"${CMAKE_BINARY_DIR}/cppwinrt-winmidi/"
)
endif()

message(STATUS "libremidi: using Windows MIDI Services")
target_include_directories(libremidi SYSTEM ${_public}
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/cppwinrt>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/cppwinrt/winmidi>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/cppwinrt-winmidi>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/cppwinrt-winmidi/winmidi>
)
target_compile_definitions(libremidi ${_public} LIBREMIDI_WINMIDI)
set(LIBREMIDI_HAS_WINMIDI 1)
Expand Down
33 changes: 6 additions & 27 deletions cmake/libremidi.winuwp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,13 @@ if(LIBREMIDI_HAS_WINMIDI)
set(LIBREMIDI_HAS_WINUWP 1)
message(STATUS "libremidi: using WinUWP")

target_compile_options(libremidi ${_public} /EHsc)
if(MSVC)
target_compile_options(libremidi ${_public} /EHsc)
endif()
target_compile_definitions(libremidi ${_public} LIBREMIDI_WINUWP)
return()
endif()

set(WINSDK_PATH "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]")
# List all the SDKs manually as CMAKE_VS_blabla is only defined for VS generators
cmake_host_system_information(
RESULT WINSDK_PATH
QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/Microsoft/Windows Kits/Installed Roots"
VALUE KitsRoot10)

file(GLOB WINSDK_GLOB RELATIVE "${WINSDK_PATH}Include/" "${WINSDK_PATH}Include/*")
set(WINSDK_LIST)
foreach(dir ${WINSDK_GLOB})
list(APPEND WINSDK_LIST "Include/${dir}/cppwinrt")
message(" - WinSDK version: Include/${dir}/cppwinrt")
endforeach()

find_path(CPPWINRT_PATH "winrt/base.h"
PATHS
"${WINSDK_PATH}"
PATH_SUFFIXES
"${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/cppwinrt"
"Include/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/cppwinrt"
${WINSDK_LIST}
)
message("winrt paths:\n -- ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/cppwinrt\n -- Include/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/cppwinrt\n -- ${WINSDK_LIST}")
message("final path:\n -- ${CPPWINRT_PATH}")

if(CPPWINRT_PATH)
message(STATUS "libremidi: using WinUWP")
set(LIBREMIDI_HAS_WINUWP 1)
Expand All @@ -44,7 +21,9 @@ if(CPPWINRT_PATH)
target_compile_definitions(libremidi ${_public} LIBREMIDI_WINUWP)
target_link_libraries(libremidi INTERFACE RuntimeObject)
# We don't need /ZW option here (support for C++/CX)' as we use C++/WinRT
target_compile_options(libremidi ${_public} /EHsc)
if(MSVC)
target_compile_options(libremidi ${_public} /EHsc)
endif()
else()
message(STATUS "libremidi: Failed to find Windows SDK, UWP MIDI backend will not be available")
return()
Expand Down
6 changes: 5 additions & 1 deletion include/libremidi/backends/winmidi/midi_in.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ class midi_in_impl final

stdx::error open_port(const input_port& port, std::string_view) override
{
auto [ep, gp] = get_port(port.device_name, port.port);
auto device_id = std::get_if<std::string>(&port.device);
if (!device_id)
return std::errc::invalid_argument;

auto [ep, gp] = get_port(*device_id, port.port);
if (!ep || !gp)
return std::errc::address_not_available;

Expand Down
6 changes: 5 additions & 1 deletion include/libremidi/backends/winmidi/midi_out.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ class midi_out_impl final

stdx::error open_port(const output_port& port, std::string_view) override
{
auto [ep, gp] = get_port(port.device_name, port.port);
auto device_id = std::get_if<std::string>(&port.device);
if (!device_id)
return std::errc::invalid_argument;

auto [ep, gp] = get_port(*device_id, port.port);
if (!ep || !gp)
return std::errc::address_not_available;

Expand Down
Loading