モバイルのもと

MIDIの再生

MIDP2.0の仕様では、音を出す要素として、SP-MIDI(Scalable Polyphony MIDI)と、ADPCM、Tone Sequenceが必須になっています。従ってMIDIで作成した音は鳴音すると考えていて良いです。

ステートマシン

MIDIはその音色の中に爆発音や銃声が入っていたりしますので、大体の音がMIDIで再生できますので、これを使えれば音関係はほぼ大丈夫かもしれません。MIDIの作成方法は他に譲ります。

MIDIを鳴音させるにはPlayerインターフェイスを使います。PlayerはManagerクラスより作成されます。

Playerは単純な音源をコントロールする為にあり、より詳細な制御はMMAPI (Mobile Multimedia API)で実施しますが、残念ながらOAPはじめ、多くの携帯端末で実装されていません。(ライセンス料が高いからという噂)

後述しますが、OAPではPrefetchedとStarted状態のPlayerは同時に4本までしか共存できません。 以下の簡略図で言えば、点線で囲まれた部分の状態が同時4本という事になります。

再生

実際に再生するまでの手順は以下の様になります。

private Player sndBGM;
InputStream in = getClass().getResourceAsStream("bgm.mid");
sndBGM = Manager.createPlayer( in, "audio/midi" );
sndBGM.prefetch();
sndBGM.setLoopCount(-1);	// 実際にBGMとして無限ループさせる場合はLoopCountを-1にする。

もしくはちょっと簡略すれば、

private Player sndBGM;
sndBGM = Manager.createPlayer( getClass().getResourceAsStream("bgm.mid"), "audio/midi" );
sndBGM.prefetch();
sndBGM.setLoopCount(-1);

Prefetched状態は、すぐさま再生が可能な状態という位置づけですが、OAPではBREWベースの為(かどうかは分かりませんが)、殆ど関係ありません。 MIDP2.0の仕様書に以下の様な記述があります。

Simple Playback Example

try {
  Player p = Manager.createPlayer("http://abc.wav");
  p.start();
} catch (MediaException pe) {
} catch (IOException ioe) {}

このようにREALIZED状態からいきなりPlayer.start()を行ってもOKです。

ボリューム設定

Playerの音量はVolumeControlを利用します。ボリュームコントロールはPlayerから取得できます。
取得したVolumeControlはPlayerに紐付けられていて、Pl.ayer毎のボリューム設定が可能となります。
なお、VolumeControlはREALIZED状態以上でしか操作できません。

VolumeControl vc = null;

// ボリュームコントロールの作成
vc = (VolumeControl)Player.getControl( "VolumeControl" );

// ボリュームレベルの取得
vol = vc.getLevel();

リスナーの実装

実際のところ未だにリスナーが必要な場面に出会わしていないので、本当に必要なシーンがまだ分かっていませんが、実装方法だけは書いておきます。

大体のデバイスにおいて音源の再生は非同期で結果が返ります。つまり、Player.start()を実行した直後に本当に鳴音が始まる訳ではありません。そこで本当に鳴音が始まった時点でランタイムからコールバックが返却されます。

リスナーはPlayerListenerを使い、playerUpdate()を実装する必要があります。詳細はサンプルコードを見てください。(midi.zip)
ちょっと長いですが、感触を使う為に以下のコードを作成しましたので試してみてください。
※SDK上と実機では相当タイミング等が違います。

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.*;


public final class applet extends MIDlet implements CommandListener {

  mycanvas canvas;
  private static Command cmdExit = new Command("Exit", Command.EXIT,1);
  private static Display display;

  /* ----------------------------------------------------------------------*/
  public applet() {
    display = Display.getDisplay(this);
    canvas = new mycanvas();

    canvas.addCommand(cmdExit);
    canvas.setCommandListener(this);

    display.setCurrent( canvas );
  }
  
  /* ----------------------------------------------------------------------*/
  public void startApp() {
  }

  /* ----------------------------------------------------------------------*/
  public void pauseApp() {
  }

  /* ----------------------------------------------------------------------*/
  public void destroyApp( boolean flag ) {
    canvas = null;
  }

  /* ----------------------------------------------------------------------*/
  public void commandAction(Command c, Displayable d) {
    try {
      destroyApp(true);
      notifyDestroyed();
    } catch ( Exception e) {}
  }
  
}


class mycanvas extends Canvas implements PlayerListener {

  private Player playerBGM;
  VolumeControl vc;
  String errString;
  String listnerString;

