FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

MIDIキーボードで3Dプリンタを演奏する

はじめまして。フリューモバイル事業部の庄司です。

不定期にアレなネタを投下していきます。よろしくお願いします。

MIDIキーボードで3Dプリンタを演奏する

わけがわからないよ…。

概要

みなさんのご家庭にも1台や0台はある、3Dプリンタ
今回はこれにMIDIキーボードをつなげて演奏してみます。

必要なもの

Javaが動作し、下記の機器が接続できる環境。

JavaからMIDI信号の受信

javax.sound.midiパッケージ以下のクラスを使います。
このパッケージはJava 1.3にて追加されたものです。わりとシンプルで使いやすいです。
今回は、キーボードからの受信のみを行うので、Receiverクラスを実装します。

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiDevice.Info;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Transmitter;

public class MidiTest implements Receiver {
    // TODO リファクタリング
    public void receiveMidi(String transmitterName) {
        Info[] infos = MidiSystem.getMidiDeviceInfo();
        Transmitter transmitter = null;
        for (Info info : infos) {
            try {
                if (!info.getName().equals(transmitterName)) {
                    continue;
                }
                MidiDevice device = MidiSystem.getMidiDevice(info);
                if (device != null) {
                    transmitter = device.getTransmitter();
                    transmitter.setReceiver(this);
                    break;
                }
            } catch (MidiUnavailableException e) {
                transmitter = null;
            }
        }
        if (transmitter == null) {
            System.exit(0);
        }
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws Throwable {
        MidiTest midiTest = new MidiTest();
        midiTest.receiveMidi("UM-ONE - UM-ONE"); // XXX デバイス名を指定
    }

    public void send(MidiMessage message, long timeStamp) {
        if (message instanceof ShortMessage) {
            ShortMessage shortMessage = ((ShortMessage) message);

            int data1 = shortMessage.getData1();

            switch (shortMessage.getCommand()) {
            case ShortMessage.NOTE_ON:
                if (shortMessage.getData2() != 0) {
                    System.out.println("MIDI note on :" + data1);
                } else {
                    System.out.println("MIDI note off:" + data1);
                }
                break;
            case ShortMessage.NOTE_OFF:
                System.out.println("MIDI note off:" + data1);
                break;
            }
        }
    }

    public void close() {
    }
}

このコードを実行すると、MIDIを受信するたびにsendメソッドが呼び出され、MIDI note numberが表示されます。

今回使ったキーボードのせいか、鍵盤を数秒間押し続けると音が止まらなくなってしまったので、ある程度長く発音されたら音を止めることにする、という処理を入れておきます。

音程を作る

演奏するためには、音程が作れないといけません。
音程=空気の振動数(周波数)です。
例えば、秒間440回(440Hz)空気を振動させることにより、「中央のラ(→ A4 → MIDI note number 69)」の音が発生します。
2倍の880Hzにすると1オクターブ上がります。
440Hz = ラであるという定義は、時代や地域によって変わります。が、この記事の本筋とは関係ありません。

今回は、不本意ではありますが、計算が楽なため平均律を使います。
平均律とは、1オクターブを12等分に分割したものです。

private double convertMidiNoteNumberToFrequency(int note) {
    return 440.0 * Math.pow(2.0, ((double) (note - 69)) / 12.0);
}

こんな感じです。

このMIDI note number→周波数変換関数をいくつか用意すると、複数の音律に対応できるようになります。
3Dプリンタによる発声で音律に拘るのはアレな感じがしますが、この部分は他の音声合成を行う際にも適用できます。

3Dプリンタによる音程作り

振動数を、「3Dプリンタの軸の移動速度」に割り当ててやることにより、3Dプリンタステッピングモーターで音程を作ることができます。

連続で発声させる場合、同じ方向ばかり続けて動かすと終端まで行ってしまうので、移動可能な範囲を制限し、これを超えないよう制御してやる必要があります。
これはZ軸で特に気をつける必要があります。最悪、X-Yのテーブルを突き破ってしまいます。

音の長さ

同じ距離を別の速度で移動するので、このままでは音程が低いほど長い音になってしまいます。
移動速度に比例した移動距離を設定してやることにより、どの音でも同じ長さにすることができます。

和音

単音って寂しいですよね?
3DプリンタにはX, Y, Zという3つの軸があります。
プラスチック素材を送るためのステッピングモーターがあるので、正確に言うと4つですが、今回は使用していません。
これらを使うと3和音が表現できます。初期の携帯電話並です(無理矢理モバイルと結びつけてみます)。

各軸の移動距離は単音のときと同じように求めることができます。
しかし、移動距離は各軸個別に指定できるのですが、移動速度は1つのみです。
X, Y, Z各軸の速度を合成ベクトルにして、その長さを求めることによって、合成された軸の速度を求めることができます。

double feedrate = Math.sqrt(Math.pow(feedrateX, 2) + Math.pow(feedrateY, 2) + Math.pow(feedrateZ, 2));

こんな感じですね。

3Dプリンタの制御

今回使用する3Dプリンタオープンソースハードウェア、そしてファームウェアと制御ソフトウェアもオープンソースソフトウェアで構成されています。
というわけで、プリンタ制御のためのコードは公開されています。そして都合が良いことにJavaで書かれています。
ReplicatorGというソフトウェアになります。

Macであれば、ReplicatorG.app/Contents/Resources/Java以下に、使用するJarパッケージやJNIライブラリが転がっています。これらをプロジェクトにコピーし、Jarにクラスパスを通します。

Jarのみだとコードが追いにくいので、以下から最新を取ってきて眺めるのも良いかと思います。
git clone git://github.com/makerbot/ReplicatorG.git

制御のメインになるクラスは、replicatorg.machineパッケージ以下にあります。

import replicatorg.app.Base;
import replicatorg.machine.MachineInterface;
import replicatorg.machine.MachineLoader;

...

