danoniplus 43.2.0 → 43.2.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.
Files changed (2) hide show
  1. package/js/danoni_main.js +180 -75
  2. 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 : 2025/12/28
7
+ * Revised : 2025/12/30
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 43.2.0`;
12
- const g_revisedDate = `2025/12/28`;
11
+ const g_version = `Ver 43.2.2`;
12
+ const g_revisedDate = `2025/12/30`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -115,6 +115,9 @@ const waitUntilLoaded = () => {
115
115
  // fps(デフォルトは60)
116
116
  let g_fps = 60;
117
117
 
118
+ // プレイ画面再生時の内部スケジューリング用のマージン時間(100ms)
119
+ let g_scheduleLead = 0.1;
120
+
118
121
  // 譜面データの&区切りを有効にするか
119
122
  let g_enableAmpersandSplit = true;
120
123
 
@@ -2350,13 +2353,25 @@ class AudioPlayer {
2350
2353
  this._eventListeners[`canplaythrough`]?.forEach(_listener => _listener());
2351
2354
  }
2352
2355
 
2356
+ /**
2357
+ * 再生処理
2358
+ * @param {number} _adjustmentTime
2359
+ * - 実際の再生開始時間は、scheduleLead + _adjustmentTime から開始される
2360
+ * - ただしゲーム内での経過時間計算は _adjustmentTime を基準に行う
2361
+ * - scheduleLead は安定した再生タイミングを確保するための内部マージン
2362
+ */
2353
2363
  play(_adjustmentTime = 0) {
2354
2364
  this._source = this._context.createBufferSource();
2355
2365
  this._source.buffer = this._buffer;
2356
2366
  this._source.playbackRate.value = this.playbackRate;
2357
2367
  this._source.connect(this._gain);
2358
- this._startTime = this._context.currentTime;
2359
- this._source.start(this._context.currentTime + _adjustmentTime, this._fadeinPosition);
2368
+
2369
+ // 実際の予約時刻(内部スケジューリング用のマージンを含む)
2370
+ const startAt = this._context.currentTime + g_scheduleLead + _adjustmentTime;
2371
+ this._source.start(startAt, this._fadeinPosition);
2372
+
2373
+ // ゲーム側の論理的開始時刻(g_scheduleLead を含めない)
2374
+ this._startTime = this._context.currentTime + _adjustmentTime;
2360
2375
  }
2361
2376
 
2362
2377
  pause() {
@@ -5542,7 +5557,7 @@ const pauseBGM = () => {
5542
5557
  }
5543
5558
  [`bgmLooped`, `bgmFadeIn`, `bgmFadeOut`].forEach(id => {
5544
5559
  if (g_stateObj[id]) {
5545
- clearInterval(g_stateObj[id]);
5560
+ clearTimeout(g_stateObj[id]);
5546
5561
  g_stateObj[id] = null;
5547
5562
  }
5548
5563
  });
@@ -5566,95 +5581,177 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5566
5581
  const musicEnd = g_headerObj.musicEnds?.[currentIdx] ?? 0;
5567
5582
  const isTitle = () => g_currentPage === `title` && _currentLoopNum === g_settings.musicLoopNum;
5568
5583
 
5584
+ /**
5585
+ * 汎用フェード処理
5586
+ * @param {number} startVolume - 開始音量 (0〜1)
5587
+ * @param {number} endVolume - 終了音量 (0〜1)
5588
+ * @param {number} step - 1ステップの増減量
5589
+ * @param {function} onEnd - フェード完了時の処理
5590
+ * @param {function} isValid - フェード継続条件(true: 継続, false: 中断)
5591
+ * @returns {number} timeoutId
5592
+ */
5593
+ const fadeVolume = (startVolume, endVolume, step, onEnd, isValid) => {
5594
+
5595
+ // 開始時点で終了音量とイコールの場合は終了
5596
+ if (startVolume === endVolume || step === 0) {
5597
+ g_audio.volume = endVolume;
5598
+ onEnd(true);
5599
+ return null;
5600
+ }
5601
+
5602
+ let volume = startVolume;
5603
+ g_audio.volume = startVolume;
5604
+
5605
+ const stepFunc = () => {
5606
+ // 継続条件チェック
5607
+ if (!isValid()) {
5608
+ onEnd(false); // 中断
5609
+ return;
5610
+ }
5611
+
5612
+ // 終了判定
5613
+ const reached =
5614
+ (startVolume < endVolume && volume >= endVolume) ||
5615
+ (startVolume > endVolume && volume <= endVolume);
5616
+
5617
+ if (reached) {
5618
+ g_audio.volume = endVolume;
5619
+ onEnd(true); // 正常終了
5620
+ return;
5621
+ }
5622
+
5623
+ // 音量更新
5624
+ volume += step;
5625
+ g_audio.volume = Math.min(Math.max(volume, 0), 1);
5626
+
5627
+ // 次のステップへ
5628
+ setTimeout(stepFunc, FADE_INTERVAL_MS);
5629
+ };
5630
+
5631
+ return setTimeout(stepFunc, FADE_INTERVAL_MS);
5632
+ };
5633
+
5634
+ /**
5635
+ * 汎用ポーリング(監視)処理
5636
+ * @param {function} check - 条件チェック関数(true なら終了)
5637
+ * @param {function} onEnd - 終了時の処理
5638
+ * @param {function} isValid - 継続条件(true: 継続, false: 中断)
5639
+ * @returns {number} timeoutId
5640
+ */
5641
+ const poll = (check, onEnd, isValid) => {
5642
+ const step = () => {
5643
+ // 継続条件チェック
5644
+ if (!isValid()) {
5645
+ onEnd(false); // 中断
5646
+ return;
5647
+ }
5648
+
5649
+ // 条件成立
5650
+ if (check()) {
5651
+ onEnd(true); // 正常終了
5652
+ return;
5653
+ }
5654
+
5655
+ // 次のチェックへ
5656
+ setTimeout(step, FADE_INTERVAL_MS);
5657
+ };
5658
+
5659
+ return setTimeout(step, FADE_INTERVAL_MS);
5660
+ };
5661
+
5569
5662
  /**
5570
5663
  * BGMのフェードアウトとシーク
5571
5664
  */
5572
5665
  const fadeOutAndSeek = () => {
5573
- let volume = g_audio.volume;
5574
- const fadeInterval = setInterval(() => {
5575
- if (volume > FADE_STEP && isTitle()) {
5576
- volume -= FADE_STEP;
5577
- g_audio.volume = Math.max(volume, 0);
5578
- } else {
5579
- clearInterval(fadeInterval);
5666
+ const start = g_audio.volume;
5667
+ const end = 0;
5668
+
5669
+ g_stateObj.bgmFadeOut = fadeVolume(
5670
+ start,
5671
+ end,
5672
+ -FADE_STEP,
5673
+ /* onEnd */
5674
+ (finished) => {
5580
5675
  g_stateObj.bgmFadeOut = null;
5676
+
5677
+ if (!finished) return; // 中断された
5678
+
5581
5679
  g_audio.pause();
5582
5680
  g_audio.currentTime = musicStart;
5583
5681
 
5584
- // フェードイン開始
5585
5682
  if (isTitle()) {
5586
5683
  setTimeout(() => {
5587
5684
  fadeIn();
5588
- if (encodeFlg) {
5589
- // base64エンコード時はtimeupdateイベントが発火しないため、
5590
- // setIntervalで時間を取得する
5591
- repeatBGM();
5592
- }
5685
+ if (encodeFlg) repeatBGM();
5593
5686
  }, FADE_DELAY_MS);
5594
5687
  } else {
5595
5688
  pauseBGM();
5596
5689
  }
5597
- }
5598
- }, FADE_INTERVAL_MS);
5599
- g_stateObj.bgmFadeOut = fadeInterval;
5690
+ },
5691
+ /* isValid */
5692
+ () =>
5693
+ isTitle() &&
5694
+ g_stateObj.bgmFadeOut !== null
5695
+ );
5600
5696
  };
5601
5697
 
5602
5698
  /**
5603
5699
  * BGMのフェードイン
5604
5700
  */
5605
5701
  const fadeIn = () => {
5606
- if (!(g_audio instanceof AudioPlayer) && !g_audio.src) {
5607
- return;
5608
- }
5609
- let volume = 0;
5702
+ if (!(g_audio instanceof AudioPlayer) && !g_audio.src) return;
5703
+
5704
+ const start = 0;
5705
+ const end = g_stateObj.bgmVolume / 100;
5706
+
5707
+ g_audio.volume = 0;
5610
5708
  g_audio.play();
5611
- const fadeInterval = setInterval(() => {
5612
- if (volume < g_stateObj.bgmVolume / 100 && isTitle()) {
5613
- volume += FADE_STEP;
5614
- g_audio.volume = Math.min(volume, 1);
5615
- } else {
5616
- clearInterval(fadeInterval);
5617
- g_stateObj.bgmFadeIn = null;
5618
- }
5619
5709
 
5620
- // フェードイン中に楽曲が変更された場合は停止
5621
- if (currentIdx !== g_headerObj.musicIdxList[g_settings.musicIdxNum]) {
5622
- pauseBGM();
5623
- clearInterval(fadeInterval);
5710
+ g_stateObj.bgmFadeIn = fadeVolume(
5711
+ start,
5712
+ end,
5713
+ FADE_STEP,
5714
+ /* onEnd */
5715
+ () => {
5624
5716
  g_stateObj.bgmFadeIn = null;
5625
- }
5626
- }, FADE_INTERVAL_MS);
5627
- g_stateObj.bgmFadeIn = fadeInterval;
5717
+ },
5718
+ /* isValid */
5719
+ () =>
5720
+ isTitle() &&
5721
+ g_stateObj.bgmFadeIn !== null &&
5722
+ currentIdx === g_headerObj.musicIdxList[g_settings.musicIdxNum]
5723
+ );
5628
5724
  };
5629
5725
 
5630
5726
  /**
5631
- * BGMのループ処理
5727
+ * BGMのループ処理 (base64エンコード時用)
5728
+ * - base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5632
5729
  */
5633
5730
  const repeatBGM = () => {
5634
- if (encodeFlg) {
5635
- // base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5636
- const repeatCheck = setInterval((num = g_settings.musicIdxNum) => {
5731
+ const numAtStart = g_settings.musicIdxNum;
5732
+
5733
+ g_stateObj.bgmLooped = poll(
5734
+ /* check */
5735
+ () => {
5637
5736
  try {
5638
- if (((g_audio.elapsedTime >= musicEnd) ||
5639
- num !== g_settings.musicIdxNum) && g_stateObj.bgmLooped !== null) {
5640
- clearInterval(repeatCheck);
5641
- g_stateObj.bgmLooped = null;
5642
- fadeOutAndSeek();
5643
- }
5644
- } catch (e) {
5645
- clearInterval(repeatCheck);
5646
- g_stateObj.bgmLooped = null;
5737
+ return (
5738
+ g_audio.elapsedTime >= musicEnd ||
5739
+ numAtStart !== g_settings.musicIdxNum
5740
+ );
5741
+ } catch {
5742
+ return true; // エラー時は終了扱い
5647
5743
  }
5648
- }, FADE_INTERVAL_MS);
5649
- g_stateObj.bgmLooped = repeatCheck;
5650
-
5651
- } else {
5652
- g_stateObj.bgmTimeupdateEvtId = g_handler.addListener(g_audio, "timeupdate", () => {
5653
- if (g_audio.currentTime >= musicEnd) {
5744
+ },
5745
+ /* onEnd */
5746
+ (finished) => {
5747
+ g_stateObj.bgmLooped = null;
5748
+ if (finished) {
5654
5749
  fadeOutAndSeek();
5655
5750
  }
5656
- });
5657
- }
5751
+ },
5752
+ /* isValid */
5753
+ () => g_stateObj.bgmLooped !== null
5754
+ );
5658
5755
  };
5659
5756
 
5660
5757
  if (encodeFlg) {
@@ -5676,9 +5773,10 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5676
5773
  g_audio = tmpAudio;
5677
5774
  }
5678
5775
  g_audio.volume = g_stateObj.bgmVolume / 100;
5679
- if (g_currentPage === `title`) {
5776
+ if (g_currentPage === `title` && musicEnd > 0) {
5680
5777
  g_audio.currentTime = musicStart;
5681
5778
  g_audio.play();
5779
+ repeatBGM();
5682
5780
  }
5683
5781
  } catch (e) {
5684
5782
  // 音源の読み込みに失敗した場合、エラーを表示
@@ -5698,9 +5796,14 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5698
5796
  g_audio.currentTime = musicStart;
5699
5797
  g_audio.play();
5700
5798
  }, { once: true });
5701
- }
5702
- if (musicEnd > 0) {
5703
- repeatBGM();
5799
+
5800
+ if (musicEnd > 0) {
5801
+ g_stateObj.bgmTimeupdateEvtId = g_handler.addListener(g_audio, "timeupdate", () => {
5802
+ if (g_audio.currentTime >= musicEnd) {
5803
+ fadeOutAndSeek();
5804
+ }
5805
+ });
5806
+ }
5704
5807
  }
5705
5808
  };
5706
5809
 
@@ -7824,26 +7927,28 @@ const gaugeFormat = (_mode, _border, _rcv, _dmg, _init, _lifeValFlg) => {
7824
7927
  const initVal = g_headerObj.maxLifeVal * _init / 100;
7825
7928
  const borderVal = g_headerObj.maxLifeVal * _border / 100;
7826
7929
 
7827
- // 整形用にライフ初期値を整数、回復・ダメージ量を小数第1位で丸める
7930
+ // 整形用にライフ初期値を整数、回復・ダメージ量を小数第2位に丸める
7828
7931
  const init = Math.round(initVal);
7829
7932
  const borderText = (_mode === C_LFE_BORDER && _border !== 0 ? Math.round(borderVal) : `-`);
7830
- const toFixed2 = _val => Math.round(_val * 100) / 100;
7933
+ const round2 = _val => Math.round(_val * 100) / 100;
7831
7934
 
7832
- let rcvText = toFixed2(_rcv), dmgText = toFixed2(_dmg);
7935
+ let rcvText = round2(_rcv), dmgText = round2(_dmg);
7833
7936
  let realRcv = _rcv, realDmg = _dmg;
7834
7937
  const allCnt = sumData(g_detailObj.arrowCnt[g_stateObj.scoreId]) +
7835
7938
  (g_headerObj.frzStartjdgUse ? 2 : 1) * sumData(g_detailObj.frzCnt[g_stateObj.scoreId]);
7836
7939
 
7940
+ // ゲージ設定が矢印数依存の場合、実際の値に変換して表示する
7941
+ // 表示上、計算した値は小数第二位までの表示とする(それ以外はそのまま)
7837
7942
  if (_lifeValFlg === C_FLG_ON) {
7838
7943
  rcvText = ``, dmgText = ``;
7839
7944
  if (allCnt > 0) {
7840
7945
  realRcv = Math.min(calcLifeVal(_rcv, allCnt), g_headerObj.maxLifeVal);
7841
7946
  realDmg = Math.min(calcLifeVal(_dmg, allCnt), g_headerObj.maxLifeVal);
7842
- rcvText = `${toFixed2(realRcv)}<br>`;
7843
- dmgText = `${toFixed2(realDmg)}<br>`;
7947
+ rcvText = `${realRcv.toFixed(2)}<br>`;
7948
+ dmgText = `${realDmg.toFixed(2)}<br>`;
7844
7949
  }
7845
- rcvText += `<span class="settings_lifeVal">(${toFixed2(_rcv)})</span>`;
7846
- dmgText += `<span class="settings_lifeVal">(${toFixed2(_dmg)})</span>`;
7950
+ rcvText += `<span class="settings_lifeVal">(${round2(_rcv)})</span>`;
7951
+ dmgText += `<span class="settings_lifeVal">(${round2(_dmg)})</span>`;
7847
7952
  }
7848
7953
 
7849
7954
  // 達成率(Accuracy)・許容ミス数の計算
@@ -10435,7 +10540,7 @@ const calcLifeVals = _allArrows => {
10435
10540
  * @param {number} _val
10436
10541
  * @param {number} _allArrows
10437
10542
  */
10438
- const calcLifeVal = (_val, _allArrows) => Math.round(_val * g_headerObj.maxLifeVal * 100 / _allArrows) / 100;
10543
+ const calcLifeVal = (_val, _allArrows) => _val * g_headerObj.maxLifeVal / _allArrows;
10439
10544
 
10440
10545
  /**
10441
10546
  * 最終フレーム数の取得
@@ -12953,7 +13058,7 @@ const mainInit = () => {
12953
13058
  // WebAudioAPIが使用できる場合は小数フレーム分だけ音源位置を調整
12954
13059
  if (g_audio instanceof AudioPlayer) {
12955
13060
  const musicStartAdjustment = (g_headerObj.blankFrame - g_stateObj.decimalAdjustment + 1) / g_fps;
12956
- musicStartTime = performance.now() + musicStartAdjustment * 1000;
13061
+ musicStartTime = performance.now() + (musicStartAdjustment + g_scheduleLead) * 1000;
12957
13062
  g_audio.play(musicStartAdjustment);
12958
13063
  }
12959
13064
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "43.2.0",
3
+ "version": "43.2.2",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "scripts": {