#pragma once #include #include #include namespace libremidi { class midi_out_winmm final : public midi1::out_api , public error_handler { public: struct : output_configuration , winmm_output_configuration { } configuration; midi_out_winmm(output_configuration&& conf, winmm_output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { // We'll issue a warning here if no devices are available but not // throw an error since the user can plug something in later. if (midiOutGetNumDevs() == 0) { warning( configuration, "midi_out_winmm::initialize: no MIDI output devices currently " "available."); } } ~midi_out_winmm() override { // Close a connection if it exists. midi_out_winmm::close_port(); } bool open_virtual_port(std::string_view) override { warning(configuration, "midi_out_winmm: open_virtual_port unsupported"); return false; } void set_client_name(std::string_view) override { warning(configuration, "midi_out_winmm: set_client_name unsupported"); } void set_port_name(std::string_view) override { warning(configuration, "midi_out_winmm: set_port_name unsupported"); } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_MM; } [[nodiscard]] bool do_open(unsigned int portNumber) { MMRESULT result = midiOutOpen(&this->outHandle, portNumber, 0, 0, CALLBACK_NULL); if (result != MMSYSERR_NOERROR) { error( configuration, "midi_out_winmm::open_port: error creating Windows MM MIDI output " "port."); return false; } return true; } bool open_port(const output_port& p, std::string_view) override { observer_winmm obs{{}, winmm_observer_configuration{}}; auto ports = obs.get_output_ports(); // First check with the display name, e.g. MIDI KEYBOARD 2 will match MIDI KEYBOARD 2 for (auto& port : ports) { if (p.display_name == port.display_name) return do_open(port.port); } // If nothing is found, try to check with the raw name for (auto& port : ports) { if (p.port_name == port.port_name) return do_open(port.port); } error( configuration, "midi_out_winmm::open_port: port not found: " + p.port_name); return false; } void close_port() override { if (this->outHandle) midiOutClose(this->outHandle); this->outHandle = nullptr; connected_ = false; } void send_message(const unsigned char* message, size_t size) override { if (!connected_) return; if (size == 0) { warning(configuration, "midi_out_winmm::send_message: message argument is empty!"); return; } if (message[0] == 0xF0) { // Sysex message buffer.assign(message, message + size); // FIXME this can be made asynchronous... see Chrome source. // But need to know whe buffers are freed. // Create and prepare MIDIHDR structure. MIDIHDR sysex{}; sysex.lpData = (LPSTR)buffer.data(); sysex.dwBufferLength = size; sysex.dwFlags = 0; auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { error( configuration, "midi_out_winmm::send_message: error preparing sysex header."); return; } // Send the message. result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { error( configuration, "midi_out_winmm::send_message: error sending sysex message."); return; } // Unprepare the buffer and MIDIHDR. // FIXME yuck while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR))) Sleep(1); } else { // Channel or system message. // Make sure the message size isn't too big. if (size > 3) { warning( configuration, "midi_out_winmm::send_message: message size is greater than 3 bytes " "(and not sysex)!"); return; } // Pack MIDI bytes into double word. DWORD packet; std::copy_n(message, size, (unsigned char*)&packet); // Send the message immediately. auto result = midiOutShortMsg(this->outHandle, packet); if (result != MMSYSERR_NOERROR) { error( configuration, "midi_out_winmm::send_message: error sending MIDI message."); } } } private: HMIDIOUT outHandle; // Handle to Midi Output Device std::vector buffer; }; }