/** * IndexedDB database for project storage */ export const DB_NAME = 'audio-editor-db'; export const DB_VERSION = 1; export interface ProjectMetadata { id: string; name: string; description?: string; createdAt: number; updatedAt: number; duration: number; // Total project duration in seconds sampleRate: number; trackCount: number; thumbnail?: string; // Base64 encoded waveform thumbnail } export interface SerializedAudioBuffer { sampleRate: number; length: number; numberOfChannels: number; channelData: Float32Array[]; // Array of channel data } export interface SerializedTrack { id: string; name: string; color: string; volume: number; pan: number; muted: boolean; soloed: boolean; collapsed: boolean; height: number; audioBuffer: SerializedAudioBuffer | null; effects: any[]; // Effect chain automation: any; // Automation data recordEnabled: boolean; } export interface ProjectData { metadata: ProjectMetadata; tracks: SerializedTrack[]; settings: { zoom: number; currentTime: number; sampleRate: number; }; } /** * Initialize IndexedDB database */ export function initDB(): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => reject(request.error); request.onsuccess = () => resolve(request.result); request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; // Create projects object store if (!db.objectStoreNames.contains('projects')) { const projectStore = db.createObjectStore('projects', { keyPath: 'metadata.id' }); projectStore.createIndex('updatedAt', 'metadata.updatedAt', { unique: false }); projectStore.createIndex('name', 'metadata.name', { unique: false }); } // Create audio buffers object store (for large files) if (!db.objectStoreNames.contains('audioBuffers')) { db.createObjectStore('audioBuffers', { keyPath: 'id' }); } }; }); } /** * Get all projects (metadata only for list view) */ export async function getAllProjects(): Promise { const db = await initDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(['projects'], 'readonly'); const store = transaction.objectStore('projects'); const index = store.index('updatedAt'); const request = index.openCursor(null, 'prev'); // Most recent first const projects: ProjectMetadata[] = []; request.onsuccess = () => { const cursor = request.result; if (cursor) { projects.push(cursor.value.metadata); cursor.continue(); } else { resolve(projects); } }; request.onerror = () => reject(request.error); }); } /** * Save project to IndexedDB */ export async function saveProject(project: ProjectData): Promise { const db = await initDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(['projects'], 'readwrite'); const store = transaction.objectStore('projects'); const request = store.put(project); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } /** * Load project from IndexedDB */ export async function loadProject(projectId: string): Promise { const db = await initDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(['projects'], 'readonly'); const store = transaction.objectStore('projects'); const request = store.get(projectId); request.onsuccess = () => resolve(request.result || null); request.onerror = () => reject(request.error); }); } /** * Delete project from IndexedDB */ export async function deleteProject(projectId: string): Promise { const db = await initDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(['projects'], 'readwrite'); const store = transaction.objectStore('projects'); const request = store.delete(projectId); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } /** * Serialize AudioBuffer for storage */ export function serializeAudioBuffer(buffer: AudioBuffer): SerializedAudioBuffer { const channelData: Float32Array[] = []; for (let i = 0; i < buffer.numberOfChannels; i++) { channelData.push(new Float32Array(buffer.getChannelData(i))); } return { sampleRate: buffer.sampleRate, length: buffer.length, numberOfChannels: buffer.numberOfChannels, channelData, }; } /** * Deserialize AudioBuffer from storage */ export function deserializeAudioBuffer( serialized: SerializedAudioBuffer, audioContext: AudioContext ): AudioBuffer { const buffer = audioContext.createBuffer( serialized.numberOfChannels, serialized.length, serialized.sampleRate ); for (let i = 0; i < serialized.numberOfChannels; i++) { buffer.copyToChannel(new Float32Array(serialized.channelData[i]), i); } return buffer; }