danoniplus 49.6.0 → 49.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/danoni_main.js +134 -25
- package/js/lib/danoni_constants.js +2 -1
- package/package.json +1 -1
package/js/danoni_main.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Source by tickle
|
|
6
6
|
* Created : 2018/10/08
|
|
7
|
-
* Revised : 2026/
|
|
7
|
+
* Revised : 2026/09/13
|
|
8
8
|
*
|
|
9
9
|
* https://github.com/cwtickle/danoniplus
|
|
10
10
|
*/
|
|
11
|
-
const g_version = `Ver 49.6.
|
|
12
|
-
const g_revisedDate = `2026/
|
|
11
|
+
const g_version = `Ver 49.6.2`;
|
|
12
|
+
const g_revisedDate = `2026/09/13`;
|
|
13
13
|
|
|
14
14
|
// カスタム用バージョン (danoni_custom.js 等で指定可)
|
|
15
15
|
let g_localVersion = ``;
|
|
@@ -135,6 +135,19 @@ let g_fps = 60;
|
|
|
135
135
|
// プレイ画面再生時の内部スケジューリング用のマージン時間(100ms)
|
|
136
136
|
let g_scheduleLead = 0.1;
|
|
137
137
|
|
|
138
|
+
// フレーム進行を音源クロック(AudioContext.currentTime)基準で補正するか
|
|
139
|
+
// - false の場合は従来通り performance.now() 基準で動作
|
|
140
|
+
let g_audioClockSync = true;
|
|
141
|
+
|
|
142
|
+
// 出力遅延(AudioContext.outputLatency)をタイミング補正に含めるか
|
|
143
|
+
// - true にすると出力デバイス(有線/Bluetooth等)によらず同じAdjustmentが使えるが、
|
|
144
|
+
// 既存のAdjustment設定値と互換性がなくなるため既定は false
|
|
145
|
+
let g_audioLatencyCompensation = false;
|
|
146
|
+
|
|
147
|
+
// 次フレームまでの待機時間の上限(ms)
|
|
148
|
+
// - 音源クロックが一時的に停止した際に待ち続けないようにするための保険
|
|
149
|
+
let g_maxFrameWait = 50;
|
|
150
|
+
|
|
138
151
|
// 譜面データの&区切りを有効にするか
|
|
139
152
|
let g_enableAmpersandSplit = true;
|
|
140
153
|
|
|
@@ -2562,6 +2575,7 @@ class AudioPlayer {
|
|
|
2562
2575
|
this._gain = this._context.createGain();
|
|
2563
2576
|
this._gain.connect(this._context.destination);
|
|
2564
2577
|
this._startTime = 0;
|
|
2578
|
+
this._scheduledTime = 0;
|
|
2565
2579
|
this._fadeinPosition = 0;
|
|
2566
2580
|
this._eventListeners = {};
|
|
2567
2581
|
this.playbackRate = 1;
|
|
@@ -2586,17 +2600,25 @@ class AudioPlayer {
|
|
|
2586
2600
|
* - scheduleLead は安定した再生タイミングを確保するための内部マージン
|
|
2587
2601
|
*/
|
|
2588
2602
|
play(_adjustmentTime = 0) {
|
|
2603
|
+
// AudioContextの時計は1回だけ読み、以降はその値を使い回す
|
|
2604
|
+
// - currentTimeはレンダークォンタム単位でしか進まないため、複数回読むと
|
|
2605
|
+
// 予約時刻と論理開始時刻が最大1クォンタム分ずれる
|
|
2606
|
+
const ctxNow = this._context.currentTime;
|
|
2607
|
+
|
|
2589
2608
|
this._source = this._context.createBufferSource();
|
|
2590
2609
|
this._source.buffer = this._buffer;
|
|
2591
2610
|
this._source.playbackRate.value = this.playbackRate;
|
|
2592
2611
|
this._source.connect(this._gain);
|
|
2593
2612
|
|
|
2594
2613
|
// 実際の予約時刻(内部スケジューリング用のマージンを含む)
|
|
2595
|
-
const startAt =
|
|
2614
|
+
const startAt = ctxNow + g_scheduleLead + _adjustmentTime;
|
|
2596
2615
|
this._source.start(startAt, this._fadeinPosition);
|
|
2597
2616
|
|
|
2598
2617
|
// ゲーム側の論理的開始時刻(g_scheduleLead を含めない)
|
|
2599
|
-
this._startTime =
|
|
2618
|
+
this._startTime = ctxNow + _adjustmentTime;
|
|
2619
|
+
|
|
2620
|
+
// 実際に音が鳴り始めるAudioContext上の時刻(フレーム同期の基準)
|
|
2621
|
+
this._scheduledTime = startAt;
|
|
2600
2622
|
}
|
|
2601
2623
|
|
|
2602
2624
|
pause() {
|
|
@@ -2621,6 +2643,26 @@ class AudioPlayer {
|
|
|
2621
2643
|
return this._context.currentTime - this._startTime + this._fadeinPosition;
|
|
2622
2644
|
}
|
|
2623
2645
|
|
|
2646
|
+
/** AudioContextの現在時刻(秒) */
|
|
2647
|
+
get contextTime() {
|
|
2648
|
+
return this._context.currentTime;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
/** 実際に音が鳴り始めるAudioContext上の時刻(秒) */
|
|
2652
|
+
get scheduledTime() {
|
|
2653
|
+
return this._scheduledTime;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
/** 音が出力デバイスから実際に出るまでの遅延(秒) */
|
|
2657
|
+
get outputLatency() {
|
|
2658
|
+
return this._context.outputLatency || this._context.baseLatency || 0;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/** AudioContextの状態(running / suspended / closed) */
|
|
2662
|
+
get contextState() {
|
|
2663
|
+
return this._context.state;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2624
2666
|
set currentTime(_currentTime) {
|
|
2625
2667
|
this._fadeinPosition = _currentTime;
|
|
2626
2668
|
}
|
|
@@ -2708,6 +2750,37 @@ const getSharedAudioContext = () => {
|
|
|
2708
2750
|
return g_sharedAudioContext;
|
|
2709
2751
|
};
|
|
2710
2752
|
|
|
2753
|
+
/**
|
|
2754
|
+
* AudioContextのウォームアップ
|
|
2755
|
+
* - 生成直後・resume直後のAudioContextは出力デバイスの起動待ちのため、
|
|
2756
|
+
* しばらく currentTime が進まない(環境により数十〜数百ms)
|
|
2757
|
+
* - この状態で音源をスケジュールすると、起動に要した時間がそのまま
|
|
2758
|
+
* 音源と譜面のずれになるため、無音を1回鳴らして時計が動き出すまで待つ
|
|
2759
|
+
* @returns {Promise<void>}
|
|
2760
|
+
*/
|
|
2761
|
+
const warmUpAudioContext = async () => {
|
|
2762
|
+
const ctx = getSharedAudioContext();
|
|
2763
|
+
if (ctx.state !== `running`) {
|
|
2764
|
+
await ctx.resume().catch(() => { });
|
|
2765
|
+
}
|
|
2766
|
+
if (ctx.state !== `running`) {
|
|
2767
|
+
return; // ジェスチャー未取得等でresumeできない場合は何もしない
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// 無音を1サンプルだけ鳴らして出力デバイスを起動させる
|
|
2771
|
+
const source = ctx.createBufferSource();
|
|
2772
|
+
source.buffer = ctx.createBuffer(1, 1, ctx.sampleRate);
|
|
2773
|
+
source.connect(ctx.destination);
|
|
2774
|
+
source.start();
|
|
2775
|
+
|
|
2776
|
+
// currentTimeが実際に進み始めるまで待機(最大500ms)
|
|
2777
|
+
const baseTime = ctx.currentTime;
|
|
2778
|
+
const limitTime = performance.now() + 500;
|
|
2779
|
+
while (ctx.currentTime === baseTime && performance.now() < limitTime) {
|
|
2780
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
|
|
2711
2784
|
/**
|
|
2712
2785
|
* クリップボードコピー関数
|
|
2713
2786
|
* 入力値をクリップボードへコピーし、メッセージを表示
|
|
@@ -6222,7 +6295,7 @@ const pauseBGM = () => {
|
|
|
6222
6295
|
g_audioForMS.load();
|
|
6223
6296
|
}
|
|
6224
6297
|
}
|
|
6225
|
-
[`bgmLooped`, `bgmFadeIn`, `bgmFadeOut`].forEach(id => {
|
|
6298
|
+
[`bgmLooped`, `bgmFadeIn`, `bgmFadeOut`, `bgmRestart`].forEach(id => {
|
|
6226
6299
|
if (g_stateObj[id]) {
|
|
6227
6300
|
clearTimeout(g_stateObj[id]);
|
|
6228
6301
|
g_stateObj[id] = null;
|
|
@@ -6347,7 +6420,8 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
|
|
|
6347
6420
|
g_audioForMS.currentTime = musicStart;
|
|
6348
6421
|
|
|
6349
6422
|
if (isTitle()) {
|
|
6350
|
-
setTimeout(() => {
|
|
6423
|
+
g_stateObj.bgmRestart = setTimeout(() => {
|
|
6424
|
+
g_stateObj.bgmRestart = null;
|
|
6351
6425
|
fadeIn();
|
|
6352
6426
|
if (encodeFlg) repeatBGM();
|
|
6353
6427
|
}, FADE_DELAY_MS);
|
|
@@ -6656,8 +6730,8 @@ const makeWarningWindow = (_text = ``, { resetFlg = false, backBtnUse = false }
|
|
|
6656
6730
|
* @param {string} [object._textColor='#000066']
|
|
6657
6731
|
* @param {string} [object._pointerEvents=C_DIS_NONE]
|
|
6658
6732
|
*/
|
|
6659
|
-
const makeInfoWindow = (_text, _animationName = ``, { _backColor = `#ccccff`, _textColor = `#000066`, _pointerEvents = C_DIS_NONE } = {}) => {
|
|
6660
|
-
const lblWarning = setWindowStyle(`<p>${_text}</p>`, _backColor, _textColor, C_ALIGN_CENTER);
|
|
6733
|
+
const makeInfoWindow = (_text, _animationName = ``, { _backColor = `#ccccff`, _textColor = `#000066`, _pointerEvents = C_DIS_NONE, _x = g_btnX(), _y = 0 } = {}) => {
|
|
6734
|
+
const lblWarning = setWindowStyle(`<p>${_text}</p>`, _backColor, _textColor, C_ALIGN_CENTER, { _x, _y });
|
|
6661
6735
|
lblWarning.style.pointerEvents = _pointerEvents;
|
|
6662
6736
|
|
|
6663
6737
|
if (_animationName !== ``) {
|
|
@@ -10970,7 +11044,7 @@ const keyConfigInit = (_kcType = g_kcType, _initFlg = false) => {
|
|
|
10970
11044
|
kcMsg2.style.fontSize = wUnit(getFontSize2(kcMsg2.textContent, g_btnWidth()));
|
|
10971
11045
|
if (_reloadFlg) {
|
|
10972
11046
|
colorPickSprite.style.display = isDefault ? C_DIS_NONE : C_DIS_INHERIT;
|
|
10973
|
-
g_keycons.colorCursorNum = g_keycons.colorCursorNum % Math.ceil(
|
|
11047
|
+
g_keycons.colorCursorNum = g_keycons.colorCursorNum % Math.ceil(g_dfColorObj.setColorInit.length / g_limitObj.kcColorPickerNum);
|
|
10974
11048
|
changeColorPickers();
|
|
10975
11049
|
}
|
|
10976
11050
|
};
|
|
@@ -10998,14 +11072,14 @@ const keyConfigInit = (_kcType = g_kcType, _initFlg = false) => {
|
|
|
10998
11072
|
|
|
10999
11073
|
// ColorPickerの切替
|
|
11000
11074
|
createCss2Button(`lnkColorR`, `[${g_keycons.colorCursorNum + 1} /`, () => {
|
|
11001
|
-
g_keycons.colorCursorNum = (g_keycons.colorCursorNum + 1) % Math.ceil(
|
|
11075
|
+
g_keycons.colorCursorNum = (g_keycons.colorCursorNum + 1) % Math.ceil(g_dfColorObj.setColorInit.length / g_limitObj.kcColorPickerNum);
|
|
11002
11076
|
changeColorPickers();
|
|
11003
11077
|
}, g_lblPosObj.lnkColorR, g_cssObj.button_Start),
|
|
11004
11078
|
|
|
11005
11079
|
// 矢印の配色をフリーズアローへ反映
|
|
11006
11080
|
createCss2Button(`lnkColorCopy`, `↓]`, () => {
|
|
11007
11081
|
if (window.confirm(g_msgObj.colorCopyConfirm)) {
|
|
11008
|
-
for (let j = 0; j <
|
|
11082
|
+
for (let j = 0; j < g_dfColorObj.setColorInit.length; j++) {
|
|
11009
11083
|
g_headerObj.frzColor[j] = g_headerObj[`frzColor${g_colorType}`][j] =
|
|
11010
11084
|
fillArray(g_headerObj[`frzColor${g_colorType}`][j].length, g_headerObj[`setColor${g_colorType}`][j]);
|
|
11011
11085
|
}
|
|
@@ -11176,7 +11250,10 @@ const keyConfigInit = (_kcType = g_kcType, _initFlg = false) => {
|
|
|
11176
11250
|
g_currentk = 0;
|
|
11177
11251
|
g_prevKey = 0;
|
|
11178
11252
|
}, {
|
|
11179
|
-
...g_lblPosObj.btnKcBack, resetFunc: () =>
|
|
11253
|
+
...g_lblPosObj.btnKcBack, resetFunc: () => {
|
|
11254
|
+
keyconfigKeyboardPreview.dispose();
|
|
11255
|
+
g_moveSettingWindow(false);
|
|
11256
|
+
},
|
|
11180
11257
|
}, g_cssObj.button_Back),
|
|
11181
11258
|
|
|
11182
11259
|
createDivCss2Label(`lblPattern`, `${g_lblNameObj.KeyPattern}: ${g_keyObj.currentPtn === -1 ?
|
|
@@ -12208,6 +12285,9 @@ const loadMusic = async () => {
|
|
|
12208
12285
|
}
|
|
12209
12286
|
|
|
12210
12287
|
if (loadSucceeded) {
|
|
12288
|
+
// 初回プレイ時に出力デバイスの起動待ちでずれるのを防ぐため、
|
|
12289
|
+
// メイン画面へ移行する前にAudioContextを起動させておく
|
|
12290
|
+
await warmUpAudioContext();
|
|
12211
12291
|
mainInit(); // 音源・譜面変換双方の完了後にまとめて呼ぶ
|
|
12212
12292
|
}
|
|
12213
12293
|
};
|
|
@@ -14964,6 +15044,7 @@ const mainInit = () => {
|
|
|
14964
15044
|
let thisTime;
|
|
14965
15045
|
let buffTime;
|
|
14966
15046
|
let musicStartTime;
|
|
15047
|
+
let musicStartCtxTime;
|
|
14967
15048
|
let musicStartFlg = false;
|
|
14968
15049
|
|
|
14969
15050
|
g_inputKeyBuffer = {};
|
|
@@ -16197,14 +16278,32 @@ const mainInit = () => {
|
|
|
16197
16278
|
}
|
|
16198
16279
|
|
|
16199
16280
|
// 60fpsから遅延するため、その差分を取って次回のタイミングで遅れをリカバリする
|
|
16281
|
+
// - WebAudioAPI使用時は音源クロック(AudioContext.currentTime)を基準とする
|
|
16282
|
+
// performance.now()とAudioContextの時計は独立して進むため、後者を基準にしないと
|
|
16283
|
+
// 再生開始時のオフセットずれやsuspend/resumeによるずれを吸収できない
|
|
16200
16284
|
thisTime = performance.now();
|
|
16201
16285
|
buffTime = 0;
|
|
16202
|
-
|
|
16286
|
+
let holdFrame = false;
|
|
16287
|
+
|
|
16288
|
+
if (g_audio instanceof AudioPlayer && g_audioClockSync && musicStartCtxTime !== undefined) {
|
|
16289
|
+
if (g_audio.contextState === `running`) {
|
|
16290
|
+
buffTime = (g_audio.contextTime - musicStartCtxTime) * 1000
|
|
16291
|
+
- (currentFrame - musicStartFrame) * 1000 / g_fps;
|
|
16292
|
+
} else {
|
|
16293
|
+
// AudioContext停止中は音源も止まっているため、フレーム進行も止めて復帰を待つ
|
|
16294
|
+
getSharedAudioContext(); // suspended時はresumeを試行
|
|
16295
|
+
holdFrame = true;
|
|
16296
|
+
}
|
|
16297
|
+
} else if (g_audio instanceof AudioPlayer || currentFrame >= musicStartFrame) {
|
|
16203
16298
|
buffTime = (thisTime - musicStartTime - (currentFrame - musicStartFrame) * 1000 / g_fps);
|
|
16204
16299
|
}
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16300
|
+
|
|
16301
|
+
if (!holdFrame) {
|
|
16302
|
+
g_scoreObj.frameNum++;
|
|
16303
|
+
g_scoreObj.baseFrame++;
|
|
16304
|
+
}
|
|
16305
|
+
g_timeoutEvtId = setTimeout(flowTimeline, holdFrame ? g_maxFrameWait :
|
|
16306
|
+
Math.min(Math.max(1000 / g_fps - buffTime, 0), g_maxFrameWait));
|
|
16208
16307
|
}
|
|
16209
16308
|
};
|
|
16210
16309
|
safeExecuteCustomHooks(`g_skinJsObj.main`, g_skinJsObj.main);
|
|
@@ -16217,6 +16316,11 @@ const mainInit = () => {
|
|
|
16217
16316
|
const musicStartAdjustment = (g_headerObj.blankFrame - g_stateObj.decimalAdjustment + 1) / g_fps;
|
|
16218
16317
|
musicStartTime = performance.now() + (musicStartAdjustment + g_scheduleLead) * 1000;
|
|
16219
16318
|
g_audio.play(musicStartAdjustment);
|
|
16319
|
+
|
|
16320
|
+
// 音源クロック基準の開始時刻(フレーム進行の基準)
|
|
16321
|
+
// - g_audioLatencyCompensationが有効な場合は出力遅延分だけ表示を後ろへずらす
|
|
16322
|
+
musicStartCtxTime = g_audio.scheduledTime
|
|
16323
|
+
+ (g_audioLatencyCompensation ? g_audio.outputLatency : 0);
|
|
16220
16324
|
}
|
|
16221
16325
|
|
|
16222
16326
|
g_timeoutEvtId = setTimeout(flowTimeline, 1000 / g_fps);
|
|
@@ -17859,15 +17963,20 @@ const resultInit = () => {
|
|
|
17859
17963
|
} else {
|
|
17860
17964
|
// Canvas の内容を PNG 画像として取得
|
|
17861
17965
|
canvas.toBlob(async blob => {
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
}
|
|
17866
|
-
|
|
17966
|
+
try {
|
|
17967
|
+
if (blob === null) {
|
|
17968
|
+
throw new Error(`Failed to create result image blob.`);
|
|
17969
|
+
}
|
|
17970
|
+
await navigator.clipboard.write([
|
|
17971
|
+
new ClipboardItem({ 'image/png': blob })
|
|
17972
|
+
]);
|
|
17973
|
+
tmpDiv.removeChild(canvas);
|
|
17974
|
+
divRoot.removeChild(tmpDiv);
|
|
17975
|
+
makeInfoWindow(_msg, `leftToRightFade`);
|
|
17976
|
+
} catch {
|
|
17977
|
+
viewResultImage();
|
|
17978
|
+
}
|
|
17867
17979
|
});
|
|
17868
|
-
tmpDiv.removeChild(canvas);
|
|
17869
|
-
divRoot.removeChild(tmpDiv);
|
|
17870
|
-
makeInfoWindow(_msg, `leftToRightFade`);
|
|
17871
17980
|
}
|
|
17872
17981
|
|
|
17873
17982
|
} catch (err) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Source by tickle
|
|
7
7
|
* Created : 2019/11/19
|
|
8
|
-
* Revised : 2026/
|
|
8
|
+
* Revised : 2026/09/13 (v49.6.2)
|
|
9
9
|
*
|
|
10
10
|
* https://github.com/cwtickle/danoniplus
|
|
11
11
|
*/
|
|
@@ -1221,6 +1221,7 @@ const g_stateObj = {
|
|
|
1221
1221
|
bgmLooped: null,
|
|
1222
1222
|
bgmFadeIn: null,
|
|
1223
1223
|
bgmFadeOut: null,
|
|
1224
|
+
bgmRestart: null,
|
|
1224
1225
|
bgmTimeupdateEvtId: null,
|
|
1225
1226
|
bgmMuteFlg: false,
|
|
1226
1227
|
|