@onjmin/dtm 0.1.25 → 0.1.27
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/README.md +1 -2
- package/dist/index.d.mts +4 -16
- package/dist/index.d.ts +4 -16
- package/dist/index.js +90 -117
- package/dist/index.mjs +90 -116
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99,7 +99,6 @@ __export(index_exports, {
|
|
|
99
99
|
playMML: () => playMML,
|
|
100
100
|
playSingingMML: () => playSingingMML,
|
|
101
101
|
setDrawOffset: () => setDrawOffset,
|
|
102
|
-
setupRecorder: () => setupRecorder,
|
|
103
102
|
shiftNotes: () => shiftNotes,
|
|
104
103
|
stripLyrics: () => stripLyrics,
|
|
105
104
|
stripMmlMeta: () => stripMmlMeta,
|
|
@@ -116,34 +115,6 @@ function createAudioContext() {
|
|
|
116
115
|
drumGainNode.connect(audioCtx.destination);
|
|
117
116
|
return { audioCtx, gainNode, drumGainNode };
|
|
118
117
|
}
|
|
119
|
-
function setupRecorder(audioCtx, gainNode, drumGainNode) {
|
|
120
|
-
let isRecording = false;
|
|
121
|
-
let recordedData = [[], []];
|
|
122
|
-
const recorderProcessor = audioCtx.createScriptProcessor(4096, 2, 2);
|
|
123
|
-
recorderProcessor.onaudioprocess = (e) => {
|
|
124
|
-
if (!isRecording) return;
|
|
125
|
-
const left = e.inputBuffer.getChannelData(0);
|
|
126
|
-
const right = e.inputBuffer.getChannelData(1);
|
|
127
|
-
recordedData[0].push(left.slice());
|
|
128
|
-
recordedData[1].push(right.slice());
|
|
129
|
-
};
|
|
130
|
-
gainNode.connect(recorderProcessor);
|
|
131
|
-
drumGainNode.connect(recorderProcessor);
|
|
132
|
-
recorderProcessor.connect(audioCtx.destination);
|
|
133
|
-
return {
|
|
134
|
-
startRecording: () => {
|
|
135
|
-
isRecording = true;
|
|
136
|
-
},
|
|
137
|
-
stopRecording: () => {
|
|
138
|
-
isRecording = false;
|
|
139
|
-
},
|
|
140
|
-
getRecordedData: () => recordedData,
|
|
141
|
-
isRecording: () => isRecording,
|
|
142
|
-
clearRecordedData: () => {
|
|
143
|
-
recordedData = [[], []];
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
118
|
async function fetchSoundFontList(ttl) {
|
|
148
119
|
const res = await fetch(`https://rpgen3.github.io/soundfont/list/${ttl}.txt`);
|
|
149
120
|
const str = await res.text();
|
|
@@ -1240,8 +1211,9 @@ var buildUI = (target, options) => {
|
|
|
1240
1211
|
target.innerHTML = `
|
|
1241
1212
|
<div class="dtm-daw" data-dtm="root">
|
|
1242
1213
|
<div class="dtm-topbar" data-dtm="transport">
|
|
1214
|
+
<button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
|
|
1243
1215
|
<button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
|
|
1244
|
-
<button class="dtm-iconbtn
|
|
1216
|
+
<button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
|
|
1245
1217
|
<label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
|
|
1246
1218
|
<span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
|
|
1247
1219
|
<span class="dtm-grow"></span>
|
|
@@ -1406,10 +1378,12 @@ var buildUI = (target, options) => {
|
|
|
1406
1378
|
</div>
|
|
1407
1379
|
<div class="dtm-output dtm-hidden" data-dtm="output-container">
|
|
1408
1380
|
<p class="dtm-label" data-dtm="output-status"></p>
|
|
1381
|
+
<div class="dtm-output-label">\u6539\u884C\u3042\u308A\u7248</div>
|
|
1409
1382
|
<div class="dtm-output-row">
|
|
1410
1383
|
<pre><code data-dtm="output-full"></code></pre>
|
|
1411
1384
|
<button class="dtm-btn dtm-btn--primary dtm-btn--icon" data-dtm="copy-full" title="\u30B3\u30D4\u30FC">${icon("copy")}</button>
|
|
1412
1385
|
</div>
|
|
1386
|
+
<div class="dtm-output-label">\uFF11\u884C\u7248</div>
|
|
1413
1387
|
<div class="dtm-output-row">
|
|
1414
1388
|
<pre><code data-dtm="output-mini"></code></pre>
|
|
1415
1389
|
<button class="dtm-btn dtm-btn--primary dtm-btn--icon" data-dtm="copy-mini" title="\u30B3\u30D4\u30FC">${icon("copy")}</button>
|
|
@@ -1437,7 +1411,8 @@ var buildUI = (target, options) => {
|
|
|
1437
1411
|
topbar: sel("transport"),
|
|
1438
1412
|
topbarLoading: sel("topbar-loading"),
|
|
1439
1413
|
playBtn: sel("play"),
|
|
1440
|
-
|
|
1414
|
+
prevBarBtn: sel("prev-bar"),
|
|
1415
|
+
nextBarBtn: sel("next-bar"),
|
|
1441
1416
|
soloCheckbox: sel("solo"),
|
|
1442
1417
|
toolPen: sel("tool-pen"),
|
|
1443
1418
|
toolSelect: sel("tool-select"),
|
|
@@ -2742,7 +2717,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
2742
2717
|
return model;
|
|
2743
2718
|
};
|
|
2744
2719
|
var PREWARM_NOTES = 3;
|
|
2745
|
-
var STREAM_LOOKAHEAD_SEC =
|
|
2720
|
+
var STREAM_LOOKAHEAD_SEC = 6;
|
|
2746
2721
|
var STREAM_POLL_MS = 100;
|
|
2747
2722
|
var FALLBACK_MODEL = "klatt";
|
|
2748
2723
|
var createSingingVoices = (ctx, destination, options = {}) => {
|
|
@@ -2806,7 +2781,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
2806
2781
|
if (!m?.renderToCache) continue;
|
|
2807
2782
|
let n = 0;
|
|
2808
2783
|
forEachSungNote(track, (note, prevVowel) => {
|
|
2809
|
-
if (n >= count) return;
|
|
2784
|
+
if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
|
|
2810
2785
|
n++;
|
|
2811
2786
|
promises.push(
|
|
2812
2787
|
m.renderToCache?.(
|
|
@@ -2839,14 +2814,18 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
2839
2814
|
if (opts?.isAudible && !opts.isAudible(track)) continue;
|
|
2840
2815
|
const t0 = anchorTime + note.startSec;
|
|
2841
2816
|
if (model.renderToCache && model.scheduleCached) {
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2817
|
+
const renderToCache = model.renderToCache;
|
|
2818
|
+
const scheduleCached = model.scheduleCached;
|
|
2819
|
+
void (async () => {
|
|
2820
|
+
const key = await renderToCache(
|
|
2821
|
+
note.syllable,
|
|
2822
|
+
prevVowel,
|
|
2823
|
+
note.pitch,
|
|
2824
|
+
note.durationSec * 1e3
|
|
2825
|
+
);
|
|
2826
|
+
if (session !== streamSession) return;
|
|
2827
|
+
if (key) scheduleCached(key, t0, peak, track.pan);
|
|
2828
|
+
})();
|
|
2850
2829
|
} else {
|
|
2851
2830
|
const when = t0 - ctx.currentTime;
|
|
2852
2831
|
model(note.syllable, {
|
|
@@ -4255,7 +4234,7 @@ var PITCH_MAP2 = {
|
|
|
4255
4234
|
b: 11
|
|
4256
4235
|
};
|
|
4257
4236
|
var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
4258
|
-
var META_DIRECTIVE = /#(inst|drum|volume|mode)=([\w-]+)/gi;
|
|
4237
|
+
var META_DIRECTIVE = /#(inst|drum|volume|drumvolume|mode)=([\w-]+)/gi;
|
|
4259
4238
|
var parseMmlMeta = (mml) => {
|
|
4260
4239
|
const meta = {};
|
|
4261
4240
|
for (const m of mml.matchAll(META_DIRECTIVE)) {
|
|
@@ -4265,6 +4244,9 @@ var parseMmlMeta = (mml) => {
|
|
|
4265
4244
|
else if (key === "volume") {
|
|
4266
4245
|
const v = Number.parseInt(m[2], 10);
|
|
4267
4246
|
if (!Number.isNaN(v)) meta.volume = v;
|
|
4247
|
+
} else if (key === "drumvolume") {
|
|
4248
|
+
const dv = Number.parseInt(m[2], 10);
|
|
4249
|
+
if (!Number.isNaN(dv)) meta.drumVolume = dv;
|
|
4268
4250
|
} else if (key === "mode") {
|
|
4269
4251
|
if (m[2] === "simple" || m[2] === "advanced") {
|
|
4270
4252
|
meta.mode = m[2];
|
|
@@ -4279,6 +4261,8 @@ var formatMmlMeta = (meta) => {
|
|
|
4279
4261
|
if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
|
|
4280
4262
|
if (meta.drum) parts.push(`#drum=${meta.drum}`);
|
|
4281
4263
|
if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
|
|
4264
|
+
if (meta.drumVolume !== void 0)
|
|
4265
|
+
parts.push(`#drumvolume=${meta.drumVolume}`);
|
|
4282
4266
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
4283
4267
|
return parts.join(" ");
|
|
4284
4268
|
};
|
|
@@ -5000,7 +4984,7 @@ var DAW_CSS = `
|
|
|
5000
4984
|
top: 0;
|
|
5001
4985
|
z-index: 20;
|
|
5002
4986
|
display: flex;
|
|
5003
|
-
flex-wrap:
|
|
4987
|
+
flex-wrap: nowrap;
|
|
5004
4988
|
align-items: center;
|
|
5005
4989
|
gap: var(--dtm-gap);
|
|
5006
4990
|
padding: 6px;
|
|
@@ -5010,6 +4994,17 @@ var DAW_CSS = `
|
|
|
5010
4994
|
inset 0 0 0 2px var(--c-black),
|
|
5011
4995
|
0 0 0 2px var(--dtm-success),
|
|
5012
4996
|
4px 4px 0 var(--c-black);
|
|
4997
|
+
overflow-x: auto;
|
|
4998
|
+
scrollbar-width: none;
|
|
4999
|
+
}
|
|
5000
|
+
.dtm-topbar::-webkit-scrollbar {
|
|
5001
|
+
display: none;
|
|
5002
|
+
}
|
|
5003
|
+
.dtm-topbar > * {
|
|
5004
|
+
flex-shrink: 0;
|
|
5005
|
+
}
|
|
5006
|
+
.dtm-topbar > .dtm-grow {
|
|
5007
|
+
flex-shrink: 1;
|
|
5013
5008
|
}
|
|
5014
5009
|
|
|
5015
5010
|
/* PLAY\u30DC\u30BF\u30F3 \u2014 \u30B2\u30FC\u30E0\u306E\u300C\u6C7A\u5B9A\u30DC\u30BF\u30F3\u300D\u7684\u5B58\u5728\u611F */
|
|
@@ -5360,6 +5355,15 @@ var DAW_CSS = `
|
|
|
5360
5355
|
line-height: 1.8;
|
|
5361
5356
|
color: var(--dtm-success);
|
|
5362
5357
|
}
|
|
5358
|
+
.dtm-output-label {
|
|
5359
|
+
font-size: 11px;
|
|
5360
|
+
color: var(--dtm-muted);
|
|
5361
|
+
font-family: var(--dtm-font);
|
|
5362
|
+
margin-top: 10px;
|
|
5363
|
+
}
|
|
5364
|
+
.dtm-output-label:first-of-type {
|
|
5365
|
+
margin-top: 0;
|
|
5366
|
+
}
|
|
5363
5367
|
.dtm-output-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 6px; }
|
|
5364
5368
|
.dtm-output-row pre { flex: 1; }
|
|
5365
5369
|
|
|
@@ -6203,6 +6207,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6203
6207
|
const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
|
|
6204
6208
|
const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
|
|
6205
6209
|
const trackVolume = meta.volume ?? options.volume ?? 100;
|
|
6210
|
+
const drumVolume = meta.drumVolume ?? 80;
|
|
6206
6211
|
const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
|
|
6207
6212
|
const useSynth = options.synth ?? !options.onPlayNote;
|
|
6208
6213
|
const secondsPerStep = 60 / bpm / STEPS_PER_BEAT3;
|
|
@@ -6819,9 +6824,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6819
6824
|
}
|
|
6820
6825
|
target.appendChild(root);
|
|
6821
6826
|
let consentOverlayEl = null;
|
|
6822
|
-
const checkConsentAndShow = () => {
|
|
6827
|
+
const checkConsentAndShow = (onAgree) => {
|
|
6823
6828
|
try {
|
|
6824
|
-
if (options.skipConsent) return;
|
|
6829
|
+
if (options.skipConsent) return false;
|
|
6825
6830
|
const unagreed = termsModels.filter((model) => {
|
|
6826
6831
|
if (agreedModelsInSession.has(model)) return false;
|
|
6827
6832
|
try {
|
|
@@ -6835,7 +6840,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6835
6840
|
return true;
|
|
6836
6841
|
}
|
|
6837
6842
|
});
|
|
6838
|
-
if (unagreed.length === 0) return;
|
|
6843
|
+
if (unagreed.length === 0) return false;
|
|
6839
6844
|
const consentOverlay = doc.createElement("div");
|
|
6840
6845
|
consentOverlay.className = "dtm-consent-overlay";
|
|
6841
6846
|
const modal = doc.createElement("div");
|
|
@@ -6878,17 +6883,19 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6878
6883
|
}
|
|
6879
6884
|
consentOverlay.remove();
|
|
6880
6885
|
consentOverlayEl = null;
|
|
6886
|
+
if (onAgree) onAgree();
|
|
6881
6887
|
};
|
|
6882
6888
|
footer.appendChild(btn);
|
|
6883
6889
|
modal.append(header, body2, footer);
|
|
6884
6890
|
consentOverlay.appendChild(modal);
|
|
6885
6891
|
doc.body.appendChild(consentOverlay);
|
|
6886
6892
|
consentOverlayEl = consentOverlay;
|
|
6893
|
+
return true;
|
|
6887
6894
|
} catch (err2) {
|
|
6888
6895
|
console.error("[dtm-player] Error in checkConsentAndShow:", err2);
|
|
6896
|
+
return false;
|
|
6889
6897
|
}
|
|
6890
6898
|
};
|
|
6891
|
-
checkConsentAndShow();
|
|
6892
6899
|
const autoScroll = (lane, el) => {
|
|
6893
6900
|
if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
|
|
6894
6901
|
const elementCenter = el.offsetLeft + el.offsetWidth / 2;
|
|
@@ -6947,7 +6954,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6947
6954
|
if (useSynth) ensureSynth().playNote(e);
|
|
6948
6955
|
},
|
|
6949
6956
|
onPlayDrum: (e) => {
|
|
6950
|
-
const velocity = e.velocity * (trackVolume / 100);
|
|
6957
|
+
const velocity = e.velocity * (drumVolume / 100) * (trackVolume / 100);
|
|
6951
6958
|
options.onPlayDrum?.({ ...e, velocity });
|
|
6952
6959
|
if (useSynth) ensureSynth().playDrum({ ...e, velocity });
|
|
6953
6960
|
},
|
|
@@ -7021,6 +7028,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
7021
7028
|
};
|
|
7022
7029
|
const play = () => {
|
|
7023
7030
|
if (playing || trackIndices.length === 0) return;
|
|
7031
|
+
if (checkConsentAndShow(() => play())) return;
|
|
7024
7032
|
if (activePlayer && activePlayer !== instance) activePlayer.stop();
|
|
7025
7033
|
activePlayer = instance;
|
|
7026
7034
|
setPlayingUI(true);
|
|
@@ -8049,7 +8057,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
8049
8057
|
};
|
|
8050
8058
|
const updateTransport = () => {
|
|
8051
8059
|
const playing = playbackState === "playing";
|
|
8052
|
-
const label = playing ? "\
|
|
8060
|
+
const label = playing ? "\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
|
|
8053
8061
|
refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
|
|
8054
8062
|
refs.playBtn.classList.toggle("dtm-play--stop", playing);
|
|
8055
8063
|
};
|
|
@@ -8317,6 +8325,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
8317
8325
|
instrument: currentInstrument || void 0,
|
|
8318
8326
|
drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
|
|
8319
8327
|
volume: masterVolume,
|
|
8328
|
+
drumVolume,
|
|
8320
8329
|
mode
|
|
8321
8330
|
});
|
|
8322
8331
|
if (refs.decomposeChordToggle.checked) {
|
|
@@ -8468,6 +8477,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
8468
8477
|
refs.masterVolume.value = String(meta.volume);
|
|
8469
8478
|
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
8470
8479
|
}
|
|
8480
|
+
if (meta.drumVolume !== void 0) {
|
|
8481
|
+
drumVolume = meta.drumVolume;
|
|
8482
|
+
refs.drumVolume.value = String(meta.drumVolume);
|
|
8483
|
+
refs.drumVolumeLabel.textContent = `${meta.drumVolume}%`;
|
|
8484
|
+
}
|
|
8471
8485
|
for (const t of trackStates) {
|
|
8472
8486
|
t.lyrics = "";
|
|
8473
8487
|
t.lyricModel = "";
|
|
@@ -8621,8 +8635,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
8621
8635
|
const wireEvents = () => {
|
|
8622
8636
|
refs.playBtn.addEventListener("click", togglePlay);
|
|
8623
8637
|
refs.playBtn.disabled = false;
|
|
8624
|
-
refs.
|
|
8625
|
-
|
|
8638
|
+
refs.prevBarBtn.addEventListener("click", () => {
|
|
8639
|
+
const targetStep = Math.max(
|
|
8640
|
+
0,
|
|
8641
|
+
Math.floor((getCurrentPlayStep() - 1) / renderConfig.stepsPerBar) * renderConfig.stepsPerBar
|
|
8642
|
+
);
|
|
8643
|
+
jumpTo(targetStep);
|
|
8644
|
+
});
|
|
8645
|
+
refs.nextBarBtn.addEventListener("click", () => {
|
|
8646
|
+
const targetStep = Math.floor(getCurrentPlayStep() / renderConfig.stepsPerBar + 1) * renderConfig.stepsPerBar;
|
|
8647
|
+
jumpTo(targetStep);
|
|
8648
|
+
});
|
|
8626
8649
|
refs.soloCheckbox.addEventListener("change", () => {
|
|
8627
8650
|
isSolo = refs.soloCheckbox.checked;
|
|
8628
8651
|
});
|
|
@@ -8985,6 +9008,20 @@ var mountDAW = (target, options = {}) => {
|
|
|
8985
9008
|
if (playbackState === "paused") return pausedPlayStep;
|
|
8986
9009
|
return playStartStep;
|
|
8987
9010
|
};
|
|
9011
|
+
const jumpTo = async (step) => {
|
|
9012
|
+
const wasPlaying = playbackState === "playing";
|
|
9013
|
+
if (wasPlaying) {
|
|
9014
|
+
sequencer.stop();
|
|
9015
|
+
options.singingVoices?.stopStream();
|
|
9016
|
+
playStartStep = step;
|
|
9017
|
+
pausedPlayStep = step;
|
|
9018
|
+
currentPlayStep = step;
|
|
9019
|
+
playbackState = "paused";
|
|
9020
|
+
await play();
|
|
9021
|
+
} else {
|
|
9022
|
+
forcePauseAt(step);
|
|
9023
|
+
}
|
|
9024
|
+
};
|
|
8988
9025
|
const forcePauseAt = (step) => {
|
|
8989
9026
|
playStartStep = step;
|
|
8990
9027
|
pausedPlayStep = step;
|
|
@@ -9059,6 +9096,7 @@ var playMML = (mml, options = {}) => {
|
|
|
9059
9096
|
const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
|
|
9060
9097
|
const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
|
|
9061
9098
|
let masterVolume = meta.volume ?? options.volume ?? 100;
|
|
9099
|
+
let drumVolume = meta.drumVolume ?? 80;
|
|
9062
9100
|
const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
|
|
9063
9101
|
(a, b) => a - b
|
|
9064
9102
|
);
|
|
@@ -9095,7 +9133,7 @@ var playMML = (mml, options = {}) => {
|
|
|
9095
9133
|
synth?.playNote(e);
|
|
9096
9134
|
},
|
|
9097
9135
|
onPlayDrum: (e) => {
|
|
9098
|
-
const velocity = e.velocity * (masterVolume / 100);
|
|
9136
|
+
const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
|
|
9099
9137
|
options.onPlayDrum?.({ ...e, velocity });
|
|
9100
9138
|
synth?.playDrum({ ...e, velocity });
|
|
9101
9139
|
},
|
|
@@ -9654,7 +9692,6 @@ var importFrom = async (url2, name) => {
|
|
|
9654
9692
|
var createDtmStudio = async (options = {}) => {
|
|
9655
9693
|
const cdn = { ...DEFAULT_CDN, ...options.cdn };
|
|
9656
9694
|
const features = {
|
|
9657
|
-
recorder: true,
|
|
9658
9695
|
midi: true,
|
|
9659
9696
|
chord: true,
|
|
9660
9697
|
presetUI: true,
|
|
@@ -9693,68 +9730,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
9693
9730
|
const singingVoices = createSingingVoices(audioCtx, masterGain, {
|
|
9694
9731
|
voiceWorkerUrl
|
|
9695
9732
|
});
|
|
9696
|
-
const recorder = features.recorder ? setupRecorder(audioCtx, masterGain, drumGain) : null;
|
|
9697
|
-
const downloadWav = () => {
|
|
9698
|
-
if (!recorder) return;
|
|
9699
|
-
const recordedData = recorder.getRecordedData();
|
|
9700
|
-
const ch = recordedData.length;
|
|
9701
|
-
const len = recordedData[0].length;
|
|
9702
|
-
if (len === 0) return;
|
|
9703
|
-
const bufSize = recordedData[0][0].length;
|
|
9704
|
-
const wave = new Float32Array(ch * len * bufSize);
|
|
9705
|
-
let idx = 0;
|
|
9706
|
-
for (let i = 0; i < len; i++)
|
|
9707
|
-
for (let j = 0; j < bufSize; j++)
|
|
9708
|
-
for (let k = 0; k < ch; k++) wave[idx++] = recordedData[k][i][j];
|
|
9709
|
-
const sampleRate = audioCtx.sampleRate;
|
|
9710
|
-
const channels = 2;
|
|
9711
|
-
const bitRate = 16;
|
|
9712
|
-
const step = bitRate / 8;
|
|
9713
|
-
const blockSize = channels * step;
|
|
9714
|
-
const byteLen = wave.length * step;
|
|
9715
|
-
const view = new DataView(new ArrayBuffer(44 + byteLen));
|
|
9716
|
-
const ws = (off2, s) => {
|
|
9717
|
-
for (let i = 0; i < s.length; i++)
|
|
9718
|
-
view.setUint8(off2 + i, s.charCodeAt(i));
|
|
9719
|
-
};
|
|
9720
|
-
ws(0, "RIFF");
|
|
9721
|
-
view.setUint32(4, 32 + byteLen, true);
|
|
9722
|
-
ws(8, "WAVE");
|
|
9723
|
-
ws(12, "fmt ");
|
|
9724
|
-
view.setUint32(16, 16, true);
|
|
9725
|
-
view.setUint16(20, 1, true);
|
|
9726
|
-
view.setUint16(22, channels, true);
|
|
9727
|
-
view.setUint32(24, sampleRate, true);
|
|
9728
|
-
view.setUint32(28, sampleRate * blockSize, true);
|
|
9729
|
-
view.setUint16(32, blockSize, true);
|
|
9730
|
-
view.setUint16(34, bitRate, true);
|
|
9731
|
-
ws(36, "data");
|
|
9732
|
-
view.setUint32(40, byteLen, true);
|
|
9733
|
-
const clamp4 = (n, a2, b) => Math.max(a2, Math.min(b, n));
|
|
9734
|
-
let off = 44;
|
|
9735
|
-
for (let i = 0; i < wave.length; i++, off += step)
|
|
9736
|
-
view.setInt16(
|
|
9737
|
-
off,
|
|
9738
|
-
clamp4(Math.round(wave[i] * 32768), -32768, 32767),
|
|
9739
|
-
true
|
|
9740
|
-
);
|
|
9741
|
-
const blob2 = new Blob([view], { type: "audio/wav" });
|
|
9742
|
-
const url2 = URL.createObjectURL(blob2);
|
|
9743
|
-
const a = document.createElement("a");
|
|
9744
|
-
a.href = url2;
|
|
9745
|
-
a.download = "record.wav";
|
|
9746
|
-
a.click();
|
|
9747
|
-
URL.revokeObjectURL(url2);
|
|
9748
|
-
};
|
|
9749
|
-
const onToggleRecord = recorder ? () => {
|
|
9750
|
-
if (recorder.isRecording()) {
|
|
9751
|
-
recorder.stopRecording();
|
|
9752
|
-
downloadWav();
|
|
9753
|
-
} else {
|
|
9754
|
-
recorder.clearRecordedData();
|
|
9755
|
-
recorder.startRecording();
|
|
9756
|
-
}
|
|
9757
|
-
} : void 0;
|
|
9758
9733
|
const listReady = new Promise((resolve) => {
|
|
9759
9734
|
SoundFont_list.init();
|
|
9760
9735
|
SoundFont_list.onload(() => resolve());
|
|
@@ -9961,7 +9936,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
9961
9936
|
onPlayDrum: playDrum,
|
|
9962
9937
|
singingVoices,
|
|
9963
9938
|
parseMidi,
|
|
9964
|
-
onToggleRecord,
|
|
9965
9939
|
onInstrumentChange: handleInstrumentChange,
|
|
9966
9940
|
...dawOverrides
|
|
9967
9941
|
};
|
|
@@ -10253,7 +10227,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
10253
10227
|
playMML,
|
|
10254
10228
|
playSingingMML,
|
|
10255
10229
|
setDrawOffset,
|
|
10256
|
-
setupRecorder,
|
|
10257
10230
|
shiftNotes,
|
|
10258
10231
|
stripLyrics,
|
|
10259
10232
|
stripMmlMeta,
|