@@ -21,6 +21,27 @@ func linearToDb(linear float64) float64 {
2121 return 20.0 * math .Log10 (linear )
2222}
2323
24+ // formatComparison returns "(unchanged)" if values match within tolerance, otherwise "(was X unit)"
25+ func formatComparison (output , input float64 , unit string , decimals int ) string {
26+ // Use tolerance based on decimal places shown
27+ tolerance := math .Pow (10 , - float64 (decimals )) * 0.5
28+ if math .Abs (output - input ) < tolerance {
29+ return "(unchanged)"
30+ }
31+ format := fmt .Sprintf ("(was %%.%df %s)" , decimals , unit )
32+ return fmt .Sprintf (format , input )
33+ }
34+
35+ // formatComparisonNoUnit returns "(unchanged)" if values match within tolerance, otherwise "(was X)"
36+ func formatComparisonNoUnit (output , input float64 , decimals int ) string {
37+ tolerance := math .Pow (10 , - float64 (decimals )) * 0.5
38+ if math .Abs (output - input ) < tolerance {
39+ return "(unchanged)"
40+ }
41+ format := fmt .Sprintf ("(was %%.%df)" , decimals )
42+ return fmt .Sprintf (format , input )
43+ }
44+
2445// ReportData contains all the information needed to generate an analysis report
2546type ReportData struct {
2647 InputPath string
@@ -104,7 +125,7 @@ func GenerateReport(data ReportData) error {
104125 } else if m .NoiseProfile .Entropy < 0.9 {
105126 noiseType = "mixed"
106127 }
107- fmt .Fprintf (f , " Entropy : %.3f (%s )\n " , m .NoiseProfile .Entropy , noiseType )
128+ fmt .Fprintf (f , " Noise Character : %s (entropy %.3f)\n " , noiseType , m .NoiseProfile .Entropy )
108129 }
109130 if m .NoiseProfile .ExtractionWarning != "" {
110131 fmt .Fprintf (f , " Warning: %s\n " , m .NoiseProfile .ExtractionWarning )
@@ -134,33 +155,73 @@ func GenerateReport(data ReportData) error {
134155
135156 if data .Result .OutputMeasurements != nil {
136157 om := data .Result .OutputMeasurements
137- fmt .Fprintf (f , "Integrated Loudness: %.1f LUFS\n " , om .OutputI )
138- fmt .Fprintf (f , "True Peak: %.1f dBTP\n " , om .OutputTP )
139- fmt .Fprintf (f , "Loudness Range: %.1f LU\n " , om .OutputLRA )
140- fmt .Fprintf (f , "Dynamic Range: %.1f dB\n " , om .DynamicRange )
141- fmt .Fprintf (f , "RMS Level: %.1f dBFS\n " , om .RMSLevel )
142- fmt .Fprintf (f , "Peak Level: %.1f dBFS\n " , om .PeakLevel )
143- fmt .Fprintf (f , "Spectral Centroid: %.0f Hz\n " , om .SpectralCentroid )
144- fmt .Fprintf (f , "Spectral Rolloff: %.0f Hz\n " , om .SpectralRolloff )
158+ m := data .Result .Measurements // Input measurements for comparison
159+
160+ if m != nil {
161+ fmt .Fprintf (f , "Integrated Loudness: %.1f LUFS %s\n " , om .OutputI , formatComparison (om .OutputI , m .InputI , "LUFS" , 1 ))
162+ fmt .Fprintf (f , "True Peak: %.1f dBTP %s\n " , om .OutputTP , formatComparison (om .OutputTP , m .InputTP , "dBTP" , 1 ))
163+ fmt .Fprintf (f , "Loudness Range: %.1f LU %s\n " , om .OutputLRA , formatComparison (om .OutputLRA , m .InputLRA , "LU" , 1 ))
164+ fmt .Fprintf (f , "Dynamic Range: %.1f dB %s\n " , om .DynamicRange , formatComparison (om .DynamicRange , m .DynamicRange , "dB" , 1 ))
165+ fmt .Fprintf (f , "RMS Level: %.1f dBFS %s\n " , om .RMSLevel , formatComparison (om .RMSLevel , m .RMSLevel , "dBFS" , 1 ))
166+ fmt .Fprintf (f , "Peak Level: %.1f dBFS %s\n " , om .PeakLevel , formatComparison (om .PeakLevel , m .PeakLevel , "dBFS" , 1 ))
167+ fmt .Fprintf (f , "Spectral Centroid: %.0f Hz %s\n " , om .SpectralCentroid , formatComparison (om .SpectralCentroid , m .SpectralCentroid , "Hz" , 0 ))
168+ fmt .Fprintf (f , "Spectral Rolloff: %.0f Hz %s\n " , om .SpectralRolloff , formatComparison (om .SpectralRolloff , m .SpectralRolloff , "Hz" , 0 ))
169+ } else {
170+ fmt .Fprintf (f , "Integrated Loudness: %.1f LUFS\n " , om .OutputI )
171+ fmt .Fprintf (f , "True Peak: %.1f dBTP\n " , om .OutputTP )
172+ fmt .Fprintf (f , "Loudness Range: %.1f LU\n " , om .OutputLRA )
173+ fmt .Fprintf (f , "Dynamic Range: %.1f dB\n " , om .DynamicRange )
174+ fmt .Fprintf (f , "RMS Level: %.1f dBFS\n " , om .RMSLevel )
175+ fmt .Fprintf (f , "Peak Level: %.1f dBFS\n " , om .PeakLevel )
176+ fmt .Fprintf (f , "Spectral Centroid: %.0f Hz\n " , om .SpectralCentroid )
177+ fmt .Fprintf (f , "Spectral Rolloff: %.0f Hz\n " , om .SpectralRolloff )
178+ }
145179 if om .ZeroCrossingsRate > 0 {
146- fmt .Fprintf (f , "Zero Crossings Rate: %.4f\n " , om .ZeroCrossingsRate )
180+ if m != nil && m .ZeroCrossingsRate > 0 {
181+ fmt .Fprintf (f , "Zero Crossings Rate: %.4f %s\n " , om .ZeroCrossingsRate , formatComparisonNoUnit (om .ZeroCrossingsRate , m .ZeroCrossingsRate , 4 ))
182+ } else {
183+ fmt .Fprintf (f , "Zero Crossings Rate: %.4f\n " , om .ZeroCrossingsRate )
184+ }
147185 }
148186 if om .MaxDifference > 0 {
149187 maxDiffPercent := (om .MaxDifference / 32768.0 ) * 100.0
150- fmt .Fprintf (f , "Max Difference: %.1f%% FS (transient indicator)\n " , maxDiffPercent )
188+ if m != nil && m .MaxDifference > 0 {
189+ inputMaxDiffPercent := (m .MaxDifference / 32768.0 ) * 100.0
190+ fmt .Fprintf (f , "Max Difference: %.1f%% FS %s\n " , maxDiffPercent , formatComparison (maxDiffPercent , inputMaxDiffPercent , "% FS" , 1 ))
191+ } else {
192+ fmt .Fprintf (f , "Max Difference: %.1f%% FS (transient indicator)\n " , maxDiffPercent )
193+ }
151194 }
152195
153196 // Show silence sample comparison (same region as Pass 1)
154197 if om .SilenceSample != nil && data .Result .Measurements != nil && data .Result .Measurements .NoiseProfile != nil {
155198 ss := om .SilenceSample
156199 np := data .Result .Measurements .NoiseProfile
157200 fmt .Fprintf (f , "Silence Sample: %.1fs at %.1fs\n " , ss .Duration .Seconds (), ss .Start .Seconds ())
158- fmt .Fprintf (f , " Noise Floor: %.1f dBFS (was %.1f dBFS, %+.1f dB)\n " ,
159- ss .NoiseFloor , np .MeasuredNoiseFloor , ss .NoiseFloor - np .MeasuredNoiseFloor )
160- fmt .Fprintf (f , " Peak Level: %.1f dBFS (was %.1f dBFS, %+.1f dB)\n " ,
161- ss .PeakLevel , np .PeakLevel , ss .PeakLevel - np .PeakLevel )
162- fmt .Fprintf (f , " Crest Factor: %.1f dB (was %.1f dB)\n " ,
163- ss .CrestFactor , np .CrestFactor )
201+
202+ // Noise Floor with delta if changed
203+ if math .Abs (ss .NoiseFloor - np .MeasuredNoiseFloor ) < 0.05 {
204+ fmt .Fprintf (f , " Noise Floor: %.1f dBFS (unchanged)\n " , ss .NoiseFloor )
205+ } else {
206+ fmt .Fprintf (f , " Noise Floor: %.1f dBFS (was %.1f dBFS, %+.1f dB)\n " ,
207+ ss .NoiseFloor , np .MeasuredNoiseFloor , ss .NoiseFloor - np .MeasuredNoiseFloor )
208+ }
209+
210+ // Peak Level with delta if changed
211+ if math .Abs (ss .PeakLevel - np .PeakLevel ) < 0.05 {
212+ fmt .Fprintf (f , " Peak Level: %.1f dBFS (unchanged)\n " , ss .PeakLevel )
213+ } else {
214+ fmt .Fprintf (f , " Peak Level: %.1f dBFS (was %.1f dBFS, %+.1f dB)\n " ,
215+ ss .PeakLevel , np .PeakLevel , ss .PeakLevel - np .PeakLevel )
216+ }
217+
218+ // Crest Factor
219+ if math .Abs (ss .CrestFactor - np .CrestFactor ) < 0.05 {
220+ fmt .Fprintf (f , " Crest Factor: %.1f dB (unchanged)\n " , ss .CrestFactor )
221+ } else {
222+ fmt .Fprintf (f , " Crest Factor: %.1f dB %s\n " , ss .CrestFactor , formatComparison (ss .CrestFactor , np .CrestFactor , "dB" , 1 ))
223+ }
224+
164225 if ss .Entropy > 0 {
165226 // Classify noise type based on entropy
166227 noiseType := "broadband (hiss)"
@@ -169,22 +230,22 @@ func GenerateReport(data ReportData) error {
169230 } else if ss .Entropy < 0.9 {
170231 noiseType = "mixed"
171232 }
172- fmt .Fprintf (f , " Entropy: %.3f (%s)\n " , ss .Entropy , noiseType )
233+ // Show with comparison to input
234+ inputNoiseType := "broadband (hiss)"
235+ if np .Entropy < 0.7 {
236+ inputNoiseType = "tonal (hum/buzz)"
237+ } else if np .Entropy < 0.9 {
238+ inputNoiseType = "mixed"
239+ }
240+ if noiseType == inputNoiseType && math .Abs (ss .Entropy - np .Entropy ) < 0.0005 {
241+ fmt .Fprintf (f , " Noise Character: %s (unchanged)\n " , noiseType )
242+ } else if noiseType == inputNoiseType {
243+ fmt .Fprintf (f , " Noise Character: %s (entropy %.3f, was %.3f)\n " , noiseType , ss .Entropy , np .Entropy )
244+ } else {
245+ fmt .Fprintf (f , " Noise Character: %s (was %s)\n " , noiseType , inputNoiseType )
246+ }
173247 }
174248 }
175-
176- // Show deltas vs input for easy comparison
177- if data .Result .Measurements != nil {
178- m := data .Result .Measurements
179- fmt .Fprintln (f , "" )
180- fmt .Fprintln (f , "Changes from Input:" )
181- fmt .Fprintf (f , " LUFS: %+.1f dB\n " , om .OutputI - m .InputI )
182- fmt .Fprintf (f , " True Peak: %+.1f dB\n " , om .OutputTP - m .InputTP )
183- fmt .Fprintf (f , " Loudness Range: %+.1f LU\n " , om .OutputLRA - m .InputLRA )
184- fmt .Fprintf (f , " Dynamic Range: %+.1f dB\n " , om .DynamicRange - m .DynamicRange )
185- fmt .Fprintf (f , " Spectral Centroid: %+.0f Hz\n " , om .SpectralCentroid - m .SpectralCentroid )
186- }
187-
188249 } else {
189250 fmt .Fprintln (f , "Note: Output measurements not available" )
190251 }
0 commit comments