@onjmin/dtm 0.1.68 → 0.1.70

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 CHANGED
@@ -1827,6 +1827,12 @@ type DtmStudio = {
1827
1827
  mountChordPlayer: (target: HTMLElement, chords: string, options?: Omit<MountChordPlayerOptions, "studio">) => ChordPlayerInstance;
1828
1828
  /** UIなしで高品質な楽器再生(SoundFont)を行う */
1829
1829
  play: (mml: string, options?: Omit<PlayMmlOptions, "audioContext" | "destination" | "onPlayNote" | "onPlayDrum" | "synth">) => MmlPlayback;
1830
+ /** UIなしで高品質な楽器再生(SoundFont)+歌声合成を行う */
1831
+ playSingingMML: (mml: string, options?: Omit<PlaySingingMmlOptions, "audioContext" | "destination" | "onPlayNote" | "onPlayDrum" | "synth">) => Promise<MmlPlayback>;
1832
+ /** SoundFontへ PlayNoteEvent を鳴らす(headless API の onPlayNote フック用) */
1833
+ playNoteEvent: (e: PlayNoteEvent) => void;
1834
+ /** SoundFontへ PlayDrumEvent を鳴らす(headless API の onPlayDrum フック用) */
1835
+ playDrumEvent: (e: PlayDrumEvent) => void;
1830
1836
  /** SoundFontを用いた単音再生を行う */
1831
1837
  playNote: (options: {
1832
1838
  pitch: number;
package/dist/index.d.ts CHANGED
@@ -1827,6 +1827,12 @@ type DtmStudio = {
1827
1827
  mountChordPlayer: (target: HTMLElement, chords: string, options?: Omit<MountChordPlayerOptions, "studio">) => ChordPlayerInstance;
1828
1828
  /** UIなしで高品質な楽器再生(SoundFont)を行う */
1829
1829
  play: (mml: string, options?: Omit<PlayMmlOptions, "audioContext" | "destination" | "onPlayNote" | "onPlayDrum" | "synth">) => MmlPlayback;
1830
+ /** UIなしで高品質な楽器再生(SoundFont)+歌声合成を行う */
1831
+ playSingingMML: (mml: string, options?: Omit<PlaySingingMmlOptions, "audioContext" | "destination" | "onPlayNote" | "onPlayDrum" | "synth">) => Promise<MmlPlayback>;
1832
+ /** SoundFontへ PlayNoteEvent を鳴らす(headless API の onPlayNote フック用) */
1833
+ playNoteEvent: (e: PlayNoteEvent) => void;
1834
+ /** SoundFontへ PlayDrumEvent を鳴らす(headless API の onPlayDrum フック用) */
1835
+ playDrumEvent: (e: PlayDrumEvent) => void;
1830
1836
  /** SoundFontを用いた単音再生を行う */
1831
1837
  playNote: (options: {
1832
1838
  pitch: number;
package/dist/index.js CHANGED
@@ -3417,6 +3417,13 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3417
3417
  const wave = tone.wave ?? "square";
3418
3418
  const attack = tone.attack ?? 0;
3419
3419
  const gainScale = tone.gain ?? 1;
3420
+ const compressor = ctx.createDynamicsCompressor();
3421
+ compressor.threshold.value = -12;
3422
+ compressor.knee.value = 6;
3423
+ compressor.ratio.value = 8;
3424
+ compressor.attack.value = 3e-3;
3425
+ compressor.release.value = 0.15;
3426
+ compressor.connect(destination);
3420
3427
  const playNote3 = (e) => {
3421
3428
  const osc = ctx.createOscillator();
3422
3429
  const gain = ctx.createGain();
@@ -3449,9 +3456,9 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3449
3456
  panner = ctx.createStereoPanner();
3450
3457
  panner.pan.value = Math.max(-1, Math.min(1, e.pan));
3451
3458
  gain.connect(panner);
3452
- panner.connect(destination);
3459
+ panner.connect(compressor);
3453
3460
  } else {
3454
- gain.connect(destination);
3461
+ gain.connect(compressor);
3455
3462
  }
3456
3463
  osc.start(t0);
3457
3464
  osc.stop(t0 + e.duration + 0.02);
@@ -3473,7 +3480,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3473
3480
  osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
3474
3481
  g2.gain.setValueAtTime(vol * 0.135, t0);
3475
3482
  g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
3476
- osc.connect(g2).connect(destination);
3483
+ osc.connect(g2).connect(compressor);
3477
3484
  osc.start(t0);
3478
3485
  osc.stop(t0 + 0.2);
3479
3486
  osc.onended = () => osc.disconnect();
@@ -3492,7 +3499,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3492
3499
  const g = ctx.createGain();
3493
3500
  g.gain.setValueAtTime(vol * (isSnareLike ? 0.105 : 0.06), t0);
3494
3501
  g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
3495
- src.connect(filter).connect(g).connect(destination);
3502
+ src.connect(filter).connect(g).connect(compressor);
3496
3503
  src.start(t0);
3497
3504
  src.stop(t0 + dur);
3498
3505
  src.onended = () => {
@@ -3506,6 +3513,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3506
3513
 
3507
3514
  // src/headless-player.ts
3508
3515
  var STEPS_PER_BAR = 192;
3516
+ var TRACK_ID_BY_INDEX = ["melody", "submelody", "bass", "chord"];
3509
3517
  var playPlacements = (placements, options) => {
3510
3518
  const bpm = options.bpm;
3511
3519
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
@@ -3524,7 +3532,11 @@ var playPlacements = (placements, options) => {
3524
3532
  pitch: p.pitch,
3525
3533
  velocity: p.velocity
3526
3534
  }));
3527
- return { id: String(index), volume: masterVolume, notes };
3535
+ return {
3536
+ id: TRACK_ID_BY_INDEX[index] ?? `t${index}`,
3537
+ volume: masterVolume,
3538
+ notes
3539
+ };
3528
3540
  });
3529
3541
  const ownsCtx = !options.audioContext;
3530
3542
  const ctx = options.audioContext ?? new AudioContext();
@@ -13150,6 +13162,7 @@ var mountDAW = (target, options = {}) => {
13150
13162
  // src/headless-singing-player.ts
13151
13163
  var STEPS_PER_BEAT4 = 48;
13152
13164
  var STEPS_PER_BAR3 = 192;
13165
+ var TRACK_ID_BY_INDEX2 = ["melody", "submelody", "bass", "chord"];
13153
13166
  var playSingingMML = async (mml, options = {}) => {
13154
13167
  const {
13155
13168
  placements,
@@ -13183,7 +13196,7 @@ var playSingingMML = async (mml, options = {}) => {
13183
13196
  velocity: p.velocity
13184
13197
  }));
13185
13198
  return {
13186
- id: String(index),
13199
+ id: TRACK_ID_BY_INDEX2[index] ?? `t${index}`,
13187
13200
  volume: trackVolume / 100 * masterVolume,
13188
13201
  notes
13189
13202
  };
@@ -13198,7 +13211,9 @@ var playSingingMML = async (mml, options = {}) => {
13198
13211
  let destroyed = false;
13199
13212
  let voices = options.singingVoices ?? null;
13200
13213
  const buildStreamTracks = (fromStep) => [...lyricTracks.entries()].map(([index, lt]) => {
13201
- const seqTrack = seqTracks.find((t) => Number(t.id) === index);
13214
+ const seqTrack = seqTracks.find(
13215
+ (t) => t.id === (TRACK_ID_BY_INDEX2[index] ?? `t${index}`)
13216
+ );
13202
13217
  const sorted = [...seqTrack?.notes ?? []].sort(
13203
13218
  (a, b) => a.startStep - b.startStep
13204
13219
  );
@@ -13217,7 +13232,7 @@ var playSingingMML = async (mml, options = {}) => {
13217
13232
  });
13218
13233
  }
13219
13234
  return {
13220
- id: String(index),
13235
+ id: TRACK_ID_BY_INDEX2[index] ?? `t${index}`,
13221
13236
  model: lt.model,
13222
13237
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
13223
13238
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
@@ -13235,7 +13250,10 @@ var playSingingMML = async (mml, options = {}) => {
13235
13250
  onCue: options.onCue,
13236
13251
  getAudioTime: () => ctx.currentTime,
13237
13252
  onPlayNote: (e) => {
13238
- const trackIdx = Number(e.trackId);
13253
+ const namedIdx = TRACK_ID_BY_INDEX2.indexOf(
13254
+ e.trackId
13255
+ );
13256
+ const trackIdx = namedIdx >= 0 ? namedIdx : Number(e.trackId);
13239
13257
  if (lyricTracks.has(trackIdx)) return;
13240
13258
  options.onPlayNote?.(e);
13241
13259
  synth?.playNote(e);
@@ -13931,6 +13949,23 @@ var TRACK_ROLES = [
13931
13949
  "t13",
13932
13950
  "t14"
13933
13951
  ];
13952
+ var resolveTrackIdxAndRole = (trackId) => {
13953
+ const roleIdx = TRACK_ROLES.indexOf(trackId);
13954
+ if (roleIdx >= 0) {
13955
+ return { trackIdx: roleIdx, role: trackId };
13956
+ }
13957
+ if (trackId.startsWith("t")) {
13958
+ const parsed2 = Number(trackId.slice(1));
13959
+ const validIdx2 = Number.isNaN(parsed2) ? 0 : parsed2;
13960
+ return {
13961
+ trackIdx: validIdx2,
13962
+ role: TRACK_ROLES[validIdx2] ?? `t${validIdx2}`
13963
+ };
13964
+ }
13965
+ const parsed = Number(trackId);
13966
+ const validIdx = Number.isNaN(parsed) ? 0 : parsed;
13967
+ return { trackIdx: validIdx, role: TRACK_ROLES[validIdx] ?? `t${validIdx}` };
13968
+ };
13934
13969
  var resolveDefaultVoiceWorkerUrl = () => {
13935
13970
  try {
13936
13971
  return new URL("./voice-worker.js", import_meta.url).href;
@@ -14494,19 +14529,19 @@ var createDtmStudio = async (options = {}) => {
14494
14529
  loadPlayerTrackInstruments()
14495
14530
  ]);
14496
14531
  const playPlayerNote = (e) => {
14497
- const idx = Number(e.trackId);
14498
- const overrideKey = playerTrackInstKeys.get(idx);
14532
+ const { trackIdx, role } = resolveTrackIdxAndRole(e.trackId);
14533
+ const overrideKey = playerTrackInstKeys.get(trackIdx);
14499
14534
  let sfInst;
14500
14535
  if (overrideKey) {
14501
14536
  sfInst = soundFonts.get(overrideKey);
14502
14537
  } else {
14503
- const role = getRoleForTrackIndex(
14504
- idx,
14538
+ const roleFromIdx = getRoleForTrackIndex(
14539
+ trackIdx,
14505
14540
  isAdvancedMode ? "advanced" : "simple"
14506
14541
  );
14507
14542
  sfInst = resolveSoundFont(
14508
14543
  playerPreset,
14509
- role,
14544
+ roleFromIdx,
14510
14545
  isAdvancedMode ? "advanced" : "simple"
14511
14546
  );
14512
14547
  }
@@ -14530,6 +14565,78 @@ var createDtmStudio = async (options = {}) => {
14530
14565
  onResumeAudio: resumeAudio
14531
14566
  });
14532
14567
  };
14568
+ const playSingingMMLInstance = (mml, opts = {}) => {
14569
+ const { placements } = parseMML(mml);
14570
+ const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
14571
+ (a, b) => a - b
14572
+ );
14573
+ const isAdvancedMode = trackIndices.some((idx) => idx >= 4);
14574
+ const playerPreset = defaultPreset;
14575
+ void loadPreset(
14576
+ playerPreset,
14577
+ trackIndices.map(String),
14578
+ isAdvancedMode ? "advanced" : "simple"
14579
+ );
14580
+ const playPlayerNote = (e) => {
14581
+ const { role } = resolveTrackIdxAndRole(e.trackId);
14582
+ let sfInst = resolveSoundFont(
14583
+ playerPreset,
14584
+ role,
14585
+ isAdvancedMode ? "advanced" : "simple"
14586
+ );
14587
+ if (!sfInst) {
14588
+ void loadPreset(
14589
+ playerPreset,
14590
+ [role],
14591
+ isAdvancedMode ? "advanced" : "simple"
14592
+ );
14593
+ sfInst = resolveSoundFont(
14594
+ playerPreset,
14595
+ role,
14596
+ isAdvancedMode ? "advanced" : "simple"
14597
+ );
14598
+ }
14599
+ if (!sfInst) return;
14600
+ sfInst.play({
14601
+ ctx: audioCtx,
14602
+ destination: masterGain,
14603
+ pitch: e.pitch,
14604
+ volume: e.volume,
14605
+ when: e.when,
14606
+ duration: e.duration
14607
+ });
14608
+ };
14609
+ return playSingingMML(mml, {
14610
+ ...opts,
14611
+ audioContext: audioCtx,
14612
+ destination: masterGain,
14613
+ synth: false,
14614
+ singingVoices,
14615
+ onPlayNote: playPlayerNote,
14616
+ onPlayDrum: playDrum,
14617
+ onResumeAudio: resumeAudio
14618
+ });
14619
+ };
14620
+ const playNoteEvent = (e) => {
14621
+ const { role } = resolveTrackIdxAndRole(e.trackId);
14622
+ let sfInst = resolveSoundFont(defaultPreset, role, "simple");
14623
+ if (!sfInst) {
14624
+ void loadPreset(defaultPreset, [role], "simple");
14625
+ sfInst = resolveSoundFont(defaultPreset, role, "simple");
14626
+ }
14627
+ if (!sfInst) return;
14628
+ sfInst.play({
14629
+ ctx: audioCtx,
14630
+ destination: masterGain,
14631
+ pitch: e.pitch,
14632
+ volume: e.volume,
14633
+ when: e.when,
14634
+ duration: e.duration
14635
+ });
14636
+ };
14637
+ const playDrumEvent = (e) => {
14638
+ playDrum(e);
14639
+ };
14533
14640
  const playNote3 = async (options2) => {
14534
14641
  await listReady;
14535
14642
  const key = options2.instrument ? resolveNameToKey(options2.instrument) : void 0;
@@ -14635,6 +14742,9 @@ var createDtmStudio = async (options = {}) => {
14635
14742
  mountPlayer,
14636
14743
  mountChordPlayer: mountChordPlayerInstance,
14637
14744
  play,
14745
+ playSingingMML: playSingingMMLInstance,
14746
+ playNoteEvent,
14747
+ playDrumEvent,
14638
14748
  playNote: playNote3,
14639
14749
  playChords: playChords3,
14640
14750
  loadPreset,
package/dist/index.mjs CHANGED
@@ -3294,6 +3294,13 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3294
3294
  const wave = tone.wave ?? "square";
3295
3295
  const attack = tone.attack ?? 0;
3296
3296
  const gainScale = tone.gain ?? 1;
3297
+ const compressor = ctx.createDynamicsCompressor();
3298
+ compressor.threshold.value = -12;
3299
+ compressor.knee.value = 6;
3300
+ compressor.ratio.value = 8;
3301
+ compressor.attack.value = 3e-3;
3302
+ compressor.release.value = 0.15;
3303
+ compressor.connect(destination);
3297
3304
  const playNote3 = (e) => {
3298
3305
  const osc = ctx.createOscillator();
3299
3306
  const gain = ctx.createGain();
@@ -3326,9 +3333,9 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3326
3333
  panner = ctx.createStereoPanner();
3327
3334
  panner.pan.value = Math.max(-1, Math.min(1, e.pan));
3328
3335
  gain.connect(panner);
3329
- panner.connect(destination);
3336
+ panner.connect(compressor);
3330
3337
  } else {
3331
- gain.connect(destination);
3338
+ gain.connect(compressor);
3332
3339
  }
3333
3340
  osc.start(t0);
3334
3341
  osc.stop(t0 + e.duration + 0.02);
@@ -3350,7 +3357,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3350
3357
  osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
3351
3358
  g2.gain.setValueAtTime(vol * 0.135, t0);
3352
3359
  g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
3353
- osc.connect(g2).connect(destination);
3360
+ osc.connect(g2).connect(compressor);
3354
3361
  osc.start(t0);
3355
3362
  osc.stop(t0 + 0.2);
3356
3363
  osc.onended = () => osc.disconnect();
@@ -3369,7 +3376,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3369
3376
  const g = ctx.createGain();
3370
3377
  g.gain.setValueAtTime(vol * (isSnareLike ? 0.105 : 0.06), t0);
3371
3378
  g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
3372
- src.connect(filter).connect(g).connect(destination);
3379
+ src.connect(filter).connect(g).connect(compressor);
3373
3380
  src.start(t0);
3374
3381
  src.stop(t0 + dur);
3375
3382
  src.onended = () => {
@@ -3383,6 +3390,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
3383
3390
 
3384
3391
  // src/headless-player.ts
3385
3392
  var STEPS_PER_BAR = 192;
3393
+ var TRACK_ID_BY_INDEX = ["melody", "submelody", "bass", "chord"];
3386
3394
  var playPlacements = (placements, options) => {
3387
3395
  const bpm = options.bpm;
3388
3396
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
@@ -3401,7 +3409,11 @@ var playPlacements = (placements, options) => {
3401
3409
  pitch: p.pitch,
3402
3410
  velocity: p.velocity
3403
3411
  }));
3404
- return { id: String(index), volume: masterVolume, notes };
3412
+ return {
3413
+ id: TRACK_ID_BY_INDEX[index] ?? `t${index}`,
3414
+ volume: masterVolume,
3415
+ notes
3416
+ };
3405
3417
  });
3406
3418
  const ownsCtx = !options.audioContext;
3407
3419
  const ctx = options.audioContext ?? new AudioContext();
@@ -13027,6 +13039,7 @@ var mountDAW = (target, options = {}) => {
13027
13039
  // src/headless-singing-player.ts
13028
13040
  var STEPS_PER_BEAT4 = 48;
13029
13041
  var STEPS_PER_BAR3 = 192;
13042
+ var TRACK_ID_BY_INDEX2 = ["melody", "submelody", "bass", "chord"];
13030
13043
  var playSingingMML = async (mml, options = {}) => {
13031
13044
  const {
13032
13045
  placements,
@@ -13060,7 +13073,7 @@ var playSingingMML = async (mml, options = {}) => {
13060
13073
  velocity: p.velocity
13061
13074
  }));
13062
13075
  return {
13063
- id: String(index),
13076
+ id: TRACK_ID_BY_INDEX2[index] ?? `t${index}`,
13064
13077
  volume: trackVolume / 100 * masterVolume,
13065
13078
  notes
13066
13079
  };
@@ -13075,7 +13088,9 @@ var playSingingMML = async (mml, options = {}) => {
13075
13088
  let destroyed = false;
13076
13089
  let voices = options.singingVoices ?? null;
13077
13090
  const buildStreamTracks = (fromStep) => [...lyricTracks.entries()].map(([index, lt]) => {
13078
- const seqTrack = seqTracks.find((t) => Number(t.id) === index);
13091
+ const seqTrack = seqTracks.find(
13092
+ (t) => t.id === (TRACK_ID_BY_INDEX2[index] ?? `t${index}`)
13093
+ );
13079
13094
  const sorted = [...seqTrack?.notes ?? []].sort(
13080
13095
  (a, b) => a.startStep - b.startStep
13081
13096
  );
@@ -13094,7 +13109,7 @@ var playSingingMML = async (mml, options = {}) => {
13094
13109
  });
13095
13110
  }
13096
13111
  return {
13097
- id: String(index),
13112
+ id: TRACK_ID_BY_INDEX2[index] ?? `t${index}`,
13098
13113
  model: lt.model,
13099
13114
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
13100
13115
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
@@ -13112,7 +13127,10 @@ var playSingingMML = async (mml, options = {}) => {
13112
13127
  onCue: options.onCue,
13113
13128
  getAudioTime: () => ctx.currentTime,
13114
13129
  onPlayNote: (e) => {
13115
- const trackIdx = Number(e.trackId);
13130
+ const namedIdx = TRACK_ID_BY_INDEX2.indexOf(
13131
+ e.trackId
13132
+ );
13133
+ const trackIdx = namedIdx >= 0 ? namedIdx : Number(e.trackId);
13116
13134
  if (lyricTracks.has(trackIdx)) return;
13117
13135
  options.onPlayNote?.(e);
13118
13136
  synth?.playNote(e);
@@ -13807,6 +13825,23 @@ var TRACK_ROLES = [
13807
13825
  "t13",
13808
13826
  "t14"
13809
13827
  ];
13828
+ var resolveTrackIdxAndRole = (trackId) => {
13829
+ const roleIdx = TRACK_ROLES.indexOf(trackId);
13830
+ if (roleIdx >= 0) {
13831
+ return { trackIdx: roleIdx, role: trackId };
13832
+ }
13833
+ if (trackId.startsWith("t")) {
13834
+ const parsed2 = Number(trackId.slice(1));
13835
+ const validIdx2 = Number.isNaN(parsed2) ? 0 : parsed2;
13836
+ return {
13837
+ trackIdx: validIdx2,
13838
+ role: TRACK_ROLES[validIdx2] ?? `t${validIdx2}`
13839
+ };
13840
+ }
13841
+ const parsed = Number(trackId);
13842
+ const validIdx = Number.isNaN(parsed) ? 0 : parsed;
13843
+ return { trackIdx: validIdx, role: TRACK_ROLES[validIdx] ?? `t${validIdx}` };
13844
+ };
13810
13845
  var resolveDefaultVoiceWorkerUrl = () => {
13811
13846
  try {
13812
13847
  return new URL("./voice-worker.js", import.meta.url).href;
@@ -14370,19 +14405,19 @@ var createDtmStudio = async (options = {}) => {
14370
14405
  loadPlayerTrackInstruments()
14371
14406
  ]);
14372
14407
  const playPlayerNote = (e) => {
14373
- const idx = Number(e.trackId);
14374
- const overrideKey = playerTrackInstKeys.get(idx);
14408
+ const { trackIdx, role } = resolveTrackIdxAndRole(e.trackId);
14409
+ const overrideKey = playerTrackInstKeys.get(trackIdx);
14375
14410
  let sfInst;
14376
14411
  if (overrideKey) {
14377
14412
  sfInst = soundFonts.get(overrideKey);
14378
14413
  } else {
14379
- const role = getRoleForTrackIndex(
14380
- idx,
14414
+ const roleFromIdx = getRoleForTrackIndex(
14415
+ trackIdx,
14381
14416
  isAdvancedMode ? "advanced" : "simple"
14382
14417
  );
14383
14418
  sfInst = resolveSoundFont(
14384
14419
  playerPreset,
14385
- role,
14420
+ roleFromIdx,
14386
14421
  isAdvancedMode ? "advanced" : "simple"
14387
14422
  );
14388
14423
  }
@@ -14406,6 +14441,78 @@ var createDtmStudio = async (options = {}) => {
14406
14441
  onResumeAudio: resumeAudio
14407
14442
  });
14408
14443
  };
14444
+ const playSingingMMLInstance = (mml, opts = {}) => {
14445
+ const { placements } = parseMML(mml);
14446
+ const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
14447
+ (a, b) => a - b
14448
+ );
14449
+ const isAdvancedMode = trackIndices.some((idx) => idx >= 4);
14450
+ const playerPreset = defaultPreset;
14451
+ void loadPreset(
14452
+ playerPreset,
14453
+ trackIndices.map(String),
14454
+ isAdvancedMode ? "advanced" : "simple"
14455
+ );
14456
+ const playPlayerNote = (e) => {
14457
+ const { role } = resolveTrackIdxAndRole(e.trackId);
14458
+ let sfInst = resolveSoundFont(
14459
+ playerPreset,
14460
+ role,
14461
+ isAdvancedMode ? "advanced" : "simple"
14462
+ );
14463
+ if (!sfInst) {
14464
+ void loadPreset(
14465
+ playerPreset,
14466
+ [role],
14467
+ isAdvancedMode ? "advanced" : "simple"
14468
+ );
14469
+ sfInst = resolveSoundFont(
14470
+ playerPreset,
14471
+ role,
14472
+ isAdvancedMode ? "advanced" : "simple"
14473
+ );
14474
+ }
14475
+ if (!sfInst) return;
14476
+ sfInst.play({
14477
+ ctx: audioCtx,
14478
+ destination: masterGain,
14479
+ pitch: e.pitch,
14480
+ volume: e.volume,
14481
+ when: e.when,
14482
+ duration: e.duration
14483
+ });
14484
+ };
14485
+ return playSingingMML(mml, {
14486
+ ...opts,
14487
+ audioContext: audioCtx,
14488
+ destination: masterGain,
14489
+ synth: false,
14490
+ singingVoices,
14491
+ onPlayNote: playPlayerNote,
14492
+ onPlayDrum: playDrum,
14493
+ onResumeAudio: resumeAudio
14494
+ });
14495
+ };
14496
+ const playNoteEvent = (e) => {
14497
+ const { role } = resolveTrackIdxAndRole(e.trackId);
14498
+ let sfInst = resolveSoundFont(defaultPreset, role, "simple");
14499
+ if (!sfInst) {
14500
+ void loadPreset(defaultPreset, [role], "simple");
14501
+ sfInst = resolveSoundFont(defaultPreset, role, "simple");
14502
+ }
14503
+ if (!sfInst) return;
14504
+ sfInst.play({
14505
+ ctx: audioCtx,
14506
+ destination: masterGain,
14507
+ pitch: e.pitch,
14508
+ volume: e.volume,
14509
+ when: e.when,
14510
+ duration: e.duration
14511
+ });
14512
+ };
14513
+ const playDrumEvent = (e) => {
14514
+ playDrum(e);
14515
+ };
14409
14516
  const playNote3 = async (options2) => {
14410
14517
  await listReady;
14411
14518
  const key = options2.instrument ? resolveNameToKey(options2.instrument) : void 0;
@@ -14511,6 +14618,9 @@ var createDtmStudio = async (options = {}) => {
14511
14618
  mountPlayer,
14512
14619
  mountChordPlayer: mountChordPlayerInstance,
14513
14620
  play,
14621
+ playSingingMML: playSingingMMLInstance,
14622
+ playNoteEvent,
14623
+ playDrumEvent,
14514
14624
  playNote: playNote3,
14515
14625
  playChords: playChords3,
14516
14626
  loadPreset,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "onjmin",
3
3
  "license": "MIT",
4
4
  "name": "@onjmin/dtm",
5
- "version": "0.1.68",
5
+ "version": "0.1.70",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {