CMX API ver.0.21チュートリアル
第4回「DeviationInstanceXMLドキュメントを生成する」

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

はじめに

これまでのチュートリアルでは,第1回でMusicXMLからの情報の取り出し,第2回ではCMX APIを使ったXMLドキュメントの生成方法について学びました.第3回の今回は,MusicXMLを読み込んで情報を取り出し,それに基づいてdeviation情報(楽器制御情報)を記述したXMLドキュメントを生成する,というのをやってみましょう.

DeviationInstanceXMLとは

まず,deviation情報というのは楽譜通りの機械的な演奏からの「ずれ」の度合を表すものです.たとえば,この音は楽譜通りのジャストのタイミングよりちょっとだけ前に発音するとか,あの音は楽譜通りの長さよりちょっと短めにするとか,テンポを微妙に変えるとか,そういうことです.DeviationInstanceXMLは,このdeviation情報を記述するためのXMLフォーマットです.

本来,deviation情報というのは,様々な楽器を考えると様々なものがあるわけですが,本バージョンではピアノに限って設計しています.ピアノという楽器は一度鍵盤を押すと離すまで何もできないわけで,どのぐらいの強さで鍵盤を押すか,どのぐらいの強さで鍵盤から指を離すか,また,それらの動作をどのタイミングで行うか,この程度しかパラメータはないことになります.このように,様々な人に好かれている楽器にしては少ないパラメータに落とし込むことができるので,工学研究で好まれるのでしょう.Renconでもピアノ演奏が対象になっています.

いま,単に「どのタイミングで」と言いましたが,タイミングの制御というのはその意味合いを考えると2つに分かれるかと思います.1つはテンポ制御としてのタイミング制御です.たとえばある小節の最後の拍をためて弾くとかというのは,この好例かと思います.もう1つは,個々の音符に対するタイミング制御です.テンポ自体は変化させずにこの音だけ少しもたった感じで弾くなどのような感じです.DeviationInstanceXMLでは,これらを分離して書けるようになっています.さらに言うと,テンポの制御は基準テンポの設定とtempo deviationとに分かれます.次節で仕様の詳細を見ていきましょう.

DeviationInstanceXMLの仕様

DeviationInstanceXMLでは,deviation情報を

の3種類に分けて記述します.詳細をnotewise deviationからpartwise deviation,そしてnon-partwise deviationの順に解説していきましょう.

Notewise deviationとは,個々の音符に対応するdeviation情報を表します.たとえば,個々の音符の発音時刻や消音時刻,鍵盤を押すときや離すときの強さ(ダイナミクス)です.

Partwise deviationとは,個々の音符には対応しないが,パートごとに値が変わってくるという性質のdeviation情報です.ピアノにおけるペダリングや基準ダイナミクスが該当します.「基準ダイナミクス」とは,文字どおりダイナミクスの基準値のことを言い,notewise deviationの項で記述する音符ごとのダイナミクスは,この基準値からの相対値ということになります.

Non-partwise deviationとは,個々の音符にも対応しないし,個々のパートにも対応しない(つまり全パート共通)deviation情報です.テンポが該当します.

では,さらに詳しい説明を始める前に実際のDeviationInstanceXMLドキュメントの一例を見てもらいましょう.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE deviation PUBLIC "-//CrestMuse//DTD CrestMuseXML 0.20 DeviationInstanceXML//EN"
                           "http://www.crestmuse.jp/cmx/dtds/deviation.dtd">
<deviation target="renconsample-s001.xml">
  <non-partwise>
    <measure number="1">
      <control beat="1.0">
        <tempo>120.0</tempo>
      </control>
    </measure>
    <measure number="2">
      <control beat="2.0">
        <tempo-deviation>0.5</tempo-deviation>
      </control>
    </measure>
  </non-partwise>
  <partwise></partwise>
  <notewise>
    <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>
    <note-deviation xlink:href="#xpointer(/score-partwise/part[@id='P1']/measure[@number='1']/note[4])">
      <attack>0.0</attack>
      <release>-0.25</release>
      <dynamics>1.0</dynamics>
      <end-dynamics>1.0</end-dynamics>
    </note-deviation>
  </notewise>
</deviation>

ちょっと長いですが,順番に見ていきましょう.まず,トップレベルタグがdeviationだということがわかるかと思います.その子ノードとして non-partwise, partwise, notewiseが並んでいます.これらは上で述べたnon-partwise deviation, partwise deviation, notewise deviationに対応します.

non-partwise要素の中を見ていくと,

<measure number="mm">
  <control beat="nn">
    <xxx>yyy</xxx>
  </control>
