CCMapper(C#版/RtMidi)

CCMapper(C#版/RtMidi)

2022/11/16
改良版CCMapperを追加した。

2022/11/13
初版

概要

CCMapper(C#版)」のMIDIライブラリはWindows用だったので、クロスプラットフォームのMIDIライブラリのRtMidiを採用したものに変更した。
これにより、WindowsとMacで動作するようになる。

準備

以下のツールをインストールする.

・Mac用VisualStudio

Visual Studio for Mac」からダウンロードしてインストールする。

・Windows用VisualStudio

visual studio setup guide

上の説明から以下のurlからダウンロード&インストールする:

https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=16

今回、使用するのはC#だが、通常(C++)のVisualStudioがインストール済みであれば、必要なときに自動的にC#のツールがインストールされる。

C#プロジェクト作成

VisualStudioを起動する:
Windows用とMac用では若干操作性が異なるが、ここではWindows用のもので説明する。

1.新しいプロジェクトを作成する
(1)テンプレート選択
そのときのテンプレートは
「コンソールアプリ」を選択する。

(2)プロジェクト名を入力する。
任意だが今回は「CCMapper_CS_RtMidi」にする。

(3)場所
デフォルトでかまわない。 C:\Users\<ユーザー名>\source\repos

(4)[ソリューションとプロジェクトを同じディレクトリに配置する(D)]に チェックを入れる。

(5)フレームワーク
フレームワークはデフォルトで構わない。
.NET Framewok 4.7.2

(6)[作成]をクリックする

2.プログラムを入力する
上の操作で、雛形のプログラムが出来ているので、それを利用して、新しくプログラムを入力する。
今回は次節にあるものを入力する。

3.ライブラリを追加する

  1. [プロジェクト(P)/NuGetパッケージの管理]に入る
  2. NuGetパッケージマネージャー画面が出現する
  3. [参照]タブに切り替えて以下の2つのライブラリを検索してインストールする
    ・RtMidi.Core
    ・Serilog.Sinks.Console
    状況によっては同意を促す画面が出てる場合、[同意]をクリックする。

3,ビルド
メニューから[ビルド(B)/xxxのビルド]を選択してビルドする。

4.実行
右矢印アイコンをクリックして実行する
または
[デバッグ/デバッグなしで開始]で実行する

CCMapper(source code)

以下が今回のソースコードになる。

CCMapper_CS_RtMidi/Program.cs


// CCMapper(RiMidi)
// 2022/11/13

using System;
using System.Collections.Generic;
using RtMidi.Core.Devices;
using RtMidi.Core.Messages;
using Serilog;

namespace RtMidi.Core.CCMapper
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                //.WriteTo.ColoredConsole()
                .WriteTo.Console()
                //.MinimumLevel.Debug() // disp debug info.
                .CreateLogger();

            using (MidiDeviceManager.Default)
            {
                var p = new Program();
            }
        }

        public Program()
        {
            bool logOut = true; // true to disp log on console

            // the following device will be opened
            string ID = "WIDI Bud Pro"; // for Windows
            //string ID = "Elefue"; // for Mac
            //string ID = "re.corder"; // for Mac
            string OD = "loopMIDI"; // for Windows/Mac

            var inputList = new List<IMidiInputDevice>();
            var outputList = new List<IMidiOutputDevice>();

            // List all available MIDI API's
            foreach (var api in MidiDeviceManager.Default.GetAvailableMidiApis())
                Console.WriteLine($"Available API: {api}");

            // Listen to all available midi devices
            void ControlChangeHandler(IMidiInputDevice sender, in ControlChangeMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] ControlChange: Channel:{msg.Channel} Control:{msg.Control} Value:{msg.Value}");
                if (msg.Control == 11 || msg.Control == 2)
                {
                    // for Aria
                    outputList[0].Send(new ControlChangeMessage(msg.Channel, 2, msg.Value));
                    outputList[0].Send(new ControlChangeMessage(msg.Channel, 7, msg.Value));
                    outputList[0].Send(new ControlChangeMessage(msg.Channel, 26, msg.Value));
                }
                else outputList[0].Send(msg);
            }

            void NoteOnHandler(IMidiInputDevice sender, in NoteOnMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] NoteOn: Channel:{msg.Channel} Key:{msg.Key} Velocity:{msg.Velocity}");
                outputList[0].Send(msg);
            }

            void NoteOffHandler(IMidiInputDevice sender, in NoteOffMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] NoteOff: Channel:{msg.Channel} Key:{msg.Key} Velocity:{msg.Velocity}");
                outputList[0].Send(msg);
            }

            try
            {
                // disp input/output devices
                Console.WriteLine("---- INPUT DEVICE ----");
                foreach (var inputDeviceInfo in MidiDeviceManager.Default.InputDevices)
                {
                    Console.WriteLine($"{inputDeviceInfo.Name}");
                }
                Console.WriteLine("---- OUTPUT DEVICE ----");
                foreach (var outputDeviceInfo in MidiDeviceManager.Default.OutputDevices)
                {
                    Console.WriteLine($"{outputDeviceInfo.Name}");
                }

                Console.WriteLine("-----------------------");
                foreach (var inputDeviceInfo in MidiDeviceManager.Default.InputDevices)
                {
                    // one device only
                    if (inputDeviceInfo.Name.Contains(ID))
                    {
                        Console.WriteLine($"Opening input:[{inputDeviceInfo.Name}]");

                        var inputDevice = inputDeviceInfo.CreateDevice();
                        inputList.Add(inputDevice);

                        inputDevice.ControlChange += ControlChangeHandler;
                        inputDevice.NoteOn += NoteOnHandler;
                        inputDevice.NoteOff += NoteOffHandler;

                        inputDevice.Open();
                    }
                }
                foreach (var outputDeviceInfo in MidiDeviceManager.Default.OutputDevices)
                {
                    // one device only
                    if (outputDeviceInfo.Name.Contains(OD))
                    {
                        Console.WriteLine($"Opening output:[{outputDeviceInfo.Name}]");

                        var outputDevice = outputDeviceInfo.CreateDevice();
                        outputList.Add(outputDevice);

                        outputDevice.Open();
                    }
                }

                Console.WriteLine("Press 'q' key to stop...");
                while (true)
                {
                    if (Console.ReadKey().Key == ConsoleKey.Q)
                    {
                        break;
                    }
                }
            }
            finally
            {
                foreach (var device in inputList)
                {
                    device.ControlChange -= ControlChangeHandler;
                    device.NoteOn -= NoteOnHandler;
                    device.NoteOff -= NoteOffHandler;
                    device.Dispose();
                }
                foreach (var device in outputList)
                {
                    device.Dispose();
                }

            }
        }
    }
}

