diff --git a/src/assets/sfx/A_bendUp.wav b/src/assets/sfx/A_bendUp.wav
new file mode 100644
index 0000000000000000000000000000000000000000..e7f05b19b081c1545ced8a6c688269ec4097caf1
Binary files /dev/null and b/src/assets/sfx/A_bendUp.wav differ
diff --git a/src/assets/sfx/Chiptone_Settings/Readme.txt b/src/assets/sfx/Chiptone_Settings/Readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..324526e4d687fd9f1f0c6643eb6ad11396f40642
--- /dev/null
+++ b/src/assets/sfx/Chiptone_Settings/Readme.txt
@@ -0,0 +1,8 @@
+The files in this folder were saved using Chiptone by SFB Games
+http://sfbgames.com/chiptone/
+----------------------------------------------------------------------
+
+- The sound names are not always a 1-to-1 match with the events they are tied to, but it should be relatively easy to figure out how they're being used in Hubs.
+
+- Generally, we try to export the sounds a 44100Hz, 16bit, and close to Chiptone's full volume. 
+- Until we have a 'settings' page in Hubs, individual sound volumes will have to be adjusted to taste.
diff --git a/src/assets/sfx/Chiptone_Settings/settings_AbendUp.cpt b/src/assets/sfx/Chiptone_Settings/settings_AbendUp.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..df2e2de68f58d683b739d260908466cfa7ab1574
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_AbendUp.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_Eb_blip.cpt b/src/assets/sfx/Chiptone_Settings/settings_Eb_blip.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..9111b3ea68b2dff6052529295ee66c02a0e5908c
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Eb_blip.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_GtritoneUp.cpt b/src/assets/sfx/Chiptone_Settings/settings_GtritoneUp.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..af9b22fe620510cb0748c3fbd3ac57fd74ac2d79
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_GtritoneUp.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_Mute.cpt b/src/assets/sfx/Chiptone_Settings/settings_Mute.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..697b6590b95df4b0e1c34094c9561c51ab2f0e3a
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Mute.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_Pause.cpt b/src/assets/sfx/Chiptone_Settings/settings_Pause.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..43920b140625a402943a7806fc8d2fb6e7801173
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Pause.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_Pen.cpt b/src/assets/sfx/Chiptone_Settings/settings_Pen.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..07aedf3e08159d4f192c5f29104dda4648559f58
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Pen.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_PenDraw1.cpt b/src/assets/sfx/Chiptone_Settings/settings_PenDraw1.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..6ce45654d8d1684bcfbb41f516159b2490311ed2
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_PenDraw1.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_PicSnapHey.cpt b/src/assets/sfx/Chiptone_Settings/settings_PicSnapHey.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..aa39278281205f1c0b9539be80cf89c1296e42e4
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_PicSnapHey.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..8a27ce5ecd321c3378baede4e4d311a5b2adeb3d
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd2.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd2.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..8238d139c9698c5d7643453eede33d9962962ace
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd2.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportStart.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..44fc3510add652c4f5fc7691bfa42f4d3c218ee1
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportStart2.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart2.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..b94094846c15297786528bd547180ce7faee47bf
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart2.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_message1.cpt b/src/assets/sfx/Chiptone_Settings/settings_message1.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..a2298abc4ad50d7bfd2e890537861f50d933fc93
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_message1.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_quickturn.cpt b/src/assets/sfx/Chiptone_Settings/settings_quickturn.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..2f08506f9c11975bf683ad88cbe01185c5331149
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_quickturn.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_slowdown.cpt b/src/assets/sfx/Chiptone_Settings/settings_slowdown.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..a913008366a75fb246be51a03217c3210ee3f141
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_slowdown.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_speedUp.cpt b/src/assets/sfx/Chiptone_Settings/settings_speedUp.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..54781d01f3b0a16e9b08c6cf97d586737247089e
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_speedUp.cpt differ
diff --git a/src/assets/sfx/Chiptone_Settings/settings_whoosh.cpt b/src/assets/sfx/Chiptone_Settings/settings_whoosh.cpt
new file mode 100644
index 0000000000000000000000000000000000000000..59561835d4412a3b0722b514135c4c21d6abe4a7
Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_whoosh.cpt differ
diff --git a/src/assets/sfx/D_teleportEnd.wav b/src/assets/sfx/D_teleportEnd.wav
new file mode 100644
index 0000000000000000000000000000000000000000..a94168da94778d6fb3eb69250510066389085687
Binary files /dev/null and b/src/assets/sfx/D_teleportEnd.wav differ
diff --git a/src/assets/sfx/D_teleportStart.wav b/src/assets/sfx/D_teleportStart.wav
new file mode 100644
index 0000000000000000000000000000000000000000..a6b0e24cb0bd8642cbd6afda928da3845a6a3dab
Binary files /dev/null and b/src/assets/sfx/D_teleportStart.wav differ
diff --git a/src/assets/sfx/Eb_blip.wav b/src/assets/sfx/Eb_blip.wav
new file mode 100644
index 0000000000000000000000000000000000000000..7428237df05a6655ba8a01a88878d43045967794
Binary files /dev/null and b/src/assets/sfx/Eb_blip.wav differ
diff --git a/src/assets/sfx/Fs_Mute.wav b/src/assets/sfx/Fs_Mute.wav
new file mode 100644
index 0000000000000000000000000000000000000000..b7bf200385d345e2b7534976db37b3f2f0b347bd
Binary files /dev/null and b/src/assets/sfx/Fs_Mute.wav differ
diff --git a/src/assets/sfx/Fs_UnMute.wav b/src/assets/sfx/Fs_UnMute.wav
new file mode 100644
index 0000000000000000000000000000000000000000..4bc442e4cb4287ab2fd3f644e8b6f88cbbb538a4
Binary files /dev/null and b/src/assets/sfx/Fs_UnMute.wav differ
diff --git a/src/assets/sfx/G_tritoneUp.wav b/src/assets/sfx/G_tritoneUp.wav
new file mode 100644
index 0000000000000000000000000000000000000000..b3489053b13befcd99e4e3a0e6e13c01f6e6e08f
Binary files /dev/null and b/src/assets/sfx/G_tritoneUp.wav differ
diff --git a/src/assets/sfx/Pause.wav b/src/assets/sfx/Pause.wav
new file mode 100644
index 0000000000000000000000000000000000000000..1dcf5bcb8cbb7be9791c81437566b3634ec5fcb0
Binary files /dev/null and b/src/assets/sfx/Pause.wav differ
diff --git a/src/assets/sfx/PenDraw1.wav b/src/assets/sfx/PenDraw1.wav
new file mode 100644
index 0000000000000000000000000000000000000000..f8f418a9686d169cfd685d35d500620c9748d2ed
Binary files /dev/null and b/src/assets/sfx/PenDraw1.wav differ
diff --git a/src/assets/sfx/PenSpawn.wav b/src/assets/sfx/PenSpawn.wav
new file mode 100644
index 0000000000000000000000000000000000000000..19d5ee41866b3bf9c804d0bea0c8cba2c877334a
Binary files /dev/null and b/src/assets/sfx/PenSpawn.wav differ
diff --git a/src/assets/sfx/PicSnapHey.wav b/src/assets/sfx/PicSnapHey.wav
new file mode 100644
index 0000000000000000000000000000000000000000..5dcb767213c9e528c8ee601e97ecc6870fe530cb
Binary files /dev/null and b/src/assets/sfx/PicSnapHey.wav differ
diff --git a/src/assets/sfx/footfall_click.wav b/src/assets/sfx/footfall_click.wav
new file mode 100644
index 0000000000000000000000000000000000000000..2d661cbdca152960a4a0831c141d574492102c98
Binary files /dev/null and b/src/assets/sfx/footfall_click.wav differ
diff --git a/src/assets/sfx/message1.wav b/src/assets/sfx/message1.wav
new file mode 100644
index 0000000000000000000000000000000000000000..ade42cae3e29e19378a02d78b492953e93c9e5d2
Binary files /dev/null and b/src/assets/sfx/message1.wav differ
diff --git a/src/assets/sfx/quickTurn.wav b/src/assets/sfx/quickTurn.wav
new file mode 100644
index 0000000000000000000000000000000000000000..130a9b9740cfac3ecbca923e8a98ed72f06561c8
Binary files /dev/null and b/src/assets/sfx/quickTurn.wav differ
diff --git a/src/assets/sfx/snap_rotate.wav b/src/assets/sfx/snap_rotate.wav
new file mode 100755
index 0000000000000000000000000000000000000000..dd9320a790d18f914aacb3ecacd03c3f0327d2e6
Binary files /dev/null and b/src/assets/sfx/snap_rotate.wav differ
diff --git a/src/assets/sfx/tap_mellow.wav b/src/assets/sfx/tap_mellow.wav
new file mode 100644
index 0000000000000000000000000000000000000000..a3fe32e27827a038dde848ece74f8c6914fd3ea0
Binary files /dev/null and b/src/assets/sfx/tap_mellow.wav differ
diff --git a/src/assets/sfx/whoosh1.wav b/src/assets/sfx/whoosh1.wav
new file mode 100644
index 0000000000000000000000000000000000000000..70360742313f25633e2d7b04f239e7b55d5c9da3
Binary files /dev/null and b/src/assets/sfx/whoosh1.wav differ
diff --git a/src/assets/sfx/woody_click.wav b/src/assets/sfx/woody_click.wav
new file mode 100644
index 0000000000000000000000000000000000000000..a67691024a6721096db4500b63b29fc58ed5430c
Binary files /dev/null and b/src/assets/sfx/woody_click.wav differ
diff --git a/src/avatar-selector.js b/src/avatar-selector.js
index 1ded01b457cd2a0d05cdd9a0ed9535b4fdd36a45..3d5be9e49643c767fb12a61067f0fc49513457ef 100644
--- a/src/avatar-selector.js
+++ b/src/avatar-selector.js
@@ -16,6 +16,7 @@ import "./components/animation-mixer";
 import "./components/audio-feedback";
 import "./components/loop-animation";
 import "./components/gamma-factor";
+import "./components/scene-sound";
 import "./gltf-component-mappings";
 import { avatars } from "./assets/avatars/avatars";
 
diff --git a/src/components/camera-tool.js b/src/components/camera-tool.js
index 9882eb88f481126ce63b9269f7a3dda44cbbfaab..2972d7fcd63b876aeaf22db191d998edb3183351 100644
--- a/src/components/camera-tool.js
+++ b/src/components/camera-tool.js
@@ -169,6 +169,7 @@ AFRAME.registerComponent("camera-tool", {
             sceneEl.emit("object_spawned", { objectType: ObjectTypes.CAMERA });
           });
         });
+        sceneEl.emit("camera_tool_took_snapshot");
         this.takeSnapshotNextTick = false;
       }
     };
