doubledepth

MID2BMSON

MIDIをBMSON図表に変換する凄いやつ。MIDI情報と指示書(JSON)が適切であれば「音階単位でのnotesの整列」が一瞬で完了する。MIDI情報が既に存在する場合は図表上での音階配置は二度手間なので、この変換器は24 keys図表に限らず有用だろう。特に役立つ状況はやはりKEYBOARDMANIA系の図表作成。「MIDI情報を参考に音声を裁断するBMS用slicers」とMID2BMSONを連携するのも面白そう。

BMSONの"mode_hint": "keyboard-24k"はいまのところbeatorajaとQwilightでsupportされている。この変換器によって生成された図表はどちらの機種でも問題なく演奏される。MIDI側で音階が合わせてあれば"keyboard-24k-double"も出力できるだろうし、指示書を"keys_count: 7"に変更すれば"beat-7k"図表にも対応できる。音階を守った結果が縦連打wwwwトポォtopologywww

いまのところ生成された"y"の値にはもれなく.0が付加される。BMSON仕様への準拠度が高いPulsusなどの実装ではerrorが発生して演奏できないので、不安な人は生成物をBmsONEで保存し直すと吉。

引っ張ってきた音階の情報を各notesに非標準propertyとして埋め込む手もあるかも。たとえばMIDI上で音階がC4として設定されているnoteなら、BMSON側に"note_number": 60のように参考値を生やしておく。すると差分図表を作る際などにその情報を参照して、後から誰でも何度でも機械的に"x"を整列できるようになる。こういったscriptingが簡単なのもBMSON形式の強みだと思う。


MID2BMSON公開から一時間くらいでたれこんでくださった方、ありがとうございました!

まだsweaterを着ている季節なのに蚊がうるさい

書きかけscriptに“オートメーション工場”をdrag-and-dropしてから分岐viewerが描画されるまでの時間が13秒から縮まらない。2分半かかっていた一箇月前からは12倍高速化できたが、なんというか余計な処理時間137秒を余所に分散させただけなので、根本的な遅さが変わっていない……

bndlr v0.1.0 致命的なbugが修正されたv0.1.1以降を再導入推奨

Electron製のBMS install managerBMS公開者は、BMS作品の各種meta情報(書庫の更新内容など)を詳細化して、(BMS書庫とは別に)Webpage上にJSON Schema形式で公開しておくことができる。Usersはこのapplicationを経由して、(それらのJSON SchemaがBMS download pageから読み取れる場合に)対象BMS作品を効率的にdownload・展開・分類・管理することができる。

bndlrを起動すると、applicationが常駐する。初回起動時はLR2 custom folderなどの設定を促される。LR2以来のBMS ecosystemに慣れ親しんだusersなら、設定項目を見れば何が必要なのか理解できるだろう。個人のwebpageでもBMS Bundle Manifestを利用できるが、BMS event会場などでこれらの情報が整備されるようになれば、eventの規模が大きくなればなるほど効果を発揮しそう。たとえば書庫の更新内容がBMS本体のそれなのか、修正差分なのか、追加差分なのか、明示的に区別できたりする。

個人でmeta情報を整備したい場合は、いまのところbms-bundle-manifest-jsを利用するとよいようだ。これはnpmがNode.jsのpackage managerであることを理解できるuser向けだろうから、よくわかっていない私は近日中に公開される予定のonline validatorを待って、実際にJSONを書いてみるつもり。


(素早くたれこんでくださった匿名さん、ありがとうございました!)

残便感

先日の愚鈍な文字列操作を数値操作に置き換えて100000回実行してみた。

