ftlm

binaural-beats-using-scittle

Table of Contents

Introduction

The best thing about being a rationalist is figuring out you are wrong about something.

I was thinking that binaural beats belong together with astral travel, frankly, just down the toilet. Turns out that not only was I wrong, but binaural beats also have cool neuroscience and pose a fun engineering problem on top of that!

Here is the current result of my journeys up on gh-pages.

What are binaural beats?

Wikipedia

A binaural beat is an auditory illusion

For example, if a 530 Hz pure tone is presented to a subject's right ear, while a 520 Hz pure tone is presented to the subject's left ear, the listener will perceive the illusion of a third tone. The third sound is called a binaural beat, and in this example would have a perceived pitch correlating to a frequency of 10 Hz, that being the difference between the 530 Hz and 520 Hz pure tones presented to each ear.

Science

There has developed a growing science about improvements in memory and attention tasks when listening to binaural beats.

BTW the frequency and the time of the exposure matter (i.e. before and during the task is best).

Oh yes, I certainly want more of that memory and attention.

eHaaoYF.png

Figure 1: A brain with headphones flying at the speed of light through space. Lightning on the brain. There is a sine wave in the background. Stylized, Colorful, Cinematic, Digital Art.

Frequency

Theta waves (5Hz) actually decrease memory task performance. So I really do not want to listen to those. I heard Andrew Huberman saying that 40Hz is the most powerful.

If you know about brain waves, you might remember 40Hz as the legendarily intriguing gamma wave.

From wikipedia article:

Gamma rhythms are correlated with large scale brain network activity and cognitive phenomena such as working memory, attention, and perceptual grouping, and can be increased in amplitude via meditation or neurostimulation.

We are really talking about cool stuff here, attention, working memory, and memory processes. Also, the Binding problem.

The neuroscience of the auditory system

Hearing is cool because the neuronal neural information first goes into the brainstem and then goes up some nuclei up to the thalamus. And eventually to the temporal lobes of the cortex (the 2 arm things on the side of the brain).

This is different from the vision system where the info goes directly to the thalamus.

Quoting this paper:

Presenting two tones with a slight frequency mismatch to each ear separately creates a perception of a third tone, a binaural beat, that oscillates at the absolute difference between the tones (Oster, 1973; Moore, 2012). These beats are thought to originate subcortically in the medial nucleus of the superior olivary complex, the first nucleus in the auditory pathway to receive bilateral input (Wernick and Starr, 1968; Kuwada et al., 1979). This “illusory” third tone is lateralized between the left and right ear of the listener, making binaural beats useful for spatial sound research (Ross et al., 2014).

from the Wikipedia articles on Sound localization

The auditory system uses several cues for sound source localization, including time difference and level difference (or intensity difference) between the ears, and spectral information.

and Superior olivary complex

The superior olivary nucleus plays a number of roles in hearing. The medial superior olive (MSO) is a specialized nucleus that is believed to measure the time difference of arrival of sounds between the ears (the interaural time difference or ITD). The ITD is a major cue for determining the azimuth of sounds, i.e., localizing them on the azimuthal plane – their degree to the left or the right.

So there is sort of a clump of neurons in the brainstem that are specialized to integrate the timing information between the left and the right ear. I'm guessing those then start firing in the 40Hz frequency and that is interpreted as a third sound higher up.

I think this is ultra cool

You throw some energy at the brain in the right form (in this case some sound waves) and it reacts in some special way. If you put it like that, it is not surprising that some things eventually, end up doing something.

Now that the phenomenon has a name we can talk about it, similar to ASMR.

Thinking about how this was always part of how the brain works. Makes me wonder if some music composers and performers implicitly hit on this earlier, or not. I tried searching the web for

binaural beats in music

but I really get the same results mix as when I search for binaural beats.

Give me those binaural beats

I dint feel like opening a youtube video just to listen to some sounds. As an aspiring hacker and engineer, I just want to make my computer make some sounds that I want to listen to. So I decided to make a simple website that just makes binaural beats and nothing else.

The problem statement:

