Skip to content

Instantly share code, notes, and snippets.

@trueroad
Last active July 31, 2022 11:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trueroad/9c5317af5f212b2de7c7012e76b9e66b to your computer and use it in GitHub Desktop.
Save trueroad/9c5317af5f212b2de7c7012e76b9e66b to your computer and use it in GitHub Desktop.
WinRT MIDI Transfer with C++/WinRT
//
// WinRT MIDI Transfer with C++/WinRT
// https://gist.github.com/trueroad/9c5317af5f212b2de7c7012e76b9e66b
//
// Copyright (C) 2022 Masamichi Hosoda.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// C++/WinRT で MIDI メッセージを転送する試み
//
// 普通のコンソールアプリで UWP MIDI の API を叩いて MIDI を扱う。
// 従来は WinMM や MME などと呼ばれる API で MIDI を取り扱っていたが、
// これはかなり古くから存在する API であり、
// 一部を除きマルチクライアントに対応していない
// (カスタムドライバでなければ同じポートを複数アプリで開くことができない)
// Bluetooth MIDI (BLE MIDI) に対応していない、などの問題がある。
// 一方、新しく実装された UWP MIDI の API は
// (UWP MIDI で使っている限り)マルチクライアント対応だし、
// Bluetooth MIDI (BLE MIDI) にも対応している。
// UWP MIDI API は UWP でなければ使えないと思っていたのだが、
// 新しく導入された C++/WinRT を使えば、従来のデスクトップアプリでも
// コンソールアプリでも、普通の C++ プログラムから使うことができる
// ことがわかった。
// そこで、実験的に MIDI IN から入ってきた MIDI メッセージを
// MIDI OUT へ転送しメッセージの内容を表示するコンソールアプリを作ってみる。
// その際、従来のコンソールアプリとの違いを明らかにするため、
// Visual Studio で C++/WinRT 向けのプロジェクトを作るのではなく、
// わざと従来のコンソールアプリ向けのプロジェクトを作り、
// 明示的に設定変更することで C++/WinRT を使えるようにする。
// コンパイラ環境
//
// C++/WinRT は Visual Studio 2017 の途中のバージョン以降もしくは
// Visual Studio 2019 以降で使うことができるとされている。
// そこで、今回は Visual Studio 2019 を使用することにする。
// Visual Studio 2019 の機能としては「C++ によるデスクトップ開発」
// 「ユニバーサル Windows プラットフォーム開発」を
// インストールしておく必要がある。また、今回は使わないが拡張機能で
// 「C++/WinRT templates and visualizer for VS2019」
// をインストールしておけば C++/WinRT 向けプロジェクトが作れるようになるので
// 通常の C++/WinRT 開発をするんは便利だろう。
// プロジェクト設定
//
// 今回は Visual Studio 2019 Community を使う。
// 「新しいプロジェクトの作成」を選び、選択肢の中から
//
// コンソールアプリ
// Windows ターミナルでコードを実行します。規定では"Hello World"を出力します。
// [C++] [Windows] [コンソール]
//
// となっているものを選ぶ。(わざと UWP や C++/WinRT を選ばない)
// プロジェクト名は winrt_midi_transfer にしておく。
//
// プロジェクトが開かれたら、メニューから
// 「プロジェクト」→「winrt_midi_transfer のプロパティ」
// を選んで「winrt_midi_transfer プロパティページ」ダイアログを出す。
//
// 左上の「構成」で「すべての構成」を選択、
// その右にある「プラットフォーム」で「すべてのプラットフォーム」を選択する。
//
// 左側「構成プロパティ」のツリーで
// 「C/C++」→「言語」をクリックする。
// 右側で
// 「準拠モード」を「はい (/permissive-)」から「いいえ (/permissive)」へ変更、
// 「C++ 言語標準」を「規定(ISO C++14 標準)」から
// 「ISO C++17 標準 (/std:c++17)」へ変更する。
//
// 左側「構成プロパティ」のツリーで
// 「C/C++」→「コマンドライン」をクリックする。
// 右側で
// 「追加のオプション」に「/await /bigobj」を入力する。
//
// 左側「構成プロパティ」のツリーで
// 「リンカー」→「入力」をクリックする。
// 右側で
// 「追加の依存ファイル」のプルダウンから「<編集...>」を選び、
// 「追加の依存ファイル」ダイアログを出す。
// 一番上の入力欄に「windowsapp.lib」を入力して OK ボタンを押す。
//
// 「winrt_midi_transfer プロパティページ」ダイアログに戻るので、
// OK ボタンを押して、設定完了。
// ビルド
//
// Visual Studio 2019 のツールバーで
// 「Debug」「x64」または「Release」「x64」を選んで
// ビルドやデバッグをすればよい。
// 参考
// 似たような機能を python winrt で実現したもの
// https://gist.github.com/trueroad/6beaf87280afb2b5e33b4838d73be6ed
#include <chrono>
#include <future>
#include <mutex>
#include <iomanip>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <windows.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Midi.h>
#include <winrt/Windows.Storage.Streams.h>
using namespace std::chrono_literals;
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Devices::Midi;
using namespace Windows::Storage::Streams;
// WinRT MIDI Transfer class
class winrt_midi_transfer
{
public:
// ポートの名前などをまとめた構造体
struct port
{
// ポートの名前
std::wstring name;
// ポートの ID
std::wstring id;
// ID の 16 進数 8 桁部分
std::wstring hex_id;
// ポートの表示名(ID の 16 進数 8 桁部分を含む)
std::wstring display_name;
};
winrt_midi_transfer()
{
// 念のため mutex をかけておく
std::lock_guard<std::mutex> lock(mtx_ctor_);
if (!event_)
{
// 終了待ちイベントが無いので作成する
event_.attach(::CreateEvent(nullptr, true, false, nullptr));
winrt::check_bool(static_cast<bool>(event_));
// Ctrl+C などのハンドラを登録する
winrt::check_bool(::SetConsoleCtrlHandler(HandlerRoutine, true));
}
}
// ポートのリストを取得する
//
// 引数:デバイスを選択するための文字列
// MidiInPort::GetDeviceSelector() または
// MidiOutPort::GetDeviceSelector() を指定する。
// 返り値:ポートのリスト
//
// IAsyncOperation などは C++/WinRT の型でなければ使用できない
// (コンパイルエラーになる)ので、
// コルーチンにするために std::future を使っている。
// マイクロソフトのドキュメントでは C++/WinRT の型以外を返す際には
// std::future より ppltasks.h の concurrency::task を勧めているが、
// 使い方がよくわからないし Visual Studio 以外の環境でも
// 使えるようになるか疑問なので C++ 標準の std::future の方を使う。
// ただし、現状 GCC-11 では C++20 を有効にしていても
// std::future ではコルーチン化できないようである。
//
// 時間がかかっても構わないのでコルーチンではなく普通の関数にしてしまう
// (FindAllAsync() に .get() を付ける)ことや、
// 返り値を返さずにメンバ変数に格納するだけにして IAsyncAction 型にする、
// 返り値を std::vector ではなく C++/WinRT の型に変えて IAsyncOperation
// を使うようにする、などの方法も考えられる。
std::future<std::vector<port>> list_ports(winrt::hstring device_selector)
{
const auto devs =
co_await DeviceInformation::FindAllAsync(device_selector);
std::vector<port> retval;
for (const auto& d : devs)
{
port p;
// d.Name() などは C++/WinRT の文字列型 winrt::hstring である。
// これは operator std::wstring_view() があり
// std::wstring には std::wstring_view() を引数に取る
// 代入演算子があるのでそのまま代入できる。
p.name = d.Name();
p.id = d.Id();
p.display_name = d.Name();
// ID の 16 進数 8 桁部分を抽出する
std::wsmatch m;
if (std::regex_search(p.id, m, hex_id_pattern_))
{
// ID の 16 進数 8 桁部分を保存する
p.hex_id = m[1];
// 表示名にも付与する
// 名前がまったく同じでも ID で区別できるようになる
std::wostringstream ss;
ss << p.name
<< L" [ "
<< p.hex_id
<< L" ]";
p.display_name = ss.str();
}
// ID に 16 進数 8 桁部分がなければ何もしない
// (p.hex_id は空文字列、表示名は名前と同じ)
// `Microsoft GS Wavetable Synth` が該当
retval.push_back(p);
}
co_return retval;
}
// Windows 10 では MIDI OUT の名前がおかしいので修正する
//
// なぜか Windows 10 では一部の MIDI OUT ポートの名前が
// `MIDI` になってしまい名前では判別がつかなくなってしまう。
// https://forum.juce.com/t/winrt-midi-output-wrong-device-names/43301
// (上記ページによると `MIDI-2` などになる場合もあるようだが
// 手元では複数ポートがあってもすべて `MIDI` になり、
// `MIDI-2` などは見たことが無い)
// これは MIDI IN/OUT で ID の 16 進数 8 桁部分が同じものがある場合に
// 発生する現象のようであり、恐らく Windows 10 のバグだが放置されている。
// (手元では USB MIDI の M4U eX の 8 ポートはすべて発生、
// BLE MIDI の MD-BT01 は IN/OUT で 16 進数 8 桁が異なるため発生しない)
//
// 上記ページにあるとおり、MIDIberry というアプリは自前で何とかしており、
// IN/OUT で ID が類似している場合に IN の名前を使っているらしい。
// そこで、同様の方法で MIDI OUT の名前を修正する。
void fix_display_name()
{
for (auto& outp : out_ports_)
{
for (const auto& inp : in_ports_)
{
if (outp.hex_id == inp.hex_id &&
std::wstring_view{ outp.name }.substr(0, 4) == L"MIDI")
{
// hex_id (ID の 16 進数 8 桁部分)が一致、かつ
// 名前の最初の 4 文字
// (std::wstring_view にすることで
// 部分文字列を新しく作らないようにしている)
// が MIDI であれば、事象発生とみなし、
// IN の表示名を OUT の表示名にコピーする。
outp.display_name = inp.display_name;
break;
}
}
}
}
// MIDI IN ポートを選択する
//
// 返り値:選択された MIDI IN ポート
// 選択されなかったら nullptr になる
IAsyncOperation<MidiInPort> select_midi_in_port()
{
in_ports_ = co_await list_ports(MidiInPort::GetDeviceSelector());
// MIDI IN ポートの選択肢を表示
std::wcout
<< std::endl
<< L"MIDI IN ports"
<< std::endl << std::endl;
for (unsigned int i = 0; i < in_ports_.size(); i++)
{
std::wcout
<< i
<< L": "
<< in_ports_[i].display_name
<< std::endl;
}
// 選択肢を入力させる
std::wcout
<< std::endl
<< L"Select number > "
<< std::flush;
unsigned int choice;
std::wcin >> choice;
// 選択範囲内か否か判定
if (choice < in_ports_.size())
{
// 選択されたポートの情報を表示
std::wcout
<< std::endl
<< L"Selected: "
<< choice
<< L", "
<< in_ports_[choice].name
<< L", "
<< in_ports_[choice].id
<< L", "
<< in_ports_[choice].hex_id
<< L", "
<< in_ports_[choice].display_name
<< std::endl;
// ポートの作成を要求
auto async = MidiInPort::FromIdAsync(in_ports_[choice].id);
// 5 秒待って完了していたら結果を受け取って返す
if (async.wait_for(5s) == AsyncStatus::Completed)
co_return async.GetResults();
// 5 秒では完了しなかったのでタイムアウト
// ペアリング済みの BLE MIDI は圏外でもリストに登場するが
// 接続できないとずっと処理中になるのでタイムアウトを設定
std::wcout
<< L"Timeout"
<< std::endl;
co_return nullptr;
}
// 選択範囲外
std::wcout
<< L"Error: "
<< choice
<< std::endl;
co_return nullptr;
}
// MIDI OUT ポートを選択する
//
// 返り値:選択された MIDI IN ポート
// 選択されなかったら nullptr になる
//
// 返り値は MidiOutPort ではなく IMidiOutPort である。
// I がつく型は I が付かない型と MidiSynthesizer 型を
// 統一的に扱える型のようである。
// ただ、GetDeviceSelector() や FromIdAsync() は
// I が付かない方にのみ存在し、しかも I が付かない FromIdAsync() は
// なぜか I が付く方を返すという WinRT 仕様になっている。
// 本コルーチンも I が付く方を返すようにしている。
IAsyncOperation<IMidiOutPort> select_midi_out_port()
{
out_ports_ = co_await list_ports(MidiOutPort::GetDeviceSelector());
fix_display_name();
// MIDI OUT ポートの選択肢を表示
std::wcout
<< std::endl
<< L"MIDI OUT ports"
<< std::endl << std::endl;
for (unsigned int i = 0; i < out_ports_.size(); i++)
{
std::wcout
<< i
<< L": "
<< out_ports_[i].display_name
<< std::endl;
}
// 選択肢を入力させる
std::wcout
<< std::endl
<< L"Select number > "
<< std::flush;
unsigned int choice;
std::wcin >> choice;
// 選択範囲内か否か判定
if (choice < out_ports_.size())
{
// 選択されたポートの情報を表示
std::wcout
<< std::endl
<< L"Selected: "
<< choice
<< L", "
<< out_ports_[choice].name
<< L", "
<< out_ports_[choice].id
<< L", "
<< out_ports_[choice].hex_id
<< L", "
<< out_ports_[choice].display_name
<< std::endl;
// ポートの作成を要求
auto async = MidiOutPort::FromIdAsync(out_ports_[choice].id);
// 5 秒待って完了していたら結果を受け取って返す
if (async.wait_for(5s) == AsyncStatus::Completed)
co_return async.GetResults();
// 5 秒では完了しなかったのでタイムアウト
// ペアリング済みの BLE MIDI は圏外でもリストに登場するが
// 接続できないとずっと処理中になるのでタイムアウトを設定
std::wcout
<< L"Timeout"
<< std::endl;
co_return nullptr;
}
// 選択範囲外
std::wcout
<< L"Error: "
<< choice
<< std::endl;
co_return nullptr;
}
// MIDI IN ポートと MIDI OUT ポートを選んで転送
IAsyncAction transfer()
{
in_port_ = co_await select_midi_in_port();
out_port_ = co_await select_midi_out_port();
if (!in_port_)
{
std::wcout
<< L"Error: MIDI IN is not opened"
<< std::endl;
co_return;
}
std::wcout
<< L"Callback starting..."
<< std::endl;
// MIDI メッセージ受信時のイベントハンドラを設定し、
// 元のイベントハンドラのトークンを保存する。
// this ポインタを使っているのでインスタンス破棄中などの
// 微妙なタイミングでイベント発生するとマズいことになる。
// get_strong() で強参照を得るなどした方がよさそうではあるが、
// 本クラスは C++/WinRT のクラスから派生していないので使えない。
auto before_token = in_port_.MessageReceived({ this,
&winrt_midi_transfer::midi_in_callback });
std::wcout
<< L"Started"
<< std::endl;
// _event がシグナル状態になるまでコルーチンを中断
co_await resume_on_signal(event_.get());
// イベントハンドラを元に戻す。
// これによって this ポインタを使った自前ハンドラが呼ばれなくなり、
// この後の this ポインタ参照を防ぐ。
in_port_.MessageReceived(before_token);
}
private:
std::wstring note_off(const MidiNoteOffMessage& message)
{
const auto channel{ message.Channel() };
const auto note{ message.Note() };
const auto velocity{ message.Velocity() };
std::wostringstream buff;
buff << L"note_off: channel "
<< channel
<< L", note "
<< note
<< L", velocity "
<< velocity;
return buff.str();
}
std::wstring note_on(const MidiNoteOnMessage& message)
{
const auto channel{ message.Channel() };
const auto note{ message.Note() };
const auto velocity{ message.Velocity() };
std::wostringstream buff;
buff << L"note_on: channel "
<< channel
<< L", note "
<< note
<< L", velocity "
<< velocity;
return buff.str();
}
std::wstring polytouch(const MidiPolyphonicKeyPressureMessage& message)
{
const auto channel{ message.Channel() };
const auto note{ message.Note() };
const auto pressure{ message.Pressure() };
std::wostringstream buff;
buff << L"polytouchf: channel "
<< channel
<< L", note "
<< note
<< L", pressure "
<< pressure;
return buff.str();
}
std::wstring control_change(const MidiControlChangeMessage& message)
{
const auto channel{ message.Channel() };
const auto controller{ message.Controller() };
const auto control_value{ message.ControlValue() };
std::wostringstream buff;
buff << L"control_change: channel "
<< channel
<< L", controller "
<< controller
<< L", control_value "
<< control_value;
return buff.str();
}
std::wstring program_change(const MidiProgramChangeMessage& message)
{
const auto channel{ message.Channel() };
const auto program{ message.Program() };
std::wostringstream buff;
buff << L"program_change: channel "
<< channel
<< L", program "
<< program;
return buff.str();
}
std::wstring aftertouch(const MidiChannelPressureMessage& message)
{
const auto channel{ message.Channel() };
const auto pressure{ message.Pressure() };
std::wostringstream buff;
buff << L"aftertouch: channel "
<< channel
<< L", pressure "
<< pressure;
return buff.str();
}
std::wstring pitchwheel(const MidiPitchBendChangeMessage& message)
{
const auto channel{ message.Channel() };
const auto bend{ message.Bend() };
std::wostringstream buff;
buff << L"pitchwheel: channel "
<< channel
<< L", bend "
<< bend;
return buff.str();
}
std::wstring sysex(const MidiSystemExclusiveMessage& message)
{
const auto raw_data{ message.RawData() };
return L"sysex";
}
std::wstring quarter_frame(const MidiTimeCodeMessage& message)
{
const auto frame_type{ message.FrameType() };
const auto raw_data{ message.RawData() };
std::wostringstream buff;
buff << L"quarter_frame: frame_type "
<< frame_type;
return buff.str();
}
std::wstring songpos(const MidiSongPositionPointerMessage& message)
{
const auto beats{ message.Beats() };
const auto raw_data{ message.RawData() };
std::wostringstream buff;
buff << L"songpos: beats "
<< beats;
return buff.str();
}
std::wstring song_select(const MidiSongSelectMessage& message)
{
const auto song{ message.Song() };
const auto raw_data{ message.RawData() };
std::wostringstream buff;
buff << L"song_select: song "
<< song;
return buff.str();
}
std::wstring tune_request(const MidiTuneRequestMessage& message)
{
return L"tune_request";
}
std::wstring sysex_eox(const MidiSystemExclusiveMessage& message)
{
return L"sysex_eox";
}
std::wstring clock(const MidiTimingClockMessage& message)
{
return L"clock";
}
std::wstring start(const MidiStartMessage& message)
{
return L"clock";
}
std::wstring continue_message(const MidiContinueMessage& message)
{
return L"continue";
}
std::wstring stop(const MidiStopMessage& message)
{
return L"stop";
}
std::wstring active_sensing(const MidiActiveSensingMessage& message)
{
return L"active_sensing";
}
std::wstring system_reset(const MidiSystemResetMessage& message)
{
return L"system_reset";
}
// MIDI メッセージを文字列へ変換する
//
// 引数:MIDI メッセージ
// 返り値:MIDI メッセージを文字列化したもの
//
// コルーチンではないので引数の破棄を考慮する必要が無く、
// const 参照で構わない。
std::wstring message_to_str(const IMidiMessage& message)
{
std::wostringstream buff;
const auto t{ message.Type() };
switch (t)
{
case MidiMessageType::NoteOff:
// C++/WinRT では IMidiMessage 型から MidiNoteOffMessage 型
// への変換のように、派生型へ変換するには
// try_as() または as() を使う。
buff << note_off(message.try_as<MidiNoteOffMessage>());
break;
case MidiMessageType::NoteOn:
buff << note_on(message.try_as<MidiNoteOnMessage>());
break;
case MidiMessageType::PolyphonicKeyPressure:
buff << polytouch(
message.try_as<MidiPolyphonicKeyPressureMessage>());
break;
case MidiMessageType::ControlChange:
buff << control_change(
message.try_as<MidiControlChangeMessage>());
break;
case MidiMessageType::ProgramChange:
buff << program_change(
message.try_as<MidiProgramChangeMessage>());
break;
case MidiMessageType::ChannelPressure:
buff << aftertouch(
message.try_as<MidiChannelPressureMessage>());
break;
case MidiMessageType::PitchBendChange:
buff << pitchwheel(
message.try_as<MidiPitchBendChangeMessage>());
break;
case MidiMessageType::SystemExclusive:
buff << sysex(
message.try_as<MidiSystemExclusiveMessage>());
break;
case MidiMessageType::MidiTimeCode:
buff << quarter_frame(
message.try_as<MidiTimeCodeMessage>());
break;
case MidiMessageType::SongPositionPointer:
buff << songpos(
message.try_as<MidiSongPositionPointerMessage>());
break;
case MidiMessageType::SongSelect:
buff << song_select(
message.try_as<MidiSongSelectMessage>());
break;
case MidiMessageType::TuneRequest:
buff << tune_request(
message.try_as<MidiTuneRequestMessage>());
break;
case MidiMessageType::EndSystemExclusive:
buff << sysex_eox(
message.try_as<MidiSystemExclusiveMessage>());
break;
case MidiMessageType::TimingClock:
buff << clock(
message.try_as<MidiTimingClockMessage>());
break;
case MidiMessageType::Start:
buff << start(
message.try_as<MidiStartMessage>());
break;
case MidiMessageType::Continue:
buff << continue_message(
message.try_as<MidiContinueMessage>());
break;
case MidiMessageType::Stop:
buff << stop(
message.try_as<MidiStopMessage>());
break;
case MidiMessageType::ActiveSensing:
buff << active_sensing(
message.try_as<MidiActiveSensingMessage>());
break;
case MidiMessageType::SystemReset:
buff << system_reset(
message.try_as<MidiSystemResetMessage>());
break;
default:
buff << L"unknown ("
<< static_cast<int>(t)
<< L")";
}
const auto raw_data = message.RawData();
const std::basic_string_view<uint8_t> raw_data_sv
{ raw_data.data(), raw_data.Length() };
buff << L"," << std::hex;
for (auto u : raw_data_sv)
{
buff << L" "
<< std::setw(2) << std::setfill(L'0')
<< u;
}
return buff.str();
}
// MIDI メッセージを表示する
// Fire and forget として終了まで待つ必要も返り値もなく、
// バックグラウンドで表示処理を行う
//
// 引数:表示する MIDI メッセージ
// C++/WinRT の型を値渡ししているので中身がコピーされており、
// 呼び出し側で破棄されても問題ないと考えられる
winrt::fire_and_forget show_midi_message(IMidiMessage message)
{
// コルーチンを中断して戻り、バックグラウンドで再開する
co_await winrt::resume_background();
// メッセージ受信時の時刻情報を取り出す
const auto timestamp = message.Timestamp();
const std::chrono::nanoseconds duration = timestamp;
{
// スレッド間でメッセージ表示に割り込まれると見にくくなるので
// 1 行の表示が終わるまで mutex でロックする。
std::lock_guard<std::mutex> lock(mtx_show_message_);
// 時刻情報とメッセージを文字列化したものを表示
std::wcout
<< duration.count()
<< L", "
<< message_to_str(message)
<< std::endl;
}
}
// MIDI IN イベントで呼ばれるコールバック
// MIDI メッセージ受信時のイベントハンドラ
//
// 引数:イベントが発生した MIDI IN ポートと MIDI メッセージ情報
void midi_in_callback(const MidiInPort& sender,
const MidiMessageReceivedEventArgs& e)
{
// MIDI メッセージを取り出す
const auto message = e.Message();
// MIDI OUT ポートが有効ならそのまま送信
// メッセージに時刻情報がついているが WinRT 仕様で送信時は無視される
if (out_port_)
out_port_.SendMessage(message);
// MIDI メッセージ表示
// Fire and forget なので制御は即時に戻る
show_midi_message(message);
}
// Ctrl+C などが押されたときのハンドラ
//
// インスタンス情報(this ポインタ)を載せて呼ぶことが
// できないので static メンバ関数
static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_C_EVENT)
{
// Ctrl+C が来たら event_ をシグナル状態にする
winrt::check_bool(::SetEvent(event_.get()));
// 割込みがあった旨を表示
std::wcout
<< L"Interrupted"
<< std::endl;
// 本来の Ctrl+C 処理をせず強制終了させない
return true;
}
// Ctrl+C 以外は本来の処理を行う
return false;
}
// MIDI IN ポート
MidiInPort in_port_ = nullptr;
// MIDI OUT ポート
IMidiOutPort out_port_ = nullptr;
// MIDI IN ポートのリスト
std::vector<port> in_ports_;
// MIDI OUT ポートのリスト
std::vector<port> out_ports_;
// ID の 16 進数 8 桁部分を抽出するパターン
// 固定で共通なので static メンバ変数
static const std::wregex hex_id_pattern_;
// コンストラクタのスレッド間競合を防ぐ mutex
// クラス全体で共通にしなければならないので static メンバ変数
static std::mutex mtx_ctor_;
// メッセージ表示中にスレッド間の割込みを防ぐ mutex
static std::mutex mtx_show_message_;
// 終了要求を示すイベント
// Ctrl+C ハンドラがプロセスで 1 つしか持てず
// インスタンス情報(this ポインタ)が載せられないので
// static メンバ変数
// カーネルのイベントの HANDLE を直接使ってもよいが、
// C++/WinRT では winrt::handle を使った方がよさそう
static winrt::handle event_;
};
// ID の 16 進数 8 桁部分を抽出するパターン、実体
const std::wregex winrt_midi_transfer::hex_id_pattern_
{ std::wregex(L"#MIDII_([0-9A-F]{8})\\..+#") };
// コンストラクタのスレッド間競合を防ぐ mutex、実体
std::mutex winrt_midi_transfer::mtx_ctor_;
// メッセージ表示中にスレッド間の割込みを防ぐ mutex、実体
std::mutex winrt_midi_transfer::mtx_show_message_;
// 終了要求を示すイベント、実体
winrt::handle winrt_midi_transfer::event_;
// メイン
int main()
{
// C++/WinRT では最初に必要
init_apartment();
std::wcout
<< L"WinRT MIDI Transfer with C++/WinRT"
<< std::endl << std::endl
<< L"https://gist.github.com/trueroad/"
L"9c5317af5f212b2de7c7012e76b9e66b"
<< std::endl << std::endl
<< L"Copyright (C) 2022 Masamichi Hosoda."
<< std::endl
<< L"All rights reserved."
<< std::endl << std::endl;
// インスタンスを作る
winrt_midi_transfer wmt;
// 転送のコルーチンを呼んで終わるまで待つ
wmt.transfer().get();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment