11import { analyser , dataArray } from "./audio.js" ;
22import { canvas , ctx } from "./canvas.js" ;
33
4+ /**
5+ * Sets up the canvas for high DPI displays with enhanced smoothing
6+ * Ensures proper scaling and sizing for optimal visual quality
7+ */
8+ function setupHighDPICanvas ( ) {
9+ // Get the container dimensions
10+ const container = canvas . parentElement ;
11+ const containerWidth = container ? container . clientWidth : window . innerWidth ;
12+ const containerHeight = container
13+ ? container . clientHeight
14+ : window . innerHeight ;
15+
16+ // Use device pixel ratio for better quality, but don't scale too much
17+ const dpr = Math . min ( window . devicePixelRatio || 1 , 2 ) ;
18+
19+ // Set the canvas CSS size to match container
20+ canvas . style . width = containerWidth + "px" ;
21+ canvas . style . height = containerHeight + "px" ;
22+
23+ // Set the canvas drawing buffer size with DPI scaling for higher resolution
24+ canvas . width = containerWidth * dpr ;
25+ canvas . height = containerHeight * dpr ;
26+
27+ // Scale all drawing operations by the DPI scaling factor
28+ ctx . scale ( dpr , dpr ) ;
29+
30+ // Enable image smoothing for anti-aliased lines
31+ ctx . imageSmoothingEnabled = true ;
32+ ctx . imageSmoothingQuality = "high" ;
33+
34+ // Set line join and cap for smoother lines
35+ ctx . lineJoin = "round" ;
36+ ctx . lineCap = "round" ;
37+ }
38+
39+ // Call this function once at startup
40+ setupHighDPICanvas ( ) ;
41+
42+ // Listen for resize events to maintain high DPI
43+ window . addEventListener ( "resize" , setupHighDPICanvas ) ;
44+
445/** @type {number } */
5- const defaultHeight = 1 ;
46+ const defaultHeight = 20 ; // Increased default height for better visibility
47+
48+ /**
49+ * Frequency bands configuration
50+ * Each band represents a range of frequencies in the audio spectrum
51+ * @typedef {Object } FrequencyBand
52+ * @property {string } name - Name of the frequency band
53+ * @property {string } color - Main color for the band visualization
54+ * @property {string } glowColor - Glow effect color
55+ * @property {number[] } range - Array with start and end indices in the frequency data array
56+ */
57+
58+ /** @type {FrequencyBand[] } */
59+ const BANDS = [
60+ {
61+ name : "high" ,
62+ color : "#00FFFF" ,
63+ glowColor : "rgba(0, 255, 255, 0.9)" ,
64+ range : [ 23 , 31 ] ,
65+ } ,
66+ {
67+ name : "mid-high" ,
68+ color : "#00BFFF" ,
69+ glowColor : "rgba(0, 191, 255, 0.9)" ,
70+ range : [ 16 , 22 ] ,
71+ } ,
72+ {
73+ name : "mid" ,
74+ color : "#FF1493" ,
75+ glowColor : "rgba(255, 20, 147, 0.9)" ,
76+ range : [ 9 , 15 ] ,
77+ } ,
78+ {
79+ name : "mid-low" ,
80+ color : "#FF00FF" ,
81+ glowColor : "rgba(255, 0, 255, 0.9)" ,
82+ range : [ 4 , 8 ] ,
83+ } ,
84+ {
85+ name : "low" ,
86+ color : "#8A2BE2" ,
87+ glowColor : "rgba(137, 43, 226, 0.9)" ,
88+ range : [ 0 , 3 ] ,
89+ } ,
90+ ] ;
91+
92+ // Animation time for wave movement
93+ let time = 0 ;
694
795/**
896 * Main drawing function that renders the audio visualization
97+ * This is called repeatedly by requestAnimationFrame
998 */
1099export function draw ( ) {
11- // Clear canvas
12- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
100+ // Update animation time for wave movement
101+ time += 0.009 ;
102+
103+ // Clear and prepare the canvas
104+ clearCanvas ( ) ;
105+
13106 // Show instructions
14107 drawInstructions ( ) ;
15- drawLine ( "green" ) ;
108+
109+ // Draw audio visualization
110+ drawVisualization ( ) ;
111+
112+ // Schedule the next frame
16113 requestAnimationFrame ( draw ) ;
17114}
18115
116+ /**
117+ * Clears the canvas and fills with background color
118+ */
119+ function clearCanvas ( ) {
120+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
121+ ctx . fillStyle = "#000000" ; // Pure black background
122+ ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
123+ }
124+
125+ /**
126+ * Draws the audio visualization based on current state
127+ */
128+ function drawVisualization ( ) {
129+ if ( analyser ) {
130+ // Get frequency data from audio analyzer
131+ analyser . getByteFrequencyData ( dataArray ) ;
132+ drawFrequencyBands ( ) ;
133+ } else {
134+ // Draw default waves when no audio is playing
135+ drawDefaultBands ( ) ;
136+ }
137+ }
138+
19139function drawInstructions ( ) {
20140 ctx . textAlign = "center" ;
141+ ctx . fillStyle = "white" ;
21142 ctx . fillText (
22143 "F: File Audio | V: Microphone | ESC: Stop Mic" ,
23144 canvas . width / 2 ,
@@ -26,18 +147,187 @@ function drawInstructions() {
26147}
27148
28149/**
29- * @param {string } color
150+ * Draws all frequency bands based on audio data
151+ * Each band is visualized as a sine wave with amplitude based on its frequency range
30152 */
31- function drawLine ( color ) {
32- // Draw visualization if audio is active
33- ctx . fillStyle = color ;
153+ function drawFrequencyBands ( ) {
154+ // Check if dataArray is initialized and has data
155+ if ( ! dataArray || dataArray . length === 0 ) {
156+ // Draw default waves if no audio data is available
157+ drawDefaultBands ( ) ;
158+ return ;
159+ }
34160
35- if ( analyser ) {
36- analyser . getByteFrequencyData ( dataArray ) ;
37- const avg = dataArray . reduce ( ( a , b ) => a + b , 0 ) / dataArray . length ;
38- const height = defaultHeight + ( avg / 255 ) * 100 ;
39- ctx . fillRect ( 0 , canvas . height / 2 - height / 2 , canvas . width , height ) ;
40- } else {
41- ctx . fillRect ( 0 , canvas . height / 2 , canvas . width , defaultHeight ) ;
161+ // Draw each frequency band
162+ BANDS . forEach ( ( band , index ) => {
163+ // Get the frequency data for this band
164+ const bandData = getBandData ( band , index ) ;
165+
166+ // Draw the line for this band
167+ drawLine (
168+ band . color ,
169+ band . glowColor ,
170+ index ,
171+ bandData . frequency ,
172+ bandData . amplitude ,
173+ time ,
174+ ) ;
175+ } ) ;
176+ }
177+
178+ /**
179+ * Draws default frequency bands when no audio data is available
180+ */
181+ function drawDefaultBands ( ) {
182+ BANDS . forEach ( ( band , index ) => {
183+ const defaultFrequency = 2 + index * 0.5 ;
184+ const defaultAmplitude = 20 + index * 5 ;
185+ drawLine (
186+ band . color ,
187+ band . glowColor ,
188+ index ,
189+ defaultFrequency ,
190+ defaultAmplitude ,
191+ time ,
192+ ) ;
193+ } ) ;
194+ }
195+
196+ /**
197+ * Calculates the frequency data for a specific band
198+ * @param {Object } band - The frequency band object
199+ * @param {number } index - The index of the band
200+ * @returns {Object } - Object containing frequency and amplitude data
201+ */
202+ function getBandData ( band , index ) {
203+ const [ start , end ] = band . range ;
204+
205+ // Ensure indices are within bounds
206+ const safeStart = Math . min ( start , dataArray . length - 1 ) ;
207+ const safeEnd = Math . min ( end , dataArray . length - 1 ) ;
208+
209+ // Calculate average frequency value for this band
210+ let sum = 0 ;
211+ for ( let i = safeStart ; i <= safeEnd ; i ++ ) {
212+ sum += dataArray [ i ] ;
213+ }
214+ const avgFrequency = sum / ( safeEnd - safeStart + 1 ) ;
215+
216+ // Calculate amplitude based on average frequency
217+ const amplitude = defaultHeight + ( avgFrequency / 255 ) * 100 ;
218+
219+ // Calculate frequency (number of waves) based on the band
220+ // Higher frequency bands get more waves
221+ const frequency = 2 + ( band . range [ 0 ] / dataArray . length ) * 10 ;
222+
223+ // Add a small random factor to make it more dynamic
224+ const randomFactor = Math . sin ( time * ( index + 1 ) ) * 5 ;
225+
226+ return {
227+ frequency,
228+ amplitude : amplitude + randomFactor ,
229+ } ;
230+ }
231+
232+ /**
233+ * Draws a single frequency band as a sine wave
234+ * @param {string } color - Color of the line
235+ * @param {string } glowColor - Color for the glow effect
236+ * @param {number } index - Band index for vertical positioning
237+ * @param {number } frequency - Number of waves to draw
238+ * @param {number } amplitude - Height of the waves
239+ * @param {number } time - Current animation time
240+ */
241+ function drawLine ( color , glowColor , index , frequency , amplitude , time ) {
242+ // Set up drawing context with enhanced visual effects
243+ setupLineStyle ( color , glowColor ) ;
244+
245+ // Draw the sine wave
246+ drawSineWave ( index , frequency , amplitude , time ) ;
247+
248+ // Reset shadow for next drawing
249+ ctx . shadowBlur = 0 ;
250+ }
251+
252+ /**
253+ * Sets up the line style with glow effect
254+ * @param {string } color - Main color of the line
255+ * @param {string } glowColor - Color for the glow effect
256+ */
257+ function setupLineStyle ( color , glowColor ) {
258+ ctx . shadowBlur = 15 ;
259+ ctx . shadowColor = glowColor ;
260+ ctx . strokeStyle = color ;
261+ ctx . lineWidth = 6 ; // Thicker lines for smoother appearance
262+ }
263+
264+ /**
265+ * Draws a sine wave with an envelope function
266+ * @param {number } index - Band index for phase calculation
267+ * @param {number } frequency - Number of waves to draw
268+ * @param {number } amplitude - Height of the waves
269+ * @param {number } time - Current animation time
270+ */
271+ function drawSineWave ( index , frequency , amplitude , time ) {
272+ ctx . beginPath ( ) ;
273+
274+ // Common vertical center for all lines
275+ const verticalCenter = canvas . height / 2 ;
276+
277+ // Start at the left edge - all lines start at the same point
278+ ctx . moveTo ( 0 , verticalCenter ) ;
279+
280+ // Draw the sine wave points
281+ for ( let x = 0 ; x < canvas . width ; x ++ ) {
282+ // Calculate the y position for this point
283+ const y = calculateWavePoint (
284+ x ,
285+ verticalCenter ,
286+ index ,
287+ frequency ,
288+ amplitude ,
289+ time ,
290+ ) ;
291+ ctx . lineTo ( x , y ) ;
42292 }
293+
294+ // Stroke with glow effect
295+ ctx . stroke ( ) ;
296+ }
297+
298+ /**
299+ * Calculates a single point on the sine wave
300+ * @param {number } x - X coordinate
301+ * @param {number } verticalCenter - Vertical center position
302+ * @param {number } index - Band index for phase calculation
303+ * @param {number } frequency - Number of waves
304+ * @param {number } amplitude - Height of the waves
305+ * @param {number } time - Current animation time
306+ * @returns {number } - Y coordinate
307+ */
308+ function calculateWavePoint (
309+ x ,
310+ verticalCenter ,
311+ index ,
312+ frequency ,
313+ amplitude ,
314+ time ,
315+ ) {
316+ // Calculate position relative to center (0 to 1, where 0.5 is center)
317+ const relativePos = x / canvas . width ;
318+
319+ // Create an envelope that peaks in the center and is flat at the edges
320+ // Using a modified bell curve (Gaussian function)
321+ const envelope = Math . exp ( - Math . pow ( ( relativePos - 0.5 ) * 5 , 2 ) ) ;
322+
323+ // Calculate phase shift based on time and band index
324+ const phase = time + ( index * Math . PI ) / 5 ;
325+
326+ // Calculate the sine wave with the envelope
327+ return (
328+ verticalCenter +
329+ Math . sin ( ( x * ( Math . PI * 2 * frequency ) ) / canvas . width + phase ) *
330+ amplitude *
331+ envelope
332+ ) ;
43333}
0 commit comments