danoniplus 41.0.3 → 41.2.0

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 CHANGED
@@ -4,12 +4,12 @@
4
4
  *
5
5
  * Source by tickle
6
6
  * Created : 2018/10/08
7
- * Revised : 2025/04/27
7
+ * Revised : 2025/05/09
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 41.0.3`;
12
- const g_revisedDate = `2025/04/27`;
11
+ const g_version = `Ver 41.2.0`;
12
+ const g_revisedDate = `2025/05/09`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -2310,6 +2310,25 @@ class AudioPlayer {
2310
2310
  }
2311
2311
  }
2312
2312
 
2313
+ close() {
2314
+ if (this._context) {
2315
+ this._context.close()?.catch(() => {/* ignore double-close */ });
2316
+ this._buffer = null;
2317
+ }
2318
+ if (this._source) {
2319
+ this._source.disconnect(this._gain);
2320
+ this._source = null;
2321
+ }
2322
+ if (this._gain) {
2323
+ this._gain.disconnect();
2324
+ this._gain = null;
2325
+ }
2326
+ }
2327
+
2328
+ get elapsedTime() {
2329
+ return this._context.currentTime - this._startTime + this._fadeinPosition;
2330
+ }
2331
+
2313
2332
  set currentTime(_currentTime) {
2314
2333
  this._fadeinPosition = _currentTime;
2315
2334
  }
@@ -2601,7 +2620,8 @@ const initialControl = async () => {
2601
2620
  g_customJsObj.preTitle.forEach(func => func());
2602
2621
  const queryMusicId = getQueryParamVal(`musicId`);
2603
2622
  g_settings.musicIdxNum = queryMusicId !== null ? Number(queryMusicId) :
2604
- g_headerObj.musicNos[g_stateObj.scoreId] || g_headerObj.musicNos[0];
2623
+ g_headerObj.musicGroups?.[g_headerObj.musicNos[g_stateObj.scoreId]] ??
2624
+ g_headerObj.musicNos[g_stateObj.scoreId] ?? g_headerObj.musicNos[0];
2605
2625
  titleInit(true);
2606
2626
 
2607
2627
  // 未使用のg_keyObjプロパティを削除
@@ -2956,6 +2976,21 @@ const copySetColor = (_baseObj, _scoreId) => {
2956
2976
  const getMusicUrl = _scoreId =>
2957
2977
  g_headerObj.musicUrls?.[g_headerObj.musicNos[_scoreId]] ?? g_headerObj.musicUrls?.[0] ?? `nosound.mp3`;
2958
2978
 
2979
+ /**
2980
+ * 音源データの実際のパスを取得
2981
+ * @param {string} _musicUrl
2982
+ * @returns {string}
2983
+ */
2984
+ const getFullMusicUrl = (_musicUrl = ``) => {
2985
+ let url = `${g_rootPath}../${g_headerObj.musicFolder}/${_musicUrl}`;
2986
+ if (_musicUrl.indexOf(C_MRK_CURRENT_DIRECTORY) !== -1) {
2987
+ url = _musicUrl.split(C_MRK_CURRENT_DIRECTORY)[1];
2988
+ } else if (g_headerObj.musicFolder.indexOf(C_MRK_CURRENT_DIRECTORY) !== -1) {
2989
+ url = `${g_headerObj.musicFolder.split(C_MRK_CURRENT_DIRECTORY)[1]}/${_musicUrl}`;
2990
+ }
2991
+ return url;
2992
+ }
2993
+
2959
2994
  /**
2960
2995
  * 譜面ファイル読込後処理(譜面詳細情報取得用)
2961
2996
  * @param {number} _scoreId
@@ -3583,6 +3618,19 @@ const headerConvert = _dosObj => {
3583
3618
  obj.musicNos = fillArray(obj.keyLabels.length);
3584
3619
  }
3585
3620
 
3621
+ // 楽曲別のグループ化設定(選曲モードのみ)
3622
+ if (hasVal(_dosObj.musicGroup)) {
3623
+ obj.musicGroups = _dosObj.musicGroup.split(`,`)
3624
+ .map((val, j) => setVal(val, j, C_TYP_NUMBER))
3625
+ .map((val, j) => val < 0 ? j + val : val);
3626
+ for (let k = obj.musicGroups.length; k <= Math.max(...obj.musicNos); k++) {
3627
+ obj.musicGroups[k] = k;
3628
+ }
3629
+ obj.musicIdxList = makeDedupliArray(obj.musicGroups);
3630
+ } else {
3631
+ obj.musicIdxList = [...Array(Math.max(...obj.musicNos) + 1).keys()];
3632
+ }
3633
+
3586
3634
  // 譜面変更セレクターの利用有無
3587
3635
  obj.difSelectorUse = getDifSelectorUse(_dosObj.difSelectorUse, obj.viewLists);
3588
3636
 
@@ -3741,7 +3789,22 @@ const headerConvert = _dosObj => {
3741
3789
 
3742
3790
  // 楽曲URL
3743
3791
  if (hasVal(_dosObj.musicUrl)) {
3744
- obj.musicUrls = splitLF2(_dosObj.musicUrl);
3792
+ const musicUrls = splitLF2(_dosObj.musicUrl);
3793
+ obj.musicUrls = [], obj.musicStarts = [], obj.musicEnds = [];
3794
+ musicUrls.forEach((val, j) => {
3795
+ const musicUrlPair = val.split(`,`);
3796
+ obj.musicUrls[j] = musicUrlPair[0] || ``;
3797
+ if (musicUrlPair[1] !== undefined) {
3798
+ const musicBGMTime = musicUrlPair[1].split(`-`).map(str => str.trim());
3799
+ obj.musicStarts[j] = Math.floor(transTimerToFrame(musicBGMTime[0] ?? 0) / g_fps);
3800
+ obj.musicEnds[j] = musicBGMTime[1] !== undefined ?
3801
+ Math.floor((transTimerToFrame(musicBGMTime[1] ?? 0)) / g_fps) :
3802
+ Math.floor((transTimerToFrame(musicBGMTime[0] ?? 0) + transTimerToFrame(`0:20`)) / g_fps);
3803
+ } else {
3804
+ obj.musicStarts[j] = 0;
3805
+ obj.musicEnds[j] = 20;
3806
+ }
3807
+ });
3745
3808
  } else {
3746
3809
  makeWarningWindow(g_msgInfoObj.E_0031);
3747
3810
  }
@@ -4874,7 +4937,7 @@ const titleInit = (_initFlg = false) => {
4874
4937
  if (getQueryParamVal(`scoreId`) !== null) {
4875
4938
  g_headerObj.viewLists = [];
4876
4939
  g_headerObj.musicNos.forEach((val, j) => {
4877
- if (val === g_settings.musicIdxNum) {
4940
+ if ((g_headerObj.musicGroups?.[val] ?? val) === g_settings.musicIdxNum) {
4878
4941
  g_headerObj.viewLists.push(j);
4879
4942
  tmpCreatorList.push(g_headerObj.creatorNames[j]);
4880
4943
  }
@@ -4893,8 +4956,7 @@ const titleInit = (_initFlg = false) => {
4893
4956
 
4894
4957
  // 選曲画面の初期化
4895
4958
  const wheelCycle = 2;
4896
- const musicMaxIdx = Math.max(...g_headerObj.musicNos);
4897
- const musicIdxTmpList = [...Array(musicMaxIdx + 1).keys()];
4959
+ g_settings.musicLoopNum = 0;
4898
4960
 
4899
4961
  /**
4900
4962
  * メイン以外の選曲ボタンの作成
@@ -4909,6 +4971,17 @@ const titleInit = (_initFlg = false) => {
4909
4971
  align: C_ALIGN_LEFT, padding: `0 10px`,
4910
4972
  }, g_cssObj.button_Default_NoColor, g_cssObj.title_base);
4911
4973
 
4974
+ /**
4975
+ * 選曲画面上の音量調整
4976
+ * @param {number} _num
4977
+ */
4978
+ const setBGMVolume = (_num = 1) => {
4979
+ g_settings.bgmVolumeNum = nextPos(g_settings.bgmVolumeNum, _num, g_settings.volumes.length);
4980
+ g_stateObj.bgmVolume = g_settings.volumes[g_settings.bgmVolumeNum];
4981
+ g_audio.volume = g_stateObj.bgmVolume / 100;
4982
+ btnBgmVolume.textContent = `${g_stateObj.bgmVolume}${g_lblNameObj.percent}`;
4983
+ };
4984
+
4912
4985
  for (let j = -g_settings.mSelectableTerms; j <= g_settings.mSelectableTerms; j++) {
4913
4986
  if (j !== 0) {
4914
4987
  divRoot.appendChild(createMSelectBtn(j));
@@ -4921,6 +4994,7 @@ const titleInit = (_initFlg = false) => {
4921
4994
  createCss2Button(`btnStart`,
4922
4995
  `>`, () => {
4923
4996
  clearTimeout(g_timeoutEvtTitleId);
4997
+ pauseBGM();
4924
4998
  g_handler.removeListener(wheelHandler);
4925
4999
  g_keyObj.prevKey = `Dummy${g_settings.musicIdxNum}`;
4926
5000
  }, Object.assign({
@@ -4931,17 +5005,47 @@ const titleInit = (_initFlg = false) => {
4931
5005
  createCss2Button(`btnMusicSelectNext`, `↓`, () => changeMSelect(1),
4932
5006
  g_lblPosObj.btnMusicSelectNext, g_cssObj.button_Setting),
4933
5007
  createCss2Button(`btnMusicSelectRandom`, `Random`, () =>
4934
- changeMSelect(Math.floor(Math.random() * musicIdxTmpList.length)),
5008
+ changeMSelect(Math.floor(Math.random() * (g_headerObj.musicIdxList.length - 1)) + 1),
4935
5009
  g_lblPosObj.btnMusicSelectRandom, g_cssObj.button_Default),
4936
5010
  createDivCss2Label(`lblMusicCnt`, ``, g_lblPosObj.lblMusicCnt),
4937
5011
  createDivCss2Label(`lblComment`, ``, g_lblPosObj.lblComment_music),
5012
+
5013
+ createDivCss2Label(`lblBgmVolume`, `BGM Volume`, g_lblPosObj.lblBgmVolume),
5014
+ createCss2Button(`btnBgmMute`, g_stateObj.bgmMuteFlg ? `&#x1f507;` : `&#x1f50a;`, evt => {
5015
+ g_stateObj.bgmMuteFlg = !g_stateObj.bgmMuteFlg;
5016
+ g_stateObj.bgmMuteFlg ? pauseBGM() : playBGM(0);
5017
+ evt.target.innerHTML = g_stateObj.bgmMuteFlg ? `&#x1f507;` : `&#x1f50a;`;
5018
+ }, g_lblPosObj.btnBgmMute, g_cssObj.button_Default),
5019
+ createCss2Button(`btnBgmVolume`, `${g_stateObj.bgmVolume}${g_lblNameObj.percent}`, () => setBGMVolume(),
5020
+ Object.assign({
5021
+ cxtFunc: () => setBGMVolume(-1),
5022
+ }, g_lblPosObj.btnBgmVolume), g_cssObj.button_Default),
5023
+ createCss2Button(`btnBgmVolumeL`, `<`, () => setBGMVolume(-1),
5024
+ g_lblPosObj.btnBgmVolumeL, g_cssObj.button_Setting),
5025
+ createCss2Button(`btnBgmVolumeR`, `>`, () => setBGMVolume(),
5026
+ g_lblPosObj.btnBgmVolumeR, g_cssObj.button_Setting),
4938
5027
  );
