feat: implement Phase 8.1 - audio recording infrastructure
Added recording capabilities to the multi-track editor: - useRecording hook with MediaRecorder API integration - Audio input device enumeration and selection - Microphone permission handling - Input level monitoring with RMS calculation - InputLevelMeter component with visual feedback - Record-enable button per track with pulsing indicator - Real-time input level display when recording Recording infrastructure is complete. Next: integrate into AudioEditor for global recording control and buffer storage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
85
components/recording/InputLevelMeter.tsx
Normal file
85
components/recording/InputLevelMeter.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
|
||||
export interface InputLevelMeterProps {
|
||||
level: number; // 0.0 to 1.0
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function InputLevelMeter({
|
||||
level,
|
||||
orientation = 'horizontal',
|
||||
className,
|
||||
}: InputLevelMeterProps) {
|
||||
// Clamp level between 0 and 1
|
||||
const clampedLevel = Math.max(0, Math.min(1, level));
|
||||
|
||||
// Calculate color based on level
|
||||
const getColor = (level: number): string => {
|
||||
if (level > 0.9) return 'bg-red-500';
|
||||
if (level > 0.7) return 'bg-yellow-500';
|
||||
return 'bg-green-500';
|
||||
};
|
||||
|
||||
const isHorizontal = orientation === 'horizontal';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative bg-muted rounded-sm overflow-hidden',
|
||||
isHorizontal ? 'h-4 w-full' : 'w-4 h-full',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Level bar */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute transition-all duration-75 ease-out',
|
||||
getColor(clampedLevel),
|
||||
isHorizontal ? 'h-full left-0 top-0' : 'w-full bottom-0 left-0'
|
||||
)}
|
||||
style={{
|
||||
[isHorizontal ? 'width' : 'height']: `${clampedLevel * 100}%`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Clip indicator (at 90%) */}
|
||||
{clampedLevel > 0.9 && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute bg-red-600 animate-pulse',
|
||||
isHorizontal
|
||||
? 'right-0 top-0 w-1 h-full'
|
||||
: 'bottom-0 left-0 h-1 w-full'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tick marks */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-0 flex',
|
||||
isHorizontal ? 'flex-row' : 'flex-col-reverse'
|
||||
)}
|
||||
>
|
||||
{[0.25, 0.5, 0.75].map((tick) => (
|
||||
<div
|
||||
key={tick}
|
||||
className={cn(
|
||||
'absolute bg-background/30',
|
||||
isHorizontal
|
||||
? 'h-full w-px top-0'
|
||||
: 'w-full h-px left-0'
|
||||
)}
|
||||
style={{
|
||||
[isHorizontal ? 'left' : 'bottom']: `${tick * 100}%`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user