@ai-rpg-engine/audio-director 2.0.0
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/director.d.ts +27 -0
- package/dist/director.d.ts.map +1 -0
- package/dist/director.js +94 -0
- package/dist/director.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/scheduler.d.ts +13 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +95 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { NarrationPlan } from '@ai-rpg-engine/presentation';
|
|
2
|
+
import type { AudioCommand, AudioDomain, AudioDirectorConfig, DuckingRule } from './types.js';
|
|
3
|
+
/** Deterministic audio cue scheduling engine. */
|
|
4
|
+
export declare class AudioDirector {
|
|
5
|
+
private cooldowns;
|
|
6
|
+
private activeLayers;
|
|
7
|
+
private duckingRules;
|
|
8
|
+
private domainPriorities;
|
|
9
|
+
private defaultCooldownMs;
|
|
10
|
+
constructor(config?: AudioDirectorConfig);
|
|
11
|
+
/** Convert a NarrationPlan into sequenced AudioCommands, applying cooldowns and ducking. */
|
|
12
|
+
schedule(plan: NarrationPlan): AudioCommand[];
|
|
13
|
+
/** Check if a resource is currently on cooldown. */
|
|
14
|
+
isOnCooldown(resourceId: string): boolean;
|
|
15
|
+
/** Register a ducking rule. */
|
|
16
|
+
addDuckingRule(rule: DuckingRule): void;
|
|
17
|
+
/** Get currently active audio layers. */
|
|
18
|
+
getActiveLayers(): Map<string, {
|
|
19
|
+
domain: AudioDomain;
|
|
20
|
+
resourceId: string;
|
|
21
|
+
}>;
|
|
22
|
+
/** Clear all cooldowns (e.g. on scene change). */
|
|
23
|
+
clearCooldowns(): void;
|
|
24
|
+
/** Build ducking commands based on active triggers. */
|
|
25
|
+
private buildDuckingCommands;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=director.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"director.d.ts","sourceRoot":"","sources":["../src/director.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,mBAAmB,EAEnB,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,iDAAiD;AACjD,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,YAAY,CAAkE;IACtF,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,MAAM,CAAC,EAAE,mBAAmB;IAMxC,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,EAAE;IA2C7C,oDAAoD;IACpD,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAMzC,+BAA+B;IAC/B,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAIvC,yCAAyC;IACzC,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAI3E,kDAAkD;IAClD,cAAc,IAAI,IAAI;IAItB,uDAAuD;IACvD,OAAO,CAAC,oBAAoB;CAmB7B"}
|
package/dist/director.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Audio Director — cue scheduling with priority, cooldowns, and ducking
|
|
2
|
+
import { DEFAULT_DOMAIN_PRIORITIES, DEFAULT_DUCKING_RULES } from './types.js';
|
|
3
|
+
import { scheduleAll } from './scheduler.js';
|
|
4
|
+
/** Deterministic audio cue scheduling engine. */
|
|
5
|
+
export class AudioDirector {
|
|
6
|
+
cooldowns = new Map();
|
|
7
|
+
activeLayers = new Map();
|
|
8
|
+
duckingRules;
|
|
9
|
+
domainPriorities;
|
|
10
|
+
defaultCooldownMs;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.defaultCooldownMs = config?.defaultCooldownMs ?? 2000;
|
|
13
|
+
this.duckingRules = config?.duckingRules ?? [...DEFAULT_DUCKING_RULES];
|
|
14
|
+
this.domainPriorities = config?.domainPriorities ?? { ...DEFAULT_DOMAIN_PRIORITIES };
|
|
15
|
+
}
|
|
16
|
+
/** Convert a NarrationPlan into sequenced AudioCommands, applying cooldowns and ducking. */
|
|
17
|
+
schedule(plan) {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
const raw = scheduleAll(plan, this.domainPriorities);
|
|
20
|
+
// Filter out cooled-down resources
|
|
21
|
+
const filtered = raw.filter((cmd) => {
|
|
22
|
+
if (cmd.action !== 'play')
|
|
23
|
+
return true;
|
|
24
|
+
return !this.isOnCooldown(cmd.resourceId);
|
|
25
|
+
});
|
|
26
|
+
// Add ducking commands for active triggers
|
|
27
|
+
const ducking = this.buildDuckingCommands(filtered);
|
|
28
|
+
// Update cooldowns for played resources
|
|
29
|
+
for (const cmd of filtered) {
|
|
30
|
+
if (cmd.action === 'play' && cmd.domain === 'sfx') {
|
|
31
|
+
this.cooldowns.set(cmd.resourceId, {
|
|
32
|
+
resourceId: cmd.resourceId,
|
|
33
|
+
lastPlayedMs: now,
|
|
34
|
+
cooldownMs: this.defaultCooldownMs,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Track active ambient layers
|
|
39
|
+
for (const cmd of filtered) {
|
|
40
|
+
if (cmd.domain === 'ambient') {
|
|
41
|
+
if (cmd.action === 'stop') {
|
|
42
|
+
this.activeLayers.delete(cmd.resourceId);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.activeLayers.set(cmd.resourceId, {
|
|
46
|
+
domain: 'ambient',
|
|
47
|
+
resourceId: cmd.resourceId,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const all = [...filtered, ...ducking];
|
|
53
|
+
all.sort((a, b) => a.timing - b.timing || b.priority - a.priority);
|
|
54
|
+
return all;
|
|
55
|
+
}
|
|
56
|
+
/** Check if a resource is currently on cooldown. */
|
|
57
|
+
isOnCooldown(resourceId) {
|
|
58
|
+
const entry = this.cooldowns.get(resourceId);
|
|
59
|
+
if (!entry)
|
|
60
|
+
return false;
|
|
61
|
+
return Date.now() - entry.lastPlayedMs < entry.cooldownMs;
|
|
62
|
+
}
|
|
63
|
+
/** Register a ducking rule. */
|
|
64
|
+
addDuckingRule(rule) {
|
|
65
|
+
this.duckingRules.push(rule);
|
|
66
|
+
}
|
|
67
|
+
/** Get currently active audio layers. */
|
|
68
|
+
getActiveLayers() {
|
|
69
|
+
return new Map(this.activeLayers);
|
|
70
|
+
}
|
|
71
|
+
/** Clear all cooldowns (e.g. on scene change). */
|
|
72
|
+
clearCooldowns() {
|
|
73
|
+
this.cooldowns.clear();
|
|
74
|
+
}
|
|
75
|
+
/** Build ducking commands based on active triggers. */
|
|
76
|
+
buildDuckingCommands(commands) {
|
|
77
|
+
const ducking = [];
|
|
78
|
+
const triggerDomains = new Set(commands.filter((c) => c.action === 'play').map((c) => c.domain));
|
|
79
|
+
for (const rule of this.duckingRules) {
|
|
80
|
+
if (triggerDomains.has(rule.trigger)) {
|
|
81
|
+
ducking.push({
|
|
82
|
+
domain: rule.target,
|
|
83
|
+
action: 'duck',
|
|
84
|
+
resourceId: '__all__',
|
|
85
|
+
priority: this.domainPriorities[rule.trigger],
|
|
86
|
+
timing: 0,
|
|
87
|
+
params: { duckLevel: rule.duckLevel, fadeMs: rule.fadeMs },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return ducking;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=director.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"director.js","sourceRoot":"","sources":["../src/director.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { AudioDomain, AudioCommand, DuckingRule, CooldownEntry, AudioDirectorConfig, } from './types.js';
|
|
2
|
+
export { DEFAULT_DOMAIN_PRIORITIES, DEFAULT_DUCKING_RULES, } from './types.js';
|
|
3
|
+
export { AudioDirector } from './director.js';
|
|
4
|
+
export { scheduleAll, scheduleSfx, scheduleAmbient, scheduleMusic, scheduleVoice, } from './scheduler.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACX,aAAa,EACb,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EACL,WAAW,EACX,WAAW,EACX,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EACL,WAAW,EACX,WAAW,EACX,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NarrationPlan, SfxCue, AmbientCue, MusicCue, SpeakerCue } from '@ai-rpg-engine/presentation';
|
|
2
|
+
import type { AudioCommand, AudioDomain } from './types.js';
|
|
3
|
+
/** Convert SFX cues to AudioCommands with timing offsets. */
|
|
4
|
+
export declare function scheduleSfx(sfx: SfxCue[], speechDurationMs: number, basePriority: number): AudioCommand[];
|
|
5
|
+
/** Convert ambient cues to AudioCommands. */
|
|
6
|
+
export declare function scheduleAmbient(layers: AmbientCue[], basePriority: number): AudioCommand[];
|
|
7
|
+
/** Convert a music cue to an AudioCommand. */
|
|
8
|
+
export declare function scheduleMusic(cue: MusicCue, basePriority: number): AudioCommand;
|
|
9
|
+
/** Convert a speaker cue to a voice AudioCommand. */
|
|
10
|
+
export declare function scheduleVoice(cue: SpeakerCue, basePriority: number): AudioCommand;
|
|
11
|
+
/** Schedule all cues from a NarrationPlan into ordered AudioCommands. */
|
|
12
|
+
export declare function scheduleAll(plan: NarrationPlan, domainPriorities: Record<AudioDomain, number>): AudioCommand[];
|
|
13
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC3G,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS5D,6DAA6D;AAC7D,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EAAE,EACb,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,MAAM,GACnB,YAAY,EAAE,CAehB;AAED,6CAA6C;AAC7C,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAAE,EACpB,YAAY,EAAE,MAAM,GACnB,YAAY,EAAE,CAShB;AAED,8CAA8C;AAC9C,wBAAgB,aAAa,CAC3B,GAAG,EAAE,QAAQ,EACb,YAAY,EAAE,MAAM,GACnB,YAAY,CASd;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,UAAU,EACf,YAAY,EAAE,MAAM,GACnB,YAAY,CAcd;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CACzB,IAAI,EAAE,aAAa,EACnB,gBAAgB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,GAC5C,YAAY,EAAE,CAkChB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Timing and sequencing logic for audio commands
|
|
2
|
+
/** Estimate speech duration in ms from text length. */
|
|
3
|
+
function estimateSpeechDurationMs(text, speed) {
|
|
4
|
+
const wordsPerMinute = 150 * speed;
|
|
5
|
+
const wordCount = text.split(/\s+/).length;
|
|
6
|
+
return Math.round((wordCount / wordsPerMinute) * 60_000);
|
|
7
|
+
}
|
|
8
|
+
/** Convert SFX cues to AudioCommands with timing offsets. */
|
|
9
|
+
export function scheduleSfx(sfx, speechDurationMs, basePriority) {
|
|
10
|
+
return sfx.map((cue) => {
|
|
11
|
+
let timing = 0;
|
|
12
|
+
if (cue.timing === 'with-text')
|
|
13
|
+
timing = 200;
|
|
14
|
+
else if (cue.timing === 'after-text')
|
|
15
|
+
timing = speechDurationMs + 100;
|
|
16
|
+
return {
|
|
17
|
+
domain: 'sfx',
|
|
18
|
+
action: 'play',
|
|
19
|
+
resourceId: cue.effectId,
|
|
20
|
+
priority: basePriority,
|
|
21
|
+
timing,
|
|
22
|
+
params: { intensity: cue.intensity },
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Convert ambient cues to AudioCommands. */
|
|
27
|
+
export function scheduleAmbient(layers, basePriority) {
|
|
28
|
+
return layers.map((cue) => ({
|
|
29
|
+
domain: 'ambient',
|
|
30
|
+
action: cue.action,
|
|
31
|
+
resourceId: cue.layerId,
|
|
32
|
+
priority: basePriority,
|
|
33
|
+
timing: 0,
|
|
34
|
+
params: { volume: cue.volume, fadeMs: cue.fadeMs },
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
/** Convert a music cue to an AudioCommand. */
|
|
38
|
+
export function scheduleMusic(cue, basePriority) {
|
|
39
|
+
return {
|
|
40
|
+
domain: 'music',
|
|
41
|
+
action: cue.action,
|
|
42
|
+
resourceId: cue.trackId ?? '',
|
|
43
|
+
priority: basePriority,
|
|
44
|
+
timing: 0,
|
|
45
|
+
params: { fadeMs: cue.fadeMs },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Convert a speaker cue to a voice AudioCommand. */
|
|
49
|
+
export function scheduleVoice(cue, basePriority) {
|
|
50
|
+
return {
|
|
51
|
+
domain: 'voice',
|
|
52
|
+
action: 'play',
|
|
53
|
+
resourceId: cue.voiceId,
|
|
54
|
+
priority: basePriority,
|
|
55
|
+
timing: 0,
|
|
56
|
+
params: {
|
|
57
|
+
text: cue.text,
|
|
58
|
+
emotion: cue.emotion,
|
|
59
|
+
speed: cue.speed,
|
|
60
|
+
entityId: cue.entityId,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Schedule all cues from a NarrationPlan into ordered AudioCommands. */
|
|
65
|
+
export function scheduleAll(plan, domainPriorities) {
|
|
66
|
+
const commands = [];
|
|
67
|
+
const speechMs = plan.voiceProfile
|
|
68
|
+
? estimateSpeechDurationMs(plan.sceneText, plan.voiceProfile.speed)
|
|
69
|
+
: estimateSpeechDurationMs(plan.sceneText, 1.0);
|
|
70
|
+
// Voice (narrator or speaker)
|
|
71
|
+
if (plan.speaker) {
|
|
72
|
+
commands.push(scheduleVoice(plan.speaker, domainPriorities.voice));
|
|
73
|
+
}
|
|
74
|
+
else if (plan.voiceProfile) {
|
|
75
|
+
commands.push(scheduleVoice({
|
|
76
|
+
entityId: '__narrator__',
|
|
77
|
+
voiceId: plan.voiceProfile.voiceId,
|
|
78
|
+
emotion: plan.voiceProfile.emotion,
|
|
79
|
+
speed: plan.voiceProfile.speed,
|
|
80
|
+
text: plan.sceneText,
|
|
81
|
+
}, domainPriorities.voice));
|
|
82
|
+
}
|
|
83
|
+
// SFX
|
|
84
|
+
commands.push(...scheduleSfx(plan.sfx, speechMs, domainPriorities.sfx));
|
|
85
|
+
// Ambient
|
|
86
|
+
commands.push(...scheduleAmbient(plan.ambientLayers, domainPriorities.ambient));
|
|
87
|
+
// Music
|
|
88
|
+
if (plan.musicCue) {
|
|
89
|
+
commands.push(scheduleMusic(plan.musicCue, domainPriorities.music));
|
|
90
|
+
}
|
|
91
|
+
// Sort by timing, then priority (higher first)
|
|
92
|
+
commands.sort((a, b) => a.timing - b.timing || b.priority - a.priority);
|
|
93
|
+
return commands;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type AudioDomain = 'voice' | 'sfx' | 'ambient' | 'music';
|
|
2
|
+
/** A scheduled audio command to be executed by the renderer. */
|
|
3
|
+
export type AudioCommand = {
|
|
4
|
+
domain: AudioDomain;
|
|
5
|
+
action: string;
|
|
6
|
+
resourceId: string;
|
|
7
|
+
priority: number;
|
|
8
|
+
timing: number;
|
|
9
|
+
params: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
/** Rule for ducking one domain when another plays. */
|
|
12
|
+
export type DuckingRule = {
|
|
13
|
+
trigger: AudioDomain;
|
|
14
|
+
target: AudioDomain;
|
|
15
|
+
duckLevel: number;
|
|
16
|
+
fadeMs: number;
|
|
17
|
+
};
|
|
18
|
+
/** Tracks cooldown for a resource to prevent spamming. */
|
|
19
|
+
export type CooldownEntry = {
|
|
20
|
+
resourceId: string;
|
|
21
|
+
lastPlayedMs: number;
|
|
22
|
+
cooldownMs: number;
|
|
23
|
+
};
|
|
24
|
+
/** Configuration for the AudioDirector. */
|
|
25
|
+
export type AudioDirectorConfig = {
|
|
26
|
+
defaultCooldownMs?: number;
|
|
27
|
+
duckingRules?: DuckingRule[];
|
|
28
|
+
domainPriorities?: Record<AudioDomain, number>;
|
|
29
|
+
};
|
|
30
|
+
/** Default domain priorities (higher number = higher priority). */
|
|
31
|
+
export declare const DEFAULT_DOMAIN_PRIORITIES: Record<AudioDomain, number>;
|
|
32
|
+
/** Default ducking rules. */
|
|
33
|
+
export declare const DEFAULT_DUCKING_RULES: DuckingRule[];
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhE,gEAAgE;AAChE,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,2CAA2C;AAC3C,MAAM,MAAM,mBAAmB,GAAG;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;CAChD,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAKjE,CAAC;AAEF,6BAA6B;AAC7B,eAAO,MAAM,qBAAqB,EAAE,WAAW,EAI9C,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Audio director types
|
|
2
|
+
/** Default domain priorities (higher number = higher priority). */
|
|
3
|
+
export const DEFAULT_DOMAIN_PRIORITIES = {
|
|
4
|
+
voice: 100,
|
|
5
|
+
sfx: 75,
|
|
6
|
+
music: 50,
|
|
7
|
+
ambient: 25,
|
|
8
|
+
};
|
|
9
|
+
/** Default ducking rules. */
|
|
10
|
+
export const DEFAULT_DUCKING_RULES = [
|
|
11
|
+
{ trigger: 'voice', target: 'ambient', duckLevel: 0.3, fadeMs: 300 },
|
|
12
|
+
{ trigger: 'voice', target: 'music', duckLevel: 0.4, fadeMs: 300 },
|
|
13
|
+
{ trigger: 'sfx', target: 'ambient', duckLevel: 0.6, fadeMs: 150 },
|
|
14
|
+
];
|
|
15
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAoCvB,mEAAmE;AACnE,MAAM,CAAC,MAAM,yBAAyB,GAAgC;IACpE,KAAK,EAAE,GAAG;IACV,GAAG,EAAE,EAAE;IACP,KAAK,EAAE,EAAE;IACT,OAAO,EAAE,EAAE;CACZ,CAAC;AAEF,6BAA6B;AAC7B,MAAM,CAAC,MAAM,qBAAqB,GAAkB;IAClD,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;IACpE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;IAClE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;CACnE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-rpg-engine/audio-director",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Audio cue scheduling, priority, ducking, and cooldown engine for AI RPG Engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "vitest run"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@ai-rpg-engine/presentation": "^2.0.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "mcp-tool-shop",
|
|
24
|
+
"homepage": "https://mcp-tool-shop-org.github.io/ai-rpg-engine/",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/mcp-tool-shop-org/ai-rpg-engine.git",
|
|
28
|
+
"directory": "packages/audio-director"
|
|
29
|
+
},
|
|
30
|
+
"keywords": ["rpg", "audio", "director", "sfx", "ambient", "game-engine"],
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/mcp-tool-shop-org/ai-rpg-engine/issues"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
}
|
|
37
|
+
}
|