1.以下の部分は実行環境(入力デバイスなど)に依存しているので、自分の環境に合わせて変更すること:

            // the following device will be opened
            string ID = "WIDI Bud Pro"; // for Windows
            //string ID = "Elefue"; // for Mac
            //string ID = "re.corder"; // for Mac
            string OD = "loopMIDI"; // for Windows/Mac

2.ウィンド・コントローラの入力データをコンソール表示するようになっているので、煩雑であるならば、以下の部分をfalseすること:

            bool logOut = true; // true to disp log on console

3.Mac向けの変更として以下のように送信部分をコメントアウトする:
使用しているソフト音源のよると思うが入力デバイスを1つに絞ることができず、CCMapperの出力とウィンド・コントローラの入力のデータが混在して届く。
NoteOn/NoteOffがダブって音源に入力されるので、音がダブって鳴ることになる。それを回避するための変更になる。
# 他のControlChangeのデータは、そのデータの性質上、ダブっても、あまり影響ない。

            void NoteOnHandler(IMidiInputDevice sender, in NoteOnMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] NoteOn: Channel:{msg.Channel} Key:{msg.Key} Velocity:{msg.Velocity}");
                //outputList[0].Send(msg);
            }

            void NoteOffHandler(IMidiInputDevice sender, in NoteOffMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] NoteOff: Channel:{msg.Channel} Key:{msg.Key} Velocity:{msg.Velocity}");
                //outputList[0].Send(msg);
            }

