@@ -71,65 +71,83 @@ const Waveform: FC<{ audio: AudioBuffer }> = ({ audio }) => {
71
71
const Sonogram : FC < { audio : AudioBuffer } > = ( { audio } ) => {
72
72
const canvas = useRef < HTMLCanvasElement > ( null ) ;
73
73
74
- const fttData = useMemo ( ( ) => {
75
- const fftSize = 512 ; // Ensure this is a power of two
76
- const fft = new FFT ( fftSize ) ;
77
- const data = audio . getChannelData ( 0 ) ;
74
+ const imageData = useMemo ( ( ) => {
75
+ const fftSize = 512 ;
76
+ const data = spectrum ( audio , fftSize ) ;
77
+ const image = spectrumToImage ( data , fftSize ) ;
78
78
79
- const chunks = Math . floor ( audio . length / fftSize ) ;
80
- const target = new Float32Array ( fftSize * chunks ) ;
81
-
82
- const sample = new Array ( fftSize ) ;
83
- const out = fft . createComplexArray ( ) ;
84
-
85
- let max = - Infinity ;
79
+ return image ;
80
+ } , [ audio ] ) ;
86
81
87
- for ( let i = 0 ; i < chunks ; i ++ ) {
88
- const offset = i * fftSize ;
89
- for ( let i = 0 ; i < sample . length ; i ++ ) sample [ i ] = data [ i + offset ] ;
90
- fft . realTransform ( out , sample ) ;
82
+ useEffect ( ( ) => {
83
+ const ctx = canvas . current ?. getContext ( "2d" ) ;
84
+ if ( ctx ) {
85
+ ctx . canvas . width = imageData . width ;
86
+ ctx . canvas . height = imageData . height ;
91
87
92
- for ( let j = 0 ; j < fftSize ; j ++ ) {
93
- target [ i * fftSize + j ] = Math . abs ( out [ j ] ) ;
94
- max = Math . max ( max , target [ i * fftSize + j ] ) ;
95
- }
88
+ ctx . putImageData ( imageData , 0 , 0 ) ;
96
89
}
90
+ } , [ imageData ] ) ;
97
91
98
- max = max / 4 ;
92
+ return < canvas ref = { canvas } className = { styles . waveform } /> ;
93
+ } ;
99
94
100
- const image = new ImageData ( chunks , fftSize ) ;
95
+ // load the spectrum for an entire audio buffer
96
+ function spectrum (
97
+ audio : AudioBuffer ,
98
+ fftSize : 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096
99
+ ) : Float32Array {
100
+ const fft = new FFT ( fftSize ) ;
101
+ const data = audio . getChannelData ( 0 ) ;
101
102
102
- for ( let i = 0 ; i < chunks ; i ++ ) {
103
- for ( let j = 0 ; j < fftSize ; j ++ ) {
104
- const idx = j * chunks + i ;
105
- const value = target [ i * fftSize + j ] ;
103
+ const chunks = Math . floor ( audio . length / fftSize ) ;
104
+ const target = new Float32Array ( fftSize * chunks ) ;
106
105
107
- const scaledValue = Math . pow ( value / max , 0.3 ) ; // Apply a power scale
106
+ const sample = new Array ( fftSize ) ;
107
+ const out = fft . createComplexArray ( ) ;
108
108
109
- const [ r , g , b , a ] = valueToColor ( scaledValue ) ;
109
+ for ( let i = 0 ; i < chunks ; i ++ ) {
110
+ const offset = i * fftSize ;
111
+ for ( let i = 0 ; i < sample . length ; i ++ ) sample [ i ] = data [ i + offset ] ;
112
+ fft . realTransform ( out , sample ) ;
110
113
111
- image . data [ idx * 4 ] = r ;
112
- image . data [ idx * 4 + 1 ] = g ;
113
- image . data [ idx * 4 + 2 ] = b ;
114
- image . data [ idx * 4 + 3 ] = a ;
115
- }
114
+ for ( let j = 0 ; j < fftSize ; j ++ ) {
115
+ target [ i * fftSize + j ] = Math . abs ( out [ j ] ) ;
116
116
}
117
+ }
117
118
118
- return image ;
119
- } , [ audio ] ) ;
119
+ return target ;
120
+ }
120
121
121
- useEffect ( ( ) => {
122
- const ctx = canvas . current ?. getContext ( "2d" ) ;
123
- if ( ctx ) {
124
- ctx . canvas . width = fttData . width ;
125
- ctx . canvas . height = fttData . height ;
122
+ // Render a spectrum as a visible image to be written to canvas
123
+ function spectrumToImage (
124
+ spectrum : Float32Array ,
125
+ fftSize : 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096
126
+ ) {
127
+ const chunks = spectrum . length / fftSize ;
128
+
129
+ const image = new ImageData ( chunks , fftSize ) ;
130
+
131
+ const max = spectrum . reduce ( ( a , b ) => Math . max ( a , b ) , - Infinity ) ;
126
132
127
- ctx . putImageData ( fttData , 0 , 0 ) ;
133
+ for ( let i = 0 ; i < chunks ; i ++ ) {
134
+ for ( let j = 0 ; j < fftSize ; j ++ ) {
135
+ const idx = j * chunks + i ;
136
+ const value = spectrum [ i * fftSize + j ] ;
137
+
138
+ const scaledValue = Math . pow ( value / max , 0.3 ) ; // Apply a power scale
139
+
140
+ const [ r , g , b , a ] = valueToColor ( scaledValue ) ;
141
+
142
+ image . data [ idx * 4 ] = r ;
143
+ image . data [ idx * 4 + 1 ] = g ;
144
+ image . data [ idx * 4 + 2 ] = b ;
145
+ image . data [ idx * 4 + 3 ] = a ;
128
146
}
129
- } , [ fttData ] ) ;
147
+ }
130
148
131
- return < canvas ref = { canvas } className = { styles . waveform } /> ;
132
- } ;
149
+ return image ;
150
+ }
133
151
134
152
function valueToColor ( value : number ) {
135
153
const r = value < 0.5 ? 0 : 255 * ( value - 0.5 ) * 2 ;
0 commit comments