/** * Project management service */ import type { Track } from '@/types/track'; import { saveProject, loadProject, getAllProjects, deleteProject, serializeAudioBuffer, deserializeAudioBuffer, type ProjectData, type SerializedTrack, } from './db'; import type { ProjectMetadata } from './db'; import { getAudioContext } from '../audio/context'; import { generateId } from '../audio/effects/chain'; // Re-export ProjectMetadata for easier importing export type { ProjectMetadata } from './db'; /** * Generate unique project ID */ export function generateProjectId(): string { return `project_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Convert tracks to serialized format */ function serializeTracks(tracks: Track[]): SerializedTrack[] { return tracks.map(track => ({ id: track.id, name: track.name, color: track.color, volume: track.volume, pan: track.pan, muted: track.mute, soloed: track.solo, collapsed: track.collapsed, height: track.height, audioBuffer: track.audioBuffer ? serializeAudioBuffer(track.audioBuffer) : null, effects: track.effectChain?.effects || [], automation: track.automation, recordEnabled: track.recordEnabled, })); } /** * Convert serialized tracks back to Track format */ function deserializeTracks(serialized: SerializedTrack[]): Track[] { const audioContext = getAudioContext(); return serialized.map(track => ({ id: track.id, name: track.name, color: track.color, volume: track.volume, pan: track.pan, mute: track.muted, solo: track.soloed, collapsed: track.collapsed, height: track.height, audioBuffer: track.audioBuffer ? deserializeAudioBuffer(track.audioBuffer, audioContext) : null, effectChain: { id: generateId(), name: `${track.name} FX`, effects: track.effects, }, automation: track.automation, recordEnabled: track.recordEnabled, selected: false, showEffects: false, selection: null, // Reset selection on load })); } /** * Calculate total project duration */ function calculateDuration(tracks: Track[]): number { let maxDuration = 0; for (const track of tracks) { if (track.audioBuffer) { maxDuration = Math.max(maxDuration, track.audioBuffer.duration); } } return maxDuration; } /** * Save current project state */ export async function saveCurrentProject( projectId: string | null, projectName: string, tracks: Track[], settings: { zoom: number; currentTime: number; sampleRate: number; }, description?: string ): Promise { const id = projectId || generateProjectId(); const now = Date.now(); const metadata: ProjectMetadata = { id, name: projectName, description, createdAt: projectId ? (await loadProject(id))?.metadata.createdAt || now : now, updatedAt: now, duration: calculateDuration(tracks), sampleRate: settings.sampleRate, trackCount: tracks.length, }; const projectData: ProjectData = { metadata, tracks: serializeTracks(tracks), settings, }; await saveProject(projectData); return id; } /** * Load project and restore state */ export async function loadProjectById(projectId: string): Promise<{ tracks: Track[]; settings: { zoom: number; currentTime: number; sampleRate: number; }; metadata: ProjectMetadata; } | null> { const project = await loadProject(projectId); if (!project) return null; return { tracks: deserializeTracks(project.tracks), settings: project.settings, metadata: project.metadata, }; } /** * Get list of all projects */ export async function listProjects(): Promise { return getAllProjects(); } /** * Delete a project */ export async function removeProject(projectId: string): Promise { return deleteProject(projectId); } /** * Duplicate a project */ export async function duplicateProject(sourceProjectId: string, newName: string): Promise { const project = await loadProject(sourceProjectId); if (!project) throw new Error('Project not found'); const newId = generateProjectId(); const now = Date.now(); const newProject: ProjectData = { ...project, metadata: { ...project.metadata, id: newId, name: newName, createdAt: now, updatedAt: now, }, }; await saveProject(newProject); return newId; }