実行

VisualStudioから実行するとコンソール画面が現れて以下のような表示になる。

コンソール出力例(Windows版)

Available API: RT_MIDI_API_WINDOWS_MM
---- INPUT DEVICE ----
loopMIDI Port 1
WIDI Bud Pro
---- OUTPUT DEVICE ----
CoolSoft MIDIMapper
Microsoft GS Wavetable Synth
VirtualMIDISynth #1
loopMIDI Port 1
WIDI Bud Pro
-----------------------
Opening input:[WIDI Bud Pro ]
Opening output:[loopMIDI Port 1 ]
Press 'q' key to stop...
[WIDI Bud Pro ] NoteOff: Channel:Channel1 Key:Key0 Velocity:0
[WIDI Bud Pro ] NoteOn: Channel:Channel1 Key:Key79 Velocity:100
[WIDI Bud Pro ] ControlChange: Channel:Channel1 Control:11 Value:53
[WIDI Bud Pro ] NoteOff: Channel:Channel1 Key:Key79 Velocity:0
[WIDI Bud Pro ] ControlChange: Channel:Channel1 Control:11 Value:0
...
...

コンソール出力例(Mac版)

Available API: RT_MIDI_API_MACOSX_CORE
---- INPUT DEVICE ----
loopMIDI bus1
Elefue Bluetooth
---- OUTPUT DEVICE ----
loopMIDI bus1
Elefue Bluetooth
-----------------------
Opening input:[Elefue Bluetooth]
Opening output:[loopMIDI bus1]
Press 'q' key to stop...
[Elefue Bluetooth] NoteOff: Channel:Channel1 Key:Key0 Velocity:0
[Elefue Bluetooth] NoteOn: Channel:Channel1 Key:Key79 Velocity:100
...
...

上のように接続している入力MIDIデバイス、出力MIDIデバイスが表示される。

MIDI接続状況:
MIDIとしての接続は「re.corder/ElefueをCCMapper経由で外部音源(Aria/Windows)と接続する(WIDI_Bud_Pro使用)」と同じ接続にする。

Macでの仮想MIDIデバイスの設定

WindowsのloopMIDIと同じような機能を仮想MIDIデバイスとして実現できる:

仮想MIDIバスの設定する」の参考にして、以下のように設定する。(日本語にするとトラブルのもとになるので英数のみとすること)

[装置はオンライン]:チェックオン
装置名:loopMIDI
ポート:bus1

Macでのウィンド・コントローラ(Bluetooth MIDI)の接続

Bluetooth MIDI 接続ガイド」の「■ Mac との接続/OS X Yosemiteの場合」を参照のこと。

[参考]dotnetによるビルド

VisualStudioをインストールしていなくても
dotnetがインストールしてあれば、以下の手順でビルドできる:


dotnet new console -o CCMapper_CS_RtMidi

cd CCMapper_CS_RtMidi/
code Program.cs

dotnet add package RtMidi.Core -s https://www.nuget.org/api/v2
dotnet add package Serilog.Sinks.Console -s https://www.nuget.org/api/v2

dotnet build
dotnet run

改良版CCMapper(source code)

以下の点を改良した:

  1. Mac環境では入力デバイスごとにソースを変更する必要があったが、自動で、Elefueとre.corderのどちらかを選択するようにした。
  2. Mac環境では、(ダブって音源がNoteOn、NoteOFFを受信しないように)CCMapperでは、NoteOn、NoteOffを送信しないようにした。
  3. Mac向けの改良であるがソースそのものはWindowsでも変更無しで動作するように考慮した。

CCMapper_CS_RtMidi/Program.cs


// CCMapper(RtMidi) for Mac
// 2022/11/16: auto selecting input/output devices
// 2022/11/13

