Files
audio-ui/lib/utils/memory-limits.ts
Sebastian Krüger 908e6caaf8 feat: enhance mobile responsiveness with collapsible controls and automation/effects bars
Added comprehensive mobile support for Phase 15 (Polish & Optimization):

**Mobile Layout Enhancements:**
- Track controls now collapsible on mobile with two states:
  - Collapsed: minimal controls with expand chevron, R/M/S buttons, horizontal level meter
  - Expanded: full height fader, pan control, all buttons
- Track collapse buttons added to mobile view (left chevron for track collapse, right chevron for control collapse)
- Master controls collapse button hidden on desktop (lg:hidden)
- Automation and effects bars now available on mobile layout
- Both bars collapsible with eye/eye-off icons, horizontally scrollable when zoomed
- Mobile vertical stacking: controls → waveform → automation → effects per track

**Bug Fixes:**
- Fixed track controls and waveform container height matching on desktop
- Fixed Modal component prop: isOpen → open in all dialog components
- Fixed TypeScript null check for audioBuffer.duration
- Fixed keyboard shortcut category: 'help' → 'view'

**Technical Improvements:**
- Consistent height calculation using trackHeight variable
- Proper responsive breakpoints with Tailwind (sm:640px, lg:1024px)
- Progressive disclosure pattern for mobile controls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 20:50:44 +01:00

161 lines
4.8 KiB
TypeScript

/**
* Memory limit checking utilities for audio file handling
*/
export interface MemoryCheckResult {
allowed: boolean;
warning?: string;
estimatedMemoryMB: number;
availableMemoryMB?: number;
}
/**
* Estimate memory required for an audio buffer
* @param duration Duration in seconds
* @param sampleRate Sample rate (default: 48000 Hz)
* @param channels Number of channels (default: 2 for stereo)
* @returns Estimated memory in MB
*/
export function estimateAudioMemory(
duration: number,
sampleRate: number = 48000,
channels: number = 2
): number {
// Each sample is a 32-bit float (4 bytes)
const bytesPerSample = 4;
const totalSamples = duration * sampleRate * channels;
const bytes = totalSamples * bytesPerSample;
// Convert to MB
return bytes / (1024 * 1024);
}
/**
* Get available device memory if supported
* @returns Available memory in MB, or undefined if not supported
*/
export function getAvailableMemory(): number | undefined {
if (typeof navigator === 'undefined') return undefined;
// @ts-ignore - deviceMemory is not in TypeScript types yet
const deviceMemory = navigator.deviceMemory;
if (typeof deviceMemory === 'number') {
// deviceMemory is in GB, convert to MB
return deviceMemory * 1024;
}
return undefined;
}
/**
* Check if a file size is within safe memory limits
* @param fileSizeBytes File size in bytes
* @returns Memory check result
*/
export function checkFileMemoryLimit(fileSizeBytes: number): MemoryCheckResult {
// Estimate memory usage (audio files decompress to ~10x their size)
const estimatedMemoryMB = (fileSizeBytes / (1024 * 1024)) * 10;
const availableMemoryMB = getAvailableMemory();
// Conservative limits
const WARN_THRESHOLD_MB = 100; // Warn if file will use > 100MB
const MAX_RECOMMENDED_MB = 500; // Don't recommend files > 500MB
if (estimatedMemoryMB > MAX_RECOMMENDED_MB) {
return {
allowed: false,
warning: `This file may require ${Math.round(estimatedMemoryMB)}MB of memory. ` +
`Files larger than ${MAX_RECOMMENDED_MB}MB are not recommended as they may cause performance issues or crashes.`,
estimatedMemoryMB,
availableMemoryMB,
};
}
if (estimatedMemoryMB > WARN_THRESHOLD_MB) {
const warning = availableMemoryMB
? `This file will require approximately ${Math.round(estimatedMemoryMB)}MB of memory. ` +
`Your device has ${Math.round(availableMemoryMB)}MB available.`
: `This file will require approximately ${Math.round(estimatedMemoryMB)}MB of memory. ` +
`Large files may cause performance issues on devices with limited memory.`;
return {
allowed: true,
warning,
estimatedMemoryMB,
availableMemoryMB,
};
}
return {
allowed: true,
estimatedMemoryMB,
availableMemoryMB,
};
}
/**
* Check if an audio buffer is within safe memory limits
* @param duration Duration in seconds
* @param sampleRate Sample rate
* @param channels Number of channels
* @returns Memory check result
*/
export function checkAudioBufferMemoryLimit(
duration: number,
sampleRate: number = 48000,
channels: number = 2
): MemoryCheckResult {
const estimatedMemoryMB = estimateAudioMemory(duration, sampleRate, channels);
const availableMemoryMB = getAvailableMemory();
const WARN_THRESHOLD_MB = 100;
const MAX_RECOMMENDED_MB = 500;
if (estimatedMemoryMB > MAX_RECOMMENDED_MB) {
return {
allowed: false,
warning: `This audio (${Math.round(duration / 60)} minutes) will require ${Math.round(estimatedMemoryMB)}MB of memory. ` +
`Audio longer than ${Math.round((MAX_RECOMMENDED_MB / sampleRate / channels / 4) / 60)} minutes may cause performance issues.`,
estimatedMemoryMB,
availableMemoryMB,
};
}
if (estimatedMemoryMB > WARN_THRESHOLD_MB) {
const warning = availableMemoryMB
? `This audio will require approximately ${Math.round(estimatedMemoryMB)}MB of memory. ` +
`Your device has ${Math.round(availableMemoryMB)}MB available.`
: `This audio will require approximately ${Math.round(estimatedMemoryMB)}MB of memory.`;
return {
allowed: true,
warning,
estimatedMemoryMB,
availableMemoryMB,
};
}
return {
allowed: true,
estimatedMemoryMB,
availableMemoryMB,
};
}
/**
* Format memory size in human-readable format
* @param bytes Size in bytes
* @returns Formatted string (e.g., "1.5 MB", "250 KB")
*/
export function formatMemorySize(bytes: number): string {
if (bytes < 1024) {
return `${bytes} B`;
} else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;
} else if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
} else {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
}