[JavaScriptのNumber型専用] bitwise演算子もどき:)
var Bitwise = function () {};
Bitwise.prototype.p = (function () {
    var a = [];
    var i = 0;
    while (i < 33) {
        a.push(Math.pow(2, i)); // 2 ** i
        i += 1;
    }
    return a;
}());
Bitwise.prototype.toInt32 = function (x) {
    var n = Number(x);
    var neg;
    if (!isFinite(n) || n === 0) { // for IE
    //if (!Number.isFinite(n) || n === 0) {
        return 0;
    }
    if (n < 0) {
        neg = true;
        n = -n;
    }
    n = Math.floor(n) % this.p[32];
    if (n > this.p[31]) {
        n -= this.p[32];
    }
    return (
        neg
        ? -n
        : n
    );
};
Bitwise.prototype.toUint32 = function (x) {
    var n = Number(x);
    var neg;
    if (!isFinite(n) || n === 0) { // for IE
    //if (!Number.isFinite(n) || n === 0) {
        return 0;
    }
    if (n < 0) {
        neg = true;
        n = -n;
    }
    n = Math.floor(n);
    if (neg) {
        n = -n;
    }
    n = n % this.p[32];
    return (
        (n < 0)
        ? this.p[32] + n
        : n
    );
};
Bitwise.prototype.toBinary = function (int32) {
    var a = [];
    var i = 0;
    var p = int32;
    var neg;
    var carry;
    if (p < 0) {
        neg = true;
        p = -p;
    }
    while (p >= 2) {
        a[i] = p % 2;
        i += 1;
        p = Math.floor(p * 0.5);
    }
    a[i] = p;
    i += 1;
    while (i < 32) {
        a[i] = 0;
        i += 1;
    }
    if (!neg) {
        return a;
    }
    i = 0;
    while (i < 32) {
        a[i] = Number(!a[i]);
        i += 1;
    }
    a[32] = 0;
    i = 0;
    do {
        carry = a[i];
        a[i] = Number(!a[i]);
        i += 1;
    } while (carry);
    a.pop();
    return a;
};
Bitwise.prototype.not = function (num) {
    var a = this.toBinary(this.toInt32(num));
    var i = 32;
    var n = 0;
    var p = this.p;
    while (i) {
        i -= 1;
        n += p[i] * (!a[i]);
    }
    return this.toInt32(n);
};
Bitwise.prototype.and = function (l, r) {
    var L = this.toBinary(this.toInt32(l));
    var R = this.toBinary(this.toInt32(r));
    var p = this.p;
    var i = 32;
    var n = 0;
    while (i) {
        i -= 1;
        n += p[i] * (L[i] && R[i]);
    }
    return this.toInt32(n);
};
Bitwise.prototype.or = function (l, r) {
    var L = this.toBinary(this.toInt32(l));
    var R = this.toBinary(this.toInt32(r));
    var p = this.p;
    var i = 32;
    var n = 0;
    while (i) {
        i -= 1;
        n += p[i] * (L[i] || R[i]);
    }
    return this.toInt32(n);
};
Bitwise.prototype.xor = function (l, r) {
    var L = this.toBinary(this.toInt32(l));
    var R = this.toBinary(this.toInt32(r));
    var p = this.p;
    var i = 32;
    var n = 0;
    while (i) {
        i -= 1;
        n += p[i] * (L[i] !== R[i]);
    }
    return this.toInt32(n);
};
Bitwise.prototype.leftShift = function (l, r) {
    var L = this.toBinary(this.toInt32(l));
    var R = this.and(this.toUint32(r), 31);
    var p = this.p;
    var n = 0;
    var i = 0;
    var B = [];
    // L = new Array(R).fill(0).concat(L).slice(0, 32);
    while (i < R) {
        B[i] = 0;
        i += 1;
    }
    while (i < 32) {
        B[i] = L[i - R];
        i += 1;
    }
    while (i) {
        i -= 1;
        n += p[i] * B[i];
    }
    return this.toInt32(n);
};
Bitwise.prototype.rightShift = function (l, r) {
    var L = this.toBinary(this.toInt32(l));
    var R = this.and(this.toUint32(r), 31);
    var p = this.p;
    var n = 0;
    // L = L.concat(new Array(R).fill(L[31])).slice(-32);
    var B = [];
    var padd = L[31];
    var i = R;
    while (i < 32) {
        B[i - R] = L[i];
        i += 1;
    }
    i = B.length;
    while (i < 32) {
        B[i] = padd;
        i += 1;
    }
    while (i) {
        i -= 1;
        n += p[i] * B[i];
    }
    return this.toInt32(n);
};
Bitwise.prototype.unsignedRightShift = function (l, r) {
    var L = this.toBinary(this.toUint32(l));
    var R = this.and(this.toUint32(r), 31);
    var p = this.p;
    var n = 0;
    // L = L.concat(new Array(R).fill(0)).slice(-32);
    var B = [];
    var i = R;
    while (i < 32) {
        B[i - R] = L[i];
        i += 1;
    }
    i = B.length;
    while (i < 32) {
        B[i] = 0;
        i += 1;
    }
    while (i) {
        i -= 1;
        n += p[i] * B[i];
    }
    return this.toUint32(n);
};


if (typeof Object.create !== "function") {
    Object.create = function (proto) {
        var F = function () {};
        F.prototype = proto;
        return new F();
    };
}


