CMX API ver.0.21チュートリアル
第5回「DeviationInstanceXMLドキュメントを生成する(その2)」

北原 鉄朗(JST CrestMuse/関西学院大学理工学研究科)
2007年10月18日

はじめに

前回は,ごくごく簡単なDeviationInstanceXMLドキュメントを生成しましたが,もう少し凝ったものを作ってみましょう.ここでは,「フェルマータが付いていたらテンポを遅くする」「スタッカートが付いていたら音符の長さを短くする」というのを取り上げます.

フェルマータが付いていたらテンポを遅くする

まずは,プログラムの大枠の部分を作ってみましょう.

import jp.crestmuse.cmx.commands.*;
import jp.crestmuse.cmx.filewrappers.*;
import javax.xml.parsers.*;
import org.xml.sax.*;

public class MyDeviationTest2 extends CMXCommand {
  protected void run() throws InvalidFileTypeException,
                              ParserConfigurationException,SAXException  {
    MusicXMLWrapper musicxml = (MusicXMLWrapper)indata();
    DeviationInstanceWrapper dev = (DeviationInstanceWrapper)
      CMXFileWrapper.createDocument(DeviationInstanceWrapper.TOP_TAG);
    dev.setTargetMusicXMLFileName(musicxml.getFileName());
    DeviationDataSet dds = dev.createDeviationDataSet();
    dds.addNonPartwiseControl(1, 1, "tempo", 120.0);
    // ここにdeviation情報を追加する処理を書く
    dds.addElementsToWrapper();
    setOutputData(dev);
  }

  public static void main(String[] args) {
    MyDeviationTest2 devtest = new MyDeviationTest2();
    try {
      devtest.start(args);
    } catch (Exception e) {
      devtest.showErrorMessage(e);
      System.exit(1);
    }
  }
}
前回のプログラムとほとんど一緒なので,特に解説はいらないと思います.テンポは120と決めて,1小節めの1拍めでテンポ=120の指定をすでに書いてあります.

「フェルマータが付いていたらその拍のテンポを遅くする」という処理を行うということは,MusicXMLドキュメントのnote要素を次々に読み込んで,フェルマータが付与されているかをチェックするということになります.MusicXMLドキュメントのnote要素を次々と読み込んでいくというのは第2回でやりましたね.これは次のように書くことができます.

import jp.crestmuse.cmx.commands.*;
import jp.crestmuse.cmx.filewrappers.*;
import javax.xml.parsers.*;
import org.xml.sax.*;

public class MyDeviationTest2 extends CMXCommand {
  protected void run() throws InvalidFileTypeException,
                              ParserConfigurationException,SAXException  {
    MusicXMLWrapper musicxml = (MusicXMLWrapper)indata();
    DeviationInstanceWrapper dev = (DeviationInstanceWrapper)
      CMXFileWrapper.createDocument(DeviationInstanceWrapper.TOP_TAG);
    dev.setTargetMusicXMLFileName(musicxml.getFileName());
    DeviationDataSet dds = dev.createDeviationDataSet();
    dds.addNonPartwiseControl(1, 1, "tempo", 120.0);
    MusicXMLWrapper.Part[] partlist = musicxml.getPartList();
    for (MusicXMLWrapper.Part part : partlist) {
      MusicXMLWrapper.Measure[] measurelist = part.getMeasureList();
      for (MusicXMLWrapper.Measure measure : measurelist) {
        MusicXMLWrapper.MusicData[] mdlist = measure.getMusicDataList();
          for (MusicXMLWrapper.MusicData md : mdlist) {
            if (md instanceof MusicXMLWrapper.Note) {
              MusicXMLWrapper.Note note = (MusicXMLWrapper.Note)md;
              // フェルマータが付いているかどうかをチェック
              // ついていたらテンポを遅くする
            }
          }
        }
      }
    }
    dds.addElementsToWrapper();
    setOutputData(dev);
  }

  public static void main(String[] args) {
    MyDeviationTest2 devtest = new MyDeviationTest2();
    try {
      devtest.start(args);
    } catch (Exception e) {
      devtest.showErrorMessage(e);
      System.exit(1);
    }
  }
}

