LoginSignup
3
0

More than 5 years have passed since last update.

LilyPondでListのプロパティ(特にInternalなやつ)をいじる際の注意点

Posted at

以前書いた記事にてコンパイルが確率的にしか通らないという問題があり、原因を解析してみた結果、「そんなのわかるかよ!」と思わず言いたくなりかねないものだったため、記事に残します。ホント、LilyPondがOSSでよかった…。
結論を先に言ってしまいますと、当たり前ではありますが、並び順に意味がある場合、崩すと予期せぬ動作を引き起こすので注意しましょう、という話です。

確率的に失敗する例

以下のコードを何度かコンパイルしてみると、たまにprogramming error: Improbable offset for stencil: -nan staff spaceが発生します1

#(define (shift-vertical-count amount)
  (lambda (beam)
   (let
    ((orig-segments (ly:beam::calc-beam-segments beam)))
    (map-in-order
     (lambda (segment)
      `((vertical-count . ,(+ (assoc-ref segment 'vertical-count) amount))
        (horizontal . ,(assoc-ref segment 'horizontal))))
     orig-segments))))

\score{
  \new Staff \relative c'{
    \override Beam.beam-segments = #(shift-vertical-count -1)
    \repeat unfold 3 {
      c8[ d e f]
    }
  }
}

test2_beaming.cropped.png

8分音符を繋げる場合、通常はvertical-countが0の桁になるのですが、上のコード例では、無理矢理vertical-countが-1の桁に変更しています(画像は上のコードで、2番目のbeamにてprogramming errorが出た場合)。
さて、どこが悪いのでしょうか? 結果的には-1という値が悪さをしているのですが、どんな悪さをしているのかは上のコードのみを睨んでもわからない筈です。というのは、これ、上述の-1という値がbeam.ccにあるSCM Beam::print (SCM)関数内でvector::operator[]経由の配列外参照(-1番目の要素へのアクセス)を引き起こす、という話だからです。

何が起きているのか

ポイントとなるのは、Beam.beam-segmentsは傾きの情報や具体的なY座標等を持っていないという事実です。Beam::print(あるいは、同じことですが、ly:beam::print)にて、印字する際に基準となる桁を拾って各種計算が行われており、基準となる桁は、beam-segmentsの先頭の要素のvertical-countが0ならばその要素、そうでなければ、{末尾の要素のvertical-count}番目の要素となります2。大抵の場合、基準はvertical-countが0の桁であり、これが正しく拾われるようにbeam-segmentsが適切にソートされている前提でBeam::printは組まれていました。もちろん、存在しないと拾うことすらできません。上に上げたコード例では、先頭のvertical-countが0ではないため、末尾のvertical-countを見て、beam-segmentsの-1番目の要素(そんな要素がなくても、vector::operator[]なので読み出されてしまいます)を基準にしようとしています。実はfeathered beamの設定が無いと基準の桁から拾った値に0がかけられてしまうので、変な値が返ってきたところで影響はありません…運悪く無限大やら非数が返ってこなければ3。普通は全ての桁のvertical-countが0未満ということはありませんので、最低限beam-segmentsの最後の要素だけなんとかしておけば、変な落ち方はしなくなります。万一、全てのvertical-countが0未満のBeamが必要だという場合には、Beam.stencilに設定する関数を頑張ってこしらえてください。

で、以前のトレモロのスニペットはどう直せば良いのか

以前のスニペットは以下の形でした:

\override Beam.beam-segments
= #(lambda (beam)
    (let
     ((segments (ly:beam::calc-beam-segments beam)))
     ;; fで順序が狂う
     (f segments)))

なので、以下のように修正すればOKな筈です:

\override Beam.beam-segments
= #(lambda (beam)
    (let
     ((segments (ly:beam::calc-beam-segments beam))
      (direction (ly:grob-property beam 'direction)))
     ;; やっつけ感が酷いけれども、こんな感じで大丈夫
     (sort
      (f segments)
      (lambda (x y)
       (let*
        ((edge (if (eqv? direction UP) cdr car))
         (lesser? (if (eqv? direction UP) > <))
         (vx (assoc-ref x 'vertical-count))
         (vy (assoc-ref y 'vertical-count))
         (hx (edge (assoc-ref x 'horizontal)))
         (hy (edge (assoc-ref y 'horizontal))))
        (or
         (< vx vy)
         (and
          (eqv? vx vy)
          (lesser? hx hy))))))))

  1. 理論的には\repeatで繰り返す必要は無い筈なのですが…\repeatなしではなかなか発現してくれませんでした。 

  2. 興味のある方は、適当にBeam.stencilbeam-segmentsの中身を表示するようなコードを仕込んでいろいろと試してみると面白いと思います。なお、ソースコード的なことを言いますと、beam.cc内のBeam::print中のextremeという変数が基準となる桁のインデックスを持っている形になります。 

  3. 運悪くこれらが返ってくると計算結果が非数になり、最終的にvoid Stencil::translate (Offset)にて目出度く(?)programming errorが発生します。 

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0