@onjmin/dtm 2.1.1 → 2.1.3
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/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +157 -21
- package/dist/index.mjs +157 -21
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -2077,6 +2077,13 @@ type DawOptions = {
|
|
|
2077
2077
|
customVocals?: CustomVocalDef[];
|
|
2078
2078
|
defaultBpm?: number;
|
|
2079
2079
|
initialMML?: string;
|
|
2080
|
+
/**
|
|
2081
|
+
* ループ再生の初期状態(既定 false)。
|
|
2082
|
+
* MML読み込み時に #loop=on があればそちらが優先される。
|
|
2083
|
+
*/
|
|
2084
|
+
initialLoop?: boolean;
|
|
2085
|
+
/** ループ再生のON/OFFが変化したときに呼ばれる */
|
|
2086
|
+
onLoopChange?: (loop: boolean) => void;
|
|
2080
2087
|
/** 利用規約への同意画面の表示をスキップするかどうか */
|
|
2081
2088
|
skipConsent?: boolean;
|
|
2082
2089
|
/**
|
|
@@ -2123,6 +2130,10 @@ type DawInstance = {
|
|
|
2123
2130
|
loadMIDI: (bytes: Uint8Array) => void | Promise<void>;
|
|
2124
2131
|
exportMIDI: () => Blob;
|
|
2125
2132
|
setBpm: (bpm: number) => void;
|
|
2133
|
+
/** 現在のループ再生状態(ON/OFF)を返す */
|
|
2134
|
+
getLoop: () => boolean;
|
|
2135
|
+
/** ループ再生状態を設定する。UIおよび再生シーケンサへ反映される。 */
|
|
2136
|
+
setLoop: (loop: boolean) => void;
|
|
2126
2137
|
getPlaybackState: () => PlaybackState;
|
|
2127
2138
|
getCurrentPlayStep: () => number;
|
|
2128
2139
|
forcePauseAt: (step: number) => void;
|
|
@@ -2787,6 +2798,11 @@ type MmlMeta = {
|
|
|
2787
2798
|
* チャンネルストリップ共通のパラメータ(歌詞トラック固有の `vocalDelay`/`d`トークンとは別軸)。
|
|
2788
2799
|
*/
|
|
2789
2800
|
trackDelaySend?: Record<number, number>;
|
|
2801
|
+
/**
|
|
2802
|
+
* ループ再生の指定(#loop=on / off)。BGM等で曲末停止せずシームレスにループさせる。
|
|
2803
|
+
* 省略時はオフ。
|
|
2804
|
+
*/
|
|
2805
|
+
loop?: boolean;
|
|
2790
2806
|
};
|
|
2791
2807
|
/** MMLからトップレベル宣言を抽出する */
|
|
2792
2808
|
declare const parseMmlMeta: (mml: string) => MmlMeta;
|
package/dist/index.d.ts
CHANGED
|
@@ -2077,6 +2077,13 @@ type DawOptions = {
|
|
|
2077
2077
|
customVocals?: CustomVocalDef[];
|
|
2078
2078
|
defaultBpm?: number;
|
|
2079
2079
|
initialMML?: string;
|
|
2080
|
+
/**
|
|
2081
|
+
* ループ再生の初期状態(既定 false)。
|
|
2082
|
+
* MML読み込み時に #loop=on があればそちらが優先される。
|
|
2083
|
+
*/
|
|
2084
|
+
initialLoop?: boolean;
|
|
2085
|
+
/** ループ再生のON/OFFが変化したときに呼ばれる */
|
|
2086
|
+
onLoopChange?: (loop: boolean) => void;
|
|
2080
2087
|
/** 利用規約への同意画面の表示をスキップするかどうか */
|
|
2081
2088
|
skipConsent?: boolean;
|
|
2082
2089
|
/**
|
|
@@ -2123,6 +2130,10 @@ type DawInstance = {
|
|
|
2123
2130
|
loadMIDI: (bytes: Uint8Array) => void | Promise<void>;
|
|
2124
2131
|
exportMIDI: () => Blob;
|
|
2125
2132
|
setBpm: (bpm: number) => void;
|
|
2133
|
+
/** 現在のループ再生状態(ON/OFF)を返す */
|
|
2134
|
+
getLoop: () => boolean;
|
|
2135
|
+
/** ループ再生状態を設定する。UIおよび再生シーケンサへ反映される。 */
|
|
2136
|
+
setLoop: (loop: boolean) => void;
|
|
2126
2137
|
getPlaybackState: () => PlaybackState;
|
|
2127
2138
|
getCurrentPlayStep: () => number;
|
|
2128
2139
|
forcePauseAt: (step: number) => void;
|
|
@@ -2787,6 +2798,11 @@ type MmlMeta = {
|
|
|
2787
2798
|
* チャンネルストリップ共通のパラメータ(歌詞トラック固有の `vocalDelay`/`d`トークンとは別軸)。
|
|
2788
2799
|
*/
|
|
2789
2800
|
trackDelaySend?: Record<number, number>;
|
|
2801
|
+
/**
|
|
2802
|
+
* ループ再生の指定(#loop=on / off)。BGM等で曲末停止せずシームレスにループさせる。
|
|
2803
|
+
* 省略時はオフ。
|
|
2804
|
+
*/
|
|
2805
|
+
loop?: boolean;
|
|
2790
2806
|
};
|
|
2791
2807
|
/** MMLからトップレベル宣言を抽出する */
|
|
2792
2808
|
declare const parseMmlMeta: (mml: string) => MmlMeta;
|
package/dist/index.js
CHANGED
|
@@ -4395,7 +4395,7 @@ var NATURAL_STEPS2 = {
|
|
|
4395
4395
|
};
|
|
4396
4396
|
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
4397
4397
|
var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
4398
|
-
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w:-]+)/gi;
|
|
4398
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo|loop)=([\w:-]+)/gi;
|
|
4399
4399
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
4400
4400
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
4401
4401
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -4448,6 +4448,9 @@ var parseMmlMeta = (mml) => {
|
|
|
4448
4448
|
} else if (key === "edo") {
|
|
4449
4449
|
const e = Number.parseInt(m[2], 10);
|
|
4450
4450
|
if (e === 12 || e === 31) meta.edo = e;
|
|
4451
|
+
} else if (key === "loop") {
|
|
4452
|
+
const v = m[2].toLowerCase();
|
|
4453
|
+
meta.loop = v === "on" || v === "1" || v === "true";
|
|
4451
4454
|
}
|
|
4452
4455
|
}
|
|
4453
4456
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -4551,6 +4554,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
4551
4554
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
4552
4555
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
4553
4556
|
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
4557
|
+
if (meta.loop) parts.push("#loop=on");
|
|
4554
4558
|
if (meta.trackInstruments) {
|
|
4555
4559
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
4556
4560
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -5078,7 +5082,11 @@ var createSequencer = (options) => {
|
|
|
5078
5082
|
if (!active) return;
|
|
5079
5083
|
const sps = secondsPerStep();
|
|
5080
5084
|
const time = options.getAudioTime() - startTime;
|
|
5081
|
-
|
|
5085
|
+
try {
|
|
5086
|
+
options.onTick(getWrappedPlayStep(time, sps));
|
|
5087
|
+
} catch (err2) {
|
|
5088
|
+
console.error("[sequencer] error in onTick callback:", err2);
|
|
5089
|
+
}
|
|
5082
5090
|
animationId = requestAnimationFrame(animate);
|
|
5083
5091
|
};
|
|
5084
5092
|
const stop = () => {
|
|
@@ -10840,6 +10848,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
10840
10848
|
options.onStop?.();
|
|
10841
10849
|
};
|
|
10842
10850
|
let isSeeking = false;
|
|
10851
|
+
let lastCommittedStep = null;
|
|
10843
10852
|
const seekTo = (step) => {
|
|
10844
10853
|
const clamped = Math.min(maxStep, Math.max(0, Math.round(step)));
|
|
10845
10854
|
renderPlayhead(clamped);
|
|
@@ -10851,16 +10860,42 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
10851
10860
|
play(clamped);
|
|
10852
10861
|
}
|
|
10853
10862
|
};
|
|
10854
|
-
|
|
10863
|
+
const commitSeek = () => {
|
|
10864
|
+
isSeeking = false;
|
|
10865
|
+
const step = Number(seekInput.value);
|
|
10866
|
+
if (lastCommittedStep !== step) {
|
|
10867
|
+
lastCommittedStep = step;
|
|
10868
|
+
seekTo(step);
|
|
10869
|
+
}
|
|
10870
|
+
};
|
|
10871
|
+
seekInput.addEventListener("pointerdown", (e) => {
|
|
10855
10872
|
isSeeking = true;
|
|
10873
|
+
lastCommittedStep = null;
|
|
10874
|
+
try {
|
|
10875
|
+
seekInput.setPointerCapture(e.pointerId);
|
|
10876
|
+
} catch {
|
|
10877
|
+
}
|
|
10856
10878
|
});
|
|
10857
10879
|
seekInput.addEventListener("input", () => {
|
|
10858
10880
|
renderPlayhead(Number(seekInput.value));
|
|
10859
10881
|
syncSeekFill();
|
|
10860
10882
|
});
|
|
10861
10883
|
seekInput.addEventListener("change", () => {
|
|
10884
|
+
commitSeek();
|
|
10885
|
+
});
|
|
10886
|
+
seekInput.addEventListener("pointerup", (e) => {
|
|
10887
|
+
try {
|
|
10888
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
10889
|
+
} catch {
|
|
10890
|
+
}
|
|
10891
|
+
commitSeek();
|
|
10892
|
+
});
|
|
10893
|
+
seekInput.addEventListener("pointercancel", (e) => {
|
|
10894
|
+
try {
|
|
10895
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
10896
|
+
} catch {
|
|
10897
|
+
}
|
|
10862
10898
|
isSeeking = false;
|
|
10863
|
-
seekTo(Number(seekInput.value));
|
|
10864
10899
|
});
|
|
10865
10900
|
playBtn.addEventListener("click", () => {
|
|
10866
10901
|
if (playing) pause();
|
|
@@ -12405,19 +12440,45 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
12405
12440
|
}
|
|
12406
12441
|
play(idx);
|
|
12407
12442
|
};
|
|
12408
|
-
|
|
12443
|
+
let lastCommittedSec = null;
|
|
12444
|
+
const commitSeek = () => {
|
|
12445
|
+
isSeeking = false;
|
|
12446
|
+
if (totalSec <= 0) return;
|
|
12447
|
+
const sec = Number(seekInput.value);
|
|
12448
|
+
if (lastCommittedSec !== sec) {
|
|
12449
|
+
lastCommittedSec = sec;
|
|
12450
|
+
const idx = getActiveIndexBySec(sec);
|
|
12451
|
+
seekTo(idx >= 0 ? idx : Math.max(0, chordEvents.length - 1));
|
|
12452
|
+
}
|
|
12453
|
+
};
|
|
12454
|
+
seekInput.addEventListener("pointerdown", (e) => {
|
|
12409
12455
|
isSeeking = true;
|
|
12456
|
+
lastCommittedSec = null;
|
|
12457
|
+
try {
|
|
12458
|
+
seekInput.setPointerCapture(e.pointerId);
|
|
12459
|
+
} catch {
|
|
12460
|
+
}
|
|
12410
12461
|
});
|
|
12411
12462
|
seekInput.addEventListener("input", () => {
|
|
12412
12463
|
updateTimeDisplay(Number(seekInput.value));
|
|
12413
12464
|
syncSeekFill();
|
|
12414
12465
|
});
|
|
12415
12466
|
seekInput.addEventListener("change", () => {
|
|
12467
|
+
commitSeek();
|
|
12468
|
+
});
|
|
12469
|
+
seekInput.addEventListener("pointerup", (e) => {
|
|
12470
|
+
try {
|
|
12471
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
12472
|
+
} catch {
|
|
12473
|
+
}
|
|
12474
|
+
commitSeek();
|
|
12475
|
+
});
|
|
12476
|
+
seekInput.addEventListener("pointercancel", (e) => {
|
|
12477
|
+
try {
|
|
12478
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
12479
|
+
} catch {
|
|
12480
|
+
}
|
|
12416
12481
|
isSeeking = false;
|
|
12417
|
-
if (totalSec <= 0) return;
|
|
12418
|
-
const sec = Number(seekInput.value);
|
|
12419
|
-
const idx = getActiveIndexBySec(sec);
|
|
12420
|
-
seekTo(idx >= 0 ? idx : Math.max(0, chordEvents.length - 1));
|
|
12421
12482
|
});
|
|
12422
12483
|
const stop = () => {
|
|
12423
12484
|
if (isLoading) {
|
|
@@ -15530,6 +15591,15 @@ var buildUI = (target, options) => {
|
|
|
15530
15591
|
</select>
|
|
15531
15592
|
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
15532
15593
|
</div>
|
|
15594
|
+
<div class="dtm-row">
|
|
15595
|
+
<span class="dtm-label">\u30EB\u30FC\u30D7\u518D\u751F</span>
|
|
15596
|
+
<label class="dtm-toggle">
|
|
15597
|
+
<input type="checkbox" data-dtm="loop-toggle">
|
|
15598
|
+
<span data-dtm="loop-toggle-label">OFF</span>
|
|
15599
|
+
</label>
|
|
15600
|
+
<span class="dtm-grow"></span>
|
|
15601
|
+
<button class="dtm-infobtn" data-dtm="loop-info" title="\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
15602
|
+
</div>
|
|
15533
15603
|
<div class="dtm-row">
|
|
15534
15604
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
15535
15605
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -15867,6 +15937,9 @@ var buildUI = (target, options) => {
|
|
|
15867
15937
|
vScrollThumb: sel("vscroll-thumb"),
|
|
15868
15938
|
hScroll: sel("hscroll"),
|
|
15869
15939
|
hScrollThumb: sel("hscroll-thumb"),
|
|
15940
|
+
loopToggle: sel("loop-toggle"),
|
|
15941
|
+
loopToggleLabel: sel("loop-toggle-label"),
|
|
15942
|
+
loopInfoBtn: sel("loop-info"),
|
|
15870
15943
|
masterVolume: sel("master-volume"),
|
|
15871
15944
|
masterVolumeLabel: sel("master-volume-label"),
|
|
15872
15945
|
masterComp: sel("master-comp"),
|
|
@@ -18050,6 +18123,16 @@ var TRACK_DELAYSEND_INFO_HTML = `
|
|
|
18050
18123
|
<p>[delay]\u30D1\u30CD\u30EB\u306E\u639B\u304B\u308A\u5177\u5408\u304C0%\u3060\u3068\u3001\u3053\u3053\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u300C\u9001\u308A\u91CF\uFF08\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304C\u3069\u308C\u3060\u3051\u63D0\u4F9B\u3059\u308B\u304B\uFF09\u300D\u3068\u300C\u30DE\u30B9\u30BF\u306E\u30C7\u30A3\u30EC\u30A4\u8A2D\u5B9A\uFF08\u5B9F\u969B\u306B\u3069\u3093\u306A\u7E70\u308A\u8FD4\u3057\u304C\u639B\u304B\u308B\u304B\uFF09\u300D\u306E\u4E8C\u6BB5\u69CB\u3048\u306B\u306A\u3063\u3066\u3044\u308B\u305F\u3081\u3067\u3059\u3002\u4E21\u65B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
|
|
18051
18124
|
</div>
|
|
18052
18125
|
`;
|
|
18126
|
+
var LOOP_INFO_HTML = `
|
|
18127
|
+
<div class="dtm-modal-body-content">
|
|
18128
|
+
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
18129
|
+
<p>\u30EB\u30FC\u30D7\u518D\u751F\u3092\u6709\u52B9\u306B\u3057\u307E\u3059\u3002ON\u306B\u3059\u308B\u3068\u3001\u518D\u751F\u304C\u66F2\u672B\u3067\u505C\u6B62\u305B\u305A\u3001\u5148\u982D\u304B\u3089\u30B7\u30FC\u30E0\u30EC\u30B9\u306B\u7E70\u308A\u8FD4\u3055\u308C\u307E\u3059\u3002</p>
|
|
18130
|
+
<h4>MML\u51FA\u529B\u3078\u306E\u53CD\u6620</h4>
|
|
18131
|
+
<p>\u30EB\u30FC\u30D7\u518D\u751F\u304CON\u306E\u5834\u5408\u3001MML\u751F\u6210\u6642\u306B <code>#loop=on</code> \u30E1\u30BF\u884C\u304C\u51FA\u529B\u3055\u308C\u307E\u3059\u3002\u57CB\u3081\u8FBC\u307F\u30D7\u30EC\u30A4\u30E4\u30FC\u3084\u518D\u751F\u5C02\u7528\u30D7\u30EC\u30A4\u30E4\u30FC\uFF08mountPlayer / mountChordPlayer\uFF09\u3067\u3082\u3053\u306EMML\u3092\u8AAD\u307F\u8FBC\u3080\u3053\u3068\u3067\u81EA\u52D5\u7684\u306B\u30EB\u30FC\u30D7\u518D\u751F\u3055\u308C\u307E\u3059\u3002</p>
|
|
18132
|
+
<h4>BGM\u3084\u30B2\u30FC\u30E0\u97F3\u697D\u5411\u3051</h4>
|
|
18133
|
+
<p>Web\u30B5\u30A4\u30C8\u306EBGM\u3084\u30B2\u30FC\u30E0\u306E\u30B9\u30C6\u30FC\u30B8\u66F2\u306A\u3069\u3001\u9014\u5207\u308C\u305A\u306B\u30EB\u30FC\u30D7\u3055\u305B\u305F\u3044\u66F2\u306E\u5236\u4F5C\u306B\u6700\u9069\u3067\u3059\u3002</p>
|
|
18134
|
+
</div>
|
|
18135
|
+
`;
|
|
18053
18136
|
var MASTER_COMP_INFO_HTML = `
|
|
18054
18137
|
<div class="dtm-modal-body-content">
|
|
18055
18138
|
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
@@ -18595,6 +18678,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
18595
18678
|
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18596
18679
|
};
|
|
18597
18680
|
applyMasterVolume(masterVolume);
|
|
18681
|
+
let loopEnabled = options.initialLoop ?? false;
|
|
18682
|
+
const applyLoop = (enabled) => {
|
|
18683
|
+
loopEnabled = enabled;
|
|
18684
|
+
if (refs.loopToggle) {
|
|
18685
|
+
refs.loopToggle.checked = enabled;
|
|
18686
|
+
}
|
|
18687
|
+
if (refs.loopToggleLabel) {
|
|
18688
|
+
refs.loopToggleLabel.textContent = enabled ? "ON" : "OFF";
|
|
18689
|
+
}
|
|
18690
|
+
options.onLoopChange?.(enabled);
|
|
18691
|
+
};
|
|
18692
|
+
applyLoop(loopEnabled);
|
|
18598
18693
|
const snapToEdoGrid = (u) => {
|
|
18599
18694
|
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
18600
18695
|
return units(Math.round(u / upr) * upr);
|
|
@@ -19656,6 +19751,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19656
19751
|
getPlayStartStep: () => playStartStep,
|
|
19657
19752
|
getDrumPattern: (currentBar) => resolveDrumPattern(currentDrumPattern, drumPatterns, currentBar),
|
|
19658
19753
|
getSoloTrackId: () => isSolo ? activeTrackId : null,
|
|
19754
|
+
getLoop: () => loopEnabled,
|
|
19659
19755
|
getAudioTime,
|
|
19660
19756
|
onPlayNote: (e) => {
|
|
19661
19757
|
if (audioMutedTracks.has(e.trackId)) return;
|
|
@@ -19683,10 +19779,24 @@ var mountDAW = (target, options = {}) => {
|
|
|
19683
19779
|
getMaxOffsetX()
|
|
19684
19780
|
);
|
|
19685
19781
|
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19782
|
+
} else if (currentPlayStep < currentOffsetX / renderConfig.stepWidth) {
|
|
19783
|
+
const visibleBars = Math.max(
|
|
19784
|
+
1,
|
|
19785
|
+
Math.round(visibleSteps / renderConfig.stepsPerBar)
|
|
19786
|
+
);
|
|
19787
|
+
currentOffsetX = clamp5(
|
|
19788
|
+
Math.floor(
|
|
19789
|
+
currentPlayStep / (visibleBars * renderConfig.stepsPerBar)
|
|
19790
|
+
) * visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
|
|
19791
|
+
0,
|
|
19792
|
+
getMaxOffsetX()
|
|
19793
|
+
);
|
|
19794
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19686
19795
|
}
|
|
19687
19796
|
redrawAll();
|
|
19688
19797
|
},
|
|
19689
19798
|
onEnd: (interrupted) => {
|
|
19799
|
+
options.onScheduleFade?.(null);
|
|
19690
19800
|
if (interrupted) {
|
|
19691
19801
|
playbackState = "paused";
|
|
19692
19802
|
pausedPlayStep = currentPlayStep;
|
|
@@ -19778,22 +19888,37 @@ var mountDAW = (target, options = {}) => {
|
|
|
19778
19888
|
}
|
|
19779
19889
|
if (fadeOutSec > 0) {
|
|
19780
19890
|
const endStep = getSongEndStepExact();
|
|
19781
|
-
|
|
19782
|
-
|
|
19783
|
-
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
|
|
19891
|
+
if (endStep > fromStep) {
|
|
19892
|
+
const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
|
|
19893
|
+
if (totalDurationSec > 0.1) {
|
|
19894
|
+
const fadeOutEndAt = anchor + totalDurationSec;
|
|
19895
|
+
const earliestStart = params.fadeInEndAt ?? anchor;
|
|
19896
|
+
const fadeOutStartAt = Math.max(
|
|
19897
|
+
fadeOutEndAt - fadeOutSec,
|
|
19898
|
+
earliestStart
|
|
19899
|
+
);
|
|
19900
|
+
if (fadeOutStartAt < fadeOutEndAt) {
|
|
19901
|
+
params.fadeOutStartAt = fadeOutStartAt;
|
|
19902
|
+
params.fadeOutEndAt = fadeOutEndAt;
|
|
19903
|
+
}
|
|
19904
|
+
}
|
|
19790
19905
|
}
|
|
19791
19906
|
}
|
|
19792
19907
|
options.onScheduleFade?.(params);
|
|
19793
19908
|
}
|
|
19794
19909
|
if (streaming && voices) {
|
|
19910
|
+
let loopLengthSec;
|
|
19911
|
+
let loopStartSec;
|
|
19912
|
+
if (loopEnabled) {
|
|
19913
|
+
const endStep = getSongEndStepExact();
|
|
19914
|
+
const sps = 60 / bpm / 48;
|
|
19915
|
+
loopStartSec = 0;
|
|
19916
|
+
loopLengthSec = endStep * sps;
|
|
19917
|
+
}
|
|
19795
19918
|
voices.startStream(streamTracks, sequencer.getStartTime(), {
|
|
19796
19919
|
isAudible: (t) => !isSolo || t.id === activeTrackId,
|
|
19920
|
+
loopLengthSec,
|
|
19921
|
+
loopStartSec,
|
|
19797
19922
|
onScheduled: (t, note, t0) => {
|
|
19798
19923
|
if (!t.id) return;
|
|
19799
19924
|
flashTrackPill(t.id, t0 - getAudioTime(), note.durationSec);
|
|
@@ -20805,6 +20930,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20805
20930
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20806
20931
|
mode,
|
|
20807
20932
|
edo: renderConfig.edo,
|
|
20933
|
+
loop: loopEnabled ? true : void 0,
|
|
20808
20934
|
trackInstruments: trackInstMeta,
|
|
20809
20935
|
trackCompression: trackCompMeta,
|
|
20810
20936
|
trackWidth: trackWidthMeta,
|
|
@@ -20834,6 +20960,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20834
20960
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20835
20961
|
mode,
|
|
20836
20962
|
edo: renderConfig.edo,
|
|
20963
|
+
loop: loopEnabled ? true : void 0,
|
|
20837
20964
|
trackInstruments: trackInstMeta,
|
|
20838
20965
|
trackCompression: trackCompMeta,
|
|
20839
20966
|
trackWidth: trackWidthMeta,
|
|
@@ -21047,6 +21174,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
21047
21174
|
}
|
|
21048
21175
|
applyEdo(meta.edo ?? 12);
|
|
21049
21176
|
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
21177
|
+
applyLoop(meta.loop ?? false);
|
|
21050
21178
|
if (meta.drumVolume !== void 0) {
|
|
21051
21179
|
drumVolume = meta.drumVolume;
|
|
21052
21180
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -21559,6 +21687,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
21559
21687
|
refs.edoInfoBtn.addEventListener("click", () => {
|
|
21560
21688
|
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
21561
21689
|
});
|
|
21690
|
+
refs.loopToggle.addEventListener("change", () => {
|
|
21691
|
+
applyLoop(refs.loopToggle.checked);
|
|
21692
|
+
});
|
|
21693
|
+
refs.loopInfoBtn.addEventListener("click", () => {
|
|
21694
|
+
showModal("\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC", LOOP_INFO_HTML);
|
|
21695
|
+
});
|
|
21562
21696
|
refs.masterVolume.addEventListener("input", () => {
|
|
21563
21697
|
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
21564
21698
|
});
|
|
@@ -22960,6 +23094,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
22960
23094
|
},
|
|
22961
23095
|
exportMIDI: exportMIDI2,
|
|
22962
23096
|
setBpm,
|
|
23097
|
+
getLoop: () => loopEnabled,
|
|
23098
|
+
setLoop: (loop) => applyLoop(loop),
|
|
22963
23099
|
getPlaybackState: () => playbackState,
|
|
22964
23100
|
getCurrentPlayStep,
|
|
22965
23101
|
forcePauseAt,
|
|
@@ -24235,6 +24371,8 @@ var createDtmStudio = async (options = {}) => {
|
|
|
24235
24371
|
};
|
|
24236
24372
|
};
|
|
24237
24373
|
const resumeAudio = () => {
|
|
24374
|
+
fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
|
|
24375
|
+
fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
|
|
24238
24376
|
if (audioCtx.state === "closed") return Promise.resolve();
|
|
24239
24377
|
return audioCtx.resume();
|
|
24240
24378
|
};
|
|
@@ -25078,9 +25216,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
25078
25216
|
if (!sfInst) return;
|
|
25079
25217
|
const vol = options2.volume ?? 80;
|
|
25080
25218
|
const dur = options2.duration ?? 1;
|
|
25081
|
-
|
|
25082
|
-
await audioCtx.resume();
|
|
25083
|
-
}
|
|
25219
|
+
await resumeAudio();
|
|
25084
25220
|
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
25085
25221
|
sfInst.play({
|
|
25086
25222
|
ctx: audioCtx,
|
package/dist/index.mjs
CHANGED
|
@@ -4213,7 +4213,7 @@ var NATURAL_STEPS2 = {
|
|
|
4213
4213
|
};
|
|
4214
4214
|
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
4215
4215
|
var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
4216
|
-
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w:-]+)/gi;
|
|
4216
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo|loop)=([\w:-]+)/gi;
|
|
4217
4217
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
4218
4218
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
4219
4219
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -4266,6 +4266,9 @@ var parseMmlMeta = (mml) => {
|
|
|
4266
4266
|
} else if (key === "edo") {
|
|
4267
4267
|
const e = Number.parseInt(m[2], 10);
|
|
4268
4268
|
if (e === 12 || e === 31) meta.edo = e;
|
|
4269
|
+
} else if (key === "loop") {
|
|
4270
|
+
const v = m[2].toLowerCase();
|
|
4271
|
+
meta.loop = v === "on" || v === "1" || v === "true";
|
|
4269
4272
|
}
|
|
4270
4273
|
}
|
|
4271
4274
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -4369,6 +4372,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
4369
4372
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
4370
4373
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
4371
4374
|
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
4375
|
+
if (meta.loop) parts.push("#loop=on");
|
|
4372
4376
|
if (meta.trackInstruments) {
|
|
4373
4377
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
4374
4378
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -4896,7 +4900,11 @@ var createSequencer = (options) => {
|
|
|
4896
4900
|
if (!active) return;
|
|
4897
4901
|
const sps = secondsPerStep();
|
|
4898
4902
|
const time = options.getAudioTime() - startTime;
|
|
4899
|
-
|
|
4903
|
+
try {
|
|
4904
|
+
options.onTick(getWrappedPlayStep(time, sps));
|
|
4905
|
+
} catch (err2) {
|
|
4906
|
+
console.error("[sequencer] error in onTick callback:", err2);
|
|
4907
|
+
}
|
|
4900
4908
|
animationId = requestAnimationFrame(animate);
|
|
4901
4909
|
};
|
|
4902
4910
|
const stop = () => {
|
|
@@ -10658,6 +10666,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
10658
10666
|
options.onStop?.();
|
|
10659
10667
|
};
|
|
10660
10668
|
let isSeeking = false;
|
|
10669
|
+
let lastCommittedStep = null;
|
|
10661
10670
|
const seekTo = (step) => {
|
|
10662
10671
|
const clamped = Math.min(maxStep, Math.max(0, Math.round(step)));
|
|
10663
10672
|
renderPlayhead(clamped);
|
|
@@ -10669,16 +10678,42 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
10669
10678
|
play(clamped);
|
|
10670
10679
|
}
|
|
10671
10680
|
};
|
|
10672
|
-
|
|
10681
|
+
const commitSeek = () => {
|
|
10682
|
+
isSeeking = false;
|
|
10683
|
+
const step = Number(seekInput.value);
|
|
10684
|
+
if (lastCommittedStep !== step) {
|
|
10685
|
+
lastCommittedStep = step;
|
|
10686
|
+
seekTo(step);
|
|
10687
|
+
}
|
|
10688
|
+
};
|
|
10689
|
+
seekInput.addEventListener("pointerdown", (e) => {
|
|
10673
10690
|
isSeeking = true;
|
|
10691
|
+
lastCommittedStep = null;
|
|
10692
|
+
try {
|
|
10693
|
+
seekInput.setPointerCapture(e.pointerId);
|
|
10694
|
+
} catch {
|
|
10695
|
+
}
|
|
10674
10696
|
});
|
|
10675
10697
|
seekInput.addEventListener("input", () => {
|
|
10676
10698
|
renderPlayhead(Number(seekInput.value));
|
|
10677
10699
|
syncSeekFill();
|
|
10678
10700
|
});
|
|
10679
10701
|
seekInput.addEventListener("change", () => {
|
|
10702
|
+
commitSeek();
|
|
10703
|
+
});
|
|
10704
|
+
seekInput.addEventListener("pointerup", (e) => {
|
|
10705
|
+
try {
|
|
10706
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
10707
|
+
} catch {
|
|
10708
|
+
}
|
|
10709
|
+
commitSeek();
|
|
10710
|
+
});
|
|
10711
|
+
seekInput.addEventListener("pointercancel", (e) => {
|
|
10712
|
+
try {
|
|
10713
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
10714
|
+
} catch {
|
|
10715
|
+
}
|
|
10680
10716
|
isSeeking = false;
|
|
10681
|
-
seekTo(Number(seekInput.value));
|
|
10682
10717
|
});
|
|
10683
10718
|
playBtn.addEventListener("click", () => {
|
|
10684
10719
|
if (playing) pause();
|
|
@@ -12223,19 +12258,45 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
12223
12258
|
}
|
|
12224
12259
|
play(idx);
|
|
12225
12260
|
};
|
|
12226
|
-
|
|
12261
|
+
let lastCommittedSec = null;
|
|
12262
|
+
const commitSeek = () => {
|
|
12263
|
+
isSeeking = false;
|
|
12264
|
+
if (totalSec <= 0) return;
|
|
12265
|
+
const sec = Number(seekInput.value);
|
|
12266
|
+
if (lastCommittedSec !== sec) {
|
|
12267
|
+
lastCommittedSec = sec;
|
|
12268
|
+
const idx = getActiveIndexBySec(sec);
|
|
12269
|
+
seekTo(idx >= 0 ? idx : Math.max(0, chordEvents.length - 1));
|
|
12270
|
+
}
|
|
12271
|
+
};
|
|
12272
|
+
seekInput.addEventListener("pointerdown", (e) => {
|
|
12227
12273
|
isSeeking = true;
|
|
12274
|
+
lastCommittedSec = null;
|
|
12275
|
+
try {
|
|
12276
|
+
seekInput.setPointerCapture(e.pointerId);
|
|
12277
|
+
} catch {
|
|
12278
|
+
}
|
|
12228
12279
|
});
|
|
12229
12280
|
seekInput.addEventListener("input", () => {
|
|
12230
12281
|
updateTimeDisplay(Number(seekInput.value));
|
|
12231
12282
|
syncSeekFill();
|
|
12232
12283
|
});
|
|
12233
12284
|
seekInput.addEventListener("change", () => {
|
|
12285
|
+
commitSeek();
|
|
12286
|
+
});
|
|
12287
|
+
seekInput.addEventListener("pointerup", (e) => {
|
|
12288
|
+
try {
|
|
12289
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
12290
|
+
} catch {
|
|
12291
|
+
}
|
|
12292
|
+
commitSeek();
|
|
12293
|
+
});
|
|
12294
|
+
seekInput.addEventListener("pointercancel", (e) => {
|
|
12295
|
+
try {
|
|
12296
|
+
seekInput.releasePointerCapture(e.pointerId);
|
|
12297
|
+
} catch {
|
|
12298
|
+
}
|
|
12234
12299
|
isSeeking = false;
|
|
12235
|
-
if (totalSec <= 0) return;
|
|
12236
|
-
const sec = Number(seekInput.value);
|
|
12237
|
-
const idx = getActiveIndexBySec(sec);
|
|
12238
|
-
seekTo(idx >= 0 ? idx : Math.max(0, chordEvents.length - 1));
|
|
12239
12300
|
});
|
|
12240
12301
|
const stop = () => {
|
|
12241
12302
|
if (isLoading) {
|
|
@@ -15348,6 +15409,15 @@ var buildUI = (target, options) => {
|
|
|
15348
15409
|
</select>
|
|
15349
15410
|
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
15350
15411
|
</div>
|
|
15412
|
+
<div class="dtm-row">
|
|
15413
|
+
<span class="dtm-label">\u30EB\u30FC\u30D7\u518D\u751F</span>
|
|
15414
|
+
<label class="dtm-toggle">
|
|
15415
|
+
<input type="checkbox" data-dtm="loop-toggle">
|
|
15416
|
+
<span data-dtm="loop-toggle-label">OFF</span>
|
|
15417
|
+
</label>
|
|
15418
|
+
<span class="dtm-grow"></span>
|
|
15419
|
+
<button class="dtm-infobtn" data-dtm="loop-info" title="\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
15420
|
+
</div>
|
|
15351
15421
|
<div class="dtm-row">
|
|
15352
15422
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
15353
15423
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -15685,6 +15755,9 @@ var buildUI = (target, options) => {
|
|
|
15685
15755
|
vScrollThumb: sel("vscroll-thumb"),
|
|
15686
15756
|
hScroll: sel("hscroll"),
|
|
15687
15757
|
hScrollThumb: sel("hscroll-thumb"),
|
|
15758
|
+
loopToggle: sel("loop-toggle"),
|
|
15759
|
+
loopToggleLabel: sel("loop-toggle-label"),
|
|
15760
|
+
loopInfoBtn: sel("loop-info"),
|
|
15688
15761
|
masterVolume: sel("master-volume"),
|
|
15689
15762
|
masterVolumeLabel: sel("master-volume-label"),
|
|
15690
15763
|
masterComp: sel("master-comp"),
|
|
@@ -17868,6 +17941,16 @@ var TRACK_DELAYSEND_INFO_HTML = `
|
|
|
17868
17941
|
<p>[delay]\u30D1\u30CD\u30EB\u306E\u639B\u304B\u308A\u5177\u5408\u304C0%\u3060\u3068\u3001\u3053\u3053\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u300C\u9001\u308A\u91CF\uFF08\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304C\u3069\u308C\u3060\u3051\u63D0\u4F9B\u3059\u308B\u304B\uFF09\u300D\u3068\u300C\u30DE\u30B9\u30BF\u306E\u30C7\u30A3\u30EC\u30A4\u8A2D\u5B9A\uFF08\u5B9F\u969B\u306B\u3069\u3093\u306A\u7E70\u308A\u8FD4\u3057\u304C\u639B\u304B\u308B\u304B\uFF09\u300D\u306E\u4E8C\u6BB5\u69CB\u3048\u306B\u306A\u3063\u3066\u3044\u308B\u305F\u3081\u3067\u3059\u3002\u4E21\u65B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
|
|
17869
17942
|
</div>
|
|
17870
17943
|
`;
|
|
17944
|
+
var LOOP_INFO_HTML = `
|
|
17945
|
+
<div class="dtm-modal-body-content">
|
|
17946
|
+
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
17947
|
+
<p>\u30EB\u30FC\u30D7\u518D\u751F\u3092\u6709\u52B9\u306B\u3057\u307E\u3059\u3002ON\u306B\u3059\u308B\u3068\u3001\u518D\u751F\u304C\u66F2\u672B\u3067\u505C\u6B62\u305B\u305A\u3001\u5148\u982D\u304B\u3089\u30B7\u30FC\u30E0\u30EC\u30B9\u306B\u7E70\u308A\u8FD4\u3055\u308C\u307E\u3059\u3002</p>
|
|
17948
|
+
<h4>MML\u51FA\u529B\u3078\u306E\u53CD\u6620</h4>
|
|
17949
|
+
<p>\u30EB\u30FC\u30D7\u518D\u751F\u304CON\u306E\u5834\u5408\u3001MML\u751F\u6210\u6642\u306B <code>#loop=on</code> \u30E1\u30BF\u884C\u304C\u51FA\u529B\u3055\u308C\u307E\u3059\u3002\u57CB\u3081\u8FBC\u307F\u30D7\u30EC\u30A4\u30E4\u30FC\u3084\u518D\u751F\u5C02\u7528\u30D7\u30EC\u30A4\u30E4\u30FC\uFF08mountPlayer / mountChordPlayer\uFF09\u3067\u3082\u3053\u306EMML\u3092\u8AAD\u307F\u8FBC\u3080\u3053\u3068\u3067\u81EA\u52D5\u7684\u306B\u30EB\u30FC\u30D7\u518D\u751F\u3055\u308C\u307E\u3059\u3002</p>
|
|
17950
|
+
<h4>BGM\u3084\u30B2\u30FC\u30E0\u97F3\u697D\u5411\u3051</h4>
|
|
17951
|
+
<p>Web\u30B5\u30A4\u30C8\u306EBGM\u3084\u30B2\u30FC\u30E0\u306E\u30B9\u30C6\u30FC\u30B8\u66F2\u306A\u3069\u3001\u9014\u5207\u308C\u305A\u306B\u30EB\u30FC\u30D7\u3055\u305B\u305F\u3044\u66F2\u306E\u5236\u4F5C\u306B\u6700\u9069\u3067\u3059\u3002</p>
|
|
17952
|
+
</div>
|
|
17953
|
+
`;
|
|
17871
17954
|
var MASTER_COMP_INFO_HTML = `
|
|
17872
17955
|
<div class="dtm-modal-body-content">
|
|
17873
17956
|
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
@@ -18413,6 +18496,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
18413
18496
|
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18414
18497
|
};
|
|
18415
18498
|
applyMasterVolume(masterVolume);
|
|
18499
|
+
let loopEnabled = options.initialLoop ?? false;
|
|
18500
|
+
const applyLoop = (enabled) => {
|
|
18501
|
+
loopEnabled = enabled;
|
|
18502
|
+
if (refs.loopToggle) {
|
|
18503
|
+
refs.loopToggle.checked = enabled;
|
|
18504
|
+
}
|
|
18505
|
+
if (refs.loopToggleLabel) {
|
|
18506
|
+
refs.loopToggleLabel.textContent = enabled ? "ON" : "OFF";
|
|
18507
|
+
}
|
|
18508
|
+
options.onLoopChange?.(enabled);
|
|
18509
|
+
};
|
|
18510
|
+
applyLoop(loopEnabled);
|
|
18416
18511
|
const snapToEdoGrid = (u) => {
|
|
18417
18512
|
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
18418
18513
|
return units(Math.round(u / upr) * upr);
|
|
@@ -19474,6 +19569,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19474
19569
|
getPlayStartStep: () => playStartStep,
|
|
19475
19570
|
getDrumPattern: (currentBar) => resolveDrumPattern(currentDrumPattern, drumPatterns, currentBar),
|
|
19476
19571
|
getSoloTrackId: () => isSolo ? activeTrackId : null,
|
|
19572
|
+
getLoop: () => loopEnabled,
|
|
19477
19573
|
getAudioTime,
|
|
19478
19574
|
onPlayNote: (e) => {
|
|
19479
19575
|
if (audioMutedTracks.has(e.trackId)) return;
|
|
@@ -19501,10 +19597,24 @@ var mountDAW = (target, options = {}) => {
|
|
|
19501
19597
|
getMaxOffsetX()
|
|
19502
19598
|
);
|
|
19503
19599
|
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19600
|
+
} else if (currentPlayStep < currentOffsetX / renderConfig.stepWidth) {
|
|
19601
|
+
const visibleBars = Math.max(
|
|
19602
|
+
1,
|
|
19603
|
+
Math.round(visibleSteps / renderConfig.stepsPerBar)
|
|
19604
|
+
);
|
|
19605
|
+
currentOffsetX = clamp5(
|
|
19606
|
+
Math.floor(
|
|
19607
|
+
currentPlayStep / (visibleBars * renderConfig.stepsPerBar)
|
|
19608
|
+
) * visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
|
|
19609
|
+
0,
|
|
19610
|
+
getMaxOffsetX()
|
|
19611
|
+
);
|
|
19612
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19504
19613
|
}
|
|
19505
19614
|
redrawAll();
|
|
19506
19615
|
},
|
|
19507
19616
|
onEnd: (interrupted) => {
|
|
19617
|
+
options.onScheduleFade?.(null);
|
|
19508
19618
|
if (interrupted) {
|
|
19509
19619
|
playbackState = "paused";
|
|
19510
19620
|
pausedPlayStep = currentPlayStep;
|
|
@@ -19596,22 +19706,37 @@ var mountDAW = (target, options = {}) => {
|
|
|
19596
19706
|
}
|
|
19597
19707
|
if (fadeOutSec > 0) {
|
|
19598
19708
|
const endStep = getSongEndStepExact();
|
|
19599
|
-
|
|
19600
|
-
|
|
19601
|
-
|
|
19602
|
-
|
|
19603
|
-
|
|
19604
|
-
|
|
19605
|
-
|
|
19606
|
-
|
|
19607
|
-
|
|
19709
|
+
if (endStep > fromStep) {
|
|
19710
|
+
const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
|
|
19711
|
+
if (totalDurationSec > 0.1) {
|
|
19712
|
+
const fadeOutEndAt = anchor + totalDurationSec;
|
|
19713
|
+
const earliestStart = params.fadeInEndAt ?? anchor;
|
|
19714
|
+
const fadeOutStartAt = Math.max(
|
|
19715
|
+
fadeOutEndAt - fadeOutSec,
|
|
19716
|
+
earliestStart
|
|
19717
|
+
);
|
|
19718
|
+
if (fadeOutStartAt < fadeOutEndAt) {
|
|
19719
|
+
params.fadeOutStartAt = fadeOutStartAt;
|
|
19720
|
+
params.fadeOutEndAt = fadeOutEndAt;
|
|
19721
|
+
}
|
|
19722
|
+
}
|
|
19608
19723
|
}
|
|
19609
19724
|
}
|
|
19610
19725
|
options.onScheduleFade?.(params);
|
|
19611
19726
|
}
|
|
19612
19727
|
if (streaming && voices) {
|
|
19728
|
+
let loopLengthSec;
|
|
19729
|
+
let loopStartSec;
|
|
19730
|
+
if (loopEnabled) {
|
|
19731
|
+
const endStep = getSongEndStepExact();
|
|
19732
|
+
const sps = 60 / bpm / 48;
|
|
19733
|
+
loopStartSec = 0;
|
|
19734
|
+
loopLengthSec = endStep * sps;
|
|
19735
|
+
}
|
|
19613
19736
|
voices.startStream(streamTracks, sequencer.getStartTime(), {
|
|
19614
19737
|
isAudible: (t) => !isSolo || t.id === activeTrackId,
|
|
19738
|
+
loopLengthSec,
|
|
19739
|
+
loopStartSec,
|
|
19615
19740
|
onScheduled: (t, note, t0) => {
|
|
19616
19741
|
if (!t.id) return;
|
|
19617
19742
|
flashTrackPill(t.id, t0 - getAudioTime(), note.durationSec);
|
|
@@ -20623,6 +20748,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20623
20748
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20624
20749
|
mode,
|
|
20625
20750
|
edo: renderConfig.edo,
|
|
20751
|
+
loop: loopEnabled ? true : void 0,
|
|
20626
20752
|
trackInstruments: trackInstMeta,
|
|
20627
20753
|
trackCompression: trackCompMeta,
|
|
20628
20754
|
trackWidth: trackWidthMeta,
|
|
@@ -20652,6 +20778,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20652
20778
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20653
20779
|
mode,
|
|
20654
20780
|
edo: renderConfig.edo,
|
|
20781
|
+
loop: loopEnabled ? true : void 0,
|
|
20655
20782
|
trackInstruments: trackInstMeta,
|
|
20656
20783
|
trackCompression: trackCompMeta,
|
|
20657
20784
|
trackWidth: trackWidthMeta,
|
|
@@ -20865,6 +20992,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20865
20992
|
}
|
|
20866
20993
|
applyEdo(meta.edo ?? 12);
|
|
20867
20994
|
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
20995
|
+
applyLoop(meta.loop ?? false);
|
|
20868
20996
|
if (meta.drumVolume !== void 0) {
|
|
20869
20997
|
drumVolume = meta.drumVolume;
|
|
20870
20998
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -21377,6 +21505,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
21377
21505
|
refs.edoInfoBtn.addEventListener("click", () => {
|
|
21378
21506
|
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
21379
21507
|
});
|
|
21508
|
+
refs.loopToggle.addEventListener("change", () => {
|
|
21509
|
+
applyLoop(refs.loopToggle.checked);
|
|
21510
|
+
});
|
|
21511
|
+
refs.loopInfoBtn.addEventListener("click", () => {
|
|
21512
|
+
showModal("\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC", LOOP_INFO_HTML);
|
|
21513
|
+
});
|
|
21380
21514
|
refs.masterVolume.addEventListener("input", () => {
|
|
21381
21515
|
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
21382
21516
|
});
|
|
@@ -22778,6 +22912,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
22778
22912
|
},
|
|
22779
22913
|
exportMIDI: exportMIDI2,
|
|
22780
22914
|
setBpm,
|
|
22915
|
+
getLoop: () => loopEnabled,
|
|
22916
|
+
setLoop: (loop) => applyLoop(loop),
|
|
22781
22917
|
getPlaybackState: () => playbackState,
|
|
22782
22918
|
getCurrentPlayStep,
|
|
22783
22919
|
forcePauseAt,
|
|
@@ -24052,6 +24188,8 @@ var createDtmStudio = async (options = {}) => {
|
|
|
24052
24188
|
};
|
|
24053
24189
|
};
|
|
24054
24190
|
const resumeAudio = () => {
|
|
24191
|
+
fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
|
|
24192
|
+
fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
|
|
24055
24193
|
if (audioCtx.state === "closed") return Promise.resolve();
|
|
24056
24194
|
return audioCtx.resume();
|
|
24057
24195
|
};
|
|
@@ -24895,9 +25033,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
24895
25033
|
if (!sfInst) return;
|
|
24896
25034
|
const vol = options2.volume ?? 80;
|
|
24897
25035
|
const dur = options2.duration ?? 1;
|
|
24898
|
-
|
|
24899
|
-
await audioCtx.resume();
|
|
24900
|
-
}
|
|
25036
|
+
await resumeAudio();
|
|
24901
25037
|
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
24902
25038
|
sfInst.play({
|
|
24903
25039
|
ctx: audioCtx,
|
package/package.json
CHANGED