feat: add @hyperframes/sfx package with typed SFX catalog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Miguel Ángel
2026-03-24 14:05:55 -04:00
co-authored by Claude Opus 4.6
parent 61c5257402
commit 740878764c
17 changed files with 776 additions and 0 deletions
+6
View File
@@ -11,9 +11,15 @@ const config: KnipConfig = {
"packages/engine": {
entry: ["src/index.ts"],
},
"packages/fonts": {
entry: ["src/index.ts", "src/catalog.ts"],
},
"packages/producer": {
entry: ["src/index.ts", "src/server.ts"],
},
"packages/sfx": {
entry: ["src/index.ts"],
},
"packages/studio": {
entry: ["src/index.ts", "src/styles/tailwind-preset.ts"],
},
View File
+73
View File
@@ -0,0 +1,73 @@
{
"name": "@hyperframes/sfx",
"version": "0.1.2",
"description": "Typed catalog of CC0 sound effects for hyperframes agents",
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
},
"./transitions": {
"import": "./src/transitions.ts",
"types": "./src/transitions.ts"
},
"./ui": {
"import": "./src/ui.ts",
"types": "./src/ui.ts"
},
"./cinematic": {
"import": "./src/cinematic.ts",
"types": "./src/cinematic.ts"
},
"./ambient": {
"import": "./src/ambient.ts",
"types": "./src/ambient.ts"
},
"./musical": {
"import": "./src/musical.ts",
"types": "./src/musical.ts"
}
},
"publishConfig": {
"access": "public",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./transitions": {
"import": "./dist/transitions.js",
"types": "./dist/transitions.d.ts"
},
"./ui": {
"import": "./dist/ui.js",
"types": "./dist/ui.d.ts"
},
"./cinematic": {
"import": "./dist/cinematic.js",
"types": "./dist/cinematic.d.ts"
},
"./ambient": {
"import": "./dist/ambient.js",
"types": "./dist/ambient.d.ts"
},
"./musical": {
"import": "./dist/musical.js",
"types": "./dist/musical.d.ts"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"scripts": {
"build": "tsc",
"test": "vitest run"
},
"devDependencies": {
"tsx": "^4.21.0",
"typescript": "^5.0.0"
}
}
+102
View File
@@ -0,0 +1,102 @@
import { SFX_BASE_URL } from './constants.js';
import type { SfxEntry } from './types.js';
export const applause: SfxEntry = {
name: 'applause',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/applause.mp3`,
duration: 3.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['clapping', 'crowd', 'celebration'],
};
export const crowdCheer: SfxEntry = {
name: 'crowdCheer',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/crowdCheer.mp3`,
duration: 3.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['cheering', 'excitement', 'crowd'],
};
export const typingKeyboard: SfxEntry = {
name: 'typingKeyboard',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/typingKeyboard.mp3`,
duration: 5.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['keyboard', 'office', 'work'],
};
export const windGentle: SfxEntry = {
name: 'windGentle',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/windGentle.mp3`,
duration: 8.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['breeze', 'nature', 'calm'],
};
export const rainLight: SfxEntry = {
name: 'rainLight',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/rainLight.mp3`,
duration: 10.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['rain', 'drizzle', 'relaxing'],
};
export const oceanWaves: SfxEntry = {
name: 'oceanWaves',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/oceanWaves.mp3`,
duration: 10.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['sea', 'waves', 'beach'],
};
export const birds: SfxEntry = {
name: 'birds',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/birds.mp3`,
duration: 8.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['chirping', 'nature', 'morning'],
};
export const cityTraffic: SfxEntry = {
name: 'cityTraffic',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/cityTraffic.mp3`,
duration: 10.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['urban', 'cars', 'street'],
};
export const cafeMurmur: SfxEntry = {
name: 'cafeMurmur',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/cafeMurmur.mp3`,
duration: 10.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['coffee', 'chatter', 'cozy'],
};
export const fireCrackle: SfxEntry = {
name: 'fireCrackle',
category: 'ambient',
url: `${SFX_BASE_URL}/ambient/fireCrackle.mp3`,
duration: 10.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['fireplace', 'warm', 'crackling'],
};
+26
View File
@@ -0,0 +1,26 @@
import * as transitions from './transitions.js';
import * as ui from './ui.js';
import * as cinematic from './cinematic.js';
import * as ambient from './ambient.js';
import * as musical from './musical.js';
import type { SfxEntry, SfxCategory } from './types.js';
export const catalog: readonly SfxEntry[] = [
...Object.values(transitions),
...Object.values(ui),
...Object.values(cinematic),
...Object.values(ambient),
...Object.values(musical),
];
export function findSfx(name: string): SfxEntry | undefined {
return catalog.find((s) => s.name === name);
}
export function findSfxByCategory(category: SfxCategory): readonly SfxEntry[] {
return catalog.filter((s) => s.category === category);
}
export function resolveSfxUrl(name: string): string | undefined {
return findSfx(name)?.url;
}
+152
View File
@@ -0,0 +1,152 @@
import { SFX_BASE_URL } from './constants.js';
import type { SfxEntry } from './types.js';
export const impactHeavy: SfxEntry = {
name: 'impactHeavy',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/impactHeavy.mp3`,
duration: 1.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['heavy', 'dramatic', 'hit'],
};
export const impactLight: SfxEntry = {
name: 'impactLight',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/impactLight.mp3`,
duration: 0.8,
format: 'mp3',
license: 'CC0-1.0',
tags: ['light', 'subtle', 'tap'],
};
export const riserSlow: SfxEntry = {
name: 'riserSlow',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/riserSlow.mp3`,
duration: 3.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['buildup', 'tension', 'ascending'],
};
export const riserFast: SfxEntry = {
name: 'riserFast',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/riserFast.mp3`,
duration: 1.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['buildup', 'quick', 'ascending'],
};
export const hit: SfxEntry = {
name: 'hit',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/hit.mp3`,
duration: 0.6,
format: 'mp3',
license: 'CC0-1.0',
tags: ['impact', 'punch', 'accent'],
};
export const boom: SfxEntry = {
name: 'boom',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/boom.mp3`,
duration: 2.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['explosion', 'deep', 'rumble'],
};
export const drumRoll: SfxEntry = {
name: 'drumRoll',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/drumRoll.mp3`,
duration: 3.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['anticipation', 'buildup', 'percussion'],
};
export const tensionBuild: SfxEntry = {
name: 'tensionBuild',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/tensionBuild.mp3`,
duration: 4.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['suspense', 'rising', 'dramatic'],
};
export const reveal: SfxEntry = {
name: 'reveal',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/reveal.mp3`,
duration: 1.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['unveil', 'surprise', 'magical'],
};
export const stinger: SfxEntry = {
name: 'stinger',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/stinger.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['accent', 'sharp', 'dramatic'],
};
export const shimmer: SfxEntry = {
name: 'shimmer',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/shimmer.mp3`,
duration: 1.2,
format: 'mp3',
license: 'CC0-1.0',
tags: ['sparkle', 'ethereal', 'bright'],
};
export const deepBass: SfxEntry = {
name: 'deepBass',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/deepBass.mp3`,
duration: 2.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['low', 'sub', 'rumble'],
};
export const epicWhoosh: SfxEntry = {
name: 'epicWhoosh',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/epicWhoosh.mp3`,
duration: 1.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['grand', 'sweep', 'movement'],
};
export const brassHit: SfxEntry = {
name: 'brassHit',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/brassHit.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['horn', 'powerful', 'accent'],
};
export const orchestraHit: SfxEntry = {
name: 'orchestraHit',
category: 'cinematic',
url: `${SFX_BASE_URL}/cinematic/orchestraHit.mp3`,
duration: 1.2,
format: 'mp3',
license: 'CC0-1.0',
tags: ['orchestra', 'full', 'dramatic'],
};
+4
View File
@@ -0,0 +1,4 @@
// Base URL for SFX assets. Uses jsdelivr CDN which auto-serves npm package files.
// Override with SFX_BASE_URL env var for self-hosted setups.
export const SFX_BASE_URL =
process.env.SFX_BASE_URL ?? 'https://cdn.jsdelivr.net/npm/@hyperframes/sfx@0.1.2/assets';
+8
View File
@@ -0,0 +1,8 @@
export type { SfxEntry, SfxCategory } from './types.js';
export { catalog, findSfx, findSfxByCategory, resolveSfxUrl } from './catalog.js';
export { SFX_BASE_URL } from './constants.js';
export * as transitions from './transitions.js';
export * as ui from './ui.js';
export * as cinematic from './cinematic.js';
export * as ambient from './ambient.js';
export * as musical from './musical.js';
+102
View File
@@ -0,0 +1,102 @@
import { SFX_BASE_URL } from './constants.js';
import type { SfxEntry } from './types.js';
export const pianoChord: SfxEntry = {
name: 'pianoChord',
category: 'musical',
url: `${SFX_BASE_URL}/musical/pianoChord.mp3`,
duration: 2.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['piano', 'chord', 'elegant'],
};
export const guitarStrum: SfxEntry = {
name: 'guitarStrum',
category: 'musical',
url: `${SFX_BASE_URL}/musical/guitarStrum.mp3`,
duration: 1.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['guitar', 'acoustic', 'strum'],
};
export const bellChime: SfxEntry = {
name: 'bellChime',
category: 'musical',
url: `${SFX_BASE_URL}/musical/bellChime.mp3`,
duration: 2.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['bell', 'chime', 'bright'],
};
export const harpGliss: SfxEntry = {
name: 'harpGliss',
category: 'musical',
url: `${SFX_BASE_URL}/musical/harpGliss.mp3`,
duration: 2.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['harp', 'glissando', 'dreamy'],
};
export const synthPad: SfxEntry = {
name: 'synthPad',
category: 'musical',
url: `${SFX_BASE_URL}/musical/synthPad.mp3`,
duration: 4.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['synth', 'pad', 'atmospheric'],
};
export const bassDrop: SfxEntry = {
name: 'bassDrop',
category: 'musical',
url: `${SFX_BASE_URL}/musical/bassDrop.mp3`,
duration: 1.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['bass', 'drop', 'electronic'],
};
export const vinylScratch: SfxEntry = {
name: 'vinylScratch',
category: 'musical',
url: `${SFX_BASE_URL}/musical/vinylScratch.mp3`,
duration: 0.8,
format: 'mp3',
license: 'CC0-1.0',
tags: ['vinyl', 'scratch', 'dj'],
};
export const countdownBeep: SfxEntry = {
name: 'countdownBeep',
category: 'musical',
url: `${SFX_BASE_URL}/musical/countdownBeep.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['countdown', 'beep', 'timer'],
};
export const ding: SfxEntry = {
name: 'ding',
category: 'musical',
url: `${SFX_BASE_URL}/musical/ding.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['ding', 'alert', 'clear'],
};
export const gong: SfxEntry = {
name: 'gong',
category: 'musical',
url: `${SFX_BASE_URL}/musical/gong.mp3`,
duration: 4.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['gong', 'resonant', 'deep'],
};
+152
View File
@@ -0,0 +1,152 @@
import { SFX_BASE_URL } from './constants.js';
import type { SfxEntry } from './types.js';
export const swoosh: SfxEntry = {
name: 'swoosh',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/swoosh.mp3`,
duration: 0.8,
format: 'mp3',
license: 'CC0-1.0',
tags: ['fast', 'movement', 'slide'],
};
export const whooshUp: SfxEntry = {
name: 'whooshUp',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/whooshUp.mp3`,
duration: 0.6,
format: 'mp3',
license: 'CC0-1.0',
tags: ['rising', 'upward', 'movement'],
};
export const whooshDown: SfxEntry = {
name: 'whooshDown',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/whooshDown.mp3`,
duration: 0.6,
format: 'mp3',
license: 'CC0-1.0',
tags: ['falling', 'downward', 'movement'],
};
export const slideIn: SfxEntry = {
name: 'slideIn',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/slideIn.mp3`,
duration: 0.7,
format: 'mp3',
license: 'CC0-1.0',
tags: ['enter', 'smooth', 'slide'],
};
export const slideOut: SfxEntry = {
name: 'slideOut',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/slideOut.mp3`,
duration: 0.7,
format: 'mp3',
license: 'CC0-1.0',
tags: ['exit', 'smooth', 'slide'],
};
export const fadeIn: SfxEntry = {
name: 'fadeIn',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/fadeIn.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['gentle', 'appear', 'soft'],
};
export const fadeOut: SfxEntry = {
name: 'fadeOut',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/fadeOut.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['gentle', 'disappear', 'soft'],
};
export const swipe: SfxEntry = {
name: 'swipe',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/swipe.mp3`,
duration: 0.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['quick', 'lateral', 'movement'],
};
export const snap: SfxEntry = {
name: 'snap',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/snap.mp3`,
duration: 0.3,
format: 'mp3',
license: 'CC0-1.0',
tags: ['instant', 'crisp', 'sharp'],
};
export const popTransition: SfxEntry = {
name: 'popTransition',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/popTransition.mp3`,
duration: 0.4,
format: 'mp3',
license: 'CC0-1.0',
tags: ['pop', 'bounce', 'playful'],
};
export const glitch: SfxEntry = {
name: 'glitch',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/glitch.mp3`,
duration: 0.8,
format: 'mp3',
license: 'CC0-1.0',
tags: ['digital', 'distortion', 'error'],
};
export const pageTurn: SfxEntry = {
name: 'pageTurn',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/pageTurn.mp3`,
duration: 0.9,
format: 'mp3',
license: 'CC0-1.0',
tags: ['paper', 'flip', 'organic'],
};
export const zip: SfxEntry = {
name: 'zip',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/zip.mp3`,
duration: 0.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['fast', 'zip', 'quick'],
};
export const tapeStop: SfxEntry = {
name: 'tapeStop',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/tapeStop.mp3`,
duration: 1.2,
format: 'mp3',
license: 'CC0-1.0',
tags: ['retro', 'slowdown', 'analog'],
};
export const reverse: SfxEntry = {
name: 'reverse',
category: 'transitions',
url: `${SFX_BASE_URL}/transitions/reverse.mp3`,
duration: 1.0,
format: 'mp3',
license: 'CC0-1.0',
tags: ['rewind', 'backward', 'retro'],
};
+11
View File
@@ -0,0 +1,11 @@
export type SfxCategory = 'transitions' | 'ui' | 'cinematic' | 'ambient' | 'musical';
export type SfxEntry = {
readonly name: string;
readonly category: SfxCategory;
readonly url: string;
readonly duration: number; // seconds
readonly format: 'mp3';
readonly license: 'CC0-1.0';
readonly tags: readonly string[];
};
+122
View File
@@ -0,0 +1,122 @@
import { SFX_BASE_URL } from './constants.js';
import type { SfxEntry } from './types.js';
export const click: SfxEntry = {
name: 'click',
category: 'ui',
url: `${SFX_BASE_URL}/ui/click.mp3`,
duration: 0.2,
format: 'mp3',
license: 'CC0-1.0',
tags: ['button', 'press', 'interface'],
};
export const tap: SfxEntry = {
name: 'tap',
category: 'ui',
url: `${SFX_BASE_URL}/ui/tap.mp3`,
duration: 0.15,
format: 'mp3',
license: 'CC0-1.0',
tags: ['touch', 'mobile', 'light'],
};
export const success: SfxEntry = {
name: 'success',
category: 'ui',
url: `${SFX_BASE_URL}/ui/success.mp3`,
duration: 0.8,
format: 'mp3',
license: 'CC0-1.0',
tags: ['complete', 'positive', 'achievement'],
};
export const error: SfxEntry = {
name: 'error',
category: 'ui',
url: `${SFX_BASE_URL}/ui/error.mp3`,
duration: 0.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['fail', 'negative', 'warning'],
};
export const notification: SfxEntry = {
name: 'notification',
category: 'ui',
url: `${SFX_BASE_URL}/ui/notification.mp3`,
duration: 0.6,
format: 'mp3',
license: 'CC0-1.0',
tags: ['alert', 'message', 'ping'],
};
export const pop: SfxEntry = {
name: 'pop',
category: 'ui',
url: `${SFX_BASE_URL}/ui/pop.mp3`,
duration: 0.3,
format: 'mp3',
license: 'CC0-1.0',
tags: ['bubble', 'playful', 'light'],
};
export const toggle: SfxEntry = {
name: 'toggle',
category: 'ui',
url: `${SFX_BASE_URL}/ui/toggle.mp3`,
duration: 0.2,
format: 'mp3',
license: 'CC0-1.0',
tags: ['switch', 'on-off', 'flip'],
};
export const confirm: SfxEntry = {
name: 'confirm',
category: 'ui',
url: `${SFX_BASE_URL}/ui/confirm.mp3`,
duration: 0.5,
format: 'mp3',
license: 'CC0-1.0',
tags: ['approve', 'accept', 'done'],
};
export const cancel: SfxEntry = {
name: 'cancel',
category: 'ui',
url: `${SFX_BASE_URL}/ui/cancel.mp3`,
duration: 0.4,
format: 'mp3',
license: 'CC0-1.0',
tags: ['dismiss', 'close', 'reject'],
};
export const hover: SfxEntry = {
name: 'hover',
category: 'ui',
url: `${SFX_BASE_URL}/ui/hover.mp3`,
duration: 0.15,
format: 'mp3',
license: 'CC0-1.0',
tags: ['mouse', 'focus', 'subtle'],
};
export const select: SfxEntry = {
name: 'select',
category: 'ui',
url: `${SFX_BASE_URL}/ui/select.mp3`,
duration: 0.25,
format: 'mp3',
license: 'CC0-1.0',
tags: ['choose', 'pick', 'highlight'],
};
export const typing: SfxEntry = {
name: 'typing',
category: 'ui',
url: `${SFX_BASE_URL}/ui/typing.mp3`,
duration: 0.1,
format: 'mp3',
license: 'CC0-1.0',
tags: ['keyboard', 'input', 'keystroke'],
};
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
}