フェルマータは音符に付与されるわけですから,MusicXMLドキュメントのnote要素の中に記述されるわけですが,具体的にどう記述されるか見てみましょう.

      <note>
        <pitch>
          <step>F</step>
          <octave>4</octave>
        </pitch>
        <duration>4</duration>
        <voice>1</voice>
        <type>quarter</type>
        <notations>
          <fermata type="inverted"/>
        </notations>
これはフェルマータが付与された音符の例です.note要素の中にnotationsタグというのがあって,その中にfermataタグというのがあるので,これがフェルマータを表していることが分かります.type="inverted"というのはおそらくフェルマータの向きと思われます.

notations要素の情報を取り出すには,MusicXMLWrapperクラスの内部クラスとして定義されているNotationsクラス(以下,MusicXMLWrapper.Notationsと書く)というのが利用できます.notations要素はnote要素の一部ですから,MusicXMLWrapper.Noteオブジェクトから取得することができます.

MusicXMLWrapper.Notations notations = note.getFirstNotations();
MusicXMLのDTDを読むと,notations要素というのは1つのnote要素に複数記述できるようですが,ほとんどの場合1つしか記述しないようなので(複数の指示を書きたい場合は1つのnotationsの中に並べて書ける),現バージョンでは1つめのnotations要素だけ取り出せるようになっています.これも,要望があれば複数のnotations要素の取得にも対応したいと思います.

MusicXMLWrapper.NotationsクラスのAPIリファレンスドキュメントを見ると,fermataというメソッドとfermataTypeメソッドというのがあることが分かります.前者はfermata要素に付与されたテキスト,後者は文字どおりfermata要素のtype属性を返します.fermata要素がなかったらどちらもnullを返すので,ここではfermataTypeメソッドを実行して返り値がnullかどうかでフェルマータの有無を判断します.

if (notations.fermataType() != null) {   // フェルマータあり
    // テンポを遅くする
}

フェルマータでテンポ遅くするのは,基本的にそのフェルマータが付いている音符の拍だけで,その音符が終わったら元のテンポに戻ります.こういった局所的なテンポの変化はtempoではなくてtempo-deviationを使います.tempo-deviationについて忘れてしまった人は,もう1度第4回を読んでみてください.tempo-deviationはnon-partwise deviationなので,DeviationDataSetクラスのaddNonPartwiseControlメソッドを利用します.このメソッドは次のようにして用います.

dds.addNonPartwiseDeviation(小節番号, 拍番号, deviation情報の種類, deviationの値);
いまはtempo-deviationの付与を考えているので,deviation情報の種類"tempo-deviation"です.deviationの値は,ここではテンポを半分にするということにして0.5にします.小節番号拍番号はフェルマータの付いた音符の小節番号と拍番号を取得する必要があります.小節番号はNoteオブジェクトからその親となるMeasureオブジェクトを取得し,numberメソッドを呼び出すことで取得できます.拍番号はNoteオブジェクトのbeatメソッドを呼び出すことで取得できます.結局,ここの記述は
dds.addNonPartwiseDeviation(note.measure().number(), note.beat(), 
                            "tempo-deviation", 0.5);
となります.

これで,「フェルマータが付いていたらその拍のテンポを遅くする」というのは完成です.第4回で述べた方法で実際に実行してDeviationInstanceXMLドキュメントをファイルに保存し,SMFに変換して確かめてみましょう.ただし,ここで挙げた例は単にフェルマータがあったらtempo-deviationを付与するというだけなので,同時刻に複数のフェルマータがあったり,いろいろな条件下でうまくいかない場合があるはずです.これの対処はぜひ自分で考えて解決してみてください.

ちなみに,第2回の最後で述べたprocessNotePartwiseを使って書くこともできます.これも実際に試してみてください.

スタッカートが付いていたら音符の長さを短くする