var Random = function (seed) {
    this.x = 123456789;
    this.y = 362436069;
    this.z = 521288629;
    this.w = seed || 88675123;
};

//Object.assign(Random.prototype, Bitwise);
Random.prototype = Object.create(Bitwise.prototype);

Random.prototype.next = function () { // Xorshift128
    var t = this.xor(this.x, this.leftShift(this.x, 11));
    this.x = this.y;
    this.y = this.z;
    this.z = this.w;
    this.w = this.xor(
        this.xor(this.w, this.unsignedRightShift(this.w, 19)),
        this.xor(t, this.unsignedRightShift(t, 8))
    );
    return this.w;
};
Random.prototype.nextInt = function (min, max) { // from min to max
    return min + (Math.abs(this.next()) % (max + 1 - min));
};

Bitwise classは元々ただのobjectをObject.assign()していたが、Internet Explorerで使えなかったのでObject.create()に変更した。共通部分を別関数に切り出していた箇所は、inliningによって400 msくらい速くなった。効果がその程度ならinliningしないほうが良さそう。

元の愚鈍な文字列操作版からは2.5倍から8倍ほど高速化することができたが、Firefoxはまだ遅い。ここからさらに高速化するにはどうすればいいだろうか。私に思いつけたのは配列を使う素朴な方法だけだったが、その配列がなんか遅いというか、根本的に(私が)賢くないというか、もっといけてるalgorithmがありそう。あとは真偽値だけを使うことで実行時最適化に期待する、とかだろうか。


pseudo-bitwise Xorshift
BrowserElapsed
Vivaldi
3.7.2218.52
447 ms
Firefox 871653 ms
Internet Explorer
6.0.2800.1106
57843 ms

vs.

real bitwise Xorshift
BrowserElapsed
Vivaldi
3.7.2218.52
8 ms
Firefox 877 ms
Internet Explorer
6.0.2800.1106
547 ms

試す前から結論は明らかだったが、IE6のXorshiftが予想外に健闘していて驚いた。古い環境でもbitwise演算子を普通に使っていけるのでは……? いや、Xorshiftが凄いのかな?

どこで使うのかわからなかったmethodを私は奇跡的に思い出すことができた。