4939
5028
  changeMSelect(0, _initFlg);
4940
5029
 
4941
5030
  let wheelCnt = 0;
4942
5031
  wheelHandler = g_handler.addListener(divRoot, `wheel`, e => {
5032
+
5033
+ // コメント欄(lblComment)のスクロール可能性をチェック
5034
+ const isScrollable = lblComment.scrollHeight > lblComment.clientHeight;
5035
+
5036
+ // マウスがコメント欄上にあり、スクロールが可能ならイベントをスキップ
5037
+ if (lblComment.contains(e.target) && isScrollable) {
5038
+ // スクロール位置の判定
5039
+ const atTop = lblComment.scrollTop === 0 && e.deltaY < 0;
5040
+ const atBottom = (lblComment.scrollTop + lblComment.clientHeight >= lblComment.scrollHeight) && e.deltaY > 0;
5041
+
5042
+ // スクロール可能&上端または下端ではないなら処理をスキップ
5043
+ if (!atTop && !atBottom) {
5044
+ return;
5045
+ }
5046
+ }
5047
+ e.preventDefault();
4943
5048
  if (g_stateObj.keyInitial && wheelCnt === 0) {
4944
- e.preventDefault();
4945
5049
  changeMSelect(e.deltaY > 0 ? 1 : -1);
4946
5050
  }
4947
5051
  wheelCnt = (wheelCnt + 1) % wheelCycle;
@@ -4949,6 +5053,7 @@ const titleInit = (_initFlg = false) => {
4949
5053
 
4950
5054
  // 初期表示用 (2秒後に選曲画面を表示)
4951
5055
  if (_initFlg && !g_headerObj.customTitleUse) {
5056
+ g_audio.muted = true;
4952
5057
  const mSelectTitleSprite = createEmptySprite(divRoot, `mSelectTitleSprite`,
4953
5058
  g_windowObj.mSelectTitleSprite, g_cssObj.settings_DifSelector);
4954
5059
  multiAppend(mSelectTitleSprite,
@@ -4967,6 +5072,8 @@ const titleInit = (_initFlg = false) => {
4967
5072
  if (_opacity <= 0) {
4968
5073
  clearTimeout(fadeOpacity);
4969
5074
  mSelectTitleSprite.style.display = C_DIS_NONE;
5075
+ g_audio.muted = false;
5076
+ g_audio.currentTime = g_headerObj.musicStarts[g_headerObj.musicIdxList[g_settings.musicIdxNum]] ?? 0;
4970
5077
  } else {
4971
5078
  mSelectTitleSprite.style.opacity = _opacity;
4972
5079
  fadeOpacity = setTimeout(() => {
@@ -5251,6 +5358,173 @@ const getCreatorInfo = (_creatorList) => {
5251
5358
  return [creatorName, creatorUrl, creatorIdx];
5252
5359
  }
5253
5360
 
5361
+ /**
5362
+ * BGMの停止
5363
+ */
5364
+ const pauseBGM = () => {
5365
+ if (g_audio) {
5366
+ g_handler.removeListener(g_stateObj.bgmTimeupdateEvtId);
5367
+ g_audio.pause();
5368
+ if (!(g_audio instanceof AudioPlayer)) {
5369
+ g_audio.removeAttribute('src');
5370
+ g_audio.load();
5371
+ }
5372
+ }
5373
+ [`bgmLooped`, `bgmFadeIn`, `bgmFadeOut`].forEach(id => {
5374
+ if (g_stateObj[id]) {
5375
+ clearInterval(g_stateObj[id]);
5376
+ g_stateObj[id] = null;
5377
+ }
5378
+ });
5379
+ };
5380
+
5381
+ /**
5382
+ * BGM再生処理
5383
+ * @param {number} _num
5384
+ * @param {number} _currentLoopNum
5385
+ */
5386
+ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5387
+ const FADE_STEP = 0.05 * g_stateObj.bgmVolume / 100;
5388
+ const FADE_INTERVAL_MS = 100;
5389
+ const FADE_DELAY_MS = 500;
5390
+
5391
+ const musicUrl = getMusicUrl(g_headerObj.viewLists[0]);
5392
+ const url = getFullMusicUrl(musicUrl);
5393
+ const encodeFlg = listMatching(musicUrl, [`.js`, `.txt`], { suffix: `$` });
5394
+ const musicStart = g_headerObj.musicStarts?.[g_headerObj.musicIdxList[g_settings.musicIdxNum]] ?? 0;
5395
+ const musicEnd = g_headerObj.musicEnds?.[g_headerObj.musicIdxList[g_settings.musicIdxNum]] ?? 0;
5396
+
5397
+ /**
5398
+ * BGMのフェードアウトとシーク
5399
+ */
5400
+ const fadeOutAndSeek = () => {
5401
+ let volume = g_audio.volume;
5402
+ const fadeInterval = setInterval(() => {
5403
+ if (volume > FADE_STEP && g_currentPage === `title`) {
5404
+ volume -= FADE_STEP;
5405
+ g_audio.volume = Math.max(volume, 0);
5406
+ } else {
5407
+ clearInterval(fadeInterval);
5408
+ g_stateObj.bgmFadeOut = null;
5409
+ g_audio.pause();
5410
+ g_audio.currentTime = musicStart;
5411
+
5412
+ // フェードイン開始
5413
+ if (g_currentPage === `title`) {
5414
+ setTimeout(() => {
5415
+ fadeIn();
5416
+ if (encodeFlg) {
5417
+ // base64エンコード時はtimeupdateイベントが発火しないため、
5418
+ // setIntervalで時間を取得する
5419
+ repeatBGM();
5420
+ }
5421
+ }, FADE_DELAY_MS);
5422
+ } else {
5423
+ pauseBGM();
5424
+ }
5425
+ }
5426
+ }, FADE_INTERVAL_MS);
5427
+ g_stateObj.bgmFadeOut = fadeInterval;
5428
+ };
5429
+
5430
+ /**
5431
+ * BGMのフェードイン
5432
+ */
5433
+ const fadeIn = () => {
5434
+ if (!(g_audio instanceof AudioPlayer) && !g_audio.src) {
5435
+ return;
5436
+ }
5437
+ let volume = 0;
5438
+ g_audio.play();
5439
+ const fadeInterval = setInterval(() => {
5440
+ if (volume < g_stateObj.bgmVolume / 100 && g_currentPage === `title`) {
5441
+ volume += FADE_STEP;
5442
+ g_audio.volume = Math.min(volume, 1);
5443
+ } else {
5444
+ clearInterval(fadeInterval);
5445
+ g_stateObj.bgmFadeIn = null;
5446
+ }
5447
+ }, FADE_INTERVAL_MS);
5448
+ g_stateObj.bgmFadeIn = fadeInterval;
5449
+ };
5450
+
5451
+ /**
5452
+ * BGMのループ処理
5453
+ */
5454
+ const repeatBGM = () => {
5455
+ if (encodeFlg) {
5456
+ // base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5457
+ const repeatCheck = setInterval((num = g_settings.musicIdxNum) => {
5458
+ try {
5459
+ if (((g_audio.elapsedTime >= musicEnd) ||
5460
+ num !== g_settings.musicIdxNum) && g_stateObj.bgmLooped !== null) {
5461
+ clearInterval(repeatCheck);
5462
+ g_stateObj.bgmLooped = null;
5463
+ fadeOutAndSeek();
5464
+ }
5465
+ } catch (e) {
5466
+ clearInterval(repeatCheck);
5467
+ g_stateObj.bgmLooped = null;
5468
+ }
5469
+ }, FADE_INTERVAL_MS);
5470
+ g_stateObj.bgmLooped = repeatCheck;
5471
+
5472
+ } else {
5473
+ g_stateObj.bgmTimeupdateEvtId = g_handler.addListener(g_audio, "timeupdate", () => {
5474
+ if (g_audio.currentTime >= musicEnd) {
5475
+ fadeOutAndSeek();
5476
+ }
5477
+ });
5478
+ }
5479
+ };
5480
+
5481
+ if (encodeFlg) {
5482
+ try {
5483
+ // base64エンコードは読込に時間が掛かるため、曲変更時のみ読込
5484
+ if (!hasVal(g_musicdata) || Math.abs(_num) % g_headerObj.musicIdxList.length !== 0) {
5485
+ await loadScript2(url);
5486
+ musicInit();
5487
+ if (_currentLoopNum !== g_settings.musicLoopNum) {
5488
+ return;
5489
+ }
5490
+ const tmpAudio = new AudioPlayer();
5491
+ const array = Uint8Array.from(atob(g_musicdata), v => v.charCodeAt(0));
5492
+ await tmpAudio.init(array.buffer);
5493
+ if (_currentLoopNum !== g_settings.musicLoopNum) {
5494
+ tmpAudio.close();
5495
+ return;
5496
+ }
5497
+ g_audio = tmpAudio;
5498
+ }
5499
+ g_audio.volume = g_stateObj.bgmVolume / 100;
5500
+ if (g_currentPage === `title`) {
5501
+ g_audio.currentTime = musicStart;
5502
+ g_audio.play();
5503
+ }
5504
+ } catch (e) {
5505
+ // 音源の読み込みに失敗した場合、エラーを表示
5506
+ console.warn(`BGM load error: ${e}`);
5507
+ }
5508
+
5509
+ } else {
5510
+ g_audio = new Audio();
5511
+ g_audio.src = url;
5512
+ g_audio.autoplay = false;
5513
+ g_audio.volume = g_stateObj.bgmVolume / 100;
5514
+ const loadedMeta = g_handler.addListener(g_audio, `loadedmetadata`, () => {
5515
+ g_handler.removeListener(loadedMeta);
5516
+ if (_currentLoopNum !== g_settings.musicLoopNum) {
5517
+ return;
5518
+ }
5519
+ g_audio.currentTime = musicStart;
5520
+ g_audio.play();
5521
+ }, { once: true });
5522
+ }
5523
+ if (musicEnd > 0) {
5524
+ repeatBGM();
5525
+ }
5526
+ };
5527
+
5254
5528
  /**
5255
5529
  * 選曲ボタンを押したときの処理
5256
5530
  * @param {number} _num
@@ -5258,12 +5532,11 @@ const getCreatorInfo = (_creatorList) => {
5258
5532
  */
5259
5533
  const changeMSelect = (_num, _initFlg = false) => {
5260
5534
  const limitedMLength = 35;
5261
- const musicMaxIdx = Math.max(...g_headerObj.musicNos);
5262
- const musicIdxTmpList = [...Array(musicMaxIdx + 1).keys()];
5535
+ pauseBGM();
5263
5536
 
5264
5537
  // 選択方向に合わせて楽曲リスト情報を再取得
5265
5538
  for (let j = -g_settings.mSelectableTerms; j <= g_settings.mSelectableTerms; j++) {
5266
- const idx = (j + _num + g_settings.musicIdxNum + musicIdxTmpList.length * 10) % musicIdxTmpList.length;
5539
+ const idx = g_headerObj.musicIdxList[(j + _num + g_settings.musicIdxNum + g_headerObj.musicIdxList.length * 10) % g_headerObj.musicIdxList.length];
5267
5540
  if (j === 0) {
5268
5541
  } else {
5269
5542
  document.getElementById(`btnMusicSelect${j}`).style.fontSize =
@@ -5274,33 +5547,39 @@ const changeMSelect = (_num, _initFlg = false) => {
5274
5547
  }
5275
5548
  }
5276
5549
  // 現在選択中の楽曲IDを再設定
5277
- g_settings.musicIdxNum = (g_settings.musicIdxNum + _num + musicIdxTmpList.length) % musicIdxTmpList.length;
5550
+ g_settings.musicIdxNum = (g_settings.musicIdxNum + _num + g_headerObj.musicIdxList.length) % g_headerObj.musicIdxList.length;
5551
+
5552
+ // 楽曲の多重読込防止(この値が変化していれば読み込まない)
5553
+ g_settings.musicLoopNum++;
5554
+ const currentLoopNum = g_settings.musicLoopNum;
5278
5555
 
5279
5556
  // 選択した楽曲に対応する譜面番号、製作者情報、曲長を取得
5280
5557
  g_headerObj.viewLists = [];
5281
- const tmpKeyList = [], tmpCreatorList = [], tmpPlayingFrameList = [];
5558
+ const tmpKeyList = [], tmpCreatorList = [], tmpPlayingFrameList = [], tmpBpmList = [];
5559
+ const targetIdx = g_headerObj.musicIdxList[(g_settings.musicIdxNum + g_headerObj.musicIdxList.length * 20) % g_headerObj.musicIdxList.length];
5282
5560
  g_headerObj.musicNos.forEach((val, j) => {
5283
- if (val === (g_settings.musicIdxNum + musicIdxTmpList.length * 20) %
5284
- musicIdxTmpList.length) {
5561
+ if ((g_headerObj.musicGroups?.[val] ?? val) === targetIdx) {
5285
5562
  g_headerObj.viewLists.push(j);
5286
5563
  tmpKeyList.push(g_headerObj.keyLabels[j]);
5287
5564
  tmpCreatorList.push(g_headerObj.creatorNames[j]);
5288
5565
  tmpPlayingFrameList.push(g_detailObj.playingFrameWithBlank[j]);
5566
+ tmpBpmList.push(g_headerObj.bpms[g_headerObj.musicNos[j]]);
5289
5567
  }
5290
5568
  });
5291
- const playingFrames = makeDedupliArray(tmpPlayingFrameList.sort((a, b) => a - b).map(val => transFrameToTimer(val))).join(`, `);
5569
+ const playingFrames = makeDedupliArray(tmpPlayingFrameList.map(val => transFrameToTimer(val))).join(`, `);
5570
+ const bpm = makeDedupliArray(tmpBpmList).join(`, `);
5292
5571
  const [creatorName, creatorUrl, creatorIdx] = getCreatorInfo(tmpCreatorList);
5293
5572
  const creatorLink = creatorIdx >= 0 ?
5294
5573
  `<a href="${creatorUrl}" target="_blank">${creatorName}</a>` : creatorName;
5295
5574
 
5296
5575
  // 選択した楽曲の情報表示
5297
- const idx = g_settings.musicIdxNum;
5576
+ const idx = g_headerObj.musicIdxList[g_settings.musicIdxNum];
5298
5577
  document.getElementById(`lblMusicSelect`).innerHTML =
5299
5578
  `<span style="font-size:${getFontSize(g_headerObj.musicTitlesForView[idx].join(`<br>`), g_btnWidth(1 / 2), getBasicFont(), 18)}px;` +
5300
5579
  `font-weight:bold">${g_headerObj.musicTitlesForView[idx].join(`<br>`)}</span>`;
5301
5580
  document.getElementById(`lblMusicSelectDetail`).innerHTML =
5302
5581
  `Maker: ${creatorLink} / Artist: <a href="${g_headerObj.artistUrls[idx]}" target="_blank">` +
5303
- `${g_headerObj.artistNames[idx]}</a><br>Duration: ${playingFrames} / BPM: ${g_headerObj.bpms[idx]}`;
5582
+ `${g_headerObj.artistNames[idx]}</a><br>Duration: ${playingFrames} / BPM: ${bpm}`;
5304
5583
 
5305
5584
  // 選択した楽曲で使われているキー種の一覧を作成
5306
5585
  deleteChildspriteAll(`keyTitleSprite`);
@@ -5312,14 +5591,14 @@ const changeMSelect = (_num, _initFlg = false) => {
5312
5591
 
5313
5592
 
5314
5593
  // 選択した楽曲の選択位置を表示
5315
- lblMusicCnt.innerHTML = `${g_settings.musicIdxNum + 1} / ${musicMaxIdx + 1}`;
5594
+ lblMusicCnt.innerHTML = `${g_settings.musicIdxNum + 1} / ${g_headerObj.musicIdxList.length}`;
5316
5595
 
5317
5596
  // 楽曲別のローカルストレージを再取得
5318
5597
  loadLocalStorage(g_settings.musicIdxNum);
5319
5598
  viewKeyStorage.cache = new Map();
5320
5599
 
5321
5600
  // 初期化もしくは楽曲変更時に速度を初期化
5322
- if (_initFlg || _num !== 0) {
5601
+ if (_initFlg || Math.abs(_num) % g_headerObj.musicIdxList.length !== 0) {
5323
5602
  g_stateObj.speed = g_headerObj.initSpeeds[g_headerObj.viewLists[0]];
5324
5603
  g_settings.speedNum = getCurrentNo(g_settings.speeds, g_stateObj.speed);
5325
5604
  }
@@ -5327,6 +5606,19 @@ const changeMSelect = (_num, _initFlg = false) => {
5327
5606
  // コメント文の加工
5328
5607
  lblComment.innerHTML = convertStrToVal(g_headerObj[`commentVal${g_settings.musicIdxNum}`]);
5329
5608
 
5609
+ // BGM再生処理
5610
+ if (!g_stateObj.bgmMuteFlg) {
5611
+ if (_initFlg) {
5612
+ playBGM(_num);
5613
+ } else {
5614
+ setTimeout(() => {
5615
+ if (currentLoopNum === g_settings.musicLoopNum) {
5616
+ playBGM(_num, currentLoopNum);
5617
+ }
5618
+ }, 500);
5619
+ }
5620
+ }
5621
+
5330
5622
  // 選曲変更時のカスタム関数実行
5331
5623
  g_customJsObj.musicSelect.forEach(func => func(g_settings.musicIdxNum));
5332
5624
  };
@@ -5830,11 +6122,7 @@ const optionInit = () => {
5830
6122
  g_stateObj.filterKeys = ``;
5831
6123
 
5832
6124
  // 楽曲データの表示
5833
- let text = `♪` + (g_headerObj.musicSelectUse ? `${g_headerObj.musicTitles[g_settings.musicIdxNum]} / ` : ``) +
5834
- `BPM: ${g_headerObj.bpms[g_settings.musicIdxNum]}`;
5835
- if (!g_headerObj.musicSelectUse && g_headerObj.bpms[g_settings.musicIdxNum] === `----`) {
5836
- text = ``;
5837
- }
6125
+ const text = getMusicInfoView();
5838
6126
  divRoot.appendChild(createDivCss2Label(`lblMusicInfo`, text,
5839
6127
  Object.assign({ siz: getFontSize(text, g_btnWidth(3 / 4), getBasicFont(), 12) }, g_lblPosObj.lblMusicInfo)));
5840
6128
 
@@ -5858,6 +6146,20 @@ const optionInit = () => {
5858
6146
  g_skinJsObj.option.forEach(func => func());
5859
6147
  };
5860
6148
 
6149
+ /**
6150
+ * 設定画面に表示する楽曲・BPM情報の取得
6151
+ * @returns {string}
6152
+ */
6153
+ const getMusicInfoView = () => {
6154
+ const idx = g_headerObj.musicNos[g_stateObj.scoreId];
6155
+ let text = `♪` + (g_headerObj.musicSelectUse ? `${g_headerObj.musicTitles[idx]} / ` : ``) +
6156
+ `BPM: ${g_headerObj.bpms[idx]}`;
6157
+ if (!g_headerObj.musicSelectUse && g_headerObj.bpms[idx] === `----`) {
6158
+ text = ``;
6159
+ }
6160
+ return text;
6161
+ };
6162
+
5861
6163
  /**
5862
6164
  * 設定画面用スプライトリストの作成
5863
6165
  * @param {object} _settingList (設定名、縦位置、縦位置差分、幅差分、高さ差分)を設定別にリスト化
@@ -6686,6 +6988,10 @@ const setDifficulty = (_initFlg) => {
6686
6988
  makeHighScore(g_stateObj.scoreId);
6687
6989
  }
6688
6990
 
6991
+ // 楽曲データの表示
6992
+ lblMusicInfo.textContent = getMusicInfoView();
6993
+ lblMusicInfo.style.fontSize = wUnit(getFontSize(lblMusicInfo.textContent, g_btnWidth(3 / 4), getBasicFont(), 12));
6994
+
6689
6995
  // ユーザカスタムイベント(初期)
6690
6996
  g_customJsObj.difficulty.forEach(func => func(_initFlg, g_canLoadDifInfoFlg));
6691
6997
 
@@ -8834,13 +9140,7 @@ const loadMusic = () => {
8834
9140
  g_currentPage = `loading`;
8835
9141
 
8836
9142
  const musicUrl = getMusicUrl(g_stateObj.scoreId);
8837
- let url = `${g_rootPath}../${g_headerObj.musicFolder}/${musicUrl}`;
8838
- if (musicUrl.indexOf(C_MRK_CURRENT_DIRECTORY) !== -1) {
8839
- url = musicUrl.split(C_MRK_CURRENT_DIRECTORY)[1];
8840
- } else if (g_headerObj.musicFolder.indexOf(C_MRK_CURRENT_DIRECTORY) !== -1) {
8841
- url = `${g_headerObj.musicFolder.split(C_MRK_CURRENT_DIRECTORY)[1]}/${musicUrl}`;
8842
- }
8843
-
9143
+ const url = getFullMusicUrl(musicUrl);
8844
9144
  g_headerObj.musicUrl = musicUrl;
8845
9145
  g_musicEncodedFlg = listMatching(musicUrl, [`.js`, `.txt`], { suffix: `$` });
8846
9146
 
@@ -8902,6 +9202,7 @@ const setAudio = async (_url) => {
8902
9202
 
8903
9203
  const loadMp3 = () => {
8904
9204
  if (g_isFile) {
9205
+ g_audio = new Audio();
8905
9206
  g_audio.src = _url;
8906
9207
  musicAfterLoaded();
8907
9208
  } else {
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2019/11/19
8
- * Revised : 2025/04/21 (v41.0.2)
8
+ * Revised : 2025/05/09 (v41.2.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -288,6 +288,21 @@ const updateWindowSiz = () => {
288
288
  siz: g_limitObj.difSelectorSiz, align: C_ALIGN_LEFT,
289
289
  overflow: C_DIS_AUTO, whiteSpace: `normal`,
290
290
  },
291
+ btnBgmMute: {
292
+ x: g_btnX() + 90, y: g_sHeight - 105, w: 40, h: 35, siz: 30,
293
+ },
294
+ lblBgmVolume: {
295
+ x: g_btnX(), y: g_sHeight - 85, w: g_btnWidth(1 / 4), h: 20, siz: 12, align: C_ALIGN_LEFT,
296
+ },
297
+ btnBgmVolume: {
298
+ x: g_btnX() + 20, y: g_sHeight - 70, w: g_btnWidth(1 / 4) - 40, h: 20, siz: 14,
299
+ },
300
+ btnBgmVolumeL: {
301
+ x: g_btnX(), y: g_sHeight - 70, w: 20, h: 20, siz: 12,
302
+ },
303
+ btnBgmVolumeR: {
304
+ x: g_btnX() + g_btnWidth(1 / 4) - 20, y: g_sHeight - 70, w: 20, h: 20, siz: 12,
305
+ },
291
306
 
292
307
  /** データ管理 */
293
308
  btnResetBack: {
@@ -999,6 +1014,13 @@ let C_WOD_FRAME = 30;
999
1014
  // 譜面データ持ち回り用
1000
1015
  const g_stateObj = {
1001
1016
  keyInitial: false,
1017
+ bgmVolume: 50,
1018
+ bgmLooped: null,
1019
+ bgmFadeIn: null,
1020
+ bgmFadeOut: null,
1021
+ bgmTimeupdateEvtId: null,
1022
+ bgmMuteFlg: false,
1023
+
1002
1024
  dosDivideFlg: false,
1003
1025
  scoreLockFlg: false,
1004
1026
  scoreId: 0,
@@ -1015,6 +1037,7 @@ const g_stateObj = {
1015
1037
  hitPosition: 0,
1016
1038
  fadein: 0,
1017
1039
  volume: 100,
1040
+
1018
1041
  lifeRcv: 2,
1019
1042
  lifeDmg: 7,
1020
1043
  lifeMode: `Border`,
@@ -1115,6 +1138,7 @@ const makeSpeedList = (_minSpd, _maxSpd) => [...Array((_maxSpd - _minSpd) * 20 +
1115
1138
  const g_settings = {
1116
1139
 
1117
1140
  musicIdxNum: 0,
1141
+ musicLoopNum: 0,
1118
1142
  dataMgtNum: {
1119
1143
  environment: 0,
1120
1144
  highscores: 0,
@@ -1163,6 +1187,7 @@ const g_settings = {
1163
1187
 
1164
1188
  volumes: [0, 0.5, 1, 2, 5, 10, 25, 50, 75, 100],
1165
1189
  volumeNum: 0,
1190
+ bgmVolumeNum: 0,
1166
1191
 
1167
1192
  appearances: [`Visible`, `Hidden`, `Hidden+`, `Sudden`, `Sudden+`, `Hid&Sud+`],
1168
1193
  appearanceNum: 0,
@@ -1239,6 +1264,7 @@ const g_settings = {
1239
1264
  };
1240
1265
 
1241
1266
  g_settings.volumeNum = g_settings.volumes.length - 1;
1267
+ g_settings.bgmVolumeNum = roundZero(g_settings.volumes.findIndex(v => v === g_stateObj.bgmVolume));
1242
1268
  g_settings.opacityNum = g_settings.opacitys.length - 1;
1243
1269
 
1244
1270
  /**
@@ -2075,6 +2101,10 @@ const g_shortcutObj = {
2075
2101
  KeyD: { id: `btnReset` },
2076
2102
  ArrowUp: { id: `btnMusicSelectPrev` },
2077
2103
  ArrowDown: { id: `btnMusicSelectNext` },
2104
+ ArrowLeft: { id: `btnBgmVolumeL` },
2105
+ ArrowRight: { id: `btnBgmVolumeR` },
2106
+ KeyM: { id: `btnBgmMute` },
2107
+ KeyR: { id: `btnMusicSelectRandom` },
2078
2108
  },
2079
2109
  dataMgt: {
2080
2110
  KeyE: { id: `btnEnvironment` },
@@ -2433,7 +2463,7 @@ Object.keys(g_btnWaitFrame).forEach(key => {
2433
2463
  // - btn + プロパティ名に合致するボタンid名に対して、
2434
2464
  // どの位置(X方向)にショートカット名を表示するかを設定
2435
2465
  const g_btnPatterns = {
2436
- title: { Start: 0, Comment: -10 },
2466
+ title: { Start: 0, Comment: -10, MusicSelectRandom: -10 },
2437
2467
  dataMgt: { Back: 0, Environment: -35, Highscores: -35, CustomKey: -35, Others: -35 },
2438
2468
  precondition: { Back: 0 },
2439
2469
  option: { Back: 0, KeyConfig: 0, Play: 0, Display: -5, Save: -10, Graph: -25 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "41.0.3",
3
+ "version": "41.2.0",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "scripts": {