DSP Study Group - Intro: Building the Violin Sound from Sine Waves
Notes from the Scicloj DSP Study Group
First meeting - Nov. 2nd 2025
Welcome! These are notes from our first study group session, where we’re learning digital signal processing together using Clojure. We’re following the excellent book Think DSP by Allen B. Downey (available free online).
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.
What is Digital Signal Processing?
Sound waves are continuous vibrations in the air. To work with them on a computer, we need to sample them - take measurements at regular intervals. The sample rate tells us how many measurements per second. CD-quality audio uses 44,100 samples per second.
This session covers concepts from Chapter 1: Sounds and Signals of Think DSP.
Clojure Libraries We’re Using
To work with DSP in Clojure, we’re using tools from the Scicloj ecosystem:
- Kindly - Visualization protocol that renders our data as interactive HTML elements (through Clay)
- dtype-next - Efficient numerical arrays and vectorized operations (like NumPy for Clojure)
- Tablecloth - DataFrame library for data manipulation and transformation
- Tableplot - Declarative plotting library built on Plotly
These libraries let us work with large audio datasets efficiently while keeping our code clean and interactive. The tech.v3.datatype.functional namespace (aliased as dfn) provides vectorized math operations that work on entire arrays at once - essential for DSP!
(require '[scicloj.kindly.v4.kind :as kind]
'[tech.v3.datatype :as dtype]
'[tech.v3.datatype.functional :as dfn]
'[clojure.math :as math]
'[tablecloth.api :as tc]
'[scicloj.tableplot.v1.plotly :as plotly])Step 1: Creating Our First Sound Wave
Let’s start by generating a simple pure tone - a sine wave at 440 Hz (the note A4, which orchestras use for tuning). This is the foundation of all audio synthesis.
(def sample-rate 44100.0)Now let’s create the actual waveform. We’ll generate 10 seconds of audio:
- Create a time axis from 0 to 10 seconds
- Calculate the sine wave: amplitude × sin(2π × frequency × time)
- Store everything in a dataset so we can plot and analyze it
(def example-wave
(let [duration 10
num-samples (* duration sample-rate)
;; Create a time vector: [0, 1/44100, 2/44100, ..., 10]
time (dtype/make-reader :float32
num-samples
(/ idx sample-rate))
freq 440 ;; A4 - the tuning note
amp 3800 ;; Amplitude (loudness)
;; Generate the sine wave
value (-> time
(dfn/* (* 2 Math/PI freq)) ;; Convert time to phase
dfn/sin ;; Calculate sine
(dfn/* amp))] ;; Scale by amplitude
(tc/dataset {:time time
:value value})))Let’s look at our dataset
example-wave_unnamed [441000 2]:
| :time | :value |
|---|---|
| 0.00000000 | 0.00000000 |
| 0.00002268 | 238.06363188 |
| 0.00004535 | 475.19199004 |
| 0.00006803 | 710.45347514 |
| 0.00009070 | 942.92382219 |
| 0.00011338 | 1171.68973162 |
| 0.00013605 | 1395.85245743 |
| 0.00015873 | 1614.53133798 |
| 0.00018141 | 1826.86725586 |
| 0.00020408 | 2032.02601309 |
| … | … |
| 9.99975057 | -2417.61940363 |
| 9.99977324 | -2229.20160836 |
| 9.99979592 | -2032.02601309 |
| 9.99981859 | -1826.86725587 |
| 9.99984127 | -1614.53133799 |
| 9.99986395 | -1395.85245742 |
| 9.99988662 | -1171.68973162 |
| 9.99990930 | -942.92382218 |
| 9.99993197 | -710.45347514 |
| 9.99995465 | -475.19199003 |
| 9.99997732 | -238.06363188 |
Visualizing the Wave
Here are the first 200 samples (about 4.5 milliseconds of audio). You can see the smooth oscillation of the sine wave.
(-> example-wave
(tc/head 200)
(plotly/layer-line {:=x :time
:=y :value}))Hearing the Sound
Seeing is believing, but hearing is more fun! Let’s create an audio player that will render this waveform as actual sound in your browser.
(defn audio [samples]
(with-meta
{:samples samples
:sample-rate sample-rate}
{:kind/audio true}))Click play to hear the 440 Hz tone!
(-> example-wave
:value
audio)Step 2: Creating Complex Sounds - Violin Synthesis
A pure sine wave sounds very artificial - like an old telephone tone. Real instruments are rich and complex because they produce many frequencies at once.
The secret? Additive synthesis - combining multiple sine waves (called harmonics). A violin playing A4 doesn’t just produce 440 Hz - it produces 440 Hz plus harmonics at 880 Hz, 1320 Hz, 1760 Hz, 2200 Hz, and more. Each harmonic has its own amplitude.
Here are the main frequency components that give a violin its characteristic sound:
(def violin-components
[[:A4 440 3800] ;; Fundamental (the note we hear)
[:A5 880 2750] ;; 2nd harmonic (octave above)
[:E6 1320 600] ;; 3rd harmonic
[:A6 1760 700] ;; 4th harmonic
[:C#7 2200 1900] ;; 5th harmonic
])Now let’s generate all five harmonics as separate columns in a dataset. Each harmonic is a sine wave at its own frequency and amplitude.
(def violin-components-dataset
(let [duration 10
num-samples (* duration sample-rate)
time (dtype/make-reader :float32
num-samples
(/ idx sample-rate))]
(->> violin-components
;; For each [label frequency amplitude] triple, generate a sine wave
(map (fn [[label freq amp]]
[label (-> time
(dfn/* (* 2 math/PI freq))
dfn/sin
(dfn/* amp))]))
(into {:time time})
tc/dataset)))Our dataset now has columns: :time, :A4, :A5, :E6, :A6, :C#7
violin-components-dataset_unnamed [441000 6]:
| :time | :A4 | :A5 | :E6 | :A6 | :C#7 |
|---|---|---|---|---|---|
| 0.00000000 | 0.00000000 | 0.00000000 | 0.00000000 | 0.00000000 | 0.00000000 |
| 0.00002268 | 238.06363188 | 343.88894016 | 112.17686450 | 173.69649356 | 585.84486581 |
| 0.00004535 | 475.19199004 | 682.37908185 | 220.39775644 | 336.52817871 | 1114.60080418 |
| 0.00006803 | 710.45347514 | 1010.15638367 | 320.84621259 | 478.30984661 | 1534.74204239 |
| 0.00009070 | 942.92382219 | 1322.07498780 | 409.97986852 | 590.17297823 | 1805.32700348 |
| 0.00011338 | 1171.68973162 | 1613.23800605 | 484.65538181 | 665.12047497 | 1899.98794726 |
| 0.00013605 | 1395.85245743 | 1879.07439740 | 542.23928357 | 698.46433088 | 1809.50043213 |
| 0.00015873 | 1614.53133798 | 2115.41072984 | 580.70084907 | 688.11886999 | 1542.68221086 |
| 0.00018141 | 1826.86725586 | 2318.53670019 | 598.68371219 | 634.73120639 | 1125.53396547 |
| 0.00020408 | 2032.02601309 | 2485.26338305 | 595.55369837 | 541.64076694 | 598.70561424 |
| … | … | … | … | … | … |
| 9.99975057 | -2417.61940363 | -2699.66148475 | -527.13722923 | -261.76219429 | 572.95438684 |
| 9.99977324 | -2229.20160836 | -2612.97329451 | -571.42118909 | -414.67040834 | -13.53509199 |
| 9.99979592 | -2032.02601309 | -2485.26338305 | -595.55369836 | -541.64076694 | -598.70561423 |
| 9.99981859 | -1826.86725587 | -2318.53670020 | -598.68371219 | -634.73120638 | -1125.53396548 |
| 9.99984127 | -1614.53133799 | -2115.41072985 | -580.70084907 | -688.11886999 | -1542.68221084 |
| 9.99986395 | -1395.85245742 | -1879.07439738 | -542.23928358 | -698.46433088 | -1809.50043214 |
| 9.99988662 | -1171.68973162 | -1613.23800604 | -484.65538181 | -665.12047497 | -1899.98794726 |
| 9.99990930 | -942.92382218 | -1322.07498779 | -409.97986853 | -590.17297823 | -1805.32700348 |
| 9.99993197 | -710.45347514 | -1010.15638367 | -320.84621260 | -478.30984661 | -1534.74204241 |
| 9.99995465 | -475.19199003 | -682.37908183 | -220.39775644 | -336.52817871 | -1114.60080415 |
| 9.99997732 | -238.06363188 | -343.88894015 | -112.17686450 | -173.69649356 | -585.84486581 |
Let’s visualize just the fundamental (A4) first:
(-> violin-components-dataset
(tc/head 200)
(plotly/layer-line {:=x :time
:=y :A4}))Now let’s see all five harmonics together. First, we’ll reshape the data from wide format (one column per harmonic) to long format (easier to plot):
(-> violin-components-dataset
(tc/head 200)
(tc/pivot->longer (complement #{:time})))_unnamed [1000 3]:
| :time | :\(column | :\)value | |
|---|---|---|
| 0.00000000 | :A4 | 0.00000000 |
| 0.00002268 | :A4 | 238.06363188 |
| 0.00004535 | :A4 | 475.19199004 |
| 0.00006803 | :A4 | 710.45347514 |
| 0.00009070 | :A4 | 942.92382219 |
| 0.00011338 | :A4 | 1171.68973162 |
| 0.00013605 | :A4 | 1395.85245743 |
| 0.00015873 | :A4 | 1614.53133798 |
| 0.00018141 | :A4 | 1826.86725586 |
| 0.00020408 | :A4 | 2032.02601309 |
| 0.00022676 | :A4 | 2229.20160836 |
| 0.00024943 | :A4 | 2417.61940362 |
| 0.00027211 | :A4 | 2596.53916731 |
| 0.00029478 | :A4 | 2765.25798252 |
| 0.00031746 | :A4 | 2923.11300851 |
| 0.00034014 | :A4 | 3069.48408479 |
| 0.00036281 | :A4 | 3203.79616754 |
| 0.00038549 | :A4 | 3325.52158876 |
| 0.00040816 | :A4 | 3434.18212930 |
| 0.00043084 | :A4 | 3529.35089763 |
| 0.00045351 | :A4 | 3610.65400696 |
| 0.00047619 | :A4 | 3677.77204411 |
| 0.00049887 | :A4 | 3730.44132438 |
| 0.00052154 | :A4 | 3768.45492750 |
| 0.00054422 | :A4 | 3791.66351051 |
| 0.00056689 | :A4 | 3799.97589452 |
| 0.00058957 | :A4 | 3793.35942292 |
| 0.00061224 | :A4 | 3771.84008965 |
| 0.00063492 | :A4 | 3735.50243708 |
| 0.00065760 | :A4 | 3684.48922391 |
| 0.00068027 | :A4 | 3619.00086427 |
| 0.00070295 | :A4 | 3539.29464039 |
| 0.00072562 | :A4 | 3445.68369181 |
| 0.00074830 | :A4 | 3338.53578517 |
| 0.00077098 | :A4 | 3218.27186934 |
| 0.00079365 | :A4 | 3085.36442172 |
| 0.00081633 | :A4 | 2940.33559195 |
| 0.00083900 | :A4 | 2783.75515060 |
| 0.00086168 | :A4 | 2616.23825074 |
| 0.00088435 | :A4 | 2438.44301118 |
| 0.00090703 | :A4 | 2251.06793095 |
| 0.00092971 | :A4 | 2054.84914511 |
| 0.00095238 | :A4 | 1850.55753274 |
| 0.00097506 | :A4 | 1638.99568840 |
| 0.00099773 | :A4 | 1420.99476901 |
| 0.00102041 | :A4 | 1197.41122849 |
| 0.00104308 | :A4 | 969.12345303 |
| 0.00106576 | :A4 | 737.02831024 |
| 0.00108844 | :A4 | 502.03762560 |
| 0.00111111 | :A4 | 265.07460023 |
| 0.00113379 | :A4 | 27.07018393 |
| 0.00115646 | :A4 | -211.04058223 |
| 0.00117914 | :A4 | -448.32223934 |
| 0.00120181 | :A4 | -683.84258580 |
| 0.00122449 | :A4 | -916.67633964 |
| 0.00124717 | :A4 | -1145.90877360 |
| 0.00126984 | :A4 | -1370.63930884 |
| 0.00129252 | :A4 | -1589.98505299 |
| 0.00131519 | :A4 | -1803.08426875 |
| 0.00133787 | :A4 | -2009.09975938 |
| 0.00136054 | :A4 | -2207.22215778 |
| 0.00138322 | :A4 | -2396.67310620 |
| 0.00140590 | :A4 | -2576.70831416 |
| 0.00142857 | :A4 | -2746.62048254 |
| 0.00145125 | :A4 | -2905.74208231 |
| 0.00147392 | :A4 | -3053.44797703 |
| 0.00149660 | :A4 | -3189.15787881 |
| 0.00151927 | :A4 | -3312.33862809 |
| 0.00154195 | :A4 | -3422.50628820 |
| 0.00156463 | :A4 | -3519.22804664 |
| 0.00158730 | :A4 | -3602.12391544 |
| 0.00160998 | :A4 | -3670.86822397 |
| 0.00163265 | :A4 | -3725.19089847 |
| 0.00165533 | :A4 | -3764.87852300 |
| 0.00167800 | :A4 | -3789.77517795 |
| 0.00170068 | :A4 | -3799.78305255 |
| 0.00172336 | :A4 | -3794.86282914 |
| 0.00174603 | :A4 | -3775.03383766 |
| 0.00176871 | :A4 | -3740.37397971 |
| 0.00179138 | :A4 | -3691.01942247 |
| 0.00181406 | :A4 | -3627.16406375 |
| 0.00183673 | :A4 | -3549.05877027 |
| 0.00185941 | :A4 | -3457.01039203 |
| 0.00188209 | :A4 | -3351.38055685 |
| 0.00190476 | :A4 | -3232.58424961 |
| 0.00192744 | :A4 | -3101.08818193 |
| 0.00195011 | :A4 | -2957.40895862 |
| 0.00197279 | :A4 | -2802.11104809 |
| 0.00199546 | :A4 | -2635.80456477 |
| 0.00201814 | :A4 | -2459.14287213 |
| 0.00204082 | :A4 | -2272.82001587 |
| 0.00206349 | :A4 | -2077.56799721 |
| 0.00208617 | :A4 | -1874.15389713 |
| 0.00210884 | :A4 | -1663.37686272 |
| 0.00213152 | :A4 | -1446.06496765 |
| 0.00215419 | :A4 | -1223.07195888 |
| 0.00217687 | :A4 | -995.27390260 |
| 0.00219955 | :A4 | -763.56574247 |
| 0.00222222 | :A4 | -528.85778365 |
| 0.00224490 | :A4 | -292.07211651 |
| 0.00226757 | :A4 | -54.13899409 |
| 0.00229025 | :A4 | 184.00682264 |
| 0.00231293 | :A4 | 421.42973708 |
| 0.00233560 | :A4 | 657.19699268 |
| 0.00235828 | :A4 | 890.38233741 |
| 0.00238095 | :A4 | 1120.06966276 |
| 0.00240363 | :A4 | 1345.35660275 |
| 0.00242630 | :A4 | 1565.35807909 |
| 0.00244898 | :A4 | 1779.20977833 |
| 0.00247166 | :A4 | 1986.07154746 |
| 0.00249433 | :A4 | 2185.13069463 |
| 0.00251701 | :A4 | 2375.60518191 |
| 0.00253968 | :A4 | 2556.74669768 |
| 0.00256236 | :A4 | 2727.84359650 |
| 0.00258503 | :A4 | 2888.22369491 |
| 0.00260771 | :A4 | 3037.25691225 |
| 0.00263039 | :A4 | 3174.35774603 |
| 0.00265306 | :A4 | 3298.98757216 |
| 0.00267574 | :A4 | 3410.65676103 |
| 0.00269841 | :A4 | 3508.92660113 |
| 0.00272109 | :A4 | 3593.41102256 |
| 0.00274376 | :A4 | 3663.77811384 |
| 0.00276644 | :A4 | 3719.75142578 |
| 0.00278912 | :A4 | 3761.11105766 |
| 0.00281179 | :A4 | 3787.69452109 |
| 0.00283447 | :A4 | 3799.39737838 |
| 0.00285714 | :A4 | 3796.17365286 |
| 0.00287982 | :A4 | 3778.03600947 |
| 0.00290249 | :A4 | 3745.05570506 |
| 0.00292517 | :A4 | 3697.36230840 |
| 0.00294785 | :A4 | 3635.14319115 |
| 0.00297052 | :A4 | 3558.64279177 |
| 0.00299320 | :A4 | 3468.16165517 |
| 0.00301587 | :A4 | 3364.05525198 |
| 0.00303855 | :A4 | 3246.73258201 |
| 0.00306122 | :A4 | 3116.65456747 |
| 0.00308390 | :A4 | 2974.33224208 |
| 0.00310658 | :A4 | 2820.32474346 |
| 0.00312925 | :A4 | 2655.23711643 |
| 0.00315193 | :A4 | 2479.71793597 |
| 0.00317460 | :A4 | 2294.45675924 |
| 0.00319728 | :A4 | 2100.18141646 |
| 0.00321995 | :A4 | 1897.65515156 |
| 0.00324263 | :A4 | 1687.67362365 |
| 0.00326531 | :A4 | 1471.06178108 |
| 0.00328798 | :A4 | 1248.67062055 |
| 0.00331066 | :A4 | 1021.37384380 |
| 0.00333333 | :A4 | 790.06442511 |
| 0.00335601 | :A4 | 555.65110311 |
| 0.00337868 | :A4 | 319.05481066 |
| 0.00340136 | :A4 | 81.20505679 |
| 0.00342404 | :A4 | -156.96372504 |
| 0.00344671 | :A4 | -394.51584802 |
| 0.00346939 | :A4 | -630.51804798 |
| 0.00349206 | :A4 | -864.04314988 |
| 0.00351474 | :A4 | -1094.17371039 |
| 0.00353742 | :A4 | -1320.00562223 |
| 0.00356009 | :A4 | -1540.65166608 |
| 0.00358277 | :A4 | -1755.24499620 |
| 0.00360544 | :A4 | -1962.94254597 |
| 0.00362812 | :A4 | -2162.92834001 |
| 0.00365079 | :A4 | -2354.41669991 |
| 0.00367347 | :A4 | -2536.65533088 |
| 0.00369615 | :A4 | -2708.92827727 |
| 0.00371882 | :A4 | -2870.55873532 |
| 0.00374150 | :A4 | -3020.91171212 |
| 0.00376417 | :A4 | -3159.39652028 |
| 0.00378685 | :A4 | -3285.46909851 |
| 0.00380952 | :A4 | -3398.63414913 |
| 0.00383220 | :A4 | -3498.44708386 |
| 0.00385488 | :A4 | -3584.51577052 |
| 0.00387755 | :A4 | -3656.50207352 |
| 0.00390023 | :A4 | -3714.12318237 |
| 0.00392290 | :A4 | -3757.15272267 |
| 0.00394558 | :A4 | -3785.42164552 |
| 0.00396825 | :A4 | -3798.81889160 |
| 0.00399093 | :A4 | -3797.29182756 |
| 0.00401361 | :A4 | -3780.84645271 |
| 0.00403628 | :A4 | -3749.54737553 |
| 0.00405896 | :A4 | -3703.51755981 |
| 0.00408163 | :A4 | -3642.93784154 |
| 0.00410431 | :A4 | -3568.04621852 |
| 0.00412698 | :A4 | -3479.13691531 |
| 0.00414966 | :A4 | -3376.55922732 |
| 0.00417234 | :A4 | -3260.71614855 |
| 0.00419501 | :A4 | -3132.06278836 |
| 0.00421769 | :A4 | -2991.10458350 |
| 0.00424036 | :A4 | -2838.39531240 |
| 0.00426304 | :A4 | -2674.53491956 |
| 0.00428571 | :A4 | -2500.16715857 |
| 0.00430839 | :A4 | -2315.97706303 |
| 0.00433107 | :A4 | -2122.68825525 |
| 0.00435374 | :A4 | -1921.06010340 |
| 0.00437642 | :A4 | -1711.88473816 |
| 0.00439909 | :A4 | -1495.98394076 |
| 0.00442177 | :A4 | -1274.20591443 |
| 0.00444444 | :A4 | -1047.42195210 |
| 0.00446712 | :A4 | -816.52301339 |
| 0.00448980 | :A4 | -582.41622428 |
| 0.00451247 | :A4 | -346.02131335 |
Plot all harmonics on the same chart, colored by frequency. Notice the mathematical relationships between these waves:
- A5 (880 Hz) oscillates exactly twice as fast as A4 (440 Hz) - this is one octave higher
- E6 (1320 Hz) is 3× the fundamental frequency (the 3rd harmonic)
- A6 (1760 Hz) is 4× the fundamental (the 4th harmonic, two octaves up)
- C#7 (2200 Hz) is 5× the fundamental (the 5th harmonic)
These integer multiples create the harmonic series, which is fundamental to music theory and acoustics. When waves are related by simple ratios like 2:1, 3:1, 4:1, they blend together harmoniously - this is why octaves and perfect fifths sound so consonant!
(-> violin-components-dataset
(tc/head 200)
(tc/pivot->longer (complement #{:time}))
(tc/rename-columns {:$value :value})
(plotly/layer-line {:=x :time
:=y :value
:=color :$column}))Step 3: Combining the Harmonics
Now for the magic moment! When we add all five sine waves together, we get a complex waveform that sounds like a violin. This is the essence of additive synthesis - rich sounds emerge from simple building blocks.
(def violin-dataset
(-> violin-components-dataset
(tc/add-column :violin
;; Add all five harmonics together
#(dfn/+ (:A4 %)
(:A5 %)
(:E6 %)
(:A6 %)
(:C#7 %)))))Look at the combined waveform - it’s much more complex than a pure sine wave! The shape repeats at 440 Hz (the fundamental), but with rich texture from the harmonics.
(-> violin-dataset
(tc/head 200)
(plotly/layer-line {:=x :time
:=y :violin}))Listen to the Violin Sound
This is the payoff! Compare this to the pure tone we heard earlier. The violin sound is warmer and more musical because of the harmonics. (We divide by 7000 to normalize the amplitude after adding the components)
(-> violin-dataset
:violin
(dfn// 7000.0)
audio)What We Learned
In this first session, we covered a few topics from Chapter 1 of Think DSP:
- Sampling and sample rates - Converting continuous signals to discrete measurements
- Generating sine waves - The building blocks of all sound
- Additive synthesis with harmonics - Creating complex sounds from simple components
Next Steps
In our next study group meetings, we’ll explore the book step by step, and learn more about sounds and signals, harmonics and the Forier transform, non-periodic signals and spectograms, noise and filtering, and more.
Join us at the Scicloj DSP Study Group!
Again, huge thanks to Allen B. Downey for Think DSP. If you find this resource valuable, consider supporting his work or sharing it with others.
source: src/dsp/intro.clj