次に,各音符にスタッカートが付いていたら,音の長さを半分にするというのをやってみましょう.これは,音符ごとのdeviationですから,note-deviationを用います.note-deviation要素の書き方をもう1度おさらいしてみましょう.第4回で示したDeviationInstanceXMLの例では,次のように書いてありました.

    <note-deviation xlink:href="#xpointer(/score-partwise/part[@id='P1']/measure[@number='1']/note[3])">
      <attack>0.0</attack>
      <release>-0.25</release>
      <dynamics>1.0</dynamics>
      <end-dynamics>1.0</end-dynamics>
    </note-deviation>
releaseというのは,消音時刻(鍵盤から指を離す時刻)が楽譜通りの時刻からどの程度ずれているかを表します.4分音符1個分の長さを1.0とした実数で表します.上の例では,鍵盤から指を離すのが,楽譜通りの時刻よりも16音符分早かったことを表しています.

note-deviation要素は,DeviationDataSetクラスのaddNoteDeviationメソッドを使って行います.DeviationDataSetオブジェクトの名前がddsだったとすると,次のようにします.

dds.addNoteDeviation(Noteオブジェクト, attack, release, dynamics, end-dynamics);
と書きます.attack(発音時刻のずれ)は今回はいじらないので0.0となります.dynamics(打鍵の強さ)とend-dynamics(離鍵の強さ)も今回はいじりませんが値は1.0になります.これは,dynamicsとend-dynamicsは基準ダイナミクス(忘れた人は第4回を参照)に対する割合として記述するからです.releaseは次のように計算して求めます.音の長さを半分にするということは,楽譜上の音の長さの半分だけ早めに鍵盤から手を離すということです.楽譜上の音の長さというのは,NoteオブジェクトのactualDurationメソッド(実際は,このメソッドはNoteクラスのスーパークラスのMusicDataクラスに定義されています)で取得できます.この半分だけ消音時刻が前にずれるわけですから,
-note.actualDuration() / 2.0
とすればよいことになります.ここで,noteには対象となるNoteオブジェクトが入っているものとします.結局,ここでの記述は,
dds.addNoteDeviation(note, 0.0, -note.actualDuration() / 2.0, 1.0, 1.0);
ということになります.

最後に,スタッカートが付いているかどうかの判定方法を説明します.まず,スタッカートの付いたnote要素の例をみてもらいましょう.

      <note>
        <pitch>
          <step>F</step>
          <octave>4</octave>
        </pitch>
        <duration>2</duration>
        <voice>1</voice>
        <type>eighth</type>
        <notations>
          <articulations>
            <staccato placement="below"/>
          </articulations>
        </notations>
      </note>
note要素の中にnotations要素があり,その中にarticulations要素というのがあり,その中にstaccato要素があるのが分かると思います.notations要素からの情報の取りだしはMusicXMLWrapper.Notationsクラスが行うので,このクラスのAPIリファレンスドキュメントを改めて見てみましょう.ここにhasArticulationというメソッドがあります.これは,articulations要素があり,その中に指定した名前の要素があればtrueを返すというのもです.すなわち,
if (notations.hasArticulation("staccato")) {
    // スタッカートあり
}
ということになります.

これで必要な情報がすべて揃いました.今回はあえて最終的なプログラムの形を示さないので,以上の情報を参考にしてご自分でプログラムを書いてみてください.それが完成したら,「フェルマータがあったらテンポを遅くする」と「スタッカートがあったら音長を短くする」というのを両方行うプログラムを書いてみましょう.

おわりに

今回は,MusicXMLドキュメントを読み,そこに含まれる情報を元にDeviationInstanceXMLドキュメントを生成する課題として,「フェルマータがあったらテンポを遅くする」「スタッカートがあったら音長を短くする」というのを取り上げました.楽譜の情報を読み取ってそれを解釈して楽器制御情報を生成しているので,自動演奏生成システムの最も簡単な形ということができるでしょう.Renconへのエントリーを希望される方は,これを参考により洗練された演奏生成システムの設計に取り組んでみてください.