The Fool on the Hill: The sound of Scittle

The Fool on the Hill: The sound of Scittle

By: :: 29 September 2025

A spiral and a tree, written in Scittle

The language of God, part one

I'm a (computer) language snob (and also, nerd). I have strong opinions on the aesthetics of computer languages; I like my language to be beautiful. Which is to say, simple, elegant, consistent, with as little arbitrary syntax and as few special cases as possible. For these reasons, I'm most comfortable in Lisp. If God did not write the universe in Lisp, God wrote it in some language so similar to Lisp as to make no difference. You need only look at a fern-moss, or a distant galaxy, or a cloud, or water curling over a weir, to see this1.

But there are a range of dialects of Lisp in common use today, and all, in one way or another, have morphed away from the elegant purity of early Lisps. They've also, frankly, shed some of the authentic crud that did exist in early Lisps, but...

For me, the modern dialect of Lisp which has the best combination of aesthetic beauty and a vibrant user community is Clojure. Yes, Scheme is equally elegant, and Common Lisp has an equally vibrant community, but Clojure is (for now) my choice.

We'll come back to this.

The language of the Web

We deliver a great deal of software, these days, through web browsers. To deliver performant software through a web browser, it's useful to be able to off-load as much of the compute cost as possible to the user's machine, allowing the load on the server to be much lower, and reducing the amount of network traffic needed to make updates. To off-load compute, we need a standardised compute environment at the client side of the link.

All standards-conforming web browsers, these days, implement one standard client-side scripting language, which is usually called JavaScript2.

JavaScript is not a horrible language. I can write it, and don't hate it. But it is not my language of choice. Fortunately there are alternatives. One of these, since 2012, has been ClojureScript, a compiler which transpiles Clojure into JavaScript. The JavaScript that the ClojureScript compiler emits is not optimised for human reading, and in the early days could be tricky to debug. However, the ability to use the same language for both server side and client side code was a huge win and I eagerly adopted it.

However, it's not perfect. One of the ways that most of those of us who build the Web learned to build the Web was by looking at and learning from the source code of pages we liked. How was this effect achieved? The source of a web page is open; you can just view source, and reveal it. From there, you can similarly view all the stylesheets used, and all the script files. But a transpiler designed to generate the most efficient and compact code possible is not greatly different from a code obfuscator; users can't see, and can't copy, your source.

So I was excited earlier this year when Michiel Borkent, a tremendous producer of excellent Clojure tools, announced Scittle. Scittle leverages the Small Clojure Interpreter, an interpreter for a substantial subset of Clojure written in JavaScript, to allow Clojure to be used as an in-browser scripting language. That means that users of pages which have their client side functionality written in Scittle can just view source and see how the magic is done. There's a demo page I wrote here on which you can do just that.

The language of God, part two

I have a close friend who is a very sincere Sikh. Sikhism, like Christianity, Judaism and Islam, is a religion which values its scriptures highly. The Sikh scriptures were written in a dialect of Punjabi, but they were written down more than four hundred years ago, and language mutates.

Many (most?) Sikhs believe that there is a correct way to pronounce the words of the scriptures, the precise way in which the gurus, five hundred years ago, would have pronounced them. My sceptical, fiercely agnostic, western brain smells cant — it's my understanding that Guru Nanak preached in the common language because he wanted to be understood, and that this veneration of an archaic dialect is simply a shibboleth — but that's not my business. My friend wants to learn this precise dialect, and wants to create tools to help others to learn this precise dialect, and has, from time to time, asked me to help her.

Three years ago, she asked me to produce a web page which displayed the Muharni — roughly the Punjabi equivalent of the alphabet — and allowed the user to hear the approved pronunciation of each glyph in a standardised range of contexts. As an extension to this, we allowed the user to record their own voice, so they could compare their enunciation of the glyphs to that of the tutor. We did this, it works, and it's here. I wrote the client side code to record sound in JavaScript, and that, too, works. You can find the complete source here.

This year, she asked me to do a similar exercise for the Japji Sahib — an extremely significant Sikh prayer, composed by Guru Nanak, which appears at the beginning of the Guru Granth Sahib, the most venerated of the Sikh scriptures. And this time, partly for aesthetic reasons, partly for vanity, I decided to write in Scittle.

