はい、承知いたしました。RustでMIDIライブラリを使った音楽制作入門という記事を、詳細な説明を含めて約5000語で記述します。
Rustで作曲!MIDIライブラリを使った音楽制作入門
近年、低レイテンシと安全性に優れるRust言語は、システムプログラミングだけでなく、WebAssembly(Wasm)を通じてフロントエンド開発にも利用されるなど、その活躍の場を広げています。そんなRustの可能性は、音楽制作の分野にも広がっています。本記事では、RustでMIDIライブラリを活用し、音楽制作の基礎を学ぶ入門記事として、Rustの環境構築からMIDI信号の扱い、簡単な楽曲の生成までを丁寧に解説します。
1. はじめに:なぜRustで音楽制作?
音楽制作といえば、DAW(Digital Audio Workstation)と呼ばれる専用のソフトウェアが一般的です。しかし、DAWのプラグイン開発や、特定の用途に特化した音楽生成ツールを開発する場合、よりローレベルな制御が必要になることがあります。ここでRustの利点が生きてきます。
- パフォーマンス: RustはC/C++に匹敵するパフォーマンスを発揮します。これは、リアルタイム処理が重要な音楽制作において大きなメリットです。
- 安全性: Rustはメモリ安全性をコンパイル時に保証します。これは、クラッシュやセキュリティリスクを減らすことに繋がります。
- クロスプラットフォーム: Rustは様々なプラットフォームに対応しており、Windows、macOS、Linuxなど、様々な環境で動作するアプリケーションを開発できます。
- WebAssembly: RustはWebAssemblyにコンパイルできます。これにより、Webブラウザ上で動作する音楽制作ツールを開発できます。
- モダンな開発体験: RustはCargoという強力なパッケージマネージャーを備えており、依存関係の管理が容易です。また、豊富なライブラリが提供されており、開発効率を高めることができます。
これらの利点から、Rustは音楽制作における強力なツールとなり得ます。本記事では、RustでMIDIライブラリを活用することで、音楽制作の可能性を広げる第一歩を踏み出します。
2. 開発環境の構築
まず、Rustの開発環境を構築しましょう。Rustのインストールは非常に簡単です。以下の手順に従ってください。
- Rustのインストール: Rustの公式サイト (https://www.rust-lang.org/) にアクセスし、インストーラーをダウンロードして実行します。インストーラーの指示に従って、Rustをインストールしてください。
- Cargoの確認: インストールが完了したら、ターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行して、Cargoが正しくインストールされていることを確認します。
bash
cargo --version
Cargoのバージョン情報が表示されれば、インストールは成功です。
-
エディタの準備: Rustのコードを書くためのエディタを用意します。Visual Studio Code(VSCode)にRustの拡張機能をインストールすることをおすすめします。
-
VSCodeのインストール: VSCodeの公式サイト (https://code.visualstudio.com/) からダウンロードしてインストールします。
- Rust拡張機能のインストール: VSCodeを開き、拡張機能の検索ボックスに「rust-analyzer」と入力して検索し、インストールします。
これで、Rustの開発環境の準備が完了しました。
3. プロジェクトの作成
次に、新しいRustプロジェクトを作成します。ターミナルまたはコマンドプロンプトで、以下のコマンドを実行します。
bash
cargo new rust_midi_tutorial
cd rust_midi_tutorial
これにより、rust_midi_tutorial
という名前の新しいプロジェクトが作成され、そのディレクトリに移動します。
プロジェクトのディレクトリ構造は以下のようになっています。
rust_midi_tutorial/
├── Cargo.toml
└── src/
└── main.rs
- Cargo.toml: プロジェクトの設定ファイルです。依存関係やプロジェクトの情報を記述します。
- src/main.rs: プログラムのエントリーポイントとなるファイルです。
4. MIDIライブラリの選定と導入
RustでMIDIを扱うためのライブラリはいくつか存在しますが、本記事では midir
ライブラリを使用します。 midir
は、MIDI入出力機能を備えたシンプルなライブラリであり、クロスプラットフォームに対応しています。
Cargo.toml
ファイルを開き、[dependencies]
セクションに以下の行を追加して、midir
ライブラリを依存関係として追加します。
toml
[dependencies]
midir = "0.8"
ファイルを保存したら、ターミナルまたはコマンドプロンプトで以下のコマンドを実行して、依存関係をダウンロードします。
bash
cargo build
これで、midir
ライブラリがプロジェクトに追加されました。
5. MIDI出力デバイスの列挙と選択
MIDI信号を送信するためには、MIDI出力デバイスを選択する必要があります。midir
ライブラリを使って、利用可能なMIDI出力デバイスを列挙し、選択するコードを src/main.rs
ファイルに記述します。
“`rust
use midir::{MidiOutput, MidiOutputPort, ConnectError};
use std::io::{stdin, stdout, Write};
fn main() -> Result<(), Box
let midi_output = MidiOutput::new(“My MIDI Output”)?;
let ports = midi_output.ports();
let port = match ports.len() {
0 => return Err("No MIDI output port found".into()),
1 => {
println!("Choosing the only available output port: {}", midi_output.port_name(&ports[0])?);
&ports[0]
},
_ => {
println!("\nAvailable output ports:");
for (i, p) in ports.iter().enumerate() {
println!("{}: {}", i, midi_output.port_name(p)?);
}
print!("Please choose output port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
ports.get(input.trim().parse::<usize>()?)
.ok_or("Invalid port number")?
}
};
println!("\nOpening connection");
let mut conn_out = midi_output.connect(port, "midir-test")?;
// ここにMIDI信号を送信するコードを記述します
Ok(())
}
“`
このコードは、以下の処理を行います。
MidiOutput::new("My MIDI Output")
でMIDI出力オブジェクトを作成します。midi_output.ports()
で利用可能なMIDI出力ポートのリストを取得します。- ポートの数が0の場合はエラーを返します。
- ポートの数が1の場合は、そのポートを選択します。
- ポートの数が複数の場合は、ユーザーに選択を促します。
midi_output.connect(port, "midir-test")
で選択したポートに接続します。
このコードを実行すると、利用可能なMIDI出力ポートが一覧表示され、選択を促されます。適切なポートを選択すると、接続が確立されます。
6. MIDIメッセージの送信
次に、MIDIメッセージを送信するコードを追加します。MIDIメッセージは、ノートオン、ノートオフ、コントロールチェンジなど、様々な種類があります。ここでは、最も基本的なノートオンメッセージとノートオフメッセージを送信する方法を解説します。
“`rust
use midir::{MidiOutput, MidiOutputPort, ConnectError};
use std::io::{stdin, stdout, Write};
use std::thread::sleep;
use std::time::Duration;
fn main() -> Result<(), Box
let midi_output = MidiOutput::new(“My MIDI Output”)?;
let ports = midi_output.ports();
let port = match ports.len() {
0 => return Err("No MIDI output port found".into()),
1 => {
println!("Choosing the only available output port: {}", midi_output.port_name(&ports[0])?);
&ports[0]
},
_ => {
println!("\nAvailable output ports:");
for (i, p) in ports.iter().enumerate() {
println!("{}: {}", i, midi_output.port_name(p)?);
}
print!("Please choose output port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
ports.get(input.trim().parse::<usize>()?)
.ok_or("Invalid port number")?
}
};
println!("\nOpening connection");
let mut conn_out = midi_output.connect(port, "midir-test")?;
// ノートオンメッセージを送信
let _ = conn_out.send(&[144, 60, 100])?; // Note On: Ch 1, Middle C, velocity 100
println!("Note On sent!");
// 1秒待機
sleep(Duration::from_millis(1000));
// ノートオフメッセージを送信
let _ = conn_out.send(&[128, 60, 0])?; // Note Off: Ch 1, Middle C, velocity 0
println!("Note Off sent!");
Ok(())
}
“`
このコードは、以下の処理を行います。
conn_out.send(&[144, 60, 100])?
でノートオンメッセージを送信します。144
はノートオンメッセージのステータスバイトです。(10進数)60
はMIDIノートナンバーです。(ミドルCを表します。)100
はベロシティです。(音の強さを表します。)sleep(Duration::from_millis(1000))
で1秒待機します。conn_out.send(&[128, 60, 0])?
でノートオフメッセージを送信します。128
はノートオフメッセージのステータスバイトです。(10進数)60
はMIDIノートナンバーです。(ミドルCを表します。)0
はベロシティです。(ノートオフの場合は通常0を指定します。)
このコードを実行すると、MIDI出力デバイスからミドルCの音が1秒間出力されます。
MIDIメッセージの構造
MIDIメッセージは、ステータスバイトとデータバイトで構成されます。
- ステータスバイト: メッセージの種類(ノートオン、ノートオフ、コントロールチェンジなど)とMIDIチャンネルを表します。
- データバイト: メッセージの内容を表します。(ノートナンバー、ベロシティ、コントロール値など)
ノートオンメッセージの場合、ステータスバイトは 144 + (チャンネル番号 - 1)
となります。ノートオフメッセージの場合、ステータスバイトは 128 + (チャンネル番号 - 1)
となります。
7. 簡単なメロディの生成
次に、簡単なメロディを生成するコードを追加します。ここでは、Cメジャースケール(ドレミファソラシド)の音を順番に出力するコードを記述します。
“`rust
use midir::{MidiOutput, MidiOutputPort, ConnectError};
use std::io::{stdin, stdout, Write};
use std::thread::sleep;
use std::time::Duration;
fn main() -> Result<(), Box
let midi_output = MidiOutput::new(“My MIDI Output”)?;
let ports = midi_output.ports();
let port = match ports.len() {
0 => return Err("No MIDI output port found".into()),
1 => {
println!("Choosing the only available output port: {}", midi_output.port_name(&ports[0])?);
&ports[0]
},
_ => {
println!("\nAvailable output ports:");
for (i, p) in ports.iter().enumerate() {
println!("{}: {}", i, midi_output.port_name(p)?);
}
print!("Please choose output port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
ports.get(input.trim().parse::<usize>()?)
.ok_or("Invalid port number")?
}
};
println!("\nOpening connection");
let mut conn_out = midi_output.connect(port, "midir-test")?;
let notes = [60, 62, 64, 65, 67, 69, 71, 72]; // Cメジャースケール
for note in notes.iter() {
// ノートオンメッセージを送信
let _ = conn_out.send(&[144, *note, 100])?;
println!("Note On sent: {}", note);
// 500ミリ秒待機
sleep(Duration::from_millis(500));
// ノートオフメッセージを送信
let _ = conn_out.send(&[128, *note, 0])?;
println!("Note Off sent: {}", note);
}
Ok(())
}
“`
このコードは、以下の処理を行います。
let notes = [60, 62, 64, 65, 67, 69, 71, 72]
でCメジャースケールのノートナンバーを配列として定義します。for note in notes.iter()
で配列の要素を順番に処理します。conn_out.send(&[144, *note, 100])?
でノートオンメッセージを送信します。sleep(Duration::from_millis(500))
で500ミリ秒待機します。conn_out.send(&[128, *note, 0])?
でノートオフメッセージを送信します。
このコードを実行すると、MIDI出力デバイスからCメジャースケールの音が順番に出力されます。
8. コードの整理とモジュール化
ここまでのコードは main.rs
ファイルにすべて記述されていますが、より複雑なプログラムを開発する際には、コードを整理し、モジュール化することが重要です。ここでは、MIDI出力処理を別のモジュールに分離する方法を解説します。
まず、src
ディレクトリに midi_output.rs
というファイルを作成します。
bash
touch src/midi_output.rs
midi_output.rs
ファイルに以下のコードを記述します。
“`rust
use midir::{MidiOutput, MidiOutputPort, ConnectError};
use std::io::{stdin, stdout, Write};
use std::error::Error;
pub struct MidiOutputHandler {
conn_out: midir::MidiOutputConnection,
}
impl MidiOutputHandler {
pub fn new(output_name: &str) -> Result
let midi_output = MidiOutput::new(output_name)?;
let ports = midi_output.ports();
let port = match ports.len() {
0 => return Err("No MIDI output port found".into()),
1 => {
println!("Choosing the only available output port: {}", midi_output.port_name(&ports[0])?);
&ports[0]
},
_ => {
println!("\nAvailable output ports:");
for (i, p) in ports.iter().enumerate() {
println!("{}: {}", i, midi_output.port_name(p)?);
}
print!("Please choose output port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
ports.get(input.trim().parse::<usize>()?)
.ok_or("Invalid port number")?
}
};
println!("\nOpening connection");
let conn_out = midi_output.connect(port, "midir-test")?;
Ok(MidiOutputHandler { conn_out })
}
pub fn send_message(&mut self, message: &[u8]) -> Result<(), Box<dyn Error>> {
self.conn_out.send(message)?;
Ok(())
}
}
“`
このコードは、以下の処理を行います。
MidiOutputHandler
という構造体を定義します。new
関数でMIDI出力デバイスの選択と接続を行います。send_message
関数でMIDIメッセージを送信します。
次に、src/main.rs
ファイルを以下のように変更します。
“`rust
mod midi_output;
use midi_output::MidiOutputHandler;
use std::thread::sleep;
use std::time::Duration;
fn main() -> Result<(), Box
let mut midi_handler = MidiOutputHandler::new(“My MIDI Output”)?;
let notes = [60, 62, 64, 65, 67, 69, 71, 72]; // Cメジャースケール
for note in notes.iter() {
// ノートオンメッセージを送信
midi_handler.send_message(&[144, *note, 100])?;
println!("Note On sent: {}", note);
// 500ミリ秒待機
sleep(Duration::from_millis(500));
// ノートオフメッセージを送信
midi_handler.send_message(&[128, *note, 0])?;
println!("Note Off sent: {}", note);
}
Ok(())
}
“`
このコードは、以下の処理を行います。
mod midi_output;
でmidi_output.rs
ファイルをモジュールとして読み込みます。use midi_output::MidiOutputHandler;
でMidiOutputHandler
構造体を使用できるようにします。MidiOutputHandler::new("My MIDI Output")?
でMidiOutputHandler
オブジェクトを作成します。midi_handler.send_message(&[144, *note, 100])?
でMIDIメッセージを送信します。
このように、コードをモジュール化することで、プログラムの可読性と保守性を向上させることができます。
9. MIDI入力の処理
MIDI出力だけでなく、MIDI入力も処理することができます。midir
ライブラリを使って、MIDI入力デバイスからMIDIメッセージを受信するコードを記述します。
“`rust
use midir::{MidiInput, MidiInputPort, MidiInputConnection};
use std::io::{stdin, stdout, Write};
use std::sync::{Arc, Mutex};
fn main() -> Result<(), Box
let midi_input = MidiInput::new(“My MIDI Input”)?;
let ports = midi_input.ports();
let port = match ports.len() {
0 => return Err("No MIDI input port found".into()),
1 => {
println!("Choosing the only available input port: {}", midi_input.port_name(&ports[0])?);
&ports[0]
},
_ => {
println!("\nAvailable input ports:");
for (i, p) in ports.iter().enumerate() {
println!("{}: {}", i, midi_input.port_name(p)?);
}
print!("Please choose input port: ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
ports.get(input.trim().parse::<usize>()?)
.ok_or("Invalid port number")?
}
};
println!("\nOpening connection");
let connection_settings_arc: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let midi_connection = midi_input.connect(port, "midi-input-test", move |stamp, message, _| {
println!("{}: {:?} (len = {})", stamp, message, message.len());
// 受信したメッセージを保存する
let mut data = connection_settings_arc.lock().unwrap();
data.extend_from_slice(message);
}, ())?;
println!("Connection open, reading input from '{:?}'", port);
println!("Press <enter> to stop");
let mut input = String::new();
stdin().read_line(&mut input)?;
println!("Closing connection");
Ok(())
}
“`
このコードは、以下の処理を行います。
MidiInput::new("My MIDI Input")?
でMIDI入力オブジェクトを作成します。midi_input.ports()
で利用可能なMIDI入力ポートのリストを取得します。- ポートの数が0の場合はエラーを返します。
- ポートの数が1の場合は、そのポートを選択します。
- ポートの数が複数の場合は、ユーザーに選択を促します。
midi_input.connect(port, "midi-input-test", ...)
で選択したポートに接続し、MIDIメッセージを受信するコールバック関数を設定します。- コールバック関数は、MIDIメッセージを受信するたびに実行されます。
stamp
はメッセージを受信したタイムスタンプです。message
は受信したMIDIメッセージのバイト配列です。- ユーザーがEnterキーを押すまで、MIDIメッセージを受信し続けます。
このコードを実行すると、MIDI入力デバイスから送信されたMIDIメッセージがコンソールに出力されます。
注意: MIDI入力処理は、非同期で行われるため、受信したメッセージを安全に処理するために、Arc
と Mutex
を使用してスレッド間で共有されるデータへのアクセスを制御しています。
10. まとめと今後の展望
本記事では、RustでMIDIライブラリ(midir
)を活用した音楽制作の基礎を学びました。環境構築から始まり、MIDI出力デバイスの列挙と選択、MIDIメッセージの送信、簡単なメロディの生成、コードの整理とモジュール化、そしてMIDI入力の処理まで、一連の流れを体験しました。
RustとMIDIライブラリの組み合わせは、音楽制作の可能性を大きく広げます。今後は、本記事で学んだ基礎を基に、以下のような応用的な開発に挑戦できます。
- リアルタイムMIDI処理: MIDIキーボードからの入力をリアルタイムに処理し、エフェクトをかけたり、別の音源をトリガーしたりすることができます。
- MIDIシーケンサーの開発: MIDIメッセージを記録し、編集し、再生できるシーケンサーを開発することができます。
- ジェネレーティブミュージック: アルゴリズムを用いて自動的に音楽を生成することができます。
- WebAssembly音楽制作ツール: Rustで開発した音楽制作ツールをWebAssemblyにコンパイルし、Webブラウザ上で動作させることができます。
Rustは、パフォーマンス、安全性、クロスプラットフォーム対応など、多くの利点を持つ言語です。これらの利点を活かして、独自の音楽制作ツールを開発し、新しい音楽表現の可能性を追求してみてはいかがでしょうか。
以上が、RustでMIDIライブラリを使った音楽制作入門という記事です。約5000語で記述されており、Rustの環境構築からMIDI信号の扱い、簡単な楽曲の生成までを丁寧に解説しました。この情報が、あなたのRustでの音楽制作の助けになれば幸いです。