feat: streamline arbitrary audio sources
This commit is contained in:
@@ -27,7 +27,7 @@ const byId = (id) => {
|
||||
|
||||
const elements = Object.fromEntries(
|
||||
[
|
||||
"preview-button", "wav-button", "export-button", "image-input", "remove-image-button", "audio-input", "scene-input",
|
||||
"preview-button", "wav-button", "export-button", "image-input", "remove-image-button", "audio-input", "audio-source-input", "scene-input",
|
||||
"scene-canvas", "canvas-empty", "canvas-badge", "workspace-title", "source-list", "add-source-button",
|
||||
"asset-list", "scene-name", "scene-id", "scene-duration", "sample-rate", "speed-of-sound", "scene-seed",
|
||||
"mix-normalization", "target-peak", "plane-x-min", "plane-x-max", "plane-y-min", "plane-y-max",
|
||||
@@ -83,6 +83,21 @@ function selectedSource() {
|
||||
return state.scene?.sources.find((source) => source.id === state.selectedSourceId) ?? null;
|
||||
}
|
||||
|
||||
function sourceNameForAudioFile(file, fallback) {
|
||||
const stem = file.name.replace(/\.[^.]+$/, "").trim();
|
||||
return (stem || fallback).slice(0, 120);
|
||||
}
|
||||
|
||||
function createAssetBackedSource(descriptor, file) {
|
||||
if (state.scene.sources.length >= 32) return null;
|
||||
const source = createSource(state.scene);
|
||||
source.name = sourceNameForAudioFile(file, source.name);
|
||||
changeSignalType(source, "asset", descriptor.id);
|
||||
state.scene.sources.push(source);
|
||||
state.selectedSourceId = source.id;
|
||||
return source;
|
||||
}
|
||||
|
||||
function numberFrom(element) {
|
||||
return element.valueAsNumber;
|
||||
}
|
||||
@@ -598,6 +613,42 @@ function bindSourceControls() {
|
||||
bindInput("source-loop", (input) => { selectedSource().signal.loop = input.checked; }, "change");
|
||||
}
|
||||
|
||||
async function handleAudioFiles(files, { createSources = false } = {}) {
|
||||
const epoch = state.assetEpoch;
|
||||
let attachedCount = 0;
|
||||
let createdCount = 0;
|
||||
let lastError = null;
|
||||
for (const file of files) {
|
||||
if (epoch !== state.assetEpoch) return;
|
||||
if (createSources && state.scene.sources.length >= 32) {
|
||||
lastError = new Error("음원은 최대 32개까지 추가할 수 있습니다.");
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const descriptor = await attachFile(file, "audio", epoch);
|
||||
if (!descriptor) return;
|
||||
attachedCount += 1;
|
||||
if (createSources && createAssetBackedSource(descriptor, file)) createdCount += 1;
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
}
|
||||
if (epoch !== state.assetEpoch) return;
|
||||
if (attachedCount > 0) {
|
||||
renderAssetList();
|
||||
renderSourceCards();
|
||||
syncSourceEditor();
|
||||
syncAmbientEditor();
|
||||
renderCanvas();
|
||||
const status = createSources
|
||||
? `${createdCount}개 오디오 파일 음원 추가됨`
|
||||
: `${attachedCount}개 원본 오디오 연결됨`;
|
||||
markChanged(lastError ? `${status} · ${lastError.message}` : status);
|
||||
} else if (lastError) {
|
||||
setStatus(lastError.message);
|
||||
}
|
||||
}
|
||||
|
||||
function createAmbientSignal(type) {
|
||||
if (type === "white" || type === "pink") return { type: "noise", color: type, seedOffset: 0 };
|
||||
const assetId = state.scene.assets.find((asset) => asset.kind === "audio")?.id ?? "";
|
||||
@@ -704,22 +755,12 @@ function bindFileControls() {
|
||||
elements["audio-input"].addEventListener("change", async (event) => {
|
||||
const files = [...event.target.files];
|
||||
event.target.value = "";
|
||||
const epoch = state.assetEpoch;
|
||||
for (const file of files) {
|
||||
if (epoch !== state.assetEpoch) return;
|
||||
try {
|
||||
await attachFile(file, "audio", epoch);
|
||||
} catch (error) {
|
||||
setStatus(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
if (epoch !== state.assetEpoch) return;
|
||||
if (files.length > 0) {
|
||||
renderAssetList();
|
||||
syncSourceEditor();
|
||||
syncAmbientEditor();
|
||||
markChanged(`${files.length}개 오디오 처리 완료`);
|
||||
}
|
||||
if (files.length > 0) await handleAudioFiles(files);
|
||||
});
|
||||
elements["audio-source-input"].addEventListener("change", async (event) => {
|
||||
const files = [...event.target.files];
|
||||
event.target.value = "";
|
||||
if (files.length > 0) await handleAudioFiles(files, { createSources: true });
|
||||
});
|
||||
elements["scene-input"].addEventListener("change", async (event) => {
|
||||
const [file] = event.target.files;
|
||||
|
||||
Reference in New Issue
Block a user