How do I play a sound in an if statement on Twine?

The format I’m using is Harlowe and I want to know if it’s possible to create a situation where a sound plays if a certain condition is met.

If it is possible, I would appreciate any guidance on how to do it.


Does it not work to just wrap your <audio> element in an if-statement?

I think I did that already, unless I’ve actually done it wrong. Here’s my code.

(set:$hide to 0)
(set: $hideAppear to 8)

(live: 1s)[
	(set: $hideAppear to it - 1)
	(if: $hideAppear is 0)
    [<audio src="audio\knocking.mp3"> (set: $hideCounter to 8)
    (replace: ?amount)[$hideAppear]]]`Preformatted text`

Ahh, okay, I think I see your problem—it’s how the (live:) macro is interacting with this. The way it’s written now, the audio element only appears on the page for one second, because $hideAppear keeps decrementing after hitting zero. But if we change that to (if: $hideAppear <= 0), the “live” macro is still replacing the audio element repeatedly (with itself) every second, so it never has time to actually play the sound.

(BTW, while testing to confirm this, I changed the audio markup to have visual controls, like so:

<audio controls autoplay>
  <source src="audio\knocking.mp3">

It’s not what you want for your actual game, obviously, but for debugging purposes, having a visual for the audio element made it easier to tell what was happening to it. Might not be useful to you, but I thought I’d pass it along just in case!)

I think your approach is going to need to be reworked, but I don’t have the brainspace to figure out a better way right now (I’ve been poking at this on breaks from working on my own project). I’ll try to come back to this later if no one else does.

How long is the audio file? What if you made the interval of the live refreshing longer than the length of the audio file? Does it play then? Now, that might not be exactly what your project needs either. Why does the if statement have to refresh every second?

Quick thought: If you just want to delay the sound (and the initialization of the $hideCounter variable), you could use the (after:) macro (Harlowe 3.3.5 manual) instead. That would do exactly what the snippet of code you’ve given was meant to do (play the sound and initialize the variable after 8 seconds). But I don’t know what the larger context is; if you wanted this to run repeatedly in the same passage, “after” wouldn’t work.

If you intend to add audio to a Harlowe based project then I strongly suggest you consider using Chapel’s Harlowe Audio Library third-party addon, instead of the <audio> element or your own Audio object based JavaScript.

Thanks for all the answers so far. I’ll give an update.

Since I wasn’t sure when I’d find the solution I decided to make a version of the minigame without sound, and instead have the text prompts come up saying things like “you hear knocking”. I’m happy to say that in this form, it works perfectly.

I’ll show the code for the whole thing below but I should first explain what it is. It’s basically a minigame. When you hear knocking, you need to click “hide”. When you hear glass shattering, you need to click “Stay still”. If the flashlight gets low, you need click “Recharge flashlight”. These events happen many times before the minigame ends, and failing to do any of these within a few seconds of getting the prompt results in death. And as I stated before, this minigame works perfectly without sound.

Here’s the code, with the exception of the buttons you click. The buttons basically add time to each respective timer to prevent the death from taking place.

(set: $hideAppear to 6)
(live: 1s)[
	(set: $hideAppear to it - 1)
	(if: $hideAppear is < 0)[You hear knocking]]
 (set: $hideMonster to 8)(live: 1s)[
	(set: $hideMonster to it - 1)
	(if: $hideMonster is < 0 or > 20)[(go-to:"Hide Death")]]
(set:$stillAppear to 14)
(live: 1s)[
	(set: $stillAppear to it - 1)
	(if: $stillAppear is < 0)[You hear glass shattering]](set: $stillMonster to 16)(live: 1s)[
	(set: $stillMonster to it - 1)
	(if: $stillMonster is < 0 or > 20)[(go-to:"Hide Death")]](set: $flashlight to 6)
Flashlight Battery: |amount>[$flashlight] (live: 1s)[
	(set: $flashlight to it - 1)
	(if: $flashlight is 0)[(go-to: "Hide Death")]
    (if:$flashlight is > 6)[(set:$flashlight to 6)]
	(replace: ?amount)[$flashlight]

If you guys think that Harlowe Audio Library would help with what I’m trying to accomplish here then I will most likely look into using that.

There are a couple of issues with your example:

1: You shouldn’t use the is comparison operator with the numerical mathematical operators like < “less than” (or > “greater than”), because is means “exactly equal to”. And a number can’t be both “exactly equal to” and “less than” at the same time.

So comparisons like…

(if: $hideAppear is < 0)[You hear knocking]]

…should be…

(if: $hideAppear < 0)[You hear knocking]]

2: Limiting a number variable to an upper limit should generally be done when the variable gets changed, not by using a comparison later.

You have the following in one of your (live:) macro calls…

(if:$flashlight is > 6)[(set:$flashlight to 6)]

…which is enforcing an upper limit of 6 to the $flashlight variable. But a better place & way to enforcing that limit is within the “button” that is increasing the $flashlight variable.
note: I don’t know how you’ve implemented that “button”, nor the amount it increases $flashlight by, so I made simple assumptions.

(link-repeat: "Increase flashlight")[
	(set: $flashlight to (min: it + 1, 6))

The above increases the variable by 1 (one) each time it’s select, and uses the (min:) macro to make sure the value never goes above 6.

3: You should general have as few (live:) macros (timers) active at the same time, especially if they have the same interval as another active (live:) macro.
note: If you really want to know the reason why just ask!

4: It is generally not a good idea to directly output text from a (live:) macro’s Hook, as it can be difficult to control the placement of that text. It is generally better to use a Named Hook combined with either the (append:) or (replace:) macro.

The following is a variant of your own code that takes the above points into consideration, as well as another couple I haven’t mentioned.

(set: $flashlight to 6, $hideAppear to 6, $hideMonster to 8, $stillAppear to 14,  $stillMonster to 16)
(live: 1s)[
	(set: $flashlight to it - 1, $hideAppear to it - 1, $hideMonster to it - 1, $stillAppear to it - 1,  $stillMonster to it - 1)
	(if: $flashlight is 0 or ($hideMonster < 0 or it > 20) or ($stillMonster < 0 or it > 20))[
		(go-to: "Hide Death")    
		(if: $hideAppear < 0)[
			(replace: ?message)[You hear knocking]
		(if: $stillAppear < 0)[
			(replace: ?message)[You hear glass shattering]
	(replace: ?amount)[$flashlight]
Flashlight Battery: |amount>[$flashlight]

note: It was unclear from your original posting about the above if the timers where meant to stop when/if either of the text messages (“You hear knocking” and “You hear glass shattering”) were shown. Which might be important as there are only two ways to stop a (live:) macro executing its Hook:

  1. Transitioning to another Passage.
  2. Calling the (stop:) macro from within the Hook associated with the (live:) macro call.

…and currently the conditions that cause the text to be displayed aren’t stopping the (live:) macros.