@onjmin/dtm 0.1.4 → 0.1.5
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 +134 -1
- package/dist/index.d.ts +134 -1
- package/dist/index.js +320 -2
- package/dist/index.mjs +318 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1121,6 +1121,139 @@ type Sequencer = {
|
|
|
1121
1121
|
};
|
|
1122
1122
|
declare const createSequencer: (options: SequencerOptions) => Sequencer;
|
|
1123
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* createDtmStudio — 「import して関数1つ」で鳴る、全部入りスタジオ(Layer 3)。
|
|
1126
|
+
*
|
|
1127
|
+
* @onjmin/dtm 本体(mountDAW / mountMmlPlayer)は発音を持たない注入式設計で、
|
|
1128
|
+
* 楽器・ドラムの実音は外部 SoundFont(rpgen3)に委ねる。デモ index.html では
|
|
1129
|
+
* その配線(AudioContext・SoundFontロード・歌声ワーカー・録音・MIDI/コード解析)を
|
|
1130
|
+
* 手書きしていたが、本モジュールはそれを丸ごと内包する。
|
|
1131
|
+
*
|
|
1132
|
+
* const studio = await createDtmStudio();
|
|
1133
|
+
* studio.mountEditor(editorEl, { initialMML }); // 編集UI(音・歌声込み)
|
|
1134
|
+
* studio.mountPlayer(playerEl, mml); // 再生専用UI(音・歌声込み)
|
|
1135
|
+
*
|
|
1136
|
+
* 何も渡さなければ rpgen3 SoundFont を実行時にCDNから動的importし、歌声合成ワーカーは
|
|
1137
|
+
* パッケージ同梱の dist/voice-worker.js を用いる。エンジンやURLは options で差し替え可能。
|
|
1138
|
+
*/
|
|
1139
|
+
|
|
1140
|
+
type SoundFontInstance = {
|
|
1141
|
+
play: (o: {
|
|
1142
|
+
ctx: AudioContext;
|
|
1143
|
+
destination: AudioNode;
|
|
1144
|
+
pitch: number;
|
|
1145
|
+
volume: number;
|
|
1146
|
+
when: number;
|
|
1147
|
+
duration: number;
|
|
1148
|
+
}) => void;
|
|
1149
|
+
};
|
|
1150
|
+
type SoundFontEngine = {
|
|
1151
|
+
load: (o: {
|
|
1152
|
+
ctx: AudioContext;
|
|
1153
|
+
fontName: string;
|
|
1154
|
+
url: string;
|
|
1155
|
+
}) => Promise<SoundFontInstance>;
|
|
1156
|
+
toURL: (fullName: string) => string;
|
|
1157
|
+
};
|
|
1158
|
+
type SoundFontDrumEngine = {
|
|
1159
|
+
load: (o: {
|
|
1160
|
+
ctx: AudioContext;
|
|
1161
|
+
font: string;
|
|
1162
|
+
id: string;
|
|
1163
|
+
keys: number[];
|
|
1164
|
+
}) => Promise<void>;
|
|
1165
|
+
play: (o: {
|
|
1166
|
+
ctx: AudioContext;
|
|
1167
|
+
destination: AudioNode;
|
|
1168
|
+
pitch: number;
|
|
1169
|
+
volume: number;
|
|
1170
|
+
when: number;
|
|
1171
|
+
duration: number;
|
|
1172
|
+
}) => void;
|
|
1173
|
+
font: unknown;
|
|
1174
|
+
};
|
|
1175
|
+
type SoundFontListEngine = {
|
|
1176
|
+
init: () => void;
|
|
1177
|
+
onload: (cb: () => void) => void;
|
|
1178
|
+
};
|
|
1179
|
+
/** 注入で差し替え可能な外部エンジン群(未指定なら CDN から取得)。 */
|
|
1180
|
+
type DtmStudioEngines = {
|
|
1181
|
+
SoundFont?: SoundFontEngine;
|
|
1182
|
+
SoundFont_drum?: SoundFontDrumEngine;
|
|
1183
|
+
SoundFont_list?: SoundFontListEngine;
|
|
1184
|
+
parseChord?: DawOptions["parseChord"];
|
|
1185
|
+
parseChords?: DawOptions["parseChords"];
|
|
1186
|
+
parseMidi?: DawOptions["parseMidi"];
|
|
1187
|
+
};
|
|
1188
|
+
/** 既定の取得元URL(rpgen3 / jsDelivr)。options.cdn で個別に上書きできる。 */
|
|
1189
|
+
declare const DEFAULT_CDN: {
|
|
1190
|
+
readonly soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs";
|
|
1191
|
+
readonly soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs";
|
|
1192
|
+
readonly soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs";
|
|
1193
|
+
readonly parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs";
|
|
1194
|
+
readonly parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs";
|
|
1195
|
+
readonly midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm";
|
|
1196
|
+
};
|
|
1197
|
+
type DtmStudioOptions = {
|
|
1198
|
+
/** 既存の AudioContext を使う(未指定なら内部生成)。 */
|
|
1199
|
+
audioContext?: AudioContext;
|
|
1200
|
+
/** マスター音量 0-1(楽器・歌声)。既定 1。 */
|
|
1201
|
+
masterVolume?: number;
|
|
1202
|
+
/** ドラム音量 0-1。既定 1。 */
|
|
1203
|
+
drumVolume?: number;
|
|
1204
|
+
/**
|
|
1205
|
+
* 歌声合成ワーカー(voice-worker.js)のURL。
|
|
1206
|
+
* 既定はパッケージ同梱の dist/voice-worker.js。
|
|
1207
|
+
* `null` を渡すとワーカーを使わず klatt のみ(koe音源はメインスレッド合成)。
|
|
1208
|
+
*/
|
|
1209
|
+
voiceWorkerUrl?: string | null;
|
|
1210
|
+
/** 初期の楽器プリセットキー(INSTRUMENT_PRESETS)。既定 "retro_game"。 */
|
|
1211
|
+
defaultPreset?: string;
|
|
1212
|
+
/** 外部エンジンの注入(指定したものは CDN 取得をスキップ)。 */
|
|
1213
|
+
engines?: DtmStudioEngines;
|
|
1214
|
+
/** CDN URL の上書き。 */
|
|
1215
|
+
cdn?: Partial<typeof DEFAULT_CDN>;
|
|
1216
|
+
/** 有効化する機能。既定はすべて true。 */
|
|
1217
|
+
features?: {
|
|
1218
|
+
/** 録音→WAVダウンロード(編集UIの録音ボタン)。 */
|
|
1219
|
+
recorder?: boolean;
|
|
1220
|
+
/** MIDIファイル読み込み。 */
|
|
1221
|
+
midi?: boolean;
|
|
1222
|
+
/** コード入力(和音)。 */
|
|
1223
|
+
chord?: boolean;
|
|
1224
|
+
/** 編集UIに楽器プリセット選択UIを差し込む。 */
|
|
1225
|
+
presetUI?: boolean;
|
|
1226
|
+
};
|
|
1227
|
+
};
|
|
1228
|
+
/** 編集UIのマウント時オプション(DawOptions を一部上書きできる)。 */
|
|
1229
|
+
type MountEditorOptions = Partial<DawOptions> & {
|
|
1230
|
+
/** このエディタで読み込む楽器プリセット(未指定なら studio の defaultPreset)。 */
|
|
1231
|
+
preset?: string;
|
|
1232
|
+
/** プリセット選択UIを出すか(未指定なら studio の features.presetUI)。 */
|
|
1233
|
+
presetUI?: boolean;
|
|
1234
|
+
};
|
|
1235
|
+
/** 再生UIのマウント時オプション(MmlPlayerOptions を一部上書きできる)。 */
|
|
1236
|
+
type MountPlayerOptions = Partial<MmlPlayerOptions>;
|
|
1237
|
+
type DtmStudio = {
|
|
1238
|
+
/** 内部で使用している AudioContext。 */
|
|
1239
|
+
audioContext: AudioContext;
|
|
1240
|
+
/** 歌声合成ヘルパ(klatt + koe音源)。 */
|
|
1241
|
+
singingVoices: SingingVoices;
|
|
1242
|
+
/** 編集UI(mountDAW)を音・歌声込みでマウントする。 */
|
|
1243
|
+
mountEditor: (target: HTMLElement, options?: MountEditorOptions) => DawInstance;
|
|
1244
|
+
/** 再生専用UI(mountMmlPlayer)を音・歌声込みでマウントする。 */
|
|
1245
|
+
mountPlayer: (target: HTMLElement, mml: string, options?: MountPlayerOptions) => MmlPlayerInstance;
|
|
1246
|
+
/** 楽器プリセットを(指定トラックぶん)ロードする。 */
|
|
1247
|
+
loadPreset: (presetKey: string, trackIds?: string[]) => Promise<void>;
|
|
1248
|
+
/** AudioContext を閉じ、生成物を破棄する。 */
|
|
1249
|
+
dispose: () => void;
|
|
1250
|
+
};
|
|
1251
|
+
/**
|
|
1252
|
+
* 全部入りスタジオを生成する。SoundFontエンジン・ドラム音源・楽器プリセットの
|
|
1253
|
+
* 初期ロードまで待ってから解決するため、await して使う。
|
|
1254
|
+
*/
|
|
1255
|
+
declare const createDtmStudio: (options?: DtmStudioOptions) => Promise<DtmStudio>;
|
|
1256
|
+
|
|
1124
1257
|
/**
|
|
1125
1258
|
* mountDAW が注入する自己完結スタイル。
|
|
1126
1259
|
* すべてのクラスは `dtm-` プレフィックスでスコープし、ホスト側CSSと衝突しにくくする。
|
|
@@ -1133,4 +1266,4 @@ declare const DAW_CSS = "\n@font-face {\n font-family: 'k8x12';\n src: url('ht
|
|
|
1133
1266
|
*/
|
|
1134
1267
|
declare const injectStyles: (doc?: Document) => void;
|
|
1135
1268
|
|
|
1136
|
-
export { type AddNoteOptions, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ConsumedSyllable, type CoreEventHandlers, 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 DawOptions, type DawViewState, type DrumPattern, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayerInstance, type MmlPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseChordFn, type ParseChordsFn, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayNoteEvent, type PlaybackState, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamVoiceNote, type StreamVoiceTrack, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, setDrawOffset, setupRecorder, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
|
|
1269
|
+
export { type AddNoteOptions, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ConsumedSyllable, type CoreEventHandlers, 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 DawOptions, type DawViewState, type DrumPattern, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayerInstance, type MmlPlayerOptions, type MountEditorOptions, type MountPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseChordFn, type ParseChordsFn, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayNoteEvent, type PlaybackState, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamVoiceNote, type StreamVoiceTrack, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, setDrawOffset, setupRecorder, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
|
package/dist/index.d.ts
CHANGED
|
@@ -1121,6 +1121,139 @@ type Sequencer = {
|
|
|
1121
1121
|
};
|
|
1122
1122
|
declare const createSequencer: (options: SequencerOptions) => Sequencer;
|
|
1123
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* createDtmStudio — 「import して関数1つ」で鳴る、全部入りスタジオ(Layer 3)。
|
|
1126
|
+
*
|
|
1127
|
+
* @onjmin/dtm 本体(mountDAW / mountMmlPlayer)は発音を持たない注入式設計で、
|
|
1128
|
+
* 楽器・ドラムの実音は外部 SoundFont(rpgen3)に委ねる。デモ index.html では
|
|
1129
|
+
* その配線(AudioContext・SoundFontロード・歌声ワーカー・録音・MIDI/コード解析)を
|
|
1130
|
+
* 手書きしていたが、本モジュールはそれを丸ごと内包する。
|
|
1131
|
+
*
|
|
1132
|
+
* const studio = await createDtmStudio();
|
|
1133
|
+
* studio.mountEditor(editorEl, { initialMML }); // 編集UI(音・歌声込み)
|
|
1134
|
+
* studio.mountPlayer(playerEl, mml); // 再生専用UI(音・歌声込み)
|
|
1135
|
+
*
|
|
1136
|
+
* 何も渡さなければ rpgen3 SoundFont を実行時にCDNから動的importし、歌声合成ワーカーは
|
|
1137
|
+
* パッケージ同梱の dist/voice-worker.js を用いる。エンジンやURLは options で差し替え可能。
|
|
1138
|
+
*/
|
|
1139
|
+
|
|
1140
|
+
type SoundFontInstance = {
|
|
1141
|
+
play: (o: {
|
|
1142
|
+
ctx: AudioContext;
|
|
1143
|
+
destination: AudioNode;
|
|
1144
|
+
pitch: number;
|
|
1145
|
+
volume: number;
|
|
1146
|
+
when: number;
|
|
1147
|
+
duration: number;
|
|
1148
|
+
}) => void;
|
|
1149
|
+
};
|
|
1150
|
+
type SoundFontEngine = {
|
|
1151
|
+
load: (o: {
|
|
1152
|
+
ctx: AudioContext;
|
|
1153
|
+
fontName: string;
|
|
1154
|
+
url: string;
|
|
1155
|
+
}) => Promise<SoundFontInstance>;
|
|
1156
|
+
toURL: (fullName: string) => string;
|
|
1157
|
+
};
|
|
1158
|
+
type SoundFontDrumEngine = {
|
|
1159
|
+
load: (o: {
|
|
1160
|
+
ctx: AudioContext;
|
|
1161
|
+
font: string;
|
|
1162
|
+
id: string;
|
|
1163
|
+
keys: number[];
|
|
1164
|
+
}) => Promise<void>;
|
|
1165
|
+
play: (o: {
|
|
1166
|
+
ctx: AudioContext;
|
|
1167
|
+
destination: AudioNode;
|
|
1168
|
+
pitch: number;
|
|
1169
|
+
volume: number;
|
|
1170
|
+
when: number;
|
|
1171
|
+
duration: number;
|
|
1172
|
+
}) => void;
|
|
1173
|
+
font: unknown;
|
|
1174
|
+
};
|
|
1175
|
+
type SoundFontListEngine = {
|
|
1176
|
+
init: () => void;
|
|
1177
|
+
onload: (cb: () => void) => void;
|
|
1178
|
+
};
|
|
1179
|
+
/** 注入で差し替え可能な外部エンジン群(未指定なら CDN から取得)。 */
|
|
1180
|
+
type DtmStudioEngines = {
|
|
1181
|
+
SoundFont?: SoundFontEngine;
|
|
1182
|
+
SoundFont_drum?: SoundFontDrumEngine;
|
|
1183
|
+
SoundFont_list?: SoundFontListEngine;
|
|
1184
|
+
parseChord?: DawOptions["parseChord"];
|
|
1185
|
+
parseChords?: DawOptions["parseChords"];
|
|
1186
|
+
parseMidi?: DawOptions["parseMidi"];
|
|
1187
|
+
};
|
|
1188
|
+
/** 既定の取得元URL(rpgen3 / jsDelivr)。options.cdn で個別に上書きできる。 */
|
|
1189
|
+
declare const DEFAULT_CDN: {
|
|
1190
|
+
readonly soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs";
|
|
1191
|
+
readonly soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs";
|
|
1192
|
+
readonly soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs";
|
|
1193
|
+
readonly parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs";
|
|
1194
|
+
readonly parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs";
|
|
1195
|
+
readonly midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm";
|
|
1196
|
+
};
|
|
1197
|
+
type DtmStudioOptions = {
|
|
1198
|
+
/** 既存の AudioContext を使う(未指定なら内部生成)。 */
|
|
1199
|
+
audioContext?: AudioContext;
|
|
1200
|
+
/** マスター音量 0-1(楽器・歌声)。既定 1。 */
|
|
1201
|
+
masterVolume?: number;
|
|
1202
|
+
/** ドラム音量 0-1。既定 1。 */
|
|
1203
|
+
drumVolume?: number;
|
|
1204
|
+
/**
|
|
1205
|
+
* 歌声合成ワーカー(voice-worker.js)のURL。
|
|
1206
|
+
* 既定はパッケージ同梱の dist/voice-worker.js。
|
|
1207
|
+
* `null` を渡すとワーカーを使わず klatt のみ(koe音源はメインスレッド合成)。
|
|
1208
|
+
*/
|
|
1209
|
+
voiceWorkerUrl?: string | null;
|
|
1210
|
+
/** 初期の楽器プリセットキー(INSTRUMENT_PRESETS)。既定 "retro_game"。 */
|
|
1211
|
+
defaultPreset?: string;
|
|
1212
|
+
/** 外部エンジンの注入(指定したものは CDN 取得をスキップ)。 */
|
|
1213
|
+
engines?: DtmStudioEngines;
|
|
1214
|
+
/** CDN URL の上書き。 */
|
|
1215
|
+
cdn?: Partial<typeof DEFAULT_CDN>;
|
|
1216
|
+
/** 有効化する機能。既定はすべて true。 */
|
|
1217
|
+
features?: {
|
|
1218
|
+
/** 録音→WAVダウンロード(編集UIの録音ボタン)。 */
|
|
1219
|
+
recorder?: boolean;
|
|
1220
|
+
/** MIDIファイル読み込み。 */
|
|
1221
|
+
midi?: boolean;
|
|
1222
|
+
/** コード入力(和音)。 */
|
|
1223
|
+
chord?: boolean;
|
|
1224
|
+
/** 編集UIに楽器プリセット選択UIを差し込む。 */
|
|
1225
|
+
presetUI?: boolean;
|
|
1226
|
+
};
|
|
1227
|
+
};
|
|
1228
|
+
/** 編集UIのマウント時オプション(DawOptions を一部上書きできる)。 */
|
|
1229
|
+
type MountEditorOptions = Partial<DawOptions> & {
|
|
1230
|
+
/** このエディタで読み込む楽器プリセット(未指定なら studio の defaultPreset)。 */
|
|
1231
|
+
preset?: string;
|
|
1232
|
+
/** プリセット選択UIを出すか(未指定なら studio の features.presetUI)。 */
|
|
1233
|
+
presetUI?: boolean;
|
|
1234
|
+
};
|
|
1235
|
+
/** 再生UIのマウント時オプション(MmlPlayerOptions を一部上書きできる)。 */
|
|
1236
|
+
type MountPlayerOptions = Partial<MmlPlayerOptions>;
|
|
1237
|
+
type DtmStudio = {
|
|
1238
|
+
/** 内部で使用している AudioContext。 */
|
|
1239
|
+
audioContext: AudioContext;
|
|
1240
|
+
/** 歌声合成ヘルパ(klatt + koe音源)。 */
|
|
1241
|
+
singingVoices: SingingVoices;
|
|
1242
|
+
/** 編集UI(mountDAW)を音・歌声込みでマウントする。 */
|
|
1243
|
+
mountEditor: (target: HTMLElement, options?: MountEditorOptions) => DawInstance;
|
|
1244
|
+
/** 再生専用UI(mountMmlPlayer)を音・歌声込みでマウントする。 */
|
|
1245
|
+
mountPlayer: (target: HTMLElement, mml: string, options?: MountPlayerOptions) => MmlPlayerInstance;
|
|
1246
|
+
/** 楽器プリセットを(指定トラックぶん)ロードする。 */
|
|
1247
|
+
loadPreset: (presetKey: string, trackIds?: string[]) => Promise<void>;
|
|
1248
|
+
/** AudioContext を閉じ、生成物を破棄する。 */
|
|
1249
|
+
dispose: () => void;
|
|
1250
|
+
};
|
|
1251
|
+
/**
|
|
1252
|
+
* 全部入りスタジオを生成する。SoundFontエンジン・ドラム音源・楽器プリセットの
|
|
1253
|
+
* 初期ロードまで待ってから解決するため、await して使う。
|
|
1254
|
+
*/
|
|
1255
|
+
declare const createDtmStudio: (options?: DtmStudioOptions) => Promise<DtmStudio>;
|
|
1256
|
+
|
|
1124
1257
|
/**
|
|
1125
1258
|
* mountDAW が注入する自己完結スタイル。
|
|
1126
1259
|
* すべてのクラスは `dtm-` プレフィックスでスコープし、ホスト側CSSと衝突しにくくする。
|
|
@@ -1133,4 +1266,4 @@ declare const DAW_CSS = "\n@font-face {\n font-family: 'k8x12';\n src: url('ht
|
|
|
1133
1266
|
*/
|
|
1134
1267
|
declare const injectStyles: (doc?: Document) => void;
|
|
1135
1268
|
|
|
1136
|
-
export { type AddNoteOptions, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ConsumedSyllable, type CoreEventHandlers, 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 DawOptions, type DawViewState, type DrumPattern, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayerInstance, type MmlPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseChordFn, type ParseChordsFn, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayNoteEvent, type PlaybackState, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamVoiceNote, type StreamVoiceTrack, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, setDrawOffset, setupRecorder, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
|
|
1269
|
+
export { type AddNoteOptions, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ConsumedSyllable, type CoreEventHandlers, 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 DawOptions, type DawViewState, type DrumPattern, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayerInstance, type MmlPlayerOptions, type MountEditorOptions, type MountPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseChordFn, type ParseChordsFn, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayNoteEvent, type PlaybackState, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamVoiceNote, type StreamVoiceTrack, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, setDrawOffset, setupRecorder, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
buildNameToKeyMapping: () => buildNameToKeyMapping,
|
|
52
52
|
collectPitchTokens: () => collectPitchTokens,
|
|
53
53
|
createAudioContext: () => createAudioContext,
|
|
54
|
+
createDtmStudio: () => createDtmStudio,
|
|
54
55
|
createKlattVoice: () => createKlattVoice,
|
|
55
56
|
createKoeVoice: () => createKoeVoice,
|
|
56
57
|
createLyricsConductor: () => createLyricsConductor,
|
|
@@ -6490,8 +6491,6 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6490
6491
|
if (useSynth) synthPlay(e);
|
|
6491
6492
|
},
|
|
6492
6493
|
onPlayDrum: (e) => {
|
|
6493
|
-
const em = emojiEls[0];
|
|
6494
|
-
if (em) jumpEmojiAt(em, e.when);
|
|
6495
6494
|
const velocity = e.velocity * (trackVolume / 100);
|
|
6496
6495
|
options.onPlayDrum?.({ ...e, velocity });
|
|
6497
6496
|
if (useSynth) drumSynth({ ...e, velocity });
|
|
@@ -6884,6 +6883,324 @@ var createPianoRoll = (options, handlers) => {
|
|
|
6884
6883
|
}
|
|
6885
6884
|
};
|
|
6886
6885
|
};
|
|
6886
|
+
|
|
6887
|
+
// src/studio.ts
|
|
6888
|
+
var import_meta = {};
|
|
6889
|
+
var DEFAULT_CDN = {
|
|
6890
|
+
soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs",
|
|
6891
|
+
soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs",
|
|
6892
|
+
soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs",
|
|
6893
|
+
parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs",
|
|
6894
|
+
parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs",
|
|
6895
|
+
midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm"
|
|
6896
|
+
};
|
|
6897
|
+
var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
|
|
6898
|
+
var TRACK_ROLES = ["melody", "submelody", "bass", "chord"];
|
|
6899
|
+
var resolveDefaultVoiceWorkerUrl = () => {
|
|
6900
|
+
try {
|
|
6901
|
+
return new URL("./voice-worker.js", import_meta.url).href;
|
|
6902
|
+
} catch {
|
|
6903
|
+
return void 0;
|
|
6904
|
+
}
|
|
6905
|
+
};
|
|
6906
|
+
var importFrom = async (url, name) => {
|
|
6907
|
+
const mod = await import(
|
|
6908
|
+
/* @vite-ignore */
|
|
6909
|
+
url
|
|
6910
|
+
);
|
|
6911
|
+
return mod[name] ?? mod.default;
|
|
6912
|
+
};
|
|
6913
|
+
var createDtmStudio = async (options = {}) => {
|
|
6914
|
+
const cdn = { ...DEFAULT_CDN, ...options.cdn };
|
|
6915
|
+
const features = {
|
|
6916
|
+
recorder: true,
|
|
6917
|
+
midi: true,
|
|
6918
|
+
chord: true,
|
|
6919
|
+
presetUI: true,
|
|
6920
|
+
...options.features
|
|
6921
|
+
};
|
|
6922
|
+
const audioCtx = options.audioContext ?? new AudioContext();
|
|
6923
|
+
const masterGain = audioCtx.createGain();
|
|
6924
|
+
masterGain.gain.value = options.masterVolume ?? 1;
|
|
6925
|
+
masterGain.connect(audioCtx.destination);
|
|
6926
|
+
const drumGain = audioCtx.createGain();
|
|
6927
|
+
drumGain.gain.value = options.drumVolume ?? 1;
|
|
6928
|
+
drumGain.connect(audioCtx.destination);
|
|
6929
|
+
const resumeAudio = () => {
|
|
6930
|
+
if (audioCtx.state === "suspended") void audioCtx.resume();
|
|
6931
|
+
};
|
|
6932
|
+
const eng = options.engines ?? {};
|
|
6933
|
+
const [SoundFont, SoundFont_drum, SoundFont_list] = await Promise.all([
|
|
6934
|
+
eng.SoundFont ?? importFrom(cdn.soundFont, "SoundFont"),
|
|
6935
|
+
eng.SoundFont_drum ?? importFrom(cdn.soundFontDrum, "SoundFont_drum"),
|
|
6936
|
+
eng.SoundFont_list ?? importFrom(cdn.soundFontList, "SoundFont_list")
|
|
6937
|
+
]);
|
|
6938
|
+
let parseChord = eng.parseChord;
|
|
6939
|
+
let parseChords = eng.parseChords;
|
|
6940
|
+
if (features.chord && (!parseChord || !parseChords)) {
|
|
6941
|
+
try {
|
|
6942
|
+
[parseChord, parseChords] = await Promise.all([
|
|
6943
|
+
parseChord ?? importFrom(
|
|
6944
|
+
cdn.parseChord,
|
|
6945
|
+
"parseChord"
|
|
6946
|
+
),
|
|
6947
|
+
parseChords ?? importFrom(
|
|
6948
|
+
cdn.parseChords,
|
|
6949
|
+
"parseChords"
|
|
6950
|
+
)
|
|
6951
|
+
]);
|
|
6952
|
+
} catch (e) {
|
|
6953
|
+
console.warn("[dtm] \u30B3\u30FC\u30C9\u89E3\u6790\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F", e);
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
let midiParser = null;
|
|
6957
|
+
let parseMidi;
|
|
6958
|
+
if (features.midi) {
|
|
6959
|
+
parseMidi = eng.parseMidi;
|
|
6960
|
+
if (!parseMidi) {
|
|
6961
|
+
const midiPromise = importFrom(
|
|
6962
|
+
cdn.midiParser,
|
|
6963
|
+
"default"
|
|
6964
|
+
).then((m) => {
|
|
6965
|
+
midiParser = m;
|
|
6966
|
+
}).catch((e) => console.warn("[dtm] midi-parser \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557", e));
|
|
6967
|
+
void midiPromise;
|
|
6968
|
+
parseMidi = (bytes) => {
|
|
6969
|
+
if (!midiParser) throw new Error("midi-parser not ready");
|
|
6970
|
+
return midiParser.parse(bytes);
|
|
6971
|
+
};
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
const voiceWorkerUrl = options.voiceWorkerUrl === null ? void 0 : options.voiceWorkerUrl ?? resolveDefaultVoiceWorkerUrl();
|
|
6975
|
+
const singingVoices = createSingingVoices(audioCtx, masterGain, {
|
|
6976
|
+
voiceWorkerUrl
|
|
6977
|
+
});
|
|
6978
|
+
const recorder = features.recorder ? setupRecorder(audioCtx, masterGain, drumGain) : null;
|
|
6979
|
+
const downloadWav = () => {
|
|
6980
|
+
if (!recorder) return;
|
|
6981
|
+
const recordedData = recorder.getRecordedData();
|
|
6982
|
+
const ch = recordedData.length;
|
|
6983
|
+
const len = recordedData[0].length;
|
|
6984
|
+
if (len === 0) return;
|
|
6985
|
+
const bufSize = recordedData[0][0].length;
|
|
6986
|
+
const wave = new Float32Array(ch * len * bufSize);
|
|
6987
|
+
let idx = 0;
|
|
6988
|
+
for (let i = 0; i < len; i++)
|
|
6989
|
+
for (let j = 0; j < bufSize; j++)
|
|
6990
|
+
for (let k = 0; k < ch; k++) wave[idx++] = recordedData[k][i][j];
|
|
6991
|
+
const sampleRate = audioCtx.sampleRate;
|
|
6992
|
+
const channels = 2;
|
|
6993
|
+
const bitRate = 16;
|
|
6994
|
+
const step = bitRate / 8;
|
|
6995
|
+
const blockSize = channels * step;
|
|
6996
|
+
const byteLen = wave.length * step;
|
|
6997
|
+
const view = new DataView(new ArrayBuffer(44 + byteLen));
|
|
6998
|
+
const ws = (off2, s) => {
|
|
6999
|
+
for (let i = 0; i < s.length; i++)
|
|
7000
|
+
view.setUint8(off2 + i, s.charCodeAt(i));
|
|
7001
|
+
};
|
|
7002
|
+
ws(0, "RIFF");
|
|
7003
|
+
view.setUint32(4, 32 + byteLen, true);
|
|
7004
|
+
ws(8, "WAVE");
|
|
7005
|
+
ws(12, "fmt ");
|
|
7006
|
+
view.setUint32(16, 16, true);
|
|
7007
|
+
view.setUint16(20, 1, true);
|
|
7008
|
+
view.setUint16(22, channels, true);
|
|
7009
|
+
view.setUint32(24, sampleRate, true);
|
|
7010
|
+
view.setUint32(28, sampleRate * blockSize, true);
|
|
7011
|
+
view.setUint16(32, blockSize, true);
|
|
7012
|
+
view.setUint16(34, bitRate, true);
|
|
7013
|
+
ws(36, "data");
|
|
7014
|
+
view.setUint32(40, byteLen, true);
|
|
7015
|
+
const clamp4 = (n, a2, b) => Math.max(a2, Math.min(b, n));
|
|
7016
|
+
let off = 44;
|
|
7017
|
+
for (let i = 0; i < wave.length; i++, off += step)
|
|
7018
|
+
view.setInt16(
|
|
7019
|
+
off,
|
|
7020
|
+
clamp4(Math.round(wave[i] * 32768), -32768, 32767),
|
|
7021
|
+
true
|
|
7022
|
+
);
|
|
7023
|
+
const blob = new Blob([view], { type: "audio/wav" });
|
|
7024
|
+
const url = URL.createObjectURL(blob);
|
|
7025
|
+
const a = document.createElement("a");
|
|
7026
|
+
a.href = url;
|
|
7027
|
+
a.download = "record.wav";
|
|
7028
|
+
a.click();
|
|
7029
|
+
URL.revokeObjectURL(url);
|
|
7030
|
+
};
|
|
7031
|
+
const onToggleRecord = recorder ? () => {
|
|
7032
|
+
if (recorder.isRecording()) {
|
|
7033
|
+
recorder.stopRecording();
|
|
7034
|
+
downloadWav();
|
|
7035
|
+
} else {
|
|
7036
|
+
recorder.clearRecordedData();
|
|
7037
|
+
recorder.startRecording();
|
|
7038
|
+
}
|
|
7039
|
+
} : void 0;
|
|
7040
|
+
const listReady = new Promise((resolve) => {
|
|
7041
|
+
SoundFont_list.init();
|
|
7042
|
+
SoundFont_list.onload(() => resolve());
|
|
7043
|
+
});
|
|
7044
|
+
const drumReady = (async () => {
|
|
7045
|
+
try {
|
|
7046
|
+
await SoundFont_drum.load({
|
|
7047
|
+
ctx: audioCtx,
|
|
7048
|
+
font: DRUM_FONT,
|
|
7049
|
+
id: "0",
|
|
7050
|
+
keys: Object.values(DRUM_KEYS)
|
|
7051
|
+
});
|
|
7052
|
+
} catch (e) {
|
|
7053
|
+
console.error("[dtm] \u30C9\u30E9\u30E0\u97F3\u6E90\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557", e);
|
|
7054
|
+
}
|
|
7055
|
+
})();
|
|
7056
|
+
let nameToKey = {};
|
|
7057
|
+
const soundFonts = /* @__PURE__ */ new Map();
|
|
7058
|
+
const loadedKeyByTrack = /* @__PURE__ */ new Map();
|
|
7059
|
+
const loadSoundFont = async (instrumentKey, trackId) => {
|
|
7060
|
+
if (loadedKeyByTrack.get(trackId) === instrumentKey) return;
|
|
7061
|
+
try {
|
|
7062
|
+
const fullName = `${instrumentKey}_${SOUNDFONT_NAME}`;
|
|
7063
|
+
soundFonts.set(
|
|
7064
|
+
trackId,
|
|
7065
|
+
await SoundFont.load({
|
|
7066
|
+
ctx: audioCtx,
|
|
7067
|
+
fontName: `_tone_${fullName}`,
|
|
7068
|
+
url: SoundFont.toURL(fullName)
|
|
7069
|
+
})
|
|
7070
|
+
);
|
|
7071
|
+
loadedKeyByTrack.set(trackId, instrumentKey);
|
|
7072
|
+
} catch (e) {
|
|
7073
|
+
console.error(`[dtm] \u697D\u5668 "${instrumentKey}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557`, e);
|
|
7074
|
+
}
|
|
7075
|
+
};
|
|
7076
|
+
const defaultPreset = options.defaultPreset ?? "retro_game";
|
|
7077
|
+
const instrumentNameFor = (preset, trackId) => preset[trackId] ?? preset.melody;
|
|
7078
|
+
const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES]) => {
|
|
7079
|
+
const preset = INSTRUMENT_PRESETS[presetKey];
|
|
7080
|
+
if (!preset) return;
|
|
7081
|
+
await listReady;
|
|
7082
|
+
await Promise.all(
|
|
7083
|
+
trackIds.map((trackId) => {
|
|
7084
|
+
const key = nameToKey[instrumentNameFor(preset, trackId)];
|
|
7085
|
+
return key ? loadSoundFont(key, trackId) : Promise.resolve();
|
|
7086
|
+
})
|
|
7087
|
+
);
|
|
7088
|
+
};
|
|
7089
|
+
await listReady;
|
|
7090
|
+
nameToKey = await buildNameToKeyMapping();
|
|
7091
|
+
await Promise.all([drumReady, loadPreset(defaultPreset)]);
|
|
7092
|
+
const playNote = (e) => {
|
|
7093
|
+
const sf = soundFonts.get(e.trackId);
|
|
7094
|
+
if (!sf) return;
|
|
7095
|
+
sf.play({
|
|
7096
|
+
ctx: audioCtx,
|
|
7097
|
+
destination: masterGain,
|
|
7098
|
+
pitch: e.pitch,
|
|
7099
|
+
volume: e.volume,
|
|
7100
|
+
when: e.when,
|
|
7101
|
+
duration: e.duration
|
|
7102
|
+
});
|
|
7103
|
+
};
|
|
7104
|
+
const playDrum = (e) => {
|
|
7105
|
+
if (!SoundFont_drum.font) return;
|
|
7106
|
+
SoundFont_drum.play({
|
|
7107
|
+
ctx: audioCtx,
|
|
7108
|
+
destination: drumGain,
|
|
7109
|
+
pitch: e.pitch,
|
|
7110
|
+
volume: e.velocity,
|
|
7111
|
+
when: e.when,
|
|
7112
|
+
duration: e.duration
|
|
7113
|
+
});
|
|
7114
|
+
};
|
|
7115
|
+
const sfForPlayerTrack = (trackId) => soundFonts.get(TRACK_ROLES[Number(trackId)] ?? "") ?? soundFonts.get(`t${trackId}`);
|
|
7116
|
+
const playPlayerNote = (e) => {
|
|
7117
|
+
const sf = sfForPlayerTrack(e.trackId);
|
|
7118
|
+
if (!sf) return;
|
|
7119
|
+
sf.play({
|
|
7120
|
+
ctx: audioCtx,
|
|
7121
|
+
destination: masterGain,
|
|
7122
|
+
pitch: e.pitch,
|
|
7123
|
+
volume: e.volume,
|
|
7124
|
+
when: e.when,
|
|
7125
|
+
duration: e.duration
|
|
7126
|
+
});
|
|
7127
|
+
};
|
|
7128
|
+
const editorPresetSelects = /* @__PURE__ */ new WeakMap();
|
|
7129
|
+
const mountedEditors = [];
|
|
7130
|
+
const mountedPlayers = [];
|
|
7131
|
+
const mountEditor = (target, opts = {}) => {
|
|
7132
|
+
const { preset, presetUI, ...dawOverrides } = opts;
|
|
7133
|
+
const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
|
|
7134
|
+
const trackIds = tracks.map((t) => t.id);
|
|
7135
|
+
const base = {
|
|
7136
|
+
getAudioTime: () => audioCtx.currentTime,
|
|
7137
|
+
onResumeAudio: resumeAudio,
|
|
7138
|
+
onPlayNote: playNote,
|
|
7139
|
+
onPlayDrum: playDrum,
|
|
7140
|
+
singingVoices,
|
|
7141
|
+
parseChord,
|
|
7142
|
+
parseChords,
|
|
7143
|
+
parseMidi,
|
|
7144
|
+
onToggleRecord,
|
|
7145
|
+
...dawOverrides
|
|
7146
|
+
};
|
|
7147
|
+
const wantPresetUI = presetUI ?? features.presetUI;
|
|
7148
|
+
if (wantPresetUI) {
|
|
7149
|
+
const select = target.ownerDocument.createElement("select");
|
|
7150
|
+
select.className = "dtm-studio-preset";
|
|
7151
|
+
for (const [key, p] of Object.entries(INSTRUMENT_PRESETS)) {
|
|
7152
|
+
const opt = target.ownerDocument.createElement("option");
|
|
7153
|
+
opt.value = key;
|
|
7154
|
+
opt.textContent = p.displayName;
|
|
7155
|
+
select.appendChild(opt);
|
|
7156
|
+
}
|
|
7157
|
+
select.value = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
7158
|
+
target.appendChild(select);
|
|
7159
|
+
editorPresetSelects.set(target, select);
|
|
7160
|
+
select.addEventListener("change", async () => {
|
|
7161
|
+
daw.setInstrument(select.value);
|
|
7162
|
+
await loadPreset(select.value, trackIds);
|
|
7163
|
+
});
|
|
7164
|
+
}
|
|
7165
|
+
const daw = mountDAW(target, base);
|
|
7166
|
+
mountedEditors.push(daw);
|
|
7167
|
+
const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
7168
|
+
daw.setInstrument(presetKey);
|
|
7169
|
+
void loadPreset(presetKey, trackIds);
|
|
7170
|
+
return daw;
|
|
7171
|
+
};
|
|
7172
|
+
const mountPlayer = (target, mml, opts = {}) => {
|
|
7173
|
+
const meta = parseMML(mml, {}).meta ?? {};
|
|
7174
|
+
if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
|
|
7175
|
+
void loadPreset(meta.instrument);
|
|
7176
|
+
}
|
|
7177
|
+
const player = mountMmlPlayer(target, mml, {
|
|
7178
|
+
getAudioTime: () => audioCtx.currentTime,
|
|
7179
|
+
onResumeAudio: resumeAudio,
|
|
7180
|
+
onPlayNote: playPlayerNote,
|
|
7181
|
+
onPlayDrum: playDrum,
|
|
7182
|
+
singingVoices,
|
|
7183
|
+
...opts
|
|
7184
|
+
});
|
|
7185
|
+
mountedPlayers.push(player);
|
|
7186
|
+
return player;
|
|
7187
|
+
};
|
|
7188
|
+
const dispose = () => {
|
|
7189
|
+
for (const p of mountedPlayers) p.destroy();
|
|
7190
|
+
for (const d of mountedEditors) d.destroy();
|
|
7191
|
+
mountedPlayers.length = 0;
|
|
7192
|
+
mountedEditors.length = 0;
|
|
7193
|
+
void audioCtx.close();
|
|
7194
|
+
};
|
|
7195
|
+
return {
|
|
7196
|
+
audioContext: audioCtx,
|
|
7197
|
+
singingVoices,
|
|
7198
|
+
mountEditor,
|
|
7199
|
+
mountPlayer,
|
|
7200
|
+
loadPreset,
|
|
7201
|
+
dispose
|
|
7202
|
+
};
|
|
7203
|
+
};
|
|
6887
7204
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6888
7205
|
0 && (module.exports = {
|
|
6889
7206
|
DAW_CSS,
|
|
@@ -6917,6 +7234,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
6917
7234
|
buildNameToKeyMapping,
|
|
6918
7235
|
collectPitchTokens,
|
|
6919
7236
|
createAudioContext,
|
|
7237
|
+
createDtmStudio,
|
|
6920
7238
|
createKlattVoice,
|
|
6921
7239
|
createKoeVoice,
|
|
6922
7240
|
createLyricsConductor,
|
package/dist/index.mjs
CHANGED
|
@@ -6387,8 +6387,6 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6387
6387
|
if (useSynth) synthPlay(e);
|
|
6388
6388
|
},
|
|
6389
6389
|
onPlayDrum: (e) => {
|
|
6390
|
-
const em = emojiEls[0];
|
|
6391
|
-
if (em) jumpEmojiAt(em, e.when);
|
|
6392
6390
|
const velocity = e.velocity * (trackVolume / 100);
|
|
6393
6391
|
options.onPlayDrum?.({ ...e, velocity });
|
|
6394
6392
|
if (useSynth) drumSynth({ ...e, velocity });
|
|
@@ -6781,6 +6779,323 @@ var createPianoRoll = (options, handlers) => {
|
|
|
6781
6779
|
}
|
|
6782
6780
|
};
|
|
6783
6781
|
};
|
|
6782
|
+
|
|
6783
|
+
// src/studio.ts
|
|
6784
|
+
var DEFAULT_CDN = {
|
|
6785
|
+
soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs",
|
|
6786
|
+
soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs",
|
|
6787
|
+
soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs",
|
|
6788
|
+
parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs",
|
|
6789
|
+
parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs",
|
|
6790
|
+
midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm"
|
|
6791
|
+
};
|
|
6792
|
+
var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
|
|
6793
|
+
var TRACK_ROLES = ["melody", "submelody", "bass", "chord"];
|
|
6794
|
+
var resolveDefaultVoiceWorkerUrl = () => {
|
|
6795
|
+
try {
|
|
6796
|
+
return new URL("./voice-worker.js", import.meta.url).href;
|
|
6797
|
+
} catch {
|
|
6798
|
+
return void 0;
|
|
6799
|
+
}
|
|
6800
|
+
};
|
|
6801
|
+
var importFrom = async (url, name) => {
|
|
6802
|
+
const mod = await import(
|
|
6803
|
+
/* @vite-ignore */
|
|
6804
|
+
url
|
|
6805
|
+
);
|
|
6806
|
+
return mod[name] ?? mod.default;
|
|
6807
|
+
};
|
|
6808
|
+
var createDtmStudio = async (options = {}) => {
|
|
6809
|
+
const cdn = { ...DEFAULT_CDN, ...options.cdn };
|
|
6810
|
+
const features = {
|
|
6811
|
+
recorder: true,
|
|
6812
|
+
midi: true,
|
|
6813
|
+
chord: true,
|
|
6814
|
+
presetUI: true,
|
|
6815
|
+
...options.features
|
|
6816
|
+
};
|
|
6817
|
+
const audioCtx = options.audioContext ?? new AudioContext();
|
|
6818
|
+
const masterGain = audioCtx.createGain();
|
|
6819
|
+
masterGain.gain.value = options.masterVolume ?? 1;
|
|
6820
|
+
masterGain.connect(audioCtx.destination);
|
|
6821
|
+
const drumGain = audioCtx.createGain();
|
|
6822
|
+
drumGain.gain.value = options.drumVolume ?? 1;
|
|
6823
|
+
drumGain.connect(audioCtx.destination);
|
|
6824
|
+
const resumeAudio = () => {
|
|
6825
|
+
if (audioCtx.state === "suspended") void audioCtx.resume();
|
|
6826
|
+
};
|
|
6827
|
+
const eng = options.engines ?? {};
|
|
6828
|
+
const [SoundFont, SoundFont_drum, SoundFont_list] = await Promise.all([
|
|
6829
|
+
eng.SoundFont ?? importFrom(cdn.soundFont, "SoundFont"),
|
|
6830
|
+
eng.SoundFont_drum ?? importFrom(cdn.soundFontDrum, "SoundFont_drum"),
|
|
6831
|
+
eng.SoundFont_list ?? importFrom(cdn.soundFontList, "SoundFont_list")
|
|
6832
|
+
]);
|
|
6833
|
+
let parseChord = eng.parseChord;
|
|
6834
|
+
let parseChords = eng.parseChords;
|
|
6835
|
+
if (features.chord && (!parseChord || !parseChords)) {
|
|
6836
|
+
try {
|
|
6837
|
+
[parseChord, parseChords] = await Promise.all([
|
|
6838
|
+
parseChord ?? importFrom(
|
|
6839
|
+
cdn.parseChord,
|
|
6840
|
+
"parseChord"
|
|
6841
|
+
),
|
|
6842
|
+
parseChords ?? importFrom(
|
|
6843
|
+
cdn.parseChords,
|
|
6844
|
+
"parseChords"
|
|
6845
|
+
)
|
|
6846
|
+
]);
|
|
6847
|
+
} catch (e) {
|
|
6848
|
+
console.warn("[dtm] \u30B3\u30FC\u30C9\u89E3\u6790\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F", e);
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
let midiParser = null;
|
|
6852
|
+
let parseMidi;
|
|
6853
|
+
if (features.midi) {
|
|
6854
|
+
parseMidi = eng.parseMidi;
|
|
6855
|
+
if (!parseMidi) {
|
|
6856
|
+
const midiPromise = importFrom(
|
|
6857
|
+
cdn.midiParser,
|
|
6858
|
+
"default"
|
|
6859
|
+
).then((m) => {
|
|
6860
|
+
midiParser = m;
|
|
6861
|
+
}).catch((e) => console.warn("[dtm] midi-parser \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557", e));
|
|
6862
|
+
void midiPromise;
|
|
6863
|
+
parseMidi = (bytes) => {
|
|
6864
|
+
if (!midiParser) throw new Error("midi-parser not ready");
|
|
6865
|
+
return midiParser.parse(bytes);
|
|
6866
|
+
};
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
const voiceWorkerUrl = options.voiceWorkerUrl === null ? void 0 : options.voiceWorkerUrl ?? resolveDefaultVoiceWorkerUrl();
|
|
6870
|
+
const singingVoices = createSingingVoices(audioCtx, masterGain, {
|
|
6871
|
+
voiceWorkerUrl
|
|
6872
|
+
});
|
|
6873
|
+
const recorder = features.recorder ? setupRecorder(audioCtx, masterGain, drumGain) : null;
|
|
6874
|
+
const downloadWav = () => {
|
|
6875
|
+
if (!recorder) return;
|
|
6876
|
+
const recordedData = recorder.getRecordedData();
|
|
6877
|
+
const ch = recordedData.length;
|
|
6878
|
+
const len = recordedData[0].length;
|
|
6879
|
+
if (len === 0) return;
|
|
6880
|
+
const bufSize = recordedData[0][0].length;
|
|
6881
|
+
const wave = new Float32Array(ch * len * bufSize);
|
|
6882
|
+
let idx = 0;
|
|
6883
|
+
for (let i = 0; i < len; i++)
|
|
6884
|
+
for (let j = 0; j < bufSize; j++)
|
|
6885
|
+
for (let k = 0; k < ch; k++) wave[idx++] = recordedData[k][i][j];
|
|
6886
|
+
const sampleRate = audioCtx.sampleRate;
|
|
6887
|
+
const channels = 2;
|
|
6888
|
+
const bitRate = 16;
|
|
6889
|
+
const step = bitRate / 8;
|
|
6890
|
+
const blockSize = channels * step;
|
|
6891
|
+
const byteLen = wave.length * step;
|
|
6892
|
+
const view = new DataView(new ArrayBuffer(44 + byteLen));
|
|
6893
|
+
const ws = (off2, s) => {
|
|
6894
|
+
for (let i = 0; i < s.length; i++)
|
|
6895
|
+
view.setUint8(off2 + i, s.charCodeAt(i));
|
|
6896
|
+
};
|
|
6897
|
+
ws(0, "RIFF");
|
|
6898
|
+
view.setUint32(4, 32 + byteLen, true);
|
|
6899
|
+
ws(8, "WAVE");
|
|
6900
|
+
ws(12, "fmt ");
|
|
6901
|
+
view.setUint32(16, 16, true);
|
|
6902
|
+
view.setUint16(20, 1, true);
|
|
6903
|
+
view.setUint16(22, channels, true);
|
|
6904
|
+
view.setUint32(24, sampleRate, true);
|
|
6905
|
+
view.setUint32(28, sampleRate * blockSize, true);
|
|
6906
|
+
view.setUint16(32, blockSize, true);
|
|
6907
|
+
view.setUint16(34, bitRate, true);
|
|
6908
|
+
ws(36, "data");
|
|
6909
|
+
view.setUint32(40, byteLen, true);
|
|
6910
|
+
const clamp4 = (n, a2, b) => Math.max(a2, Math.min(b, n));
|
|
6911
|
+
let off = 44;
|
|
6912
|
+
for (let i = 0; i < wave.length; i++, off += step)
|
|
6913
|
+
view.setInt16(
|
|
6914
|
+
off,
|
|
6915
|
+
clamp4(Math.round(wave[i] * 32768), -32768, 32767),
|
|
6916
|
+
true
|
|
6917
|
+
);
|
|
6918
|
+
const blob = new Blob([view], { type: "audio/wav" });
|
|
6919
|
+
const url = URL.createObjectURL(blob);
|
|
6920
|
+
const a = document.createElement("a");
|
|
6921
|
+
a.href = url;
|
|
6922
|
+
a.download = "record.wav";
|
|
6923
|
+
a.click();
|
|
6924
|
+
URL.revokeObjectURL(url);
|
|
6925
|
+
};
|
|
6926
|
+
const onToggleRecord = recorder ? () => {
|
|
6927
|
+
if (recorder.isRecording()) {
|
|
6928
|
+
recorder.stopRecording();
|
|
6929
|
+
downloadWav();
|
|
6930
|
+
} else {
|
|
6931
|
+
recorder.clearRecordedData();
|
|
6932
|
+
recorder.startRecording();
|
|
6933
|
+
}
|
|
6934
|
+
} : void 0;
|
|
6935
|
+
const listReady = new Promise((resolve) => {
|
|
6936
|
+
SoundFont_list.init();
|
|
6937
|
+
SoundFont_list.onload(() => resolve());
|
|
6938
|
+
});
|
|
6939
|
+
const drumReady = (async () => {
|
|
6940
|
+
try {
|
|
6941
|
+
await SoundFont_drum.load({
|
|
6942
|
+
ctx: audioCtx,
|
|
6943
|
+
font: DRUM_FONT,
|
|
6944
|
+
id: "0",
|
|
6945
|
+
keys: Object.values(DRUM_KEYS)
|
|
6946
|
+
});
|
|
6947
|
+
} catch (e) {
|
|
6948
|
+
console.error("[dtm] \u30C9\u30E9\u30E0\u97F3\u6E90\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557", e);
|
|
6949
|
+
}
|
|
6950
|
+
})();
|
|
6951
|
+
let nameToKey = {};
|
|
6952
|
+
const soundFonts = /* @__PURE__ */ new Map();
|
|
6953
|
+
const loadedKeyByTrack = /* @__PURE__ */ new Map();
|
|
6954
|
+
const loadSoundFont = async (instrumentKey, trackId) => {
|
|
6955
|
+
if (loadedKeyByTrack.get(trackId) === instrumentKey) return;
|
|
6956
|
+
try {
|
|
6957
|
+
const fullName = `${instrumentKey}_${SOUNDFONT_NAME}`;
|
|
6958
|
+
soundFonts.set(
|
|
6959
|
+
trackId,
|
|
6960
|
+
await SoundFont.load({
|
|
6961
|
+
ctx: audioCtx,
|
|
6962
|
+
fontName: `_tone_${fullName}`,
|
|
6963
|
+
url: SoundFont.toURL(fullName)
|
|
6964
|
+
})
|
|
6965
|
+
);
|
|
6966
|
+
loadedKeyByTrack.set(trackId, instrumentKey);
|
|
6967
|
+
} catch (e) {
|
|
6968
|
+
console.error(`[dtm] \u697D\u5668 "${instrumentKey}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557`, e);
|
|
6969
|
+
}
|
|
6970
|
+
};
|
|
6971
|
+
const defaultPreset = options.defaultPreset ?? "retro_game";
|
|
6972
|
+
const instrumentNameFor = (preset, trackId) => preset[trackId] ?? preset.melody;
|
|
6973
|
+
const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES]) => {
|
|
6974
|
+
const preset = INSTRUMENT_PRESETS[presetKey];
|
|
6975
|
+
if (!preset) return;
|
|
6976
|
+
await listReady;
|
|
6977
|
+
await Promise.all(
|
|
6978
|
+
trackIds.map((trackId) => {
|
|
6979
|
+
const key = nameToKey[instrumentNameFor(preset, trackId)];
|
|
6980
|
+
return key ? loadSoundFont(key, trackId) : Promise.resolve();
|
|
6981
|
+
})
|
|
6982
|
+
);
|
|
6983
|
+
};
|
|
6984
|
+
await listReady;
|
|
6985
|
+
nameToKey = await buildNameToKeyMapping();
|
|
6986
|
+
await Promise.all([drumReady, loadPreset(defaultPreset)]);
|
|
6987
|
+
const playNote = (e) => {
|
|
6988
|
+
const sf = soundFonts.get(e.trackId);
|
|
6989
|
+
if (!sf) return;
|
|
6990
|
+
sf.play({
|
|
6991
|
+
ctx: audioCtx,
|
|
6992
|
+
destination: masterGain,
|
|
6993
|
+
pitch: e.pitch,
|
|
6994
|
+
volume: e.volume,
|
|
6995
|
+
when: e.when,
|
|
6996
|
+
duration: e.duration
|
|
6997
|
+
});
|
|
6998
|
+
};
|
|
6999
|
+
const playDrum = (e) => {
|
|
7000
|
+
if (!SoundFont_drum.font) return;
|
|
7001
|
+
SoundFont_drum.play({
|
|
7002
|
+
ctx: audioCtx,
|
|
7003
|
+
destination: drumGain,
|
|
7004
|
+
pitch: e.pitch,
|
|
7005
|
+
volume: e.velocity,
|
|
7006
|
+
when: e.when,
|
|
7007
|
+
duration: e.duration
|
|
7008
|
+
});
|
|
7009
|
+
};
|
|
7010
|
+
const sfForPlayerTrack = (trackId) => soundFonts.get(TRACK_ROLES[Number(trackId)] ?? "") ?? soundFonts.get(`t${trackId}`);
|
|
7011
|
+
const playPlayerNote = (e) => {
|
|
7012
|
+
const sf = sfForPlayerTrack(e.trackId);
|
|
7013
|
+
if (!sf) return;
|
|
7014
|
+
sf.play({
|
|
7015
|
+
ctx: audioCtx,
|
|
7016
|
+
destination: masterGain,
|
|
7017
|
+
pitch: e.pitch,
|
|
7018
|
+
volume: e.volume,
|
|
7019
|
+
when: e.when,
|
|
7020
|
+
duration: e.duration
|
|
7021
|
+
});
|
|
7022
|
+
};
|
|
7023
|
+
const editorPresetSelects = /* @__PURE__ */ new WeakMap();
|
|
7024
|
+
const mountedEditors = [];
|
|
7025
|
+
const mountedPlayers = [];
|
|
7026
|
+
const mountEditor = (target, opts = {}) => {
|
|
7027
|
+
const { preset, presetUI, ...dawOverrides } = opts;
|
|
7028
|
+
const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
|
|
7029
|
+
const trackIds = tracks.map((t) => t.id);
|
|
7030
|
+
const base = {
|
|
7031
|
+
getAudioTime: () => audioCtx.currentTime,
|
|
7032
|
+
onResumeAudio: resumeAudio,
|
|
7033
|
+
onPlayNote: playNote,
|
|
7034
|
+
onPlayDrum: playDrum,
|
|
7035
|
+
singingVoices,
|
|
7036
|
+
parseChord,
|
|
7037
|
+
parseChords,
|
|
7038
|
+
parseMidi,
|
|
7039
|
+
onToggleRecord,
|
|
7040
|
+
...dawOverrides
|
|
7041
|
+
};
|
|
7042
|
+
const wantPresetUI = presetUI ?? features.presetUI;
|
|
7043
|
+
if (wantPresetUI) {
|
|
7044
|
+
const select = target.ownerDocument.createElement("select");
|
|
7045
|
+
select.className = "dtm-studio-preset";
|
|
7046
|
+
for (const [key, p] of Object.entries(INSTRUMENT_PRESETS)) {
|
|
7047
|
+
const opt = target.ownerDocument.createElement("option");
|
|
7048
|
+
opt.value = key;
|
|
7049
|
+
opt.textContent = p.displayName;
|
|
7050
|
+
select.appendChild(opt);
|
|
7051
|
+
}
|
|
7052
|
+
select.value = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
7053
|
+
target.appendChild(select);
|
|
7054
|
+
editorPresetSelects.set(target, select);
|
|
7055
|
+
select.addEventListener("change", async () => {
|
|
7056
|
+
daw.setInstrument(select.value);
|
|
7057
|
+
await loadPreset(select.value, trackIds);
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
const daw = mountDAW(target, base);
|
|
7061
|
+
mountedEditors.push(daw);
|
|
7062
|
+
const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
7063
|
+
daw.setInstrument(presetKey);
|
|
7064
|
+
void loadPreset(presetKey, trackIds);
|
|
7065
|
+
return daw;
|
|
7066
|
+
};
|
|
7067
|
+
const mountPlayer = (target, mml, opts = {}) => {
|
|
7068
|
+
const meta = parseMML(mml, {}).meta ?? {};
|
|
7069
|
+
if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
|
|
7070
|
+
void loadPreset(meta.instrument);
|
|
7071
|
+
}
|
|
7072
|
+
const player = mountMmlPlayer(target, mml, {
|
|
7073
|
+
getAudioTime: () => audioCtx.currentTime,
|
|
7074
|
+
onResumeAudio: resumeAudio,
|
|
7075
|
+
onPlayNote: playPlayerNote,
|
|
7076
|
+
onPlayDrum: playDrum,
|
|
7077
|
+
singingVoices,
|
|
7078
|
+
...opts
|
|
7079
|
+
});
|
|
7080
|
+
mountedPlayers.push(player);
|
|
7081
|
+
return player;
|
|
7082
|
+
};
|
|
7083
|
+
const dispose = () => {
|
|
7084
|
+
for (const p of mountedPlayers) p.destroy();
|
|
7085
|
+
for (const d of mountedEditors) d.destroy();
|
|
7086
|
+
mountedPlayers.length = 0;
|
|
7087
|
+
mountedEditors.length = 0;
|
|
7088
|
+
void audioCtx.close();
|
|
7089
|
+
};
|
|
7090
|
+
return {
|
|
7091
|
+
audioContext: audioCtx,
|
|
7092
|
+
singingVoices,
|
|
7093
|
+
mountEditor,
|
|
7094
|
+
mountPlayer,
|
|
7095
|
+
loadPreset,
|
|
7096
|
+
dispose
|
|
7097
|
+
};
|
|
7098
|
+
};
|
|
6784
7099
|
export {
|
|
6785
7100
|
DAW_CSS,
|
|
6786
7101
|
DEFAULT_BPM,
|
|
@@ -6813,6 +7128,7 @@ export {
|
|
|
6813
7128
|
buildNameToKeyMapping,
|
|
6814
7129
|
collectPitchTokens,
|
|
6815
7130
|
createAudioContext,
|
|
7131
|
+
createDtmStudio,
|
|
6816
7132
|
createKlattVoice,
|
|
6817
7133
|
createKoeVoice,
|
|
6818
7134
|
createLyricsConductor,
|
package/package.json
CHANGED