Make a sound on 1 ear (e.g. left headphone speaker)

Make a second sound on the other ear, with a +40Hz frequency.

End result

beats.cljs

Behold 46 lines of just get the shit done.

(ns beats)

(def binaural-beat-freq 40)
(defonce ctx (or (js/window.AudioContext.) (js/window.webkitAudioContext.)))
(def slider (js/document.getElementById "frequencyRange"))
(def display (js/document.getElementById "frequencyDisplay"))

(defn ->panner [left?]
  (let [panner (. ctx createStereoPanner)]
    (set! (.. panner -pan -value) (if left? -1 1))
    (.connect panner ctx.destination)
    panner))

(def panners
  (delay {:left (->panner true) :right (->panner false)}))

(defn update-display! [value]
  (set! (.-innerHTML display)
        (str "Base frequency: " value " Hz")))

(def get-oscillator
  (memoize
   (fn [panner]
     (let [o (ctx.createOscillator)]
       (set! (.- o type) "sine")
       (. o start)
       (. o (connect panner))
       o))))

(defn oscillate [panner hz]
  (let [o (get-oscillator panner)
        _ (set! (.. o -frequency -value) hz)])
  hz)

(defn update-app [frequency-value]
  (let [frequency-value (/ frequency-value 1000.0)]
    (-> @panners :right (oscillate (+ frequency-value binaural-beat-freq)))
    (-> @panners :left (oscillate frequency-value))
    (update-display! frequency-value)))

(defn start-app []
  (update-app (* 1000 152.74))
  (set! (.-value slider) (* 1000 152.74)))

(set! (.-update_app js/window) update-app)
(set! (.-start_app js/window) start-app)

index.html

  <!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Just 40Hz binaural beats</title>
    <script>var SCITTLE_NREPL_WEBSOCKET_PORT = 1340;</script>
    <script src="https://cdn.jsdelivr.net/npm/scittle@0.3.10/dist/scittle.js" type="application/javascript"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/scittle@0.3.10/dist/scittle.nrepl.js" type="application/javascript"></script> -->
    <script type="application/x-scittle" src="beats.cljs"></script>

<style>
  body {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    gap: 1rem;
    margin: 0;
  }
</style>

  </head>
  <body>
    <h1>Just 40Hz binaural beats</h1>
    <audio></audio>
    <button onclick="start_app()"> play </button>
    <div class="slidecontainer">
      <input type="range" min="100000" max="450000" value="15274"
      class="slider" id="frequencyRange" onchange="update_app(this.value)"> </div>
    <div id="frequencyDisplay"> </div>
    <a href="https://github.com/benjamin-asdf/just-40hz-binaural-beats">code</a>
  </body>
</html>

scittle

I set up a beautiful hello world and connect with nrepl and get greeted with a friendly Isn't cool? this :).

It took me a moment to figure out I should open my index.html with a browser. Then do the cider-connect-cljs

1 ear, 1 sound

First I think let's start making a sound in 1 ear.

I am thinking a frequency that I can hear would be nice so I search the web for human auditory frequencies

1 kHz sounds like a good first thing to try. I am figuring something inside the speech area should sound natural (turned out that was a naive assumption).

The first playground

I have a file called playground.clj (Later I figured out I should rename to .cljs :) ).

Now getting heavily inspired by this code. Going 1 form by 1 like a usual lisp interaction dev experience, lulling it up:

(def ctx (js/window.AudioContext.))
(def ctx js/window.webkitAudioContext)

(def panner (ctx.createStereoPanner))
(set! (.. panner -pan -value) -1)
(.. panner (connect ctx.destination))

(def oscillators (atom []))
(let [o (ctx.createOscillator)
      _ (set! (.- o type) "sine")
      _ (set! (.. o -frequency -value) 1000)]
  (. o start)
  (. o (connect panner))
  (swap! oscillators conj o))

I connect my headphones via Bluetooth (a small feat on Linux, maybe another blog post), and lo and behold I hear a tone in my left ear. It is a really obnoxious sound, after a few seconds, it is so painful that I move the headphone to the side.

