Skip to content

Commit a110b48

Browse files
committed
Norway meetup wip
1 parent 417d1d4 commit a110b48

1 file changed

Lines changed: 90 additions & 80 deletions

File tree

src/clojure_norway/meetup_2025_12/violin.clj

Lines changed: 90 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
[tech.v3.tensor :as tensor]
99
[tablecloth.column.api :as tcc]
1010
[fastmath.stats :as stats]
11-
[tech.v3.datatype :as dtype]))
11+
[tech.v3.datatype :as dtype]
12+
[clojure.math :as math]))
1213

1314
;; ## Data source
1415
;;
@@ -223,8 +224,8 @@ wav-ds
223224
:=color :$column}))
224225

225226
(defn normalize [values]
226-
(tcc// values
227-
(tcc/reduce-max (tcc/abs values))))
227+
(dfn// values
228+
(double (dfn/reduce-max (dfn/abs values)))))
228229

229230
(def combined-dataset
230231
(-> components-dataset
@@ -257,83 +258,92 @@ wav-ds
257258
;; ## Spectogram (WIP)
258259

259260
(import 'com.github.psambit9791.jdsp.windows.Hanning)
260-
(require '[tech.v3.dataset.tensor :as ds-tensor])
261-
262-
(let [window-size 0.03 ; seconds
263-
window-samples (* sample-rate window-size)
264-
265-
;; Create Hanning window to reduce spectral leakage
266-
hanning (-> window-samples
267-
Hanning.
268-
.getWindow
269-
dtype/as-reader)
270-
271-
;; Overlap parameters
272-
overlap-fraction 0.05
273-
hop (int (* overlap-fraction window-samples))
274-
275-
n-samples (count wav-samples)
276-
277-
;; Calculate how many windows we can extract
278-
n-windows (-> n-samples
279-
(- window-samples)
280-
(quot hop))
281-
282-
;; Create sliding windows: [time × windows × channels]
283-
windows-shape [window-samples n-windows]
284-
windows (tensor/compute-tensor
285-
windows-shape
286-
(fn [i j]
287-
;; Extract sample at time i, window j, channel k
288-
(wav-samples (+ (* j hop) i)))
289-
:float32)
290-
291-
;; Apply Hanning window to each segment
292-
hanninged-windows (tensor/compute-tensor
293-
windows-shape
294-
(fn [i j]
295-
(* (windows i j)
296-
(hanning i)))
297-
:float32)
298-
299-
;; ;; Compute power spectrum for each window
300-
spectogram (-> hanninged-windows
301-
;; Rearrange to [windows × time]
302-
(tensor/transpose [1 0])
303-
(tensor/slice 1)
304-
;; Apply FFT to each window
305-
(->> (pmap (fn [window]
306-
(let [fft (-> window
307-
dtype/as-reader
308-
double-array
309-
DiscreteFourier.)]
310-
(.transform fft)
311-
;; Get magnitude spectrum (power)
312-
(.getMagnitude fft true)))))
313-
tensor/->tensor
314-
;; Reshape to [windows × frequencies]
315-
(#(tensor/reshape %
316-
[n-windows
317-
(-> % first count)]))
318-
;; Transpose to [frequencies × windows] for plotting
319-
(tensor/transpose [1 0]))]
320-
;; (-> windows
321-
;; (dfn/* 100)
322-
;; plotly/imshow)
323-
324-
;; (-> hanninged-windows
325-
;; (dfn/* 100)
326-
;; plotly/imshow)
327-
328-
(-> spectogram
329-
(#(tensor/broadcast % (cons 3 (dtype/shape %))))
330-
(dfn/* 50)
331-
(tensor/transpose [1 2 0])
332-
plotly/imshow))
333-
334-
335-
336-
261+
(require '[tech.v3.dataset.tensor :as ds-tensor]
262+
'[tech.v3.libs.buffered-image :as bufimg])
263+
264+
265+
(def spectrogram
266+
(let [window-size 0.1 ; seconds
267+
window-samples (* sample-rate window-size)
268+
269+
;; Create Hanning window to reduce spectral leakage
270+
hanning (-> window-samples
271+
Hanning.
272+
.getWindow
273+
dtype/as-reader)
274+
275+
;; Overlap parameters
276+
overlap-fraction 0.1
277+
hop (int (* overlap-fraction window-samples))
278+
279+
n-samples (count wav-samples)
280+
281+
;; Calculate how many windows we can extract
282+
n-windows (-> n-samples
283+
(- window-samples)
284+
(quot hop))
285+
286+
;; Create sliding windows: [time × windows × channels]
287+
windows-shape [window-samples n-windows]
288+
windows (tensor/compute-tensor
289+
windows-shape
290+
(fn [i j]
291+
;; Extract sample at time i, window j, channel k
292+
(wav-samples (+ (* j hop) i)))
293+
:float32)
294+
295+
;; Apply Hanning window to each segment
296+
hanninged-windows (tensor/compute-tensor
297+
windows-shape
298+
(fn [i j]
299+
(* (windows i j)
300+
(hanning i)))
301+
:float32)
302+
303+
;; ;; Compute power spectrum for each window
304+
spectrogram (-> hanninged-windows
305+
;; Rearrange to [windows × time]
306+
(tensor/transpose [1 0])
307+
(tensor/slice 1)
308+
;; Apply FFT to each window
309+
(->> (pmap (fn [window]
310+
(let [fft (-> window
311+
dtype/as-reader
312+
double-array
313+
DiscreteFourier.)]
314+
(.transform fft)
315+
;; Get magnitude spectrum (power)
316+
(-> fft
317+
(.getMagnitude true)
318+
(dtype/as-reader)
319+
(dtype/sub-buffer 0 2000))))))
320+
tensor/->tensor
321+
;; Reshape to [windows × frequencies]
322+
(#(tensor/reshape %
323+
[n-windows
324+
(-> % first count)]))
325+
;; Transpose to [frequencies × windows] for plotting
326+
(tensor/transpose [1 0]))]
327+
328+
spectrogram))
329+
330+
(require '[clojure2d.color])
331+
332+
(def palette
333+
(let [p (color/palette :viridis)]
334+
(memoize
335+
(fn [ratio]
336+
(p (int (math/round (* ratio (dec (count p))))))))))
337+
338+
(def normalized-spectrogram
339+
(normalize spectrogram))
340+
341+
(-> (tensor/compute-tensor
342+
(conj (dtype/shape spectrogram) 3)
343+
(fn [y x c]
344+
((palette (normalized-spectrogram y x))
345+
c)))
346+
bufimg/tensor->image)
337347

338348

339349

0 commit comments

Comments
 (0)