|
| 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