QBasic Play in Javascript - an exploration of old and new
Tags: programming, javascript, picture
Published: 2020-02-29 20:36:17

---
title: QBasic Play in Javascript - an exploration of old and new
published: true
date: 2020-02-29 16:11:08 UTC
tags: programming,javascript,picture
canonical_url: https://siderite.dev/blog/qbasic-play-in-javascript/
---

Original post here: https://siderite.dev/blog/qbasic-play-in-javascript
**Note that you can only play the sounds there**

## ![Screen capture of the Gorillas game](https://siderite.dev/Posts/files/Gorillas_637185894686653014.jpg)Intro

  This post will take you on an adventure through time and sound. It will touch the following software development concepts:

- await/async in Javascript
- named groups in regular expressions in Javascript
- the AudioContext API in Javascript
- musical note theory
- Gorillas!

  In times immemorial, computers were running something called the [DOS operating system](https://en.wikipedia.org/wiki/DOS "a platform-independent acronym for Disk Operating System which later became a common shorthand for disk-based operating systems on IBM PC compatibles") and almost the entire interface was text based. There was a way to draw things on the screen, by setting the values of pixels directly in the video memory. The sound was something generated on a "[PC speaker](https://en.wikipedia.org/wiki/PC_speaker "a loudspeaker built into some IBM PC compatible computers")" which was a little more than a small speaker connected to a power port and which you had to make work by handling "[interrupts](https://en.wikipedia.org/wiki/Interrupt "an input signal to the processor indicating an event that needs immediate attention. An interrupt signal alerts the processor and serves as a request for the processor to interrupt the currently executing code, so that the event can be processed in a timely manner")". And yet, since this is when I had my childhood, I remember so many weird little games and programs from that time with a lot of nostalgic glee.

  One of these games was [Gorillas](https://en.wikipedia.org/wiki/Gorillas_(video_game) "a video game first distributed with MS-DOS 5 and published in 1991 by IBM corporation."), where two angry gorillas would attempt to murder each other by throwing explosive bananas. The player would have to enter the angle and speed and also take into account a wind speed that was displayed as an arrow on the bottom of the screen. That's all. The sounds were ridiculous, the graphics really abstract and yet it was fun. So, as I was remembering the game, I thought: what would it take to make that game available in a modern setting? I mean, the programming languages, the way people thought about development, the hardware platform, everything has changed.

  In this post I will detail the [PLAY command](https://en.wikibooks.org/wiki/QBasic/Appendix#PLAY "Used to play notes and a score in QBasic on the PC speaker. The tones are indicated by letters A through G. Accidentals are indicated with a + or # (for sharp) or - (for flat) immediately after the note letter") from the ancient programming language [QBASIC](https://en.wikipedia.org/wiki/QBasic "a short form of Quick Beginners All purpose Symbolic Instruction Code, is an integrated development environment (IDE) and interpreter for a variety of BASIC programming languages which are based on QuickBASIC"). This command was being used to generate sound by instructing the computer to play musical notes on the PC speaker. Here is an example of usage:

```
PLAY "MBT160O1L8CDEDCDL4ECC"
```

  This would play the short song at the beginning of the Gorillas game. The string tells the computer to play the sound in the background, at a tempo of 160 in the first octave, with notes of an eighth of a measure: CDEDCD then end with quarter measure notes: ECC. I want to replicate this with Javascript, one because it's simpler to prototype and second because I can make the result work in this very post.

## Sound and Music

  But first, let's see how musical notes are being generated in Javascript, using the [audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API "provides a powerful and versatile system for controlling audio on the Web, allowing developers to choose audio sources, add effects to audio, create audio visualizations, apply spatial effects (such as panning) and much more."). First you have to create an [AudioContext](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext "represents an audio-processing graph built from audio modules linked together, each represented by an AudioNode. An audio context controls both the creation of the nodes it contains and the execution of the audio processing, or decoding. ") instance, with which you create an [Oscillator](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode "represents a periodic waveform, such as a sine wave. It is an AudioScheduledSourceNode audio-processing module that causes a specified frequency of a given wave to be created—in effect, a constant tone."). On the oscillator you set the frequency and then... after a while you stop the sound. The reason why the API seems so simplistic is because it works by creating an audio graph of nodes that connect to each other and build on each other. There are multiple ways in which to generate sound, including filling a buffer with data and playing that, but I am not going to go that way.

  Therefore, in order to PLAY in Javascript I need to translate concepts like [tempo](https://en.wikipedia.org/wiki/Tempo "Beats per minute"), [octaves](https://en.wikipedia.org/wiki/Octave "an octave (Latin: octavus: eighth) or perfect octave (sometimes called the diapason) is the interval between one musical pitch and another with double its frequency."), [notes](https://en.wikipedia.org/wiki/Musical_note "Notes can represent the pitch and duration of a sound in musical notation. A note can also represent a pitch class.") and [measures](https://en.wikipedia.org/wiki/Beat_(music) "the beat is the basic unit of time, the pulse (regularly repeating event), of the mensural level (or beat level)") into values like duration and frequency. That's why we need a little bit of musical theory.

  In music, sounds are split into domains called octaves, each holding seven notes that, depending on your country, are either Do, Re, Mi, Fa, So, La, Si or A, B,C, D, E, F and G or something else. Then you have half notes, so called sharp or flat notes: A# is half a note above A and A♭ is a half note below A. A# is the same as B♭. For reasons that I don't want to even know, the octaves start with C. Also the notes themselves are not equally spaced. The octaves are not of the same size, in terms of frequency. Octave 0 starts at 16.35Hz and ends at 30.87, octave 1 ranges between 32.70 and 61.74. In fact, each octave spreads on twice as much frequency space as the one before. Each note has twice the frequency of the same note on the lower octave.

  In a more numerical way, octaves are split into 12: C, C#, D, E♭, E, F, F#, G, G#, A, B♭, B. Note (heh heh) that there are no half notes between B and C and E and F. The frequency of one of these notes is 2<sup>1/12</sup> times the one before. Therefore one can compute the frequency of a note as:

_Frequency = Key note \* 2<sup>n/12</sup>_, where the key note is a note that you use as a base and n is the note-distance between the key note and the note you want to play.

  The default key note is A<sub>4</sub>, or note A from octave 4, at 440Hz. That means B♭ has a frequency of 440\*1.059463 = 466.2.

  Having computed the frequency, we now need the duration. The input parameters for this are: tempo, note length, mode and the occasional "dot":

- tempo is the number of quarter measures in a minute
  - this means if the tempo is 120, a measure is 60000 milliseconds divided by 120, then divided by 4, so 125 milliseconds
- note length - the length of a note relative to a measure
  - these are usually fractions of a measure: 1, 1/2, 1/4, 1/8, 1/16, etc
- mode - this determines a general speed of playing the melody
  - as defined by the PLAY command, you have:
    - normal: a measure is 7/8 of a default measure
    - legato: a measure is a measure
    - staccato: a measure is 3/4 of a default measure
- dotted note - this means a specific note will be played for 3/2 of the defined duration for that note

  This gives us the formula:

_Duration = note length \* mode \* 60000 / 4 / tempo \* dotDuration_

## Code

  With this knowledge, we can start writing code that will interpret musical values and play a sound. Now, the code will be self explanatory, hopefully. The only thing I want to discuss outside of the audio related topic is the use of async/await in Javascript, which I will do below the code. So here it is:

```
class QBasicSound {

    constructor() {
        this.octave = 4;
        this.noteLength = 4;
        this.tempo = 120;
        this.mode = 7 / 8;
        this.foreground = true;
        this.type = 'square';
    }

    setType(type) {
        this.type = type;
    }

    async playSound(frequency, duration) {
        if (!this._audioContext) {
            this._audioContext = new AudioContext();
        }
        // a 0 frequency means a pause
        if (frequency == 0) {
            await delay(duration);
        } else {
            const o = this._audioContext.createOscillator();
            const g = this._audioContext.createGain();
            o.connect(g);
            g.connect(this._audioContext.destination);
            o.frequency.value = frequency;
            o.type = this.type;
            o.start();
            await delay(duration);
            // slowly decrease the volume of the note instead of just stopping so that it doesn't click in an annoying way
            g.gain.exponentialRampToValueAtTime(0.00001, this._audioContext.currentTime + 0.1);
        }
    }

    getNoteValue(octave, note) {
        const octaveNotes = 'C D EF G A B';
        const index = octaveNotes.indexOf(note.toUpperCase());
        if (index < 0) {
            throw new Error(note + ' is not a valid note');
        }
        return octave * 12 + index;
    }

    async playNote(octave, note, duration) {
        const A4 = 440;
        const noteValue = this.getNoteValue(octave, note);
        const freq = A4 * Math.pow(2, (noteValue - 48) / 12);
        await this.playSound(freq, duration);
    }

    async play(commandString) {
        const reg = /(?<octave>O\d+)|(?<octaveUp>>)|(?<octaveDown><)|(?<note>[A-G][#+-]?\d*\.?)|(?<noteN>N\d+\.?)|(?<length>L\d+)|(?<legato>ML)|(?<normal>MN)|(?<staccato>MS)|(?<pause>P\d+\.?)|(?<tempo>T\d+)|(?<foreground>MF)|(?<background>MB)/gi;
        let match = reg.exec(commandString);
        let promise = Promise.resolve();
        while (match) {
            let noteValue = null;
            let longerNote = false;
            let temporaryLength = 0;
            if (match.groups.octave) {
                this.octave = parseInt(match[0].substr(1));
            }
            if (match.groups.octaveUp) {
                this.octave++;
            }
            if (match.groups.octaveDown) {
                this.octave--;
            }
            if (match.groups.note) {
                const noteMatch = /(?<note>[A-G])(?<suffix>[#+-]?)(?<shorthand>\d*)(?<longerNote>\.?)/i.exec(match[0]);
                if (noteMatch.groups.longerNote) {
                    longerNote = true;
                }
                if (noteMatch.groups.shorthand) {
                    temporaryLength = parseInt(noteMatch.groups.shorthand);
                }
                noteValue = this.getNoteValue(this.octave, noteMatch.groups.note);
                switch (noteMatch.groups.suffix) {
                    case '#':
                    case '+':
                        noteValue++;
                        break;
                    case '-':
                        noteValue--;
                        break;
                }
            }
            if (match.groups.noteN) {
                const noteNMatch = /N(?<noteValue>\d+)(?<longerNote>\.?)/i.exec(match[0]);
                if (noteNMatch.groups.longerNote) {
                    longerNote = true;
                }
                noteValue = parseInt(noteNMatch.groups.noteValue);
            }
            if (match.groups.length) {
                this.noteLength = parseInt(match[0].substr(1));
            }
            if (match.groups.legato) {
                this.mode = 1;
            }
            if (match.groups.normal) {
                this.mode = 7 / 8;
            }
            if (match.groups.staccato) {
                this.mode = 3 / 4;
            }
            if (match.groups.pause) {
                const pauseMatch = /P(?<length>\d+)(?<longerNote>\.?)/i.exec(match[0]);
                if (pauseMatch.groups.longerNote) {
                    longerNote = true;
                }
                noteValue = 0;
                temporaryLength = parseInt(pauseMatch.groups.length);
            }
            if (match.groups.tempo) {
                this.tempo = parseInt(match[0].substr(1));
            }
            if (match.groups.foreground) {
                this.foreground = true;
            }
            if (match.groups.background) {
                this.foreground = false;
            }

            if (noteValue !== null) {
                const noteDuration = this.mode * (60000 * 4 / this.tempo) * (longerNote ? 1 : 3 / 2);
                const duration = temporaryLength
                    ? noteDuration / temporaryLength
                    : noteDuration / this.noteLength;
                const A4 = 440;
                const freq = noteValue == 0
                    ? 0
                    : A4 * Math.pow(2, (noteValue - 48) / 12);
                const playPromise = () => this.playSound(freq, duration);
                promise = promise.then(playPromise)
            }
            match = reg.exec(commandString);
        }
        if (this.foreground) {
            await promise;
        } else {
            promise;
        }
    }
}

function delay(duration) {
    return new Promise(resolve => setTimeout(resolve, duration));
}
```

One uses the code like this:

```
var player = new QBasicSound();
await player.play('T160O1L8CDEDCDL4ECC');
```

Note that you cannot start [playing the sound directly](https://developers.google.com/web/updates/2017/09/autoplay-policy-changes "Autoplay with sound is allowed if: User has interacted with the domain (click, tap, etc.). On desktop, the user's Media Engagement Index threshold has been crossed, meaning the user has previously played video with sound. The user has added the site to their home screen on mobile or installed the PWA on desktop."), you need to wait for a user interaction first. An annoying rule to suppress annoying websites which would start playing the sound on load. And here is the result (press multiple times on Play for different melodies):

[To play the sounds you need to visit the original post]

## Javascript in modern times

There are two concepts that were used in this code that I want to discuss: named regular expression groups and async/await. Coincidentally, both are C# concepts that have crept up in the modern Javascript specifications when .NET developers from Microsoft started contributing to the language.

[Named groups](https://javascript.info/regexp-groups#named-groups "Remembering groups by their numbers is hard. For simple patterns it’s doable, but for more complex ones counting parentheses is inconvenient. We have a much better option: give names to parentheses.") are something that appeared in ES2018 and it is something I've been using with joy in .NET and hated when I didn't have it in some other language. Look at the difference between the original design and the current one:

```
// original design
var match = /(a)bc/.exec('abcd');
if (match && match[1]) { /*do something with match[1]*/ }

// new feature
const match = /(?<theA>a)bc/.exec('abcd');
if (match && match.groups.theA) { /*do something with match.groups.theA*/ }
```

There are multiple advantages to this:

- readability for people revisiting the code
- robustness in the face of changes to the regular expression
  - the index might change if new groups are added to it
- the code aligns with the C# code (I like that :) )

My advice is to always use named groups when using regular expressions.

Another concept is [await/async](https://javascript.info/async-await "There’s a special syntax to work with promises in a more comfortable fashion, called async/await. It’s surprisingly easy to understand and use."). In .NET it is used to hide complex asynchronous interactions in the code and with the help of the compiler helps with all the tasks that are running at the same time. Unfortunately, in C#, that means polluting code with async keywords on all levels as async methods can only be used inside other async methods. No such qualms in Javascript.

While in .NET the await/async system runs over Task&lt;T&gt; methods, in Javascript it runs over [Promises](https://javascript.info/promise-chaining "The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value."). Both are abstractions over work that is being done asynchronously.

A most basic example is this:

```
// original design
getSomethingAsync(url,function(data) {
  getSomethingElseAsync(data.url,function(data2) {
    // do something with data2
  }, errorHandler2);
},errorHandler1);

// Promises
getSomethingAsync(url)
  .then(function(data) {
    getSomethingElseAsync(data.url);
  })
  .then(function(data2) {
    // so something with data2
  })
  .catch(errorHandler);

// async/await
try {
  var data = await getSomethingAsync(url);
  var data2 = await getSomethingElseAsync(data.url);
  // do something with data2
} catch(ex) {
  errorHandler(ex);
}
```

You see that the await/async way looks like synchronous code, you can even catch errors. await can be used on any function that returns a Promise instance and the result of it is a non-blocking wait until the Promise resolves and returns the value that was passed to the resolve function.

If you go back to the QBasicSound class, at the end, depending on if the sound is in the foreground or background, the function is either awaiting a promise or ... letting it run in the background. You might also notice that I've added a delay function at the end of the code which is using setTimeout to resolve a Promise. Here is what is actually going on:

```
// using await
console.log(1);
await delay(1000).then(()=>console.log(2));
console.log(3);
// this logs 1,2,3

// NOT using await
console.log(1);
delay(1000).then(()=>console.log(2));
console.log(3);
// this logs 1,3,2
```

In the first case, the Promise that was constructed by a one second delay and then logging 2 is awaited, meaning the code waits for the result. After it is executed, 3 gets logged. In the second case, the logging of 2 is executed after one second delay, but the code does not wait for the result, therefore 3 is logged immediately and 2 comes after.

What sorcery is this?! Isn't Javascript supposed to be single threaded? How does it work? Well, consider that in the delay function, the resolve function will only be called after a timeout of one second. When executed, it starts the timeout, then reaches the end of the function. It has not been resolved yet, so it passes control back to the engine, which uses it to execute other things. When the timeout is fired, the engine takes back control, executes the resolve function, then passes control back. All of this is invisible to the user, who gets the illusion of multithreaded behavior.

Already some standard out of the box APIs are async, like fetch. In order to get an object from a REST API that is called via HTTP the code would look like this:

```
// fetch API
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
```

## Conclusion

I spent an entire day learning about sounds and writing code that would emulate QBASIC code from a billion years ago. Who knows, maybe my next project will be to port the entire Gorillas game in Javascript. Now one can lovingly recreate the sounds of one's childhood.

### Other references:

[Gorillas.BAS](https://gist.github.com/caffo/1326838 "the source code for Gorillas")

[QBasic/Appendix](https://en.wikibooks.org/wiki/QBasic/Appendix "The command reference for QBASIC")

[Generate Sounds Programmatically With Javascript](https://marcgg.com/blog/2016/11/01/javascript-audio/ "During a recent hackathon, I decided to build a multiplayer 8 bits sequencer using sounds generated programatically with the Web Audio API. I didn’t want to only use the HTML 5 audio tag because I found it too limiting… but the first thing I discovered is that getting the right kind of sound is not straightforward at all, especially if you only have a very basic musical background like myself. So how exactly do you create a clear, ringing and nice sounding note?")

[Musical Notes](http://www.techlib.com/reference/musical_note_frequencies.htm "A musical octave spans a factor of two in frequency and there are twelve notes per octave. Notes are separated by the factor 2 1/12 or 1.059463.")

[Gorrilas game online](https://playclassic.games/games/action-dos-games-online/qbasic-gorillas/play/ "55, 70, aaaaarrrghhh!")