  mycanvas() {
    InputStream in = getClass().getResourceAsStream("bgm.mid");
    try {
      playerBGM = Manager.createPlayer( in, "audio/midi" );
      playerBGM.addPlayerListener(this);
    } catch (Exception e) {}
    playerBGM.setLoopCount(-1);
  }
  
  /* ----------------------------------------------------------------------*/
    public void paint( Graphics g ) {
    int yy = 0;
    int fontHeight = Font.getDefaultFont().getHeight();
    g.setColor( 255, 255, 255 );
    g.fillRect( 0, 0, getWidth(), getHeight() );

    g.setColor( 0, 0, 0 );
    g.drawString( "1:realize()", 0, yy, g.TOP|g.LEFT);
    g.drawString( "2:prefetch()", 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "3:start()", 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "4:stop()", 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "5:close()", 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "6:volUp()", 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "7:volDonw()", 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "command result:"+errString, 0, yy+=fontHeight, g.TOP|g.LEFT);
    g.drawString( "listner result:"+listnerString, 0, yy+=fontHeight, g.TOP|g.LEFT);

    switch( playerBGM.getState() ) {
      case Player.CLOSED:
        g.drawString( "CLOSED", 0, yy+=fontHeight, g.TOP|g.LEFT);
        break;
      case Player.REALIZED:
        g.drawString( "REALIZED", 0, yy+=fontHeight, g.TOP|g.LEFT);
        break;
      case Player.PREFETCHED:
        g.drawString( "PREFETCHED", 0, yy+=fontHeight, g.TOP|g.LEFT);
        break;
      case Player.STARTED:
        g.drawString( "STARTED", 0, yy+=fontHeight, g.TOP|g.LEFT);
        break;
      case Player.UNREALIZED:
        g.drawString( "UNREALIZED", 0, yy+=fontHeight, g.TOP|g.LEFT);
        break;
      default:
        g.drawString( "???", 0, yy+=fontHeight, g.TOP|g.LEFT);
        break;
    }
  }

  /* ----------------------------------------------------------------------*/
  public void playerUpdate( Player player, String event, Object eventData ) {
   listnerString = event;
   repaint();
  }
  
  /* ----------------------------------------------------------------------*/
    public void keyPressed( int keycode ) {
    errString = "no error";
    try {
          switch ( keycode ) {
        case KEY_NUM1:
          playerBGM.realize();
          break;

        case KEY_NUM2:
          playerBGM.prefetch();
          break;

        case KEY_NUM3:
          playerBGM.start();
          break;

        case KEY_NUM4:
          playerBGM.stop();
          break;

        case KEY_NUM5:
          playerBGM.close();
          break;

        case KEY_NUM6:
          vc = (VolumeControl)playerBGM.getControl( "VolumeControl" );
          vc.setLevel(vc.getLevel()+1);
          break;

        case KEY_NUM7:
          vc = (VolumeControl)playerBGM.getControl( "VolumeControl" );
          vc.setLevel(vc.getLevel()-1);
          break;

        default:
          break;
      }
    } catch( Exception e ) {
      errString = e.toString();
    }
    listnerString = "";
    repaint();
  }
}

音周りのOAP独自仕様について

Play〜Stop〜Playを連続的に呼び出しても二度目のPlayはほぼ鳴らない様です。Play〜Stop〜deallocate〜Playとすれば連続して鳴音させる事が出来ます。

Prefetchすると早いのか?

JSRのドキュメントを読むと『Prefetchするとなるべく早く再生できるように努力します。』とありますが、OAPの仕様によると前述の通り同時4本までしかPrefetch状態になれないので、ナカナカ使いづらい感じがあります。OAPでは本当に有効なのでしょうか。これについて少し考察してみます。

現在のOAPの仕様とBREWの仕様を比較して想像するに以下の図のような実装であると思われます。

左が純粋なBREWのステートマシーン、右がそれに無理やり当てはめたMIDPのステートマシーンです。「Prefetched状態」とはIMEDIAインターフェイスで言えば「PAUSE状態」になります。これならばOAP仕様の「PREFETCHED/PLAY状態は4本まで」という符号と一致します。

IMEDIAには「READY状態」から「PAUSE状態」へ遷移するAPIはありません。なので、実際のところ「REALIZED状態」から「PREFETCHED状態」への状態遷移は、VM上で実現されているだけで、IMEDIAとしてはREADYのままであろうと想像できます。(もしかしたらIMEDIAのPAUSEを使って無いかも・・・)

結論からして、「OAPでは無理にPrefetchする必要はない」と考えています。


モバイルの素へ戻る MIDP Scrollトップへ戻る


Google
MIDP2.0 CLDC1.1 JSR WWW

Copyright 2007 Mobile no THU