diff --git a/src/components/emit-state-change.js b/src/components/emit-state-change.js
new file mode 100644
index 0000000000000000000000000000000000000000..c436293d703cfda73c2b3f14102b8abfcdbd8fad
--- /dev/null
+++ b/src/components/emit-state-change.js
@@ -0,0 +1,36 @@
+AFRAME.registerComponent("emit-state-change", {
+  multiple: true,
+  schema: {
+    state: { type: "string" },
+    transform: { type: "string", oneof: ["rising", "falling"] },
+    event: { type: "string" }
+  },
+
+  init() {
+    this.stateadded = this.stateadded.bind(this);
+    this.stateremoved = this.stateremoved.bind(this);
+  },
+
+  stateadded(e) {
+    if (e.detail === this.data.state) {
+      this.el.emit(this.data.event);
+    }
+  },
+  stateremoved(e) {
+    if (e.detail === this.data.state) {
+      this.el.emit(this.data.event);
+    }
+  },
+
+  update() {
+    this.el.removeEventListener("stateadded", this.stateadded);
+    this.el.removeEventListener("stateremoved", this.stateremoved);
+
+    if (this.data.transform === "rising") {
+      this.el.addEventListener("stateadded", this.stateadded);
+    }
+    if (this.data.transform === "falling") {
+      this.el.addEventListener("stateremoved", this.stateremoved);
+    }
+  }
+});
diff --git a/src/components/freeze-controller.js b/src/components/freeze-controller.js
index db434dec0f6bf42ec7bd5ff1bb0496de24d2ba65..bde74c975b2591220aac8e3f373defa05249cf4b 100644
--- a/src/components/freeze-controller.js
+++ b/src/components/freeze-controller.js
@@ -38,8 +38,10 @@ AFRAME.registerComponent("freeze-controller", {
     window.APP.store.update({ activity: { hasFoundFreeze: true } });
     NAF.connection.adapter.toggleFreeze();
     if (NAF.connection.adapter.frozen) {
+      this.el.emit("play_freeze_sound");
       this.el.addState("frozen");
     } else {
+      this.el.emit("play_thaw_sound");
       this.el.removeState("frozen");
     }
   }
diff --git a/src/components/scene-sound.js b/src/components/scene-sound.js
new file mode 100644
index 0000000000000000000000000000000000000000..249da3ebf130cca91a0ca6a544a3c33e9a3fc4b7
--- /dev/null
+++ b/src/components/scene-sound.js
@@ -0,0 +1,15 @@
+// As temporary measure to avoid having to customize the `sound` component that currently lives in `aframe`,
+// we say that a `scene-sound` will have an associated `sound` component that is triggered when the given
+// event is fired on the scene.
+AFRAME.registerComponent("scene-sound", {
+  multiple: true,
+  schema: {
+    sound: { type: "string" },
+    on: { type: "string" }
+  },
+
+  init() {
+    const sound = this.el.components[`${this.attrName.replace("scene-", "")}`];
+    this.el.sceneEl.addEventListener(this.data.on, sound.playSound);
+  }
+});
diff --git a/src/components/tools/networked-drawing.js b/src/components/tools/networked-drawing.js
index 518de9cd82489111ce6585861df99cfd26776015..8ff854b02e7bc38bf4725540937b1044a1edd0b9 100644
--- a/src/components/tools/networked-drawing.js
+++ b/src/components/tools/networked-drawing.js
@@ -105,7 +105,7 @@ AFRAME.registerComponent("networked-drawing", {
     this.scene.remove(this.drawing);
   },
 
-  tick() {
+  tick(t) {
     const connected = NAF.connection.isConnected() && this.networkedEl;
     const isMine = connected && NAF.utils.isMine(this.networkedEl);
 
@@ -144,7 +144,7 @@ AFRAME.registerComponent("networked-drawing", {
       }
     }
 
-    this._deleteLines();
+    this._deleteExpiredLines(t);
   },
 
   _broadcastDrawing: (() => {
@@ -193,12 +193,11 @@ AFRAME.registerComponent("networked-drawing", {
     };
   })(),
 
-  _deleteLines() {
+  _deleteExpiredLines(time) {
     const length = this.networkBufferHistory.length;
     if (length > 0) {
-      const now = Date.now();
-      const time = this.networkBufferHistory[0].time;
-      if (length > this.data.maxLines || time + this.data.maxDrawTimeout <= now) {
+      const drawTime = this.networkBufferHistory[0].time;
+      if (length > this.data.maxLines || drawTime + this.data.maxDrawTimeout <= time) {
         const datum = this.networkBufferHistory[0];
         if (length > 1) {
           datum.idxLength += 2 - (this.segments % 2);
@@ -381,6 +380,7 @@ AFRAME.registerComponent("networked-drawing", {
   })(),
 
   _endLine() {
+    this.el.emit("stop_draw");
     if (!this.drawStarted) return;
 
     if (this.networkedEl && NAF.utils.isMine(this.networkedEl)) this._pushToNetworkBuffer(null);
@@ -388,7 +388,7 @@ AFRAME.registerComponent("networked-drawing", {
     const datum = {
       networkBufferCount: this.networkBufferCount,
       idxLength: this.vertexCount - 1,
-      time: Date.now()
+      time: this.el.sceneEl.clock.elapsedTime * 1000
     };
     this.networkBufferHistory.push(datum);
     this.vertexCount = 0;
diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js
index f09e63101f4366440a06ba4d60481999edaba6b8..ed6583aa0f8ccc13b7246e40845e82ef4a045998 100644
--- a/src/components/tools/pen.js
+++ b/src/components/tools/pen.js
@@ -160,13 +160,14 @@ AFRAME.registerComponent("pen", {
     if (this.currentDrawing) {
       this.el.object3D.getWorldPosition(this.worldPosition);
       this._getNormal(this.normal, this.worldPosition, this.direction);
-
+      this.el.emit("start_draw");
       this.currentDrawing.startDraw(this.worldPosition, this.direction, this.normal, this.data.color, this.data.radius);
     }
   },
 
   _endDraw() {
     if (this.currentDrawing) {
+      this.el.emit("stop_draw");
       this.timeSinceLastDraw = 0;
       this.el.object3D.getWorldPosition(this.worldPosition);
       this._getNormal(this.normal, this.worldPosition, this.direction);
diff --git a/src/hub.html b/src/hub.html
index b935796c02caec4cfeda0a3a7f712ca0b47f0990..eb014178ae3d76838335191c5e867f369aafcfc9 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -66,10 +66,31 @@
             <a-asset-item id="botrobert" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotRobert_Avatar-e9554880f3.gltf"></a-asset-item>
             <a-asset-item id="botwoody" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotWoody_Avatar-0140485a23.gltf"></a-asset-item>
 
-            <a-asset-item id="quack" src="./assets/sfx/quack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item>
-            <a-asset-item id="specialquack" src="./assets/sfx/specialquack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item>
-
-            <img id="water-normal-map" crossorigin="anonymous" src="./assets/waternormals.jpg">
+            <a-asset-item id="quack"                                      src="./assets/sfx/quack.mp3"          response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="specialquack"                               src="./assets/sfx/specialquack.mp3"   response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-teleport_start"                 src="./assets/sfx/D_teleportStart.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-teleport_end"                   src="./assets/sfx/D_teleportEnd.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-snap_rotate"                    src="./assets/sfx/quickTurn.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-media_loaded"                   src="./assets/sfx/A_bendUp.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-hud_hover_start"                src="./assets/sfx/Eb_blip.wav"  response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-hover"                          src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="DISABLED_sound_asset-hover_off"             src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-cursor_distance_change_blocked" src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-cursor_distance_changed"        src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-hud_click"                      src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-toggle_mute"                    src="./assets/sfx/Fs_Mute.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-toggle_freeze"                  src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-freeze"                         src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-thaw"                           src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-toggle_space_bubble"            src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-spawn_pen"                      src="./assets/sfx/PenSpawn.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-increase_pen_radius"            src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-decrease_pen_radius"            src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-next_pen_color"                 src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-prev_pen_color"                 src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-start_draw"                     src="./assets/sfx/PenDraw1.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-stop_draw"                      src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-camera_tool_took_snapshot"      src="./assets/sfx/PicSnapHey.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
 
             <!-- Templates -->
             <template id="video-template">
@@ -153,6 +174,10 @@
                     destroy-at-extreme-distances
                     set-yxz-order
                     pinnable
+                    sound__hover="src: #sound_asset-hover; on: hovered; poolSize: 1;"
+                    sound__hoveroff ="src: #sound_asset-hover_off; on: unhovered; poolSize: 1;"
+                    emit-state-change__hovered="state: hovered; transform: rising; event: hovered;"
+                    emit-state-change__unhovered="state: hovered; transform: falling; event: unhovered;"
                 >
                     <a-entity class="ui interactable-ui" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end">
                         <a-entity class="freeze-menu" visible-while-frozen="withinDistance: 3;">
@@ -174,6 +199,16 @@
                     sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;"
                     hoverable
                     scale="0.5 0.5 0.5"
+                    sound__next_pen_color="src: #sound_asset-next_pen_color; on: next_pen_color; poolSize: 2;"
+                    sound__prev_pen_color="src: #sound_asset-prev_pen_color; on: prev_pen_color; poolSize: 2;"
+                    sound__start_draw="src: #sound_asset-start_draw; on: start_draw; poolSize: 2;"
+                    sound__stop_draw="src: #sound_asset-stop_draw; on: stop_draw; poolSize: 2;"
+                    sound__increase_pen_radius="src: #sound_asset-increase_pen_radius; on: increase_pen_radius; poolSize: 2;"
+                    sound__decrease_pen_radius="src: #sound_asset-decrease_pen_radius; on: decrease_pen_radius; poolSize: 2;"
+                    sound__hover="src: #sound_asset-hover; on: hovered; poolSize: 1;"
+                    sound__hoveroff ="src: #sound_asset-hover_off; on: unhovered; poolSize: 1;"
+                    emit-state-change__hovered="state: hovered; transform: rising; event: hovered;"
+                    emit-state-change__unhovered="state: hovered; transform: falling; event: unhovered;"
                 >
                     <a-sphere
                         id="pen"
@@ -347,16 +382,47 @@
                 vr-mode-toggle-playing__hud-controller
             >
                 <a-entity in-world-hud="haptic:#player-right-controller;raycaster:#player-right-controller;" rotation="30 0 0">
-                    <a-rounded height="0.08" width="0.5" color="#000000" position="-0.20 0.125 0" radius="0.040" opacity="0.35" class="hud bg"></a-rounded>
-                    <a-entity id="hud-hub-entry-link" text=" value:; width:1.1; align:center;" position="0.05 0.165 0"></a-entity>
-                    <a-rounded height="0.13" width="0.59" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;" hoverable></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze" hoverable></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pen; activeTooltipText: Pen; image: #spawn-pen; hoverImage: #spawn-pen-hover; activeImage: #spawn-pen; activeHoverImage: #spawn-pen-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud penhud" material="alphaTest:0.1;" hoverable></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Camera; activeTooltipText: Camera; image: #spawn-camera; hoverImage: #spawn-camera-hover; activeImage: #spawn-camera; activeHoverImage: #spawn-camera-hover" scale="0.1 0.1 0.1" position="0.28 0 0.001" class="ui hud cameraBtn" material="alphaTest:0.1;" hoverable></a-image>
-                    <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg">
-                        <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity>
-                    </a-rounded>
+                <a-rounded height="0.08" width="0.5" color="#000000" position="-0.20 0.125 0" radius="0.040" opacity="0.35" class="hud bg"></a-rounded>
+                <a-entity id="hud-hub-entry-link" text=" value:; width:1.1; align:center;" position="0.05 0.165 0"></a-entity>
+                <a-rounded height="0.13" width="0.59" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded>
+                <a-image
+                    icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover"
+                    scale="0.1 0.1 0.1"
+                    position="-0.17 0 0.001"
+                    class="ui hud mic"
+                    material="alphaTest:0.1;"
+                    hoverable
+                    sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;"
+                ></a-image>
+                <a-image
+                    icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover"
+                    scale="0.2 0.2 0.2"
+                    position="0 0 0.005"
+                    class="ui hud freeze"
+                    hoverable
+                    sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;"
+                ></a-image>
+                <a-image
+                    icon-button="tooltip: #hud-tooltip; tooltipText: Pen; activeTooltipText: Pen; image: #spawn-pen; hoverImage: #spawn-pen-hover; activeImage: #spawn-pen; activeHoverImage: #spawn-pen-hover"
+                    scale="0.1 0.1 0.1"
+                    position="0.17 0 0.001"
+                    class="ui hud penhud"
+                    material="alphaTest:0.1;"
+                    hoverable
+                    sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;"
+                ></a-image>
+                <a-image
+                    icon-button="tooltip: #hud-tooltip; tooltipText: Camera; activeTooltipText: Camera; image: #spawn-camera; hoverImage: #spawn-camera-hover; activeImage: #spawn-camera; activeHoverImage: #spawn-camera-hover"
+                    scale="0.1 0.1 0.1"
+                    position="0.28 0 0.001"
+                    class="ui hud cameraBtn"
+                    material="alphaTest:0.1;"
+                    hoverable
+                    sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;"
+                ></a-image>
+                <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg">
+                    <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity>
+                </a-rounded>
                 </a-entity>
             </a-entity>
 
@@ -368,6 +434,44 @@
                 rotation
                 pitch-yaw-rotator
                 set-yxz-order
+                sound__teleport_start="positional: false; src: #sound_asset-teleport_start; on: nothing; poolSize: 2;"
+                scene-sound__teleport_start="on: right-teleport_down;"
+                sound__teleport_start2="positional: false; src: #sound_asset-teleport_start; on: nothing; poolSize: 2;"
+                scene-sound__teleport_start2="on: left-teleport_down;"
+                sound__teleport_end="positional: false; src: #sound_asset-teleport_end; on: nothing; poolSize: 2;"
+                scene-sound__teleport_end="on: right-teleport_up;"
+                sound__teleport_end2="positional: false; src: #sound_asset-teleport_end; on: nothing; poolSize: 2;"
+                scene-sound__teleport_end2="on: left-teleport_up;"
+                sound__snap_rotate_left="positional: false; src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;"
+                scene-sound__snap_rotate_left="on: snap_rotate_left;"
+                sound__snap_rotate_right="positional: false; src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;"
+                scene-sound__snap_rotate_right="on: snap_rotate_right;"
+                sound__model_loaded="positional: false; src: #sound_asset-media_loaded; on: nothing; poolSize: 2;"
+                scene-sound__model_loaded="on: model-loaded;"
+                sound__image_loaded="positional: false; src: #sound_asset-media_loaded; on: nothing; poolSize: 2;"
+                scene-sound__image_loaded="on: image-loaded;"
+                sound__hud_action_mute="positional: false; src: #sound_asset-toggle_mute; on: nothing; poolSize: 2;"
+                scene-sound__hud_action_mute="on: action_mute;"
+                sound__hud_action_freeze="positional: false; src: #sound_asset-toggle_freeze; on: nothing; poolSize: 2;"
+                scene-sound__hud_action_freeze="on: action_freeze;"
+                sound__action_freeze="positional: false; src: #sound_asset-freeze; on: nothing; poolSize: 2;"
+                scene-sound__action_freeze="on: play_freeze_sound;"
+                sound__action_thaw="positional: false; src: #sound_asset-thaw; on: nothing; poolSize: 2;"
+                scene-sound__action_thaw="on: play_thaw_sound;"
+                sound__hud_action_space_bubble="positional: false; src: #sound_asset-toggle_space_bubble; on: nothing; poolSize: 2;"
+                scene-sound__hud_action_space_bubble="on: action_space_bubble;"
+                sound__hud_spawn_pen="positional: false; src: #sound_asset-spawn_pen; on: nothing; poolSize: 2;"
+                scene-sound__hud_spawn_pen="on: spawn_pen;"
+                sound__hud_hover_start="positional: false; src: #sound_asset-hud_hover_start; on: nothing; poolSize: 5;"
+                scene-sound__hud_hover_start="on: play_sound-hud_hover_start;"
+                sound__hud_click="positional: false; src: #sound_asset-hud_click; on: nothing; poolSize: 1;"
+                scene-sound__hud_click="on: hud_click;"
+                sound__cursor_distance_changed="positional: false; src: #sound_asset-cursor_distance_changed; on: nothing; poolSize: 1;"
+                scene-sound__cursor_distance_changed="on: cursor-distance-changed;"
+                sound__cursor_distance_change_blocked="positional: false; src: #sound_asset-cursor_distance_change_blocked; on: nothing; poolSize: 1;"
+                scene-sound__cursor_distance_change_blocked="on: cursor-distance-change-blocked;"
+                sound__camera_tool_took_snapshot="positional: false; src: #sound_asset-camera_tool_took_snapshot; on: nothing; poolSize: 1;"
+                scene-sound__camera_tool_took_snapshot="on: camera_tool_took_snapshot;"
             >
                 <a-entity
                     id="gaze-teleport"
diff --git a/src/hub.js b/src/hub.js
index 81a821cc3706a80bd1d36480a66e316ce744f088..3205ce594fb5ef653afa346f8b85c9887edf74ee 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -63,6 +63,8 @@ import "./components/destroy-at-extreme-distances";
 import "./components/gamma-factor";
 import "./components/visible-to-owner";
 import "./components/camera-tool";
+import "./components/scene-sound";
+import "./components/emit-state-change";
 import "./components/action-to-event";
 import "./components/stop-event-propagation";
 
diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js
index 74577ff1ec0291d8bd14665fe0a7054c8a0f70e3..62f597fa7859b5c9b4affd6b21ac8c2596fc71ea 100644
--- a/src/react-components/2d-hud.js
+++ b/src/react-components/2d-hud.js
@@ -4,26 +4,35 @@ import cx from "classnames";
 
 import styles from "../assets/stylesheets/2d-hud.scss";
 import uiStyles from "../assets/stylesheets/ui-root.scss";
+import { WithHoverSound } from "./wrap-with-audio";
 
 const TopHUD = ({ muted, frozen, onToggleMute, onToggleFreeze, onSpawnPen, onSpawnCamera }) => (
   <div className={cx(styles.container, styles.top, styles.unselectable)}>
     <div className={cx(uiStyles.uiInteractive, styles.panel, styles.left)}>
+      <WithHoverSound>
+        <div
+          className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })}
+          title={muted ? "Unmute Mic" : "Mute Mic"}
+          onClick={onToggleMute}
+        />
+      </WithHoverSound>
+    </div>
+    <WithHoverSound>
       <div
-        className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })}
-        title={muted ? "Unmute Mic" : "Mute Mic"}
-        onClick={onToggleMute}
+        className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, {
+          [styles.active]: frozen
+        })}
+        title={frozen ? "Resume" : "Pause"}
+        onClick={onToggleFreeze}
       />
-    </div>
-    <div
-      className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, {
-        [styles.active]: frozen
-      })}
-      title={frozen ? "Resume" : "Pause"}
-      onClick={onToggleFreeze}
-    />
+    </WithHoverSound>
     <div className={cx(uiStyles.uiInteractive, styles.panel, styles.right)}>
-      <div className={cx(styles.iconButton, styles.spawn_pen)} title={"Drawing Pen"} onClick={onSpawnPen} />
-      <div className={cx(styles.iconButton, styles.spawn_camera)} title={"Camera"} onClick={onSpawnCamera} />
+      <WithHoverSound>
+        <div className={cx(styles.iconButton, styles.spawn_pen)} title={"Drawing Pen"} onClick={onSpawnPen} />
+      </WithHoverSound>
+      <WithHoverSound>
+        <div className={cx(styles.iconButton, styles.spawn_camera)} title={"Camera"} onClick={onSpawnCamera} />
+      </WithHoverSound>
     </div>
   </div>
 );
@@ -61,11 +70,13 @@ const BottomHUD = ({ onCreateObject, showPhotoPicker, onMediaPicked }) => (
       <div />
     )}
     <div>
-      <div
-        className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)}
-        title={"Create Object"}
-        onClick={onCreateObject}
-      />
+      <WithHoverSound>
+        <div
+          className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)}
+          title={"Create Object"}
+          onClick={onCreateObject}
+        />
+      </WithHoverSound>
     </div>
   </div>
 );
