Files
audio-ui/components/recording/InputLevelMeter.tsx

87 lines
2.3 KiB
TypeScript
Raw Normal View History

'use client';
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
export interface InputLevelMeterProps {
level: number; // 0.0 to 1.0 (normalized dB scale)
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));
const isHorizontal = orientation === 'horizontal';
// Professional audio meter gradient:
// Green (0-70% = -60dB to -18dB)
// Yellow (70-90% = -18dB to -6dB)
// Red (90-100% = -6dB to 0dB)
const gradient = isHorizontal
? 'linear-gradient(to right, rgb(34, 197, 94) 0%, rgb(34, 197, 94) 70%, rgb(234, 179, 8) 85%, rgb(239, 68, 68) 100%)'
: 'linear-gradient(to top, rgb(34, 197, 94) 0%, rgb(34, 197, 94) 70%, rgb(234, 179, 8) 85%, rgb(239, 68, 68) 100%)';
return (
<div
className={cn(
'relative bg-muted rounded-sm overflow-hidden',
isHorizontal ? 'h-4 w-full' : 'w-4 h-full',
className
)}
>
{/* Level bar with gradient */}
<div
className={cn(
'absolute transition-all duration-75 ease-out',
isHorizontal ? 'h-full left-0 top-0' : 'w-full bottom-0 left-0'
)}
style={{
[isHorizontal ? 'width' : 'height']: `${clampedLevel * 100}%`,
background: gradient,
}}
/>
{/* 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>
);
}