[WIP] Add ability to play sound
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
This commit is contained in:
@@ -3,6 +3,7 @@ title: "Some Music Theory From (Computational) First Principles"
|
|||||||
date: 2025-09-20T18:36:28-07:00
|
date: 2025-09-20T18:36:28-07:00
|
||||||
draft: true
|
draft: true
|
||||||
filters: ["./to-parens.lua"]
|
filters: ["./to-parens.lua"]
|
||||||
|
custom_js: ["playsound.js"]
|
||||||
---
|
---
|
||||||
|
|
||||||
Sound is a perturbation in air pressure that our ear recognizes and interprets.
|
Sound is a perturbation in air pressure that our ear recognizes and interprets.
|
||||||
@@ -86,6 +87,17 @@ class Superimpose:
|
|||||||
width, height
|
width, height
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def hugo_shortcode(body):
|
||||||
|
return "{{" + "< " + body + " >" + "}}"
|
||||||
|
|
||||||
|
class PlayNotes:
|
||||||
|
def __init__(self, *hzs):
|
||||||
|
self.hzs = hzs
|
||||||
|
|
||||||
|
def _repr_html_(self):
|
||||||
|
toplay = ",".join([str(hz) for hz in self.hzs])
|
||||||
|
return hugo_shortcode(f"playsound \"{toplay}\"")
|
||||||
|
|
||||||
class VerticalStack:
|
class VerticalStack:
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
self.args = args
|
self.args = args
|
||||||
@@ -156,6 +168,11 @@ middleC = Frequency(261.63)
|
|||||||
middleC
|
middleC
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(middleC.hz)
|
||||||
|
```
|
||||||
|
|
||||||
Great! Now, if you're a composer, you can play this note and make music out
|
Great! Now, if you're a composer, you can play this note and make music out
|
||||||
of it. Except, music made with just one note is a bit boring, just like saying
|
of it. Except, music made with just one note is a bit boring, just like saying
|
||||||
the same word over and over again won't make for an interesting story.
|
the same word over and over again won't make for an interesting story.
|
||||||
@@ -165,13 +182,23 @@ other frequency.
|
|||||||
```{python}
|
```{python}
|
||||||
g4 = Frequency(392.445)
|
g4 = Frequency(392.445)
|
||||||
g4
|
g4
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(g4.hz)
|
||||||
|
```
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
fSharp4 = Frequency(370.000694) # we write this F#
|
fSharp4 = Frequency(370.000694) # we write this F#
|
||||||
fSharp4
|
fSharp4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(fSharp4.hz)
|
||||||
|
```
|
||||||
|
|
||||||
This is pretty cool. You can start making melodies with these notes, and sing
|
This is pretty cool. You can start making melodies with these notes, and sing
|
||||||
some jingles. However, if your friend sings along with you, and happens to
|
some jingles. However, if your friend sings along with you, and happens to
|
||||||
sing F# while you're singing the middle C, it's going to sound pretty awful.
|
sing F# while you're singing the middle C, it's going to sound pretty awful.
|
||||||
@@ -194,6 +221,10 @@ show you the bigger picture.
|
|||||||
```{python}
|
```{python}
|
||||||
Superimpose(Frequency(middleC.hz*4), Frequency(fSharp4.hz*4))
|
Superimpose(Frequency(middleC.hz*4), Frequency(fSharp4.hz*4))
|
||||||
```
|
```
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(middleC.hz, fSharp4.hz)
|
||||||
|
```
|
||||||
|
|
||||||
Looking at this picture, we can see that it's far more disordered than the
|
Looking at this picture, we can see that it's far more disordered than the
|
||||||
pure sine waves we've been looking at so far. There's not much of a pattern
|
pure sine waves we've been looking at so far. There's not much of a pattern
|
||||||
@@ -244,6 +275,11 @@ VerticalStack(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(middleC.hz, twiceMiddleC.hz)
|
||||||
|
```
|
||||||
|
|
||||||
You can easily inspect the new graph to verify that it has a repeating pattern,
|
You can easily inspect the new graph to verify that it has a repeating pattern,
|
||||||
and that this pattern repeats exactly as frequently as the lower-frequency
|
and that this pattern repeats exactly as frequently as the lower-frequency
|
||||||
note at the top. Indeed, these two notes sound quite good together. It turns
|
note at the top. Indeed, these two notes sound quite good together. It turns
|
||||||
@@ -293,18 +329,28 @@ VerticalStack(
|
|||||||
Superimpose(middleC, thriceMiddleC)
|
Superimpose(middleC, thriceMiddleC)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(middleC.hz, thriceMiddleC.hz)
|
||||||
|
```
|
||||||
|
|
||||||
That's not bad! These two sound good together as well, but they are not
|
That's not bad! These two sound good together as well, but they are not
|
||||||
in the same pitch class. There's only one problem: these notes are a bit
|
in the same pitch class. There's only one problem: these notes are a bit
|
||||||
far apart in terms of pitch. Wait a minute --- weren't we just talking about
|
far apart in terms of pitch. That `triceMiddleC` note is really high!
|
||||||
singing notes that were too high at half their original frequency?
|
Wait a minute --- weren't we just talking about singing notes that were too
|
||||||
We can do that here. The result is a note we've already seen:
|
high at half their original frequency? We can do that here. The result is a
|
||||||
|
note we've already seen:
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
print(thriceMiddleC.hz/2)
|
print(thriceMiddleC.hz/2)
|
||||||
print(g4.hz)
|
print(g4.hz)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{python}
|
||||||
|
#| echo: false
|
||||||
|
PlayNotes(middleC.hz, g4.hz)
|
||||||
|
```
|
||||||
|
|
||||||
In the end, we got G4 by multiplying our original frequency by $3/2$. What if
|
In the end, we got G4 by multiplying our original frequency by $3/2$. What if
|
||||||
we keep applying this process to find more notes? Let's not even worry
|
we keep applying this process to find more notes? Let's not even worry
|
||||||
about the specific frequencies (like `261.63`) for a moment. We'll start
|
about the specific frequencies (like `261.63`) for a moment. We'll start
|
||||||
@@ -325,12 +371,20 @@ while len(seen) < 6:
|
|||||||
|
|
||||||
seen.add(new_note)
|
seen.add(new_note)
|
||||||
note = new_note
|
note = new_note
|
||||||
|
```
|
||||||
|
|
||||||
|
For an admittedly handwavy reason, let's also throw in one note that we
|
||||||
|
get from going _backwards_: dividing by $2/3$ instead of multiplying.
|
||||||
|
This division puts us below our original frequency, so let's double it.
|
||||||
|
|
||||||
|
```{python}
|
||||||
# Throw in one more by going *backwards*. More on that in a bit.
|
# Throw in one more by going *backwards*. More on that in a bit.
|
||||||
seen.add(Fraction(2/3) * 2)
|
seen.add(Fraction(2/3) * 2)
|
||||||
|
|
||||||
fractions = sorted(list(seen))
|
fractions = sorted(list(seen))
|
||||||
|
fractions
|
||||||
|
```
|
||||||
|
|
||||||
|
```{python}
|
||||||
frequencies = [middleC.hz * float(frac) for frac in fractions]
|
frequencies = [middleC.hz * float(frac) for frac in fractions]
|
||||||
frequencies
|
frequencies
|
||||||
```
|
```
|
||||||
|
|||||||
15
content/blog/music_theory/playsound.js
Normal file
15
content/blog/music_theory/playsound.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
window.addEventListener("load", (event) => {
|
||||||
|
for (const elt of document.getElementsByClassName("mt-sound-play-button")) {
|
||||||
|
elt.addEventListener("click", (event) => {
|
||||||
|
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
for (const freq of event.target.getAttribute("data-sound-info").split(",")) {
|
||||||
|
const oscillator = audioCtx.createOscillator();
|
||||||
|
oscillator.type = "sine"; // waveform: sine, square, sawtooth, triangle
|
||||||
|
oscillator.frequency.value = parseInt(freq); // Hz
|
||||||
|
oscillator.connect(audioCtx.destination);
|
||||||
|
oscillator.start();
|
||||||
|
oscillator.stop(audioCtx.currentTime + 2); // stop after 1 second
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user