@onjmin/dtm 0.1.96 → 0.1.99

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
@@ -942,6 +942,12 @@ type DawOptions = {
942
942
  onPlayDrum?: (e: PlayDrumEvent) => void;
943
943
  /** 初回ユーザー操作時に呼ばれる(AudioContextのresume等に使う) */
944
944
  onResumeAudio?: () => void | Promise<void>;
945
+ /**
946
+ * 「MIDI / MML 出力」パネルの「WAV書き出し」ボタン押下時に呼ばれる。
947
+ * 未指定ならボタン自体を表示しない(ライブラリは音を出さないため、書き出し処理は
948
+ * AudioContextを持つ利用側 = createDtmStudio 等が担う)。
949
+ */
950
+ onExportWav?: () => void | Promise<void>;
945
951
  /** 再生の基準クロック秒。既定 performance.now()/1000。利用側は audioCtx.currentTime を返す */
946
952
  getAudioTime?: () => number;
947
953
  /**
@@ -2295,6 +2301,15 @@ type DtmStudio = {
2295
2301
  * MediaRecorder のストリーム合成(canvas + audio)に使えます。
2296
2302
  */
2297
2303
  getAudioStreamTrack: () => MediaStreamTrack;
2304
+ /**
2305
+ * WAV書き出し用の録音を開始する(マスタバス最終段=リバーブ/ディレイ/コンプ/
2306
+ * リミッター/フェード適用後のPCMをそのままタップする)。
2307
+ * 呼び出し側で再生(daw.play() 等)を開始し、曲が終わったら stopWavRecording を呼ぶ想定。
2308
+ * 実際の再生と同じ経路を録るため等速(曲の長さ分だけ実時間がかかる)。
2309
+ */
2310
+ startWavRecording: () => void;
2311
+ /** startWavRecording で開始した録音を止め、16bit PCM WAVのBlobを返す。 */
2312
+ stopWavRecording: () => Promise<Blob>;
2298
2313
  /** 歌声合成ヘルパ(klatt + koe音源)。 */
2299
2314
  singingVoices: SingingVoices;
2300
2315
  /** 編集UI(mountDAW)を音・歌声込みでマウントする。 */
@@ -2425,4 +2440,17 @@ declare const createSynth: (ctx: AudioContext, destination?: AudioNode, tone?: S
2425
2440
 
2426
2441
  declare const VOICE_IMAGES: Record<string, string>;
2427
2442
 
2428
- export { type AddNoteOptions, type AnyDrumPattern, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ChordPlayerInstance, type ConsumedSyllable, type CoreEventHandlers, type CustomVocalDef, DAW_CSS, DEFAULT_BPM, DEFAULT_GATE, DEFAULT_PAN, DEFAULT_PLAYBACK_VELOCITY, DEFAULT_STEPS_PER_BAR, DEFAULT_VELOCITY, DEFAULT_VOCAL_VOLUME, DRUM_FONT, DRUM_KEYS, DRUM_PATTERNS, type DawInstance, type DawMode, type DawOptions, type DawViewState, type DrumPattern, type DrumPatternDef, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, type FadeScheduleParams, GM_INSTRUMENT_NAMES, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricSyncData, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, MidiSearchClient, type MidiSearchConfig, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountChordPlayerOptions, type MountEditorOptions, type MountPlayerOptions, type Note, type NoteData, type NoteRemove, type OctaveUnisonMode, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PicotuneSearchParams, type PicotuneSong, type PitchToken, type PlayChordsOptions, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlayNoteOptions, type PlayPlacementsOptions, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VIBRATO_MIN_SEC, VOICE_IMAGES, VOICE_IMAGE_KEY, type VoiceExpression, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildDrumPatternJson, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decodeMml, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, encodeMml, exportMIDI, extractDrumPatternFromNotes, extractMidiDrumPattern, extractMidiPlacements, extractMidiPlacementsByTrack, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getDrumPatternKeys, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, isPlausibleMidiTranscription, isValidHttpUrl, koeUrl, mountChordPlayer, mountDAW, mountMmlPlayer, normalizeDrumPatterns, normalizeLyrics, onClick, panToStereo, parseCustomVocals, parseLyrics, parseMML, parseMmlMeta, playChords, playMML, playNote, playPlacements, playSingingMML, resolveDrumPattern, resolveLoopPoint, setBackgroundActive, setDrawOffset, shiftNotes, showLoadingOverlay, stripCustomVocals, stripLyrics, stripMmlMeta, transposeNotes, vocalVolumeToGain };
2443
+ /**
2444
+ * PCM(Float32、チャンネル別)から 16bit PCM WAV の Blob を作る、依存なしの小さなエンコーダ。
2445
+ * 追加ライブラリなしで完結させたいので RIFF/WAVE ヘッダを手書きしている。
2446
+ */
2447
+ /**
2448
+ * チャンネルごとの Float32Array(同じ長さ)から 16bit PCM WAV の Blob を生成する。
2449
+ * @param channels 各チャンネルのサンプル列(例: [left, right])
2450
+ * @param sampleRate サンプリングレート(Hz)
2451
+ */
2452
+ declare const encodeWavPCM16: (channels: Float32Array[], sampleRate: number) => Blob;
2453
+ /** Float32Array のチャンクを1本へ連結する。 */
2454
+ declare const concatFloat32: (chunks: Float32Array[]) => Float32Array;
2455
+
2456
+ export { type AddNoteOptions, type AnyDrumPattern, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ChordPlayerInstance, type ConsumedSyllable, type CoreEventHandlers, type CustomVocalDef, DAW_CSS, DEFAULT_BPM, DEFAULT_GATE, DEFAULT_PAN, DEFAULT_PLAYBACK_VELOCITY, DEFAULT_STEPS_PER_BAR, DEFAULT_VELOCITY, DEFAULT_VOCAL_VOLUME, DRUM_FONT, DRUM_KEYS, DRUM_PATTERNS, type DawInstance, type DawMode, type DawOptions, type DawViewState, type DrumPattern, type DrumPatternDef, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, type FadeScheduleParams, GM_INSTRUMENT_NAMES, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricSyncData, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, MidiSearchClient, type MidiSearchConfig, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountChordPlayerOptions, type MountEditorOptions, type MountPlayerOptions, type Note, type NoteData, type NoteRemove, type OctaveUnisonMode, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PicotuneSearchParams, type PicotuneSong, type PitchToken, type PlayChordsOptions, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlayNoteOptions, type PlayPlacementsOptions, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VIBRATO_MIN_SEC, VOICE_IMAGES, VOICE_IMAGE_KEY, type VoiceExpression, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildDrumPatternJson, buildNameToKeyMapping, collectPitchTokens, concatFloat32, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decodeMml, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, encodeMml, encodeWavPCM16, exportMIDI, extractDrumPatternFromNotes, extractMidiDrumPattern, extractMidiPlacements, extractMidiPlacementsByTrack, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getDrumPatternKeys, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, isPlausibleMidiTranscription, isValidHttpUrl, koeUrl, mountChordPlayer, mountDAW, mountMmlPlayer, normalizeDrumPatterns, normalizeLyrics, onClick, panToStereo, parseCustomVocals, parseLyrics, parseMML, parseMmlMeta, playChords, playMML, playNote, playPlacements, playSingingMML, resolveDrumPattern, resolveLoopPoint, setBackgroundActive, setDrawOffset, shiftNotes, showLoadingOverlay, stripCustomVocals, stripLyrics, stripMmlMeta, transposeNotes, vocalVolumeToGain };
package/dist/index.d.ts CHANGED
@@ -942,6 +942,12 @@ type DawOptions = {
942
942
  onPlayDrum?: (e: PlayDrumEvent) => void;
943
943
  /** 初回ユーザー操作時に呼ばれる(AudioContextのresume等に使う) */
944
944
  onResumeAudio?: () => void | Promise<void>;
945
+ /**
946
+ * 「MIDI / MML 出力」パネルの「WAV書き出し」ボタン押下時に呼ばれる。
947
+ * 未指定ならボタン自体を表示しない(ライブラリは音を出さないため、書き出し処理は
948
+ * AudioContextを持つ利用側 = createDtmStudio 等が担う)。
949
+ */
950
+ onExportWav?: () => void | Promise<void>;
945
951
  /** 再生の基準クロック秒。既定 performance.now()/1000。利用側は audioCtx.currentTime を返す */
946
952
  getAudioTime?: () => number;
947
953
  /**
@@ -2295,6 +2301,15 @@ type DtmStudio = {
2295
2301
  * MediaRecorder のストリーム合成(canvas + audio)に使えます。
2296
2302
  */
2297
2303
  getAudioStreamTrack: () => MediaStreamTrack;
2304
+ /**
2305
+ * WAV書き出し用の録音を開始する(マスタバス最終段=リバーブ/ディレイ/コンプ/
2306
+ * リミッター/フェード適用後のPCMをそのままタップする)。
2307
+ * 呼び出し側で再生(daw.play() 等)を開始し、曲が終わったら stopWavRecording を呼ぶ想定。
2308
+ * 実際の再生と同じ経路を録るため等速(曲の長さ分だけ実時間がかかる)。
2309
+ */
2310
+ startWavRecording: () => void;
2311
+ /** startWavRecording で開始した録音を止め、16bit PCM WAVのBlobを返す。 */
2312
+ stopWavRecording: () => Promise<Blob>;
2298
2313
  /** 歌声合成ヘルパ(klatt + koe音源)。 */
2299
2314
  singingVoices: SingingVoices;
2300
2315
  /** 編集UI(mountDAW)を音・歌声込みでマウントする。 */
@@ -2425,4 +2440,17 @@ declare const createSynth: (ctx: AudioContext, destination?: AudioNode, tone?: S
2425
2440
 
2426
2441
  declare const VOICE_IMAGES: Record<string, string>;
2427
2442
 
2428
- export { type AddNoteOptions, type AnyDrumPattern, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ChordPlayerInstance, type ConsumedSyllable, type CoreEventHandlers, type CustomVocalDef, DAW_CSS, DEFAULT_BPM, DEFAULT_GATE, DEFAULT_PAN, DEFAULT_PLAYBACK_VELOCITY, DEFAULT_STEPS_PER_BAR, DEFAULT_VELOCITY, DEFAULT_VOCAL_VOLUME, DRUM_FONT, DRUM_KEYS, DRUM_PATTERNS, type DawInstance, type DawMode, type DawOptions, type DawViewState, type DrumPattern, type DrumPatternDef, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, type FadeScheduleParams, GM_INSTRUMENT_NAMES, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricSyncData, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, MidiSearchClient, type MidiSearchConfig, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountChordPlayerOptions, type MountEditorOptions, type MountPlayerOptions, type Note, type NoteData, type NoteRemove, type OctaveUnisonMode, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PicotuneSearchParams, type PicotuneSong, type PitchToken, type PlayChordsOptions, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlayNoteOptions, type PlayPlacementsOptions, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VIBRATO_MIN_SEC, VOICE_IMAGES, VOICE_IMAGE_KEY, type VoiceExpression, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildDrumPatternJson, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decodeMml, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, encodeMml, exportMIDI, extractDrumPatternFromNotes, extractMidiDrumPattern, extractMidiPlacements, extractMidiPlacementsByTrack, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getDrumPatternKeys, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, isPlausibleMidiTranscription, isValidHttpUrl, koeUrl, mountChordPlayer, mountDAW, mountMmlPlayer, normalizeDrumPatterns, normalizeLyrics, onClick, panToStereo, parseCustomVocals, parseLyrics, parseMML, parseMmlMeta, playChords, playMML, playNote, playPlacements, playSingingMML, resolveDrumPattern, resolveLoopPoint, setBackgroundActive, setDrawOffset, shiftNotes, showLoadingOverlay, stripCustomVocals, stripLyrics, stripMmlMeta, transposeNotes, vocalVolumeToGain };
2443
+ /**
2444
+ * PCM(Float32、チャンネル別)から 16bit PCM WAV の Blob を作る、依存なしの小さなエンコーダ。
2445
+ * 追加ライブラリなしで完結させたいので RIFF/WAVE ヘッダを手書きしている。
2446
+ */
2447
+ /**
2448
+ * チャンネルごとの Float32Array(同じ長さ)から 16bit PCM WAV の Blob を生成する。
2449
+ * @param channels 各チャンネルのサンプル列(例: [left, right])
2450
+ * @param sampleRate サンプリングレート(Hz)
2451
+ */
2452
+ declare const encodeWavPCM16: (channels: Float32Array[], sampleRate: number) => Blob;
2453
+ /** Float32Array のチャンクを1本へ連結する。 */
2454
+ declare const concatFloat32: (chunks: Float32Array[]) => Float32Array;
2455
+
2456
+ export { type AddNoteOptions, type AnyDrumPattern, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ChordPlayerInstance, type ConsumedSyllable, type CoreEventHandlers, type CustomVocalDef, DAW_CSS, DEFAULT_BPM, DEFAULT_GATE, DEFAULT_PAN, DEFAULT_PLAYBACK_VELOCITY, DEFAULT_STEPS_PER_BAR, DEFAULT_VELOCITY, DEFAULT_VOCAL_VOLUME, DRUM_FONT, DRUM_KEYS, DRUM_PATTERNS, type DawInstance, type DawMode, type DawOptions, type DawViewState, type DrumPattern, type DrumPatternDef, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, type FadeScheduleParams, GM_INSTRUMENT_NAMES, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricSyncData, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, MidiSearchClient, type MidiSearchConfig, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountChordPlayerOptions, type MountEditorOptions, type MountPlayerOptions, type Note, type NoteData, type NoteRemove, type OctaveUnisonMode, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PicotuneSearchParams, type PicotuneSong, type PitchToken, type PlayChordsOptions, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlayNoteOptions, type PlayPlacementsOptions, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VIBRATO_MIN_SEC, VOICE_IMAGES, VOICE_IMAGE_KEY, type VoiceExpression, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildDrumPatternJson, buildNameToKeyMapping, collectPitchTokens, concatFloat32, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decodeMml, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, encodeMml, encodeWavPCM16, exportMIDI, extractDrumPatternFromNotes, extractMidiDrumPattern, extractMidiPlacements, extractMidiPlacementsByTrack, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getDrumPatternKeys, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, isPlausibleMidiTranscription, isValidHttpUrl, koeUrl, mountChordPlayer, mountDAW, mountMmlPlayer, normalizeDrumPatterns, normalizeLyrics, onClick, panToStereo, parseCustomVocals, parseLyrics, parseMML, parseMmlMeta, playChords, playMML, playNote, playPlacements, playSingingMML, resolveDrumPattern, resolveLoopPoint, setBackgroundActive, setDrawOffset, shiftNotes, showLoadingOverlay, stripCustomVocals, stripLyrics, stripMmlMeta, transposeNotes, vocalVolumeToGain };
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  buildDrumPatternJson: () => buildDrumPatternJson,
57
57
  buildNameToKeyMapping: () => buildNameToKeyMapping,
58
58
  collectPitchTokens: () => collectPitchTokens,
59
+ concatFloat32: () => concatFloat32,
59
60
  createAudioContext: () => createAudioContext,
60
61
  createDtmStudio: () => createDtmStudio,
61
62
  createKlattVoice: () => createKlattVoice,
@@ -75,6 +76,7 @@ __export(index_exports, {
75
76
  drawSelectedNotes: () => drawSelectedNotes,
76
77
  drawSelectionRect: () => drawSelectionRect,
77
78
  encodeMml: () => encodeMml,
79
+ encodeWavPCM16: () => encodeWavPCM16,
78
80
  exportMIDI: () => exportMIDI,
79
81
  extractDrumPatternFromNotes: () => extractDrumPatternFromNotes,
80
82
  extractMidiDrumPattern: () => extractMidiDrumPattern,
@@ -11752,6 +11754,7 @@ var buildUI = (target, options) => {
11752
11754
  <div class="dtm-row">
11753
11755
  <button class="dtm-btn dtm-btn--accent" data-dtm="export-midi">MIDI\u51FA\u529B</button>
11754
11756
  <button class="dtm-btn dtm-btn--success" data-dtm="generate-mml">MML\u751F\u6210</button>
11757
+ <button class="dtm-btn dtm-btn--primary dtm-hidden" data-dtm="export-wav">WAV\u66F8\u304D\u51FA\u3057</button>
11755
11758
  </div>
11756
11759
  <div class="dtm-row">
11757
11760
  <button class="dtm-btn dtm-btn--primary" data-dtm="drum-json-export" title="\u8AAD\u307F\u8FBC\u3093\u3060MIDI\u306E\u30C9\u30E9\u30E0\u5B9A\u7FA9\u3092JSON\u51FA\u529B">\u30C9\u30E9\u30E0JSON\u51FA\u529B</button>
@@ -11895,6 +11898,7 @@ var buildUI = (target, options) => {
11895
11898
  macroHarmonic: sel("macro-harmonic"),
11896
11899
  macroMono: sel("macro-mono"),
11897
11900
  exportMidiBtn: sel("export-midi"),
11901
+ exportWavBtn: sel("export-wav"),
11898
11902
  drumJsonExportBtn: sel("drum-json-export"),
11899
11903
  drumJsonOutput: sel("drum-json-output"),
11900
11904
  drumJsonStatus: sel("drum-json-status"),
@@ -13749,7 +13753,6 @@ var AUTO_MASTER_INFO_HTML = `
13749
13753
  <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Decay: <strong>\u73FE\u5728\u306EBPM\u306B\u5FDC\u3058\u3066\u81EA\u52D5\u8A08\u7B97</strong>\uFF08\u901F\u3044\u66F2\u307B\u3069\u77ED\u304F0.9\u79D2\u5BC4\u308A\u3001\u9045\u3044\u66F2\u307B\u3069\u9577\u304F3.0\u79D2\u5BC4\u308A\uFF09</li>
13750
13754
  <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Pre Delay: 20ms\uFF08\u539F\u97F3\u306E\u8F2A\u90ED\u3092\u4FDD\u3063\u305F\u307E\u307E\u5965\u884C\u304D\u3092\u8DB3\u3059\u5B9A\u756A\u5024\uFF09</li>
13751
13755
  <li>\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8: \u672A\u8A2D\u5B9A\uFF080\u79D2\uFF09\u306E\u3068\u304D\u3060\u30511.5\u79D2\u3092\u88DC\u3046\uFF08\u66F2\u306E\u7D42\u308F\u308A\u3092\u3076\u3064\u5207\u308A\u306B\u3057\u306A\u3044\uFF09\u3002\u65E2\u306B0\u79D2\u4EE5\u5916\u306E\u5024\u304C\u8A2D\u5B9A\u6E08\u307F\u306A\u3089\u4E0A\u66F8\u304D\u3057\u306A\u3044</li>
13752
- <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON</li>
13753
13756
  </ul>
13754
13757
  <p style="margin-top:4px;"><small>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6Mix\u3092\u5BC6\u5EA6\u3067\u6C7A\u3081\u308B\u306E\u306F\u3001\u300C\u97F3\u6570\u306E\u591A\u3044\u539A\u3044\u30A2\u30EC\u30F3\u30B8\u306B\u6B8B\u97FF\u3092\u91CD\u306D\u3059\u304E\u308B\u3068\u6FC1\u308B\u3001\u97F3\u6570\u306E\u5C11\u306A\u3044\u9699\u9593\u306E\u591A\u3044\u7DE8\u6210\u306B\u306F\u6B8B\u97FF\u3067\u7A7A\u9593\u3092\u57CB\u3081\u308B\u3068\u5FC3\u5730\u3088\u3044\u300D\u3068\u3044\u3046\u30DF\u30AD\u30B7\u30F3\u30B0\u306E\u5B9A\u77F3\u306B\u57FA\u3065\u304D\u307E\u3059\u3002</small></p>
13755
13758
  <h4>\u697D\u5668\u30FB\u97F3\u91CF\u30FB\u5F79\u5272\u3054\u3068\u306E\u30DF\u30C3\u30AF\u30B9\u306E\u81EA\u52D5\u5272\u308A\u5F53\u3066</h4>
@@ -13764,10 +13767,10 @@ var AUTO_MASTER_INFO_HTML = `
13764
13767
  <h4>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u306E\u81EA\u52D5\u5224\u5B9A</h4>
13765
13768
  <p>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u304C\u8907\u6570\u3042\u308B\u5834\u5408\uFF08\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u30FB\u639B\u3051\u58F0\u7B49\uFF09\u3001\u767A\u97F3\u6642\u9593\u304C\u6700\u3082\u9577\u3044\u30C8\u30E9\u30C3\u30AF\u3092\u300C\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u300D\u3068\u307F\u306A\u3057\u3001\u4ED6\u3068\u306F\u9055\u3046\u6271\u3044\u306B\u3057\u307E\u3059\u3002</p>
13766
13769
  <ul>
13767
- <li>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB: \u97F3\u5727\u5F37\u531630%\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A15%\uFF08\u524D\u306B\u51FA\u3059\uFF09\u30FB\u5B9A\u4F4D\u306F\u4E2D\u592E\u56FA\u5B9A</li>
13768
- <li>\u305D\u308C\u4EE5\u5916\u306E\u30DC\u30FC\u30AB\u30EB: \u97F3\u5727\u5F37\u531625%\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A30%\uFF08\u5965\u3078\u99B4\u67D3\u307E\u305B\u308B\uFF09\u30FB\u5B9A\u4F4D\u306F\u5DE6\u53F3\u3078\u4EA4\u4E92\u306B\u632F\u3063\u3066\u30C0\u30D6\u30EA\u30F3\u30B0\u611F\u3092\u51FA\u3059</li>
13770
+ <li>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u30FB\u58F0\u91CF\u3092\u65E2\u5B9A\u306E1.1\u500D\uFF08\u524D\u306B\u51FA\u3059\uFF09\u30FB\u97F3\u5727\u5F37\u531630%\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45100%\uFF08\u72ED\u3081\u30FB\u4E2D\u592E\u5BC4\u308A\u3067\u30AF\u30EA\u30A2\u306B\uFF09\u30FBEQ\u4F4E\u57DF-3dB\uFF08\u30E9\u30F3\u30D6\u30EB/\u3053\u3082\u308A\u9664\u53BB\uFF09\u30FBEQ\u9AD8\u57DF+2dB\uFF08\u629C\u3051\u30FB\u30D7\u30EC\u30BC\u30F3\u30B9\uFF09\u30FB\u30C7\u30A3\u30EC\u30A4\u9001\u308A15%\uFF08\u8A9E\u5C3E\u306B\u8EFD\u3044\u30B9\u30E9\u30C3\u30D7\uFF09\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%\u30FB\u5B9A\u4F4D\u306F\u4E2D\u592E\u56FA\u5B9A</li>
13771
+ <li>\u305D\u308C\u4EE5\u5916\u306E\u30DC\u30FC\u30AB\u30EB\uFF08\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u7B49\uFF09: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8OFF\u30FB\u58F0\u91CF\u3092\u65E2\u5B9A\u306E0.85\u500D\u3092\u57FA\u6E96\u306B\u3001\u526F\u30DC\u30FC\u30AB\u30EB\u306E\u672C\u6570\u304C\u591A\u3044\u307B\u3069\u3055\u3089\u306B\u7D5E\u308B\uFF08\u91CD\u306A\u3063\u3066\u97F3\u5727\u304C\u7A4D\u307F\u4E0A\u304C\u308B\u5206\u3092\u7B49\u30D1\u30EF\u30FC\u5247\u3067\u76F8\u6BBA\uFF09\u30FB\u97F3\u5727\u5F37\u531625%\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45120%\uFF08\u5E83\u3052\u3066\u300C\u58C1\u300D\u306E\u3088\u3046\u306A\u539A\u307F\u3092\u51FA\u3059\uFF09\u30FBEQ\u4F4E\u57DF-3dB\u30FBEQ\u9AD8\u57DF\u306F\u7121\u5909\u5316\uFF08\u660E\u308B\u304F\u3057\u3059\u304E\u308B\u3068\u524D\u306B\u51FA\u3066\u3057\u307E\u3046\u305F\u3081\uFF09\u30FB\u30C7\u30A3\u30EC\u30A4\u9001\u308A0%\uFF08\u639B\u3051\u308B\u3068\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3066\u56E3\u5B50\u306B\u306A\u308B\u305F\u3081\uFF09\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A45%\uFF08\u5965\u3078\u99B4\u67D3\u307E\u305B\u308B\uFF09\u30FB\u5B9A\u4F4D\u306F\u5DE6\u53F3\u3078\u4EA4\u4E92\u306B\u632F\u3063\u3066\u30C0\u30D6\u30EA\u30F3\u30B0\u611F\u3092\u51FA\u3059</li>
13769
13772
  </ul>
13770
- <p style="margin-top:4px;"><small>\u300C\u524D\u306B\u51FA\u3057\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u5C11\u306A\u3081\u3001\u5965\u306B\u7F6E\u304D\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u591A\u3081\u300D\u3068\u3044\u3046\u5B9A\u77F3\u306B\u57FA\u3065\u3044\u3066\u3044\u307E\u3059\u3002\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
13773
+ <p style="margin-top:4px;"><small>\u300C\u524D\u306B\u51FA\u3057\u305F\u3044\u97F3\u306F\u58F0\u91CF\u5927\u304D\u3081\u30FB\u72ED\u3081\u30FB\u30EA\u30D0\u30FC\u30D6\u5C11\u306A\u3081\u3001\u5965\u306B\u7F6E\u304D\u305F\u3044\u97F3\u306F\u58F0\u91CF\u63A7\u3048\u3081\u30FB\u5E83\u3081\u30FB\u30EA\u30D0\u30FC\u30D6\u591A\u3081\u300D\u3068\u3044\u3046\u5B9A\u77F3\u306B\u57FA\u3065\u3044\u3066\u3044\u307E\u3059\u3002EQ\u306E\u4F4E\u57DF\u30AB\u30C3\u30C8\u306F\u30DC\u30FC\u30AB\u30EB\u3067\u307B\u307C\u666E\u904D\u7684\u306A\u5B9A\u77F3\uFF08\u5B9F\u8CEA\u7684\u306A\u30CF\u30A4\u30D1\u30B9\u30D5\u30A3\u30EB\u30BF\u306E\u4EE3\u7528\uFF09\u3067\u3059\u3002\u30D3\u30D6\u30E9\u30FC\u30C8\u3092\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u3078\u639B\u3051\u306A\u3044\u306E\u306F\u3001\u8907\u6570\u306E\u58F0\u304C\u540C\u6642\u306B\u63FA\u308C\u308B\u3068\u58F0\u3054\u3068\u306E\u4F4D\u76F8\u30FB\u901F\u3055\u306E\u30BA\u30EC\u3067\u548C\u97F3\u304C\u300C\u3046\u306D\u308B\u300D\u300C\u6FC1\u308B\u300D\u305F\u3081\u3067\u3001\u5408\u5531\u3084\u30B9\u30C8\u30EA\u30F3\u30B0\u30B9\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u594F\u6CD5\u3068\u540C\u3058\u304F\u3001\u30CF\u30E2\u30EA/\u30B3\u30FC\u30E9\u30B9\u306F\u30B9\u30C8\u30EC\u30FC\u30C8\u30C8\u30FC\u30F3\u306B\u3057\u3066\u97F3\u7A0B\u306E\u8EF8\u3092\u5B89\u5B9A\u3055\u305B\u308B\u65B9\u304C\u99B4\u67D3\u307F\u307E\u3059\u3002\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A/\u4E0B\u3092\u91CD\u306D\u3066\u539A\u307F\u3092\u51FA\u3059\u52A0\u5DE5\uFF09\u306F\u3053\u3053\u3067\u306F\u81EA\u52D5\u9069\u7528\u3057\u307E\u305B\u3093 \u2014 \u58F0\u8CEA\u3092\u5927\u304D\u304F\u5909\u3048\u308B\u6F14\u51FA\u76EE\u7684\u306E\u52A0\u5DE5\u3067\u3042\u308A\u3001\u66F2\u306B\u3088\u3063\u3066\u5408\u3046/\u5408\u308F\u306A\u3044\u304C\u5206\u304B\u308C\u308B\u5275\u4F5C\u4E0A\u306E\u9078\u629E\uFF08\u30D0\u30E9\u30FC\u30C9\u3067\u306F\u4E0D\u81EA\u7136\u306B\u306A\u308A\u3084\u3059\u3044\u7B49\uFF09\u3067\u3001\u30DF\u30C3\u30AF\u30B9\u306E\u662F\u6B63\u3068\u306F\u6027\u8CEA\u304C\u9055\u3046\u305F\u3081\u3067\u3059\u3002\u4F7F\u3044\u305F\u3044\u5834\u5408\u306F\u5404\u30DC\u30FC\u30AB\u30EB\u30C8\u30E9\u30C3\u30AF\u306E\u8A2D\u5B9A\u6B04\u304B\u3089\u500B\u5225\u306B\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
13771
13774
  </div>
13772
13775
  `;
13773
13776
  var MIDI_INFO_HTML = `
@@ -16997,6 +17000,7 @@ var mountDAW = (target, options = {}) => {
16997
17000
  }
16998
17001
  let mainVocalId = null;
16999
17002
  let mainVocalScore = -1;
17003
+ let nonMainVocalCount = 0;
17000
17004
  for (const t of trackStates) {
17001
17005
  if (!t.lyricModel) continue;
17002
17006
  const score = t.core.getNotes().reduce((sum, n) => sum + n.durationSteps, 0);
@@ -17005,6 +17009,9 @@ var mountDAW = (target, options = {}) => {
17005
17009
  mainVocalId = t.config.id;
17006
17010
  }
17007
17011
  }
17012
+ for (const t of trackStates) {
17013
+ if (t.lyricModel && t.config.id !== mainVocalId) nonMainVocalCount++;
17014
+ }
17008
17015
  const autoPreset = INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano;
17009
17016
  const roleByTrackId = /* @__PURE__ */ new Map();
17010
17017
  for (const t of trackStates) {
@@ -17046,28 +17053,43 @@ var mountDAW = (target, options = {}) => {
17046
17053
  t.trackPan = pan;
17047
17054
  options.onTrackPanChange?.(t.config.id, pan);
17048
17055
  const comp = t.lyricModel ? isMainVocal ? 30 : 25 : roleMix?.compression ?? 35;
17049
- const width = t.lyricModel ? 115 : roleMix?.width ?? 115;
17056
+ const width = t.lyricModel ? isMainVocal ? 100 : 120 : roleMix?.width ?? 115;
17050
17057
  t.trackCompression = comp;
17051
17058
  t.trackWidth = width;
17052
17059
  options.onTrackCompressionChange?.(t.config.id, comp);
17053
17060
  options.onTrackWidthChange?.(t.config.id, width);
17054
- if (!t.lyricModel) {
17055
- const reverbSend = roleMix?.reverbSend ?? 15;
17056
- t.trackReverbSend = reverbSend;
17057
- options.onTrackReverbSendChange?.(t.config.id, reverbSend);
17061
+ if (t.lyricModel) {
17062
+ t.trackEqLow = -3;
17063
+ t.trackEqHigh = isMainVocal ? 2 : 0;
17064
+ options.onTrackEqLowChange?.(t.config.id, t.trackEqLow);
17065
+ options.onTrackEqHighChange?.(t.config.id, t.trackEqHigh);
17066
+ } else {
17058
17067
  const eqLow = roleMix?.eqLow ?? 0;
17059
17068
  const eqHigh = roleMix?.eqHigh ?? 0;
17060
17069
  t.trackEqLow = eqLow;
17061
17070
  t.trackEqHigh = eqHigh;
17062
17071
  options.onTrackEqLowChange?.(t.config.id, eqLow);
17063
17072
  options.onTrackEqHighChange?.(t.config.id, eqHigh);
17073
+ }
17074
+ if (!t.lyricModel) {
17075
+ const reverbSend = roleMix?.reverbSend ?? 15;
17076
+ t.trackReverbSend = reverbSend;
17077
+ options.onTrackReverbSendChange?.(t.config.id, reverbSend);
17064
17078
  const delaySend = roleMix?.delaySend ?? 0;
17065
17079
  t.trackDelaySend = delaySend;
17066
17080
  options.onTrackDelaySendChange?.(t.config.id, delaySend);
17067
17081
  }
17068
17082
  if (t.lyricModel) {
17069
- t.vocalVibrato = true;
17083
+ t.vocalVibrato = isMainVocal;
17070
17084
  t.vocalReverb = isMainVocal ? 25 : 45;
17085
+ t.vocalDelay = isMainVocal ? 15 : 0;
17086
+ t.vocalVolume = isMainVocal ? Math.round(DEFAULT_VOCAL_VOLUME * 1.1) : clamp5(
17087
+ Math.round(
17088
+ DEFAULT_VOCAL_VOLUME * 0.85 / Math.sqrt(Math.max(1, nonMainVocalCount))
17089
+ ),
17090
+ 60,
17091
+ MAX_VOCAL_VOLUME
17092
+ );
17071
17093
  fireLyricsChange(t);
17072
17094
  }
17073
17095
  }
@@ -17135,6 +17157,12 @@ var mountDAW = (target, options = {}) => {
17135
17157
  a.click();
17136
17158
  URL.revokeObjectURL(url2);
17137
17159
  });
17160
+ if (options.onExportWav) {
17161
+ refs.exportWavBtn.classList.remove("dtm-hidden");
17162
+ refs.exportWavBtn.addEventListener("click", () => {
17163
+ void options.onExportWav?.();
17164
+ });
17165
+ }
17138
17166
  const copy = (text, btn) => {
17139
17167
  navigator.clipboard?.writeText(text);
17140
17168
  btn.classList.add("dtm-btn--success");
@@ -19011,6 +19039,64 @@ var SoundFont_list = new class {
19011
19039
  }
19012
19040
  }();
19013
19041
 
19042
+ // src/wav-export.ts
19043
+ var floatTo16BitPCM = (view, offset, input) => {
19044
+ for (let i2 = 0; i2 < input.length; i2++, offset += 2) {
19045
+ const s = Math.max(-1, Math.min(1, input[i2]));
19046
+ view.setInt16(offset, s < 0 ? s * 32768 : s * 32767, true);
19047
+ }
19048
+ };
19049
+ var writeString = (view, offset, str) => {
19050
+ for (let i2 = 0; i2 < str.length; i2++) {
19051
+ view.setUint8(offset + i2, str.charCodeAt(i2));
19052
+ }
19053
+ };
19054
+ var encodeWavPCM16 = (channels, sampleRate) => {
19055
+ const numChannels = channels.length;
19056
+ const numFrames = channels[0]?.length ?? 0;
19057
+ const bytesPerSample = 2;
19058
+ const blockAlign = numChannels * bytesPerSample;
19059
+ const dataSize = numFrames * blockAlign;
19060
+ const buffer = new ArrayBuffer(44 + dataSize);
19061
+ const view = new DataView(buffer);
19062
+ writeString(view, 0, "RIFF");
19063
+ view.setUint32(4, 36 + dataSize, true);
19064
+ writeString(view, 8, "WAVE");
19065
+ writeString(view, 12, "fmt ");
19066
+ view.setUint32(16, 16, true);
19067
+ view.setUint16(20, 1, true);
19068
+ view.setUint16(22, numChannels, true);
19069
+ view.setUint32(24, sampleRate, true);
19070
+ view.setUint32(28, sampleRate * blockAlign, true);
19071
+ view.setUint16(32, blockAlign, true);
19072
+ view.setUint16(34, bytesPerSample * 8, true);
19073
+ writeString(view, 36, "data");
19074
+ view.setUint32(40, dataSize, true);
19075
+ if (numChannels === 1) {
19076
+ floatTo16BitPCM(view, 44, channels[0]);
19077
+ } else {
19078
+ let offset = 44;
19079
+ for (let i2 = 0; i2 < numFrames; i2++) {
19080
+ for (let c = 0; c < numChannels; c++) {
19081
+ const s = Math.max(-1, Math.min(1, channels[c][i2]));
19082
+ view.setInt16(offset, s < 0 ? s * 32768 : s * 32767, true);
19083
+ offset += 2;
19084
+ }
19085
+ }
19086
+ }
19087
+ return new Blob([buffer], { type: "audio/wav" });
19088
+ };
19089
+ var concatFloat32 = (chunks) => {
19090
+ const total = chunks.reduce((sum, c) => sum + c.length, 0);
19091
+ const out = new Float32Array(total);
19092
+ let offset = 0;
19093
+ for (const c of chunks) {
19094
+ out.set(c, offset);
19095
+ offset += c.length;
19096
+ }
19097
+ return out;
19098
+ };
19099
+
19014
19100
  // src/studio.ts
19015
19101
  var import_meta = {};
19016
19102
  var SOUNDFONT_NAME2 = "FluidR3_GM_sf2_file";
@@ -19488,6 +19574,28 @@ var createDtmStudio = async (options = {}) => {
19488
19574
  },
19489
19575
  onPlayNote: playNote4,
19490
19576
  onPlayDrum: playDrum,
19577
+ onExportWav: async () => {
19578
+ if (!daw) return;
19579
+ daw.stop();
19580
+ await resumeAudio();
19581
+ startWavRecording();
19582
+ await daw.play();
19583
+ await new Promise((resolve) => {
19584
+ const iv = setInterval(() => {
19585
+ if (daw.getPlaybackState() !== "playing") {
19586
+ clearInterval(iv);
19587
+ resolve();
19588
+ }
19589
+ }, 200);
19590
+ });
19591
+ const blob2 = await stopWavRecording();
19592
+ const url2 = URL.createObjectURL(blob2);
19593
+ const a = document.createElement("a");
19594
+ a.href = url2;
19595
+ a.download = "dtm.wav";
19596
+ a.click();
19597
+ URL.revokeObjectURL(url2);
19598
+ },
19491
19599
  singingVoices,
19492
19600
  parseMidi,
19493
19601
  onInstrumentChange: handleInstrumentChange,
@@ -20075,11 +20183,48 @@ var createDtmStudio = async (options = {}) => {
20075
20183
  const getAudioStreamTrack = () => {
20076
20184
  return createMediaStreamDestination().stream.getAudioTracks()[0];
20077
20185
  };
20186
+ let wavTap = null;
20187
+ const startWavRecording = () => {
20188
+ if (wavTap) return;
20189
+ const bufferSize = 4096;
20190
+ const processor = audioCtx.createScriptProcessor(bufferSize, 2, 2);
20191
+ const chunksL = [];
20192
+ const chunksR = [];
20193
+ processor.onaudioprocess = (e) => {
20194
+ chunksL.push(new Float32Array(e.inputBuffer.getChannelData(0)));
20195
+ chunksR.push(
20196
+ new Float32Array(
20197
+ e.inputBuffer.getChannelData(
20198
+ e.inputBuffer.numberOfChannels > 1 ? 1 : 0
20199
+ )
20200
+ )
20201
+ );
20202
+ };
20203
+ const silentSink = audioCtx.createGain();
20204
+ silentSink.gain.value = 0;
20205
+ fadeGain.connect(processor);
20206
+ processor.connect(silentSink);
20207
+ silentSink.connect(audioCtx.destination);
20208
+ wavTap = { processor, silentSink, chunksL, chunksR };
20209
+ };
20210
+ const stopWavRecording = async () => {
20211
+ if (!wavTap) throw new Error("[dtm] startWavRecording \u304C\u547C\u3070\u308C\u3066\u3044\u307E\u305B\u3093");
20212
+ const { processor, silentSink, chunksL, chunksR } = wavTap;
20213
+ fadeGain.disconnect(processor);
20214
+ processor.disconnect(silentSink);
20215
+ silentSink.disconnect(audioCtx.destination);
20216
+ wavTap = null;
20217
+ const left = concatFloat32(chunksL);
20218
+ const right = concatFloat32(chunksR);
20219
+ return encodeWavPCM16([left, right], audioCtx.sampleRate);
20220
+ };
20078
20221
  return {
20079
20222
  audioContext: audioCtx,
20080
20223
  masterGain,
20081
20224
  createMediaStreamDestination,
20082
20225
  getAudioStreamTrack,
20226
+ startWavRecording,
20227
+ stopWavRecording,
20083
20228
  singingVoices,
20084
20229
  mountEditor,
20085
20230
  mountPlayer,
@@ -20142,6 +20287,7 @@ var createDtmStudio = async (options = {}) => {
20142
20287
  buildDrumPatternJson,
20143
20288
  buildNameToKeyMapping,
20144
20289
  collectPitchTokens,
20290
+ concatFloat32,
20145
20291
  createAudioContext,
20146
20292
  createDtmStudio,
20147
20293
  createKlattVoice,
@@ -20161,6 +20307,7 @@ var createDtmStudio = async (options = {}) => {
20161
20307
  drawSelectedNotes,
20162
20308
  drawSelectionRect,
20163
20309
  encodeMml,
20310
+ encodeWavPCM16,
20164
20311
  exportMIDI,
20165
20312
  extractDrumPatternFromNotes,
20166
20313
  extractMidiDrumPattern,
package/dist/index.mjs CHANGED
@@ -11621,6 +11621,7 @@ var buildUI = (target, options) => {
11621
11621
  <div class="dtm-row">
11622
11622
  <button class="dtm-btn dtm-btn--accent" data-dtm="export-midi">MIDI\u51FA\u529B</button>
11623
11623
  <button class="dtm-btn dtm-btn--success" data-dtm="generate-mml">MML\u751F\u6210</button>
11624
+ <button class="dtm-btn dtm-btn--primary dtm-hidden" data-dtm="export-wav">WAV\u66F8\u304D\u51FA\u3057</button>
11624
11625
  </div>
11625
11626
  <div class="dtm-row">
11626
11627
  <button class="dtm-btn dtm-btn--primary" data-dtm="drum-json-export" title="\u8AAD\u307F\u8FBC\u3093\u3060MIDI\u306E\u30C9\u30E9\u30E0\u5B9A\u7FA9\u3092JSON\u51FA\u529B">\u30C9\u30E9\u30E0JSON\u51FA\u529B</button>
@@ -11764,6 +11765,7 @@ var buildUI = (target, options) => {
11764
11765
  macroHarmonic: sel("macro-harmonic"),
11765
11766
  macroMono: sel("macro-mono"),
11766
11767
  exportMidiBtn: sel("export-midi"),
11768
+ exportWavBtn: sel("export-wav"),
11767
11769
  drumJsonExportBtn: sel("drum-json-export"),
11768
11770
  drumJsonOutput: sel("drum-json-output"),
11769
11771
  drumJsonStatus: sel("drum-json-status"),
@@ -13618,7 +13620,6 @@ var AUTO_MASTER_INFO_HTML = `
13618
13620
  <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Decay: <strong>\u73FE\u5728\u306EBPM\u306B\u5FDC\u3058\u3066\u81EA\u52D5\u8A08\u7B97</strong>\uFF08\u901F\u3044\u66F2\u307B\u3069\u77ED\u304F0.9\u79D2\u5BC4\u308A\u3001\u9045\u3044\u66F2\u307B\u3069\u9577\u304F3.0\u79D2\u5BC4\u308A\uFF09</li>
13619
13621
  <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Pre Delay: 20ms\uFF08\u539F\u97F3\u306E\u8F2A\u90ED\u3092\u4FDD\u3063\u305F\u307E\u307E\u5965\u884C\u304D\u3092\u8DB3\u3059\u5B9A\u756A\u5024\uFF09</li>
13620
13622
  <li>\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8: \u672A\u8A2D\u5B9A\uFF080\u79D2\uFF09\u306E\u3068\u304D\u3060\u30511.5\u79D2\u3092\u88DC\u3046\uFF08\u66F2\u306E\u7D42\u308F\u308A\u3092\u3076\u3064\u5207\u308A\u306B\u3057\u306A\u3044\uFF09\u3002\u65E2\u306B0\u79D2\u4EE5\u5916\u306E\u5024\u304C\u8A2D\u5B9A\u6E08\u307F\u306A\u3089\u4E0A\u66F8\u304D\u3057\u306A\u3044</li>
13621
- <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON</li>
13622
13623
  </ul>
13623
13624
  <p style="margin-top:4px;"><small>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6Mix\u3092\u5BC6\u5EA6\u3067\u6C7A\u3081\u308B\u306E\u306F\u3001\u300C\u97F3\u6570\u306E\u591A\u3044\u539A\u3044\u30A2\u30EC\u30F3\u30B8\u306B\u6B8B\u97FF\u3092\u91CD\u306D\u3059\u304E\u308B\u3068\u6FC1\u308B\u3001\u97F3\u6570\u306E\u5C11\u306A\u3044\u9699\u9593\u306E\u591A\u3044\u7DE8\u6210\u306B\u306F\u6B8B\u97FF\u3067\u7A7A\u9593\u3092\u57CB\u3081\u308B\u3068\u5FC3\u5730\u3088\u3044\u300D\u3068\u3044\u3046\u30DF\u30AD\u30B7\u30F3\u30B0\u306E\u5B9A\u77F3\u306B\u57FA\u3065\u304D\u307E\u3059\u3002</small></p>
13624
13625
  <h4>\u697D\u5668\u30FB\u97F3\u91CF\u30FB\u5F79\u5272\u3054\u3068\u306E\u30DF\u30C3\u30AF\u30B9\u306E\u81EA\u52D5\u5272\u308A\u5F53\u3066</h4>
@@ -13633,10 +13634,10 @@ var AUTO_MASTER_INFO_HTML = `
13633
13634
  <h4>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u306E\u81EA\u52D5\u5224\u5B9A</h4>
13634
13635
  <p>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u304C\u8907\u6570\u3042\u308B\u5834\u5408\uFF08\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u30FB\u639B\u3051\u58F0\u7B49\uFF09\u3001\u767A\u97F3\u6642\u9593\u304C\u6700\u3082\u9577\u3044\u30C8\u30E9\u30C3\u30AF\u3092\u300C\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u300D\u3068\u307F\u306A\u3057\u3001\u4ED6\u3068\u306F\u9055\u3046\u6271\u3044\u306B\u3057\u307E\u3059\u3002</p>
13635
13636
  <ul>
13636
- <li>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB: \u97F3\u5727\u5F37\u531630%\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A15%\uFF08\u524D\u306B\u51FA\u3059\uFF09\u30FB\u5B9A\u4F4D\u306F\u4E2D\u592E\u56FA\u5B9A</li>
13637
- <li>\u305D\u308C\u4EE5\u5916\u306E\u30DC\u30FC\u30AB\u30EB: \u97F3\u5727\u5F37\u531625%\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A30%\uFF08\u5965\u3078\u99B4\u67D3\u307E\u305B\u308B\uFF09\u30FB\u5B9A\u4F4D\u306F\u5DE6\u53F3\u3078\u4EA4\u4E92\u306B\u632F\u3063\u3066\u30C0\u30D6\u30EA\u30F3\u30B0\u611F\u3092\u51FA\u3059</li>
13637
+ <li>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u30FB\u58F0\u91CF\u3092\u65E2\u5B9A\u306E1.1\u500D\uFF08\u524D\u306B\u51FA\u3059\uFF09\u30FB\u97F3\u5727\u5F37\u531630%\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45100%\uFF08\u72ED\u3081\u30FB\u4E2D\u592E\u5BC4\u308A\u3067\u30AF\u30EA\u30A2\u306B\uFF09\u30FBEQ\u4F4E\u57DF-3dB\uFF08\u30E9\u30F3\u30D6\u30EB/\u3053\u3082\u308A\u9664\u53BB\uFF09\u30FBEQ\u9AD8\u57DF+2dB\uFF08\u629C\u3051\u30FB\u30D7\u30EC\u30BC\u30F3\u30B9\uFF09\u30FB\u30C7\u30A3\u30EC\u30A4\u9001\u308A15%\uFF08\u8A9E\u5C3E\u306B\u8EFD\u3044\u30B9\u30E9\u30C3\u30D7\uFF09\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%\u30FB\u5B9A\u4F4D\u306F\u4E2D\u592E\u56FA\u5B9A</li>
13638
+ <li>\u305D\u308C\u4EE5\u5916\u306E\u30DC\u30FC\u30AB\u30EB\uFF08\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u7B49\uFF09: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8OFF\u30FB\u58F0\u91CF\u3092\u65E2\u5B9A\u306E0.85\u500D\u3092\u57FA\u6E96\u306B\u3001\u526F\u30DC\u30FC\u30AB\u30EB\u306E\u672C\u6570\u304C\u591A\u3044\u307B\u3069\u3055\u3089\u306B\u7D5E\u308B\uFF08\u91CD\u306A\u3063\u3066\u97F3\u5727\u304C\u7A4D\u307F\u4E0A\u304C\u308B\u5206\u3092\u7B49\u30D1\u30EF\u30FC\u5247\u3067\u76F8\u6BBA\uFF09\u30FB\u97F3\u5727\u5F37\u531625%\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45120%\uFF08\u5E83\u3052\u3066\u300C\u58C1\u300D\u306E\u3088\u3046\u306A\u539A\u307F\u3092\u51FA\u3059\uFF09\u30FBEQ\u4F4E\u57DF-3dB\u30FBEQ\u9AD8\u57DF\u306F\u7121\u5909\u5316\uFF08\u660E\u308B\u304F\u3057\u3059\u304E\u308B\u3068\u524D\u306B\u51FA\u3066\u3057\u307E\u3046\u305F\u3081\uFF09\u30FB\u30C7\u30A3\u30EC\u30A4\u9001\u308A0%\uFF08\u639B\u3051\u308B\u3068\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3066\u56E3\u5B50\u306B\u306A\u308B\u305F\u3081\uFF09\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A45%\uFF08\u5965\u3078\u99B4\u67D3\u307E\u305B\u308B\uFF09\u30FB\u5B9A\u4F4D\u306F\u5DE6\u53F3\u3078\u4EA4\u4E92\u306B\u632F\u3063\u3066\u30C0\u30D6\u30EA\u30F3\u30B0\u611F\u3092\u51FA\u3059</li>
13638
13639
  </ul>
13639
- <p style="margin-top:4px;"><small>\u300C\u524D\u306B\u51FA\u3057\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u5C11\u306A\u3081\u3001\u5965\u306B\u7F6E\u304D\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u591A\u3081\u300D\u3068\u3044\u3046\u5B9A\u77F3\u306B\u57FA\u3065\u3044\u3066\u3044\u307E\u3059\u3002\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
13640
+ <p style="margin-top:4px;"><small>\u300C\u524D\u306B\u51FA\u3057\u305F\u3044\u97F3\u306F\u58F0\u91CF\u5927\u304D\u3081\u30FB\u72ED\u3081\u30FB\u30EA\u30D0\u30FC\u30D6\u5C11\u306A\u3081\u3001\u5965\u306B\u7F6E\u304D\u305F\u3044\u97F3\u306F\u58F0\u91CF\u63A7\u3048\u3081\u30FB\u5E83\u3081\u30FB\u30EA\u30D0\u30FC\u30D6\u591A\u3081\u300D\u3068\u3044\u3046\u5B9A\u77F3\u306B\u57FA\u3065\u3044\u3066\u3044\u307E\u3059\u3002EQ\u306E\u4F4E\u57DF\u30AB\u30C3\u30C8\u306F\u30DC\u30FC\u30AB\u30EB\u3067\u307B\u307C\u666E\u904D\u7684\u306A\u5B9A\u77F3\uFF08\u5B9F\u8CEA\u7684\u306A\u30CF\u30A4\u30D1\u30B9\u30D5\u30A3\u30EB\u30BF\u306E\u4EE3\u7528\uFF09\u3067\u3059\u3002\u30D3\u30D6\u30E9\u30FC\u30C8\u3092\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u3078\u639B\u3051\u306A\u3044\u306E\u306F\u3001\u8907\u6570\u306E\u58F0\u304C\u540C\u6642\u306B\u63FA\u308C\u308B\u3068\u58F0\u3054\u3068\u306E\u4F4D\u76F8\u30FB\u901F\u3055\u306E\u30BA\u30EC\u3067\u548C\u97F3\u304C\u300C\u3046\u306D\u308B\u300D\u300C\u6FC1\u308B\u300D\u305F\u3081\u3067\u3001\u5408\u5531\u3084\u30B9\u30C8\u30EA\u30F3\u30B0\u30B9\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u594F\u6CD5\u3068\u540C\u3058\u304F\u3001\u30CF\u30E2\u30EA/\u30B3\u30FC\u30E9\u30B9\u306F\u30B9\u30C8\u30EC\u30FC\u30C8\u30C8\u30FC\u30F3\u306B\u3057\u3066\u97F3\u7A0B\u306E\u8EF8\u3092\u5B89\u5B9A\u3055\u305B\u308B\u65B9\u304C\u99B4\u67D3\u307F\u307E\u3059\u3002\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A/\u4E0B\u3092\u91CD\u306D\u3066\u539A\u307F\u3092\u51FA\u3059\u52A0\u5DE5\uFF09\u306F\u3053\u3053\u3067\u306F\u81EA\u52D5\u9069\u7528\u3057\u307E\u305B\u3093 \u2014 \u58F0\u8CEA\u3092\u5927\u304D\u304F\u5909\u3048\u308B\u6F14\u51FA\u76EE\u7684\u306E\u52A0\u5DE5\u3067\u3042\u308A\u3001\u66F2\u306B\u3088\u3063\u3066\u5408\u3046/\u5408\u308F\u306A\u3044\u304C\u5206\u304B\u308C\u308B\u5275\u4F5C\u4E0A\u306E\u9078\u629E\uFF08\u30D0\u30E9\u30FC\u30C9\u3067\u306F\u4E0D\u81EA\u7136\u306B\u306A\u308A\u3084\u3059\u3044\u7B49\uFF09\u3067\u3001\u30DF\u30C3\u30AF\u30B9\u306E\u662F\u6B63\u3068\u306F\u6027\u8CEA\u304C\u9055\u3046\u305F\u3081\u3067\u3059\u3002\u4F7F\u3044\u305F\u3044\u5834\u5408\u306F\u5404\u30DC\u30FC\u30AB\u30EB\u30C8\u30E9\u30C3\u30AF\u306E\u8A2D\u5B9A\u6B04\u304B\u3089\u500B\u5225\u306B\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
13640
13641
  </div>
13641
13642
  `;
13642
13643
  var MIDI_INFO_HTML = `
@@ -16866,6 +16867,7 @@ var mountDAW = (target, options = {}) => {
16866
16867
  }
16867
16868
  let mainVocalId = null;
16868
16869
  let mainVocalScore = -1;
16870
+ let nonMainVocalCount = 0;
16869
16871
  for (const t of trackStates) {
16870
16872
  if (!t.lyricModel) continue;
16871
16873
  const score = t.core.getNotes().reduce((sum, n) => sum + n.durationSteps, 0);
@@ -16874,6 +16876,9 @@ var mountDAW = (target, options = {}) => {
16874
16876
  mainVocalId = t.config.id;
16875
16877
  }
16876
16878
  }
16879
+ for (const t of trackStates) {
16880
+ if (t.lyricModel && t.config.id !== mainVocalId) nonMainVocalCount++;
16881
+ }
16877
16882
  const autoPreset = INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano;
16878
16883
  const roleByTrackId = /* @__PURE__ */ new Map();
16879
16884
  for (const t of trackStates) {
@@ -16915,28 +16920,43 @@ var mountDAW = (target, options = {}) => {
16915
16920
  t.trackPan = pan;
16916
16921
  options.onTrackPanChange?.(t.config.id, pan);
16917
16922
  const comp = t.lyricModel ? isMainVocal ? 30 : 25 : roleMix?.compression ?? 35;
16918
- const width = t.lyricModel ? 115 : roleMix?.width ?? 115;
16923
+ const width = t.lyricModel ? isMainVocal ? 100 : 120 : roleMix?.width ?? 115;
16919
16924
  t.trackCompression = comp;
16920
16925
  t.trackWidth = width;
16921
16926
  options.onTrackCompressionChange?.(t.config.id, comp);
16922
16927
  options.onTrackWidthChange?.(t.config.id, width);
16923
- if (!t.lyricModel) {
16924
- const reverbSend = roleMix?.reverbSend ?? 15;
16925
- t.trackReverbSend = reverbSend;
16926
- options.onTrackReverbSendChange?.(t.config.id, reverbSend);
16928
+ if (t.lyricModel) {
16929
+ t.trackEqLow = -3;
16930
+ t.trackEqHigh = isMainVocal ? 2 : 0;
16931
+ options.onTrackEqLowChange?.(t.config.id, t.trackEqLow);
16932
+ options.onTrackEqHighChange?.(t.config.id, t.trackEqHigh);
16933
+ } else {
16927
16934
  const eqLow = roleMix?.eqLow ?? 0;
16928
16935
  const eqHigh = roleMix?.eqHigh ?? 0;
16929
16936
  t.trackEqLow = eqLow;
16930
16937
  t.trackEqHigh = eqHigh;
16931
16938
  options.onTrackEqLowChange?.(t.config.id, eqLow);
16932
16939
  options.onTrackEqHighChange?.(t.config.id, eqHigh);
16940
+ }
16941
+ if (!t.lyricModel) {
16942
+ const reverbSend = roleMix?.reverbSend ?? 15;
16943
+ t.trackReverbSend = reverbSend;
16944
+ options.onTrackReverbSendChange?.(t.config.id, reverbSend);
16933
16945
  const delaySend = roleMix?.delaySend ?? 0;
16934
16946
  t.trackDelaySend = delaySend;
16935
16947
  options.onTrackDelaySendChange?.(t.config.id, delaySend);
16936
16948
  }
16937
16949
  if (t.lyricModel) {
16938
- t.vocalVibrato = true;
16950
+ t.vocalVibrato = isMainVocal;
16939
16951
  t.vocalReverb = isMainVocal ? 25 : 45;
16952
+ t.vocalDelay = isMainVocal ? 15 : 0;
16953
+ t.vocalVolume = isMainVocal ? Math.round(DEFAULT_VOCAL_VOLUME * 1.1) : clamp5(
16954
+ Math.round(
16955
+ DEFAULT_VOCAL_VOLUME * 0.85 / Math.sqrt(Math.max(1, nonMainVocalCount))
16956
+ ),
16957
+ 60,
16958
+ MAX_VOCAL_VOLUME
16959
+ );
16940
16960
  fireLyricsChange(t);
16941
16961
  }
16942
16962
  }
@@ -17004,6 +17024,12 @@ var mountDAW = (target, options = {}) => {
17004
17024
  a.click();
17005
17025
  URL.revokeObjectURL(url2);
17006
17026
  });
17027
+ if (options.onExportWav) {
17028
+ refs.exportWavBtn.classList.remove("dtm-hidden");
17029
+ refs.exportWavBtn.addEventListener("click", () => {
17030
+ void options.onExportWav?.();
17031
+ });
17032
+ }
17007
17033
  const copy = (text, btn) => {
17008
17034
  navigator.clipboard?.writeText(text);
17009
17035
  btn.classList.add("dtm-btn--success");
@@ -18880,6 +18906,64 @@ var SoundFont_list = new class {
18880
18906
  }
18881
18907
  }();
18882
18908
 
18909
+ // src/wav-export.ts
18910
+ var floatTo16BitPCM = (view, offset, input) => {
18911
+ for (let i2 = 0; i2 < input.length; i2++, offset += 2) {
18912
+ const s = Math.max(-1, Math.min(1, input[i2]));
18913
+ view.setInt16(offset, s < 0 ? s * 32768 : s * 32767, true);
18914
+ }
18915
+ };
18916
+ var writeString = (view, offset, str) => {
18917
+ for (let i2 = 0; i2 < str.length; i2++) {
18918
+ view.setUint8(offset + i2, str.charCodeAt(i2));
18919
+ }
18920
+ };
18921
+ var encodeWavPCM16 = (channels, sampleRate) => {
18922
+ const numChannels = channels.length;
18923
+ const numFrames = channels[0]?.length ?? 0;
18924
+ const bytesPerSample = 2;
18925
+ const blockAlign = numChannels * bytesPerSample;
18926
+ const dataSize = numFrames * blockAlign;
18927
+ const buffer = new ArrayBuffer(44 + dataSize);
18928
+ const view = new DataView(buffer);
18929
+ writeString(view, 0, "RIFF");
18930
+ view.setUint32(4, 36 + dataSize, true);
18931
+ writeString(view, 8, "WAVE");
18932
+ writeString(view, 12, "fmt ");
18933
+ view.setUint32(16, 16, true);
18934
+ view.setUint16(20, 1, true);
18935
+ view.setUint16(22, numChannels, true);
18936
+ view.setUint32(24, sampleRate, true);
18937
+ view.setUint32(28, sampleRate * blockAlign, true);
18938
+ view.setUint16(32, blockAlign, true);
18939
+ view.setUint16(34, bytesPerSample * 8, true);
18940
+ writeString(view, 36, "data");
18941
+ view.setUint32(40, dataSize, true);
18942
+ if (numChannels === 1) {
18943
+ floatTo16BitPCM(view, 44, channels[0]);
18944
+ } else {
18945
+ let offset = 44;
18946
+ for (let i2 = 0; i2 < numFrames; i2++) {
18947
+ for (let c = 0; c < numChannels; c++) {
18948
+ const s = Math.max(-1, Math.min(1, channels[c][i2]));
18949
+ view.setInt16(offset, s < 0 ? s * 32768 : s * 32767, true);
18950
+ offset += 2;
18951
+ }
18952
+ }
18953
+ }
18954
+ return new Blob([buffer], { type: "audio/wav" });
18955
+ };
18956
+ var concatFloat32 = (chunks) => {
18957
+ const total = chunks.reduce((sum, c) => sum + c.length, 0);
18958
+ const out = new Float32Array(total);
18959
+ let offset = 0;
18960
+ for (const c of chunks) {
18961
+ out.set(c, offset);
18962
+ offset += c.length;
18963
+ }
18964
+ return out;
18965
+ };
18966
+
18883
18967
  // src/studio.ts
18884
18968
  var SOUNDFONT_NAME2 = "FluidR3_GM_sf2_file";
18885
18969
  var TRACK_ROLES = [
@@ -19356,6 +19440,28 @@ var createDtmStudio = async (options = {}) => {
19356
19440
  },
19357
19441
  onPlayNote: playNote4,
19358
19442
  onPlayDrum: playDrum,
19443
+ onExportWav: async () => {
19444
+ if (!daw) return;
19445
+ daw.stop();
19446
+ await resumeAudio();
19447
+ startWavRecording();
19448
+ await daw.play();
19449
+ await new Promise((resolve) => {
19450
+ const iv = setInterval(() => {
19451
+ if (daw.getPlaybackState() !== "playing") {
19452
+ clearInterval(iv);
19453
+ resolve();
19454
+ }
19455
+ }, 200);
19456
+ });
19457
+ const blob2 = await stopWavRecording();
19458
+ const url2 = URL.createObjectURL(blob2);
19459
+ const a = document.createElement("a");
19460
+ a.href = url2;
19461
+ a.download = "dtm.wav";
19462
+ a.click();
19463
+ URL.revokeObjectURL(url2);
19464
+ },
19359
19465
  singingVoices,
19360
19466
  parseMidi,
19361
19467
  onInstrumentChange: handleInstrumentChange,
@@ -19943,11 +20049,48 @@ var createDtmStudio = async (options = {}) => {
19943
20049
  const getAudioStreamTrack = () => {
19944
20050
  return createMediaStreamDestination().stream.getAudioTracks()[0];
19945
20051
  };
20052
+ let wavTap = null;
20053
+ const startWavRecording = () => {
20054
+ if (wavTap) return;
20055
+ const bufferSize = 4096;
20056
+ const processor = audioCtx.createScriptProcessor(bufferSize, 2, 2);
20057
+ const chunksL = [];
20058
+ const chunksR = [];
20059
+ processor.onaudioprocess = (e) => {
20060
+ chunksL.push(new Float32Array(e.inputBuffer.getChannelData(0)));
20061
+ chunksR.push(
20062
+ new Float32Array(
20063
+ e.inputBuffer.getChannelData(
20064
+ e.inputBuffer.numberOfChannels > 1 ? 1 : 0
20065
+ )
20066
+ )
20067
+ );
20068
+ };
20069
+ const silentSink = audioCtx.createGain();
20070
+ silentSink.gain.value = 0;
20071
+ fadeGain.connect(processor);
20072
+ processor.connect(silentSink);
20073
+ silentSink.connect(audioCtx.destination);
20074
+ wavTap = { processor, silentSink, chunksL, chunksR };
20075
+ };
20076
+ const stopWavRecording = async () => {
20077
+ if (!wavTap) throw new Error("[dtm] startWavRecording \u304C\u547C\u3070\u308C\u3066\u3044\u307E\u305B\u3093");
20078
+ const { processor, silentSink, chunksL, chunksR } = wavTap;
20079
+ fadeGain.disconnect(processor);
20080
+ processor.disconnect(silentSink);
20081
+ silentSink.disconnect(audioCtx.destination);
20082
+ wavTap = null;
20083
+ const left = concatFloat32(chunksL);
20084
+ const right = concatFloat32(chunksR);
20085
+ return encodeWavPCM16([left, right], audioCtx.sampleRate);
20086
+ };
19946
20087
  return {
19947
20088
  audioContext: audioCtx,
19948
20089
  masterGain,
19949
20090
  createMediaStreamDestination,
19950
20091
  getAudioStreamTrack,
20092
+ startWavRecording,
20093
+ stopWavRecording,
19951
20094
  singingVoices,
19952
20095
  mountEditor,
19953
20096
  mountPlayer,
@@ -20009,6 +20152,7 @@ export {
20009
20152
  buildDrumPatternJson,
20010
20153
  buildNameToKeyMapping,
20011
20154
  collectPitchTokens,
20155
+ concatFloat32,
20012
20156
  createAudioContext,
20013
20157
  createDtmStudio,
20014
20158
  createKlattVoice,
@@ -20028,6 +20172,7 @@ export {
20028
20172
  drawSelectedNotes,
20029
20173
  drawSelectionRect,
20030
20174
  encodeMml,
20175
+ encodeWavPCM16,
20031
20176
  exportMIDI,
20032
20177
  extractDrumPatternFromNotes,
20033
20178
  extractMidiDrumPattern,
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.96",
5
+ "version": "0.1.99",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {