Ubuntu で C#：スタンダード MIDI ファイルを作成する

# Ubuntu で C#：スタンダード MIDI ファイルを作成する

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 環境の C# / .NET からスタンダード MIDI ファイルを作成する方法を紹介します。  
![csharp_on_ubuntu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/23c7c71f-e5f1-3bb8-45b6-60ad19e41238.png)

## 実現すること

+ Ubuntu 22.04 で C# / .NET プログラムからスタンダード MIDI ファイルを作成します。

:::note alert
C# には本来[厳格なコーディング規則](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/coding-style/coding-conventions)がありますが、この記事では可読性のために、一部規則に沿わない表記方法を使用しています。ご注意ください。
:::

## 技術トピック

### スタンダード MIDI ファイルとは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [スタンダード MIDI ファイル](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%80%E3%83%BC%E3%83%89MIDI%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)

:::note info
スタンダード MIDI ファイル（Standard MIDI File）は、音楽の演奏データを保存および転送するためのファイル形式です。

|キーワード|内容|
|:--|:--|
|プラットフォーム非依存性|スタンダード MIDI ファイルは、異なる音楽ソフトウェアやデバイス間での互換性が高いため、さまざまなプラットフォームやデバイスで使用することができます。|
|軽量で効率的|MIDI ファイルはテキストベースではなく、音楽データ自体をコンパクトに表現するため、ファイルサイズが小さく、データの転送や保存に効率的です。|
|多機能性|MIDI ファイルは、音符、テンポ、音色、演奏のタイミングなど、音楽パフォーマンスに関するさまざまな情報を格納できます。これにより、演奏データの再生、編集、交換が容易になります。|
|インターネット上での共有|MIDI ファイルはファイルサイズが小さく、再生には音源が必要ですが、音楽データを簡単に共有できるため、ウェブ上での楽曲の交換や共有が容易です。|
|作曲や編曲の効率化|MIDI ファイルは楽曲の構成要素を表現するため、作曲家や編曲家は作業を効率化し、既存の MIDI データを編集して新しいアレンジを作成することができます。|
|教育や学習のツール|MIDI ファイルは、音楽教育や学習のツールとして広く使用されます。演奏や作曲の練習に活用したり、楽曲の分析や学習を支援したりすることができます。|
:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

.NET SDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/d68031c91462f6808dff)

```sh
$ dotnet --list-sdks
7.0.202 [/usr/share/dotnet/sdk]
$ dotnet --version
7.0.202
```

</details>

## 作成する音楽の仕様

```sh
ドレミファソラシド を4回繰り返す
```