</measure>
という構造が見てとれます.これはご想像のとおり,第mm小節のnn拍目にxxxに関する値をyyyに設定するという意味です.たとえば,上記では第1小節の1拍めにテンポを120に設定しています.つまり,non-partwise要素の中にはmeasure要素が並び,measure要素の中にはcontrol要素が並び,control要素の中の子要素のタグ名でdeviationの種類を指定するというようになっています.上の例ではたまたま1つのmeasure要素にはcontrol要素は1つしかなかったですが,複数買いてもかまいません.ただし,control要素の中に入れる要素は1つです.

上の例をよく見てみると,tempoというのとtempo-deviationというものがあるのがわかると思います.このtempo-deviationというのは,局所的(一時的)にテンポを変えたいときに用いるもので,その拍だけテンポを変えるというものです.上の例では2小節目の2拍めでtempo-deviation=0.5となっているので,この拍だけテンポが60になって3拍めからは120に戻ります.これを表にまとめると次のようになります.

tempotempo-deviation
意味テンポ(基準値)テンポのゆれ
記述内容BPM (beats per minute)基準値に対する割合
有効範囲次のtempo設定まで有効その拍の終わりまで

おおまかなテンポはtempoコントロールで記述し,細かな,たとえばこの拍だけ少し「タメ」て弾こうといった制御はtempo-deviationコントロールで記述します.

次にpartwise deviationですが,ペダルと基準ダイナミクスの設定が入る予定ですが,まだ未実装のため,対応していません.少なくともペダルについては次バージョンで対応予定です.

最後にnotewise deviationについてです.notewise要素の中を見るとnote-deviation要素が並んでいることが分かると思います.さらに,note-deviation要素の属性にxlink:href="#xpointer(/score-partwise/part[@id='P1']/measure[@number='1']/note[3])"と書いています.この#xpointer(...)というのはXPointerといって,あるXMLドキュメントにおけるある部分を指し示すためのものです.ここでは,対象となるMusicXML文書(deviation要素のtarget属性で指定)における,id属性が'P1'のpart要素の中のnumber属性が'1'のmeasure要素の中の3番めのnote要素を指し示しています.つまり,最初のnote-deviation要素というのは,P1というパートの1小節目の3つめの音符に対するdeviation情報だということです.note-deviation要素の中のattack要素とrelease要素は発音時刻と消音時刻のズレです.楽譜通りの時刻をプラス・マイナス・ゼロとして,4分音符1個分を1.0とする実数値で記述します.dynamicsとend-dynamicsはそれぞれ打鍵と離鍵の強さです.ただし,end-dynamicsの処理は現バージョンでは未実装です.

ダイナミクスは,本来の仕様では基準ダイナミクスとnotewiseダイナミクス(いずれも仮称)に分けて記述することになっています.テンポと同様に表にするとこのようになります.

base-dynamicsdynamics
意味ダイナミクスの基準値音符ごとの基準値からのゆれ
記述内容あるダイナミクスに対する割合base-dynamicsに対する割合
記述場所partwise要素内notewise要素内

フォルテやピアノといった大局的なダイナミクス制御に基準ダイナミクスを利用し,個々の音符の細かな制御をnotewiseダイナミクスで記述することを想定しています.注意すべきは,両方とも相対値だということです.DeviationInstanceXMLはMIDIとは完全に切り離されているので,基準ダイナミクスの1.0という値がMIDIのベロシティではどんな値になるのかは,規格としては定義されていません.ただし,現バージョンではbase-dynamicsは未実装ですので,1.0で固定されており,ベロシティへの変換も,基準ダイナミクス1.0=ベロシティ100となっています.

ここでの例では登場しませんでしたが,notewise要素に記述できるものとして,note-deviationの他にchord-deviationとmiss-noteがあります.これらについては,また機会があれば触れたいと思います.

DeviationInstanceXMLについては音楽情報科学研究会発表論文(PDF版)の3.3節にも多少詳しく書いています.

DeviationInstanceXMLドキュメントを生成する方法の概要

DeviationInstanceXMLドキュメントもCrestMuseXMLの一部ですから,当然CMX API内で扱う枠組が用意されています.CMX APIの枠組みでXMLドキュメントをどうやって生成するかを忘れてしまった人は,もう一度本チュートリアルの第3回を読んでみましょう.これを読むと,
CMXFileWrapper.createDocument(トップレベルタグ名)
とすれば,指定したトップレベルタグに対応するファイルラッパクラスのオブジェクトを生成してくれると書いてあります.DeviationInstanceXMLのファイルラッパクラスはDeviationInstanceWraperで,トップレベルタグはDeviationInstanceWrapperクラスの定数TOP_TAGとして定義されていますので,
DeviationInstanceWrapper dev = (DeviationInstanceWrapper)
    CMXFileWrapper.createDocument(DeviationInstanceWrapper.TOG_TAG)