using System;
using System.Collections.Generic;
using RtMidi.Core.Devices;
using RtMidi.Core.Messages;
using Serilog;

namespace RtMidi.Core.CCMapper
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                //.WriteTo.ColoredConsole()
                .WriteTo.Console()
                //.MinimumLevel.Debug() // disp debug info.
                .CreateLogger();

            using (MidiDeviceManager.Default)
            {
                var p = new Program();
            }
        }

        public Program()
        {
            bool logOut = true; // true to disp log on console

            bool enableSendNote = true; // false will Not send note

            // the following device will be opened
            string ID0 = "WIDI Bud Pro"; // for Windows
            string ID1 = "Elefue"; // for Mac
            string ID2 = "re.corder"; // for Mac
            string OD = "loopMIDI"; // for Windows/Mac

            var inputList = new List<IMidiInputDevice>();
            var outputList = new List<IMidiOutputDevice>();

            // List all available MIDI API's
            foreach (var api in MidiDeviceManager.Default.GetAvailableMidiApis())
                Console.WriteLine($"Available API: {api}");

            // Listen to all available midi devices
            void ControlChangeHandler(IMidiInputDevice sender, in ControlChangeMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] ControlChange: Channel:{msg.Channel} Control:{msg.Control} Value:{msg.Value}");
                if (msg.Control == 11 || msg.Control == 2)
                {
                    // for Aria
                    outputList[0].Send(new ControlChangeMessage(msg.Channel, 2, msg.Value));
                    outputList[0].Send(new ControlChangeMessage(msg.Channel, 7, msg.Value));
                    outputList[0].Send(new ControlChangeMessage(msg.Channel, 26, msg.Value));
                }
                else outputList[0].Send(msg);
            }

            void NoteOnHandler(IMidiInputDevice sender, in NoteOnMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] NoteOn: Channel:{msg.Channel} Key:{msg.Key} Velocity:{msg.Velocity}");
                if (enableSendNote) outputList[0].Send(msg);
            }

            void NoteOffHandler(IMidiInputDevice sender, in NoteOffMessage msg)
            {
                if (logOut) Console.WriteLine($"[{sender.Name}] NoteOff: Channel:{msg.Channel} Key:{msg.Key} Velocity:{msg.Velocity}");
                if (enableSendNote) outputList[0].Send(msg);
            }

            try
            {
                // disp input/output devices
                Console.WriteLine("---- INPUT DEVICE ----");
                foreach (var inputDeviceInfo in MidiDeviceManager.Default.InputDevices)
                {
                    Console.WriteLine($"{inputDeviceInfo.Name}");
                }
                Console.WriteLine("---- OUTPUT DEVICE ----");
                foreach (var outputDeviceInfo in MidiDeviceManager.Default.OutputDevices)
                {
                    Console.WriteLine($"{outputDeviceInfo.Name}");
                }

                Console.WriteLine("-----------------------");
                foreach (var inputDeviceInfo in MidiDeviceManager.Default.InputDevices)
                {
                    // one device only
                    if (inputDeviceInfo.Name.Contains(ID0))
                    {
                        Console.WriteLine($"Opening input:[{inputDeviceInfo.Name}]");

                        var inputDevice = inputDeviceInfo.CreateDevice();
                        inputList.Add(inputDevice);

                        inputDevice.ControlChange += ControlChangeHandler;
                        inputDevice.NoteOn += NoteOnHandler;
                        inputDevice.NoteOff += NoteOffHandler;

                        inputDevice.Open();

                        break;
                    }
                    if (inputDeviceInfo.Name.Contains(ID1))
                    {
                        Console.WriteLine($"Opening input:[{inputDeviceInfo.Name}]");

                        var inputDevice = inputDeviceInfo.CreateDevice();
                        inputList.Add(inputDevice);

                        inputDevice.ControlChange += ControlChangeHandler;
                        inputDevice.NoteOn += NoteOnHandler;
                        inputDevice.NoteOff += NoteOffHandler;

                        inputDevice.Open();

                        break;
                    }
                    if (inputDeviceInfo.Name.Contains(ID2))
                    {
                        Console.WriteLine($"Opening input:[{inputDeviceInfo.Name}]");

                        var inputDevice = inputDeviceInfo.CreateDevice();
                        inputList.Add(inputDevice);

                        inputDevice.ControlChange += ControlChangeHandler;
                        inputDevice.NoteOn += NoteOnHandler;
                        inputDevice.NoteOff += NoteOffHandler;

                        inputDevice.Open();

                        break;
                    }
                }
                //---------------------------------------------------------------------
                foreach (var outputDeviceInfo in MidiDeviceManager.Default.OutputDevices)
                {
                    // one device only
                    if (outputDeviceInfo.Name.Contains(OD))
                    {
                        Console.WriteLine($"Opening output:[{outputDeviceInfo.Name}]");

                        // check Windows or Mac
                        if (outputDeviceInfo.Name.Contains("bus1")) enableSendNote = false;
                        if (!enableSendNote) Console.WriteLine("running at Mac Env.");
                        else Console.WriteLine("running at Windows Env.");

                        var outputDevice = outputDeviceInfo.CreateDevice();
                        outputList.Add(outputDevice);

                        outputDevice.Open();
                    }
                }

                Console.WriteLine("Press 'q' key to stop...");
                while (true)
                {
                    if (Console.ReadKey().Key == ConsoleKey.Q)
                    {
                        break;
                    }
                }
            }
            finally
            {
                foreach (var device in inputList)
                {
                    device.ControlChange -= ControlChangeHandler;
                    device.NoteOn -= NoteOnHandler;
                    device.NoteOff -= NoteOffHandler;
                    device.Dispose();
                }
                foreach (var device in outputList)
                {
                    device.Dispose();
                }

            }
        }
    }
}

