Skip to content

Commit cf48d39

Browse files
authored
Merge pull request #125 from ClojureCivitas/dsp-intro
Dsp intro draft
2 parents 3253c34 + c9d302b commit cf48d39

File tree

3 files changed

+274
-15
lines changed

3 files changed

+274
-15
lines changed

deps.edn

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111
metosin/malli {:mvn/version "0.19.1"}
1212
clj-fuzzy/clj-fuzzy {:mvn/version "0.4.1"}
1313
clj-thamil/clj-thamil {:mvn/version "0.2.0"}
14-
org.scicloj/clay {:mvn/version "2.0.2"
15-
#_#_:git/url "https://github.com/scicloj/clay.git"
16-
#_#_:git/sha "0240b1a4248fb2b1e67b933cbd51ef02a805ada9"}
17-
org.scicloj/kindly {:mvn/version "4-beta20"}
14+
org.scicloj/clay {:mvn/version "2.0.2"}
15+
org.scicloj/kindly {:mvn/version "4-beta21"}
1816
thi.ng/geom {:mvn/version "1.0.1"}
1917
org.eclipse.elk/org.eclipse.elk.core {:mvn/version "0.10.0"}
2018
org.eclipse.elk/org.eclipse.elk.graph {:mvn/version "0.10.0"}

site/db.edn

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
{:author
2-
[{:id :chouser
3-
:name "Chris Houser"
4-
:image "https://avatars.githubusercontent.com/u/36110?v=4"
5-
:url "https://chouser.us/"
6-
:links [{:icon "github" :href "https://github.com/chouser"}]}
7-
{:id :ezmiller
8-
:name "Ethan Miller"
9-
:image "https://avatars.githubusercontent.com/u/772738?v=4"
10-
:url "https://github.com/ezmiller"
2+
[{:id :chouser
3+
:name "Chris Houser"
4+
:image "https://avatars.githubusercontent.com/u/36110?v=4"
5+
:url "https://chouser.us/"
6+
:links [{:icon "github" :href "https://github.com/chouser"}]}
7+
{:id :eugnes
8+
:name "Eugen Nesbakken"
9+
:image "https://avatars.githubusercontent.com/u/46522650"
10+
:affiliation [:bu :endor]
11+
:links [{:icon "github" :href "https://github.com/eugnes03"}]}
12+
{:id :ezmiller
13+
:name "Ethan Miller"
14+
:image "https://avatars.githubusercontent.com/u/772738?v=4"
15+
:url "https://github.com/ezmiller"
1116
:affiliation [:scicloj]}
1217
{:id :kirahowe
1318
:name "Kira Howe"
@@ -19,7 +24,7 @@
1924
:name "Daniel Slutsky"
2025
:image "https://avatars.githubusercontent.com/u/5673102?v=4"
2126
:url "https://scicloj.github.io/contributors/daslu/"
22-
:affiliation [:scicloj]
27+
:affiliation [:scicloj :endor]
2328
:links [{:icon "github" :href "https://github.com/daslu"}]}
2429
{:id :danielsz
2530
:name "Daniel Szmulewicz"
@@ -186,7 +191,13 @@
186191
:url "https://techascent.com"}
187192
{:id :cim
188193
:name "cim"
189-
:url "https://cim.hkubs.hku.hk/"}]
194+
:url "https://cim.hkubs.hku.hk/"}
195+
{:id :endor
196+
:name "Endor Global"
197+
:url "https://endor.global"}
198+
{:id :bu
199+
:name "Boston University"
200+
:url "https://www.bu.edu/"}]
190201

191202
:topic
192203
[{:id :core

src/signal_processing/intro.clj

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
^{:kindly/hide-code true
2+
:clay {:title "DSP Intro"
3+
:quarto {:author [:eugnes :daslu]
4+
:description "Starting the journey of DSP in Clojure."
5+
:category :clojure
6+
:type :post
7+
:date "2025-11-07"
8+
:tags [:dsp :math :music]
9+
:draft true}}}
10+
(ns signal-processing.intro
11+
(:require [scicloj.kindly.v4.kind :as kind]
12+
[tech.v3.datatype :as dtype]
13+
[tech.v3.datatype.functional :as dfn]
14+
[clojure.math :as math]
15+
[tablecloth.api :as tc]
16+
[scicloj.tableplot.v1.plotly :as plotly]))
17+
18+
;; # Introduction to Digital Signal Processing
19+
;;
20+
;; **Notes from the [Scicloj DSP Study Group](https://scicloj.github.io/docs/community/groups/dsp-study/)**
21+
;; *First meeting - Nov. 2nd 2025*
22+
;;
23+
;; Welcome! These are notes from our first study group session, where we're learning
24+
;; digital signal processing together using Clojure. We're following the excellent book
25+
;; [**Think DSP** by Allen B. Downey](https://greenteapress.com/wp/think-dsp/) (available free online).
26+
;;
27+
;; **Huge thanks to Professor Downey** for writing such an accessible and free introduction to DSP, and for sharing with us the work-in-progress notebooks of [Think DSP 2](https://allendowney.github.io/ThinkDSP2/index.html).
28+
;;
29+
;;;; ## What is Digital Signal Processing?
30+
;;
31+
;; Sound waves are continuous vibrations in the air. To work with them on a computer,
32+
;; we need to **sample** them - take measurements at regular intervals. The **sample rate**
33+
;; tells us how many measurements per second. CD-quality audio uses 44,100 samples per second.
34+
;;
35+
;; This session covers concepts from **Chapter 1: Sounds and Signals** of Think DSP.
36+
37+
;; ## Clojure Libraries We're Using
38+
;;
39+
;; To work with DSP in Clojure, we're using tools from the Scicloj ecosystem:
40+
;;
41+
;; - **[Kindly](https://scicloj.github.io/kindly-noted/kindly)** - Visualization protocol that renders our data as interactive HTML elements (through Clay)
42+
;; - **[dtype-next](https://github.com/cnuernber/dtype-next)** - Efficient numerical arrays and vectorized operations (like NumPy for Clojure)
43+
;; - **[Tablecloth](https://scicloj.github.io/tablecloth/)** - DataFrame library for data manipulation and transformation
44+
;; - **[Tableplot](https://scicloj.github.io/tableplot/)** - Declarative plotting library built on Plotly
45+
;;
46+
;; These libraries let us work with large audio datasets efficiently while keeping our code
47+
;; clean and interactive. The `tech.v3.datatype.functional` namespace (aliased as `dfn`) provides
48+
;; vectorized math operations that work on entire arrays at once - essential for DSP!
49+
50+
(require '[scicloj.kindly.v4.kind :as kind]
51+
'[tech.v3.datatype :as dtype]
52+
'[tech.v3.datatype.functional :as dfn]
53+
'[clojure.math :as math]
54+
'[tablecloth.api :as tc]
55+
'[scicloj.tableplot.v1.plotly :as plotly])
56+
57+
;; ## Step 1: Creating Our First Sound Wave
58+
;;
59+
;; Let's start by generating a simple pure tone - a sine wave at 440 Hz (the note A4,
60+
;; which orchestras use for tuning). This is the foundation of all audio synthesis.
61+
62+
(def sample-rate 44100.0)
63+
64+
;; Now let's create the actual waveform. We'll generate 10 seconds of audio:
65+
;;
66+
;; - Create a time axis from 0 to 10 seconds
67+
;; - Calculate the sine wave: amplitude × sin(2π × frequency × time)
68+
;; - Store everything in a dataset so we can plot and analyze it
69+
70+
(def example-wave
71+
(let [duration 10
72+
num-samples (* duration sample-rate)
73+
;; Create a time vector: [0, 1/44100, 2/44100, ..., 10]
74+
time (dtype/make-reader :float32
75+
num-samples
76+
(/ idx sample-rate))
77+
freq 440 ;; A4 - the tuning note
78+
amp 3800 ;; Amplitude (loudness)
79+
;; Generate the sine wave
80+
value (-> time
81+
(dfn/* (* 2 Math/PI freq)) ;; Convert time to phase
82+
dfn/sin ;; Calculate sine
83+
(dfn/* amp))] ;; Scale by amplitude
84+
(tc/dataset {:time time
85+
:value value})))
86+
87+
;; Let's look at our dataset
88+
example-wave
89+
90+
;; ## Visualizing the Wave
91+
;;
92+
;; Here are the first 200 samples (about 4.5 milliseconds of audio).
93+
;; You can see the smooth oscillation of the sine wave.
94+
95+
(-> example-wave
96+
(tc/head 200)
97+
(plotly/layer-line {:=x :time
98+
:=y :value}))
99+
100+
;; ## Hearing the Sound
101+
;;
102+
;; Seeing is believing, but hearing is more fun! Let's create an audio player
103+
;; that will render this waveform as actual sound in your browser.
104+
105+
(defn audio [samples]
106+
(with-meta
107+
{:samples samples
108+
:sample-rate sample-rate}
109+
{:kind/audio true}))
110+
111+
;; Click play to hear the 440 Hz tone!
112+
(-> example-wave
113+
:value
114+
audio)
115+
116+
;; ## Step 2: Creating Complex Sounds - Violin Synthesis
117+
;;
118+
;; A pure sine wave sounds very artificial - like an old telephone tone.
119+
;; Real instruments are rich and complex because they produce many frequencies at once.
120+
;;
121+
;; The secret? **Additive synthesis** - combining multiple sine waves (called harmonics).
122+
;; A violin playing A4 doesn't just produce 440 Hz - it produces 440 Hz plus harmonics
123+
;; at 880 Hz, 1320 Hz, 1760 Hz, 2200 Hz, and more. Each harmonic has its own amplitude.
124+
;;
125+
;; Here are the main frequency components that give a violin its characteristic sound:
126+
127+
(def violin-components
128+
[[:A4 440 3800] ;; Fundamental (the note we hear)
129+
[:A5 880 2750] ;; 2nd harmonic (octave above)
130+
[:E6 1320 600] ;; 3rd harmonic
131+
[:A6 1760 700] ;; 4th harmonic
132+
[:C#7 2200 1900]]);; 5th harmonic
133+
134+
;; Now let's generate all five harmonics as separate columns in a dataset.
135+
;; Each harmonic is a sine wave at its own frequency and amplitude.
136+
137+
(def violin-components-dataset
138+
(let [duration 10
139+
num-samples (* duration sample-rate)
140+
time (dtype/make-reader :float32
141+
num-samples
142+
(/ idx sample-rate))]
143+
(->> violin-components
144+
;; For each [label frequency amplitude] triple, generate a sine wave
145+
(map (fn [[label freq amp]]
146+
[label (-> time
147+
(dfn/* (* 2 math/PI freq))
148+
dfn/sin
149+
(dfn/* amp))]))
150+
(into {:time time})
151+
tc/dataset)))
152+
153+
;; Our dataset now has columns: :time, :A4, :A5, :E6, :A6, :C#7
154+
violin-components-dataset
155+
156+
;; Let's visualize just the fundamental (A4) first:
157+
158+
(-> violin-components-dataset
159+
(tc/head 200)
160+
(plotly/layer-line {:=x :time
161+
:=y :A4}))
162+
163+
;; Now let's see all five harmonics together. First, we'll reshape the data
164+
;; from wide format (one column per harmonic) to long format (easier to plot):
165+
166+
(-> violin-components-dataset
167+
(tc/head 200)
168+
(tc/pivot->longer (complement #{:time})))
169+
170+
;; Plot all harmonics on the same chart, colored by frequency.
171+
;; Notice the mathematical relationships between these waves:
172+
;;
173+
;; - **A5 (880 Hz)** oscillates exactly twice as fast as A4 (440 Hz) - this is **one octave** higher
174+
;; - **E6 (1320 Hz)** is 3× the fundamental frequency (the 3rd harmonic)
175+
;; - **A6 (1760 Hz)** is 4× the fundamental (the 4th harmonic, two octaves up)
176+
;; - **C#7 (2200 Hz)** is 5× the fundamental (the 5th harmonic)
177+
;;
178+
;; These integer multiples create the **harmonic series**, which is fundamental to music theory
179+
;; and acoustics. When waves are related by simple ratios like 2:1, 3:1, 4:1, they blend together
180+
;; harmoniously - this is why octaves and perfect fifths sound so consonant!
181+
182+
(-> violin-components-dataset
183+
(tc/head 200)
184+
(tc/pivot->longer (complement #{:time}))
185+
(tc/rename-columns {:$value :value})
186+
(plotly/layer-line {:=x :time
187+
:=y :value
188+
:=color :$column}))
189+
190+
;; ## Step 3: Combining the Harmonics
191+
;;
192+
;; Now for the magic moment! When we add all five sine waves together,
193+
;; we get a complex waveform that sounds like a violin. This is the essence
194+
;; of additive synthesis - rich sounds emerge from simple building blocks.
195+
196+
(def violin-dataset
197+
(-> violin-components-dataset
198+
(tc/add-column :violin
199+
;; Add all five harmonics together
200+
#(dfn/+ (:A4 %)
201+
(:A5 %)
202+
(:E6 %)
203+
(:A6 %)
204+
(:C#7 %)))))
205+
206+
;; Look at the combined waveform - it's much more complex than a pure sine wave!
207+
;; The shape repeats at 440 Hz (the fundamental), but with rich texture from the harmonics.
208+
209+
(-> violin-dataset
210+
(tc/head 200)
211+
(plotly/layer-line {:=x :time
212+
:=y :violin}))
213+
214+
;; ## Listen to the Violin Sound
215+
;;
216+
;; This is the payoff! Compare this to the pure tone we heard earlier.
217+
;; The violin sound is warmer and more musical because of the harmonics.
218+
;; (We divide by 7000 to normalize the amplitude after adding the components)
219+
220+
(-> violin-dataset
221+
:violin
222+
(dfn// 7000.0)
223+
audio)
224+
225+
;; ## What We Learned
226+
;;
227+
;; In this first session, we covered the fundamentals from Chapter 1 of Think DSP:
228+
;;
229+
;; - **Sampling and sample rates** - Converting continuous signals to discrete measurements
230+
;; - **Generating sine waves** - The building blocks of all sound
231+
;; - **Additive synthesis with harmonics** - Creating complex sounds from simple components
232+
;;
233+
;; ## Next Steps
234+
;;
235+
;; In our next study group meetings, we'll explore:
236+
;;
237+
;; - **Chapter 2:** Harmonics and the Fourier transform
238+
;; - **Chapter 3:** Non-periodic signals and spectrograms
239+
;; - **Chapter 4:** Noise and filtering
240+
;;
241+
;; Join us at the [Scicloj DSP Study Group](https://scicloj.github.io/docs/community/groups/dsp-study/)!
242+
;;
243+
;; ---
244+
;;
245+
;; *Again, huge thanks to Allen B. Downey for Think DSP. If you find this resource valuable,
246+
;; consider [supporting his work](https://greenteapress.com/wp/) or sharing it with others.*
247+
248+
249+
250+

0 commit comments

Comments
 (0)