とすればOKです.

次に,このDeviationInstanceXMLがどのMusicXMLに対するdeviation情報なのかを記述するため,

dev.setTargetMusicXMLFileName(indata().getFileName());
を実行します.これを入れないと,せっかく生成したDeviationInstanceXMLドキュメントがどの楽譜に対するものなのかわからないので,演奏を生成できなくなってしまいます.

ファイルラッパオブジェクトを生成した段階では,中身は空なので,要素を追加していきましょう.要素の追加方法を再びチュートリアルの第3回を読んで思い出してみましょう.要素の追加はaddChild, addText, returnToParentなどのメソッドを使って行うと書いてあります.どのメソッドも「カレント要素」が基準となっており,カレント要素が移り変わりながら,新しい要素を追加する方式になっています.言い換えると,要素の追加は出現順(木構造における行きがけ順)で行う必要があります.

ここで,問題が発生します.DeviationInstanceXMLの場合,non-partwise deviationに含まれる情報をすべて書き出してから,partwise deviationを書きだし,最後にnotewise deviationを書き出す,という順番にしなければならないということです.non-partwise deviationの生成を楽曲の最後まで行ってから,また楽曲の先頭に戻ってnon-partwise deviationを生成する,さらに・・・,というのは非常に非効率的ですね.

これを解決するにはどうしたらいいでしょうか.すべての種類のdeviation情報を一時的に貯めておくためのオブジェクトを用意し,それには順番は関係なくどんどんdeviation情報を追加していける,そして,最後にその情報を正しい順序に並べ替えてDeviationInstanceWrapperオブジェクトに追加していく,という風にすれば,DeviationInstanceXMLの仕様(deviation情報の出現順)を気にせずにdeviation情報を扱えそうです.それを実現するのが,DeviationDataSetというクラスです.

DeviationDataSetのAPIリファレンスドキュメントを見てみましょう.DeviationDataSetには,現状では次の4つのメソッドが用意されています. addNonPartwiseControlメソッドは,文字どおりnon-partwise deviation(たとえばテンポの設定など)を追加するものです.addNoteDeviationメソッドは,notewise deviationの1種であるnote-deviation要素を追加するものです.addChordDeviationメソッドは,上では説明を省略したchord-deviation要素を追加するものです.これらを使ってdeviation情報を次々に追加していって最後にaddElementsToWrapperメソッドを使ってdeviation情報をDeviationInstanceWrapperオブジェクトに追加します.ちなみに,現バージョンではpartwise deviationは未実装なので,それを追加するメソッドは用意されていません.

このAPIリファレンスドキュメントをもっと注意深く見てみると,コンストラクタの記述がないことがわかります.実は,このクラスのコンストラクタはpackage private(デフォルトのスコープ)になっているため,jp.crestmuse.cmx.filewrappersパッケージ以外からはコンストラクタはアクセスできないようになっています.では,このクラスのインスタンスはどうやって生成するのでしょうか.実は,DeviationInstanceWrapperにその方法が用意されています.createDeviationDataSetというメソッドです.つまり,

DeviationInstanceWrapper dev = (DeviationInstanceWrapper)
    CMXFileWrapper.createDocument(DeviationInstanceWrapper.TOG_TAG)
DeviationDataSet dds = dev.createDeviationDataSet();
とすることで,DeviationDataSetオブジェクトが得られます.

DeviationInstanceXMLドキュメントを生成してみよう:テンポを指定する

DeviationInstanceXMLドキュメント生成の基本的な考え方を学んだところで,ごくごく簡単なDeviationInstanceXMLドキュメントを生成してみましょう.ここでは,単にテンポを指定するというのをやってみます.

プログラムの大枠を書いてみましょう.

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

public class MyDeviationTest1 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();
    // ここで DeviaionDataSet クラスのメソッドを使ってdeviation情報を追加
    // 続きの処理
  }

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

いままでの説明を丁寧に読んでいけば,上記のプログラムは一通り理解できるはずです(runメソッドに見慣れないthrows指定があるのを除けば).では,テンポの指定をnon-partwise deviationとして追加しましょう.これは次のように行います.

dds.addNonPartwiseControl(1, 1, "tempo", 120.0);
これは1小節めの1拍めに"tempo"という名前のnon-partwise controlを挿入し,その値を120.0に設定するという意味です.

