@onjmin/dtm 0.1.21 → 0.1.23
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 +66 -0
- package/dist/index.d.mts +207 -3
- package/dist/index.d.ts +207 -3
- package/dist/index.js +490 -152
- package/dist/index.mjs +486 -152
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3919,7 +3919,7 @@ var PITCH_MAP2 = {
|
|
|
3919
3919
|
b: 11
|
|
3920
3920
|
};
|
|
3921
3921
|
var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
3922
|
-
var META_DIRECTIVE = /#(inst|drum|volume)=([\w-]+)/gi;
|
|
3922
|
+
var META_DIRECTIVE = /#(inst|drum|volume|mode)=([\w-]+)/gi;
|
|
3923
3923
|
var parseMmlMeta = (mml) => {
|
|
3924
3924
|
const meta = {};
|
|
3925
3925
|
for (const m of mml.matchAll(META_DIRECTIVE)) {
|
|
@@ -3929,6 +3929,10 @@ var parseMmlMeta = (mml) => {
|
|
|
3929
3929
|
else if (key === "volume") {
|
|
3930
3930
|
const v = Number.parseInt(m[2], 10);
|
|
3931
3931
|
if (!Number.isNaN(v)) meta.volume = v;
|
|
3932
|
+
} else if (key === "mode") {
|
|
3933
|
+
if (m[2] === "simple" || m[2] === "advanced") {
|
|
3934
|
+
meta.mode = m[2];
|
|
3935
|
+
}
|
|
3932
3936
|
}
|
|
3933
3937
|
}
|
|
3934
3938
|
return meta;
|
|
@@ -3939,6 +3943,7 @@ var formatMmlMeta = (meta) => {
|
|
|
3939
3943
|
if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
|
|
3940
3944
|
if (meta.drum) parts.push(`#drum=${meta.drum}`);
|
|
3941
3945
|
if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
|
|
3946
|
+
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
3942
3947
|
return parts.join(" ");
|
|
3943
3948
|
};
|
|
3944
3949
|
var parseMML = (mml, options = {}) => {
|
|
@@ -3974,7 +3979,8 @@ var parseMML = (mml, options = {}) => {
|
|
|
3974
3979
|
const part = rawPart.trim();
|
|
3975
3980
|
if (part.startsWith("@")) {
|
|
3976
3981
|
let idx = Number.parseInt(part.substring(1), 10);
|
|
3977
|
-
if (clampTrackCount !== void 0 && idx >= clampTrackCount)
|
|
3982
|
+
if (clampTrackCount !== void 0 && idx >= clampTrackCount)
|
|
3983
|
+
idx = clampTrackCount - 1;
|
|
3978
3984
|
trackIndex = idx;
|
|
3979
3985
|
octave = 4;
|
|
3980
3986
|
currentStep = 0;
|
|
@@ -4177,6 +4183,18 @@ var VOICE_IMAGES = {
|
|
|
4177
4183
|
var STEPS_PER_BEAT2 = 48;
|
|
4178
4184
|
var PLAN_TIME = 0.5;
|
|
4179
4185
|
var TICK_INTERVAL_MS = 20;
|
|
4186
|
+
var resolveLoopPoint = (point, bpm, stepsPerBar, sps) => {
|
|
4187
|
+
if ("step" in point) {
|
|
4188
|
+
return point.step;
|
|
4189
|
+
}
|
|
4190
|
+
if ("bar" in point) {
|
|
4191
|
+
return Math.max(0, point.bar - 1) * stepsPerBar;
|
|
4192
|
+
}
|
|
4193
|
+
if ("seconds" in point) {
|
|
4194
|
+
return point.seconds / sps;
|
|
4195
|
+
}
|
|
4196
|
+
return 0;
|
|
4197
|
+
};
|
|
4180
4198
|
var createSequencer = (options) => {
|
|
4181
4199
|
let timeline = [];
|
|
4182
4200
|
let startTime = 0;
|
|
@@ -4186,28 +4204,74 @@ var createSequencer = (options) => {
|
|
|
4186
4204
|
let active = false;
|
|
4187
4205
|
let fromStepValue = 0;
|
|
4188
4206
|
let trackVolumeMap = /* @__PURE__ */ new Map();
|
|
4207
|
+
let isLooping = false;
|
|
4208
|
+
let loopStartStep = 0;
|
|
4209
|
+
let loopEndStep = 0;
|
|
4210
|
+
let loopStartSec = 0;
|
|
4211
|
+
let loopEndSec = 0;
|
|
4212
|
+
let loopDurationSec = 0;
|
|
4213
|
+
let loopStartIndex = 0;
|
|
4214
|
+
let loopBase = 0;
|
|
4215
|
+
let lastPlayStep = 0;
|
|
4189
4216
|
const secondsPerStep = () => 60 / options.getBpm() / STEPS_PER_BEAT2;
|
|
4217
|
+
const getWrappedPlayStep = (time, sps) => {
|
|
4218
|
+
if (!isLooping || loopDurationSec <= 0 || time < loopEndSec) {
|
|
4219
|
+
return fromStepValue + time / sps;
|
|
4220
|
+
}
|
|
4221
|
+
const elapsedInLoop = (time - loopEndSec) % loopDurationSec;
|
|
4222
|
+
return loopStartStep + elapsedInLoop / sps;
|
|
4223
|
+
};
|
|
4190
4224
|
const buildTimeline = (fromStep) => {
|
|
4191
4225
|
timeline = [];
|
|
4192
4226
|
trackVolumeMap = /* @__PURE__ */ new Map();
|
|
4193
4227
|
const sps = secondsPerStep();
|
|
4228
|
+
const bpm = options.getBpm();
|
|
4229
|
+
const stepsPerBar = options.stepsPerBar;
|
|
4230
|
+
const loopOption = options.getLoop?.() ?? false;
|
|
4231
|
+
isLooping = !!loopOption;
|
|
4232
|
+
if (typeof loopOption === "object") {
|
|
4233
|
+
loopStartStep = loopOption.start ? resolveLoopPoint(loopOption.start, bpm, stepsPerBar, sps) : 0;
|
|
4234
|
+
const endVal = loopOption.end ? resolveLoopPoint(loopOption.end, bpm, stepsPerBar, sps) : null;
|
|
4235
|
+
loopEndStep = endVal !== null ? endVal : -1;
|
|
4236
|
+
} else {
|
|
4237
|
+
loopStartStep = 0;
|
|
4238
|
+
loopEndStep = -1;
|
|
4239
|
+
}
|
|
4240
|
+
const startLimit = isLooping ? Math.min(fromStep, loopStartStep) : fromStep;
|
|
4241
|
+
let maxEndStep = 0;
|
|
4194
4242
|
for (const track of options.getTracks()) {
|
|
4195
4243
|
trackVolumeMap.set(track.id, track.volume);
|
|
4196
4244
|
for (const note of track.notes) {
|
|
4245
|
+
if (note.startStep < startLimit) continue;
|
|
4197
4246
|
const relativeStart = note.startStep - fromStep;
|
|
4198
|
-
|
|
4199
|
-
const
|
|
4247
|
+
const when = relativeStart * sps;
|
|
4248
|
+
const duration = note.durationSteps * sps;
|
|
4249
|
+
maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
|
|
4200
4250
|
timeline.push({
|
|
4201
4251
|
trackId: track.id,
|
|
4202
4252
|
pitch: note.pitch,
|
|
4203
4253
|
volume: track.volume / 100,
|
|
4204
|
-
velocity,
|
|
4205
|
-
when
|
|
4206
|
-
duration
|
|
4254
|
+
velocity: note.velocity ?? DEFAULT_PLAYBACK_VELOCITY,
|
|
4255
|
+
when,
|
|
4256
|
+
duration
|
|
4207
4257
|
});
|
|
4208
4258
|
}
|
|
4209
4259
|
}
|
|
4210
4260
|
timeline.sort((a, b) => a.when - b.when);
|
|
4261
|
+
if (loopEndStep === -1) {
|
|
4262
|
+
loopEndStep = maxEndStep;
|
|
4263
|
+
}
|
|
4264
|
+
loopStartSec = (loopStartStep - fromStep) * sps;
|
|
4265
|
+
loopEndSec = (loopEndStep - fromStep) * sps;
|
|
4266
|
+
loopDurationSec = loopEndSec - loopStartSec;
|
|
4267
|
+
loopStartIndex = 0;
|
|
4268
|
+
while (loopStartIndex < timeline.length) {
|
|
4269
|
+
const noteStartStep = fromStep + timeline[loopStartIndex].when / sps;
|
|
4270
|
+
if (noteStartStep >= loopStartStep - 1e-4) {
|
|
4271
|
+
break;
|
|
4272
|
+
}
|
|
4273
|
+
loopStartIndex++;
|
|
4274
|
+
}
|
|
4211
4275
|
};
|
|
4212
4276
|
const scheduleTick = () => {
|
|
4213
4277
|
const sps = secondsPerStep();
|
|
@@ -4216,9 +4280,16 @@ var createSequencer = (options) => {
|
|
|
4216
4280
|
for (const track of options.getTracks()) {
|
|
4217
4281
|
trackVolumeMap.set(track.id, track.volume);
|
|
4218
4282
|
}
|
|
4219
|
-
while (
|
|
4220
|
-
|
|
4221
|
-
|
|
4283
|
+
while (true) {
|
|
4284
|
+
let ev = timeline[nowIndex];
|
|
4285
|
+
if (nowIndex >= timeline.length || isLooping && ev && ev.when >= loopEndSec) {
|
|
4286
|
+
if (!isLooping || loopDurationSec <= 0) break;
|
|
4287
|
+
nowIndex = loopStartIndex;
|
|
4288
|
+
loopBase += loopDurationSec;
|
|
4289
|
+
ev = timeline[nowIndex];
|
|
4290
|
+
}
|
|
4291
|
+
if (!ev) break;
|
|
4292
|
+
const _when = ev.when + loopBase - time;
|
|
4222
4293
|
if (_when > PLAN_TIME) break;
|
|
4223
4294
|
nowIndex++;
|
|
4224
4295
|
if (soloId && ev.trackId !== soloId) continue;
|
|
@@ -4236,7 +4307,7 @@ var createSequencer = (options) => {
|
|
|
4236
4307
|
const pattern = options.getDrumPattern();
|
|
4237
4308
|
if (pattern && pattern.length > 0) {
|
|
4238
4309
|
const { stepsPerBar } = options;
|
|
4239
|
-
const currentStep = (
|
|
4310
|
+
const currentStep = getWrappedPlayStep(time, sps);
|
|
4240
4311
|
const currentStepInBar = currentStep % stepsPerBar;
|
|
4241
4312
|
const nextStep = currentStepInBar + 4;
|
|
4242
4313
|
const crossedBar = currentStepInBar < 4;
|
|
@@ -4253,19 +4324,44 @@ var createSequencer = (options) => {
|
|
|
4253
4324
|
});
|
|
4254
4325
|
}
|
|
4255
4326
|
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4327
|
+
if (time >= 0) {
|
|
4328
|
+
const currentStep = getWrappedPlayStep(time, sps);
|
|
4329
|
+
if (options.cues && options.cues.length > 0 && options.onCue) {
|
|
4330
|
+
const bpm = options.getBpm();
|
|
4331
|
+
const stepsPerBar = options.stepsPerBar;
|
|
4332
|
+
const isCueCrossed = (cueStep, prevStep, currStep) => {
|
|
4333
|
+
if (currStep >= prevStep) {
|
|
4334
|
+
return cueStep > prevStep && cueStep <= currStep;
|
|
4335
|
+
} else {
|
|
4336
|
+
const reachedEnd = cueStep > prevStep && cueStep <= loopEndStep;
|
|
4337
|
+
const startedNew = cueStep >= loopStartStep && cueStep <= currStep;
|
|
4338
|
+
return reachedEnd || startedNew;
|
|
4339
|
+
}
|
|
4340
|
+
};
|
|
4341
|
+
for (const cue of options.cues) {
|
|
4342
|
+
const cueStep = resolveLoopPoint(cue.time, bpm, stepsPerBar, sps);
|
|
4343
|
+
if (isCueCrossed(cueStep, lastPlayStep, currentStep)) {
|
|
4344
|
+
options.onCue(cue.id);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
lastPlayStep = currentStep;
|
|
4349
|
+
}
|
|
4350
|
+
if (!isLooping) {
|
|
4351
|
+
const last = timeline[timeline.length - 1];
|
|
4352
|
+
const lastWhen = last?.when ?? 0;
|
|
4353
|
+
const lastDuration = last?.duration ?? 0;
|
|
4354
|
+
if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
|
|
4355
|
+
stop();
|
|
4356
|
+
options.onEnd();
|
|
4357
|
+
}
|
|
4262
4358
|
}
|
|
4263
4359
|
};
|
|
4264
4360
|
const animate = () => {
|
|
4265
4361
|
if (!active) return;
|
|
4266
4362
|
const sps = secondsPerStep();
|
|
4267
4363
|
const time = options.getAudioTime() - startTime;
|
|
4268
|
-
options.onTick(
|
|
4364
|
+
options.onTick(getWrappedPlayStep(time, sps));
|
|
4269
4365
|
animationId = requestAnimationFrame(animate);
|
|
4270
4366
|
};
|
|
4271
4367
|
const stop = () => {
|
|
@@ -4287,7 +4383,17 @@ var createSequencer = (options) => {
|
|
|
4287
4383
|
if (timeline.length === 0 && !options.getDrumPattern()?.length) return;
|
|
4288
4384
|
active = true;
|
|
4289
4385
|
startTime = options.getAudioTime() + START_DELAY;
|
|
4386
|
+
const sps = secondsPerStep();
|
|
4290
4387
|
nowIndex = 0;
|
|
4388
|
+
while (nowIndex < timeline.length) {
|
|
4389
|
+
const noteStartStep = fromStepValue + timeline[nowIndex].when / sps;
|
|
4390
|
+
if (noteStartStep >= fromStepValue - 1e-4) {
|
|
4391
|
+
break;
|
|
4392
|
+
}
|
|
4393
|
+
nowIndex++;
|
|
4394
|
+
}
|
|
4395
|
+
loopBase = 0;
|
|
4396
|
+
lastPlayStep = fromStepValue - 1e-4;
|
|
4291
4397
|
intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
|
|
4292
4398
|
animationId = requestAnimationFrame(animate);
|
|
4293
4399
|
};
|
|
@@ -4299,6 +4405,73 @@ var createSequencer = (options) => {
|
|
|
4299
4405
|
};
|
|
4300
4406
|
};
|
|
4301
4407
|
|
|
4408
|
+
// src/synth.ts
|
|
4409
|
+
var freqFromPitch = (pitch) => 440 * 2 ** ((pitch - 69) / 12);
|
|
4410
|
+
var createSynth = (ctx, destination = ctx.destination) => {
|
|
4411
|
+
const playNote = (e) => {
|
|
4412
|
+
const osc = ctx.createOscillator();
|
|
4413
|
+
const gain = ctx.createGain();
|
|
4414
|
+
osc.type = "square";
|
|
4415
|
+
osc.frequency.value = freqFromPitch(e.pitch);
|
|
4416
|
+
const t0 = ctx.currentTime + e.when;
|
|
4417
|
+
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5);
|
|
4418
|
+
gain.gain.setValueAtTime(peak, t0);
|
|
4419
|
+
gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
|
|
4420
|
+
osc.connect(gain);
|
|
4421
|
+
if (typeof ctx.createStereoPanner === "function" && e.pan) {
|
|
4422
|
+
const panner = ctx.createStereoPanner();
|
|
4423
|
+
panner.pan.value = Math.max(-1, Math.min(1, e.pan));
|
|
4424
|
+
gain.connect(panner);
|
|
4425
|
+
panner.connect(destination);
|
|
4426
|
+
} else {
|
|
4427
|
+
gain.connect(destination);
|
|
4428
|
+
}
|
|
4429
|
+
osc.start(t0);
|
|
4430
|
+
osc.stop(t0 + e.duration + 0.02);
|
|
4431
|
+
};
|
|
4432
|
+
const playDrum = (e) => {
|
|
4433
|
+
const t0 = ctx.currentTime + e.when;
|
|
4434
|
+
const vol = Math.max(1e-4, Math.min(1, e.velocity));
|
|
4435
|
+
const isKick = e.pitch === 35 || e.pitch === 36;
|
|
4436
|
+
const isSnareLike = e.pitch === 38 || e.pitch === 39 || e.pitch === 40;
|
|
4437
|
+
if (isKick) {
|
|
4438
|
+
const osc = ctx.createOscillator();
|
|
4439
|
+
const g2 = ctx.createGain();
|
|
4440
|
+
osc.frequency.setValueAtTime(150, t0);
|
|
4441
|
+
osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
|
|
4442
|
+
g2.gain.setValueAtTime(vol * 0.9, t0);
|
|
4443
|
+
g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
|
|
4444
|
+
osc.connect(g2).connect(destination);
|
|
4445
|
+
osc.start(t0);
|
|
4446
|
+
osc.stop(t0 + 0.2);
|
|
4447
|
+
osc.onended = () => osc.disconnect();
|
|
4448
|
+
return;
|
|
4449
|
+
}
|
|
4450
|
+
const dur = isSnareLike ? 0.18 : 0.05;
|
|
4451
|
+
const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
|
|
4452
|
+
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
4453
|
+
const data = buffer.getChannelData(0);
|
|
4454
|
+
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
4455
|
+
const src = ctx.createBufferSource();
|
|
4456
|
+
src.buffer = buffer;
|
|
4457
|
+
const filter = ctx.createBiquadFilter();
|
|
4458
|
+
filter.type = isSnareLike ? "bandpass" : "highpass";
|
|
4459
|
+
filter.frequency.value = isSnareLike ? 2e3 : 8e3;
|
|
4460
|
+
const g = ctx.createGain();
|
|
4461
|
+
g.gain.setValueAtTime(vol * (isSnareLike ? 0.7 : 0.4), t0);
|
|
4462
|
+
g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
|
|
4463
|
+
src.connect(filter).connect(g).connect(destination);
|
|
4464
|
+
src.start(t0);
|
|
4465
|
+
src.stop(t0 + dur);
|
|
4466
|
+
src.onended = () => {
|
|
4467
|
+
src.disconnect();
|
|
4468
|
+
filter.disconnect();
|
|
4469
|
+
g.disconnect();
|
|
4470
|
+
};
|
|
4471
|
+
};
|
|
4472
|
+
return { playNote, playDrum };
|
|
4473
|
+
};
|
|
4474
|
+
|
|
4302
4475
|
// src/styles.ts
|
|
4303
4476
|
var STYLE_ID = "dtm-daw-styles";
|
|
4304
4477
|
var DAW_CSS = `
|
|
@@ -4969,6 +5142,13 @@ var DAW_CSS = `
|
|
|
4969
5142
|
font-size: 13px;
|
|
4970
5143
|
line-height: 1.6;
|
|
4971
5144
|
}
|
|
5145
|
+
.dtm-modal-body a {
|
|
5146
|
+
color: var(--dtm-primary);
|
|
5147
|
+
text-decoration: underline;
|
|
5148
|
+
}
|
|
5149
|
+
.dtm-modal-body a:hover {
|
|
5150
|
+
color: var(--dtm-accent);
|
|
5151
|
+
}
|
|
4972
5152
|
.dtm-modal-body h4 {
|
|
4973
5153
|
margin: 12px 0 6px 0;
|
|
4974
5154
|
color: var(--dtm-primary);
|
|
@@ -5423,7 +5603,6 @@ var showBalloon = (balloonEl) => {
|
|
|
5423
5603
|
hideActiveBalloon();
|
|
5424
5604
|
}, 3e3);
|
|
5425
5605
|
};
|
|
5426
|
-
var freqFromPitch = (pitch) => 440 * 2 ** ((pitch - 69) / 12);
|
|
5427
5606
|
var mountMmlPlayer = (target, mml, options = {}) => {
|
|
5428
5607
|
injectStyles(target.ownerDocument ?? document);
|
|
5429
5608
|
const {
|
|
@@ -5494,68 +5673,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
5494
5673
|
if (!audioCtx) audioCtx = new AudioContext();
|
|
5495
5674
|
return audioCtx;
|
|
5496
5675
|
};
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
osc.type = "square";
|
|
5502
|
-
osc.frequency.value = freqFromPitch(e.pitch);
|
|
5503
|
-
const t0 = ctx.currentTime + e.when;
|
|
5504
|
-
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5);
|
|
5505
|
-
gain.gain.setValueAtTime(peak, t0);
|
|
5506
|
-
gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
|
|
5507
|
-
osc.connect(gain);
|
|
5508
|
-
if (typeof ctx.createStereoPanner === "function" && e.pan) {
|
|
5509
|
-
const panner = ctx.createStereoPanner();
|
|
5510
|
-
panner.pan.value = Math.max(-1, Math.min(1, e.pan));
|
|
5511
|
-
gain.connect(panner);
|
|
5512
|
-
panner.connect(ctx.destination);
|
|
5513
|
-
} else {
|
|
5514
|
-
gain.connect(ctx.destination);
|
|
5515
|
-
}
|
|
5516
|
-
osc.start(t0);
|
|
5517
|
-
osc.stop(t0 + e.duration + 0.02);
|
|
5518
|
-
};
|
|
5519
|
-
const drumSynth = (e) => {
|
|
5520
|
-
const ctx = ensureCtx();
|
|
5521
|
-
const t0 = ctx.currentTime + e.when;
|
|
5522
|
-
const vol = Math.max(1e-4, Math.min(1, e.velocity));
|
|
5523
|
-
const isKick = e.pitch === 35 || e.pitch === 36;
|
|
5524
|
-
const isSnareLike = e.pitch === 38 || e.pitch === 39 || e.pitch === 40;
|
|
5525
|
-
if (isKick) {
|
|
5526
|
-
const osc = ctx.createOscillator();
|
|
5527
|
-
const g2 = ctx.createGain();
|
|
5528
|
-
osc.frequency.setValueAtTime(150, t0);
|
|
5529
|
-
osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
|
|
5530
|
-
g2.gain.setValueAtTime(vol * 0.9, t0);
|
|
5531
|
-
g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
|
|
5532
|
-
osc.connect(g2).connect(ctx.destination);
|
|
5533
|
-
osc.start(t0);
|
|
5534
|
-
osc.stop(t0 + 0.2);
|
|
5535
|
-
osc.onended = () => osc.disconnect();
|
|
5536
|
-
return;
|
|
5537
|
-
}
|
|
5538
|
-
const dur = isSnareLike ? 0.18 : 0.05;
|
|
5539
|
-
const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
|
|
5540
|
-
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
5541
|
-
const data = buffer.getChannelData(0);
|
|
5542
|
-
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
5543
|
-
const src = ctx.createBufferSource();
|
|
5544
|
-
src.buffer = buffer;
|
|
5545
|
-
const filter = ctx.createBiquadFilter();
|
|
5546
|
-
filter.type = isSnareLike ? "bandpass" : "highpass";
|
|
5547
|
-
filter.frequency.value = isSnareLike ? 2e3 : 8e3;
|
|
5548
|
-
const g = ctx.createGain();
|
|
5549
|
-
g.gain.setValueAtTime(vol * (isSnareLike ? 0.7 : 0.4), t0);
|
|
5550
|
-
g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
|
|
5551
|
-
src.connect(filter).connect(g).connect(ctx.destination);
|
|
5552
|
-
src.start(t0);
|
|
5553
|
-
src.stop(t0 + dur);
|
|
5554
|
-
src.onended = () => {
|
|
5555
|
-
src.disconnect();
|
|
5556
|
-
filter.disconnect();
|
|
5557
|
-
g.disconnect();
|
|
5558
|
-
};
|
|
5676
|
+
let synthInstance = null;
|
|
5677
|
+
const ensureSynth = () => {
|
|
5678
|
+
if (!synthInstance) synthInstance = createSynth(ensureCtx());
|
|
5679
|
+
return synthInstance;
|
|
5559
5680
|
};
|
|
5560
5681
|
let voices = null;
|
|
5561
5682
|
const ensureVoices = () => {
|
|
@@ -5925,12 +6046,12 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
5925
6046
|
if (em) jumpEmojiAt(em, e.when);
|
|
5926
6047
|
if (lyricTracks.has(trackIdx)) return;
|
|
5927
6048
|
options.onPlayNote?.(e);
|
|
5928
|
-
if (useSynth)
|
|
6049
|
+
if (useSynth) ensureSynth().playNote(e);
|
|
5929
6050
|
},
|
|
5930
6051
|
onPlayDrum: (e) => {
|
|
5931
6052
|
const velocity = e.velocity * (trackVolume / 100);
|
|
5932
6053
|
options.onPlayDrum?.({ ...e, velocity });
|
|
5933
|
-
if (useSynth)
|
|
6054
|
+
if (useSynth) ensureSynth().playDrum({ ...e, velocity });
|
|
5934
6055
|
},
|
|
5935
6056
|
onTick: (step) => {
|
|
5936
6057
|
renderPlayhead(step);
|
|
@@ -6005,13 +6126,19 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6005
6126
|
if (activePlayer && activePlayer !== instance) activePlayer.stop();
|
|
6006
6127
|
activePlayer = instance;
|
|
6007
6128
|
setPlayingUI(true);
|
|
6008
|
-
void
|
|
6009
|
-
|
|
6010
|
-
const
|
|
6011
|
-
if (
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6129
|
+
void (async () => {
|
|
6130
|
+
const resumes = [];
|
|
6131
|
+
const r = options.onResumeAudio?.();
|
|
6132
|
+
if (r) resumes.push(r);
|
|
6133
|
+
if (useSynth) {
|
|
6134
|
+
const ctx = ensureCtx();
|
|
6135
|
+
if (ctx.state === "suspended") resumes.push(ctx.resume());
|
|
6136
|
+
}
|
|
6137
|
+
if (resumes.length > 0) await Promise.all(resumes);
|
|
6138
|
+
if (!playing || activePlayer !== instance) return;
|
|
6139
|
+
if (voicesAvailable && lyricTracks.size > 0) ensureVoices().reset();
|
|
6140
|
+
await startWhenReady();
|
|
6141
|
+
})();
|
|
6015
6142
|
};
|
|
6016
6143
|
const stop = () => {
|
|
6017
6144
|
if (!playing) return;
|
|
@@ -7026,8 +7153,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
7026
7153
|
stepsPerBar: renderConfig.stepsPerBar
|
|
7027
7154
|
});
|
|
7028
7155
|
const play = async () => {
|
|
7029
|
-
options.onResumeAudio?.();
|
|
7030
7156
|
if (playbackState === "playing") return;
|
|
7157
|
+
await options.onResumeAudio?.();
|
|
7031
7158
|
const fromStep = playbackState === "paused" ? pausedPlayStep : playStartStep;
|
|
7032
7159
|
options.singingVoices?.reset();
|
|
7033
7160
|
const lyricMap = buildLyricsMap();
|
|
@@ -7381,7 +7508,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
7381
7508
|
const metaLine = formatMmlMeta({
|
|
7382
7509
|
instrument: currentInstrument || void 0,
|
|
7383
7510
|
drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
|
|
7384
|
-
volume: masterVolume
|
|
7511
|
+
volume: masterVolume,
|
|
7512
|
+
mode
|
|
7385
7513
|
});
|
|
7386
7514
|
if (refs.decomposeChordToggle.checked) {
|
|
7387
7515
|
const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
|
|
@@ -8184,6 +8312,123 @@ var INSTRUMENT_PRESETS = {
|
|
|
8184
8312
|
}
|
|
8185
8313
|
};
|
|
8186
8314
|
|
|
8315
|
+
// src/headless-player.ts
|
|
8316
|
+
var STEPS_PER_BAR2 = 192;
|
|
8317
|
+
var playMML = (mml, options = {}) => {
|
|
8318
|
+
const { placements, bpm: parsedBpm, meta } = parseMML(mml);
|
|
8319
|
+
const bpm = parsedBpm ?? options.defaultBpm ?? DEFAULT_BPM;
|
|
8320
|
+
const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
|
|
8321
|
+
const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
|
|
8322
|
+
let masterVolume = meta.volume ?? options.volume ?? 100;
|
|
8323
|
+
const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
|
|
8324
|
+
(a, b) => a - b
|
|
8325
|
+
);
|
|
8326
|
+
const seqTracks = trackIndices.map((index) => {
|
|
8327
|
+
let id = 0;
|
|
8328
|
+
const notes = placements.filter((p) => p.trackIndex === index).map((p) => ({
|
|
8329
|
+
id: id++,
|
|
8330
|
+
startStep: p.startStep,
|
|
8331
|
+
durationSteps: p.durationSteps,
|
|
8332
|
+
pitch: p.pitch,
|
|
8333
|
+
velocity: 100
|
|
8334
|
+
}));
|
|
8335
|
+
return { id: String(index), volume: masterVolume, notes };
|
|
8336
|
+
});
|
|
8337
|
+
const ownsCtx = !options.audioContext;
|
|
8338
|
+
const ctx = options.audioContext ?? new AudioContext();
|
|
8339
|
+
const destination = options.destination ?? ctx.destination;
|
|
8340
|
+
const useSynth = options.synth ?? !options.onPlayNote;
|
|
8341
|
+
const synth = useSynth ? createSynth(ctx, destination) : null;
|
|
8342
|
+
const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
|
|
8343
|
+
let playing = false;
|
|
8344
|
+
const seq = createSequencer({
|
|
8345
|
+
getTracks: () => seqTracks,
|
|
8346
|
+
getBpm: () => bpm,
|
|
8347
|
+
getPlayStartStep: () => 0,
|
|
8348
|
+
getDrumPattern: () => drumPattern,
|
|
8349
|
+
getSoloTrackId: () => null,
|
|
8350
|
+
getLoop: () => options.loop ?? false,
|
|
8351
|
+
cues: options.cues,
|
|
8352
|
+
onCue: options.onCue,
|
|
8353
|
+
getAudioTime: () => ctx.currentTime,
|
|
8354
|
+
onPlayNote: (e) => {
|
|
8355
|
+
options.onPlayNote?.(e);
|
|
8356
|
+
synth?.playNote(e);
|
|
8357
|
+
},
|
|
8358
|
+
onPlayDrum: (e) => {
|
|
8359
|
+
const velocity = e.velocity * (masterVolume / 100);
|
|
8360
|
+
options.onPlayDrum?.({ ...e, velocity });
|
|
8361
|
+
synth?.playDrum({ ...e, velocity });
|
|
8362
|
+
},
|
|
8363
|
+
onTick: () => {
|
|
8364
|
+
},
|
|
8365
|
+
onEnd: () => finish(),
|
|
8366
|
+
stepsPerBar: STEPS_PER_BAR2
|
|
8367
|
+
});
|
|
8368
|
+
const finish = () => {
|
|
8369
|
+
if (!playing) return;
|
|
8370
|
+
playing = false;
|
|
8371
|
+
options.onStop?.();
|
|
8372
|
+
};
|
|
8373
|
+
const onVisibilityChange = () => {
|
|
8374
|
+
if (!playing) return;
|
|
8375
|
+
if (document.hidden) {
|
|
8376
|
+
void ctx.suspend();
|
|
8377
|
+
} else if (ctx.state === "suspended") {
|
|
8378
|
+
void ctx.resume();
|
|
8379
|
+
}
|
|
8380
|
+
};
|
|
8381
|
+
if (pauseWhenHidden && typeof document !== "undefined") {
|
|
8382
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
8383
|
+
}
|
|
8384
|
+
playing = true;
|
|
8385
|
+
void (async () => {
|
|
8386
|
+
const resumes = [];
|
|
8387
|
+
const r = options.onResumeAudio?.();
|
|
8388
|
+
if (r) resumes.push(r);
|
|
8389
|
+
if (ctx.state === "suspended") resumes.push(ctx.resume());
|
|
8390
|
+
if (resumes.length > 0) await Promise.all(resumes);
|
|
8391
|
+
if (!playing) return;
|
|
8392
|
+
seq.start(0);
|
|
8393
|
+
})();
|
|
8394
|
+
const stop = () => {
|
|
8395
|
+
if (!playing) return;
|
|
8396
|
+
seq.stop();
|
|
8397
|
+
finish();
|
|
8398
|
+
};
|
|
8399
|
+
const setVolume = (volume) => {
|
|
8400
|
+
masterVolume = volume;
|
|
8401
|
+
for (const t of seqTracks) t.volume = volume;
|
|
8402
|
+
};
|
|
8403
|
+
const suspend = () => ctx.suspend();
|
|
8404
|
+
const resume = () => ctx.resume();
|
|
8405
|
+
const destroy = () => {
|
|
8406
|
+
seq.stop();
|
|
8407
|
+
playing = false;
|
|
8408
|
+
if (pauseWhenHidden && typeof document !== "undefined") {
|
|
8409
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
8410
|
+
}
|
|
8411
|
+
if (ownsCtx) void ctx.close();
|
|
8412
|
+
};
|
|
8413
|
+
return {
|
|
8414
|
+
stop,
|
|
8415
|
+
isPlaying: () => playing,
|
|
8416
|
+
setVolume,
|
|
8417
|
+
suspend,
|
|
8418
|
+
resume,
|
|
8419
|
+
destroy
|
|
8420
|
+
};
|
|
8421
|
+
};
|
|
8422
|
+
|
|
8423
|
+
// src/headless-singing-player.ts
|
|
8424
|
+
var playSingingMML = (_mml, _options = {}) => {
|
|
8425
|
+
return Promise.reject(
|
|
8426
|
+
new Error(
|
|
8427
|
+
"playSingingMML is not implemented yet. See implementation notes at the top of headless-singing-player.ts."
|
|
8428
|
+
)
|
|
8429
|
+
);
|
|
8430
|
+
};
|
|
8431
|
+
|
|
8187
8432
|
// src/piano-roll.ts
|
|
8188
8433
|
var createPianoRoll = (options, handlers) => {
|
|
8189
8434
|
const {
|
|
@@ -8635,7 +8880,23 @@ var DEFAULT_CDN = {
|
|
|
8635
8880
|
soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs"
|
|
8636
8881
|
};
|
|
8637
8882
|
var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
|
|
8638
|
-
var TRACK_ROLES = [
|
|
8883
|
+
var TRACK_ROLES = [
|
|
8884
|
+
"melody",
|
|
8885
|
+
"submelody",
|
|
8886
|
+
"bass",
|
|
8887
|
+
"chord",
|
|
8888
|
+
"t4",
|
|
8889
|
+
"t5",
|
|
8890
|
+
"t6",
|
|
8891
|
+
"t7",
|
|
8892
|
+
"t8",
|
|
8893
|
+
"t9",
|
|
8894
|
+
"t10",
|
|
8895
|
+
"t11",
|
|
8896
|
+
"t12",
|
|
8897
|
+
"t13",
|
|
8898
|
+
"t14"
|
|
8899
|
+
];
|
|
8639
8900
|
var resolveDefaultVoiceWorkerUrl = () => {
|
|
8640
8901
|
try {
|
|
8641
8902
|
return new URL("./voice-worker.js", import.meta.url).href;
|
|
@@ -8667,7 +8928,8 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8667
8928
|
drumGain.gain.value = options.drumVolume ?? 1;
|
|
8668
8929
|
drumGain.connect(audioCtx.destination);
|
|
8669
8930
|
const resumeAudio = () => {
|
|
8670
|
-
if (audioCtx.state === "suspended")
|
|
8931
|
+
if (audioCtx.state === "suspended") return audioCtx.resume();
|
|
8932
|
+
return Promise.resolve();
|
|
8671
8933
|
};
|
|
8672
8934
|
const eng = options.engines ?? {};
|
|
8673
8935
|
const [SoundFont, SoundFont_drum, SoundFont_list] = await Promise.all([
|
|
@@ -8771,45 +9033,77 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8771
9033
|
})();
|
|
8772
9034
|
let nameToKey = {};
|
|
8773
9035
|
const soundFonts = /* @__PURE__ */ new Map();
|
|
8774
|
-
const
|
|
8775
|
-
const
|
|
8776
|
-
if (
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
loadedKeyByTrack.set(trackId, instrumentKey);
|
|
8788
|
-
} catch (e) {
|
|
9036
|
+
const loadingByKey = /* @__PURE__ */ new Map();
|
|
9037
|
+
const loadInstrument = (instrumentKey) => {
|
|
9038
|
+
if (soundFonts.has(instrumentKey)) return Promise.resolve();
|
|
9039
|
+
const inflight = loadingByKey.get(instrumentKey);
|
|
9040
|
+
if (inflight) return inflight;
|
|
9041
|
+
const fullName = `${instrumentKey}_${SOUNDFONT_NAME}`;
|
|
9042
|
+
const p = SoundFont.load({
|
|
9043
|
+
ctx: audioCtx,
|
|
9044
|
+
fontName: `_tone_${fullName}`,
|
|
9045
|
+
url: SoundFont.toURL(fullName)
|
|
9046
|
+
}).then((sf) => {
|
|
9047
|
+
soundFonts.set(instrumentKey, sf);
|
|
9048
|
+
}).catch((e) => {
|
|
8789
9049
|
console.error(`[dtm] \u697D\u5668 "${instrumentKey}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557`, e);
|
|
8790
|
-
}
|
|
9050
|
+
}).finally(() => {
|
|
9051
|
+
loadingByKey.delete(instrumentKey);
|
|
9052
|
+
});
|
|
9053
|
+
loadingByKey.set(instrumentKey, p);
|
|
9054
|
+
return p;
|
|
8791
9055
|
};
|
|
8792
9056
|
const defaultPreset = options.defaultPreset ?? "retro_game";
|
|
9057
|
+
const getRoleForTrackIndex = (idx, mode = "simple") => {
|
|
9058
|
+
if (mode === "simple") {
|
|
9059
|
+
if (idx === 0) return "melody";
|
|
9060
|
+
if (idx === 1) return "submelody";
|
|
9061
|
+
if (idx === 2) return "bass";
|
|
9062
|
+
return "chord";
|
|
9063
|
+
} else {
|
|
9064
|
+
return TRACK_ROLES[idx] ?? `t${idx}`;
|
|
9065
|
+
}
|
|
9066
|
+
};
|
|
9067
|
+
const getRoleFromTrackId = (trackId, mode = "simple") => {
|
|
9068
|
+
if (trackId === "melody" || trackId === "submelody" || trackId === "bass" || trackId === "chord") {
|
|
9069
|
+
return trackId;
|
|
9070
|
+
}
|
|
9071
|
+
if (trackId.startsWith("t")) {
|
|
9072
|
+
const idx = Number(trackId.substring(1));
|
|
9073
|
+
if (!isNaN(idx)) {
|
|
9074
|
+
return getRoleForTrackIndex(idx, mode);
|
|
9075
|
+
}
|
|
9076
|
+
}
|
|
9077
|
+
return trackId;
|
|
9078
|
+
};
|
|
8793
9079
|
const instrumentNameFor = (preset, trackId) => preset[trackId] ?? preset.melody;
|
|
8794
|
-
const
|
|
9080
|
+
const resolveSoundFont = (presetKey, trackId, mode = "simple") => {
|
|
9081
|
+
const preset = INSTRUMENT_PRESETS[presetKey];
|
|
9082
|
+
if (!preset) return void 0;
|
|
9083
|
+
const role = getRoleFromTrackId(trackId, mode);
|
|
9084
|
+
const key = nameToKey[instrumentNameFor(preset, role)];
|
|
9085
|
+
return key ? soundFonts.get(key) : void 0;
|
|
9086
|
+
};
|
|
9087
|
+
const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES], mode = "simple") => {
|
|
8795
9088
|
const preset = INSTRUMENT_PRESETS[presetKey];
|
|
8796
9089
|
if (!preset) return;
|
|
8797
9090
|
await listReady;
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
9091
|
+
const keys = /* @__PURE__ */ new Set();
|
|
9092
|
+
for (const trackId of trackIds) {
|
|
9093
|
+
const role = getRoleFromTrackId(trackId, mode);
|
|
9094
|
+
const key = nameToKey[instrumentNameFor(preset, role)];
|
|
9095
|
+
if (key) keys.add(key);
|
|
9096
|
+
}
|
|
9097
|
+
await Promise.all([...keys].map((key) => loadInstrument(key)));
|
|
8804
9098
|
};
|
|
8805
|
-
const applyPreset = async (daw, presetKey, trackIds, loadingTarget) => {
|
|
9099
|
+
const applyPreset = async (daw, presetKey, trackIds, loadingTarget, mode = "simple") => {
|
|
8806
9100
|
const wasPlaying = daw.getPlaybackState() === "playing";
|
|
8807
9101
|
if (wasPlaying) daw.pause();
|
|
8808
9102
|
const overlay = loadingTarget ? showLoadingOverlay(loadingTarget) : null;
|
|
8809
9103
|
daw.setLoading?.(true);
|
|
8810
9104
|
try {
|
|
8811
9105
|
daw.setInstrument(presetKey);
|
|
8812
|
-
await loadPreset(presetKey, trackIds);
|
|
9106
|
+
await loadPreset(presetKey, trackIds, mode);
|
|
8813
9107
|
} finally {
|
|
8814
9108
|
overlay?.remove();
|
|
8815
9109
|
daw.setLoading?.(false);
|
|
@@ -8844,8 +9138,10 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8844
9138
|
const key = select.value;
|
|
8845
9139
|
opts.onChange?.(key);
|
|
8846
9140
|
const trackIds = opts.getTrackIds?.() ?? [...TRACK_ROLES];
|
|
9141
|
+
const isAdvanced = trackIds.includes("t0");
|
|
9142
|
+
const mode = isAdvanced ? "advanced" : "simple";
|
|
8847
9143
|
try {
|
|
8848
|
-
await applyPreset(daw, key, trackIds, opts.loadingTarget);
|
|
9144
|
+
await applyPreset(daw, key, trackIds, opts.loadingTarget, mode);
|
|
8849
9145
|
} finally {
|
|
8850
9146
|
busy = false;
|
|
8851
9147
|
}
|
|
@@ -8870,18 +9166,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8870
9166
|
await listReady;
|
|
8871
9167
|
nameToKey = await buildNameToKeyMapping();
|
|
8872
9168
|
await Promise.all([drumReady, loadPreset(defaultPreset)]);
|
|
8873
|
-
const playNote = (e) => {
|
|
8874
|
-
const sf = soundFonts.get(e.trackId);
|
|
8875
|
-
if (!sf) return;
|
|
8876
|
-
sf.play({
|
|
8877
|
-
ctx: audioCtx,
|
|
8878
|
-
destination: masterGain,
|
|
8879
|
-
pitch: e.pitch,
|
|
8880
|
-
volume: e.volume,
|
|
8881
|
-
when: e.when,
|
|
8882
|
-
duration: e.duration
|
|
8883
|
-
});
|
|
8884
|
-
};
|
|
8885
9169
|
const playDrum = (e) => {
|
|
8886
9170
|
if (!SoundFont_drum.font) return;
|
|
8887
9171
|
SoundFont_drum.play({
|
|
@@ -8893,19 +9177,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8893
9177
|
duration: e.duration
|
|
8894
9178
|
});
|
|
8895
9179
|
};
|
|
8896
|
-
const sfForPlayerTrack = (trackId) => soundFonts.get(TRACK_ROLES[Number(trackId)] ?? "") ?? soundFonts.get(`t${trackId}`);
|
|
8897
|
-
const playPlayerNote = (e) => {
|
|
8898
|
-
const sf = sfForPlayerTrack(e.trackId);
|
|
8899
|
-
if (!sf) return;
|
|
8900
|
-
sf.play({
|
|
8901
|
-
ctx: audioCtx,
|
|
8902
|
-
destination: masterGain,
|
|
8903
|
-
pitch: e.pitch,
|
|
8904
|
-
volume: e.volume,
|
|
8905
|
-
when: e.when,
|
|
8906
|
-
duration: e.duration
|
|
8907
|
-
});
|
|
8908
|
-
};
|
|
8909
9180
|
const editorPresetSelects = /* @__PURE__ */ new WeakMap();
|
|
8910
9181
|
const mountedEditors = [];
|
|
8911
9182
|
const mountedPlayers = [];
|
|
@@ -8914,6 +9185,25 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8914
9185
|
const { preset, presetUI, ...dawOverrides } = opts;
|
|
8915
9186
|
const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
|
|
8916
9187
|
const trackIds = tracks.map((t) => t.id);
|
|
9188
|
+
const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
9189
|
+
let editorPreset = presetKey;
|
|
9190
|
+
const isAdvancedMode = dawOverrides.mode === "advanced";
|
|
9191
|
+
const playNote = (e) => {
|
|
9192
|
+
const sf = resolveSoundFont(
|
|
9193
|
+
editorPreset,
|
|
9194
|
+
e.trackId,
|
|
9195
|
+
isAdvancedMode ? "advanced" : "simple"
|
|
9196
|
+
);
|
|
9197
|
+
if (!sf) return;
|
|
9198
|
+
sf.play({
|
|
9199
|
+
ctx: audioCtx,
|
|
9200
|
+
destination: masterGain,
|
|
9201
|
+
pitch: e.pitch,
|
|
9202
|
+
volume: e.volume,
|
|
9203
|
+
when: e.when,
|
|
9204
|
+
duration: e.duration
|
|
9205
|
+
});
|
|
9206
|
+
};
|
|
8917
9207
|
const base = {
|
|
8918
9208
|
getAudioTime: () => audioCtx.currentTime,
|
|
8919
9209
|
onResumeAudio: resumeAudio,
|
|
@@ -8926,7 +9216,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8926
9216
|
};
|
|
8927
9217
|
const daw = mountDAW(target, base);
|
|
8928
9218
|
mountedEditors.push(daw);
|
|
8929
|
-
const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
8930
9219
|
const wantPresetUI = presetUI ?? features.presetUI;
|
|
8931
9220
|
let presetSelect = null;
|
|
8932
9221
|
if (wantPresetUI) {
|
|
@@ -8937,13 +9226,21 @@ var createDtmStudio = async (options = {}) => {
|
|
|
8937
9226
|
getTrackIds: () => trackIds,
|
|
8938
9227
|
value: presetKey,
|
|
8939
9228
|
loadingTarget: rollEl ?? target,
|
|
8940
|
-
position: "prepend"
|
|
9229
|
+
position: "prepend",
|
|
9230
|
+
// 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
|
|
9231
|
+
onChange: (key) => {
|
|
9232
|
+
editorPreset = key;
|
|
9233
|
+
}
|
|
8941
9234
|
});
|
|
8942
9235
|
editorPresetSelects.set(target, presetSelect);
|
|
8943
9236
|
}
|
|
8944
9237
|
daw.setInstrument(presetKey);
|
|
8945
9238
|
daw.setLoading?.(true);
|
|
8946
|
-
void loadPreset(
|
|
9239
|
+
void loadPreset(
|
|
9240
|
+
presetKey,
|
|
9241
|
+
trackIds,
|
|
9242
|
+
isAdvancedMode ? "advanced" : "simple"
|
|
9243
|
+
).finally(() => {
|
|
8947
9244
|
daw.setLoading?.(false);
|
|
8948
9245
|
});
|
|
8949
9246
|
const destroy = () => {
|
|
@@ -9041,10 +9338,43 @@ var createDtmStudio = async (options = {}) => {
|
|
|
9041
9338
|
return instance;
|
|
9042
9339
|
};
|
|
9043
9340
|
const mountPlayer = (target, mml, opts = {}) => {
|
|
9044
|
-
const
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9341
|
+
const parsed = parseMML(mml, {});
|
|
9342
|
+
const meta = parsed.meta ?? {};
|
|
9343
|
+
const playerPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : defaultPreset;
|
|
9344
|
+
const isAdvancedMode = meta.mode === "advanced";
|
|
9345
|
+
const trackIndices = [
|
|
9346
|
+
...new Set(parsed.placements.map((p) => p.trackIndex))
|
|
9347
|
+
];
|
|
9348
|
+
const trackIds = trackIndices.map(
|
|
9349
|
+
(idx) => getRoleForTrackIndex(idx, isAdvancedMode ? "advanced" : "simple")
|
|
9350
|
+
);
|
|
9351
|
+
const loadTrackIds = trackIds.length > 0 ? trackIds : [...TRACK_ROLES];
|
|
9352
|
+
void loadPreset(
|
|
9353
|
+
playerPreset,
|
|
9354
|
+
loadTrackIds,
|
|
9355
|
+
isAdvancedMode ? "advanced" : "simple"
|
|
9356
|
+
);
|
|
9357
|
+
const playPlayerNote = (e) => {
|
|
9358
|
+
const idx = Number(e.trackId);
|
|
9359
|
+
const role = getRoleForTrackIndex(
|
|
9360
|
+
idx,
|
|
9361
|
+
isAdvancedMode ? "advanced" : "simple"
|
|
9362
|
+
);
|
|
9363
|
+
const sf = resolveSoundFont(
|
|
9364
|
+
playerPreset,
|
|
9365
|
+
role,
|
|
9366
|
+
isAdvancedMode ? "advanced" : "simple"
|
|
9367
|
+
);
|
|
9368
|
+
if (!sf) return;
|
|
9369
|
+
sf.play({
|
|
9370
|
+
ctx: audioCtx,
|
|
9371
|
+
destination: masterGain,
|
|
9372
|
+
pitch: e.pitch,
|
|
9373
|
+
volume: e.volume,
|
|
9374
|
+
when: e.when,
|
|
9375
|
+
duration: e.duration
|
|
9376
|
+
});
|
|
9377
|
+
};
|
|
9048
9378
|
const player = mountMmlPlayer(target, mml, {
|
|
9049
9379
|
getAudioTime: () => audioCtx.currentTime,
|
|
9050
9380
|
onResumeAudio: resumeAudio,
|
|
@@ -9122,6 +9452,7 @@ export {
|
|
|
9122
9452
|
createPianoRoll,
|
|
9123
9453
|
createSequencer,
|
|
9124
9454
|
createSingingVoices,
|
|
9455
|
+
createSynth,
|
|
9125
9456
|
createVoiceRegistry,
|
|
9126
9457
|
decomposeToMonophonic,
|
|
9127
9458
|
drawGrid,
|
|
@@ -9135,6 +9466,7 @@ export {
|
|
|
9135
9466
|
extractMidiPlacementsByTrack,
|
|
9136
9467
|
fetchSoundFontList,
|
|
9137
9468
|
formatMmlMeta,
|
|
9469
|
+
freqFromPitch,
|
|
9138
9470
|
generateRandomPattern,
|
|
9139
9471
|
getDrawOffset,
|
|
9140
9472
|
getGridCanvas,
|
|
@@ -9157,6 +9489,8 @@ export {
|
|
|
9157
9489
|
parseLyrics,
|
|
9158
9490
|
parseMML,
|
|
9159
9491
|
parseMmlMeta,
|
|
9492
|
+
playMML,
|
|
9493
|
+
playSingingMML,
|
|
9160
9494
|
setDrawOffset,
|
|
9161
9495
|
setupRecorder,
|
|
9162
9496
|
shiftNotes,
|