Random.prototype.next = function () { // Xorshift128
    this.x[4] = this.x[0] ^ (this.x[0] << 11);
    // this.x[0] = this.x[1];
    // this.x[1] = this.x[2];
    // this.x[2] = this.x[3];
    this.x.copyWithin(0, 1, 4);

Bitwise Shift演算子っぽい動きは、このmethodを使えばだいぶ短く書けそう。

真似び日記

XorshiftJavaScript版のTypedArray版を書いて、乱数を100000回生成するまでの時間を比較したところ、どちらも20 milli­secondsで処理が完了した。昔は「JavaScriptのNumber型に対するbitwise演算」は避けるべきとされていたようだが(入り口と出口にtoInt32toUint32が挟まるので遅かったらしい)、現代の実行環境は問題なく最適化してくれるようだ(あるいはTypedArray遅い?)。

(TypedArray版:)
const Random = function (seed = 88675123) {
    this.x = Uint32Array.of(123456789, 362436069, 521288629, seed, 0);
};
Random.prototype.next = function () { // Xorshift128
    this.x[4] = this.x[0] ^ (this.x[0] << 11);
    this.x.copyWithin(0, 1, 4);
    this.x[3] = (this.x[3] ^ (this.x[3] >>> 19)) ^ (this.x[4] ^ (this.x[4] >>> 8));
    return this.x[3];
};
Random.prototype.nextInt = function (min, max) { // from min to max
    return min + (Math.abs(this.next()) % (max + 1 - min));
};

Xorshift128、行列内容を全部0にするのは厳禁」だが、それ以外なら何でもいいらしい。そこは変更する理由はないのでseedはそのままにした。計算途中の一時的な値もTypedArrayの外に出した時点でtoInt32が適用されてしまうらしいので、元の一時変数tはTypedArray要素に置き換えた。


TypedArray版の実行結果はNumber版とは微妙に異なる(C言語版と同じ結果になると思われる)。どちらも2進数表現としては等価だが、JavaScriptのNumber型に対するbitwise演算は符号付き32-bit整数にcastingされる。既定値のseedで乱数を10回生成した結果は以下の通り:

const random = new Random(); // seed: default parameters (= 88675123)
random.next(); // 0b11011100101000110100010111101010 //  -593279510 or 3701687786
random.next(); // 0b00011011010100010001011011100110 //   458299110
random.next(); // 0b10010101000100000100100110101010 // -1794094678 or 2500872618
random.next(); // 0b11011000100011010000000010110000 //  -661847888 or 3633119408
random.next(); // 0b00011110110001111000001001011110 //   516391518
random.next(); // 0b10001101101100100100000101000110 // -1917697722 or 2377269574
random.next(); // 0b10011010111110000001010001000011 // -1695017917 or 2599949379
random.next(); // 0b00101010110000000000111100101100 //   717229868
random.next(); // 0b00001000001101111010110101011000 //   137866584
random.next(); // 0b00010111100100000110010101101001 //   395339113

このようにNumber版には負数が混じるため、絶対値を得るnextInt(min, max)では結果が変わる。

// random.nextInt(1, 1024) x 30:
// Number:     [535, 743, 599, 849, 607, 699, 958, 813, 345, 362, 469, 550, 273, 746, 653, 812, 53, 535, 333, 903, 169, 1013, 173, 601, 866, 922, 606, 737, 795, 916, 358]
// TypedArray: [491, 743, 427, 177, 607, 327,  68, 813, 345, 362, 469, 550, 753, 746, 373, 214, 53, 535, 333, 903, 169,   13, 173, 425, 160, 922, 606, 737, 795, 916, 358]

確率分布としてはどちらも大差なさそうに見えるが、よくわからない。“オートメーション工場”の一部の分岐のように「#RANDOM 1024、ただし対応する枝は#IF 16まで」みたいな場合は困るかも。いや、剰余を使うならべつに問題ないんじゃないかな…… ん〜〜まあ無難にC言語版に寄せておくか。

Linterがbitwise演算子に目くじらを立てるので、私は「XorもShiftも使わないXorshift」も書いてみた。Number型を32-bit整数様に整形し、それを"0""1"が並ぶ長さ32の文字列に変換し、それを一文字ずつ操作して書き戻す愚鈍な実装だ。100000回の乱数生成に4318 msを要した。速度面は元の版に及ぶべくもない(ゆえに実用性もない)が、Number型に対するbitwise演算の動きは理解できた。

(以下のように使えた:)
bitwise.not(-1234567.89);                   // 1234566
bitwise.and(-1234, -567.89);                //   -1784
bitwise.or(-1234, -567.89);                 //     -17
bitwise.xor(-1234, -567.89);                //    1767
bitwise.leftShift(-1234, -567.89);          // -631808
bitwise.rightShift(-1234, -567.89);         //      -3
bitwise.unsignedRightShift(-1234, -567.89); // 8388605

文字列を使わず数値型のままbitmaskとやらを行えば、たぶん10倍以上高速化できそうな気がするが、実用性が皆無とわかりきっているにこれ以上手を入れるのはさすがに不毛かな〜


Linterがbitwise演算子を戒める理由は、論理演算子の書き間違いに見えるかららしい。未来の私は~~-567.89-567.89|0などを読めないに違いないと思うので、私自身はこういう書き方は避けたいが、他人が読むわけでもないcodeなら自信ニキは好きに書けばいいのでは。あとXorshiftのようにbitwise演算が必須なalgorithmであれば、linterが何といおうと普通にbitwise演算子を使うべきだろう。

BMSの分岐解析を高速化した

私の書きかけscriptは、“オートメーション工場”を読み込むと154秒間操作不能状態に陥っていた。この致命的な硬直時間は、refactoringによって3秒まで短縮された。UIの描画完了までは数十秒を要するが、待機中もbrowserを操作できるようになった。私は頑張った。UI描画時間ももっと縮めたい。

Web browsersのJSON.stringify()は入れ子が深すぎると死ぬ

以下の雑なHTMLで入れ子の深さを適当に書き換えてWeb browsersで開く。
<!DOCTYPE html>
<meta charset="utf-8">
<title>JSON nest3104</title>
<style>
pre {
    inline-size: 100%;
    white-space: pre-wrap;
}
</style>
<body>
<h1>JSON nest3104</h1>
<pre id="TEST"></pre>
<script>
var getProperty = function (object, propertyPath) {
    // https://qiita.com/standard-software/items/bb044217d0a4b394b8e2
    if (!object) {
        return undefined;
    }
    var result = object;
    var propertyArray = propertyPath.slice(1, -1).split("][");
    var i = 0;
    var l = propertyArray.length;
    var property;
    var quot = "\"";
    while (i < l) {
        property = propertyArray[i];
        if (
            property.length > 2
            && property.charAt(0) === quot
            && property.slice(-1) === quot
        ) {
            property = property.slice(1, -1);
        }
        // bracket記法なら空文字列は妥当なkeyのはずなのでcommented out
        // if (property === "") {
        //     return undefined;
        // }
        if (typeof result[property] === "undefined") {
            return undefined;
        }
        result = result[property];
        i += 1;
    }
    return result;
};

(function () {
    "use strict";
    var maxlevel = 3104;
    var rxCancel = new RegExp(new Array(100 + 1).join(" "), "g");
    var p1 = "{\"type\":\"div\",\"attr\":{\"depth\":";
    var p2 = "},\"children\":[";
    var p3 = "]}";
    var c = "[\"children\"][0]";
    var json;
    var result = [];
    var q = [];
    var query;
    var i = 0;
    var l = maxlevel / 2;
    while (i < l) {
        result.push(p1 + (1 + i * 2) + p2);
        q.push(c);
        i += 1;
    }
    q.pop();
    while (i) {
        result.push(p3);
        i -= 1;
    }
    json = JSON.parse(result.join(""));
    query = q.join("") + "[\"attr\"][\"depth\"]";
    document.getElementById("TEST").appendChild(
        document.createTextNode(
            String("")
            // + JSON.stringify(json, null, 1).replace(rxCancel, "")
            + "\n\njson" + query + ": \n"
            + JSON.stringify(getProperty(json, query), null, 1)
        )
    );
}());
</script>

一個目のJSON.stringify()がcommented out状態なら、computerのmemoryが許す限り入れ子できそう。私は30000000層までは実際にpropertyにaccessできることを確認した。

一個目のJSON.stringify()を有効化している状態で、深すぎる入れ子のJavaScript Objectが入力されると、scriptが停止する。手元の64-bit Windows用Firefox 87.0は3105層以上で停止し、Vivaldi 3.7.2218.49は5017層以上で停止した。非再帰版JSON parserを自前で実装すれば、この問題は解決できそうだが、巨大なJSONに立ち向かうためにはそもそもjqのような入出力streamが必要なのかも。一応Streams APIとか既にあるし、Oboe.jsなるものも見つけた。“bolero”を131080回繰り返すBMSON図表も読み込めるようにできるだろうか?

User Interface粗描中

ほぼ“オートメーション工場”でのみ発生する致命的な問題をずっと放置していたが、解決できそうな方法を2つ見つけてしまった。見つけたくなかった……

まあ淡々と試行錯誤するのみ。

原因不明のerrorで詰まったので今日は寝る

書きかけのcodeを忘れきる前に続きに取り掛かれたのでよかった。

Random seedといえば

BmsToAviがRandom seedを受け付けるsoftwareだった。

2000. 2. 3 AM  1:38
Ver 0.00a9
    ランダムローディング時の乱数列の初期化値を与えられるようにした
    0が本当にランダム それ以外は設定とみなす

“Seed (= #SETRANDOM) (but buggy)”私は少なくとも2010年時点では「乱数列の初期化値」を#SETRANDOMと勘違いしており、この勘違いは十年以上もの間、正されなかった。な〜にが“but buggy”だ、そんなことを書く私こそが虫、そう私は虫螻蛭螾……

情報系のliteracyが皆無な私はともかく、二十一世紀の義務教育課程を経た方々なら乱数のseed値という概念は普通に理解できそう(“65535”とかと同じ雰囲気で)。

ランダム譜面ジェネレーター

JavaScriptで無作為に図表を生成するscript。個人的にはseedを指定できるところが特に興味深かった。BMSでrandom seedを意識できる状況としては、LR2body.exeにBMS fileを直接drag-and-dropする場合などがある(seedが固定され、#RANDOMやRANDOM optionの結果が常に一定になる)。生成器なら再現性はあったほうが便利かもしれない、なるほどな〜、と感じ入ったので真似させていただく予定。

いまのところ細かい不具合が多いように見えるが、どれも致命的ではなさそう。気になる方はOffline版を手元で各自修正すると良さそう。最後のoutput関数からjQueryを外せば一応動作する。あとはHTMLのtype="text" type="number"type="number"に全置換したり、お好みで。

Qwilightを自動更新できない

このアプリ パッケージは、信頼できる証明書で署名されていません。(0x800B010A)

BMSツクール Ver2.07

XTRM Runtimeの詳細をどこかにmemoしていないか検索した際、やばいWAV fileに関する情報がついでに発掘された。問題のback_bgm.wavWindows Vista–8.1のExplorerを実際に応答不能状態にするTask ManagerからExplorerを再起動するなどしないと復帰できないうえ、このfileの移動や削除はCommand Prompt経由でしか行えない。現在のWindows 10では対策されている。

肝心のツクールはWAV slicing(のmarkers移動操作)がUndo/Redo可能になった。


μBMSCが特定の音声を再生しようとすると強制終了するらしいということを小耳に挟んだ。教えてくださった方、ありがとうございます。いまのところ心当たりはないが気をつけてみます。iBMSC系はcomputerのspecが不足しているといろいろ大変で、具体的には垂直軸のhover描画処理が異様に遅れたりするので、負荷の高い処理が輻輳するとヤバいとかありそう。

XTRM Runtime v10.0

Visual Basic 6.0 IDE サポート終了後に実施された2012年・2016年の累積的なセキュリティ更新に対応しており、フリーソフトとしては現在安全に利用できる唯一のランタイムパッケージです。

同梱文書の通り、このprogramをinstallすれば手軽に最新のVB6動作環境が得られる。BMSEが必要とするCOMDLG32.OCXおよびMSCOMCTL.OCX最新版を得るために、煩雑な手順を踏む必要はもはやない。これを受けてBMSE非公式Help重量版も微更新した。

またもや勘違う

BMSChecker作者です。

BMSChecker v1.2.0で"「重複定義による音量調整」の検出が出来なくなった"件について、これに該当する機能に心当たりが無いのですが、以前はどのように利用されていたのでしょうか。

はじめまして。そしてごめんなさい、件の機能は私の勘違いでした。たとえばteletroit#041以降で「重複定義による音量調整」を行っていますが(各小節BGMの16–17列目)、v1.1.0(v1.0.1)を改めて試したところ、BGM16–17列目のnotesは一個も列挙されませんでした。“Diff”で検出されたerrorsの番号を私が見間違えただけだったようです…… 不確かな情報で戸惑わせてしまったことをお詫びいたします。

「こんなところまで検出してくれるなんて凄いな〜」と感心した記憶がはっきり残っているのがおそろしい…… 私は自分の経験をまったく信じられない……

あの日私は悔し紛れに#ENSDWに対応した

bms-language-supportですが、VScodeの構文解析が基本RegExpなので、閉じタグがない状態をうまく記載する方法が思いつかなくて…

(釈迦の耳に説法でしょうけれど)#IF(#ELSEIF#ELSE)–#ENDIFのSyntax highlightingは期待通りに働いているように見えるので、これをほぼそのまま転用できるのではないでしょうか?[追記] 転用できませんね! ごめんなさい!

妥当な#IF-#ELSEIF-#ELSEのサンプルコード。

#TITLE SWITCH
#BPM 130
#SWITCH 3
#CASE 1
#00111:11
#CASE 2
#00212:22
#SKIP
#DEF
#00313:33
#ENDSW
#00115:55
  • #IF#SWITCHに置き換える。[追記] これは無理ですね!?
  • #ELSEIF#CASEに置き換える。
  • #ELSE#DEFに置き換える。
  • #ENDIF#ENDSWに置き換える。

この場合に問題となりそうなのは、#SWITCHから最初のlabelまでの間のnode#SWITCHのscopeにあるが#SWITCH blockの直下にない#SKIP他の#CASE labelsよりも上に書かれた#DEF、の取り扱いでしょうか。そのあたりまで正規表現だけでどうにかできるのか私には分かりませんが、他の言語のrulesetも正規表現で書かれているなら参考にはできそう。あと先日の当日記のBMS例に閉じtagがなかったのは私の書きかけparserが緩いだけであって、真っ当なvalidatorなら赤く染めるのが正しそうです。

返信を書きながらまたひとつ愚かなBMS codeを思いつきました。ありがとうございました。
#TITLE else first
#BPM 130
#RANDOM 3
#ELSE
#00313:33
#IF 1
#00111:11
#ELSEIF 2
#00212:22
#ENDIF
#ENDRANDOM
#00115:55

私の書きかけparserは、#IF-#ENDIFを#ELSE block配下の入れ子とみなす。

入れ子あり・かつ終端符を補う拙作parserだとこうなるのもわかるけど、とはいえこれを見ていると「突然の#ELSE」を無条件で丸吞みするのはヤバなのでは……って思えてくる。“BABA IS YOU”だと突然のELSEは「該当条件なし」を意味するのだったか。私もそうすべきかな。nanasigroove1の場合、#ELSEは必ず選ばれ、あとは二択になる。Codeの動きがわからない。まあ「突然の#DEF」ならともかく、「突然の#ELSE」がどう解釈されても図表著者はどうこういえない。

[追記] Codeの動きがたぶん分かった。nanasigroove1は#ELSEIFをsupportしていないので、#ELSEIF 2はただの#ELSEとして解釈される、っぽい。

#TITLE void
#BPM 130
#SWITCH 1
#00111:11
#ENDSW

IIDXvはこういうのをきちんと叱ってくれるので良い。

BMSChecker v1.2.0

NOTICE: Notes without keysound exist: 100.0%

などと知らせてくれるようになった。「重複定義による音量調整」を(それをerrorとみなすかどうかはともかく)検出してくれるのは微妙に便利だったような気もするが、今回からそれができなくなった。まあ、適当に手元でscriptを書けば解決できるか。[追記] そんな機能は最初からなかった。私の勘違いでした。ごめんなさい。

食べ過ぎて何も考えられない

 日本語と英語以外は自信無いのと需要も分からないので、どうにもやる気が起きません……

 気付かない部分の修正とかは投げてくれる人がいれば助かるんですが

私からすると第二言語を扱える方々はそれだけで超人です! 多言語対応appsの多くは未訳部分を英語のまま公開しているように見えます。μBMSCの場合は言語fileに要素が存在しなければ他の言語にfallbackしてくれるっぽいので、英語xmlを用意していただけたところまでで必要十分といいますか、あとはできるマンにお任せしてもまったく問題ないように思います。

#IF#ENDIF#SWITCH#ENDSWで階層を上げ下げしているらしき箇所が気になって仕方がないので、block内容をmainpanelに描画せず拡張painに隔離する修正とか投げたいですけれども。Honeypotに迷い込んでbot判定を食らいTwitterすら使いこなせなかった私がそこまで辿り着けるかどうか……

μBMSCの細かなbugs

Data\xxx.Lang.xml と聞いて……

"View (V)" の部分を日本語化できるようにしたとき、中国語と韓国語は一切触らなかったことを思い出しました。

訳せないのはもちろん触ったら厄介なことを起こしそうで……見てみたら項目すらつくられてませんでしたね。

英語xmlを基にして日本語xmlに翻訳されたNekokan氏だあ〜! そうですね、簡体字中国語xmlと韓国語xmlではこの箇所は言語files内に要素自体が存在せず、日本語表示から切り替えると「表示 (V)」のままになったりしますね。配下のview menu itemsは各言語に既に翻訳されているのですけれども。

内容の差異をWinMergeで比較すると、View要素が存在しないことがはっきり示される。

同様に“<ExportBMSON amp="8">Export .BMSON file</ExportBMSON>相当の要素も簡体字中国語xmlと韓国語xmlには存在しませんし、簡体字中国語xmlは72行目の「<CheckUpdatesC>检查更新(中文)</CheckUpdatesC>」だけ改行文字がLFだったりして不思議。私はGitHubにissuesを立てるべきですが、先日の社内情報大公開おじさんみたいなことを絶対にやらかしそうな自覚もあり躊躇しています。

μBMSC 3.5.0.2

Disable vertical movesの設定が保存されるようになった。検索dialogなどの細かいbugsが修正された。推し。私はData\xxx.Lang.xmlの202行目あたりをtext editorで開き、修正して使っている。

テキストノードの内容を#EXRANKから#DEFEXRANKに変更する。

Releasesの3.5.0の項、“Add Commands”#LNTYPE#LNMODEかな。あとtypo: (can't inport)[追記] 修正されていた。

BMSツクール Ver2.06

WAVを同名で上書き保存すると中身が消失する危ない感じのbugが修正された。あとCtrl+JでWAVの各種chunk情報が見られる機能は個人的に大助かり。私が今まで使っていたRIFF File Viewerよりも遥かに便利。ただsabamiso氏のFlashbacked four piece of my foolish preconceptions about India(WAV HQ)のような不思議なchunksだと\x94XなどのJSON invalidなescape文字列が出てきたりする。作品名をぱっと思い出せないが、Loop tagがめちゃくちゃ入れ子にされているWAV fileとかもどこかで見かけた記憶がある……

あとSemantic Versioningは廃止された模様。Semantic Versioningの闇で言及されているPre-release部分の解釈については、私もBMSON parserを書いてみた際にひっかかった記憶がある。

#SKIP, #SKIP, run run run

#TITLE skip
#BPM 130
#SWITCH 2
  #SWITCH 2
  #CASE 1
    #00111:11
    #SKIP
  #CASE 2
    #00113:33
  #ENDSW
  #SKIP
#CASE 1
  #00115:55
#CASE 2
  #00211:11

「一つめの#SWITCH 2」から「二つめの#CASE 1」までの区間に、「入れ子にされた#SWITCH#ENDSW」と「外側の#SWITCHに属する#SKIP」が存在する例。

一般的なprogramming言語では、このようなcodeは成立しないものと思われる。

11行目の#SKIPをcommented outすれば、このBMS codeはHDX/IIDXvでも演奏される。入れ子にされた#SWITCH#ENDSWは丸ごと無視されるが、BMIIDXView2015のtree viewerでは構造化されている様子が確認できる。

nanasigrooveはこのBMS codeを受け入れる。ただしindentは消しておく必要がある。7行目の#SKIPは機能するが、11行目の#SKIPは無視される。[追記] Flowは「二つめの#CASE 1」にFALL-THROUGHしない

私は書きかけの分岐parserをnanasigrooveの解釈に寄せたTest用の図表をtypoしたせいで二日間を無駄にした。bms-language-supportを使っていればと後悔したが、確認したところ妥当な#SWITCH内容もv0.1.3では赤く染まるようだ。

BMS commandsの既定値について

こんばんは。

主要なBMS再生ソフトにおける、 #RANK 4 (いわゆるVERY EASY判定)に対応していない場合のフォールバック先になる判定について、既に調べたことがありますでしょうか?

https://stellabms.xyz/u/st/644

上記リンクの #RANK 4 を使用した譜面の難易度議論で、「Lunatic Rave 2では #RANK 4 のフォールバックが#RANK 2(いわゆるNORMAL判定)である」という興味深い情報を知って、もしかしたらhitkeyさんならこれに関連することを知ってたりするのかな~、と思って質問した次第です。

こんばんは。主要なBMS clientsについて私はよく知っているわけではなく、省略時の詳細は覚えていません。しかしながら私はWindows XPを使っていた時代にCan I useのBMS版を組もうとしており(断念しましたが)、#RANKに限らず既定値全般について調査を進めてはいました。結果をどこにmemoしたか忘れてしまいましたが……

BMS Creator(iBMSCでない)の判定入力枠にはDefaultなる選択肢が存在します。これを選択して保存すると#RANK自体が書き出されず、判定は各clientsの定める既定値に委ねられます。当時のこの作成環境から、昔のBMSではそれなりにfallback判定が使われていた可能性があります。#RANK 4に関しても、しっかりした実装なら非対応の値を受け取ったとき既定値が適用されるのだろうと思います。

BMSツクール Ver2.0.5

Bツ(略)です。

スライス時のエラー

すみません!プログラム側のミスでした。簡単にいうと「書き込み中のファイルの内容を読み込もうとした」ために発生したエラーです。修正したVer2.05を公開しました。

更新の自動チェック機能ありがたいです! Bit深度を変更してのWAV sliceも正常に動作することを確認しました。当方は細かい機能までは試しきれていませんが、既定の設定値を普通に適用するぶんには安定して動作しているんじゃないかなと思います。また何か問題があればご報告いたしますので〜

日記

BMS関連

拙作BMS
bubble / hitkey
二次配布BMS
ノイズの海と鯨 / moka
PARTY TIME IN MY DREAM / HAIJI
BMSE非公式ヘルプ
Lite
Lite-online
Full
Full-online
buglist
iBMSC
Web (Japanese version)
issues
BMS差分
a­nal­gam
boléro
Ketch­up
quovadis
SELF
yellows
Do not use non-ascii filenames
Brilliant Techno Square
雑多なメモ
bmsplayer data
bms benchmark
Secrets - Feeling Pomu 2nd
grid2sec
bmx2xxx
BMx Outliner
BMS command memo
BMS command memo (Japanese version)
BMS EVENT LITE
#RANDOM BMS list
BMS #OPTION command
BMS Bitmap test
Extended BPM
STOP Sequence
BMS Edge Cases
BMS extensions proposed by Sonorous (unofficial Japanese version)
BMS 2.0 (unofficial Japanese version)
BMS Editors
Do not use non-ascii filenames
BM98 Kikuchan Version 3.30 Revision #4.2
BMSON Checker
_wsh_bms2bmson.js

その他

HTML関連メモ
Dakuten on HTML
nest1000
EVS
Nervous Cascading
Source Han Sans test
User-Agent String
CSS Logical Properties