Files
audio-ui/lib/storage/db.ts

194 lines
5.0 KiB
TypeScript
Raw Normal View History

/**
* 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<IDBDatabase> {
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<ProjectMetadata[]> {
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<void> {
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<ProjectData | null> {
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<void> {
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;
}