    private MachineInterface getMachine() {
        MachineLoader machineLoader = new MachineLoader();
        String name = Base.preferences.get("machine.name", null);
        boolean loaded = machineLoader.load(name);

        String targetPort = Base.preferences.get("serial.last_selected", null);
        if (targetPort == null) {
            return null;
        }
        machineLoader.connect(targetPort);
        return machineLoader.getMachine();
    }

このようにして、制御に使用するMachineInterfaceを実装しているクラスのインスタンスを取得します。

あとは、

import replicatorg.model.StringListSource;

...

MachineInterface machine = getMachine();
Vector<String> codes = new Vector<String>();
codes.add("G21"); // 何らかのG-Code
machine.buildDirect(new StringListSource(codes));

などとすると、3Dプリンタの制御が行われます。

G-Codeの生成

G-Codeとは、CNC(Computerized Numerically Controlled)マシンを制御するための言語です。
G1、G2…、と命令に連番が振られていて、命令ごとに個別にパラメータが定義されています。Gで始まるからG-Codeです。詳しくはWikipedia英語版あたりで調べてみてください。
ReplicatorGのMachineInterfaceクラスを使うと、前述のようにテキストベースの記述から制御することができます。
各軸を動かすためのG-Codeは「G1」になります。今回使うのは3Dプリンタ初期化用のG-Codeを除いて、この「G1」のみを使っています。

例えば、「ドミソ(C4 E4 G4)」を1秒間鳴らすためのG-Codeはこのようになります。

G1 X5.9460355750 Y-7.4915353844 Z8.9089871814 F784.2541543808

ファイル名をceg.gcodeなどとしてテキストファイルで保存し、ReplicatorGで開いて実行すると「ドミソ」が鳴るはずです。
(事故防止のため、事前に各軸を中央付近にリセットしておく必要があります)。

実装の結合そして演奏

ここまでで

までができました。

これらを結合、つまりMIDIを受信して、現在鳴っている音を3つまで格納するようなSetを保持し、そこから和音の周波数を求め、G-Codeを生成、プリンタに指令することにより、演奏できるようになるはずです。

Java実行形式(jar)とコードの一式は以下になります。
ソースコードのライセンスは、ReplicatorGと同じく「GPLv2」となります。
作成したばかりなので、動作の改善やドキュメントなど、しばらくは随時更新します。

使い方

  • zipを展開して出来る、readme.txtの通りにファイルを設置します。
  • MIDIインタフェースとキーボード、3DプリンタをPCに繋ぎます。
  • 3DプリンタはあらかじめReplicatorGで設定しておきます(最後に使った設定を使います)。
  • JNIライブラリ(librxtxserial.jnilib)が32bitでしか動かないので、vmargsに「-d32」を指定してmainクラスを実行します。
java -d32 -jar Midibot.jar

Swingで画面インターフェースを作ってみました。MIDIインターフェースを選んで、「Choose & start to play.」ボタンを押して、一旦軸が端まで動き、中央に止まったら、演奏できます。
例外出たときとか、特に処理入れてないので変化しません…。「Reset machine.」ボタンで、軸の位置を再度初期化します。演奏していてズレてきたら押してみるといいかも。
変な挙動になるかもしれないので、いつでも3Dプリンタの方のリセットボタンが押せるよう、準備してからお使いください。

まとめ

というわけで、3Dプリンタの可能性を試してみました。プリンタとしては使っていませんが…。3Dプリンタ、一昔前と比べるとお手頃価格なのに、結構な精度が出るので良いですよ。一家に一台、いかがでしょうか。

今回、javax.sound.midiを初めて使ってみましたが、クセもあまりなく使いやすかったです。
MIDI自体がシンプルということもあって対応機器は楽器に限らず色々とある(し、簡単に作れる)ので、遊びに・実用に使ってみるのも良いかもしれません。
iMIDIとかネットワーク上に乗るMIDIプロトコルも出てきているので(MacではOSがiMIDIをサポートしている)、「楽器との接続」以上のことが出来るようになってきています。

ReplicatorGはArduino IDEからのforkということもあってJavaで書かれています。JavaなのでServletと組み合わせて遠隔3Dプリントサーバとか作れるかもしれません。エラーの監視が大変でしょうけど。