サイトアイコン Omomuki Tech

Java標準ライブラリ javax.sound.midi を徹底解説!MIDIデバイスの制御から音楽再生まで

この記事から得られる知識

この記事を読むことで、あなたは以下の知識を習得できます。

  • 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デバイスの情報を取得したり、デフォルトのSequencerSynthesizerを取得したりする静的メソッドを提供します。
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--- 一覧終わり ---");
    }
}

実行時のポイント

このコードを実行すると、OSに認識されているMIDIデバイスがすべて表示されます。 “Microsoft GS Wavetable Synth” や “Gervill” といったソフトウェアシンセサイザーのほか、USBで接続されたMIDIキーボードや外部音源モジュールなどもリストアップされるはずです。

ステップ2: MIDIファイルを再生する (Sequencer)

javax.sound.midiの最も一般的な使い方の1つが、MIDIファイルの再生です。 これにはSequencerインターフェースを利用します。 再生までの手順は非常に体系化されており、以下のステップで実現できます。

  1. MidiSystem.getSequencer()で、デフォルトのシーケンサーオブジェクトを取得する。
  2. 取得したシーケンサーをopen()メソッドで開く。
  3. MidiSystem.getSequence()にMIDIファイルを指定し、Sequenceオブジェクトを生成する。
  4. シーケンサーにsetSequence()メソッドでSequenceオブジェクトをセットする。
  5. start()メソッドで再生を開始する。
  6. 再生が終了するまで待機する。(これがないと、メインスレッドが終了してしまい音が鳴る前にプログラムが終了してしまう)。
  7. 再生が終わったら、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ブロックは現時点では不要だが、より複雑な制御ではリソース解放に重要
        }
    }
}

再生終了のハンドリング

上記のサンプルでは簡易的にユーザーの入力を待っていますが、より正確に再生終了を検知するにはMetaEventListenerを利用します。 addMetaEventListener()でリスナーを登録し、曲の終わりを示すメタイベント(タイプ 47)をリッスンすることで、再生完了時に特定の処理を自動的に実行させることができます。

ステップ3: リアルタイムに音を鳴らす (Synthesizer & ShortMessage)

MIDIファイルの再生だけでなく、プログラムから直接MIDIメッセージを送信してリアルタイムに音を鳴らすことも可能です。 これにはSynthesizerShortMessageクラスを主に使用します。 ShortMessageは、ノートオン(音を鳴らす)、ノートオフ(音を止める)、プログラムチェンジ(音色を変える)といった、最も基本的なMIDIコマンドをカプセル化します。

音を鳴らす手順は以下の通りです。

  1. MidiSystem.getSynthesizer()で、デフォルトのシンセサイザーを取得する。
  2. シンセサイザーをopen()メソッドで開く。
  3. シンセサイザーからgetReceiver()メソッドでReceiverを取得する。
  4. ShortMessageオブジェクトを作成し、setMessage()で内容(ノートオンなど)を設定する。
  5. 作成したメッセージを、Receiver.send()メソッドでシンセサイザーに送信する。
  6. 音を止めるには、対応するノートオフメッセージを送信する。
  7. 処理が終わったら、シンセサイザーを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("シンセサイザーを解放しました。");
            }
        }
    }
}

音色の変更

音色を変更するには、PROGRAM_CHANGEメッセージを送信します。 メッセージの第2データバイト(サンプルコードでは`instrumentNumber`)に、GM(General MIDI)規格で定められた音色番号を指定します。例えば、48にするとストリングス、24にするとアコースティックギターの音色になります。いろいろな番号を試して、音の変化を楽しんでみてください。

まとめ

このブログでは、Javaの標準ライブラリであるjavax.sound.midiの基本的な使い方について、3つのステップに分けて解説しました。

  • まず、MidiSystemを使ってシステム上のMIDIデバイスを探索する方法を学びました。
  • 次に、Sequencerを用いてMIDIファイルを簡単に再生する方法を、具体的なコードと共に示しました。
  • 最後に、SynthesizerShortMessageを組み合わせて、プログラムから直接音を生成・制御するテクニックを紹介しました。

javax.sound.midiは、ここで紹介した機能以外にも、MIDI入力デバイスからのデータ受信、複数デバイス間のデータルーティング、より複雑なMIDIイベントの作成など、奥深い機能を数多く備えています。この記事が、Javaによる音楽プログラミングの世界への第一歩となり、あなたの創造性を刺激するきっかけとなれば幸いです。

モバイルバージョンを終了