Skip to content

Commit 1664c37

Browse files
committedMar 2, 2025
faster transmission and better ui
1 parent bf9d710 commit 1664c37

File tree

6 files changed

+2105
-457
lines changed

6 files changed

+2105
-457
lines changed
 

‎src/App.css

+568-67
Large diffs are not rendered by default.

‎src/App.tsx

+856-45
Large diffs are not rendered by default.
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.frequency-visualizer {
2+
position: relative;
3+
width: 100%;
4+
height: 175px;
5+
overflow: hidden;
6+
background-color: #000;
7+
border-radius: 4px;
8+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
9+
transition: all 0.3s ease;
10+
}
11+
12+
.frequency-visualizer canvas {
13+
position: absolute;
14+
top: 0;
15+
left: 0;
16+
width: 100%;
17+
height: 100%;
18+
transition: box-shadow 0.3s ease;
19+
}
20+
21+
/* Add a subtle glow effect for the transmission mode */
22+
.frequency-visualizer canvas.transmit-active {
23+
box-shadow: 0 0 15px rgba(0, 255, 80, 0.3),
24+
inset 0 0 25px rgba(0, 255, 80, 0.2);
25+
border: 1px solid rgba(0, 255, 120, 0.4);
26+
}
27+
28+
/* Add a pulsing animation for the transmission active state */
29+
@keyframes transmitPulse {
30+
0% { box-shadow: 0 0 15px rgba(0, 255, 80, 0.3), inset 0 0 25px rgba(0, 255, 80, 0.2); }
31+
50% { box-shadow: 0 0 25px rgba(0, 255, 80, 0.5), inset 0 0 35px rgba(0, 255, 80, 0.3); }
32+
100% { box-shadow: 0 0 15px rgba(0, 255, 80, 0.3), inset 0 0 25px rgba(0, 255, 80, 0.2); }
33+
}
34+
35+
.frequency-visualizer canvas.transmit-active {
36+
animation: transmitPulse 2s infinite ease-in-out;
37+
}

‎src/components/FrequencyVisualizer.tsx

+175-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,185 @@
1-
import React from 'react';
1+
import React, { useRef, useEffect } from 'react';
2+
import './FrequencyVisualizer.css';
3+
import { MAXIMUM_VALID_FREQUENCY } from '../utils/audioCodec';
24

35
interface FrequencyVisualizerProps {
46
frequencies: number[];
7+
transmitMode?: boolean;
58
}
69

