@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
169 lines • 6.63 kB
JavaScript
/**
* ElevenLabs Music / Sound Generation Handler
*
* Distinct from ElevenLabs TTS — uses the `/v1/sound-generation` endpoint
* (synchronous; returns binary audio directly, no polling).
*
* @module music/providers/ElevenLabsMusic
* @see https://elevenlabs.io/docs/api-reference/sound-generation
*/
import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
import { MUSIC_ERROR_CODES, MusicError } from "../../utils/musicProcessor.js";
import { logger } from "../../utils/logger.js";
import { MAX_AUDIO_BYTES, readBoundedBuffer } from "../../utils/sizeGuard.js";
const DEFAULT_BASE_URL = "https://api.elevenlabs.io/v1";
const REQUEST_TIMEOUT_MS = 60_000; // longer because synchronous generation
/**
* ElevenLabs Music / Sound Generation Handler.
*
* Auth: `xi-api-key: ${ELEVENLABS_API_KEY}` (shares the same env var as
* ElevenLabs TTS — same account; different endpoint).
*
* Best for: short sound effects (ambient drones, hits, foley) and short
* music loops up to 22 seconds.
*/
export class ElevenLabsMusic {
maxDurationSeconds = 22;
supportedFormats = ["mp3"];
supportedGenres = [
"ambient",
"cinematic",
"lo-fi",
"electronic",
"orchestral",
"soundscape",
];
apiKey;
baseUrl;
constructor(apiKey) {
const resolved = (apiKey ?? process.env.ELEVENLABS_API_KEY ?? "").trim();
this.apiKey = resolved.length > 0 ? resolved : null;
this.baseUrl = (process.env.ELEVENLABS_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
}
isConfigured() {
return this.apiKey !== null;
}
async generate(options) {
if (!this.apiKey) {
throw new MusicError({
code: MUSIC_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
message: "ELEVENLABS_API_KEY not configured (shared with ElevenLabs TTS — same account, different endpoint)",
category: ErrorCategory.CONFIGURATION,
severity: ErrorSeverity.HIGH,
retriable: false,
});
}
const promptText = options.prompt?.trim();
if (!promptText) {
throw new MusicError({
code: MUSIC_ERROR_CODES.INVALID_INPUT,
message: "ElevenLabs Music requires a non-empty prompt",
category: ErrorCategory.VALIDATION,
severity: ErrorSeverity.LOW,
retriable: false,
});
}
const requestedDuration = options.duration ?? 8;
if (requestedDuration <= 0 || requestedDuration > this.maxDurationSeconds) {
throw new MusicError({
code: MUSIC_ERROR_CODES.INVALID_INPUT,
message: `ElevenLabs Music duration must be between 1 and ${this.maxDurationSeconds} seconds; got ${requestedDuration}`,
category: ErrorCategory.VALIDATION,
severity: ErrorSeverity.LOW,
retriable: false,
context: {
requested: requestedDuration,
maximum: this.maxDurationSeconds,
},
});
}
const startTime = Date.now();
const duration = requestedDuration;
const body = {
text: this.buildPrompt({ ...options, prompt: promptText }),
duration_seconds: duration,
prompt_influence: 0.3,
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
let response;
try {
response = await fetch(`${this.baseUrl}/sound-generation`, {
method: "POST",
headers: {
"xi-api-key": this.apiKey,
"Content-Type": "application/json",
Accept: "audio/mpeg",
},
body: JSON.stringify(body),
signal: controller.signal,
});
}
catch (err) {
if (err instanceof Error && err.name === "AbortError") {
throw new MusicError({
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
message: `ElevenLabs Music request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`,
category: ErrorCategory.NETWORK,
severity: ErrorSeverity.HIGH,
retriable: true,
originalError: err,
});
}
throw new MusicError({
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
message: `ElevenLabs Music network error: ${err instanceof Error ? err.message : String(err)}`,
category: ErrorCategory.NETWORK,
severity: ErrorSeverity.HIGH,
retriable: true,
originalError: err instanceof Error ? err : undefined,
});
}
finally {
clearTimeout(timeoutId);
}
if (!response.ok) {
const text = await response.text();
const retriable = response.status === 408 ||
response.status === 429 ||
response.status >= 500;
throw new MusicError({
code: MUSIC_ERROR_CODES.GENERATION_FAILED,
message: `ElevenLabs Music synthesis failed: ${response.status} — ${text}`,
category: retriable ? ErrorCategory.NETWORK : ErrorCategory.EXECUTION,
severity: ErrorSeverity.HIGH,
retriable,
context: { status: response.status },
});
}
const buffer = await readBoundedBuffer(response, MAX_AUDIO_BYTES, "ElevenLabs music");
const latency = Date.now() - startTime;
logger.info(`[ElevenLabsMusic] Generated ${buffer.length} bytes in ${latency}ms`);
return {
buffer,
format: "mp3",
size: buffer.length,
duration,
provider: "elevenlabs-music",
metadata: {
latency,
provider: "elevenlabs-music",
model: "elevenlabs-sound-v1",
},
};
}
buildPrompt(options) {
const parts = [options.prompt];
if (options.genre) {
parts.push(`Genre: ${options.genre}`);
}
if (options.mood) {
parts.push(`Mood: ${options.mood}`);
}
if (options.tempo !== undefined) {
parts.push(`${options.tempo} BPM`);
}
return parts.join(". ");
}
}
//# sourceMappingURL=ElevenLabsMusic.js.map