Playing Hz, first round

Here is a more endurable sound:

(set! (.. (@oscillators 0) -frequency -value) 200)

Playing around live at the REPL with sound is fun!

Now I am thinking, let's pick some music frequency, those are supposed to be crafted over hundreds of years to be beautiful frequencies, right?

Checking out a website. I pick D#3 / Eb3. Whatever that is.

(def a-note-freq 152.74)
(set! (.. (@oscillators 0) -frequency -value) a-note-freq)

binaural!

Let's do the thing where I add +40Hz and play that on the other ear. First I start generalizing how I make a panner:

(defn ->panner [ctx left?]
  (let
      [panner (. ctx createStereoPanner)
       _ (set! (.. panner -pan -value) (if left? -1 1))
       _ (.. panner (connect ctx.destination))]
    panner))

For some reason running this I get:

Failed to execute 'connect' on 'AudioNode': Overload resolution failed.

I went and asked in the nbb clojurians slack. I am sure one day we will figure out this error, in the meantime…

just put the second sound in the other ear, whatever

(ns beats)

(def a-note-freq 152.74)
(def binaural-beat-freq 40)
(def oscillators (atom []))
(defonce ctx (js/window.AudioContext.))

(def panners
  {:left
   (let [left? true]
     (let [panner (. ctx createStereoPanner)]
       (set! (.. panner -pan -value) (if left? -1 1))
       (.connect panner ctx.destination)
       panner))
   :right
   (let [left? nil]
     (let [panner (. ctx createStereoPanner)]
       (set! (.. panner -pan -value) (if left? -1 1))
       (.connect panner ctx.destination)
       panner))})

(defn oscillate [panner hz]
  (let [o (ctx.createOscillator)
        _ (set! (.- o type) "sine")
        _ (set! (.. o -frequency -value) hz)]
    (. o start)
    (. o (connect panner))
    (swap! oscillators conj o)))

(-> panners :right (oscillate (+ a-note-freq binaural-beat-freq)))
(-> panners :left (oscillate a-note-freq))

lol, it works.

Intermediate results

Artifacts

Every now and then there are artifacts in the sound with my headphones setup. Some are just rustling and crackling.

Others sound like some kind of metal being stretched far, far away, underwater, or in slow motion. It reminds me a bit of the Dungeon Keeper soundtrack. Dark and eerie, sort of slow mow.

Binaural beat?

Pretty sure I get the third-tone illusion. I would describe it as some kind of background or in between "airy" sound. It is supposed to seem to come from the middle of the brain. It is as if the fabric of the sound is richer. Like there is sound in more places.

Change the base frequency

It occurred to me, that if I get the 40Hz sound illusion, then I would predict that I can change the base frequency, keeping the 40Hz difference between left and right ear invariant. I should then constantly perceive a 3rd tone which is the binaural beat.

I really need a slider for frequencies in my life.

I did not do much web dev in my life and making a slider was a first.

I managed to put something together, drawing inspiration from w3 schools for the Html, scittle for how to export, and stackoverflow for how to add a function to "onchange".

nice dev experience, scittle really

  1. I did not need to restart my REPL at any point, adding those Html elements, then reloading my index.html - it handles that.
  2. I get auto completions with cider. E.g. js/.. does something.

    BTW here is a tip, for sci projects (babashka, nbb, scittle). Currently, you need to evaluate a namespace form first. Then you can get completions. At least with cider.

M8rSuZ3.png

Figure 2: A mouse with headphones typing on a laptop. Intelligent. Sparks of magic are flying around. There is lightning on the mouse and laptop. Computer programming source code. Colorful, playful.

The word scittle makes me think of a small mammal in the habitus of a mouse. I imagine it scurrying and curiously sniffing around.

code:

index.html