7-
const FrequencyVisualizer: React.FC<FrequencyVisualizerProps> = ({ frequencies }) => {
10+
const FrequencyVisualizer: React.FC<FrequencyVisualizerProps> = ({
11+
frequencies,
12+
transmitMode = false
13+
}) => {
14+
const canvasRef = useRef<HTMLCanvasElement>(null);
15+
16+
useEffect(() => {
17+
const canvas = canvasRef.current;
18+
if (!canvas) return;
19+
20+
const ctx = canvas.getContext('2d');
21+
if (!ctx) return;
22+
23+
// Clear the canvas
24+
ctx.clearRect(0, 0, canvas.width, canvas.height);
25+
26+
// Set background color
27+
ctx.fillStyle = '#000'; // Black background
28+
ctx.fillRect(0, 0, canvas.width, canvas.height);
29+
30+
// Draw vertical lines for separation
31+
ctx.strokeStyle = transmitMode ? 'rgba(0, 100, 0, 0.3)' : 'rgba(50, 50, 50, 0.5)';
32+
ctx.lineWidth = 1;
33+
34+
const numDivisions = 10;
35+
const divisionWidth = canvas.width / numDivisions;
36+
37+
// Draw frequency scale (Hz) at the bottom
38+
const maxFreq = MAXIMUM_VALID_FREQUENCY; // Use imported constant
39+
ctx.fillStyle = transmitMode ? 'rgba(0, 180, 40, 0.8)' : 'rgba(49, 133, 255, 0.8)';
40+
ctx.font = '10px monospace';
41+
42+
// Add padding to ensure edge labels don't get cut off
43+
const edgePadding = 30; // Pixels to pad on each side
44+
const usableWidth = canvas.width - (edgePadding * 2);
45+
46+
for (let i = 0; i <= numDivisions; i++) {
47+
// Calculate positions with padding
48+
const normalizedPosition = i / numDivisions;
49+
const x = edgePadding + (normalizedPosition * usableWidth);
50+
51+
// Calculate the frequency at this position
52+
const freq = Math.round(normalizedPosition * maxFreq);
53+
54+
// Set text alignment based on position
55+
if (i === 0) {
56+
ctx.textAlign = 'left'; // Left-align the first label
57+
} else if (i === numDivisions) {
58+
ctx.textAlign = 'right'; // Right-align the last label
59+
} else {
60+
ctx.textAlign = 'center'; // Center-align all other labels
61+
}
62+
63+
// Draw frequency label
64+
ctx.fillText(`${freq}Hz`, x, canvas.height - 5);
65+
66+
// Skip drawing the line at position 0
67+
if (i === 0) continue;
68+
69+
// Draw division line (keeping original grid divisions)
70+
const gridX = i * (canvas.width / numDivisions);
71+
ctx.beginPath();
72+
ctx.moveTo(gridX, 0);
73+
ctx.lineTo(gridX, canvas.height - 15); // Make space for the labels
74+
ctx.stroke();
75+
}
76+
77+
// Only draw if we have data
78+
if (!frequencies.length) return;
79+
80+
// Adjust the drawing area for the frequency bars to account for padding
81+
const graphWidth = canvas.width - (2 * edgePadding);
82+
const barWidth = graphWidth / frequencies.length;
83+
84+
// Draw each frequency bar
85+
frequencies.forEach((value, i) => {
86+
const x = edgePadding + (i * barWidth);
87+
88+
// Normalize value to fit in canvas (values typically 0-255)
89+
// Increase minimum height to make small values more visible
90+
const normalizedHeight = (value / 255) * (canvas.height - 20);
91+
const height = Math.max(3, normalizedHeight); // Increased minimum height from 1px to 3px
92+
const y = canvas.height - 20 - height;
93+
94+
// Create a gradient for the bar color - use brighter colors for better visibility
95+
let gradient;
96+
if (transmitMode) {
97+
// Brighter green gradient for transmission mode
98+
gradient = ctx.createLinearGradient(0, y, 0, canvas.height - 20);
99+
gradient.addColorStop(0, 'rgba(0, 255, 120, 1.0)'); // Increased brightness and opacity
100+
gradient.addColorStop(1, 'rgba(0, 220, 80, 0.9)'); // Increased brightness and opacity
101+
} else {
102+
// Brighter cyan/blue gradient
103+
gradient = ctx.createLinearGradient(0, y, 0, canvas.height - 20);
104+
gradient.addColorStop(0, 'rgba(20, 255, 255, 1.0)'); // Increased brightness and opacity
105+
gradient.addColorStop(1, 'rgba(65, 155, 255, 0.9)'); // Increased brightness and opacity
106+
}
107+
108+
ctx.fillStyle = gradient;
109+
110+
// Draw a bar with increased width and slight glow effect
111+
const barWidthAdjusted = Math.max(2, barWidth - 1); // Ensure minimum width of 2px
112+
113+
ctx.beginPath();
114+
ctx.moveTo(x, y); // Top left
115+
ctx.lineTo(x + barWidthAdjusted, y); // Top right
116+
ctx.lineTo(x + barWidthAdjusted, canvas.height - 20); // Bottom right
117+
ctx.lineTo(x, canvas.height - 20); // Bottom left
118+
ctx.closePath();
119+
ctx.fill();
120+
121+
// Add glow effect to make bars more visible
122+
if (value > 50) { // Only add glow to significant signals
123+
ctx.shadowColor = transmitMode ? 'rgba(0, 255, 120, 0.7)' : 'rgba(20, 255, 255, 0.7)';
124+
ctx.shadowBlur = 3;
125+
ctx.fillRect(x, y, barWidthAdjusted, height);
126+
ctx.shadowBlur = 0; // Reset shadow for other elements
127+
}
128+
});
129+
130+
// Draw key frequency markers for important ranges
131+
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
132+
ctx.textAlign = 'center';
133+
ctx.font = '9px monospace';
134+
135+
// Draw markers for important ranges from our audio codec
136+
const keyRanges = [
137+
{ freq: 900, label: 'SPACE' },
138+
{ freq: 1300, label: 'SPECIAL' },
139+
{ freq: 4700, label: 'NUMBERS' },
140+
{ freq: 5700, label: 'LETTERS' },
141+
{ freq: 2500, label: 'START' },
142+
{ freq: 2700, label: 'END' }
143+
];
144+
145+
// Use the same padding logic for marker positions
146+
keyRanges.forEach(marker => {
147+
// Calculate normalized position (0-1) and apply padding
148+
const normalizedPos = marker.freq / maxFreq;
149+
const x = edgePadding + (normalizedPos * usableWidth);
150+
151+
// Draw a dashed vertical line
152+
ctx.setLineDash([2, 2]);
153+
ctx.strokeStyle = transmitMode ? 'rgba(0, 255, 0, 0.4)' : 'rgba(255, 255, 255, 0.4)';
154+
ctx.beginPath();
155+
ctx.moveTo(x, 0);
156+
ctx.lineTo(x, canvas.height - 20);
157+
ctx.stroke();
158+
ctx.setLineDash([]); // Reset dash
159+
160+
// Adjust text alignment based on position
161+
if (normalizedPos < 0.05) {
162+
ctx.textAlign = 'left';
163+
} else if (normalizedPos > 0.95) {
164+
ctx.textAlign = 'right';
165+
} else {
166+
ctx.textAlign = 'center';
167+
}
168+
169+
// Draw the label
170+
ctx.fillText(marker.label, x, 10);
171+
});
172+
173+
}, [frequencies, transmitMode]);
174+
8175
return (
9176
<div className="frequency-visualizer">
10-
{frequencies.map((value, index) => (
11-
<div
12-
key={index}
13-
className="frequency-bar"
14-
style={{
15-
height: `${Math.min(value, 150)}px`,
16-
}}
17-
/>
18-
))}
177+
<canvas
178+
ref={canvasRef}
179+
width={800}
180+
height={175} // Slightly increased height to ensure all labels are visible
181+
className={transmitMode ? 'transmit-active' : ''}
182+
/>
19183
</div>
20184
);
21185
};

‎src/utils/audioCodec.ts

+469-334
Large diffs are not rendered by default.

‎unifont.woff

3.08 MB
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.