feat(audio): audio system

This commit is contained in:
2026-02-24 17:34:01 +01:00
parent 8a67577d36
commit ef00bd06bb
3 changed files with 74 additions and 3 deletions

View File

@@ -21,6 +21,10 @@ type ModelTree = {
[key: string]: string | ModelTree;
};
type AudioTree = {
[key: string]: string | AudioTree;
};
type ImageData = {
path: string;
buffer: Buffer;
@@ -30,6 +34,7 @@ type ImageData = {
const IMAGE_EXTENSIONS = [".png", ".webp"];
const MODEL_EXTENSIONS = [".glb"];
const AUDIO_EXTENSIONS = [".mp3", ".ogg", ".wav"];
const MAX_WIDTH = 2048;
const toCamelCase = (str: string) => {
@@ -71,6 +76,7 @@ export default defineNuxtModule({
const rootDir = nuxt.options.rootDir;
const imagesDir = join(rootDir, "app/assets/nds/images");
const modelsDir = join(rootDir, "public/nds/models");
const audioDir = join(rootDir, "public/nds/audios");
const templateFile = join(rootDir, "app/composables/useAssets.ts.in");
const outputFile = join(rootDir, "app/composables/useAssets.ts");
const atlasOutputPath = join(rootDir, "public/nds/atlas.webp");
@@ -214,10 +220,46 @@ ${sp} }`;
return `{\n${lines.join(",\n")},\n${sp}}`;
};
const buildAudioTree = (audioPaths: string[]): AudioTree => {
const tree: AudioTree = {};
for (const path of audioPaths) {
const parts = relative(audioDir, path).split("/");
const fileName = parse(parts.at(-1)!).name;
let node = tree;
for (const part of parts.slice(0, -1)) {
const key = toCamelCase(part);
node[key] ??= {};
node = node[key] as AudioTree;
}
node[toCamelCase(fileName)] = `/nds/audios/${relative(audioDir, path)}`;
}
return tree;
};
const generateAudioCode = (tree: AudioTree, indent = 0): string => {
const entries = Object.entries(tree);
if (!entries.length) return "{}";
const sp = " ".repeat(indent);
const lines = entries.map(([key, value]) => {
if (typeof value === "string") {
return `${sp} ${key}: createAudio("${value}")`;
}
return `${sp} ${key}: ${generateAudioCode(value, indent + 1)}`;
});
return `{\n${lines.join(",\n")}\n${sp}}`;
};
const generateAssets = async () => {
try {
const imagePaths = await scanByExt(imagesDir, IMAGE_EXTENSIONS);
const modelPaths = await scanByExt(modelsDir, MODEL_EXTENSIONS);
const audioPaths = await scanByExt(audioDir, AUDIO_EXTENSIONS);
const totalAssets = (imagePaths.length > 0 ? 1 : 0) + modelPaths.length;
@@ -271,16 +313,20 @@ ${sp} }`;
const modelTree = buildModelTree(modelPaths);
const modelCode = generateModelCode(modelTree);
const audioTree = buildAudioTree(audioPaths);
const audioCode = generateAudioCode(audioTree);
const template = await readFile(templateFile, "utf-8");
const code = template
.replace("{{TOTAL}}", totalAssets.toString())
.replace("{{ATLAS_HASH}}", atlasHash)
.replace("{{IMAGES}}", imageCode)
.replace("{{MODELS}}", modelCode);
.replace("{{MODELS}}", modelCode)
.replace("{{AUDIO}}", audioCode);
await writeFile(outputFile, code, "utf-8");
logger.success(
`Generated assets with ${imagePaths.length} images (${atlasWidth}x${atlasHeight}) and ${modelPaths.length} models`,
`Generated assets with ${imagePaths.length} images (${atlasWidth}x${atlasHeight}), ${modelPaths.length} models and ${audioPaths.length} audio files`,
);
} catch (error) {
logger.error("Error generating assets:", error);
@@ -308,6 +354,7 @@ ${sp} }`;
nuxt.hook("ready", () => {
watchAndRegenerate(imagesDir, IMAGE_EXTENSIONS, "image");
watchAndRegenerate(audioDir, AUDIO_EXTENSIONS, "audio");
watchAndRegenerate(modelsDir, MODEL_EXTENSIONS, "model");
watch(templateFile, () => {
logger.info("Template file changed");