:::note warn
この記事では、[**MIDI**](https://ja.wikipedia.org/wiki/MIDI) や [**スタンダード MIDI ファイル(SMF)**](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%80%E3%83%BC%E3%83%89MIDI%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB) の仕様の詳細などは説明しておりません。ご了承ください。
:::

## MIDI ファイルを作成する手順

### プロジェクトを作成

プロジェクトルートフォルダに移動します。
※ ~/tmp をプロジェクトルートフォルダとします。

```sh
$ cd ~/tmp
```

コンソールアプリを作成します。  
※ MIDIApp がアプリ名です。  

```sh
$ dotnet new console -o MIDIApp -f net7.0
```

一度コンソールアプリをビルド・実行します。

```sh
$ cd ~/tmp/MIDIApp
$ dotnet run
Hello, World!
```

:::note info
ここまでの作業で、C# / .NET コンソールアプリの雛形が作成できました😋 
:::

### ライブラリの追加

SMF バイナリファイルを生成できる **[Sanford.Multimedia.Midi](https://github.com/tebjan/Sanford.Multimedia.Midi)** パッケージを [**NuGet**](https://learn.microsoft.com/ja-jp/nuget/what-is-nuget) で取得します。

```sh
$ dotnet add package Sanford.Multimedia.Midi
```

パッケージを確認します。

```sh
$ dotnet list package
プロジェクト 'MIDIApp' に次のパッケージ参照が含まれています
   [net7.0]:
   最上位レベル パッケージ                   要求済み    解決済み
   > Sanford.Multimedia.Midi      6.6.2   6.6.2
```

NU1701 警告を抑制します。

:::note warn
**Sanford.Multimedia.Midi** は元々 .NET 7 用には作られていないのでビルド時などに警告が表示されます。しかし、今回の利用には支障がない為、以下の記述を追加して **NU1701 警告表示**を抑制します。
:::

```sh
$ vim MIDIApp.csproj
```

```xml:MIDIApp.csproj の一部
<Project Sdk="Microsoft.NET.Sdk">
　<!-- 省略 -->
  <PropertyGroup>
    <NoWarn>NU1701</NoWarn>
  </PropertyGroup>

</Project>
```

:::note info
ここまでの手順で、プロジェクトに **Sanford.Multimedia.Midi** のパッケージを組み込むことができました。
:::

## コードの追加と修正

スタンダード MIDI ファイルに対応するバイト配列に変換するクラスを作成します。

```sh
$ vim Converter.cs
```

```csharp:Converter.cs
using static System.Convert;
using static System.Math;

namespace MIDIApp {
    // コンバータークラス
    public static class Converter {

        // 数値 BPM を SMF のテンポ情報に変換
        public static byte[] ToByteTempo(int tempo) {
            double double_value = 60 * Pow(x: 10, y: 6) / tempo;
            string hex = int.Parse(s: Round(a: double_value).ToString()).ToString("X6"); // 16進数6桁の変換
            char[] char_array = hex.ToCharArray();
            return new byte[3]{ // 3バイトで返す
                ToByte(value: char_array[0].ToString() + char_array[1].ToString(), fromBase: 16),
                ToByte(value: char_array[2].ToString() + char_array[3].ToString(), fromBase: 16),
                ToByte(value: char_array[4].ToString() + char_array[5].ToString(), fromBase: 16)
            };
        }

        // 文字列をバイト配列に変換
        public static byte[] ToByteArray(string target) {
            return target.ToCharArray().Select(selector: x => ToByte(value: x)).ToArray();
        }
    }
}
```

Program.cs を修正します。

```sh
$ vim Program.cs
```

```csharp:Program.cs
using static System.Console;

using Sanford.Multimedia.Midi;
using static Sanford.Multimedia.Midi.ChannelCommand;
using static Sanford.Multimedia.Midi.MetaType;

using static MIDIApp.Converter;

namespace MIDIApp {
    class Program {
        static void Main(string[] args) {
            // シーケンスの作成
            Sequence sequence = new();
            sequence.Format = 1;

            int channel = 0; // MIDI チャンネル番号
            int velocity = 112; // ノートオン時のベロシティ
            int bpm = 120; // テンポ
            int eighth_note_length = 6000 / (bpm * 4); // 8分音符の長さ（ミリ秒）
            string song_name = "New Song 001"; // 曲名
            string copyright = "STUDIO Awesome"; // 著作権

            // ノートの長さを 8分音符に設定
            int duration = eighth_note_length;

            // ドレミファソラシドの音階を設定
            int[] notes = { 60, 62, 64, 65, 67, 69, 71, 72 };
            notes = Enumerable.Repeat(notes, 4).SelectMany(n => n).ToArray(); // 4回繰り返す

            // コンダクタートラックの設定
            Track conductor_track = new();
            conductor_track.Insert(position: 0, new MetaMessage(Tempo, ToByteTempo(bpm)));
            conductor_track.Insert(position: 0, new MetaMessage(TrackName, ToByteArray(song_name)));
            conductor_track.Insert(position: 0, new MetaMessage(Copyright, ToByteArray(copyright)));
            sequence.Add(item: conductor_track);

            // シーケンストラックの設定
            Track sequence_track = new();
            sequence_track.Insert(position: 0, new MetaMessage(TrackName, ToByteArray("Melody")));
            sequence.Add(item: sequence_track);

            // 音階の設定
            int tick = 0; // 時間のトラッキング用変数
            foreach (int note_number in notes) {
                // ノートオンイベントの作成
                sequence_track.Insert(position: tick, new ChannelMessage(NoteOn, channel, note_number, velocity));
                // 次のノートまでの時間を加算
                tick += duration;
                // ノートオフイベントの作成
                sequence_track.Insert(position: tick, new ChannelMessage(NoteOff, channel, note_number, 0));
            }

            // MIDI ファイルの保存
            sequence.Save($"{song_name.Replace(" ", "_")}.mid");
            WriteLine("completed.");
        }
    }
}
```

ターミナルから実行します。

```sh
$ dotnet run
completed.
```

プロジェクトフォルダに以下の MIDI ファイルが出力されます。

```sh
New_Song_001.mid
```

:::note warn
[**🎵作成した MIDI ファイルを MP3 に変換した音楽を聴いてみる**](https://drive.google.com/file/d/1x6YS-LApLWN6XzYzbyaIOxWd6qM3d_f1/view?usp=drive_link)
:::

:::note info
ここまでの手順で、Ubuntu の C# / .NET からスタンダード MIDI ファイルを作成することができました。作成した MIDI ファイルは、Windows 11 であれば標準の Windows メディアプレイヤーなどから再生することが可能です😋
:::

## まとめ

Ubuntu 22.04 で C# からスタンダード MIDI ファイルを作成して出力することができました。

どうでしたか？ Window 11 の WSL Ubuntu に、C# / .NET の開発環境を手軽に構築することができます。ぜひお試しください。今後も .NET の開発環境などを紹介していきますので、ぜひお楽しみにしてください。