diff --git a/src/react-components/auto-exit-warning.js b/src/react-components/auto-exit-warning.js
index d8691a85182c7f8bbd3ee9a7472e428df9aa2d3b..ede4d6e5290034768eed7eb1c0707a9d47e4a72b 100644
--- a/src/react-components/auto-exit-warning.js
+++ b/src/react-components/auto-exit-warning.js
@@ -2,6 +2,8 @@ import React from "react";
 import { FormattedMessage } from "react-intl";
 import PropTypes from "prop-types";
 
+import { WithHoverSound } from "./wrap-with-audio";
+
 const AutoExitWarning = props => (
   <div className="autoexit-panel">
     <div className="autoexit-panel__title">
@@ -12,9 +14,11 @@ const AutoExitWarning = props => (
     <div className="autoexit-panel__subtitle">
       <FormattedMessage id="autoexit.subtitle" />
     </div>
-    <div className="autoexit-panel__cancel-button" onClick={props.onCancel}>
-      <FormattedMessage id="autoexit.cancel" />
-    </div>
+    <WithHoverSound>
+      <div className="autoexit-panel__cancel-button" onClick={props.onCancel}>
+        <FormattedMessage id="autoexit.cancel" />
+      </div>
+    </WithHoverSound>
   </div>
 );
 
diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js
index 0c024655d46b39c77bf7c5f50e87733907cbd790..6ffbe380d160ae375b4171585e7422cd09c8809d 100644
--- a/src/react-components/avatar-selector.js
+++ b/src/react-components/avatar-selector.js
@@ -4,6 +4,7 @@ import { injectIntl } from "react-intl";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faAngleLeft } from "@fortawesome/free-solid-svg-icons/faAngleLeft";
 import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight";
