bonar note

京都のエンジニア bonar の技術的なことや技術的でない日常のブログです。

[perl][java] Inline::Java + Java Sound APIでPerlからサウンドファイルを制御

Perlmidi音楽ファイルを扱おうと思った場合、ファイルの書き出しだけであればMIDI::Simpleで十分で、再生まで行おうと思うと MIDI::Music という選択になるかと思います。

ただし、MIDI::Music は XS で soundcard.h を使うので、Mac OS X ではかなりつらいことになります。この辺を CoreAudio で代替するコードを書くとモテるかも?

porting command line unix tools to Max OS X
http://developer.apple.com/jp/technotes/tn2071.html

再生部分の処理を自分で書こうにも、音声ファイルの再生はちょっと想像しただけでもかなり複雑です。そのマシンに複数あるサウンドでバイスを正しく認識し、そのどれかを選択、対象となるファイルがそのデバイスの対応フォーマットかどうかを判別してファイルから読み取ったデータを流し込むという手順になります。これらはそのマシンの環境に深く依存するため、アプリケーション側から見ると、それらの環境依存ファクターを吸収するレイヤーが欲しくなります。

Java の場合は Java Sound API を介してやや抽象的な操作が出来るので素敵です。かなりインチキっぽいですが、Inline::Java を用いてそれをPerlから使う事が出来ます。

inline_java.pl

#!/usr/bin/perl

use strict;
use warnings;

sub java_sound_source { q{
import java.lang.*;
import java.io.*;
import javax.sound.sampled.*;

public class JavaSound {
    public  static final int STREAM_BUFFER_SIZE = 1024 * 64;
    private Mixer[] mixers = null;

    public JavaSound() { 
        this.setup_mixer();
    }
    private void setup_mixer () {
        Mixer.Info[] mixer_info = AudioSystem.getMixerInfo();
        Mixer[] mixers = new Mixer[mixer_info.length];
        for (int i = 0; i < mixer_info.length; i++) {
            Mixer.Info mixer = mixer_info[i];
            mixers[i] = AudioSystem.getMixer(mixer_info[i]);
        }
        this.mixers = mixers;
    }
    public void play_wave_file(String filename) {
        // setup audio stream
        File file = new File(filename);
        AudioInputStream stream = null;
        try {
            stream = this.get_audio(file);
        }
        catch (IOException e) {
            System.err.println("cannot open file");
            return;
        }
        catch (UnsupportedAudioFileException e) {
            System.err.println("unsupported format");
            return;
        }
        // open first audio line of first mixer (lazy!)
        Mixer target_mixer = this.mixers[0];
        Line.Info[] lines = target_mixer.getSourceLineInfo();
        SourceDataLine outline = null;
        try {
            outline = (SourceDataLine)target_mixer.getLine(lines[0]);
            outline.open(stream.getFormat(), STREAM_BUFFER_SIZE);
        }
        catch (LineUnavailableException e) {
            System.err.println("line label unavailable " 
                + e.getMessage());
            return;
        }

        try { // play (read audio bytes from the file and put them to
              // output source data line)
            outline.start();
            
            int bytes_done = 0;
            byte[] buffer  = new byte[STREAM_BUFFER_SIZE];
            while ((bytes_done = stream.read(buffer)) != -1) {
                outline.write(buffer, 0, STREAM_BUFFER_SIZE);
            }
            outline.stop();
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            return;
        }
    }
    public static AudioInputStream get_audio(File file) 
        throws UnsupportedAudioFileException, IOException {
        return AudioSystem.getAudioInputStream(file);
    }
}

}}

use Inline Java => \&java_sound_source;

my $javasound = new JavaSound();
$javasound->play_wave_file(shift);

以下のコマンドで .wav ファイルを再生することが出来ます。

bonar$ perl inline_java.pl Survivalism_bonar_mix.wav 

「それがありなら、なんでもありだろwww」って感じですが、J2SEはもはやほとんどのプラットフォームで標準で準備されているので、意外とありなのではと思っています。同じ要領でmidiファイルの再生やリアルタイムな制御も出来る事になります。

じゃあ全部Javaで書けよって話ですが。新年あけましておめでとうございます。