関連情報

CCMapper関連:
CCMapper改良版(openFrameworks)
processingでCCMapperを実装する
python版CCMapper - re.corder/ElefueをCCMapper経由で外部音源(Aria/Windows)と接続する(WIDI_Bud_Pro使用)

C#MIDI関連:
RtMidi.Core/github
RtMidi.Core/nuget

Next MIDI
.NET Global exception handler in console application
C#でMIDI通信を扱うNext MIDI Projectで受信する

openframework関連:
openFrameworksを使用して独自のMIDI生成のリアルタイムビジュアルを作成します。
Novation LauchpadとopenFrameworksを使ってResolumeのVJコントローラを作る : コーディング編
プロジェクトにアドオンを追加する方法
新規プロジェクトの作成
Listen to events
変数の値を見る
ofLog
openFrameworks-コンソール表示する
oF:Windowsのopenframeworksでコンソールウインドウを出さない方法
openFrameworks-Log vol.1/環境設定と導入
openFramewoks – OSC (Open Sound Control) を利用したネットワーク連携

pygame.midi関連:
PythonでMIDI
pygame.midi

loopMIDI関連:
loopMIDI
loopMIDIでつなぐ

WIDI_Bud_Pro関連:
WIDI Bud Pro
WIDI Bud Pro 技術情報

PC音源関連:
Lyrihorn 2(VST3)を使ってみる
EVI-NER(VST3)を使ってみる
re.corder/Elefueに外部音源(Aria/Windows)を接続する(WIDI_Bud_Pro経由)
EWI5000をソフト音源(IFW)と接続する

re.corder関連:
owner’s manual re.corder
re.corder Downloads
re.corder Frequently Asked Questions

MIDI関連:
現時点、最強のBluetooth MIDIかも!? 各種BLE-MIDI機器と自動でペアリングしてくれるWIDI Masterがスゴイ!
Midi View

ASIO関連:
asio4all - ASIOドライバーのないオーディオインターフェイスをASIO対応にできるソフト

Aria関連:
EWI MASTER BOOK CD付教則完全ガイド【改訂版】のp100-p119の音色の設定方法がある

以上

Go to Toplevel