Reader, it does not work. Worse, reader, I am having trouble deducing why.

A statement of the problem

To record sound in a web browser, one gets audio from the user's media devices as a stream, wraps that in a fresh MediaRecorder object, and handles chunks of audio data from that object whenever it passes them. In JavaScript, in our Muharni implementation, it goeth thusly:

function recordStudentSound(r, c) {
    console.info("Entered recordStudentSound for row " + r + ", column " + c);

    if (Number.isInteger(r) && Number.isInteger(c)) {
        $('#record-stop').css('color', 'green');

        try {
            createProgressbar('progress', '5s');

            navigator.mediaDevices.getUserMedia({ audio: true })
                .then(stream => {
                    const mediaRecorder = new MediaRecorder(stream);
                    const audioChunks = [];

                    mediaRecorder.start();
                    mediaRecorder.onerror = function (e) {
                        console.log("An error has occurred: " + e.message);
                    };
                    mediaRecorder.addEventListener("dataavailable", event => {
                        console.info("Audio recorded...")
                        audioChunks.push(event.data);
                    });

                    mediaRecorder.onstop = function (e) {
                        console.log("data available after MediaRecorder.stop() called.");

                        if (audioChunks.length > 0) {
                            studentSounds[r][c] = new Blob(audioChunks);

                             });
                            console.log("Successfully recorded student sound for row " + r + ", column " + c);
                        } else {
                            console.warn("Failed to record student sound for row " + r + ", column " + c);
                            window.alert("No sound detected. Check your microphone?");
                        }
                    };

                    setTimeout(() => {
                        mediaRecorder.requestData();
                        mediaRecorder.stop();

                    }, 5000);
                });
        } catch (error) {
            console.error(error);
            if (error instanceof TypeError) {
                window.alert("Sound recording is only possible on secure connections.");
            }
            else if (error instanceof DOMException) {
                window.alert("No microphone detected? " + error.message);
            }
        }
    }
}

This, as I say, works.

In my new implementation, in Scittle, I transcribed this as:

(defn record-student-sound!
  [phrase-no]
  (.info js/console "Recording student sound for phrase " phrase-no)

  (try
    (.then (.getUserMedia (.mediaDevices js/navigator) {:audio true})
           (fn [arg]
             (let [media-recorder (js/MediaRecorder. arg)
                   audio-chunks (atom [])]
               (set! (.-onerror media-recorder)
                     (fn [e]
                       (.error js/console (str "Error while recording sound: " e))))
               (.addEventListener media-recorder "dataavailable" 
                                 (fn [event]
                                    (.info js/console "Audio recorded...")
                                    (swap! audio-chunks conj (.-data event)))
                                  )
               (set! (.-onstop media-recorder)
                     (fn [_]
                        (js/console.log "data available after MediaRecorder.stop() called.")
                        (if (> (count @audio-chunks) 0)
                          (swap! student-recordings assoc phrase-no
                                 (js/Blob. (clj->js @audio-chunks)))
                          (do 
                            (.warn js/console (str "Failed to record student sound for row for phrase " phrase-no))
                            (.alert js/window "No sound detected. Check your microphone?")) ))
                     )
               (.start media-recorder))))
    (catch js/Error e
      (.log js/console
            (str "Error thrown while recording sound: " (.-message e))))
    (catch :default x
      (str "Unexpected object thrown while recording " x))))

This does not work. It fails, reporting an error in the console:

Error thrown while recording sound: f is not a function

Which f is it that is not a function? It looks to me as though it must be the outer anonymous function, since the message includes the word 'thrown', which the catch of the try block in record-student-sound! prints, but the onerror handler does not. But Scittle is really not, as this stage in its development, easy to debug.


  1. I believe, as I've written before, that any strong claim to gnosis is a blasphemy. The claim to know the language in which God wrote the universe — that there is a God, and that the universe is written — is a strong claim to gnosis. Therefore, I am a hypocrite.

  2. but may more properly be called ECMAScript.

Tags: Software Lisp Clojure

| |

About Cookies

This site does not track you; it puts no cookies on your browser. Consequently you don't have to click through any annoying click-throughs, and your privacy rights are not affected.

Wouldn't it be nice if more sites were like this?