feat: complete Phase 10 - add phase correlation, LUFS, and audio statistics
Implemented remaining Phase 10 analysis tools:
**Phase Correlation Meter (10.3)**
- Real-time stereo phase correlation display
- Pearson correlation coefficient calculation
- Color-coded indicator (-1 to +1 scale)
- Visual feedback: Mono-like, Good Stereo, Wide Stereo, Phase Issues
**LUFS Loudness Meter (10.3)**
- Momentary, Short-term, and Integrated LUFS measurements
- Simplified K-weighting approximation
- Vertical bar display with -70 to 0 LUFS range
- -23 LUFS broadcast standard reference line
- Real-time history tracking (10 seconds)
**Audio Statistics (10.4)**
- Project info: track count, duration, sample rate, channels, bit depth
- Level analysis: peak, RMS, dynamic range, headroom
- Real-time buffer analysis from all tracks
- Color-coded warnings for clipping and low headroom
**Integration**
- Added 5-button toggle in master column (FFT, SPEC, PHS, LUFS, INFO)
- All analyzers share consistent 192px width layout
- Theme-aware styling for light/dark modes
- Compact button labels for space efficiency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 02:00:41 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import * as React from 'react';
|
|
|
|
|
import { cn } from '@/lib/utils/cn';
|
|
|
|
|
import type { Track } from '@/types/track';
|
|
|
|
|
|
|
|
|
|
export interface AudioStatisticsProps {
|
|
|
|
|
tracks: Track[];
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function AudioStatistics({ tracks, className }: AudioStatisticsProps) {
|
|
|
|
|
const stats = React.useMemo(() => {
|
|
|
|
|
if (tracks.length === 0) {
|
|
|
|
|
return {
|
|
|
|
|
totalDuration: 0,
|
|
|
|
|
longestTrack: 0,
|
|
|
|
|
sampleRate: 0,
|
|
|
|
|
channels: 0,
|
|
|
|
|
bitDepth: 32,
|
|
|
|
|
peakAmplitude: 0,
|
|
|
|
|
rmsLevel: 0,
|
|
|
|
|
dynamicRange: 0,
|
|
|
|
|
trackCount: 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let maxDuration = 0;
|
|
|
|
|
let maxPeak = 0;
|
|
|
|
|
let sumRms = 0;
|
|
|
|
|
let minPeak = 1;
|
|
|
|
|
let sampleRate = 0;
|
|
|
|
|
let channels = 0;
|
|
|
|
|
|
|
|
|
|
tracks.forEach(track => {
|
2025-11-19 02:14:32 +01:00
|
|
|
if (!track.audioBuffer) return;
|
feat: complete Phase 10 - add phase correlation, LUFS, and audio statistics
Implemented remaining Phase 10 analysis tools:
**Phase Correlation Meter (10.3)**
- Real-time stereo phase correlation display
- Pearson correlation coefficient calculation
- Color-coded indicator (-1 to +1 scale)
- Visual feedback: Mono-like, Good Stereo, Wide Stereo, Phase Issues
**LUFS Loudness Meter (10.3)**
- Momentary, Short-term, and Integrated LUFS measurements
- Simplified K-weighting approximation
- Vertical bar display with -70 to 0 LUFS range
- -23 LUFS broadcast standard reference line
- Real-time history tracking (10 seconds)
**Audio Statistics (10.4)**
- Project info: track count, duration, sample rate, channels, bit depth
- Level analysis: peak, RMS, dynamic range, headroom
- Real-time buffer analysis from all tracks
- Color-coded warnings for clipping and low headroom
**Integration**
- Added 5-button toggle in master column (FFT, SPEC, PHS, LUFS, INFO)
- All analyzers share consistent 192px width layout
- Theme-aware styling for light/dark modes
- Compact button labels for space efficiency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 02:00:41 +01:00
|
|
|
|
2025-11-19 02:14:32 +01:00
|
|
|
const duration = track.audioBuffer.duration;
|
feat: complete Phase 10 - add phase correlation, LUFS, and audio statistics
Implemented remaining Phase 10 analysis tools:
**Phase Correlation Meter (10.3)**
- Real-time stereo phase correlation display
- Pearson correlation coefficient calculation
- Color-coded indicator (-1 to +1 scale)
- Visual feedback: Mono-like, Good Stereo, Wide Stereo, Phase Issues
**LUFS Loudness Meter (10.3)**
- Momentary, Short-term, and Integrated LUFS measurements
- Simplified K-weighting approximation
- Vertical bar display with -70 to 0 LUFS range
- -23 LUFS broadcast standard reference line
- Real-time history tracking (10 seconds)
**Audio Statistics (10.4)**
- Project info: track count, duration, sample rate, channels, bit depth
- Level analysis: peak, RMS, dynamic range, headroom
- Real-time buffer analysis from all tracks
- Color-coded warnings for clipping and low headroom
**Integration**
- Added 5-button toggle in master column (FFT, SPEC, PHS, LUFS, INFO)
- All analyzers share consistent 192px width layout
- Theme-aware styling for light/dark modes
- Compact button labels for space efficiency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 02:00:41 +01:00
|
|
|
maxDuration = Math.max(maxDuration, duration);
|
|
|
|
|
|
|
|
|
|
// Get sample rate and channels from first track
|
|
|
|
|
if (sampleRate === 0) {
|
2025-11-19 02:14:32 +01:00
|
|
|
sampleRate = track.audioBuffer.sampleRate;
|
|
|
|
|
channels = track.audioBuffer.numberOfChannels;
|
feat: complete Phase 10 - add phase correlation, LUFS, and audio statistics
Implemented remaining Phase 10 analysis tools:
**Phase Correlation Meter (10.3)**
- Real-time stereo phase correlation display
- Pearson correlation coefficient calculation
- Color-coded indicator (-1 to +1 scale)
- Visual feedback: Mono-like, Good Stereo, Wide Stereo, Phase Issues
**LUFS Loudness Meter (10.3)**
- Momentary, Short-term, and Integrated LUFS measurements
- Simplified K-weighting approximation
- Vertical bar display with -70 to 0 LUFS range
- -23 LUFS broadcast standard reference line
- Real-time history tracking (10 seconds)
**Audio Statistics (10.4)**
- Project info: track count, duration, sample rate, channels, bit depth
- Level analysis: peak, RMS, dynamic range, headroom
- Real-time buffer analysis from all tracks
- Color-coded warnings for clipping and low headroom
**Integration**
- Added 5-button toggle in master column (FFT, SPEC, PHS, LUFS, INFO)
- All analyzers share consistent 192px width layout
- Theme-aware styling for light/dark modes
- Compact button labels for space efficiency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 02:00:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate peak and RMS from buffer
|
2025-11-19 02:14:32 +01:00
|
|
|
for (let ch = 0; ch < track.audioBuffer.numberOfChannels; ch++) {
|
|
|
|
|
const channelData = track.audioBuffer.getChannelData(ch);
|
feat: complete Phase 10 - add phase correlation, LUFS, and audio statistics
Implemented remaining Phase 10 analysis tools:
**Phase Correlation Meter (10.3)**
- Real-time stereo phase correlation display
- Pearson correlation coefficient calculation
- Color-coded indicator (-1 to +1 scale)
- Visual feedback: Mono-like, Good Stereo, Wide Stereo, Phase Issues
**LUFS Loudness Meter (10.3)**
- Momentary, Short-term, and Integrated LUFS measurements
- Simplified K-weighting approximation
- Vertical bar display with -70 to 0 LUFS range
- -23 LUFS broadcast standard reference line
- Real-time history tracking (10 seconds)
**Audio Statistics (10.4)**
- Project info: track count, duration, sample rate, channels, bit depth
- Level analysis: peak, RMS, dynamic range, headroom
- Real-time buffer analysis from all tracks
- Color-coded warnings for clipping and low headroom
**Integration**
- Added 5-button toggle in master column (FFT, SPEC, PHS, LUFS, INFO)
- All analyzers share consistent 192px width layout
- Theme-aware styling for light/dark modes
- Compact button labels for space efficiency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 02:00:41 +01:00
|
|
|
let chPeak = 0;
|
|
|
|
|
let chRmsSum = 0;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < channelData.length; i++) {
|
|
|
|
|
const abs = Math.abs(channelData[i]);
|
|
|
|
|
chPeak = Math.max(chPeak, abs);
|
|
|
|
|
chRmsSum += channelData[i] * channelData[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maxPeak = Math.max(maxPeak, chPeak);
|
|
|
|
|
minPeak = Math.min(minPeak, chPeak);
|
|
|
|
|
sumRms += Math.sqrt(chRmsSum / channelData.length);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const avgRms = sumRms / (tracks.length * Math.max(1, channels));
|
|
|
|
|
const peakDb = maxPeak > 0 ? 20 * Math.log10(maxPeak) : -Infinity;
|
|
|
|
|
const rmsDb = avgRms > 0 ? 20 * Math.log10(avgRms) : -Infinity;
|
|
|
|
|
const dynamicRange = peakDb - rmsDb;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
totalDuration: maxDuration,
|
|
|
|
|
longestTrack: maxDuration,
|
|
|
|
|
sampleRate,
|
|
|
|
|
channels,
|
|
|
|
|
bitDepth: 32, // Web Audio API uses 32-bit float
|
|
|
|
|
peakAmplitude: maxPeak,
|
|
|
|
|
rmsLevel: avgRms,
|
|
|
|
|
dynamicRange: dynamicRange > 0 ? dynamicRange : 0,
|
|
|
|
|
trackCount: tracks.length,
|
|
|
|
|
};
|
|
|
|
|
}, [tracks]);
|
|
|
|
|
|
|
|
|
|
const formatDuration = (seconds: number) => {
|
|
|
|
|
const mins = Math.floor(seconds / 60);
|
|
|
|
|
const secs = Math.floor(seconds % 60);
|
|
|
|
|
const ms = Math.floor((seconds % 1) * 1000);
|
|
|
|
|
return `${mins}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatDb = (linear: number) => {
|
|
|
|
|
if (linear === 0) return '-∞ dB';
|
|
|
|
|
const db = 20 * Math.log10(linear);
|
|
|
|
|
return db > -60 ? `${db.toFixed(1)} dB` : '-∞ dB';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn('w-full h-full bg-card/50 border-2 border-accent/50 rounded-lg p-3', className)}>
|
|
|
|
|
<div className="text-[10px] font-bold text-accent uppercase tracking-wider mb-3">
|
|
|
|
|
Audio Statistics
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2 text-[10px]">
|
|
|
|
|
{/* File Info */}
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="text-[9px] text-muted-foreground uppercase tracking-wide">Project Info</div>
|
|
|
|
|
<div className="grid grid-cols-2 gap-x-2 gap-y-1">
|
|
|
|
|
<div className="text-muted-foreground">Tracks:</div>
|
|
|
|
|
<div className="font-mono text-right">{stats.trackCount}</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">Duration:</div>
|
|
|
|
|
<div className="font-mono text-right">{formatDuration(stats.totalDuration)}</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">Sample Rate:</div>
|
|
|
|
|
<div className="font-mono text-right">{stats.sampleRate > 0 ? `${(stats.sampleRate / 1000).toFixed(1)} kHz` : 'N/A'}</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">Channels:</div>
|
|
|
|
|
<div className="font-mono text-right">{stats.channels > 0 ? (stats.channels === 1 ? 'Mono' : 'Stereo') : 'N/A'}</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">Bit Depth:</div>
|
|
|
|
|
<div className="font-mono text-right">{stats.bitDepth}-bit float</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Divider */}
|
|
|
|
|
<div className="border-t border-border/30" />
|
|
|
|
|
|
|
|
|
|
{/* Audio Levels */}
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="text-[9px] text-muted-foreground uppercase tracking-wide">Levels</div>
|
|
|
|
|
<div className="grid grid-cols-2 gap-x-2 gap-y-1">
|
|
|
|
|
<div className="text-muted-foreground">Peak:</div>
|
|
|
|
|
<div className={cn(
|
|
|
|
|
'font-mono text-right',
|
|
|
|
|
stats.peakAmplitude > 0.99 ? 'text-red-500 font-bold' : ''
|
|
|
|
|
)}>
|
|
|
|
|
{formatDb(stats.peakAmplitude)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">RMS:</div>
|
|
|
|
|
<div className="font-mono text-right">{formatDb(stats.rmsLevel)}</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">Dynamic Range:</div>
|
|
|
|
|
<div className="font-mono text-right">
|
|
|
|
|
{stats.dynamicRange > 0 ? `${stats.dynamicRange.toFixed(1)} dB` : 'N/A'}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-muted-foreground">Headroom:</div>
|
|
|
|
|
<div className={cn(
|
|
|
|
|
'font-mono text-right',
|
|
|
|
|
stats.peakAmplitude > 0.99 ? 'text-red-500' :
|
|
|
|
|
stats.peakAmplitude > 0.9 ? 'text-yellow-500' : 'text-green-500'
|
|
|
|
|
)}>
|
|
|
|
|
{stats.peakAmplitude > 0 ? `${(20 * Math.log10(1 / stats.peakAmplitude)).toFixed(1)} dB` : 'N/A'}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|