Skip to content

Commit c430f76

Browse files
committed
Nowrway meetup - wip
1 parent a110b48 commit c430f76

1 file changed

Lines changed: 123 additions & 86 deletions

File tree

src/clojure_norway/meetup_2025_12/violin.clj

Lines changed: 123 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
(kind/audio
3737
{:src violin-file-name})
3838

39+
(kind/audio
40+
{:src (str "clojure_norway/meetup_2025_12/" violin-file-name)})
41+
3942
;; ## Reading the Wav file as data
4043

4144
;; [Reading WAV files](https://clojurecivitas.github.io/dsp/wav_files.html).
@@ -91,6 +94,8 @@ wav-format
9194
(with-open [wav-stream (io/input-stream violin-file-path)]
9295
(audio-data wav-stream)))
9396

97+
(require '[tech.v3.datatype :as dtype])
98+
9499
(def wav-samples
95100
;; one of the two stereo channels
96101
(dfn// (->> wav-shorts
@@ -131,92 +136,96 @@ wav-ds
131136
(apply concat))
132137
:sample-rate sample-rate}
133138

134-
;; ## Computing the power spectrum of the data
139+
;; ## Computing the Discrete Fouried Transform the data
135140

136141
(import 'com.github.psambit9791.jdsp.transform.DiscreteFourier)
137142

138143
(count wav-samples)
139144

140-
(def some-part
141-
(->> wav-samples
142-
(drop sample-rate) ; drop a second
143-
(take (* sample-rate 0.06)) ; take 6% of a second
144-
))
145-
146-
(def power-spectrum
147-
(let [fft (-> some-part
148-
double-array
149-
DiscreteFourier.)]
150-
(.transform fft)
151-
;; Get magnitude spectrum (power)
152-
(.getMagnitude fft true)))
153-
154-
(count power-spectrum)
145+
(defn some-part [t0 t1]
146+
(dtype/sub-buffer
147+
wav-samples
148+
(int (* sample-rate t0))
149+
(int (* sample-rate (- t1 t0)))))
155150

156151
(def Nyquist-freq (* 0.5 sample-rate))
157152

158-
(def power-spectrum-ds
159-
(tc/dataset
160-
{:freq (dfn// (range)
161-
(/ (count power-spectrum) Nyquist-freq))
162-
:power power-spectrum}))
163-
164-
(-> power-spectrum-ds
153+
(defn data->dft-ds [data]
154+
(let [dft (-> data
155+
double-array
156+
DiscreteFourier.)]
157+
(.transform dft)
158+
(let [amp (.getMagnitude dft true)
159+
phase (.getPhaseRad dft true)]
160+
(-> (tc/dataset {:freq (dfn// (range)
161+
(/ (count amp) Nyquist-freq))
162+
:amplitude amp
163+
:phase phase})
164+
(tc/add-column :power #(tcc/sq (:amplitude %)))))))
165+
166+
(def some-dft-ds
167+
(data->dft-ds
168+
(some-part 1 1.05)))
169+
170+
(-> some-dft-ds
165171
(plotly/layer-line {:=x :freq
166172
:=y :power}))
167173

168-
;; ## Finding peaks (WIP)
174+
;; ## Finding peaks
169175

170176
(import 'com.github.psambit9791.jdsp.signal.peaks.FindPeak)
171177

172-
(def n-peaks 10)
173-
174-
(def peaks-ds
175-
(let [peaks (-> power-spectrum
178+
(defn dft-ds->peaks-ds [dft-ds {:keys [n-peaks]}]
179+
(let [peaks (-> dft-ds
180+
:power
181+
double-array
176182
FindPeak.
177183
.detectPeaks)]
178-
(-> (tc/dataset {:freq (dfn// (.getPeaks peaks)
179-
(/ (count power-spectrum) Nyquist-freq ))
180-
:power (.getHeights peaks)
181-
:prominence (.getProminence peaks)})
184+
(.getPeaks peaks)
185+
(-> dft-ds
186+
(tc/select-rows (dtype/as-reader
187+
(.getPeaks peaks)))
188+
(tc/add-column :prominence (.getProminence peaks))
182189
(tc/order-by [:prominence] :desc)
183190
(tc/head n-peaks))))
184191

185-
(-> power-spectrum-ds
192+
(-> some-dft-ds
186193
(plotly/base {:=x :freq
187194
:=y :power})
188195
plotly/layer-line
189-
(plotly/layer-point {:=dataset peaks-ds}))
190-
196+
(plotly/layer-point {:=dataset (dft-ds->peaks-ds some-dft-ds
197+
{:n-peaks 10})}))
191198

192199
;; ## Synthesizing from the peaks
193200

194201
(require '[clojure.math :as math])
195-
(require '[tech.v3.datatype :as dtype])
196202

197-
(def components-dataset
198-
(let [duration 1
199-
num-samples (* duration sample-rate)
203+
(defn peaks-ds->components-ds [peaks-ds {:keys [duration]}]
204+
(let [num-samples (* duration sample-rate)
200205
time (dtype/make-reader :float32
201206
num-samples
202207
(/ idx sample-rate))]
203208
(->> (tc/rows peaks-ds :as-maps)
204209
;; For each peak, generate a sine wave
205-
(map-indexed (fn [i {:keys [freq power]}]
210+
(map-indexed (fn [i {:keys [freq power phase]}]
206211
[(str "wave" i)
207212
(-> time
208213
(dfn/* (* 2 math/PI freq))
209-
dfn/sin
214+
;; (dfn/+ phase)
215+
dfn/cos
210216
(dfn/* power))]))
211217
(into {:time time})
212218
tc/dataset)))
213219

214-
(-> components-dataset
215-
(tc/head 200)
216-
(tc/pivot->longer (complement #{:time})))
220+
(-> some-dft-ds
221+
(dft-ds->peaks-ds {:n-peaks 10})
222+
(peaks-ds->components-ds {:duration 1})
223+
(tc/head 500))
217224

218-
(-> components-dataset
219-
(tc/head 200)
225+
(-> some-dft-ds
226+
(dft-ds->peaks-ds {:n-peaks 10})
227+
(peaks-ds->components-ds {:duration 1})
228+
(tc/head 500)
220229
(tc/pivot->longer (complement #{:time}))
221230
(tc/rename-columns {:$value :value})
222231
(plotly/layer-line {:=x :time
@@ -227,8 +236,8 @@ wav-ds
227236
(dfn// values
228237
(double (dfn/reduce-max (dfn/abs values)))))
229238

230-
(def combined-dataset
231-
(-> components-dataset
239+
(defn components-ds->combined-ds [components-ds]
240+
(-> components-ds
232241
(tc/add-column :combined
233242
(fn [ds]
234243
(-> ds
@@ -237,8 +246,11 @@ wav-ds
237246
(->> (apply tcc/+))
238247
normalize)))))
239248

240-
(-> combined-dataset
241-
(tc/head 200)
249+
(-> some-dft-ds
250+
(dft-ds->peaks-ds {:n-peaks 10})
251+
(peaks-ds->components-ds {:duration 1})
252+
components-ds->combined-ds
253+
(tc/head 500)
242254
(plotly/layer-line {:=x :time
243255
:=y :combined}))
244256

@@ -248,22 +260,24 @@ wav-ds
248260
:sample-rate sample-rate}
249261
{:kind/audio true}))
250262

251-
(-> some-part
263+
(-> (some-part 1 1.05)
252264
audio)
253265

254-
(-> combined-dataset
266+
(-> some-dft-ds
267+
(dft-ds->peaks-ds {:n-peaks 10})
268+
(peaks-ds->components-ds {:duration 1})
269+
components-ds->combined-ds
255270
:combined
256271
audio)
257272

258273
;; ## Spectogram (WIP)
259274

260275
(import 'com.github.psambit9791.jdsp.windows.Hanning)
261-
(require '[tech.v3.dataset.tensor :as ds-tensor]
262-
'[tech.v3.libs.buffered-image :as bufimg])
263276

264277

265-
(def spectrogram
266-
(let [window-size 0.1 ; seconds
278+
279+
(def stft
280+
(let [window-size 0.05 ; seconds
267281
window-samples (* sample-rate window-size)
268282

269283
;; Create Hanning window to reduce spectral leakage
@@ -298,36 +312,43 @@ wav-ds
298312
(fn [i j]
299313
(* (windows i j)
300314
(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])
315+
:float32)]
316+
317+
(-> hanninged-windows
318+
;; Rearrange to [windows × time]
319+
(tensor/transpose [1 0])
320+
(tensor/slice 1)
321+
;; Apply FFT to each window
322+
(->> (pmap (fn [window]
323+
(let [dft (-> window
324+
dtype/as-reader
325+
double-array
326+
DiscreteFourier.)]
327+
(.transform dft)
328+
(let [amp (.getMagnitude dft true)
329+
phase (.getPhaseRad dft true)]
330+
(-> (tc/dataset {:freq (dfn// (range)
331+
(/ (count amp) Nyquist-freq))
332+
:amplitude amp
333+
:phase phase})
334+
(tc/add-column :power #(tcc/sq (:amplitude %))))))))))))
335+
336+
337+
(require '[tech.v3.dataset.tensor :as ds-tensor])
338+
339+
(def spectrogram
340+
(-> stft
341+
(->> (map :power))
342+
tensor/->tensor
343+
;; Reshape to [windows × frequencies]
344+
(#(tensor/reshape %
345+
[(count %)
346+
(-> % first count)]))
347+
;; Transpose to [frequencies × windows] for plotting
348+
(tensor/transpose [1 0])))
349+
350+
351+
(require '[clojure2d.color :as color])
331352

332353
(def palette
333354
(let [p (color/palette :viridis)]
@@ -338,6 +359,8 @@ wav-ds
338359
(def normalized-spectrogram
339360
(normalize spectrogram))
340361

362+
(require '[tech.v3.libs.buffered-image :as bufimg])
363+
341364
(-> (tensor/compute-tensor
342365
(conj (dtype/shape spectrogram) 3)
343366
(fn [y x c]
@@ -346,4 +369,18 @@ wav-ds
346369
bufimg/tensor->image)
347370

348371

372+
;; ## Playing the spectrogram
373+
374+
375+
376+
(-> stft
377+
(->> (map (fn [dft]
378+
(-> dft
379+
(dft-ds->peaks-ds {:n-peaks 5})
380+
(peaks-ds->components-ds {:duration 0.005})
381+
components-ds->combined-ds
382+
(tc/select-columns [:combined]))))
383+
(apply tc/concat))
384+
:combined
385+
audio)
349386

0 commit comments

Comments
 (0)