Skip to content

Commit 783c100

Browse files
authored
Merge pull request #36 from Fury7425/update
Update
2 parents 76b2c25 + 95255a1 commit 783c100

File tree

2 files changed

+172
-109
lines changed

2 files changed

+172
-109
lines changed

src-tauri/src/audio/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -597,8 +597,8 @@ impl AudioEngine {
597597
clip_count: 0,
598598
sample_rate: input_config.sample_rate.0,
599599
recent_mono: Vec::new(),
600-
rough_fr_hz: logspace(20.0, (input_config.sample_rate.0 as f32 * 0.45).min(20_000.0), 32),
601-
rough_fr_db: vec![0.0; 32],
600+
rough_fr_hz: logspace(20.0, (input_config.sample_rate.0 as f32 * 0.45).min(20_000.0), 48),
601+
rough_fr_db: vec![0.0; 48],
602602
}));
603603

604604
let err_fn = |err| {
@@ -631,7 +631,7 @@ impl AudioEngine {
631631
);
632632
if !next_rough.is_empty() && state.rough_fr_db.len() == next_rough.len() {
633633
for (prev, next) in state.rough_fr_db.iter_mut().zip(next_rough.iter()) {
634-
*prev = *prev * 0.65 + *next * 0.35;
634+
*prev = *prev * 0.78 + *next * 0.22;
635635
}
636636
}
637637
let _ = app.emit(

src/ui/pages/sweep-fr-page.tsx

Lines changed: 169 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function SweepFrPage({
6565

6666
const currentNorm = Math.min(1, Math.max(0, (monitor.currentDbfs + 96) / 96));
6767
const peakNorm = Math.min(1, Math.max(0, (monitor.peakDbfs + 96) / 96));
68-
const roughFrPath = (() => {
68+
const roughFrGraph = (() => {
6969
const freqs = monitor.roughFrHz;
7070
const values = monitor.roughFrDb;
7171
if (freqs.length < 2 || values.length < 2 || freqs.length !== values.length) {
@@ -77,15 +77,58 @@ export function SweepFrPage({
7777
const maxLog = Math.log10(maxHz);
7878
const spanLog = Math.max(1e-6, maxLog - minLog);
7979

80-
return values
81-
.map((value, index) => {
82-
const hz = Math.min(maxHz, Math.max(minHz, freqs[index]));
83-
const x = ((Math.log10(hz) - minLog) / spanLog) * 100;
84-
const clamped = Math.max(-18, Math.min(18, value));
85-
const y = ((18 - clamped) / 36) * 100;
86-
return `${x.toFixed(2)},${y.toFixed(2)}`;
80+
const smoothValues = values.map((_, index) => {
81+
let weightedSum = 0;
82+
let weightTotal = 0;
83+
for (let offset = -2; offset <= 2; offset += 1) {
84+
const target = index + offset;
85+
if (target < 0 || target >= values.length) {
86+
continue;
87+
}
88+
const weight = offset === 0 ? 3 : Math.abs(offset) === 1 ? 2 : 1;
89+
weightedSum += values[target] * weight;
90+
weightTotal += weight;
91+
}
92+
return weightTotal > 0 ? weightedSum / weightTotal : values[index];
93+
});
94+
95+
const points = smoothValues.map((value, index) => {
96+
const hz = Math.min(maxHz, Math.max(minHz, freqs[index]));
97+
const x = ((Math.log10(hz) - minLog) / spanLog) * 100;
98+
const clamped = Math.max(-18, Math.min(18, value));
99+
const y = 10 + ((18 - clamped) / 36) * 80;
100+
return { x, y };
101+
});
102+
103+
if (points.length < 2) {
104+
return null;
105+
}
106+
107+
let linePath = `M ${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)}`;
108+
for (let i = 1; i < points.length - 1; i += 1) {
109+
const midX = (points[i].x + points[i + 1].x) / 2;
110+
const midY = (points[i].y + points[i + 1].y) / 2;
111+
linePath += ` Q ${points[i].x.toFixed(2)} ${points[i].y.toFixed(2)} ${midX.toFixed(2)} ${midY.toFixed(2)}`;
112+
}
113+
const last = points[points.length - 1];
114+
linePath += ` T ${last.x.toFixed(2)} ${last.y.toFixed(2)}`;
115+
116+
const xGuides = [20, 100, 1000, 10000]
117+
.map((freq) => {
118+
const x = ((Math.log10(freq) - minLog) / spanLog) * 100;
119+
if (!Number.isFinite(x) || x < 0 || x > 100) {
120+
return null;
121+
}
122+
const label = freq >= 1000 ? `${Math.round(freq / 1000)}k` : `${freq}`;
123+
return { x, label };
87124
})
88-
.join(" ");
125+
.filter((entry): entry is { x: number; label: string } => entry !== null);
126+
127+
return {
128+
linePath,
129+
areaPath: `${linePath} L 100 100 L 0 100 Z`,
130+
xGuides
131+
};
89132
})();
90133

91134
useEffect(() => {
@@ -219,71 +262,35 @@ export function SweepFrPage({
219262
<p className="muted" style={{ marginTop: 0 }}>
220263
{monitor.status}
221264
</p>
222-
<div className="field-grid-2" style={{ marginBottom: 12 }}>
223-
<div>
224-
<div className="level-meter" style={{ marginBottom: 12 }}>
225-
<div className="level-meter-grid" />
226-
<div className="level-meter-bars">
227-
{meterHistory.map((level, index) => (
228-
<span
229-
key={`meter-${index}`}
230-
className={`level-meter-bar ${
231-
level > 0.92 ? "is-hot" : level > 0.72 ? "is-warm" : ""
232-
}`.trim()}
233-
style={{
234-
height: `${Math.max(8, level * 100)}%`
235-
}}
236-
/>
237-
))}
238-
</div>
239-
<span className="level-meter-peak" style={{ left: `${peakNorm * 100}%` }} />
240-
</div>
241-
<div className="field-grid-3">
242-
<div className="field-row">
243-
<span className="field-label">Current Level</span>
244-
<strong>{monitor.currentDbfs.toFixed(1)} dBFS</strong>
245-
</div>
246-
<div className="field-row">
247-
<span className="field-label">Peak Level</span>
248-
<strong>{monitor.peakDbfs.toFixed(1)} dBFS</strong>
249-
</div>
250-
<div className="field-row">
251-
<span className="field-label">SPL Estimate</span>
252-
<strong>{monitor.splEstimate.toFixed(1)} dB SPL</strong>
253-
</div>
254-
</div>
265+
<div className="level-meter" style={{ marginBottom: 12 }}>
266+
<div className="level-meter-grid" />
267+
<div className="level-meter-bars">
268+
{meterHistory.map((level, index) => (
269+
<span
270+
key={`meter-${index}`}
271+
className={`level-meter-bar ${
272+
level > 0.92 ? "is-hot" : level > 0.72 ? "is-warm" : ""
273+
}`.trim()}
274+
style={{
275+
height: `${Math.max(8, level * 100)}%`
276+
}}
277+
/>
278+
))}
255279
</div>
256-
<div className="level-meter">
257-
<div
258-
style={{
259-
display: "flex",
260-
justifyContent: "space-between",
261-
alignItems: "center",
262-
marginBottom: 8
263-
}}
264-
>
265-
<span className="field-label">Live Rough FR (Pink Noise)</span>
266-
<span className="muted">{pinkNoisePlaying ? "Live" : "Idle"}</span>
267-
</div>
268-
<svg viewBox="0 0 100 100" style={{ width: "100%", height: 100, display: "block" }}>
269-
<line x1="0" y1="50" x2="100" y2="50" stroke="var(--level-grid)" strokeWidth="1" />
270-
<line x1="0" y1="25" x2="100" y2="25" stroke="var(--level-grid)" strokeWidth="0.6" />
271-
<line x1="0" y1="75" x2="100" y2="75" stroke="var(--level-grid)" strokeWidth="0.6" />
272-
{pinkNoisePlaying && roughFrPath ? (
273-
<polyline
274-
points={roughFrPath}
275-
fill="none"
276-
stroke="var(--accent-strong)"
277-
strokeWidth="1.8"
278-
strokeLinejoin="round"
279-
strokeLinecap="round"
280-
/>
281-
) : (
282-
<text x="50" y="54" textAnchor="middle" fontSize="7" fill="var(--text-muted)">
283-
Start Pink Noise + Monitoring
284-
</text>
285-
)}
286-
</svg>
280+
<span className="level-meter-peak" style={{ left: `${peakNorm * 100}%` }} />
281+
</div>
282+
<div className="field-grid-3">
283+
<div className="field-row">
284+
<span className="field-label">Current Level</span>
285+
<strong>{monitor.currentDbfs.toFixed(1)} dBFS</strong>
286+
</div>
287+
<div className="field-row">
288+
<span className="field-label">Peak Level</span>
289+
<strong>{monitor.peakDbfs.toFixed(1)} dBFS</strong>
290+
</div>
291+
<div className="field-row">
292+
<span className="field-label">SPL Estimate</span>
293+
<strong>{monitor.splEstimate.toFixed(1)} dB SPL</strong>
287294
</div>
288295
</div>
289296
{monitor.clipCount > 0 && (
@@ -314,42 +321,98 @@ export function SweepFrPage({
314321
</section>
315322

316323
<section className="page-card">
317-
<h3 className="section-subheading">Sweep FR Results</h3>
318-
<div className="scroll-box" style={{ minHeight: 468, maxHeight: 468 }}>
319-
<pre className="mono-pre">
320-
{lastResult
321-
? JSON.stringify(lastResult, null, 2)
322-
: "No sweep result yet. Run Sweep to populate this panel."}
323-
</pre>
324-
</div>
325-
<div className="row-end" style={{ marginTop: 12 }}>
326-
<button
327-
type="button"
328-
className="skin-btn secondary"
329-
disabled={running || !hasSweepResult}
330-
onClick={onExportLastJson}
331-
>
332-
Export LAST (JSON)
333-
</button>
334-
<button
335-
type="button"
336-
className="skin-btn secondary"
337-
disabled={running || !hasSweepHistory}
338-
onClick={onExportAllJson}
339-
>
340-
Export ALL (JSON)
341-
</button>
342-
<button
343-
type="button"
344-
className="skin-btn secondary"
345-
disabled={running || !hasSweepResult}
346-
onClick={onExportLastSquiglink}
347-
>
348-
Export LAST to Squiglink
349-
</button>
324+
<h3 className="section-subheading">Live Rough FR (Pink Noise)</h3>
325+
<p className="muted" style={{ marginTop: 0 }}>
326+
{pinkNoisePlaying ? "Live preview running" : "Start Pink Noise + Monitoring"}
327+
</p>
328+
<div className="level-meter">
329+
<svg viewBox="0 0 100 100" style={{ width: "100%", height: 128, display: "block" }}>
330+
<line x1="0" y1="10" x2="100" y2="10" stroke="var(--level-grid)" strokeWidth="0.6" />
331+
<line x1="0" y1="50" x2="100" y2="50" stroke="var(--level-grid)" strokeWidth="1" />
332+
<line x1="0" y1="90" x2="100" y2="90" stroke="var(--level-grid)" strokeWidth="0.6" />
333+
{(roughFrGraph?.xGuides ?? []).map((guide) => (
334+
<g key={`guide-${guide.label}-${guide.x.toFixed(2)}`}>
335+
<line
336+
x1={guide.x}
337+
y1="8"
338+
x2={guide.x}
339+
y2="92"
340+
stroke="var(--level-grid)"
341+
strokeWidth="0.45"
342+
/>
343+
<text x={guide.x} y="98" textAnchor="middle" fontSize="6" fill="var(--text-muted)">
344+
{guide.label}
345+
</text>
346+
</g>
347+
))}
348+
<text x="2" y="12" fontSize="6" fill="var(--text-muted)">
349+
+18 dB
350+
</text>
351+
<text x="2" y="52" fontSize="6" fill="var(--text-muted)">
352+
0 dB
353+
</text>
354+
<text x="2" y="92" fontSize="6" fill="var(--text-muted)">
355+
-18 dB
356+
</text>
357+
{pinkNoisePlaying && roughFrGraph ? (
358+
<>
359+
<path d={roughFrGraph.areaPath} fill="var(--accent-dim)" opacity="0.2" />
360+
<path
361+
d={roughFrGraph.linePath}
362+
fill="none"
363+
stroke="var(--accent-strong)"
364+
strokeWidth="2"
365+
strokeLinejoin="round"
366+
strokeLinecap="round"
367+
/>
368+
</>
369+
) : (
370+
<text x="50" y="54" textAnchor="middle" fontSize="7" fill="var(--text-muted)">
371+
Waiting for live data
372+
</text>
373+
)}
374+
</svg>
350375
</div>
351376
</section>
352377
</div>
378+
379+
<section className="page-card" style={{ marginTop: 12 }}>
380+
<h3 className="section-subheading">Sweep FR Results</h3>
381+
<div className="scroll-box" style={{ minHeight: 468, maxHeight: 468 }}>
382+
<pre className="mono-pre">
383+
{lastResult
384+
? JSON.stringify(lastResult, null, 2)
385+
: "No sweep result yet. Run Sweep to populate this panel."}
386+
</pre>
387+
</div>
388+
<div className="row-end" style={{ marginTop: 12 }}>
389+
<button
390+
type="button"
391+
className="skin-btn secondary"
392+
disabled={running || !hasSweepResult}
393+
onClick={onExportLastJson}
394+
>
395+
Export LAST (JSON)
396+
</button>
397+
<button
398+
type="button"
399+
className="skin-btn secondary"
400+
disabled={running || !hasSweepHistory}
401+
onClick={onExportAllJson}
402+
>
403+
Export ALL (JSON)
404+
</button>
405+
<button
406+
type="button"
407+
className="skin-btn secondary"
408+
disabled={running || !hasSweepResult}
409+
onClick={onExportLastSquiglink}
410+
>
411+
Export LAST to Squiglink
412+
</button>
413+
</div>
414+
</section>
415+
353416
</section>
354417
</div>
355418
);

0 commit comments

Comments
 (0)