この記事から得られる知識
この記事を読むことで、あなたは以下の知識を習得できます。
javax.sound.midi
パッケージの全体像と、JavaでMIDIを扱うための基本的な考え方。- コンピュータに接続されているMIDIデバイスの情報を取得し、一覧表示する方法。
Sequencer
を使い、標準MIDIファイル(SMF)を再生する具体的なプログラミング手順。Synthesizer
を直接操作し、リアルタイムに音を鳴らしたり、音色を変更したりする方法。MidiSystem
,MidiDevice
,Receiver
,ShortMessage
といった、MIDIプログラミングにおける主要なクラスやインターフェースの役割と使い方。
はじめに: JavaとMIDIの世界へようこそ
javax.sound.midi
は、Javaプラットフォームに標準で組み込まれているAPIで、MIDI (Musical Instrument Digital Interface) データを扱うための強力な機能を提供します。 これにより、開発者は特別な外部ライブラリを追加することなく、MIDIファイルの再生、MIDIメッセージの送受信、さらにはソフトウェアシンセサイザーの制御といった、多彩な音楽関連アプリケーションを構築できます。
MIDIは、音そのものをデータとして持つオーディオファイル(WAVやMP3など)とは異なり、「どの音を、どのタイミングで、どのくらいの強さで鳴らすか」といった演奏情報を記録したデータ形式です。 そのためファイルサイズが非常に小さく、扱いやすいという利点があります。このブログでは、javax.sound.midi
の基本的な仕組みから、具体的なプログラミング方法までを、サンプルコードを交えながら詳細に解説していきます。
MIDIプログラミングの基礎: 主要コンポーネントの理解
javax.sound.midi
を使いこなすためには、まずその中核をなすいくつかの重要なクラスとインターフェースの役割を理解する必要があります。これらは連携して動作し、MIDIデータの流れを制御します。
クラス/インターフェース | 役割 |
---|---|
MidiSystem | すべてのMIDI操作の入り口となるクラスです。 システムに接続されたMIDIデバイスの情報を取得したり、デフォルトのSequencer やSynthesizer を取得したりする静的メソッドを提供します。 |
MidiDevice | MIDIデバイス(ハードウェア音源、ソフトウェアシンセサイザー、MIDIポートなど)を抽象化するインターフェースです。 デバイスを開いたり閉じたりする基本的な操作を定義します。 |
Sequencer | MidiDevice を拡張したインターフェースで、MIDIシーケンスデータ(通常はMIDIファイル)を再生および録音する機能を持っています。 楽団の指揮者のような役割を果たします。 |
Synthesizer | これもMidiDevice を拡張したインターフェースで、MIDIメッセージを受け取って実際に音を生成する役割を担います。 ソフトウェア音源がこれにあたります。JDKにはデフォルトで “Gervill” というソフトウェアシンセサイザーが搭載されていることが多いです。 |
Receiver | MIDIメッセージの受信口を表すインターフェースです。 send() メソッドを持ち、ここにMidiMessage を送ることで、関連付けられたデバイス(通常はSynthesizer やMIDI出力ポート)がメッセージを処理します。 |
Transmitter | MIDIメッセージの送信元を表すインターフェースです。 setReceiver() メソッドで送信先Receiver を指定し、MIDIデバイス(例えばMIDIキーボード)からの信号を他のデバイスへ流します。 |
MidiMessage | すべてのMIDIメッセージの基底となる抽象クラスです。 具体的にはShortMessage , SysexMessage , MetaMessage などのサブクラスが使われます。 |
Sequence | 複数のTrack を保持するコンテナです。 通常、1つの標準MIDIファイルが1つのSequence オブジェクトに対応します。 |
Track | 時間順にソートされたMIDIイベント (MidiEvent ) の集合です。 楽曲の各パート(ピアノ、ドラムなど)がそれぞれ別のトラックに記録されることが一般的です。 |
ステップ1: 利用可能なMIDIデバイスの情報を取得する
プログラミングの第一歩として、まずは自分のシステムがどのようなMIDIデバイスを認識しているかを確認してみましょう。これにはMidiSystem.getMidiDeviceInfo()
メソッドを使用します。 このメソッドは、利用可能なすべてのMIDIデバイスに関する情報 (MidiDevice.Info
) の配列を返します。
以下のコードは、システムの全MIDIデバイスの名前、ベンダー、説明を表示する簡単なプログラムです。
<?xml version="1.0" encoding="UTF-8"?>
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
public class ListMidiDevices {
public static void main(String[] args) {
System.out.println("--- 利用可能なMIDIデバイス一覧 ---");
// MidiDevice.Infoオブジェクトの配列としてデバイスリストを取得
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
if (infos.length == 0) {
System.out.println("利用可能なMIDIデバイスが見つかりませんでした。");
} else {
for (int i = 0; i < infos.length; i++) {
try {
// InfoオブジェクトからMidiDeviceインスタンスを取得
MidiDevice device = MidiSystem.getMidiDevice(infos[i]);
System.out.println("\n[デバイス " + i + "]");
System.out.println(" 名前: " + infos[i].getName());
System.out.println(" ベンダー: " + infos[i].getVendor());
System.out.println(" 説明: " + infos[i].getDescription());
System.out.println(" バージョン: " + infos[i].getVersion());
// デバイスの種類を判別
// 送信ポート(Transmitter)の最大数。0でなければ入力デバイスの可能性がある
System.out.println(" 入力ポート(Transmitter)数: " + device.getMaxTransmitters());
// 受信ポート(Receiver)の最大数。0でなければ出力デバイスの可能性がある
System.out.println(" 出力ポート(Receiver)数: " + device.getMaxReceivers());
} catch (MidiUnavailableException e) {
System.err.println("デバイス " + i + " の取得中にエラーが発生しました: " + e.getMessage());
}
}
}
System.out.println("\n--- 一覧終わり ---");
}
}
ステップ2: MIDIファイルを再生する (Sequencer)
javax.sound.midi
の最も一般的な使い方の1つが、MIDIファイルの再生です。 これにはSequencer
インターフェースを利用します。 再生までの手順は非常に体系化されており、以下のステップで実現できます。
MidiSystem.getSequencer()
で、デフォルトのシーケンサーオブジェクトを取得する。- 取得したシーケンサーを
open()
メソッドで開く。 MidiSystem.getSequence()
にMIDIファイルを指定し、Sequence
オブジェクトを生成する。- シーケンサーに
setSequence()
メソッドでSequence
オブジェクトをセットする。 start()
メソッドで再生を開始する。- 再生が終了するまで待機する。(これがないと、メインスレッドが終了してしまい音が鳴る前にプログラムが終了してしまう)。
- 再生が終わったら、
close()
メソッドでシーケンサーのリソースを解放する。
以下のサンプルコードは、指定されたMIDIファイルを再生するプログラムです。
<?xml version="1.0" encoding="UTF-8"?>
import javax.sound.midi.*;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class MidiFilePlayer {
public static void main(String[] args) {
// 再生したいMIDIファイルのパスを指定
// 実行環境に合わせてパスを修正してください
String midiFilePath = "path/to/your/midi_file.mid";
File midiFile = new File(midiFilePath);
if (!midiFile.exists()) {
System.err.println("MIDIファイルが見つかりません: " + midiFilePath);
return;
}
try {
// 1. デフォルトのシーケンサーを取得
Sequencer sequencer = MidiSystem.getSequencer();
if (sequencer == null) {
System.err.println("シーケンサーがシステムでサポートされていません。");
return;
}
// 2. シーケンサーを開く
sequencer.open();
// 3. MIDIファイルからSequenceオブジェクトを作成
Sequence sequence = MidiSystem.getSequence(midiFile);
// 4. シーケンサーにSequenceをセット
sequencer.setSequence(sequence);
System.out.println("MIDIファイルの再生を開始します...");
// 5. 再生を開始
sequencer.start();
// 6. 再生終了を待機
// シーケンサーが動作中である間、メインスレッドを待機させる
// Enterキーが押されたら終了する簡易的な待機処理
System.out.println("再生中です。Enterキーを押すと停止して終了します。");
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
// 再生を停止
sequencer.stop();
} catch (MidiUnavailableException e) {
System.err.println("MIDIデバイスが利用できません: " + e.getMessage());
e.printStackTrace();
} catch (InvalidMidiDataException e) {
System.err.println("無効なMIDIデータです: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("ファイルの読み込み中にエラーが発生しました: " + e.getMessage());
e.printStackTrace();
} finally {
// finallyブロックは現時点では不要だが、より複雑な制御ではリソース解放に重要
}
}
}
ステップ3: リアルタイムに音を鳴らす (Synthesizer & ShortMessage)
MIDIファイルの再生だけでなく、プログラムから直接MIDIメッセージを送信してリアルタイムに音を鳴らすことも可能です。 これにはSynthesizer
とShortMessage
クラスを主に使用します。 ShortMessage
は、ノートオン(音を鳴らす)、ノートオフ(音を止める)、プログラムチェンジ(音色を変える)といった、最も基本的なMIDIコマンドをカプセル化します。
音を鳴らす手順は以下の通りです。
MidiSystem.getSynthesizer()
で、デフォルトのシンセサイザーを取得する。- シンセサイザーを
open()
メソッドで開く。 - シンセサイザーから
getReceiver()
メソッドでReceiver
を取得する。 ShortMessage
オブジェクトを作成し、setMessage()
で内容(ノートオンなど)を設定する。- 作成したメッセージを、
Receiver.send()
メソッドでシンセサイザーに送信する。 - 音を止めるには、対応するノートオフメッセージを送信する。
- 処理が終わったら、シンセサイザーを
close()
で閉じる。
以下のサンプルコードは、「ドレミファソラシド」とピアノで演奏するプログラムです。
<?xml version="1.0" encoding="UTF-8"?>
import javax.sound.midi.*;
public class RealtimePlayer {
public static void main(String[] args) {
Synthesizer synthesizer = null;
try {
// 1. デフォルトのシンセサイザーを取得して開く
synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
// 2. シンセサイザーからReceiverを取得
Receiver receiver = synthesizer.getReceiver();
// 3. ShortMessageを使って音を鳴らす
ShortMessage message = new ShortMessage();
// ドレミファソラシドのノートナンバー配列
int[] notes = {60, 62, 64, 65, 67, 69, 71, 72};
int channel = 0; // MIDIチャンネル (0-15)
int velocity = 100; // ベロシティ(音の強さ 0-127)
int duration = 500; // 音の長さ (ミリ秒)
// (オプション) 音色をピアノ(0)に設定
// GM規格では音色番号0はAcoustic Grand Piano
message.setMessage(ShortMessage.PROGRAM_CHANGE, channel, 0, 0);
receiver.send(message, -1);
System.out.println("音色をピアノに変更しました。");
Thread.sleep(100); // 変更が適用されるのを少し待つ
System.out.println("ドレミファソラシドを演奏します...");
for (int note : notes) {
// ノートオン (音を鳴らす)
message.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);
receiver.send(message, -1); // -1はタイムスタンプ即時実行を意味する
// 指定された時間だけ待機
Thread.sleep(duration);
// ノートオフ (音を止める)
// ベロシティ0のノートオンはノートオフとして機能することが多い
message.setMessage(ShortMessage.NOTE_OFF, channel, note, 0);
receiver.send(message, -1);
}
System.out.println("演奏が終了しました。");
Thread.sleep(1000); //最後の音が消えるのを待つ
} catch (MidiUnavailableException e) {
System.err.println("シンセサイザーが利用できません: " + e.getMessage());
e.printStackTrace();
} catch (InvalidMidiDataException e) {
System.err.println("無効なMIDIメッセージです: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4. シンセサイザーを閉じる
if (synthesizer != null && synthesizer.isOpen()) {
synthesizer.close();
System.out.println("シンセサイザーを解放しました。");
}
}
}
}
まとめ
このブログでは、Javaの標準ライブラリであるjavax.sound.midi
の基本的な使い方について、3つのステップに分けて解説しました。
- まず、
MidiSystem
を使ってシステム上のMIDIデバイスを探索する方法を学びました。 - 次に、
Sequencer
を用いてMIDIファイルを簡単に再生する方法を、具体的なコードと共に示しました。 - 最後に、
Synthesizer
とShortMessage
を組み合わせて、プログラムから直接音を生成・制御するテクニックを紹介しました。
javax.sound.midi
は、ここで紹介した機能以外にも、MIDI入力デバイスからのデータ受信、複数デバイス間のデータルーティング、より複雑なMIDIイベントの作成など、奥深い機能を数多く備えています。この記事が、Javaによる音楽プログラミングの世界への第一歩となり、あなたの創造性を刺激するきっかけとなれば幸いです。