+import { WithHoverSound } from "./wrap-with-audio";
 
 class AvatarSelector extends Component {
   static propTypes = {
@@ -176,12 +177,16 @@ class AvatarSelector extends Component {
           />
           <a-entity hide-when-quality="low" light="type: ambient; color: #FFF" />
         </a-scene>
-        <button className="avatar-selector__previous-button" onClick={this.emitChangeToPrevious}>
-          <FontAwesomeIcon icon={faAngleLeft} />
-        </button>
-        <button className="avatar-selector__next-button" onClick={this.emitChangeToNext}>
-          <FontAwesomeIcon icon={faAngleRight} />
-        </button>
+        <WithHoverSound>
+          <button className="avatar-selector__previous-button" onClick={this.emitChangeToPrevious}>
+            <FontAwesomeIcon icon={faAngleLeft} />
+          </button>
+        </WithHoverSound>
+        <WithHoverSound>
+          <button className="avatar-selector__next-button" onClick={this.emitChangeToNext}>
+            <FontAwesomeIcon icon={faAngleRight} />
+          </button>
+        </WithHoverSound>
       </div>
     );
   }
diff --git a/src/react-components/create-object-dialog.js b/src/react-components/create-object-dialog.js
index 6fa624612dcf4eadb658fa7f74f069983a9aeb53..1951aefbdfe6476fbe9124bcac2d6401f40a6828 100644
--- a/src/react-components/create-object-dialog.js
+++ b/src/react-components/create-object-dialog.js
@@ -7,6 +7,7 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes";
 import styles from "../assets/stylesheets/create-object-dialog.scss";
 import cx from "classnames";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 const attributionHostnames = {
   "giphy.com": giphyLogo,
@@ -108,14 +109,18 @@ export default class CreateObjectDialog extends Component {
     const { onCreate, onClose, ...other } = this.props; // eslint-disable-line no-unused-vars
 
     const cancelButton = (
-      <label className={cx(styles.smallButton, styles.cancelIcon)} onClick={this.reset}>
-        <FontAwesomeIcon icon={faTimes} />
-      </label>
+      <WithHoverSound>
+        <label className={cx(styles.smallButton, styles.cancelIcon)} onClick={this.reset}>
+          <FontAwesomeIcon icon={faTimes} />
+        </label>
+      </WithHoverSound>
     );
     const uploadButton = (
-      <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}>
-        <FontAwesomeIcon icon={faPaperclip} />
-      </label>
+      <WithHoverSound>
+        <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}>
+          <FontAwesomeIcon icon={faPaperclip} />
+        </label>
+      </WithHoverSound>
     );
     const filenameLabel = <label className={cx(styles.leftSideOfInput)}>{this.state.fileName}</label>;
     const urlInput = (
@@ -129,7 +134,7 @@ export default class CreateObjectDialog extends Component {
     );
 
     return (
-      <DialogContainer title="Create Object" onClose={onClose} {...other}>
+      <DialogContainer title="Create Object" onClose={this.props.onClose} {...other}>
         <div>
           {isMobile ? mobileInstructions : desktopInstructions}
           <form onSubmit={this.onCreateClicked}>
@@ -146,9 +151,11 @@ export default class CreateObjectDialog extends Component {
                 {this.state.url || this.state.fileName ? cancelButton : uploadButton}
               </div>
               <div className={styles.buttons}>
-                <button className={styles.actionButton}>
-                  <span>Create</span>
-                </button>
+                <WithHoverSound>
+                  <button className={styles.actionButton}>
+                    <span>Create</span>
+                  </button>
+                </WithHoverSound>
               </div>
               {this.state.attributionImage ? (
                 <div>
diff --git a/src/react-components/create-room-dialog.js b/src/react-components/create-room-dialog.js
index a42a2f36be7fea6bdfef0d74c6120ce59e24df01..b2f1cba898b956e1bbc35feda14638d1f00a669b 100644
--- a/src/react-components/create-room-dialog.js
+++ b/src/react-components/create-room-dialog.js
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$";
 
@@ -54,9 +55,11 @@ export default class CreateRoomDialog extends Component {
                 />
               )}
               <div className="custom-scene-form__buttons">
-                <button className="custom-scene-form__action-button">
-                  <span>Create Room</span>
-                </button>
+                <WithHoverSound>
+                  <button className="custom-scene-form__action-button">
+                    <span>Create Room</span>
+                  </button>
+                </WithHoverSound>
               </div>
             </div>
           </form>
diff --git a/src/react-components/dialog-container.js b/src/react-components/dialog-container.js
index 18c37956c7408150d59d2385e251817ad9747cb7..a38923e7a71d57b4bb66aa23c855381af362d3ff 100644
--- a/src/react-components/dialog-container.js
+++ b/src/react-components/dialog-container.js
@@ -1,5 +1,6 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class DialogContainer extends Component {
   static propTypes = {
@@ -41,9 +42,11 @@ export default class DialogContainer extends Component {
           <div className="dialog__box">
             <div className="dialog__box__contents">
               {this.props.onClose && (
-                <button className="dialog__box__contents__close" onClick={this.props.onClose}>
-                  <span>×</span>
-                </button>
+                <WithHoverSound>
+                  <button className="dialog__box__contents__close" onClick={this.props.onClose}>
+                    <span>×</span>
+                  </button>
+                </WithHoverSound>
               )}
               <div className="dialog__box__contents__title">{this.props.title}</div>
               <div className="dialog__box__contents__body">{this.props.children}</div>
diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js
index 9e64b3ab4c419b6728e1351e7080629f1b6a97ea..3b4d034d60693c6a2a99538581cae8499b666a69 100644
--- a/src/react-components/entry-buttons.js
+++ b/src/react-components/entry-buttons.js
@@ -9,27 +9,32 @@ import GearVREntryImg from "../assets/images/gearvr_entry.svg";
 import DaydreamEntryImg from "../assets/images/daydream_entry.svg";
 import DeviceEntryImg from "../assets/images/device_entry.svg";
 import styles from "../assets/stylesheets/entry.scss";
-
-const EntryButton = props => (
-  <button className={styles.entryButton} onClick={props.onClick}>
-    <img src={props.iconSrc} className={styles.icon} />
-    <div className={styles.label}>
-      <div className={styles.contents}>
-        <span>
-          <FormattedMessage id={props.prefixMessageId} />
-        </span>
-        <span className={styles.bolded}>
-          <FormattedMessage id={props.mediumMessageId} />
-        </span>
-        {props.subtitle && (
-          <div className={styles.subtitle}>
-            <FormattedMessage id={props.subtitle} />
+import { WithHoverSound } from "./wrap-with-audio";
+
+const EntryButton = props => {
+  return (
+    <WithHoverSound>
+      <button className={styles.entryButton} onClick={props.onClick}>
+        <img src={props.iconSrc} className={styles.icon} />
+        <div className={styles.label}>
+          <div className={styles.contents}>
+            <span>
+              <FormattedMessage id={props.prefixMessageId} />
+            </span>
+            <span className={styles.bolded}>
+              <FormattedMessage id={props.mediumMessageId} />
+            </span>
+            {props.subtitle && (
+              <div className={styles.subtitle}>
+                <FormattedMessage id={props.subtitle} />
+              </div>
+            )}
           </div>
-        )}
-      </div>
-    </div>
-  </button>
-);
+        </div>
+      </button>
+    </WithHoverSound>
+  );
+};
 
 EntryButton.propTypes = {
   onClick: PropTypes.func,
diff --git a/src/react-components/help-dialog.js b/src/react-components/help-dialog.js
index abb04a1c9e107323f9993d0e160b352c39753fa2..0e20c0aa98499e87f8e9fa4e52858d610d183f7f 100644
--- a/src/react-components/help-dialog.js
+++ b/src/react-components/help-dialog.js
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import { FormattedMessage } from "react-intl";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class HelpDialog extends Component {
   render() {
@@ -9,9 +10,12 @@ export default class HelpDialog extends Component {
         <div className="info-dialog__help">
           <p style={{ textAlign: "center" }}>
             Join the Hubs community on{" "}
-            <a target="_blank" rel="noopener noreferrer" href="https://discord.gg/XzrGUY8">
-              Discord
-            </a>!
+            <WithHoverSound>
+              <a target="_blank" rel="noopener noreferrer" href="https://discord.gg/XzrGUY8">
+                Discord
+              </a>
+            </WithHoverSound>
+            !
           </p>
           <p>When in a room, other avatars can see and hear you.</p>
           <p>
@@ -28,15 +32,25 @@ export default class HelpDialog extends Component {
             The <b>Pause/Resume Toggle</b> pauses all other avatars and lets you block others or remove objects.
           </p>
           <p className="dialog__box__contents__links">
-            <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md">
-              <FormattedMessage id="profile.terms_of_use" />
-            </a>
-            <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md">
-              <FormattedMessage id="profile.privacy_notice" />
-            </a>
-            <a target="_blank" rel="noopener noreferrer" href="/?report">
-              <FormattedMessage id="help.report_issue" />
-            </a>
+            <WithHoverSound>
+              <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md">
+                <FormattedMessage id="profile.terms_of_use" />
+              </a>
+            </WithHoverSound>
+            <WithHoverSound>
+              <a
+                target="_blank"
+                rel="noopener noreferrer"
+                href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
+              >
+                <FormattedMessage id="profile.privacy_notice" />
+              </a>
+            </WithHoverSound>
+            <WithHoverSound>
+              <a target="_blank" rel="noopener noreferrer" href="/?report">
+                <FormattedMessage id="help.report_issue" />
+              </a>
+            </WithHoverSound>
           </p>
         </div>
       </DialogContainer>
diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js
index c95a342fc2d4ac12755a48321a3bcb97848aa4bb..521490271cab4cc98bbdfab4e682f23d4fef974d 100644
--- a/src/react-components/home-root.js
+++ b/src/react-components/home-root.js
@@ -21,6 +21,7 @@ import ReportDialog from "./report-dialog.js";
 import JoinUsDialog from "./join-us-dialog.js";
 import UpdatesDialog from "./updates-dialog.js";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 addLocaleData([...en]);
 
@@ -173,17 +174,29 @@ class HomeRoot extends Component {
         <div className={styles.home}>
           <div className={mainContentClassNames}>
             <div className={styles.headerContent}>
-              <div className={styles.titleAndNav}>
+              <div className={styles.titleAndNav} onClick={() => (document.location = "/")}>
+                <WithHoverSound>
+                  <div className={styles.hubs}>hubs</div>
+                </WithHoverSound>
+                <WithHoverSound>
+                  <div className={styles.preview}>preview</div>
+                </WithHoverSound>
                 <div className={styles.links}>
-                  <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener">
-                    <FormattedMessage id="home.source_link" />
-                  </a>
-                  <a href="https://discord.gg/XzrGUY8" rel="noreferrer noopener">
-                    <FormattedMessage id="home.community_link" />
-                  </a>
-                  <a href="/spoke" rel="noreferrer noopener">
-                    Spoke
-                  </a>
+                  <WithHoverSound>
+                    <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener">
+                      <FormattedMessage id="home.source_link" />
+                    </a>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <a href="https://discord.gg/XzrGUY8" rel="noreferrer noopener">
+                      <FormattedMessage id="home.community_link" />
+                    </a>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <a href="/spoke" rel="noreferrer noopener">
+                      Spoke
+                    </a>
+                  </WithHoverSound>
                 </div>
               </div>
             </div>
@@ -219,62 +232,76 @@ class HomeRoot extends Component {
               </div>
               {this.state.environments.length > 1 && (
                 <div>
-                  <div className={styles.joinButton}>
-                    <a href="/link">
-                      <FormattedMessage id="home.join_room" />
-                    </a>
-                  </div>
-                  <div className={styles.spokeButton}>
-                    <a href="/spoke">
-                      <FormattedMessage id="home.create_with_spoke" />
-                    </a>
-                  </div>
+                  <WithHoverSound>
+                    <div className={styles.joinButton}>
+                      <a href="/link">
+                        <FormattedMessage id="home.join_room" />
+                      </a>
+                    </div>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <div className={styles.spokeButton}>
+                      <a href="/spoke">
+                        <FormattedMessage id="home.create_with_spoke" />
+                      </a>
+                    </div>
+                  </WithHoverSound>
                 </div>
               )}
             </div>
             <div className={styles.footerContent}>
               <div className={styles.links}>
                 <div className={styles.top}>
-                  <a
-                    className={styles.link}
-                    rel="noopener noreferrer"
-                    href="#"
-                    onClick={this.onDialogLinkClicked(this.showJoinUsDialog.bind(this))}
-                  >
-                    <FormattedMessage id="home.join_us" />
-                  </a>
-                  <a
-                    className={styles.link}
-                    rel="noopener noreferrer"
-                    href="#"
-                    onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))}
-                  >
-                    <FormattedMessage id="home.get_updates" />
-                  </a>
-                  <a
-                    className={styles.link}
-                    rel="noopener noreferrer"
-                    href="#"
-                    onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))}
-                  >
-                    <FormattedMessage id="home.report_issue" />
-                  </a>
-                  <a
-                    className={styles.link}
-                    target="_blank"
-                    rel="noopener noreferrer"
-                    href="https://github.com/mozilla/hubs/blob/master/TERMS.md"
-                  >
-                    <FormattedMessage id="home.terms_of_use" />
-                  </a>
-                  <a
-                    className={styles.link}
-                    target="_blank"
-                    rel="noopener noreferrer"
-                    href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
-                  >
-                    <FormattedMessage id="home.privacy_notice" />
-                  </a>
+                  <WithHoverSound>
+                    <a
+                      className={styles.link}
+                      rel="noopener noreferrer"
+                      href="#"
+                      onClick={this.onDialogLinkClicked(this.showJoinUsDialog.bind(this))}
+                    >
+                      <FormattedMessage id="home.join_us" />
+                    </a>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <a
+                      className={styles.link}
+                      rel="noopener noreferrer"
+                      href="#"
+                      onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))}
+                    >
+                      <FormattedMessage id="home.get_updates" />
+                    </a>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <a
+                      className={styles.link}
+                      rel="noopener noreferrer"
+                      href="#"
+                      onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))}
+                    >
+                      <FormattedMessage id="home.report_issue" />
+                    </a>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <a
+                      className={styles.link}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      href="https://github.com/mozilla/hubs/blob/master/TERMS.md"
+                    >
+                      <FormattedMessage id="home.terms_of_use" />
+                    </a>
+                  </WithHoverSound>
+                  <WithHoverSound>
+                    <a
+                      className={styles.link}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
+                    >
+                      <FormattedMessage id="home.privacy_notice" />
+                    </a>
+                  </WithHoverSound>
 
                   <img className={styles.mozLogo} src={mozLogo} />
                 </div>
diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js
index 640f00d5d790516e0d8cd2d71f9bb71f1a13e89c..11f846e744009d5dca6a329d4a371a54c449c2c1 100644
--- a/src/react-components/hub-create-panel.js
+++ b/src/react-components/hub-create-panel.js
@@ -8,6 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { resolveURL, extractUrlBase } from "../utils/resolveURL";
 import { getReticulumFetchUrl } from "../utils/phoenix-utils";
 import CreateRoomDialog from "./create-room-dialog.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png";
 import styles from "../assets/stylesheets/hub-create.scss";
@@ -186,10 +187,12 @@ class HubCreatePanel extends Component {
                       {environmentAuthor &&
                         environmentAuthor.name &&
                         (environmentAuthor.url ? (
-                          <a href={environmentAuthor.url} rel="noopener noreferrer" className={styles.author}>
-                            <FormattedMessage id="home.environment_author_by" />
-                            <span>{environmentAuthor.name}</span>
-                          </a>
+                          <WithHoverSound>
+                            <a href={environmentAuthor.url} rel="noopener noreferrer" className={styles.author}>
+                              <FormattedMessage id="home.environment_author_by" />
+                              <span>{environmentAuthor.name}</span>
+                            </a>
+                          </WithHoverSound>
                         ) : (
                           <span className={styles.author}>
                             <FormattedMessage id="home.environment_author_by" />
@@ -199,9 +202,15 @@ class HubCreatePanel extends Component {
                       {environmentAuthor &&
                         environmentAuthor.organization &&
                         (environmentAuthor.organization.url ? (
-                          <a href={environmentAuthor.organization.url} rel="noopener noreferrer" className={styles.org}>
-                            <span>{environmentAuthor.organization.name}</span>
-                          </a>
+                          <WithHoverSound>
+                            <a
+                              href={environmentAuthor.organization.url}
+                              rel="noopener noreferrer"
+                              className={styles.org}
+                            >
+                              <span>{environmentAuthor.organization.name}</span>
+                            </a>
+                          </WithHoverSound>
                         ) : (
                           <span className={styles.org}>
                             <span>{environmentAuthor.organization.name}</span>
@@ -209,26 +218,38 @@ class HubCreatePanel extends Component {
                         ))}
                     </div>
                     <div className={styles.footer}>
-                      <button onClick={this.showCustomSceneDialog} className={styles.customButton}>
-                        <FormattedMessage id="home.room_create_options" />
-                      </button>
+                      <WithHoverSound>
+                        <button onClick={this.showCustomSceneDialog} className={styles.customButton}>
+                          <FormattedMessage id="home.room_create_options" />
+                        </button>
+                      </WithHoverSound>
                     </div>
                   </div>
                   <div className={styles.controls}>
-                    <button className={styles.prev} type="button" tabIndex="1" onClick={this.setToPreviousEnvironment}>
-                      <FontAwesomeIcon icon={faAngleLeft} />
-                    </button>
-
-                    <button className={styles.next} type="button" tabIndex="2" onClick={this.setToNextEnvironment}>
-                      <FontAwesomeIcon icon={faAngleRight} />
-                    </button>
+                    <WithHoverSound>
+                      <button
+                        className={styles.prev}
+                        type="button"
+                        tabIndex="1"
+                        onClick={this.setToPreviousEnvironment}
+                      >
+                        <FontAwesomeIcon icon={faAngleLeft} />
+                      </button>
+                    </WithHoverSound>
+                    <WithHoverSound>
+                      <button className={styles.next} type="button" tabIndex="2" onClick={this.setToNextEnvironment}>
+                        <FontAwesomeIcon icon={faAngleRight} />
+                      </button>
+                    </WithHoverSound>
                   </div>
                 </div>
               </div>
               <div className={styles.container}>
-                <button type="submit" tabIndex="5" className={styles.submitButton}>
-                  <FormattedMessage id="home.room_create_button" />
-                </button>
+                <WithHoverSound>
+                  <button type="submit" tabIndex="5" className={styles.submitButton}>
+                    <FormattedMessage id="home.room_create_button" />
+                  </button>
+                </WithHoverSound>
               </div>
             </div>
           </div>
diff --git a/src/react-components/invite-dialog.js b/src/react-components/invite-dialog.js
index 62fe7975b1b23fdfa8219407d2ba69652bfb84c7..35992a998116acf154ba7be713deff4ede3d4289 100644
--- a/src/react-components/invite-dialog.js
+++ b/src/react-components/invite-dialog.js
@@ -5,6 +5,7 @@ import classNames from "classnames";
 import { FormattedMessage } from "react-intl";
 import { share } from "../utils/share";
 
+import { WithHoverSound } from "./wrap-with-audio";
 import styles from "../assets/stylesheets/invite-dialog.scss";
 
 function pad(num, size) {
@@ -50,14 +51,18 @@ export default class InviteDialog extends Component {
     return (
       <div className={styles.dialog}>
         <div className={styles.attachPoint} />
-        <div className={styles.close} onClick={() => this.props.onClose()}>
-          <span>×</span>
-        </div>
+        <WithHoverSound>
+          <div className={styles.close} onClick={() => this.props.onClose()}>
+            <span>×</span>
+          </div>
+        </WithHoverSound>
         <div>
           <FormattedMessage id="invite.enter_via" />
-          <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer">
-            hub.link
-          </a>
+          <WithHoverSound>
+            <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer">
+              hub.link
+            </a>
+          </WithHoverSound>
           <FormattedMessage id="invite.and_enter_code" />
         </div>
         <div className={styles.code}>
@@ -74,20 +79,26 @@ export default class InviteDialog extends Component {
           <input type="text" readOnly onFocus={e => e.target.select()} value={shortLinkText} />
         </div>
         <div className={styles.buttons}>
-          <button className={styles.linkButton} onClick={this.copyClicked.bind(this, shortLink)}>
-            <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span>
-          </button>
+          <WithHoverSound>
+            <button className={styles.linkButton} onClick={this.copyClicked.bind(this, shortLink)}>
+              <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span>
+            </button>
+          </WithHoverSound>
           {this.props.allowShare &&
             navigator.share && (
-              <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}>
-                <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span>
-              </button>
+              <WithHoverSound>
+                <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}>
+                  <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span>
+                </button>
+              </WithHoverSound>
             )}
           {this.props.allowShare &&
             !navigator.share && (
-              <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}>
-                <FormattedMessage id="invite.tweet" />
-              </button>
+              <WithHoverSound>
+                <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}>
+                  <FormattedMessage id="invite.tweet" />
+                </button>
+              </WithHoverSound>
             )}
         </div>
       </div>
diff --git a/src/react-components/invite-team-dialog.js b/src/react-components/invite-team-dialog.js
index a2b3bcd50c40331487642e0d103b402d464bd5f9..6c0b685ddfad382bc2dfc022dc152a3c1c4898a6 100644
--- a/src/react-components/invite-team-dialog.js
+++ b/src/react-components/invite-team-dialog.js
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import DialogContainer from "./dialog-container.js";
 import PropTypes from "prop-types";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class InviteTeamDialog extends Component {
   static propTypes = {
@@ -30,9 +31,11 @@ export default class InviteTeamDialog extends Component {
           </div>
           <div className="invite-team-form">
             <div className="invite-team-form__buttons">
-              <button className="invite-team-form__action-button" onClick={this.inviteClicked}>
-                <span>{this.state.inviteButtonText}</span>
-              </button>
+              <WithHoverSound>
+                <button className="invite-team-form__action-button" onClick={this.inviteClicked}>
+                  <span>{this.state.inviteButtonText}</span>
+                </button>
+              </WithHoverSound>
             </div>
           </div>
         </div>
diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js
index d3f45051d33736e65ecde5805eee3bb465959e07..e8482958e20120f6beb6a0cbfd34f7f21c398af8 100644
--- a/src/react-components/link-dialog.js
+++ b/src/react-components/link-dialog.js
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
 import classNames from "classnames";
 import { FormattedMessage } from "react-intl";
 import LinkDialogHeader from "../assets/images/link_dialog_header.svg";
+import { WithHoverSound } from "./wrap-with-audio";
 
 import styles from "../assets/stylesheets/link-dialog.scss";
 
@@ -17,9 +18,11 @@ export default class LinkDialog extends Component {
 
     return (
       <div className={styles.dialog}>
-        <div className={styles.close} onClick={() => this.props.onClose()}>
-          <span>×</span>
-        </div>
+        <WithHoverSound>
+          <div className={styles.close} onClick={() => this.props.onClose()}>
+            <span>×</span>
+          </div>
+        </WithHoverSound>
         <div>
           {!linkCode && (
             <div>
@@ -41,9 +44,11 @@ export default class LinkDialog extends Component {
               <div>
                 <FormattedMessage id="link.in_your_browser" />
               </div>
-              <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer">
-                hub.link
-              </a>
+              <WithHoverSound>
+                <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer">
+                  hub.link
+                </a>
+              </WithHoverSound>
               <div>
                 <FormattedMessage id="link.enter_code" />
               </div>
@@ -59,9 +64,11 @@ export default class LinkDialog extends Component {
               <div className={styles.keepOpen}>
                 <FormattedMessage id="link.do_not_close" />
               </div>
-              <button className={styles.closeButton} onClick={() => this.props.onClose()}>
-                <FormattedMessage id="link.cancel" />
-              </button>
+              <WithHoverSound>
+                <button className={styles.closeButton} onClick={() => this.props.onClose()}>
+                  <FormattedMessage id="link.cancel" />
+                </button>
+              </WithHoverSound>
             </div>
           )}
         </div>
diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js
index e06442749445204c858846428f548e526a38c3d8..130625b8ddb867978a9db24297d05f79305da646 100644
--- a/src/react-components/link-root.js
+++ b/src/react-components/link-root.js
@@ -8,6 +8,7 @@ import classNames from "classnames";
 import styles from "../assets/stylesheets/link.scss";
 import { disableiOSZoom } from "../utils/disable-ios-zoom";
 import HeadsetIcon from "../assets/images/generic_vr_entry.svg";
+import { WithHoverSound } from "./wrap-with-audio";
 
 const MAX_DIGITS = 6;
 const MAX_LETTERS = 4;
@@ -186,9 +187,11 @@ class LinkRoot extends Component {
                 {!this.state.isAlphaMode &&
                   this.props.showHeadsetLinkOption && (
                     <span>
-                      <a href="#" onClick={() => this.toggleMode()}>
-                        <FormattedMessage id="link.linking_a_headset" />
-                      </a>
+                      <WithHoverSound>
+                        <a href="#" onClick={() => this.toggleMode()}>
+                          <FormattedMessage id="link.linking_a_headset" />
+                        </a>
+                      </WithHoverSound>
                     </span>
                   )}
               </div>
@@ -199,53 +202,61 @@ class LinkRoot extends Component {
                 ? ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
                 : [1, 2, 3, 4, 5, 6, 7, 8, 9]
               ).map((d, i) => (
-                <button
-                  disabled={this.state.entered.length === this.maxAllowedChars()}
-                  key={`char_${i}`}
-                  className={styles.keypadButton}
-                  onClick={() => {
-                    if (!hasTouchEvents) this.addToEntry(d);
-                  }}
-                  onTouchStart={() => this.addToEntry(d)}
-                >
-                  {d}
-                </button>
+                <WithHoverSound key={`char_${i}`}>
+                  <button
+                    disabled={this.state.entered.length === this.maxAllowedChars()}
+                    className={styles.keypadButton}
+                    key={`char_${i}`}
+                    onClick={() => {
+                      if (!hasTouchEvents) this.addToEntry(d);
+                    }}
+                    onTouchStart={() => this.addToEntry(d)}
+                  >
+                    {d}
+                  </button>
+                </WithHoverSound>
               ))}
               {this.props.showHeadsetLinkOption ? (
-                <button
-                  className={classNames(styles.keypadButton, styles.keypadToggleMode)}
-                  onTouchStart={() => this.toggleMode()}
-                  onClick={() => {
-                    if (!hasTouchEvents) this.toggleMode();
-                  }}
-                >
-                  {this.state.isAlphaMode ? "123" : "ABC"}
-                </button>
+                <WithHoverSound>
+                  <button
+                    className={classNames(styles.keypadButton, styles.keypadToggleMode)}
+                    onTouchStart={() => this.toggleMode()}
+                    onClick={() => {
+                      if (!hasTouchEvents) this.toggleMode();
+                    }}
+                  >
+                    {this.state.isAlphaMode ? "123" : "ABC"}
+                  </button>
+                </WithHoverSound>
               ) : (
                 <div />
               )}
               {!this.state.isAlphaMode && (
+                <WithHoverSound>
+                  <button
+                    disabled={this.state.entered.length === this.maxAllowedChars()}
+                    className={classNames(styles.keypadButton, styles.keypadZeroButton)}
+                    onTouchStart={() => this.addToEntry(0)}
+                    onClick={() => {
+                      if (!hasTouchEvents) this.addToEntry(0);
+                    }}
+                  >
+                    0
+                  </button>
+                </WithHoverSound>
+              )}
+              <WithHoverSound>
                 <button
-                  disabled={this.state.entered.length === this.maxAllowedChars()}
-                  className={classNames(styles.keypadButton, styles.keypadZeroButton)}
-                  onTouchStart={() => this.addToEntry(0)}
+                  disabled={this.state.entered.length === 0 || this.state.entered.length === this.maxAllowedChars()}
+                  className={classNames(styles.keypadButton, styles.keypadBackspace)}
+                  onTouchStart={() => this.removeChar()}
                   onClick={() => {
-                    if (!hasTouchEvents) this.addToEntry(0);
+                    if (!hasTouchEvents) this.removeChar();
                   }}
                 >
-                  0
+                  ⌫
                 </button>
-              )}
-              <button
-                disabled={this.state.entered.length === 0 || this.state.entered.length === this.maxAllowedChars()}
-                className={classNames(styles.keypadButton, styles.keypadBackspace)}
-                onTouchStart={() => this.removeChar()}
-                onClick={() => {
-                  if (!hasTouchEvents) this.removeChar();
-                }}
-              >
-                ⌫
-              </button>
+              </WithHoverSound>
             </div>
 
             <div className={styles.footer}>
@@ -255,11 +266,15 @@ class LinkRoot extends Component {
                     className={styles.linkHeadsetFooterLink}
                     style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }}
                   >
-                    <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
+                    <WithHoverSound>
+                      <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
+                    </WithHoverSound>
                     <span>
-                      <a href="#" onClick={() => this.toggleMode()}>
-                        <FormattedMessage id="link.linking_a_headset" />
-                      </a>
+                      <WithHoverSound>
+                        <a href="#" onClick={() => this.toggleMode()}>
+                          <FormattedMessage id="link.linking_a_headset" />
+                        </a>
+                      </WithHoverSound>
                     </span>
                   </div>
                 )}
diff --git a/src/react-components/presence-list.js b/src/react-components/presence-list.js
index 9770da2fdbd950b442314d6fbb39f181bdb75ed2..40d18b9d34b63e0a1f4871cf75b9c6a63923ce55 100644
--- a/src/react-components/presence-list.js
+++ b/src/react-components/presence-list.js
@@ -6,6 +6,7 @@ import PhoneImage from "../assets/images/presence_phone.png";
 import DesktopImage from "../assets/images/presence_desktop.png";
 import HMDImage from "../assets/images/presence_vr.png";
 import { FormattedMessage } from "react-intl";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class PresenceList extends Component {
   static propTypes = {
@@ -21,22 +22,24 @@ export default class PresenceList extends Component {
     const image = context && context.mobile ? PhoneImage : context && context.hmd ? HMDImage : DesktopImage;
 
     return (
-      <div className={styles.row} key={sessionId}>
-        <div className={styles.device}>
-          <img src={image} />
-        </div>
-        <div
-          className={classNames({
-            [styles.displayName]: true,
-            [styles.selfDisplayName]: sessionId === this.props.sessionId
-          })}
-        >
-          {profile && profile.displayName}
-        </div>
-        <div className={styles.presence}>
-          <FormattedMessage id={`presence.in_${meta.presence}`} />
+      <WithHoverSound>
+        <div className={styles.row} key={sessionId}>
+          <div className={styles.device}>
+            <img src={image} />
+          </div>
+          <div
+            className={classNames({
+              [styles.displayName]: true,
+              [styles.selfDisplayName]: sessionId === this.props.sessionId
+            })}
+          >
+            {profile && profile.displayName}
+          </div>
+          <div className={styles.presence}>
+            <FormattedMessage id={`presence.in_${meta.presence}`} />
+          </div>
         </div>
-      </div>
+      </WithHoverSound>
     );
   };
 
diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js
index 307ae73b0dac97d5b50908d5773f28bc2953aec1..e4d02c70a4bb896816c44d8018aeae2da8ea0139 100644
--- a/src/react-components/profile-entry-panel.js
+++ b/src/react-components/profile-entry-panel.js
@@ -5,6 +5,7 @@ import { SCHEMA } from "../storage/store";
 import styles from "../assets/stylesheets/profile.scss";
 import classNames from "classnames";
 import hubLogo from "../assets/images/hub-preview-white.png";
+import { WithHoverSound } from "./wrap-with-audio";
 
 class ProfileEntryPanel extends Component {
   static propTypes = {
@@ -107,18 +108,29 @@ class ProfileEntryPanel extends Component {
                 ref={ifr => (this.avatarSelector = ifr)}
               />
             </div>
-            <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} />
+            <WithHoverSound>
+              <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} />
+            </WithHoverSound>
             <div className={styles.links}>
-              <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md">
-                <FormattedMessage id="profile.terms_of_use" />
-              </a>
-              <a
-                target="_blank"
-                rel="noopener noreferrer"
-                href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
-              >
-                <FormattedMessage id="profile.privacy_notice" />
-              </a>
+              <WithHoverSound>
+                <a
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  href="https://github.com/mozilla/hubs/blob/master/TERMS.md"
+                >
+                  <FormattedMessage id="profile.terms_of_use" />
+                </a>
+              </WithHoverSound>
+
+              <WithHoverSound>
+                <a
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
+                >
+                  <FormattedMessage id="profile.privacy_notice" />
+                </a>
+              </WithHoverSound>
             </div>
           </div>
         </form>
diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b5b54ce5f9324390936934f531966bb60cb6c20
--- /dev/null
+++ b/src/react-components/profile-info-header.js
@@ -0,0 +1,35 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion";
+import { WithHoverSound } from "./wrap-with-audio";
+
+export const ProfileInfoHeader = props => (
+  <div className="profile-info-header">
+    <div className="profile-info-header__menu-buttons">
+      <WithHoverSound>
+        <button className="profile-info-header__menu-buttons__menu-button" onClick={props.onClickHelp}>
+          <i className="profile-info-header__menu-buttons__menu-button__icon">
+            <FontAwesomeIcon icon={faQuestion} />
+          </i>
+        </button>
+      </WithHoverSound>
+    </div>
+    <div className="profile-info-header__profile_display_name">
+      <WithHoverSound>
+        <img src="../assets/images/account.svg" onClick={props.onClickName} className="profile-info-header__icon" />
+      </WithHoverSound>
+      <WithHoverSound>
+        <div onClick={props.onClickName} title={props.name}>
+          {props.name}
+        </div>
+      </WithHoverSound>
+    </div>
+  </div>
+);
+
+ProfileInfoHeader.propTypes = {
+  onClickName: PropTypes.func,
+  onClickHelp: PropTypes.func,
+  name: PropTypes.string
+};
diff --git a/src/react-components/report-dialog.js b/src/react-components/report-dialog.js
index 36072eed617957090fc2942a5f17047b3ea533ba..359f4da55236d7d3f36248df94943195252ccb59 100644
--- a/src/react-components/report-dialog.js
+++ b/src/react-components/report-dialog.js
@@ -1,5 +1,6 @@
 import React, { Component } from "react";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class ReportDialog extends Component {
   render() {
@@ -9,20 +10,29 @@ export default class ReportDialog extends Component {
           <p>Need to report a problem?</p>
           <p>
             You can file a{" "}
-            <a href="https://github.com/mozilla/hubs/issues" target="_blank" rel="noopener noreferrer">
-              GitHub Issue
-            </a>{" "}
-            or e-mail us for support at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>.
+            <WithHoverSound>
+              <a href="https://github.com/mozilla/hubs/issues" target="_blank" rel="noopener noreferrer">
+                GitHub Issue
+              </a>
+            </WithHoverSound>
+            or e-mail us for support at{" "}
+            <WithHoverSound>
+              <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>
+            </WithHoverSound>
           </p>
           <p>
             You can also find us in{" "}
-            <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer">
-              #social
-            </a>{" "}
+            <WithHoverSound>
+              <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer">
+                #social
+              </a>
+            </WithHoverSound>
             on the{" "}
-            <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer">
-              WebVR Slack
-            </a>.
+            <WithHoverSound>
+              <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer">
+                WebVR Slack
+              </a>
+            </WithHoverSound>
           </p>
         </span>
       </DialogContainer>
diff --git a/src/react-components/safari-dialog.js b/src/react-components/safari-dialog.js
index de96bfbf4545e6496e97a3e0dab81593adad7519..608eaa03c5b19cdf26de58440116be6d9707d02e 100644
--- a/src/react-components/safari-dialog.js
+++ b/src/react-components/safari-dialog.js
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import copy from "copy-to-clipboard";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class SafariDialog extends Component {
   state = {
@@ -27,9 +28,11 @@ export default class SafariDialog extends Component {
               className="invite-form__link_field"
             />
             <div className="invite-form__buttons">
-              <button className="invite-form__action-button" onClick={onCopyClicked}>
-                <span>{this.state.copyLinkButtonText}</span>
-              </button>
+              <WithHoverSound>
+                <button className="invite-form__action-button" onClick={onCopyClicked}>
+                  <span>{this.state.copyLinkButtonText}</span>
+                </button>
+              </WithHoverSound>
             </div>
           </div>
         </div>
diff --git a/src/react-components/scene-ui.js b/src/react-components/scene-ui.js
index 0a49bd07bd7b3504957dea05ff318e5a6052863f..43cd7df8a9d00aba70423ebd2b7b74b80051e0dc 100644
--- a/src/react-components/scene-ui.js
+++ b/src/react-components/scene-ui.js
@@ -8,6 +8,7 @@ import hubLogo from "../assets/images/hub-preview-white.png";
 import spokeLogo from "../assets/images/spoke_logo_black.png";
 import { getReticulumFetchUrl } from "../utils/phoenix-utils";
 import { generateHubName } from "../utils/name-generation";
+import { WithHoverSound } from "./wrap-with-audio";
 import CreateRoomDialog from "./create-room-dialog.js";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faEllipsisH } from "@fortawesome/free-solid-svg-icons/faEllipsisH";
@@ -92,13 +93,15 @@ class SceneUI extends Component {
           <span key={a.url}>
             <a href={a.url} target="_blank" rel="noopener noreferrer">
               {a.name} by {a.author} {source}
-            </a>&nbsp;
+            </a>
+            &nbsp;
           </span>
         );
       } else {
         return (
           <span key={`${a.name} ${a.author}`}>
-            {a.name} by {a.author}&nbsp;
+            {a.name} by {a.author}
+            &nbsp;
           </span>
         );
       }
@@ -108,7 +111,8 @@ class SceneUI extends Component {
       if (!this.props.sceneAttributions.extras) {
         attributions = (
           <span>
-            <span>{this.props.sceneAttributions.creator ? `by ${this.props.sceneAttributions.creator}` : ""}</span>&nbsp;
+            <span>{this.props.sceneAttributions.creator ? `by ${this.props.sceneAttributions.creator}` : ""}</span>
+            &nbsp;
             <br />
             {this.props.sceneAttributions.content && this.props.sceneAttributions.content.map(toAttributionSpan)}
           </span>
@@ -133,26 +137,37 @@ class SceneUI extends Component {
           <div className={styles.whiteOverlay} />
           <div className={styles.grid}>
             <div className={styles.mainPanel}>
-              <a href="/" className={styles.logo}>
-                <img src={hubLogo} />
-              </a>
+              <WithHoverSound>
+                <a href="/" className={styles.logo}>
+                  <img src={hubLogo} />
+                </a>
+              </WithHoverSound>
               <div className={styles.logoTagline}>
                 <FormattedMessage id="scene.logo_tagline" />
               </div>
               <div className={styles.createButtons}>
-                <button className={styles.createButton} onClick={this.createRoom}>
-                  <FormattedMessage id="scene.create_button" />
-                </button>
-                <button className={styles.optionsButton} onClick={() => this.setState({ showCustomRoomDialog: true })}>
-                  <FontAwesomeIcon icon={faEllipsisH} />
-                </button>
+                <WithHoverSound>
+                  <button className={styles.createButton} onClick={this.createRoom}>
+                    <FormattedMessage id="scene.create_button" />
+                  </button>
+                </WithHoverSound>
+                <WithHoverSound>
+                  <button
+                    className={styles.optionsButton}
+                    onClick={() => this.setState({ showCustomRoomDialog: true })}
+                  >
+                    <FontAwesomeIcon icon={faEllipsisH} />
+                  </button>
+                </WithHoverSound>
               </div>
-              <a href={tweetLink} rel="noopener noreferrer" target="_blank" className={styles.tweetButton}>
-                <img src="../assets/images/twitter.svg" />
-                <div>
-                  <FormattedMessage id="scene.tweet_button" />
-                </div>
-              </a>
+              <WithHoverSound>
+                <a href={tweetLink} rel="noopener noreferrer" target="_blank" className={styles.tweetButton}>
+                  <img src="../assets/images/twitter.svg" />
+                  <div>
+                    <FormattedMessage id="scene.tweet_button" />
+                  </div>
+                </a>
+              </WithHoverSound>
             </div>
           </div>
           <div className={styles.info}>
diff --git a/src/react-components/slack-dialog.js b/src/react-components/slack-dialog.js
new file mode 100644
index 0000000000000000000000000000000000000000..d53329b3d7f2ef4ce448f5035898c49107eee9ec
--- /dev/null
+++ b/src/react-components/slack-dialog.js
@@ -0,0 +1,41 @@
+import React, { Component } from "react";
+import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
+
+export default class SlackDialog extends Component {
+  render() {
+    return (
+      <DialogContainer title="Get in Touch" {...this.props}>
+        <span>
+          <p>Want to join the conversation?</p>
+          <p>
+            Join us on the{" "}
+            <WithHoverSound>
+              <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer">
+                WebVR Slack
+              </a>
+            </WithHoverSound>{" "}
+            in the{" "}
+            <WithHoverSound>
+              <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer">
+                #social
+              </a>
+            </WithHoverSound>{" "}
+            channel.
+            <br />
+            VR meetups every Friday at noon PDT!
+          </p>
+          <p>
+            Or, tweet at{" "}
+            <WithHoverSound>
+              <a href="https://twitter.com/mozillareality" target="_blank" rel="noopener noreferrer">
+                @mozillareality
+              </a>
+            </WithHoverSound>{" "}
+            on Twitter.
+          </p>
+        </span>
+      </DialogContainer>
+    );
+  }
+}
diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js
index 012afee70e1e2aa4a590a49f800512a5944a7cc4..77b0dfba4310c7986f9e4775cc38aa35c2449e8b 100644
--- a/src/react-components/ui-root.js
+++ b/src/react-components/ui-root.js
@@ -9,6 +9,7 @@ import MovingAverage from "moving-average";
 import screenfull from "screenfull";
 import styles from "../assets/stylesheets/ui-root.scss";
 import entryStyles from "../assets/stylesheets/entry.scss";
+import { ReactAudioContext, WithHoverSound } from "./wrap-with-audio";
 
 import { lang, messages } from "../utils/i18n";
 import AutoExitWarning from "./auto-exit-warning";
@@ -143,6 +144,17 @@ class UIRoot extends Component {
     this.props.scene.addEventListener("stateadded", this.onAframeStateChanged);
     this.props.scene.addEventListener("stateremoved", this.onAframeStateChanged);
     this.props.scene.addEventListener("exit", this.exit);
+    const scene = this.props.scene;
+    this.setState({
+      audioContext: {
+        playSound: sound => {
+          scene.emit(sound);
+        },
+        onMouseLeave: () => {
+          //          scene.emit("play_sound-hud_mouse_leave");
+        }
+      }
+    });
   }
 
   componentWillUnmount() {
@@ -555,25 +567,35 @@ class UIRoot extends Component {
     this.setState({ dialog: null });
   };
 
-  showHelpDialog = () => {
-    this.setState({ dialog: <HelpDialog onClose={this.closeDialog} /> });
-  };
+  showHelpDialog() {
+    this.setState({
+      dialog: <HelpDialog onClose={this.closeDialog} />
+    });
+  }
 
-  showSafariDialog = () => {
-    this.setState({ dialog: <SafariDialog onClose={this.closeDialog} /> });
-  };
+  showSafariDialog() {
+    this.setState({
+      dialog: <SafariDialog onClose={this.closeDialog} />
+    });
+  }
 
-  showInviteTeamDialog = () => {
-    this.setState({ dialog: <InviteTeamDialog hubChannel={this.props.hubChannel} onClose={this.closeDialog} /> });
-  };
+  showInviteTeamDialog() {
+    this.setState({
+      dialog: <InviteTeamDialog hubChannel={this.props.hubChannel} onClose={this.closeDialog} />
+    });
+  }
 
-  showCreateObjectDialog = () => {
-    this.setState({ dialog: <CreateObjectDialog onCreate={this.createObject} onClose={this.closeDialog} /> });
-  };
+  showCreateObjectDialog() {
+    this.setState({
+      dialog: <CreateObjectDialog onCreate={this.createObject} onClose={this.closeDialog} />
+    });
+  }
 
-  showWebVRRecommendDialog = () => {
-    this.setState({ dialog: <WebVRRecommendDialog onClose={this.closeDialog} /> });
-  };
+  showWebVRRecommendDialog() {
+    this.setState({
+      dialog: <WebVRRecommendDialog onClose={this.closeDialog} />
+    });
+  }
 
   onMiniInviteClicked = () => {
     const link = "https://hub.link/" + this.props.hubId;
@@ -607,16 +629,23 @@ class UIRoot extends Component {
       subtitle = (
         <div>
           Sorry, this room is no longer available.
-          <p />
-          A room may be closed if we receive reports that it violates our{" "}
-          <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md">
-            Terms of Use
-          </a>.
-          <br />
-          If you have questions, contact us at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>.
-          <p />
+          <p />A room may be closed if we receive reports that it violates our{" "}
+          <WithHoverSound>
+            <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md">
+              Terms of Use
+            </a>
+          </WithHoverSound>
+          .<br />
+          If you have questions, contact us at{" "}
+          <WithHoverSound>
+            <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>
+          </WithHoverSound>
+          .<p />
           If you&apos;d like to run your own server, hubs&apos;s source code is available on{" "}
-          <a href="https://github.com/mozilla/hubs">GitHub</a>.
+          <WithHoverSound>
+            <a href="https://github.com/mozilla/hubs">GitHub</a>
+          </WithHoverSound>
+          .
         </div>
       );
     } else if (this.props.platformUnsupportedReason === "no_data_channels") {
@@ -624,15 +653,23 @@ class UIRoot extends Component {
       subtitle = (
         <div>
           Your browser does not support{" "}
-          <a
-            href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility"
-            rel="noreferrer noopener"
-          >
-            WebRTC Data Channels
-          </a>, which is required to use Hubs.<br />If you&quot;d like to use Hubs with Oculus or SteamVR, you can{" "}
-          <a href="https://www.mozilla.org/firefox" rel="noreferrer noopener">
-            Download Firefox
-          </a>.
+          <WithHoverSound>
+            <a
+              href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility"
+              rel="noreferrer noopener"
+            >
+              WebRTC Data Channels
+            </a>
+          </WithHoverSound>
+          , which is required to use Hubs.
+          <br />
+          If you&quot;d like to use Hubs with Oculus or SteamVR, you can{" "}
+          <WithHoverSound>
+            <a href="https://www.mozilla.org/firefox" rel="noreferrer noopener">
+              Download Firefox
+            </a>
+          </WithHoverSound>
+          .
         </div>
       );
     } else {
@@ -644,7 +681,10 @@ class UIRoot extends Component {
           <p />
           {this.props.roomUnavailableReason && (
             <div>
-              You can also <a href="/">create a new room</a>.
+              You can also{" "}
+              <WithHoverSound>
+                <a href="/">create a new room</a>.
+              </WithHoverSound>
             </div>
           )}
         </div>
@@ -698,10 +738,12 @@ class UIRoot extends Component {
         <div className={entryStyles.name}>{this.props.hubName}</div>
 
         <div className={entryStyles.center}>
-          <div onClick={() => this.setState({ showProfileEntry: true })} className={entryStyles.profileName}>
-            <img src="../assets/images/account.svg" className={entryStyles.profileIcon} />
-            <div title={this.props.store.state.profile.displayName}>{this.props.store.state.profile.displayName}</div>
-          </div>
+          <WithHoverSound>
+            <div onClick={() => this.setState({ showProfileEntry: true })} className={entryStyles.profileName}>
+              <img src="../assets/images/account.svg" className={entryStyles.profileIcon} />
+              <div title={this.props.store.state.profile.displayName}>{this.props.store.state.profile.displayName}</div>
+            </div>
+          </WithHoverSound>
 
           <form onSubmit={this.sendMessage}>
             <div className={styles.messageEntry} style={{ height: pendingMessageFieldHeight }}>
@@ -721,7 +763,9 @@ class UIRoot extends Component {
                 }}
                 placeholder="Send a message..."
               />
-              <input className={styles.messageEntrySubmit} type="submit" value="send" />
+              <WithHoverSound>
+                <input className={styles.messageEntrySubmit} type="submit" value="send" />
+              </WithHoverSound>
             </div>
           </form>
         </div>
@@ -741,12 +785,14 @@ class UIRoot extends Component {
         )}
 
         <div className={entryStyles.buttonContainer}>
-          <button
-            className={classNames([entryStyles.actionButton, entryStyles.wideButton])}
-            onClick={() => this.handleStartEntry()}
-          >
-            <FormattedMessage id="entry.enter-room" />
-          </button>
+          <WithHoverSound>
+            <button
+              className={classNames([entryStyles.actionButton, entryStyles.wideButton])}
+              onClick={() => this.handleStartEntry()}
+            >
+              <FormattedMessage id="entry.enter-room" />
+            </button>
+          </WithHoverSound>
         </div>
       </div>
     );
@@ -821,20 +867,26 @@ class UIRoot extends Component {
           </div>
           <div className="mic-grant-panel__button-container">
             {this.state.entryStep == ENTRY_STEPS.mic_grant ? (
-              <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}>
-                <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" />
-              </button>
+              <WithHoverSound>
+                <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}>
+                  <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" />
+                </button>
+              </WithHoverSound>
             ) : (
-              <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}>
-                <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" />
-              </button>
+              <WithHoverSound>
+                <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}>
+                  <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" />
+                </button>
+              </WithHoverSound>
             )}
           </div>
-        </div>
-        <div className="mic-grant-panel__next-container">
-          <button className={classNames("mic-grant-panel__next")} onClick={this.onMicGrantButton}>
-            <FormattedMessage id="audio.granted-next" />
-          </button>
+          <div className="mic-grant-panel__next-container">
+            <WithHoverSound>
+              <button className={classNames("mic-grant-panel__next")} onClick={this.onMicGrantButton}>
+                <FormattedMessage id="audio.granted-next" />
+              </button>
+            </WithHoverSound>
+          </div>
         </div>
       </div>
     );
@@ -883,38 +935,43 @@ class UIRoot extends Component {
                 />
               )}
             </div>
-            <div className="audio-setup-panel__levels__icon" onClick={this.playTestTone}>
-              <img
-                src="../assets/images/level_background.png"
-                srcSet="../assets/images/level_background@2x.png 2x"
-                className="audio-setup-panel__levels__icon-part"
-              />
-              <img
-                src="../assets/images/level_fill.png"
-                srcSet="../assets/images/level_fill@2x.png 2x"
-                className="audio-setup-panel__levels__icon-part"
-                style={speakerClip}
-              />
-              <img
-                src="../assets/images/speaker_level.png"
-                srcSet="../assets/images/speaker_level@2x.png 2x"
-                className="audio-setup-panel__levels__icon-part"
-              />
-            </div>
+            <WithHoverSound>
+              <div className="audio-setup-panel__levels__icon" onClick={this.playTestTone}>
+                <img
+                  src="../assets/images/level_background.png"
+                  srcSet="../assets/images/level_background@2x.png 2x"
+                  className="audio-setup-panel__levels__icon-part"
+                />
+                <img
+                  src="../assets/images/level_fill.png"
+                  srcSet="../assets/images/level_fill@2x.png 2x"
+                  className="audio-setup-panel__levels__icon-part"
+                  style={speakerClip}
+                />
+                <img
+                  src="../assets/images/speaker_level.png"
+                  srcSet="../assets/images/speaker_level@2x.png 2x"
+                  className="audio-setup-panel__levels__icon-part"
+                />
+              </div>
+            </WithHoverSound>
           </div>
           {this.state.audioTrack && (
             <div className="audio-setup-panel__device-chooser">
-              <select
-                className="audio-setup-panel__device-chooser__dropdown"
-                value={this.selectedMicDeviceId()}
-                onChange={this.micDeviceChanged}
-              >
-                {this.state.micDevices.map(d => (
-                  <option key={d.deviceId} value={d.deviceId}>
-                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{d.label}
-                  </option>
-                ))}
-              </select>
+              <WithHoverSound>
+                <select
+                  className="audio-setup-panel__device-chooser__dropdown"
+                  value={this.selectedMicDeviceId()}
+                  onChange={this.micDeviceChanged}
+                >
+                  {this.state.micDevices.map(d => (
+                    <option key={d.deviceId} value={d.deviceId}>
+                      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                      {d.label}
+                    </option>
+                  ))}
+                </select>
+              </WithHoverSound>
               <img
                 className="audio-setup-panel__device-chooser__mic-icon"
                 src="../assets/images/mic_small.png"
@@ -941,9 +998,11 @@ class UIRoot extends Component {
           )}
         </div>
         <div className="audio-setup-panel__enter-button-container">
-          <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}>
-            <FormattedMessage id="audio.enter-now" />
-          </button>
+          <WithHoverSound>
+            <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}>
+              <FormattedMessage id="audio.enter-now" />
+            </button>
+          </WithHoverSound>
         </div>
       </div>
     );
@@ -973,11 +1032,13 @@ class UIRoot extends Component {
       dialogContents = (
         <div className={entryStyles.entryDialog}>
           <div>&nbsp;</div>
-          <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}>
-            <i>
-              <FontAwesomeIcon icon={faChevronUp} />
-            </i>
-          </button>
+          <WithHoverSound>
+            <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}>
+              <i>
+                <FontAwesomeIcon icon={faChevronUp} />
+              </i>
+            </button>
+          </WithHoverSound>
         </div>
       );
     } else {
@@ -989,11 +1050,13 @@ class UIRoot extends Component {
       ) : (
         <div className={entryStyles.entryDialog}>
           {!this.state.entryPanelCollapsed && (
-            <button onClick={() => this.setState({ entryPanelCollapsed: true })} className={entryStyles.collapse}>
-              <i>
-                <FontAwesomeIcon icon={faChevronDown} />
-              </i>
-            </button>
+            <WithHoverSound>
+              <button onClick={() => this.setState({ entryPanelCollapsed: true })} className={entryStyles.collapse}>
+                <i>
+                  <FontAwesomeIcon icon={faChevronDown} />
+                </i>
+              </button>
+            </WithHoverSound>
           )}
           {startPanel}
           {devicePanel}
@@ -1015,179 +1078,192 @@ class UIRoot extends Component {
     const textRows = this.state.pendingMessage.split("\n").length;
     const pendingMessageTextareaHeight = textRows * 28 + "px";
     const pendingMessageFieldHeight = textRows * 28 + 20 + "px";
-
     return (
-      <IntlProvider locale={lang} messages={messages}>
-        <div className={styles.ui}>
-          {this.state.dialog}
-
-          {this.state.showProfileEntry && (
-            <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} />
-          )}
+      <ReactAudioContext.Provider value={this.state.audioContext}>
+        <IntlProvider locale={lang} messages={messages}>
+          <div className={styles.ui}>
+            {this.state.dialog}
 
-          {(!entryFinished || this.isWaitingForAutoExit()) && (
-            <div className={styles.uiDialog}>
-              <PresenceLog entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} />
-              <div className={dialogBoxContentsClassNames}>{dialogContents}</div>
-            </div>
-          )}
+            {this.state.showProfileEntry && (
+              <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} />
+            )}
 
-          {entryFinished && (
-            <PresenceLog inRoom={true} entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} />
-          )}
-          {entryFinished && (
-            <form onSubmit={this.sendMessage}>
-              <div className={styles.messageEntryInRoom} style={{ height: pendingMessageFieldHeight }}>
-                <textarea
-                  style={{ height: pendingMessageTextareaHeight }}
-                  className={classNames([
-                    styles.messageEntryInput,
-                    styles.messageEntryInputInRoom,
-                    "chat-focus-target"
-                  ])}
-                  value={this.state.pendingMessage}
-                  rows={textRows}
-                  onFocus={e => e.target.select()}
-                  onChange={e => {
-                    e.stopPropagation();
-                    this.setState({ pendingMessage: e.target.value });
-                  }}
-                  onKeyDown={e => {
-                    if (e.keyCode === 13 && !e.shiftKey) {
-                      this.sendMessage(e);
-                    } else if (e.keyCode === 13 && e.shiftKey && e.ctrlKey) {
-                      spawnChatMessage(e.target.value);
-                      this.setState({ pendingMessage: "" });
-                    } else if (e.keyCode === 27) {
-                      e.target.blur();
-                    }
-                  }}
-                  placeholder="Send a message..."
-                />
-                <input
-                  className={classNames([styles.messageEntrySubmit, styles.messageEntrySubmitInRoom])}
-                  type="submit"
-                  value="send"
-                />
-                <button
-                  className={classNames([styles.messageEntrySpawn])}
-                  onClick={() => {
-                    if (this.state.pendingMessage.length > 0) {
-                      spawnChatMessage(this.state.pendingMessage);
-                      this.setState({ pendingMessage: "" });
-                    } else {
-                      this.showCreateObjectDialog();
-                    }
-                  }}
-                />
+            {(!entryFinished || this.isWaitingForAutoExit()) && (
+              <div className={styles.uiDialog}>
+                <PresenceLog entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} />
+                <div className={dialogBoxContentsClassNames}>{dialogContents}</div>
               </div>
-            </form>
-          )}
-
-          <div
-            className={classNames({
-              [styles.inviteContainer]: true,
-              [styles.inviteContainerBelowHud]: entryFinished,
-              [styles.inviteContainerInverted]: this.state.showInviteDialog
-            })}
-          >
-            {!showVREntryButton && (
-              <button
-                className={classNames({ [styles.hideSmallScreens]: this.occupantCount() > 1 && entryFinished })}
-                onClick={() => this.toggleInviteDialog()}
-              >
-                <FormattedMessage id="entry.invite-others-nag" />
-              </button>
             )}
-            {!showVREntryButton &&
-              this.occupantCount() > 1 &&
-              entryFinished && (
-                <button onClick={this.onMiniInviteClicked} className={styles.inviteMiniButton}>
-                  <span>
-                    {this.state.miniInviteActivated
-                      ? navigator.share
-                        ? "sharing..."
-                        : "copied!"
-                      : "hub.link/" + this.props.hubId}
-                  </span>
-                </button>
-              )}
-            {showVREntryButton && (
-              <button onClick={() => this.props.scene.enterVR()}>
-                <FormattedMessage id="entry.return-to-vr" />
-              </button>
+
+            {entryFinished && (
+              <PresenceLog inRoom={true} entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} />
             )}
-            {this.state.showInviteDialog && (
-              <InviteDialog
-                allowShare={!this.props.availableVREntryTypes.isInHMD}
-                entryCode={this.props.hubEntryCode}
-                hubId={this.props.hubId}
-                onClose={() => this.setState({ showInviteDialog: false })}
-              />
+            {entryFinished && (
+              <form onSubmit={this.sendMessage}>
+                <div className={styles.messageEntryInRoom} style={{ height: pendingMessageFieldHeight }}>
+                  <textarea
+                    style={{ height: pendingMessageTextareaHeight }}
+                    className={classNames([
+                      styles.messageEntryInput,
+                      styles.messageEntryInputInRoom,
+                      "chat-focus-target"
+                    ])}
+                    value={this.state.pendingMessage}
+                    rows={textRows}
+                    onFocus={e => e.target.select()}
+                    onChange={e => {
+                      e.stopPropagation();
+                      this.setState({ pendingMessage: e.target.value });
+                    }}
+                    onKeyDown={e => {
+                      if (e.keyCode === 13 && !e.shiftKey) {
+                        this.sendMessage(e);
+                      } else if (e.keyCode === 13 && e.shiftKey && e.ctrlKey) {
+                        spawnChatMessage(e.target.value);
+                        this.setState({ pendingMessage: "" });
+                      } else if (e.keyCode === 27) {
+                        e.target.blur();
+                      }
+                    }}
+                    placeholder="Send a message..."
+                  />
+                  <input
+                    className={classNames([styles.messageEntrySubmit, styles.messageEntrySubmitInRoom])}
+                    type="submit"
+                    value="send"
+                  />
+                  <WithHoverSound>
+                    <button
+                      className={classNames([styles.messageEntrySpawn])}
+                      onClick={() => {
+                        if (this.state.pendingMessage.length > 0) {
+                          spawnChatMessage(this.state.pendingMessage);
+                          this.setState({ pendingMessage: "" });
+                        } else {
+                          this.showCreateObjectDialog();
+                        }
+                      }}
+                    />
+                  </WithHoverSound>
+                </div>
+              </form>
             )}
-          </div>
-
-          {this.state.showLinkDialog && (
-            <LinkDialog
-              linkCode={this.state.linkCode}
-              onClose={() => {
-                this.state.linkCodeCancel();
-                this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null });
-              }}
-            />
-          )}
-
-          <button onClick={() => this.showHelpDialog()} className={styles.helpIcon}>
-            <i>
-              <FontAwesomeIcon icon={faQuestion} />
-            </i>
-          </button>
-
-          <div
-            onClick={() => this.setState({ showPresenceList: !this.state.showPresenceList })}
-            className={classNames({
-              [styles.presenceInfo]: true,
-              [styles.presenceInfoSelected]: this.state.showPresenceList
-            })}
-          >
-            <FontAwesomeIcon icon={faUsers} />
-            <span className={styles.occupantCount}>{this.occupantCount()}</span>
-          </div>
 
-          {this.state.showPresenceList && (
-            <PresenceList presences={this.props.presences} sessionId={this.props.sessionId} />
-          )}
-
-          {this.state.entryStep === ENTRY_STEPS.finished ? (
-            <div>
-              <TwoDHUD.TopHUD
-                muted={this.state.muted}
-                frozen={this.state.frozen}
-                spacebubble={this.state.spacebubble}
-                onToggleMute={this.toggleMute}
-                onToggleFreeze={this.toggleFreeze}
-                onToggleSpaceBubble={this.toggleSpaceBubble}
-                onSpawnPen={this.spawnPen}
-                onSpawnCamera={() => this.props.scene.emit("action_spawn_camera")}
-              />
-              {this.props.isSupportAvailable && (
-                <div className={styles.nagCornerButton}>
-                  <button onClick={() => this.showInviteTeamDialog()}>
-                    <FormattedMessage id="entry.invite-team-nag" />
+            <div
+              className={classNames({
+                [styles.inviteContainer]: true,
+                [styles.inviteContainerBelowHud]: entryFinished,
+                [styles.inviteContainerInverted]: this.state.showInviteDialog
+              })}
+            >
+              {!showVREntryButton && (
+                <WithHoverSound>
+                  <button
+                    className={classNames({ [styles.hideSmallScreens]: this.occupantCount() > 1 && entryFinished })}
+                    onClick={() => this.toggleInviteDialog()}
+                  >
+                    <FormattedMessage id="entry.invite-others-nag" />
                   </button>
-                </div>
+                </WithHoverSound>
               )}
-              {!this.isWaitingForAutoExit() && (
-                <TwoDHUD.BottomHUD
-                  onCreateObject={() => this.showCreateObjectDialog()}
-                  showPhotoPicker={AFRAME.utils.device.isMobile()}
-                  onMediaPicked={this.createObject}
+              {!showVREntryButton &&
+                this.occupantCount() > 1 &&
+                entryFinished && (
+                  <WithHoverSound>
+                    <button onClick={this.onMiniInviteClicked} className={styles.inviteMiniButton}>
+                      <span>
+                        {this.state.miniInviteActivated
+                          ? navigator.share
+                            ? "sharing..."
+                            : "copied!"
+                          : "hub.link/" + this.props.hubId}
+                      </span>
+                    </button>
+                  </WithHoverSound>
+                )}
+              {showVREntryButton && (
+                <WithHoverSound>
+                  <button onClick={() => this.props.scene.enterVR()}>
+                    <FormattedMessage id="entry.return-to-vr" />
+                  </button>
+                </WithHoverSound>
+              )}
+              {this.state.showInviteDialog && (
+                <InviteDialog
+                  allowShare={!this.props.availableVREntryTypes.isInHMD}
+                  entryCode={this.props.hubEntryCode}
+                  hubId={this.props.hubId}
+                  onClose={() => this.setState({ showInviteDialog: false })}
                 />
               )}
             </div>
-          ) : null}
-        </div>
-      </IntlProvider>
+
+            {this.state.showLinkDialog && (
+              <LinkDialog
+                linkCode={this.state.linkCode}
+                onClose={() => {
+                  this.state.linkCodeCancel();
+                  this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null });
+                }}
+              />
+            )}
+
+            <WithHoverSound>
+              <button onClick={() => this.showHelpDialog()} className={styles.helpIcon}>
+                <i>
+                  <FontAwesomeIcon icon={faQuestion} />
+                </i>
+              </button>
+            </WithHoverSound>
+
+            <div
+              onClick={() => this.setState({ showPresenceList: !this.state.showPresenceList })}
+              className={classNames({
+                [styles.presenceInfo]: true,
+                [styles.presenceInfoSelected]: this.state.showPresenceList
+              })}
+            >
+              <FontAwesomeIcon icon={faUsers} />
+              <span className={styles.occupantCount}>{this.occupantCount()}</span>
+            </div>
+
+            {this.state.showPresenceList && (
+              <PresenceList presences={this.props.presences} sessionId={this.props.sessionId} />
+            )}
+
+            {this.state.entryStep === ENTRY_STEPS.finished ? (
+              <div>
+                <TwoDHUD.TopHUD
+                  muted={this.state.muted}
+                  frozen={this.state.frozen}
+                  spacebubble={this.state.spacebubble}
+                  onToggleMute={this.toggleMute}
+                  onToggleFreeze={this.toggleFreeze}
+                  onToggleSpaceBubble={this.toggleSpaceBubble}
+                  onSpawnPen={this.spawnPen}
+                  onSpawnCamera={() => this.props.scene.emit("action_spawn_camera")}
+                />
+                {this.props.isSupportAvailable && (
+                  <div className={styles.nagCornerButton}>
+                    <WithHoverSound>
+                      <button onClick={() => this.showInviteTeamDialog()}>
+                        <FormattedMessage id="entry.invite-team-nag" />
+                      </button>
+                    </WithHoverSound>
+                  </div>
+                )}
+                {!this.isWaitingForAutoExit() && (
+                  <TwoDHUD.BottomHUD
+                    onCreateObject={() => this.showCreateObjectDialog()}
+                    showPhotoPicker={AFRAME.utils.device.isMobile()}
+                    onMediaPicked={this.createObject}
+                  />
+                )}
+              </div>
+            ) : null}
+          </div>
+        </IntlProvider>
+      </ReactAudioContext.Provider>
     );
   }
 }
diff --git a/src/react-components/updates-dialog.js b/src/react-components/updates-dialog.js
index 71d732c2404a366a7bb5e858ba0609bc6d241473..b5a746582e5b460c73b1bb1b5105ba6003537744 100644
--- a/src/react-components/updates-dialog.js
+++ b/src/react-components/updates-dialog.js
@@ -3,6 +3,7 @@ import { FormattedMessage } from "react-intl";
 import PropTypes from "prop-types";
 import formurlencoded from "form-urlencoded";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class UpdatesDialog extends Component {
   static propTypes = {
@@ -44,30 +45,38 @@ export default class UpdatesDialog extends Component {
           <p>Sign up to get updates about new features in Hubs.</p>
           <form onSubmit={signUpForMailingList}>
             <div className="mailing-list-form">
-              <input
-                type="email"
-                value={this.state.mailingListEmail}
-                onChange={e => this.setState({ mailingListEmail: e.target.value })}
-                className="mailing-list-form__email_field"
-                required
-                placeholder="Your email here"
-              />
-              <label className="mailing-list-form__privacy">
+              <WithHoverSound>
                 <input
-                  className="mailing-list-form__privacy_checkbox"
-                  type="checkbox"
+                  type="email"
+                  value={this.state.mailingListEmail}
+                  onChange={e => this.setState({ mailingListEmail: e.target.value })}
+                  className="mailing-list-form__email_field"
                   required
-                  value={this.state.mailingListPrivacy}
-                  onChange={e => this.setState({ mailingListPrivacy: e.target.checked })}
+                  placeholder="Your email here"
                 />
+              </WithHoverSound>
+              <label className="mailing-list-form__privacy">
+                <WithHoverSound>
+                  <input
+                    className="mailing-list-form__privacy_checkbox"
+                    type="checkbox"
+                    required
+                    value={this.state.mailingListPrivacy}
+                    onChange={e => this.setState({ mailingListPrivacy: e.target.checked })}
+                  />
+                </WithHoverSound>
                 <span className="mailing-list-form__privacy_label">
                   <FormattedMessage id="mailing_list.privacy_label" />{" "}
-                  <a target="_blank" rel="noopener noreferrer" href="https://www.mozilla.org/en-US/privacy/">
-                    <FormattedMessage id="mailing_list.privacy_link" />
-                  </a>
+                  <WithHoverSound>
+                    <a target="_blank" rel="noopener noreferrer" href="https://www.mozilla.org/en-US/privacy/">
+                      <FormattedMessage id="mailing_list.privacy_link" />
+                    </a>
+                  </WithHoverSound>
                 </span>
               </label>
-              <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" />
+              <WithHoverSound>
+                <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" />
+              </WithHoverSound>
             </div>
           </form>
         </span>
diff --git a/src/react-components/webvr-recommend-dialog.js b/src/react-components/webvr-recommend-dialog.js
index 264245b273f69e3ba6c17a893362d3f0f715c2a7..7080457c4d37164a9e75e6f5af1c8003a534cbbe 100644
--- a/src/react-components/webvr-recommend-dialog.js
+++ b/src/react-components/webvr-recommend-dialog.js
@@ -1,5 +1,6 @@
 import React, { Component } from "react";
 import DialogContainer from "./dialog-container.js";
+import { WithHoverSound } from "./wrap-with-audio";
 
 export default class WebVRRecommendDialog extends Component {
   render() {
@@ -7,14 +8,18 @@ export default class WebVRRecommendDialog extends Component {
       <DialogContainer title="Enter in VR" {...this.props}>
         <div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
           <p>To enter Hubs with Oculus or SteamVR, you can use Firefox.</p>
-          <a className="info-dialog--action-button" href="https://www.mozilla.org/firefox">
-            Download Firefox
-          </a>
+          <WithHoverSound>
+            <a className="info-dialog--action-button" href="https://www.mozilla.org/firefox">
+              Download Firefox
+            </a>
+          </WithHoverSound>
           <p style={{ fontSize: "0.8em" }}>
             For a full list of browsers with experimental VR support, visit{" "}
-            <a href="https://webvr.rocks" target="_blank" rel="noopener noreferrer">
-              WebVR Rocks
-            </a>.
+            <WithHoverSound>
+              <a href="https://webvr.rocks" target="_blank" rel="noopener noreferrer">
+                WebVR Rocks
+              </a>
+            </WithHoverSound>
           </p>
         </div>
       </DialogContainer>
diff --git a/src/react-components/wrap-with-audio.js b/src/react-components/wrap-with-audio.js
new file mode 100644
index 0000000000000000000000000000000000000000..71cef333b21f4d0a087ce16785c09c78078f7c80
--- /dev/null
+++ b/src/react-components/wrap-with-audio.js
@@ -0,0 +1,28 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+export const ReactAudioContext = React.createContext({});
+
+export const hudHoverSound = "play_sound-hud_hover_start";
+
+export const WithHoverSound = ({ sound, children }) => {
+  return (
+    <ReactAudioContext.Consumer>
+      {context => {
+        return React.cloneElement(children, {
+          onMouseEnter: e => {
+            if (context && context.playSound) {
+              context.playSound(sound || hudHoverSound);
+            }
+            children.props.onMouseEnter && children.props.onMouseEnter(e);
+          }
+        });
+      }}
+    </ReactAudioContext.Consumer>
+  );
+};
+
+WithHoverSound.propTypes = {
+  children: PropTypes.object,
+  sound: PropTypes.string
+};