<!DOCTYPE html>
<html>
  <head>
    <script>var SCITTLE_NREPL_WEBSOCKET_PORT = 1340;</script>
    <script src="https://cdn.jsdelivr.net/npm/scittle@0.3.10/dist/scittle.js" type="application/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/scittle@0.3.10/dist/scittle.nrepl.js" type="application/javascript"></script>
    <script type="application/x-scittle" src="beats.cljs"></script>
  </head>
  <body>
    <div class="slidecontainer">
      <input type="range" min="120000" max="250000" value="15274"
      class="slider" id="frequencyRange" onchange="update_app(this.value)"> </div>
    <div id="frequencyDisplay"> </div>
  </body>
</html>

beats.cljs

(ns beats)

(def binaural-beat-freq 40)
(defonce ctx (js/window.AudioContext.))
(def slider (js/document.getElementById "frequencyRange"))
(def display (js/document.getElementById "frequencyDisplay"))

(def panners
  {:left
   (let [left? true]
     (let [panner (. ctx createStereoPanner)]
       (set! (.. panner -pan -value) (if left? -1 1))
       (.connect panner ctx.destination)
       panner))
   :right
   (let [left? nil]
     (let [panner (. ctx createStereoPanner)]
       (set! (.. panner -pan -value) (if left? -1 1))
       (.connect panner ctx.destination)
       panner))})

(defn update-display! [value]
  (set! (.-innerHTML display)
        (str "Base frequency: " value)))

(def get-oscillator
  (memoize
   (fn [panner]
     (let [o (ctx.createOscillator)]
       (set! (.- o type) "sine")
       (. o start)
       (. o (connect panner))
       o))))

(defn oscillate [panner hz]
  (let [o (get-oscillator panner)
        _ (set! (.. o -frequency -value) hz)])
  hz)

(defn update-app [frequency-value]
  (let [frequency-value (/ frequency-value 1000.0)]
    (-> panners :right (oscillate (+ frequency-value binaural-beat-freq)))
    (-> panners :left (oscillate frequency-value))
    (update-display! frequency-value)))

(set! (.-update_app js/window) update-app)

(update-app (* 1000 152.74))

The fact that I export my function by setting this global window object is quite wild. And then how I write a string of js code in the 'onchange' of the Html - damn. As I said, I am new to the web. Now I know one reason why it is called the wild west. But it let me "just do" what I wanted without being in my way so I appreciate that.

On the scittle side, I updated the code to only get 1 oscillator per slider. Seems like stuff is working. Switching up the frequencies via the slider is satisfying. For some reason, those artifacts are also gone. Maybe sliders just ensure balance and harmony in the world?

I am pretty sure I can tell there is 1 sound that seems to stay the same across frequencies.

I do the float * 1000 trick because I want to work with 2 digits for my frequencies.

Btw I also quickly tried what a real 40Hz tone sounds like. Ultra-low. I almost feel like I can make out the single waves, maybe that are the headphones cracking? Not surprising as the lower bound of human hearing is 20hz. The sound of the highest achievement of human thought. Genius insights, ideas that come to you like lightning. Globally integrating patterns of the brain and mind.

xijojsT.png

Figure 3: Harry potter wearing headphones having spiritual enlightenment about magic. Glowing magic sparks fly around. Colorful, cinematic, video game concept art.

Notes

Images

Made with stable-diffusion. The captions are the prompts I used.

Update 1

Currently does not work on mobile. Seems like I have the opportunity for another web dev feat, then. Seems like the issue is isolated on the sound appearing because the slider value works fine (meaning that the scittle code is running fine).

Update 2

Now it also works on mobile! Somebody helpfully pointed out on slack that on mac there was an error about audio refusing to play before the first touch event. I fixed that by adding a play button. But mobile still did not work, ok. After checking stackoverflow I still sort of have the idea that it has to do with user interaction events and this AudioContext. I first wanted to delay the whole audio setup until you click the button. But read that you should make a single AudioContext at the beginning.

So I try to delay the 1 other thing in the code:

(def panners
 (delay {:left (->panner true) :right (->panner false)}))
(-> @panners :right (oscillate (+ frequency-value binaural-beat-freq)))
(-> @panners :left (oscillate frequency-value))

It works, haha!

Date: 2022-09-19 Mon 11:04

Email: Benjamin.Schwerdtner@gmail.com

About
Contact