@onjmin/dtm 2.1.2 → 2.1.4
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 +158 -22
- package/dist/index.mjs +158 -22
- 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"),
|
|
@@ -17721,8 +17794,10 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
|
17721
17794
|
const getXY = (e) => {
|
|
17722
17795
|
const { clientX, clientY } = e;
|
|
17723
17796
|
const rect = g_grid_canvas.getBoundingClientRect();
|
|
17724
|
-
const
|
|
17725
|
-
const
|
|
17797
|
+
const scaleX = rect.width > 0 ? g_grid_canvas.width / rect.width : 1;
|
|
17798
|
+
const scaleY = rect.height > 0 ? g_grid_canvas.height / rect.height : 1;
|
|
17799
|
+
const x2 = Math.floor((clientX - rect.left) * scaleX);
|
|
17800
|
+
const y = Math.floor((clientY - rect.top) * scaleY);
|
|
17726
17801
|
return [x2, y, e.buttons];
|
|
17727
17802
|
};
|
|
17728
17803
|
const getGridPosition = (e) => {
|
|
@@ -17756,7 +17831,7 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
|
17756
17831
|
const absoluteY = y + g_draw_offset_y;
|
|
17757
17832
|
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
17758
17833
|
const pitch = units(pitchRangeStart + (keyCount - 1 - yIndex) * upr);
|
|
17759
|
-
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
17834
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount * upr) {
|
|
17760
17835
|
requestAnimationFrame(() => callback(step, pitch));
|
|
17761
17836
|
}
|
|
17762
17837
|
},
|
|
@@ -18050,6 +18125,16 @@ var TRACK_DELAYSEND_INFO_HTML = `
|
|
|
18050
18125
|
<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
18126
|
</div>
|
|
18052
18127
|
`;
|
|
18128
|
+
var LOOP_INFO_HTML = `
|
|
18129
|
+
<div class="dtm-modal-body-content">
|
|
18130
|
+
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
18131
|
+
<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>
|
|
18132
|
+
<h4>MML\u51FA\u529B\u3078\u306E\u53CD\u6620</h4>
|
|
18133
|
+
<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>
|
|
18134
|
+
<h4>BGM\u3084\u30B2\u30FC\u30E0\u97F3\u697D\u5411\u3051</h4>
|
|
18135
|
+
<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>
|
|
18136
|
+
</div>
|
|
18137
|
+
`;
|
|
18053
18138
|
var MASTER_COMP_INFO_HTML = `
|
|
18054
18139
|
<div class="dtm-modal-body-content">
|
|
18055
18140
|
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
@@ -18595,6 +18680,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
18595
18680
|
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18596
18681
|
};
|
|
18597
18682
|
applyMasterVolume(masterVolume);
|
|
18683
|
+
let loopEnabled = options.initialLoop ?? false;
|
|
18684
|
+
const applyLoop = (enabled) => {
|
|
18685
|
+
loopEnabled = enabled;
|
|
18686
|
+
if (refs.loopToggle) {
|
|
18687
|
+
refs.loopToggle.checked = enabled;
|
|
18688
|
+
}
|
|
18689
|
+
if (refs.loopToggleLabel) {
|
|
18690
|
+
refs.loopToggleLabel.textContent = enabled ? "ON" : "OFF";
|
|
18691
|
+
}
|
|
18692
|
+
options.onLoopChange?.(enabled);
|
|
18693
|
+
};
|
|
18694
|
+
applyLoop(loopEnabled);
|
|
18598
18695
|
const snapToEdoGrid = (u) => {
|
|
18599
18696
|
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
18600
18697
|
return units(Math.round(u / upr) * upr);
|
|
@@ -19190,7 +19287,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
19190
19287
|
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19191
19288
|
} = renderConfig;
|
|
19192
19289
|
const offset = renderer.getDrawOffset();
|
|
19193
|
-
|
|
19290
|
+
const notes = active.core.getNotes();
|
|
19291
|
+
for (let i2 = notes.length - 1; i2 >= 0; i2--) {
|
|
19292
|
+
const note = notes[i2];
|
|
19194
19293
|
const logicalX = note.startStep * stepWidth;
|
|
19195
19294
|
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
19196
19295
|
const logicalY = yIndex * keyHeight;
|
|
@@ -19266,14 +19365,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
19266
19365
|
return;
|
|
19267
19366
|
}
|
|
19268
19367
|
hasDragged = false;
|
|
19269
|
-
const
|
|
19368
|
+
const hitMargin = event.pointerType === "mouse" ? 0 : Math.min(
|
|
19369
|
+
TOUCH_HIT_MARGIN,
|
|
19370
|
+
Math.max(0, Math.floor(renderConfig.keyHeight * 0.25))
|
|
19371
|
+
);
|
|
19372
|
+
const existing = findActiveNoteAt(x2, y, hitMargin);
|
|
19270
19373
|
if (existing) {
|
|
19271
19374
|
playPreview(existing.pitchUnits);
|
|
19272
19375
|
const { stepWidth } = renderConfig;
|
|
19273
19376
|
const offset = renderer.getDrawOffset();
|
|
19274
19377
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
19275
19378
|
const w = existing.durationSteps * stepWidth;
|
|
19276
|
-
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
19379
|
+
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w + hitMargin) {
|
|
19277
19380
|
dragState = {
|
|
19278
19381
|
noteId: existing.id,
|
|
19279
19382
|
mode: "resize",
|
|
@@ -19288,7 +19391,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19288
19391
|
noteId: existing.id,
|
|
19289
19392
|
mode: "move",
|
|
19290
19393
|
dragOffsetStep: step - existing.startStep,
|
|
19291
|
-
dragOffsetPitch:
|
|
19394
|
+
dragOffsetPitch: 0,
|
|
19292
19395
|
startStep: existing.startStep,
|
|
19293
19396
|
durationSteps: existing.durationSteps,
|
|
19294
19397
|
lastPreviewPitch: existing.pitchUnits
|
|
@@ -19403,7 +19506,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19403
19506
|
const orig = selectedOriginal.find((o) => o.id === grab.id);
|
|
19404
19507
|
if (orig) {
|
|
19405
19508
|
const newGrab = units(orig.pitch + deltaPitch);
|
|
19406
|
-
if (newGrab !== lastMultiPreviewPitch && newGrab >=
|
|
19509
|
+
if (newGrab !== lastMultiPreviewPitch && newGrab >= PITCH_RANGE_START && newGrab <= PITCH_RANGE_END) {
|
|
19407
19510
|
lastMultiPreviewPitch = newGrab;
|
|
19408
19511
|
playPreview(newGrab);
|
|
19409
19512
|
}
|
|
@@ -19552,11 +19655,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
19552
19655
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
19553
19656
|
event.preventDefault();
|
|
19554
19657
|
if (isActiveLocked()) return;
|
|
19555
|
-
const {
|
|
19658
|
+
const { x: x2, y } = renderer.getGridPosition(event);
|
|
19556
19659
|
const active = getActive();
|
|
19557
|
-
const note =
|
|
19558
|
-
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
19559
|
-
);
|
|
19660
|
+
const note = findActiveNoteAt(x2, y);
|
|
19560
19661
|
if (note) active.core.deleteNoteById(note.id);
|
|
19561
19662
|
});
|
|
19562
19663
|
gridCanvas.addEventListener(
|
|
@@ -19656,6 +19757,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19656
19757
|
getPlayStartStep: () => playStartStep,
|
|
19657
19758
|
getDrumPattern: (currentBar) => resolveDrumPattern(currentDrumPattern, drumPatterns, currentBar),
|
|
19658
19759
|
getSoloTrackId: () => isSolo ? activeTrackId : null,
|
|
19760
|
+
getLoop: () => loopEnabled,
|
|
19659
19761
|
getAudioTime,
|
|
19660
19762
|
onPlayNote: (e) => {
|
|
19661
19763
|
if (audioMutedTracks.has(e.trackId)) return;
|
|
@@ -19683,6 +19785,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
19683
19785
|
getMaxOffsetX()
|
|
19684
19786
|
);
|
|
19685
19787
|
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19788
|
+
} else if (currentPlayStep < currentOffsetX / renderConfig.stepWidth) {
|
|
19789
|
+
const visibleBars = Math.max(
|
|
19790
|
+
1,
|
|
19791
|
+
Math.round(visibleSteps / renderConfig.stepsPerBar)
|
|
19792
|
+
);
|
|
19793
|
+
currentOffsetX = clamp5(
|
|
19794
|
+
Math.floor(
|
|
19795
|
+
currentPlayStep / (visibleBars * renderConfig.stepsPerBar)
|
|
19796
|
+
) * visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
|
|
19797
|
+
0,
|
|
19798
|
+
getMaxOffsetX()
|
|
19799
|
+
);
|
|
19800
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19686
19801
|
}
|
|
19687
19802
|
redrawAll();
|
|
19688
19803
|
},
|
|
@@ -19798,8 +19913,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
19798
19913
|
options.onScheduleFade?.(params);
|
|
19799
19914
|
}
|
|
19800
19915
|
if (streaming && voices) {
|
|
19916
|
+
let loopLengthSec;
|
|
19917
|
+
let loopStartSec;
|
|
19918
|
+
if (loopEnabled) {
|
|
19919
|
+
const endStep = getSongEndStepExact();
|
|
19920
|
+
const sps = 60 / bpm / 48;
|
|
19921
|
+
loopStartSec = 0;
|
|
19922
|
+
loopLengthSec = endStep * sps;
|
|
19923
|
+
}
|
|
19801
19924
|
voices.startStream(streamTracks, sequencer.getStartTime(), {
|
|
19802
19925
|
isAudible: (t) => !isSolo || t.id === activeTrackId,
|
|
19926
|
+
loopLengthSec,
|
|
19927
|
+
loopStartSec,
|
|
19803
19928
|
onScheduled: (t, note, t0) => {
|
|
19804
19929
|
if (!t.id) return;
|
|
19805
19930
|
flashTrackPill(t.id, t0 - getAudioTime(), note.durationSec);
|
|
@@ -20811,6 +20936,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20811
20936
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20812
20937
|
mode,
|
|
20813
20938
|
edo: renderConfig.edo,
|
|
20939
|
+
loop: loopEnabled ? true : void 0,
|
|
20814
20940
|
trackInstruments: trackInstMeta,
|
|
20815
20941
|
trackCompression: trackCompMeta,
|
|
20816
20942
|
trackWidth: trackWidthMeta,
|
|
@@ -20840,6 +20966,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20840
20966
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20841
20967
|
mode,
|
|
20842
20968
|
edo: renderConfig.edo,
|
|
20969
|
+
loop: loopEnabled ? true : void 0,
|
|
20843
20970
|
trackInstruments: trackInstMeta,
|
|
20844
20971
|
trackCompression: trackCompMeta,
|
|
20845
20972
|
trackWidth: trackWidthMeta,
|
|
@@ -21053,6 +21180,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
21053
21180
|
}
|
|
21054
21181
|
applyEdo(meta.edo ?? 12);
|
|
21055
21182
|
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
21183
|
+
applyLoop(meta.loop ?? false);
|
|
21056
21184
|
if (meta.drumVolume !== void 0) {
|
|
21057
21185
|
drumVolume = meta.drumVolume;
|
|
21058
21186
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -21565,6 +21693,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
21565
21693
|
refs.edoInfoBtn.addEventListener("click", () => {
|
|
21566
21694
|
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
21567
21695
|
});
|
|
21696
|
+
refs.loopToggle.addEventListener("change", () => {
|
|
21697
|
+
applyLoop(refs.loopToggle.checked);
|
|
21698
|
+
});
|
|
21699
|
+
refs.loopInfoBtn.addEventListener("click", () => {
|
|
21700
|
+
showModal("\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC", LOOP_INFO_HTML);
|
|
21701
|
+
});
|
|
21568
21702
|
refs.masterVolume.addEventListener("input", () => {
|
|
21569
21703
|
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
21570
21704
|
});
|
|
@@ -22966,6 +23100,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
22966
23100
|
},
|
|
22967
23101
|
exportMIDI: exportMIDI2,
|
|
22968
23102
|
setBpm,
|
|
23103
|
+
getLoop: () => loopEnabled,
|
|
23104
|
+
setLoop: (loop) => applyLoop(loop),
|
|
22969
23105
|
getPlaybackState: () => playbackState,
|
|
22970
23106
|
getCurrentPlayStep,
|
|
22971
23107
|
forcePauseAt,
|
|
@@ -23419,7 +23555,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
23419
23555
|
const h = keyHeight;
|
|
23420
23556
|
const renderX = logicalX - offset.x;
|
|
23421
23557
|
const renderY = logicalY - offset.y;
|
|
23422
|
-
if (x2 >= renderX && x2
|
|
23558
|
+
if (x2 >= renderX && x2 < renderX + w && y >= renderY && y < renderY + h) {
|
|
23423
23559
|
return note;
|
|
23424
23560
|
}
|
|
23425
23561
|
}
|
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"),
|
|
@@ -17539,8 +17612,10 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
|
17539
17612
|
const getXY = (e) => {
|
|
17540
17613
|
const { clientX, clientY } = e;
|
|
17541
17614
|
const rect = g_grid_canvas.getBoundingClientRect();
|
|
17542
|
-
const
|
|
17543
|
-
const
|
|
17615
|
+
const scaleX = rect.width > 0 ? g_grid_canvas.width / rect.width : 1;
|
|
17616
|
+
const scaleY = rect.height > 0 ? g_grid_canvas.height / rect.height : 1;
|
|
17617
|
+
const x2 = Math.floor((clientX - rect.left) * scaleX);
|
|
17618
|
+
const y = Math.floor((clientY - rect.top) * scaleY);
|
|
17544
17619
|
return [x2, y, e.buttons];
|
|
17545
17620
|
};
|
|
17546
17621
|
const getGridPosition = (e) => {
|
|
@@ -17574,7 +17649,7 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
|
17574
17649
|
const absoluteY = y + g_draw_offset_y;
|
|
17575
17650
|
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
17576
17651
|
const pitch = units(pitchRangeStart + (keyCount - 1 - yIndex) * upr);
|
|
17577
|
-
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
17652
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount * upr) {
|
|
17578
17653
|
requestAnimationFrame(() => callback(step, pitch));
|
|
17579
17654
|
}
|
|
17580
17655
|
},
|
|
@@ -17868,6 +17943,16 @@ var TRACK_DELAYSEND_INFO_HTML = `
|
|
|
17868
17943
|
<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
17944
|
</div>
|
|
17870
17945
|
`;
|
|
17946
|
+
var LOOP_INFO_HTML = `
|
|
17947
|
+
<div class="dtm-modal-body-content">
|
|
17948
|
+
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
17949
|
+
<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>
|
|
17950
|
+
<h4>MML\u51FA\u529B\u3078\u306E\u53CD\u6620</h4>
|
|
17951
|
+
<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>
|
|
17952
|
+
<h4>BGM\u3084\u30B2\u30FC\u30E0\u97F3\u697D\u5411\u3051</h4>
|
|
17953
|
+
<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>
|
|
17954
|
+
</div>
|
|
17955
|
+
`;
|
|
17871
17956
|
var MASTER_COMP_INFO_HTML = `
|
|
17872
17957
|
<div class="dtm-modal-body-content">
|
|
17873
17958
|
<h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
|
|
@@ -18413,6 +18498,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
18413
18498
|
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18414
18499
|
};
|
|
18415
18500
|
applyMasterVolume(masterVolume);
|
|
18501
|
+
let loopEnabled = options.initialLoop ?? false;
|
|
18502
|
+
const applyLoop = (enabled) => {
|
|
18503
|
+
loopEnabled = enabled;
|
|
18504
|
+
if (refs.loopToggle) {
|
|
18505
|
+
refs.loopToggle.checked = enabled;
|
|
18506
|
+
}
|
|
18507
|
+
if (refs.loopToggleLabel) {
|
|
18508
|
+
refs.loopToggleLabel.textContent = enabled ? "ON" : "OFF";
|
|
18509
|
+
}
|
|
18510
|
+
options.onLoopChange?.(enabled);
|
|
18511
|
+
};
|
|
18512
|
+
applyLoop(loopEnabled);
|
|
18416
18513
|
const snapToEdoGrid = (u) => {
|
|
18417
18514
|
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
18418
18515
|
return units(Math.round(u / upr) * upr);
|
|
@@ -19008,7 +19105,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
19008
19105
|
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19009
19106
|
} = renderConfig;
|
|
19010
19107
|
const offset = renderer.getDrawOffset();
|
|
19011
|
-
|
|
19108
|
+
const notes = active.core.getNotes();
|
|
19109
|
+
for (let i2 = notes.length - 1; i2 >= 0; i2--) {
|
|
19110
|
+
const note = notes[i2];
|
|
19012
19111
|
const logicalX = note.startStep * stepWidth;
|
|
19013
19112
|
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
19014
19113
|
const logicalY = yIndex * keyHeight;
|
|
@@ -19084,14 +19183,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
19084
19183
|
return;
|
|
19085
19184
|
}
|
|
19086
19185
|
hasDragged = false;
|
|
19087
|
-
const
|
|
19186
|
+
const hitMargin = event.pointerType === "mouse" ? 0 : Math.min(
|
|
19187
|
+
TOUCH_HIT_MARGIN,
|
|
19188
|
+
Math.max(0, Math.floor(renderConfig.keyHeight * 0.25))
|
|
19189
|
+
);
|
|
19190
|
+
const existing = findActiveNoteAt(x2, y, hitMargin);
|
|
19088
19191
|
if (existing) {
|
|
19089
19192
|
playPreview(existing.pitchUnits);
|
|
19090
19193
|
const { stepWidth } = renderConfig;
|
|
19091
19194
|
const offset = renderer.getDrawOffset();
|
|
19092
19195
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
19093
19196
|
const w = existing.durationSteps * stepWidth;
|
|
19094
|
-
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
19197
|
+
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w + hitMargin) {
|
|
19095
19198
|
dragState = {
|
|
19096
19199
|
noteId: existing.id,
|
|
19097
19200
|
mode: "resize",
|
|
@@ -19106,7 +19209,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19106
19209
|
noteId: existing.id,
|
|
19107
19210
|
mode: "move",
|
|
19108
19211
|
dragOffsetStep: step - existing.startStep,
|
|
19109
|
-
dragOffsetPitch:
|
|
19212
|
+
dragOffsetPitch: 0,
|
|
19110
19213
|
startStep: existing.startStep,
|
|
19111
19214
|
durationSteps: existing.durationSteps,
|
|
19112
19215
|
lastPreviewPitch: existing.pitchUnits
|
|
@@ -19221,7 +19324,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19221
19324
|
const orig = selectedOriginal.find((o) => o.id === grab.id);
|
|
19222
19325
|
if (orig) {
|
|
19223
19326
|
const newGrab = units(orig.pitch + deltaPitch);
|
|
19224
|
-
if (newGrab !== lastMultiPreviewPitch && newGrab >=
|
|
19327
|
+
if (newGrab !== lastMultiPreviewPitch && newGrab >= PITCH_RANGE_START && newGrab <= PITCH_RANGE_END) {
|
|
19225
19328
|
lastMultiPreviewPitch = newGrab;
|
|
19226
19329
|
playPreview(newGrab);
|
|
19227
19330
|
}
|
|
@@ -19370,11 +19473,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
19370
19473
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
19371
19474
|
event.preventDefault();
|
|
19372
19475
|
if (isActiveLocked()) return;
|
|
19373
|
-
const {
|
|
19476
|
+
const { x: x2, y } = renderer.getGridPosition(event);
|
|
19374
19477
|
const active = getActive();
|
|
19375
|
-
const note =
|
|
19376
|
-
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
19377
|
-
);
|
|
19478
|
+
const note = findActiveNoteAt(x2, y);
|
|
19378
19479
|
if (note) active.core.deleteNoteById(note.id);
|
|
19379
19480
|
});
|
|
19380
19481
|
gridCanvas.addEventListener(
|
|
@@ -19474,6 +19575,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
19474
19575
|
getPlayStartStep: () => playStartStep,
|
|
19475
19576
|
getDrumPattern: (currentBar) => resolveDrumPattern(currentDrumPattern, drumPatterns, currentBar),
|
|
19476
19577
|
getSoloTrackId: () => isSolo ? activeTrackId : null,
|
|
19578
|
+
getLoop: () => loopEnabled,
|
|
19477
19579
|
getAudioTime,
|
|
19478
19580
|
onPlayNote: (e) => {
|
|
19479
19581
|
if (audioMutedTracks.has(e.trackId)) return;
|
|
@@ -19501,6 +19603,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
19501
19603
|
getMaxOffsetX()
|
|
19502
19604
|
);
|
|
19503
19605
|
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19606
|
+
} else if (currentPlayStep < currentOffsetX / renderConfig.stepWidth) {
|
|
19607
|
+
const visibleBars = Math.max(
|
|
19608
|
+
1,
|
|
19609
|
+
Math.round(visibleSteps / renderConfig.stepsPerBar)
|
|
19610
|
+
);
|
|
19611
|
+
currentOffsetX = clamp5(
|
|
19612
|
+
Math.floor(
|
|
19613
|
+
currentPlayStep / (visibleBars * renderConfig.stepsPerBar)
|
|
19614
|
+
) * visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
|
|
19615
|
+
0,
|
|
19616
|
+
getMaxOffsetX()
|
|
19617
|
+
);
|
|
19618
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
19504
19619
|
}
|
|
19505
19620
|
redrawAll();
|
|
19506
19621
|
},
|
|
@@ -19616,8 +19731,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
19616
19731
|
options.onScheduleFade?.(params);
|
|
19617
19732
|
}
|
|
19618
19733
|
if (streaming && voices) {
|
|
19734
|
+
let loopLengthSec;
|
|
19735
|
+
let loopStartSec;
|
|
19736
|
+
if (loopEnabled) {
|
|
19737
|
+
const endStep = getSongEndStepExact();
|
|
19738
|
+
const sps = 60 / bpm / 48;
|
|
19739
|
+
loopStartSec = 0;
|
|
19740
|
+
loopLengthSec = endStep * sps;
|
|
19741
|
+
}
|
|
19619
19742
|
voices.startStream(streamTracks, sequencer.getStartTime(), {
|
|
19620
19743
|
isAudible: (t) => !isSolo || t.id === activeTrackId,
|
|
19744
|
+
loopLengthSec,
|
|
19745
|
+
loopStartSec,
|
|
19621
19746
|
onScheduled: (t, note, t0) => {
|
|
19622
19747
|
if (!t.id) return;
|
|
19623
19748
|
flashTrackPill(t.id, t0 - getAudioTime(), note.durationSec);
|
|
@@ -20629,6 +20754,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20629
20754
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20630
20755
|
mode,
|
|
20631
20756
|
edo: renderConfig.edo,
|
|
20757
|
+
loop: loopEnabled ? true : void 0,
|
|
20632
20758
|
trackInstruments: trackInstMeta,
|
|
20633
20759
|
trackCompression: trackCompMeta,
|
|
20634
20760
|
trackWidth: trackWidthMeta,
|
|
@@ -20658,6 +20784,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20658
20784
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
20659
20785
|
mode,
|
|
20660
20786
|
edo: renderConfig.edo,
|
|
20787
|
+
loop: loopEnabled ? true : void 0,
|
|
20661
20788
|
trackInstruments: trackInstMeta,
|
|
20662
20789
|
trackCompression: trackCompMeta,
|
|
20663
20790
|
trackWidth: trackWidthMeta,
|
|
@@ -20871,6 +20998,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
20871
20998
|
}
|
|
20872
20999
|
applyEdo(meta.edo ?? 12);
|
|
20873
21000
|
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
21001
|
+
applyLoop(meta.loop ?? false);
|
|
20874
21002
|
if (meta.drumVolume !== void 0) {
|
|
20875
21003
|
drumVolume = meta.drumVolume;
|
|
20876
21004
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -21383,6 +21511,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
21383
21511
|
refs.edoInfoBtn.addEventListener("click", () => {
|
|
21384
21512
|
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
21385
21513
|
});
|
|
21514
|
+
refs.loopToggle.addEventListener("change", () => {
|
|
21515
|
+
applyLoop(refs.loopToggle.checked);
|
|
21516
|
+
});
|
|
21517
|
+
refs.loopInfoBtn.addEventListener("click", () => {
|
|
21518
|
+
showModal("\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC", LOOP_INFO_HTML);
|
|
21519
|
+
});
|
|
21386
21520
|
refs.masterVolume.addEventListener("input", () => {
|
|
21387
21521
|
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
21388
21522
|
});
|
|
@@ -22784,6 +22918,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
22784
22918
|
},
|
|
22785
22919
|
exportMIDI: exportMIDI2,
|
|
22786
22920
|
setBpm,
|
|
22921
|
+
getLoop: () => loopEnabled,
|
|
22922
|
+
setLoop: (loop) => applyLoop(loop),
|
|
22787
22923
|
getPlaybackState: () => playbackState,
|
|
22788
22924
|
getCurrentPlayStep,
|
|
22789
22925
|
forcePauseAt,
|
|
@@ -23237,7 +23373,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
23237
23373
|
const h = keyHeight;
|
|
23238
23374
|
const renderX = logicalX - offset.x;
|
|
23239
23375
|
const renderY = logicalY - offset.y;
|
|
23240
|
-
if (x2 >= renderX && x2
|
|
23376
|
+
if (x2 >= renderX && x2 < renderX + w && y >= renderY && y < renderY + h) {
|
|
23241
23377
|
return note;
|
|
23242
23378
|
}
|
|
23243
23379
|
}
|
package/package.json
CHANGED