ここでは最も簡単な例ということで,これでdeviation情報の追加を終わりにします.DeviationDataSetオブジェクトにすべてのdeviation情報を追加したら,その情報をDeviationInstanceWrapperオブジェクトに書き込みます.これは次のようにして行います.

dds.addElementsToWrapper();
どのDeviationInstanceWrapperオブジェクトに書き込むかは指定していませんが,これはDeviationDataSetオブジェクトを生成したDeviationInstanceWrapperオブジェクトに書き込むようになっています(DeviaionDataSetオブジェクトはDeviationInstanceWrapperオブジェクトから生成するようになってましたよね!).最後に,
setOutputData(dev);
として,devというDeviationInstanceWrapperオブジェクトを出力用オブジェクトに指定しています.こうすると,このdevというオブジェクトの中身が画面に出力されます.プログラム全体を以下に示します.
import jp.crestmuse.cmx.commands.*;
import jp.crestmuse.cmx.filewrappers.*;
import javax.xml.parsers.*;
import org.xml.sax.*;

public class MyDeviationTest1 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);
    dds.addElementsToWrapper();
    setOutputData(dev);
  }

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

では,このプログラムを実行してみましょう.

%java MyDeviationTest1 renconsample-s001.xml
このプログラムの実行では,MusicXMLファイルを引数に与えます.次のような実行結果が得られるはずです.
[renconsample-s001.xml]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE deviation PUBLIC "-//CrestMuse//DTD CrestMuseXML 0.20 DeviationInstanceXML//EN"
                           "http://www.crestmuse.jp/cmx/dtds/deviation.dtd">
<deviation>
  <non-partwise>
    <measure number="1">
      <control beat="1.0">
        <tempo>120.0</tempo>
      </control>
    </measure>
  </non-partwise>
  <partwise/>
  <notewise/>
</deviation>

生成したXMLドキュメントをファイルに保存する

これまで作成したプログラムの実行では,処理対象となるXMLファイルを引数に渡していましたが,CMXCommandを継承して作成したプログラムでは,オプションを指定することができるようになっています.たとえば,

%java MyDeviationTest1 -o output.xml renconsample-s001.xml
とすれば,いままで画面に出力していた処理結果をファイル(output.xml)に出力することができます.また,
%java MyDeviationTest1 -d devfiles renconsample-s001.xml
とすれば,処理結果をdevfilesという名前のディレクトリに保存します.ファイル名はrenconsample-s001.xmlのままです.
%java MyDeviationTest1 -ext devx renconsample-s001.xml
とすれば,入力ファイル名の拡張子をdevxに置き換えた名前(この場合はrenconsample-s001.devx)で処理結果を保存します.
%java MyDeviationTest1 -d devfiles -ext devx renconsample-s001.xml
というように-dオプションと-extオプションを併用することも可能です.CMXCommandを継承して作成したプログラムでは,処理対象のファイルを複数指定することが可能ですが,-oオプションを利用してしまうとすべてのファイルに対する処理結果を同じファイルに上書き保存してしまうので,注意してください.

生成したDeviationInstanceXMLドキュメントをSMFに変換する

生成したDeviationInstanceXMLドキュメントをSMFに変換するためのコマンドがすでに用意されています.いま,

%java MyDeviationTest1 -ext devx renconsample-s001.xml
としてrenconsample-s001.devxという名前でDeviationInstanceXMLドキュメントを保存したとすると,
java jp.crestmuse.cmx.commands.ApplyDeviationInstance -smf renconsample-s001.mid renconsample-s001.devx
とすると,renconsample-s001.midという名前でSMFとして演奏情報を保存することができます.これを実行するにはMusicXMLファイルとDeviationInstanceXMLファイルの両方が必要で,ApplyDeviationInstanceコマンドの引数ではDeviationInstanceXMLファイルだけ指定します.MusicXMLファイルの名前は,指定されたDeviationInstanceXMLファイルのtarget属性から取得します.MusicXMLファイルの置き場所を移動したなど,実際の置き場とtarget属性での指定が食い違ってしまったときは,-targetオプションを使ってMusicXMLファイルのパスを指定できます.

おわりに

今回は,DeviationInstanceXMLドキュメントの生成方法を学び,MusicXMLファイルとDeviationInstanceXMLファイルの組からSMFを生成する方法も学びました.上のサンプルプログラムのテンポの値をいろいろ変えてみて,それが演奏(SMF)に反映されていることを確認しましょう.次回は,もう少し凝ったDeviationInstanceXMLドキュメントを扱います.