mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-20 15:20:39 -07:00
Add post-processing audio effects system
Adds a full effects pipeline powered by Spotify's pedalboard library, enabling users to apply professional DSP effects (flanger, reverb, delay, compressor, pitch shift, filters, gain) to generated audio. Key features: - Effects chain editor with drag-and-drop reordering (dnd-kit) - Generation versions: clean copy always saved, processed versions created on top - Built-in presets (Robotic, Radio, Echo Chamber, Deep Voice) + custom user presets - Per-profile default effects chain (auto-applied to new generations) - Per-generation effects override from the generation form - Apply effects to existing generations from history (creates new version) - Ephemeral preview endpoint for auditioning effects without persisting - Dedicated Effects sidebar tab with preset management and live preview - Version switcher in history cards with expandable panel - Backward-compatible: existing generations backfilled as clean versions
This commit is contained in:
@@ -2,9 +2,15 @@ import type { LanguageCode } from '@/lib/constants/languages';
|
||||
import { useServerStore } from '@/stores/serverStore';
|
||||
import type {
|
||||
ActiveTasksResponse,
|
||||
ApplyEffectsRequest,
|
||||
AvailableEffectsResponse,
|
||||
CudaStatus,
|
||||
EffectConfig,
|
||||
EffectPresetCreate,
|
||||
EffectPresetResponse,
|
||||
GenerationRequest,
|
||||
GenerationResponse,
|
||||
GenerationVersionResponse,
|
||||
HealthResponse,
|
||||
HistoryListResponse,
|
||||
HistoryQuery,
|
||||
@@ -583,6 +589,93 @@ class ApiClient {
|
||||
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
// Effects & Versions
|
||||
async getAvailableEffects(): Promise<AvailableEffectsResponse> {
|
||||
return this.request<AvailableEffectsResponse>('/effects/available');
|
||||
}
|
||||
|
||||
async listEffectPresets(): Promise<EffectPresetResponse[]> {
|
||||
return this.request<EffectPresetResponse[]>('/effects/presets');
|
||||
}
|
||||
|
||||
async createEffectPreset(data: EffectPresetCreate): Promise<EffectPresetResponse> {
|
||||
return this.request<EffectPresetResponse>('/effects/presets', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEffectPreset(presetId: string): Promise<void> {
|
||||
await this.request<void>(`/effects/presets/${presetId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
async listGenerationVersions(generationId: string): Promise<GenerationVersionResponse[]> {
|
||||
return this.request<GenerationVersionResponse[]>(`/generations/${generationId}/versions`);
|
||||
}
|
||||
|
||||
async applyEffectsToGeneration(
|
||||
generationId: string,
|
||||
data: ApplyEffectsRequest,
|
||||
): Promise<GenerationVersionResponse> {
|
||||
return this.request<GenerationVersionResponse>(
|
||||
`/generations/${generationId}/versions/apply-effects`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async setDefaultVersion(
|
||||
generationId: string,
|
||||
versionId: string,
|
||||
): Promise<GenerationVersionResponse> {
|
||||
return this.request<GenerationVersionResponse>(
|
||||
`/generations/${generationId}/versions/${versionId}/set-default`,
|
||||
{ method: 'PUT' },
|
||||
);
|
||||
}
|
||||
|
||||
async deleteGenerationVersion(generationId: string, versionId: string): Promise<void> {
|
||||
await this.request<void>(`/generations/${generationId}/versions/${versionId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
getVersionAudioUrl(versionId: string): string {
|
||||
return `${this.getBaseUrl()}/audio/version/${versionId}`;
|
||||
}
|
||||
|
||||
async updateProfileEffects(
|
||||
profileId: string,
|
||||
effectsChain: EffectConfig[] | null,
|
||||
): Promise<VoiceProfileResponse> {
|
||||
return this.request<VoiceProfileResponse>(`/profiles/${profileId}/effects`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ effects_chain: effectsChain }),
|
||||
});
|
||||
}
|
||||
|
||||
async previewEffects(generationId: string, effectsChain: EffectConfig[]): Promise<Blob> {
|
||||
const url = `${this.getBaseUrl()}/effects/preview/${generationId}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ effects_chain: effectsChain }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({
|
||||
detail: response.statusText,
|
||||
}));
|
||||
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient();
|
||||
|
||||
Reference in New Issue
Block a user