Adding location-based music to Quixe: a tutorial

I found an easy way to add music to Quixe based on your location for my IFComp game, and I thought I’d write a tutorial for it, since it’s so easy and I don’t just want to hide it. This could all be much more concise if it were function-ified, but one characteristic of this forum is that when someone posts code, someone posts better code, which would be great in this situation.

You’ll need some kind of text editor to edit html and js files (I used Notepad++ in Windows). I’ve added a sample glkote.min.js file to show what the completed setup is like.

Step 1

Make sure you have a glulx file and that you ‘release with an interpreter’. This modification only works if you haven’t modified the status line.

Step 2

Add the music files you want to your materials directory (the same one that has play.html). There are many music formats that could work with this technique, but mp3’s play on the most browsers. (They used to require a licensing fee, but that expired this year).

Step 3
Open up play.html. Copy and past the following to add a song to the list of playable music:

[code]

Your browser does not support the audio element. [/code]

I placed this right before the /body tag at the end. The ‘preload = “none”’ is designed to make the game load faster, and the word ‘loop’ tells the website to loop the audio. The ‘id = “spangled”’ is just a place to name the audio you’ve added, so you can refer to it later.

Step 4

In the Interpreter folder of the Materials folder, open up glkote.min.js.

Search this document for the phrase ‘insert_text_detecting(el, rtext);’. It should appear three times. The first time represents the status line; the second time represents all other text; and the third one is where that function is actually defined.

The first time that it occurs, we replace that line with our music system. This part is complicated, so I wanted to show how I built it up. I initially just detected what room you’re in. The if statement detects the room name (slice just gets the first few characters, and the == tells if it is equal to whatever room you’re looking for. The slice starts at 1 because the status line always has a space in it; it ends at 1+the number of letters in the room name). In the ‘getelementbyID’ parentheses you just put the name that you gave the music earlier.

if(rtext.slice(1,11) == 'First Room'){ document.getElementById('spangled').play(); } else if(rtext.slice(1,5) == 'Home'){ document.getElementById('spooky').play(); } insert_text_detecting(el, rtext);

However, this won’t turn off the old audio when the new audio starts playing. What I did with this was I paused all of the other music and then set their times to 0 (the most common way of ‘stopping’ music in HTML5). So now it looks like this:

if(rtext.slice(1,11) == 'First Room'){ document.getElementById('spooky').pause(); document.getElementById('spooky').currentTime = 0; document.getElementById('spangled').play(); } else if(rtext.slice(1,5) == 'Home'){ document.getElementById('spangled').pause(); document.getElementById('spangled').currentTime = 0; document.getElementById('spooky').play(); } insert_text_detecting(el, rtext);

However, this causes an error in Internet Explorer where you can’t set currentTime if the song is not currently loaded. This was hard to work around; I had to google around a lot. Basically, you just check to see if you can even access the song, and if you can, then you set currentTime to 0:

if(rtext.slice(1,11) == 'First Room'){ document.getElementById('spooky').pause(); if(document.getElementById('spooky').readyState > HTMLMediaElement.HAVE_NOTHING){ document.getElementById('spooky').currentTime = 0; } document.getElementById('spangled').play(); } else if(rtext.slice(1,5) == 'Home'){ document.getElementById('spangled').pause(); if(document.getElementById('spangled').readyState > HTMLMediaElement.HAVE_NOTHING){ document.getElementById('spangled').currentTime = 0; } document.getElementById('spooky').play(); } insert_text_detecting(el, rtext);

This is the final code that I used, and it works in every browser I tried (including safari, chrome, firefox, internet explorer, android, and iOS).

Step 5

You may want to be able to turn music on and off. In that case, you can open glkote.min.js again. At the top, there are a bunch of statements starting with ‘var’ and ending with semicolons; in the middle of these, add the phrase

var MusicOn = false;
[/code] to add a global variable that turns music on and off.

Then you can add code like the following to your Inform game:

[code]To turn on the music:
	say "Music has been enabled. Press any key.";
	now the left hand status line is "Music Enabled";
	update the status line;
	wait for any key;
	now the left hand status line is "[the player's surroundings]";
	now MusicOn is 1;

And add the following line next to all your other ‘if’ statements in glkote.min.js:

	if(rtext.slice(1,14) == 'Music Enabled'){
		MusicOn = true;
	}

Then you can take all of your ‘if’ statements and wrap them in one big if statement:

if(MusicOn == true){ .....code goes here }

You can do similar things to turn the music off in-game (making sure to stop all music running).

Summary

Except for that last little bit about turning music off and on, this is all done completely in-browser, without changing the game file, which means that the game file will work just fine when played offline (the music will just not play). Because the status line is redrawn after UNDO/RESTART/RESTORE, the music plays just fine after these events.
glkote.min.js (206 KB)

That’s pretty brilliant, and not as scary as one might think.

I wonder how hard it would be for someone to build this into QuixE natively?

Very nice technique, and very nice writeup. Kudos!

The thing with this is that all the sounds are currently hardcoded. For a “native” version you’d want Quixe to load the sounds from the file and have them triggered from the game code directly. Or alternately, use something like Vorple which similarly watches for text printed to a certain channel and reacts accordingly.

Vorple is my ideal interpreter, but I hate having to set up a fake network to get it to run. As I’m going along, though, I’ve begun to realize that a lot of cool effects can be achieved from vanilla glulx with just a little tweaking.

For instance, I discovered that you can apply text animations like gas transitions or JavaScript fades to all text that appears, and it takes a very small line of code. It’s fun!

I think that’s the one thing that holds Vorple back a bit. I would love a standalone interpreter for it, but I’m sure that would be a nightmare to implement across platforms.

It would almost require an extension with lots of “If Vorple is the interpeter…/else…” switches.