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

[以前書いた記事](https://qiita.com/comp_to88_ly/items/e00feaf5b89dcb4c635a)にてコンパイルが確率的にしか通らないという問題があり、原因を解析してみた結果、「そんなのわかるかよ！」と思わず言いたくなりかねないものだったため、記事に残します。ホント、[LilyPond](http://lilypond.org/)が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](https://qiita-image-store.s3.amazonaws.com/0/211391/9ec6bd26-bfd7-9bee-1dea-c8d9bcb609e9.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.stencil```に```beam-segments```の中身を表示するようなコードを仕込んでいろいろと試してみると面白いと思います。なお、ソースコード的なことを言いますと、```beam.cc```内の```Beam::print```中の```extreme```という変数が基準となる桁のインデックスを持っている形になります。
[^3]: 運悪くこれらが返ってくると計算結果が非数になり、最終的に```void Stencil::translate (Offset)```にて目出度く（？）```programming error```が発生します。