diff --git a/src/AudioContext.js b/src/AudioContext.js
new file mode 100644
index 0000000000000000000000000000000000000000..8d1554ff5eb63f27e5c96da5cd3122c96393f63e
--- /dev/null
+++ b/src/AudioContext.js
@@ -0,0 +1,7 @@
+import React from "react";
+export const AudioContext = React.createContext({
+  onMouseEnter: () => {
+    console.log("enter sound not configured");
+  },
+  onMouseLeave: () => {}
+});
diff --git a/src/assets/sfx/237716__hydranos__beeph.mp3 b/src/assets/sfx/237716__hydranos__beeph.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..37190370c6ca0e9352cfce9b207034ecee746757
Binary files /dev/null and b/src/assets/sfx/237716__hydranos__beeph.mp3 differ
diff --git a/src/assets/sfx/238270__meroleroman7__robot.wav b/src/assets/sfx/238270__meroleroman7__robot.wav
new file mode 100644
index 0000000000000000000000000000000000000000..ca7207f96e584e4a918f13b9c99ba197a7280930
Binary files /dev/null and b/src/assets/sfx/238270__meroleroman7__robot.wav differ
diff --git a/src/assets/sfx/431532__supersound23__popping.mp3 b/src/assets/sfx/431532__supersound23__popping.mp3
new file mode 100755
index 0000000000000000000000000000000000000000..0a5a6d0a3b59e76df4c630f9c9d2696892f0bbfe
Binary files /dev/null and b/src/assets/sfx/431532__supersound23__popping.mp3 differ
diff --git a/src/assets/sfx/HUBS_GOODBYE_Happy_Tech.wav b/src/assets/sfx/HUBS_GOODBYE_Happy_Tech.wav
new file mode 100755
index 0000000000000000000000000000000000000000..04992a2245c1444958792d2e80d4bda264d4ef48
Binary files /dev/null and b/src/assets/sfx/HUBS_GOODBYE_Happy_Tech.wav differ
diff --git a/src/assets/sfx/HUBS_WELCOME_Arrival.wav b/src/assets/sfx/HUBS_WELCOME_Arrival.wav
new file mode 100755
index 0000000000000000000000000000000000000000..4e152532c4221e29565f7908a088f099bb93485c
Binary files /dev/null and b/src/assets/sfx/HUBS_WELCOME_Arrival.wav differ
diff --git a/src/assets/sfx/HUBS_WELCOME_Complex_Synth.wav b/src/assets/sfx/HUBS_WELCOME_Complex_Synth.wav
new file mode 100755
index 0000000000000000000000000000000000000000..932f84c861a5ff876648419adaccbb5f5147025b
Binary files /dev/null and b/src/assets/sfx/HUBS_WELCOME_Complex_Synth.wav differ
diff --git a/src/assets/sfx/HUBS_WELCOME_Happy_Tech.wav b/src/assets/sfx/HUBS_WELCOME_Happy_Tech.wav
new file mode 100755
index 0000000000000000000000000000000000000000..c0e398252d000d744f0fee5d9b59d89dbdcab41c
Binary files /dev/null and b/src/assets/sfx/HUBS_WELCOME_Happy_Tech.wav differ
diff --git a/src/assets/sfx/Object_Grow_New.wav b/src/assets/sfx/Object_Grow_New.wav
new file mode 100755
index 0000000000000000000000000000000000000000..5513a526cfb794bd87fc7fdb5d8227020e896c93
Binary files /dev/null and b/src/assets/sfx/Object_Grow_New.wav differ
diff --git a/src/assets/sfx/Object_Lock_New.wav b/src/assets/sfx/Object_Lock_New.wav
new file mode 100755
index 0000000000000000000000000000000000000000..19dcf0bd0730cfda7d9b4d9a62c3f1517613874a
Binary files /dev/null and b/src/assets/sfx/Object_Lock_New.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/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.aif b/src/assets/sfx/tap_mellow.aif
new file mode 100644
index 0000000000000000000000000000000000000000..3e39298e64ca5c157d1d32df3cfece94235bcc17
Binary files /dev/null and b/src/assets/sfx/tap_mellow.aif 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/teleport_cancel.wav b/src/assets/sfx/teleport_cancel.wav
new file mode 100755
index 0000000000000000000000000000000000000000..d19009ca8f38970196ae5541ef7613d5efe4ae05
Binary files /dev/null and b/src/assets/sfx/teleport_cancel.wav differ
diff --git a/src/assets/sfx/teleport_end.wav b/src/assets/sfx/teleport_end.wav
new file mode 100755
index 0000000000000000000000000000000000000000..02dd434b4d476de5f9f74f9ac034a4e94e399a19
Binary files /dev/null and b/src/assets/sfx/teleport_end.wav differ
diff --git a/src/assets/sfx/teleport_start.wav b/src/assets/sfx/teleport_start.wav
new file mode 100755
index 0000000000000000000000000000000000000000..702badd2838dde1ecd4eb01e8122e203e3686585
Binary files /dev/null and b/src/assets/sfx/teleport_start.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 cc6bbefb233cecc6d28980fd2d1630c5d1730952..d229c35f985fab178eede531dbfb76e7a7b377f3 100644
--- a/src/components/camera-tool.js
+++ b/src/components/camera-tool.js
@@ -139,6 +139,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/cursor-controller.js b/src/components/cursor-controller.js
index 421b084c309710e9442dd3f44019fdcb0cad94d5..c796a1348fa037a0f0f7df5b812f465ee1de2d06 100644
--- a/src/components/cursor-controller.js
+++ b/src/components/cursor-controller.js
@@ -229,10 +229,12 @@ AFRAME.registerComponent("cursor-controller", {
     const targetDistanceMod = this.currentDistanceMod + delta;
     const moddedDistance = this.currentDistance - targetDistanceMod;
     if (moddedDistance > far || moddedDistance < near) {
+      this.el.emit("cursor-distance-change-blocked");
       return false;
     }
 
     this.currentDistanceMod = targetDistanceMod;
+    this.el.emit("cursor-distance-changed");
     return true;
   },
 
diff --git a/src/components/emit-state-change.js b/src/components/emit-state-change.js
new file mode 100644
index 0000000000000000000000000000000000000000..b3357cd3911f26b8165235ca06e32e64d8bf0ac0
--- /dev/null
+++ b/src/components/emit-state-change.js
@@ -0,0 +1,33 @@
+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() {
+    if (this.data.transform === "rising") {
+      this.el.addEventListener("stateadded", this.stateadded);
+    }
+    if (this.data.transform === "falling") {
+      this.el.addEventListener("stateremoved", this.stateadded);
+    }
+  }
+});
diff --git a/src/components/scene-sound.js b/src/components/scene-sound.js
new file mode 100644
index 0000000000000000000000000000000000000000..430eda4c0b52b0a703158362f0d2fd75ff5f62df
--- /dev/null
+++ b/src/components/scene-sound.js
@@ -0,0 +1,12 @@
+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..f4fe0a0de5a484bb636b165e48ad42802c0e398a 100644
--- a/src/components/tools/networked-drawing.js
+++ b/src/components/tools/networked-drawing.js
@@ -381,6 +381,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);
diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js
index 855c94e0a1d11f5905dd20f55acdda27ebad227d..edeb6e9e748db54f7ed0b3674dc064523f6dcbd6 100644
--- a/src/components/tools/pen.js
+++ b/src/components/tools/pen.js
@@ -120,13 +120,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);
@@ -151,18 +152,23 @@ AFRAME.registerComponent("pen", {
     switch (evt.detail) {
       case "activated":
         this._startDraw();
+        this.el.emit("start_draw");
         break;
       case "colorNext":
         this._changeColor(1);
+        this.el.emit("next_pen_color");
         break;
       case "colorPrev":
         this._changeColor(-1);
+        this.el.emit("prev_pen_color");
         break;
       case "radiusUp":
         this._changeRadius(this.data.minRadius);
+        this.el.emit("increase_pen_radius");
         break;
       case "radiusDown":
         this._changeRadius(-this.data.minRadius);
+        this.el.emit("decrease_pen_radius");
         break;
       case "grabbed":
         this.grabbed = true;
diff --git a/src/hub.html b/src/hub.html
index 4884abca46af25a088c333af4fab7ad673731ec9..0eb0a47548b3ec8b85641a4be2ca9671e1d3c1fc 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -62,6 +62,7 @@
             <img id="spawn-pen-hover" crossorigin="anonymous" src="./assets/hud/spawn_pen-hover.png">
             <img id="spawn-camera" crossorigin="anonymous" src="./assets/hud/spawn_camera.png">
             <img id="spawn-camera-hover" crossorigin="anonymous" src="./assets/hud/spawn_camera-hover.png">
+            <img id="water-normal-map" crossorigin="anonymous" src="./assets/waternormals.jpg">
 
             <a-asset-item id="botdefault" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotDefault_Avatar-9f71f8ff22.gltf"></a-asset-item>
             <a-asset-item id="botbobo" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotBobo_Avatar-f9740a010b.gltf"></a-asset-item>
@@ -74,11 +75,32 @@
             <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/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-teleport_end"                   src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-snap_rotate"                    src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-media_loaded"                   src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-hud_mouseout"                   src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-hud_mouseover"                  src="./assets/sfx/tap_mellow.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="sound_asset-hover_off"                      src="./assets/sfx/footfall_click.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/footfall_click.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/tap_mellow.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-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/tap_mellow.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/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-start_draw"                     src="./assets/sfx/tap_mellow.wav"     response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-stop_draw"                      src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+            <a-asset-item id="sound_asset-camera_tool_took_snapshot"      src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item>
+
+            <!-- <a-asset-item id="watch-model" response-type="arraybuffer" src="./assets/hud/watch.glb"></a-asset-item> -->
             <!-- Templates -->
             <template id="video-template">
                 <a-entity class="video" geometry="primitive: plane;" material="side: double; shader: flat;" networked-video-player></a-entity>
@@ -161,6 +183,10 @@
                     rotation
                     activatable__increase-scale="buttonStartEvents: scroll_right; buttonEndEvents: horizontal_scroll_release; activatedState: scaleUp;"
                     activatable__decrease-scale="buttonStartEvents: scroll_left; buttonEndEvents: horizontal_scroll_release; activatedState: scaleDown;"
+                    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;"
                 >
                     <!-- HACK: rotation component above is required for its side effect of setting YXZ order -->
                     <a-entity class="delete-button" visible-while-frozen>
@@ -185,6 +211,16 @@
                     activatable__increase-radius="buttonStartEvents: increase_radius, scroll_up; buttonEndEvents: thumb_up, secondary_hand_release, vertical_scroll_release; activatedState: radiusUp;"
                     activatable__decrease-radius="buttonStartEvents: decrease_radius, scroll_down; buttonEndEvents: thumb_up, secondary_hand_release, vertical_scroll_release; activatedState: radiusDown;"
                     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"
@@ -341,10 +377,45 @@
               <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;"></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"></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 pen" material="alphaTest:0.1;"></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;"></a-image>
+              <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;"
+                  sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;"
+                  sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;"
+                  sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;"
+              ></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"
+                  sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;"
+                  sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;"
+                  sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;"
+              ></a-image>
+              <a-image
+                  icon-button="tooltip: #hud-tooltip; tooltipText: Spawn Pen; activeTooltipText: Spawn 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 pen"
+                  material="alphaTest:0.1;"
+                  sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;"
+                  sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;"
+                  sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;"
+              ></a-image>
+              <a-image
+                  icon-button="tooltip: #hud-tooltip; tooltipText: Spawn Camera; activeTooltipText: Spawn 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;"
+                  sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;"
+                  sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;"
+                  sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;"
+              ></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>
@@ -357,6 +428,38 @@
               camera
               personal-space-bubble="radius: 0.4;"
               pitch-yaw-rotator
+              sound__teleport_start="src: #sound_asset-teleport_start; on: nothing; poolSize: 2;"
+              scene-sound__teleport_start="on: cursor-teleport_down;"
+              sound__teleport_end="src: #sound_asset-teleport_end; on: nothing; poolSize: 2;"
+              scene-sound__teleport_end="on: cursor-teleport_up;"
+              sound__snap_rotate_left="src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;"
+              scene-sound__snap_rotate_left="on: snap_rotate_left;"
+              sound__snap_rotate_right="src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;"
+              scene-sound__snap_rotate_right="on: snap_rotate_right;"
+              sound__model_loaded="src: #sound_asset-media_loaded; on: nothing; poolSize: 2;"
+              scene-sound__model_loaded="on: model-loaded;"
+              sound__image_loaded="src: #sound_asset-media_loaded; on: nothing; poolSize: 2;"
+              scene-sound__image_loaded="on: image-loaded;"
+              sound__hud_action_mute="src: #sound_asset-toggle_mute; on: nothing; poolSize: 2;"
+              scene-sound__hud_action_mute="on: action_mute;"
+              sound__hud_action_freeze="src: #sound_asset-toggle_freeze; on: nothing; poolSize: 2;"
+              scene-sound__hud_action_freeze="on: action_freeze;"
+              sound__hud_action_space_bubble="src: #sound_asset-toggle_space_bubble; on: nothing; poolSize: 2;"
+              scene-sound__hud_action_space_bubble="on: action_space_bubble;"
+              sound__hud_spawn_pen="src: #sound_asset-spawn_pen; on: nothing; poolSize: 2;"
+              scene-sound__hud_spawn_pen="on: spawn_pen;"
+              sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: nothing; poolSize: 1;"
+              scene-sound__hud_mouseover="on: play_sound-hud_mouse_enter;"
+              sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: nothing; poolSize: 1;"
+              scene-sound__hud_mouseout="on: play_sound-hud_mouse_leave;"
+              sound__hud_click="src: #sound_asset-hud_click; on: nothing; poolSize: 1;"
+              scene-sound__hud_click="on: hud_click;"
+              sound__cursor_distance_changed="src: #sound_asset-cursor_distance_changed; on: nothing; poolSize: 1;"
+              scene-sound__cursor_distance_changed="on: cursor-distance-changed;"
+              sound__cursor_distance_change_blocked="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="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 a091cb4249135153be71241510caff22f42745ee..b7a1b3d8f7854dbb19260fdd4fd15702d5e874c9 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -70,6 +70,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 ReactDOM from "react-dom";
 import React from "react";
diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js
index 74577ff1ec0291d8bd14665fe0a7054c8a0f70e3..d40e661c9a63338ae027ff44d004fc906d86147c 100644
--- a/src/react-components/2d-hud.js
+++ b/src/react-components/2d-hud.js
@@ -4,28 +4,49 @@ import cx from "classnames";
 
 import styles from "../assets/stylesheets/2d-hud.scss";
 import uiStyles from "../assets/stylesheets/ui-root.scss";
+import { AudioContext } from "../AudioContext";
 
 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)}>
-      <div
-        className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })}
-        title={muted ? "Unmute Mic" : "Mute Mic"}
-        onClick={onToggleMute}
-      />
-    </div>
-    <div
-      className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, {
-        [styles.active]: frozen
-      })}
-      title={frozen ? "Resume" : "Pause"}
-      onClick={onToggleFreeze}
-    />
-    <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} />
-    </div>
-  </div>
+  <AudioContext.Consumer>
+    {audio => (
+      <div className={cx(styles.container, styles.top, styles.unselectable)}>
+        <div className={cx(uiStyles.uiInteractive, styles.panel, styles.left)}>
+          <div
+            className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })}
+            title={muted ? "Unmute Mic" : "Mute Mic"}
+            onClick={onToggleMute}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          />
+        </div>
+        <div
+          className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, {
+            [styles.active]: frozen
+          })}
+          title={frozen ? "Resume" : "Pause"}
+          onClick={onToggleFreeze}
+          onMouseEnter={audio.onMouseEnter}
+          onMouseLeave={audio.onMouseLeave}
+        />
+        <div className={cx(uiStyles.uiInteractive, styles.panel, styles.right)}>
+          <div
+            className={cx(styles.iconButton, styles.spawn_pen)}
+            title={"Drawing Pen"}
+            onClick={onSpawnPen}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          />
+          <div
+            className={cx(styles.iconButton, styles.spawn_camera)}
+            title={"Camera"}
+            onClick={onSpawnCamera}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          />
+        </div>
+      </div>
+    )}
+  </AudioContext.Consumer>
 );
 
 TopHUD.propTypes = {
@@ -38,36 +59,47 @@ TopHUD.propTypes = {
 };
 
 const BottomHUD = ({ onCreateObject, showPhotoPicker, onMediaPicked }) => (
-  <div className={cx(styles.container, styles.column, styles.bottom, styles.unselectable)}>
-    {showPhotoPicker ? (
-      <div className={cx(uiStyles.uiInteractive, styles.panel, styles.up)}>
-        <input
-          id="media-picker-input"
-          className={cx(styles.hide)}
-          type="file"
-          accept="image/*"
-          multiple
-          onChange={e => {
-            for (const file of e.target.files) {
-              onMediaPicked(file);
-            }
-          }}
-        />
-        <label htmlFor="media-picker-input">
-          <div className={cx(styles.iconButton, styles.mobileMediaPicker)} title={"Pick Media"} />
-        </label>
+  <AudioContext.Consumer>
+    {audio => (
+      <div className={cx(styles.container, styles.column, styles.bottom, styles.unselectable)}>
+        {showPhotoPicker ? (
+          <div className={cx(uiStyles.uiInteractive, styles.panel, styles.up)}>
+            <input
+              id="media-picker-input"
+              className={cx(styles.hide)}
+              type="file"
+              accept="image/*"
+              multiple
+              onChange={e => {
+                for (const file of e.target.files) {
+                  onMediaPicked(file);
+                }
+              }}
+            />
+            <label htmlFor="media-picker-input">
+              <div
+                className={cx(styles.iconButton, styles.mobileMediaPicker)}
+                title={"Pick Media"}
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              />
+            </label>
+          </div>
+        ) : (
+          <div />
+        )}
+        <div>
+          <div
+            className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)}
+            title={"Create Object"}
+            onClick={onCreateObject}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          />
+        </div>
       </div>
-    ) : (
-      <div />
     )}
-    <div>
-      <div
-        className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)}
-        title={"Create Object"}
-        onClick={onCreateObject}
-      />
-    </div>
-  </div>
+  </AudioContext.Consumer>
 );
 
 BottomHUD.propTypes = {
diff --git a/src/react-components/auto-exit-warning.js b/src/react-components/auto-exit-warning.js
index d8691a85182c7f8bbd3ee9a7472e428df9aa2d3b..4f54111ed0290f3e15bc1112c4df5181fd3aef14 100644
--- a/src/react-components/auto-exit-warning.js
+++ b/src/react-components/auto-exit-warning.js
@@ -2,20 +2,31 @@ import React from "react";
 import { FormattedMessage } from "react-intl";
 import PropTypes from "prop-types";
 
+import { AudioContext } from "../AudioContext";
+
 const AutoExitWarning = props => (
-  <div className="autoexit-panel">
-    <div className="autoexit-panel__title">
-      <FormattedMessage id="autoexit.title" />
-      <span>{props.secondsRemaining}</span>
-      <FormattedMessage id="autoexit.title_units" />
-    </div>
-    <div className="autoexit-panel__subtitle">
-      <FormattedMessage id="autoexit.subtitle" />
-    </div>
-    <div className="autoexit-panel__cancel-button" onClick={props.onCancel}>
-      <FormattedMessage id="autoexit.cancel" />
-    </div>
-  </div>
+  <AudioContext.Consumer>
+    {audio => (
+      <div className="autoexit-panel">
+        <div className="autoexit-panel__title">
+          <FormattedMessage id="autoexit.title" />
+          <span>{props.secondsRemaining}</span>
+          <FormattedMessage id="autoexit.title_units" />
+        </div>
+        <div className="autoexit-panel__subtitle">
+          <FormattedMessage id="autoexit.subtitle" />
+        </div>
+        <div
+          className="autoexit-panel__cancel-button"
+          onClick={props.onCancel}
+          onMouseEnter={audio.onMouseEnter}
+          onMouseLeave={audio.onMouseLeave}
+        >
+          <FormattedMessage id="autoexit.cancel" />
+        </div>
+      </div>
+    )}
+  </AudioContext.Consumer>
 );
 
 AutoExitWarning.propTypes = {
diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js
index 0c024655d46b39c77bf7c5f50e87733907cbd790..314b45d74f95ecb14091c14d2f6d95cfc922ec0a 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 { AudioContext } from "../AudioContext";
 
 class AvatarSelector extends Component {
   static propTypes = {
@@ -176,12 +177,30 @@ 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>
+        <AudioContext.Consumer>
+          {audio => (
+            <button
+              className="avatar-selector__previous-button"
+              onClick={this.emitChangeToPrevious}
+              onMouseEnter={audio.onMouseEnter}
+              onMouseLeave={audio.onMouseLeave}
+            >
+              <FontAwesomeIcon icon={faAngleLeft} />
+            </button>
+          )}
+        </AudioContext.Consumer>
+        <AudioContext.Consumer>
+          {audio => (
+            <button
+              className="avatar-selector__next-button"
+              onClick={this.emitChangeToNext}
+              onMouseEnter={audio.onMouseEnter}
+              onMouseLeave={audio.onMouseLeave}
+            >
+              <FontAwesomeIcon icon={faAngleRight} />
+            </button>
+          )}
+        </AudioContext.Consumer>
       </div>
     );
   }
diff --git a/src/react-components/create-object-dialog.js b/src/react-components/create-object-dialog.js
index 5ccfe2ce692a234fff6e76d8657516c1ac1d8816..46b1d95d12ff6bcaa0d7d856cb94516be8b9bc68 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 { AudioContext } from "../AudioContext";
 
 const attributionHostnames = {
   "giphy.com": giphyLogo,
@@ -87,14 +88,32 @@ 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>
+      <AudioContext.Consumer>
+        {audio => (
+          <label
+            className={cx(styles.smallButton, styles.cancelIcon)}
+            onClick={this.reset}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          >
+            <FontAwesomeIcon icon={faTimes} />
+          </label>
+        )}
+      </AudioContext.Consumer>
     );
     const uploadButton = (
-      <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}>
-        <FontAwesomeIcon icon={faPaperclip} />
-      </label>
+      <AudioContext.Consumer>
+        {audio => (
+          <label
+            htmlFor={fileInputId}
+            className={cx(styles.smallButton, styles.uploadIcon)}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          >
+            <FontAwesomeIcon icon={faPaperclip} />
+          </label>
+        )}
+      </AudioContext.Consumer>
     );
     const filenameLabel = <label className={cx(styles.leftSideOfInput)}>{this.state.fileName}</label>;
     const urlInput = (
@@ -108,7 +127,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}>
@@ -125,9 +144,17 @@ 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>
+                <AudioContext.Consumer>
+                  {audio => (
+                    <button
+                      className={styles.actionButton}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      <span>Create</span>
+                    </button>
+                  )}
+                </AudioContext.Consumer>
               </div>
               {this.state.attributionImage ? (
                 <div>
diff --git a/src/react-components/create-room-dialog.js b/src/react-components/create-room-dialog.js
index 756cc9bf43736059999a1a66b2fdfe5d856a4e96..15e61023ae2cbc13b78640138db39c463936cdcb 100644
--- a/src/react-components/create-room-dialog.js
+++ b/src/react-components/create-room-dialog.js
@@ -1,10 +1,11 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import DialogContainer from "./dialog-container.js";
+import { AudioContext } from "../AudioContext";
 
 const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$";
 
-export default class CreateObjectDialog extends Component {
+export default class CreateRoomDialog extends Component {
   static propTypes = {
     onCustomScene: PropTypes.func,
     onClose: PropTypes.func
@@ -46,9 +47,17 @@ export default class CreateObjectDialog extends Component {
                 onChange={e => this.setState({ customSceneUrl: e.target.value })}
               />
               <div className="custom-scene-form__buttons">
-                <button className="custom-scene-form__action-button">
-                  <span>create</span>
-                </button>
+                <AudioContext.Consumer>
+                  {audio => (
+                    <button
+                      className="custom-scene-form__action-button"
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      <span>create</span>
+                    </button>
+                  )}
+                </AudioContext.Consumer>
               </div>
             </div>
           </form>
diff --git a/src/react-components/dialog-container.js b/src/react-components/dialog-container.js
index 18c37956c7408150d59d2385e251817ad9747cb7..f57aa92ccdcf02edd0bf19168e3ef7c64c698edc 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 { AudioContext } from "../AudioContext";
 
 export default class DialogContainer extends Component {
   static propTypes = {
@@ -37,20 +38,29 @@ export default class DialogContainer extends Component {
   render() {
     return (
       <div className="dialog-overlay">
-        <div className="dialog" onClick={this.onContainerClicked}>
-          <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>
-              )}
-              <div className="dialog__box__contents__title">{this.props.title}</div>
-              <div className="dialog__box__contents__body">{this.props.children}</div>
-              <div className="dialog__box__contents__button-container" />
+        <AudioContext.Consumer>
+          {audio => (
+            <div className="dialog" onClick={this.onContainerClicked}>
+              <div className="dialog__box">
+                <div className="dialog__box__contents">
+                  {this.props.onClose && (
+                    <button
+                      className="dialog__box__contents__close"
+                      onClick={this.props.onClose}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      <span>×</span>
+                    </button>
+                  )}
+                  <div className="dialog__box__contents__title">{this.props.title}</div>
+                  <div className="dialog__box__contents__body">{this.props.children}</div>
+                  <div className="dialog__box__contents__button-container" />
+                </div>
+              </div>
             </div>
-          </div>
-        </div>
+          )}
+        </AudioContext.Consumer>
       </div>
     );
   }
diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js
index 9e64b3ab4c419b6728e1351e7080629f1b6a97ea..469572e40dcdedc7429fca48deaa1325475726b5 100644
--- a/src/react-components/entry-buttons.js
+++ b/src/react-components/entry-buttons.js
@@ -1,6 +1,7 @@
 import React from "react";
 import { FormattedMessage } from "react-intl";
 import PropTypes from "prop-types";
+import { AudioContext } from "../AudioContext";
 
 import MobileScreenEntryImg from "../assets/images/mobile_screen_entry.svg";
 import DesktopScreenEntryImg from "../assets/images/desktop_screen_entry.svg";
@@ -11,24 +12,33 @@ 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} />
+  <AudioContext.Consumer>
+    {audio => (
+      <button
+        className={styles.entryButton}
+        onClick={props.onClick}
+        onMouseEnter={audio.onMouseEnter}
+        onMouseLeave={audio.onMouseLeave}
+      >
+        <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>
+    )}
+  </AudioContext.Consumer>
 );
 
 EntryButton.propTypes = {
diff --git a/src/react-components/help-dialog.js b/src/react-components/help-dialog.js
index 7e1fb4ba46801f921159a853acc41b5737a64331..ba6da9a9711a3bb944976b011483d898567ac135 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 { AudioContext } from "../AudioContext";
 
 export default class HelpDialog extends Component {
   render() {
@@ -21,17 +22,39 @@ export default class HelpDialog extends Component {
           <p>
             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>
-          </p>
+          <AudioContext.Consumer>
+            {audio => (
+              <p className="dialog__box__contents__links">
+                <a
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  href="https://github.com/mozilla/hubs/blob/master/TERMS.md"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <FormattedMessage id="profile.terms_of_use" />
+                </a>
+                <a
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <FormattedMessage id="profile.privacy_notice" />
+                </a>
+                <a
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  href="/?report"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <FormattedMessage id="help.report_issue" />
+                </a>
+              </p>
+            )}
+          </AudioContext.Consumer>
         </div>
       </DialogContainer>
     );
diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js
index b7b36ecc74829b3b3a7e7bda7901ebfe073e7e63..49ccd30da1da604e2aac5cb1298271275cefc009 100644
--- a/src/react-components/home-root.js
+++ b/src/react-components/home-root.js
@@ -20,6 +20,7 @@ import ReportDialog from "./report-dialog.js";
 import SlackDialog from "./slack-dialog.js";
 import UpdatesDialog from "./updates-dialog.js";
 import DialogContainer from "./dialog-container.js";
+import { AudioContext } from "../AudioContext";
 
 addLocaleData([...en]);
 
@@ -180,119 +181,146 @@ class HomeRoot extends Component {
     });
 
     return (
-      <IntlProvider locale={lang} messages={messages}>
-        <div className={styles.home}>
-          <div className={mainContentClassNames}>
-            <div className={styles.headerContent}>
-              <div className={styles.titleAndNav}>
-                <div className={styles.links}>
-                  <a
-                    href="https://blog.mozvr.com/introducing-hubs-a-new-way-to-get-together-online/"
-                    rel="noreferrer noopener"
-                  >
-                    <FormattedMessage id="home.about_link" />
-                  </a>
-                  <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener">
-                    <FormattedMessage id="home.source_link" />
-                  </a>
-                </div>
-              </div>
-              <div className={styles.ident} />
-            </div>
-            <div className={styles.heroContent}>
-              <div className={styles.attribution}>
-                Medieval Fantasy Book by{" "}
-                <a
-                  target="_blank"
-                  rel="noreferrer noopener"
-                  href="https://sketchfab.com/models/06d5a80a04fc4c5ab552759e9a97d91a?utm_campaign=06d5a80a04fc4c5ab552759e9a97d91a&utm_medium=embed&utm_source=oembed"
-                >
-                  Pixel
-                </a>
-              </div>
-              <div className={styles.container}>
-                <img className={styles.logo} src={hubLogo} />
-                <div className={styles.title}>
-                  <FormattedMessage id="home.hero_title" />
+      <AudioContext.Consumer>
+        {audio => (
+          <IntlProvider locale={lang} messages={messages}>
+            <div className={styles.home}>
+              <div className={mainContentClassNames}>
+                <div className={styles.headerContent}>
+                  <div className={styles.titleAndNav} onClick={() => (document.location = "/")}>
+                    <div className={styles.hubs} onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                      hubs
+                    </div>
+                    <div className={styles.preview} onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                      preview
+                    </div>
+                    <div className={styles.links}>
+                      <a
+                        href="https://blog.mozvr.com/introducing-hubs-a-new-way-to-get-together-online/"
+                        rel="noreferrer noopener"
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="home.about_link" />
+                      </a>
+                      <a
+                        href="https://github.com/mozilla/hubs"
+                        rel="noreferrer noopener"
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="home.source_link" />
+                      </a>
+                    </div>
+                  </div>
+                  <div className={styles.ident} />
                 </div>
-                {this.state.environments.length === 0 && (
-                  <div className="loader-wrap">
-                    <div className="loader">
-                      <div className="loader-center" />
+                <div className={styles.heroContent}>
+                  <div className={styles.attribution}>
+                    Medieval Fantasy Book by{" "}
+                    <a
+                      target="_blank"
+                      rel="noreferrer noopener"
+                      href="https://sketchfab.com/models/06d5a80a04fc4c5ab552759e9a97d91a?utm_campaign=06d5a80a04fc4c5ab552759e9a97d91a&utm_medium=embed&utm_source=oembed"
+                    >
+                      Pixel
+                    </a>
+                  </div>
+                  <div className={styles.container}>
+                    <img className={styles.logo} src={hubLogo} />
+                    <div className={styles.title}>
+                      <FormattedMessage id="home.hero_title" />
                     </div>
+                    {this.state.environments.length === 0 && (
+                      <div className="loader-wrap">
+                        <div className="loader">
+                          <div className="loader-center" />
+                        </div>
+                      </div>
+                    )}
                   </div>
-                )}
-              </div>
-              <div className={styles.create}>
-                <HubCreatePanel
-                  initialEnvironment={this.props.initialEnvironment}
-                  environments={this.state.environments}
-                />
-              </div>
-              {this.state.environments.length > 1 && (
-                <div className={styles.joinButton}>
-                  <a href="/link">
-                    <FormattedMessage id="home.join_room" />
-                  </a>
+                  <div className={styles.create}>
+                    <HubCreatePanel
+                      initialEnvironment={this.props.initialEnvironment}
+                      environments={this.state.environments}
+                    />
+                  </div>
+                  {this.state.environments.length > 1 && (
+                    <div className={styles.joinButton}>
+                      <a href="/link" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                        <FormattedMessage id="home.join_room" />
+                      </a>
+                    </div>
+                  )}
                 </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.showSlackDialog.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>
+                <div className={styles.footerContent}>
+                  <div className={styles.links}>
+                    <div className={styles.top}>
+                      <a
+                        className={styles.link}
+                        rel="noopener noreferrer"
+                        href="#"
+                        onClick={this.onDialogLinkClicked(this.showSlackDialog.bind(this))}
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="home.join_us" />
+                      </a>
+                      <a
+                        className={styles.link}
+                        rel="noopener noreferrer"
+                        href="#"
+                        onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))}
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="home.get_updates" />
+                      </a>
+                      <a
+                        className={styles.link}
+                        rel="noopener noreferrer"
+                        href="#"
+                        onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))}
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <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"
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <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"
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="home.privacy_notice" />
+                      </a>
 
-                  <img className={styles.mozLogo} src={mozLogo} />
+                      <img className={styles.mozLogo} src={mozLogo} />
+                    </div>
+                  </div>
                 </div>
               </div>
+              <video playsInline muted loop autoPlay className={styles.backgroundVideo} id="background-video">
+                <source src={homeVideoWebM} type="video/webm" />
+                <source src={homeVideoMp4} type="video/mp4" />
+              </video>
+              {this.state.dialog}
             </div>
-          </div>
-          <video playsInline muted loop autoPlay className={styles.backgroundVideo} id="background-video">
-            <source src={homeVideoWebM} type="video/webm" />
-            <source src={homeVideoMp4} type="video/mp4" />
-          </video>
-          {this.state.dialog}
-        </div>
-      </IntlProvider>
+          </IntlProvider>
+        )}
+      </AudioContext.Consumer>
     );
   }
 }
diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js
index 8a9ca35e28edb3d99c3685e483e3f031c755c557..4df0fa184e0aa781ef06e4dbcf24031b3de6d7f0 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 { AudioContext } from "../AudioContext";
 
 import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png";
 import styles from "../assets/stylesheets/hub-create.scss";
@@ -173,75 +174,118 @@ class HubCreatePanel extends Component {
     const environmentThumbnail = this._getEnvironmentThumbnail(this.state.environmentIndex);
 
     return (
-      <div>
-        <form onSubmit={this.createHub}>
-          <div className={styles.createPanel}>
-            <div className={styles.form}>
-              <div className={styles.environment}>
-                <div className={styles.picker}>
-                  <img className={styles.image} srcSet={environmentThumbnail.srcset} />
-                  <div className={styles.labels}>
-                    <div className={styles.header}>
-                      <span className={styles.title}>{environmentTitle}</span>
-                      {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>
-                        ) : (
-                          <span className={styles.author}>
-                            <FormattedMessage id="home.environment_author_by" />
-                            <span>{environmentAuthor.name}</span>
-                          </span>
-                        ))}
-                      {environmentAuthor &&
-                        environmentAuthor.organization &&
-                        (environmentAuthor.organization.url ? (
-                          <a href={environmentAuthor.organization.url} rel="noopener noreferrer" className={styles.org}>
-                            <span>{environmentAuthor.organization.name}</span>
-                          </a>
-                        ) : (
-                          <span className={styles.org}>
-                            <span>{environmentAuthor.organization.name}</span>
-                          </span>
-                        ))}
-                    </div>
-                    <div className={styles.footer}>
-                      <button onClick={this.showCustomSceneDialog} className={styles.customButton}>
-                        <FormattedMessage id="home.room_create_options" />
-                      </button>
+      <AudioContext.Consumer>
+        {audio => (
+          <div>
+            <form onSubmit={this.createHub}>
+              <div className={styles.createPanel}>
+                <div className={styles.form}>
+                  <div className={styles.environment}>
+                    <div className={styles.picker}>
+                      <img className={styles.image} srcSet={environmentThumbnail.srcset} />
+                      <div className={styles.labels}>
+                        <div className={styles.header}>
+                          <span className={styles.title}>{environmentTitle}</span>
+                          {environmentAuthor &&
+                            environmentAuthor.name &&
+                            (environmentAuthor.url ? (
+                              <a
+                                href={environmentAuthor.url}
+                                rel="noopener noreferrer"
+                                className={styles.author}
+                                onMouseEnter={audio.onMouseEnter}
+                                onMouseLeave={audio.onMouseLeave}
+                              >
+                                <FormattedMessage id="home.environment_author_by" />
+                                <span>{environmentAuthor.name}</span>
+                              </a>
+                            ) : (
+                              <span className={styles.author}>
+                                <FormattedMessage id="home.environment_author_by" />
+                                <span>{environmentAuthor.name}</span>
+                              </span>
+                            ))}
+                          {environmentAuthor &&
+                            environmentAuthor.organization &&
+                            (environmentAuthor.organization.url ? (
+                              <a
+                                href={environmentAuthor.organization.url}
+                                rel="noopener noreferrer"
+                                className={styles.org}
+                                onMouseEnter={audio.onMouseEnter}
+                                onMouseLeave={audio.onMouseLeave}
+                              >
+                                <span>{environmentAuthor.organization.name}</span>
+                              </a>
+                            ) : (
+                              <span className={styles.org}>
+                                <span>{environmentAuthor.organization.name}</span>
+                              </span>
+                            ))}
+                        </div>
+                        <div className={styles.footer}>
+                          <button
+                            onClick={this.showCustomSceneDialog}
+                            onMouseEnter={audio.onMouseEnter}
+                            onMouseLeave={audio.onMouseLeave}
+                            className={styles.customButton}
+                          >
+                            <FormattedMessage id="home.room_create_options" />
+                          </button>
+                        </div>
+                      </div>
+                      <div className={styles.controls}>
+                        <button
+                          className={styles.prev}
+                          type="button"
+                          tabIndex="1"
+                          onClick={this.setToPreviousEnvironment}
+                          onMouseEnter={audio.onMouseEnter}
+                          onMouseLeave={audio.onMouseLeave}
+                        >
+                          <FontAwesomeIcon icon={faAngleLeft} />
+                        </button>
+
+                        <button
+                          className={styles.next}
+                          type="button"
+                          tabIndex="2"
+                          onClick={this.setToNextEnvironment}
+                          onMouseEnter={audio.onMouseEnter}
+                          onMouseLeave={audio.onMouseLeave}
+                        >
+                          <FontAwesomeIcon icon={faAngleRight} />
+                        </button>
+                      </div>
                     </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} />
+                  <div className={styles.container}>
+                    <button
+                      type="submit"
+                      tabIndex="5"
+                      className={styles.submitButton}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      <FormattedMessage id="home.room_create_button" />
                     </button>
                   </div>
                 </div>
               </div>
-              <div className={styles.container}>
-                <button type="submit" tabIndex="5" className={styles.submitButton}>
-                  <FormattedMessage id="home.room_create_button" />
-                </button>
-              </div>
-            </div>
+            </form>
+            {this.state.showCustomSceneDialog && (
+              <CreateRoomDialog
+                onClose={() => this.setState({ showCustomSceneDialog: false })}
+                onCustomScene={(name, url) => {
+                  this.setState({ showCustomSceneDialog: false, name: name, customSceneUrl: url }, () =>
+                    this.createHub()
+                  );
+                }}
+              />
+            )}
           </div>
-        </form>
-        {this.state.showCustomSceneDialog && (
-          <CreateRoomDialog
-            onClose={() => this.setState({ showCustomSceneDialog: false })}
-            onCustomScene={(name, url) => {
-              this.setState({ showCustomSceneDialog: false, name: name, customSceneUrl: url }, () => this.createHub());
-            }}
-          />
         )}
-      </div>
+      </AudioContext.Consumer>
     );
   }
 }
diff --git a/src/react-components/invite-dialog.js b/src/react-components/invite-dialog.js
index 5a9e4446a6486dfb5fe049bb913885b76a469756..238775205e35b9a166ba4a66b114be039fb0ade7 100644
--- a/src/react-components/invite-dialog.js
+++ b/src/react-components/invite-dialog.js
@@ -4,6 +4,7 @@ import copy from "copy-to-clipboard";
 import classNames from "classnames";
 import { FormattedMessage } from "react-intl";
 
+import { AudioContext } from "../AudioContext";
 import styles from "../assets/stylesheets/invite-dialog.scss";
 
 function pad(num, size) {
@@ -45,43 +46,72 @@ export default class InviteDialog extends Component {
     const shareLink = `hub.link/${entryCodeString}`;
 
     return (
-      <div className={styles.dialog}>
-        <div className={styles.attachPoint} />
-        <div className={styles.close} onClick={() => this.props.onClose()}>
-          <span>×</span>
-        </div>
-        <div>
-          <FormattedMessage id="invite.enter_via" />
-          <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer">
-            hub.link
-          </a>
-          <FormattedMessage id="invite.and_enter_code" />
-        </div>
-        <div className={styles.code}>
-          {entryCodeString.split("").map((d, i) => (
-            <div className={classNames({ [styles.digit]: true, [styles[`digit_${i}`]]: true })} key={`link_code_${i}`}>
-              {d}
+      <AudioContext.Consumer>
+        {audio => (
+          <div className={styles.dialog}>
+            <div className={styles.attachPoint} />
+            <div
+              className={styles.close}
+              onClick={() => this.props.onClose()}
+              onMouseEnter={audio.onMouseEnter}
+              onMouseLeave={audio.onMouseLeave}
+            >
+              <span>×</span>
             </div>
-          ))}
-        </div>
-        <div>
-          <FormattedMessage id="invite.or_visit" />
-        </div>
-        <div className={styles.domain}>
-          <input type="text" readOnly onFocus={e => e.target.select()} value={shareLink} />
-        </div>
-        <div className={styles.buttons}>
-          <button className={styles.linkButton} onClick={this.copyClicked.bind(this, "https://" + shareLink)}>
-            <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span>
-          </button>
-          {this.props.allowShare &&
-            navigator.share && (
-              <button className={styles.linkButton} onClick={this.shareClicked.bind(this, "https://" + shareLink)}>
-                <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span>
+            <div>
+              <FormattedMessage id="invite.enter_via" />
+              <a
+                href="https://hub.link"
+                target="_blank"
+                className={styles.hubLinkLink}
+                rel="noopener noreferrer"
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                hub.link
+              </a>
+              <FormattedMessage id="invite.and_enter_code" />
+            </div>
+            <div className={styles.code}>
+              {entryCodeString.split("").map((d, i) => (
+                <div
+                  className={classNames({ [styles.digit]: true, [styles[`digit_${i}`]]: true })}
+                  key={`link_code_${i}`}
+                >
+                  {d}
+                </div>
+              ))}
+            </div>
+            <div>
+              <FormattedMessage id="invite.or_visit" />
+            </div>
+            <div className={styles.domain}>
+              <input type="text" readOnly onFocus={e => e.target.select()} value={shareLink} />
+            </div>
+            <div className={styles.buttons}>
+              <button
+                className={styles.linkButton}
+                onClick={this.copyClicked.bind(this, "https://" + shareLink)}
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span>
               </button>
-            )}
-        </div>
-      </div>
+              {this.props.allowShare &&
+                navigator.share && (
+                  <button
+                    className={styles.linkButton}
+                    onClick={this.shareClicked.bind(this, "https://" + shareLink)}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span>
+                  </button>
+                )}
+            </div>
+          </div>
+        )}
+      </AudioContext.Consumer>
     );
   }
 }
diff --git a/src/react-components/invite-team-dialog.js b/src/react-components/invite-team-dialog.js
index a2b3bcd50c40331487642e0d103b402d464bd5f9..65ccc55caa679b691b8cf14d24e122324f6ce512 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 { AudioContext } from "../AudioContext";
 
 export default class InviteTeamDialog extends Component {
   static propTypes = {
@@ -30,9 +31,18 @@ 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>
+              <AudioContext.Consumer>
+                {audio => (
+                  <button
+                    className="invite-team-form__action-button"
+                    onClick={this.inviteClicked}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <span>{this.state.inviteButtonText}</span>
+                  </button>
+                )}
+              </AudioContext.Consumer>
             </div>
           </div>
         </div>
diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js
index d3f45051d33736e65ecde5805eee3bb465959e07..7461ad08618719efddd0b9ea1b77b0d5f6bd2e28 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 { AudioContext } from "../AudioContext";
 
 import styles from "../assets/stylesheets/link-dialog.scss";
 
@@ -16,56 +17,77 @@ export default class LinkDialog extends Component {
     const { linkCode } = this.props;
 
     return (
-      <div className={styles.dialog}>
-        <div className={styles.close} onClick={() => this.props.onClose()}>
-          <span>×</span>
-        </div>
-        <div>
-          {!linkCode && (
+      <AudioContext.Consumer>
+        {audio => (
+          <div className={styles.dialog}>
+            <div
+              className={styles.close}
+              onClick={() => this.props.onClose()}
+              onMouseEnter={audio.onMouseEnter}
+              onMouseLeave={audio.onMouseLeave}
+            >
+              <span>×</span>
+            </div>
             <div>
-              <div className={classNames("loading-panel", styles.codeLoadingPanel)}>
-                <div className="loader-wrap">
-                  <div className="loader">
-                    <div className="loader-center" />
+              {!linkCode && (
+                <div>
+                  <div className={classNames("loading-panel", styles.codeLoadingPanel)}>
+                    <div className="loader-wrap">
+                      <div className="loader">
+                        <div className="loader-center" />
+                      </div>
+                    </div>
                   </div>
                 </div>
-              </div>
-            </div>
-          )}
-          {linkCode && (
-            <div className={styles.contents}>
-              <img className={styles.imageHeader} src={LinkDialogHeader} />
-              <div className={styles.header}>
-                <FormattedMessage id="link.connect_headset" />
-              </div>
-              <div>
-                <FormattedMessage id="link.in_your_browser" />
-              </div>
-              <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer">
-                hub.link
-              </a>
-              <div>
-                <FormattedMessage id="link.enter_code" />
-              </div>
+              )}
               {linkCode && (
-                <div className={styles.code}>
-                  {linkCode.split("").map((d, i) => (
-                    <span className={styles.digit} key={`link_code_${i}`}>
-                      {d}
-                    </span>
-                  ))}
+                <div className={styles.contents}>
+                  <img className={styles.imageHeader} src={LinkDialogHeader} />
+                  <div className={styles.header}>
+                    <FormattedMessage id="link.connect_headset" />
+                  </div>
+                  <div>
+                    <FormattedMessage id="link.in_your_browser" />
+                  </div>
+                  <a
+                    href="https://hub.link"
+                    className={styles.domain}
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    hub.link
+                  </a>
+                  <div>
+                    <FormattedMessage id="link.enter_code" />
+                  </div>
+                  {linkCode && (
+                    <div className={styles.code}>
+                      {linkCode.split("").map((d, i) => (
+                        <span className={styles.digit} key={`link_code_${i}`}>
+                          {d}
+                        </span>
+                      ))}
+                    </div>
+                  )}
+                  <div className={styles.keepOpen}>
+                    <FormattedMessage id="link.do_not_close" />
+                  </div>
+                  <button
+                    className={styles.closeButton}
+                    onClick={() => this.props.onClose()}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <FormattedMessage id="link.cancel" />
+                  </button>
                 </div>
               )}
-              <div className={styles.keepOpen}>
-                <FormattedMessage id="link.do_not_close" />
-              </div>
-              <button className={styles.closeButton} onClick={() => this.props.onClose()}>
-                <FormattedMessage id="link.cancel" />
-              </button>
             </div>
-          )}
-        </div>
-      </div>
+          </div>
+        )}
+      </AudioContext.Consumer>
     );
   }
 }
diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js
index 5253de0130b73f10d7e71ada313fa01057e0aaf1..2d53455412b5e40c048ecc849c248dd2c08d2e4a 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 { AudioContext } from "../AudioContext";
 
 const MAX_DIGITS = 6;
 const MAX_LETTERS = 4;
@@ -135,127 +136,155 @@ class LinkRoot extends Component {
 
     return (
       <IntlProvider locale={lang} messages={messages}>
-        <div className={styles.link}>
-          <div className={styles.linkContents}>
-            <div className={styles.logo}>
-              <img src="../assets/images/hub-preview-light-no-shadow.png" />
-            </div>
-            {this.state.entered.length === this.maxAllowedChars() && (
-              <div className={classNames("loading-panel", styles.codeLoadingPanel)}>
-                <div className="loader-wrap">
-                  <div className="loader">
-                    <div className="loader-center" />
-                  </div>
+        <AudioContext.Consumer>
+          {audio => (
+            <div className={styles.link}>
+              <div className={styles.linkContents}>
+                <div className={styles.logo}>
+                  <img src="../assets/images/hub-preview-light-no-shadow.png" />
                 </div>
-              </div>
-            )}
-
-            <div className={styles.enteredContents}>
-              <div className={styles.header}>
-                <FormattedMessage
-                  id={
-                    this.state.failedAtLeastOnce
-                      ? "link.try_again"
-                      : "link.link_page_header_" + (!this.state.isAlphaMode ? "entry" : "headset")
-                  }
-                />
-              </div>
+                {this.state.entered.length === this.maxAllowedChars() && (
+                  <div className={classNames("loading-panel", styles.codeLoadingPanel)}>
+                    <div className="loader-wrap">
+                      <div className="loader">
+                        <div className="loader-center" />
+                      </div>
+                    </div>
+                  </div>
+                )}
 
-              <div className={styles.entered}>
-                <input
-                  className={styles.charInput}
-                  type={this.state.isAlphaMode ? "text" : "tel"}
-                  pattern="[0-9A-I]*"
-                  value={this.state.entered}
-                  onChange={ev => {
-                    if (!this.state.isAlphaMode && ev.target.value.match(/[a-z]/i)) {
-                      this.setState({ isAlphaMode: true });
-                    }
-
-                    this.setState({ entered: ev.target.value.toUpperCase() });
-                  }}
-                />
-              </div>
+                <div className={styles.enteredContents}>
+                  <div className={styles.header}>
+                    <FormattedMessage
+                      id={
+                        this.state.failedAtLeastOnce
+                          ? "link.try_again"
+                          : "link.link_page_header_" + (!this.state.isAlphaMode ? "entry" : "headset")
+                      }
+                    />
+                  </div>
 
-              <div className={styles.enteredFooter}>
-                {!this.state.isAlphaMode && (
-                  <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
-                )}
-                {!this.state.isAlphaMode && (
-                  <span>
-                    <a href="#" onClick={() => this.toggleMode()}>
-                      <FormattedMessage id="link.linking_a_headset" />
-                    </a>
-                  </span>
-                )}
-              </div>
-            </div>
+                  <div className={styles.entered}>
+                    <input
+                      className={styles.charInput}
+                      type={this.state.isAlphaMode ? "text" : "tel"}
+                      pattern="[0-9A-I]*"
+                      value={this.state.entered}
+                      onChange={ev => {
+                        if (!this.state.isAlphaMode && ev.target.value.match(/[a-z]/i)) {
+                          this.setState({ isAlphaMode: true });
+                        }
+
+                        this.setState({ entered: ev.target.value.toUpperCase() });
+                      }}
+                    />
+                  </div>
 
-            <div className={styles.keypad}>
-              {(this.state.isAlphaMode
-                ? ["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>
-              ))}
-              <button
-                className={classNames(styles.keypadButton, styles.keypadToggleMode)}
-                onTouchStart={() => this.toggleMode()}
-                onClick={() => {
-                  if (!hasTouchEvents) this.toggleMode();
-                }}
-              >
-                {this.state.isAlphaMode ? "123" : "ABC"}
-              </button>
-              {!this.state.isAlphaMode && (
-                <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>
-              )}
-              <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>
-            </div>
+                  <div className={styles.enteredFooter}>
+                    {!this.state.isAlphaMode && (
+                      <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
+                    )}
+                    {!this.state.isAlphaMode && (
+                      <span>
+                        <a
+                          href="#"
+                          onClick={() => this.toggleMode()}
+                          onMouseEnter={audio.onMouseEnter}
+                          onMouseLeave={audio.onMouseLeave}
+                        >
+                          <FormattedMessage id="link.linking_a_headset" />
+                        </a>
+                      </span>
+                    )}
+                  </div>
+                </div>
+
+                <div className={styles.keypad}>
+                  {(this.state.isAlphaMode
+                    ? ["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)}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      {d}
+                    </button>
+                  ))}
+                  <button
+                    className={classNames(styles.keypadButton, styles.keypadToggleMode)}
+                    onTouchStart={() => this.toggleMode()}
+                    onClick={() => {
+                      if (!hasTouchEvents) this.toggleMode();
+                    }}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    {this.state.isAlphaMode ? "123" : "ABC"}
+                  </button>
+                  {!this.state.isAlphaMode && (
+                    <button
+                      disabled={this.state.entered.length === this.maxAllowedChars()}
+                      className={classNames(styles.keypadButton, styles.keypadZeroButton)}
+                      onTouchStart={() => this.addToEntry(0)}
+                      onClick={() => {
+                        if (!hasTouchEvents) this.addToEntry(0);
+                      }}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      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();
+                    }}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    ⌫
+                  </button>
+                </div>
 
-            <div className={styles.footer}>
-              <div
-                className={styles.linkHeadsetFooterLink}
-                style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }}
-              >
-                <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
-                <span>
-                  <a href="#" onClick={() => this.toggleMode()}>
-                    <FormattedMessage id="link.linking_a_headset" />
-                  </a>
-                </span>
+                <div className={styles.footer}>
+                  <div
+                    className={styles.linkHeadsetFooterLink}
+                    style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }}
+                  >
+                    <img
+                      onClick={() => this.toggleMode()}
+                      src={HeadsetIcon}
+                      className={styles.headsetIcon}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    />
+                    <span>
+                      <a
+                        href="#"
+                        onClick={() => this.toggleMode()}
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="link.linking_a_headset" />
+                      </a>
+                    </span>
+                  </div>
+                </div>
               </div>
             </div>
-          </div>
-        </div>
+          )}
+        </AudioContext.Consumer>
       </IntlProvider>
     );
   }
diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js
index 307ae73b0dac97d5b50908d5773f28bc2953aec1..9dff6f08d3b95fecb860bf1d212b7dd905315b3b 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 { AudioContext } from "../AudioContext";
 
 class ProfileEntryPanel extends Component {
   static propTypes = {
@@ -75,55 +76,73 @@ class ProfileEntryPanel extends Component {
     const { formatMessage } = this.props.intl;
 
     return (
-      <div className={styles.profileEntry}>
-        <form onSubmit={this.saveStateAndFinish} className={styles.form}>
-          <div className={classNames([styles.box, styles.darkened])}>
-            <label htmlFor="#profile-entry-display-name" className={styles.title}>
-              <FormattedMessage id="profile.header" />
-            </label>
-            <input
-              id="profile-entry-display-name"
-              className={styles.formFieldText}
-              value={this.state.displayName}
-              onFocus={e => e.target.select()}
-              onChange={e => this.setState({ displayName: e.target.value })}
-              required
-              spellCheck="false"
-              pattern={SCHEMA.definitions.profile.properties.displayName.pattern}
-              title={formatMessage({ id: "profile.display_name.validation_warning" })}
-              ref={inp => (this.nameInput = inp)}
-            />
-            <div className={styles.avatarSelectorContainer}>
-              <div className="loading-panel">
-                <div className="loader-wrap">
-                  <div className="loader">
-                    <div className="loader-center" />
+      <AudioContext.Consumer>
+        {audio => (
+          <div className={styles.profileEntry}>
+            <form onSubmit={this.saveStateAndFinish} className={styles.form}>
+              <div className={classNames([styles.box, styles.darkened])}>
+                <label htmlFor="#profile-entry-display-name" className={styles.title}>
+                  <FormattedMessage id="profile.header" />
+                </label>
+                <input
+                  id="profile-entry-display-name"
+                  className={styles.formFieldText}
+                  value={this.state.displayName}
+                  onFocus={e => e.target.select()}
+                  onChange={e => this.setState({ displayName: e.target.value })}
+                  required
+                  spellCheck="false"
+                  pattern={SCHEMA.definitions.profile.properties.displayName.pattern}
+                  title={formatMessage({ id: "profile.display_name.validation_warning" })}
+                  ref={inp => (this.nameInput = inp)}
+                />
+                <div className={styles.avatarSelectorContainer}>
+                  <div className="loading-panel">
+                    <div className="loader-wrap">
+                      <div className="loader">
+                        <div className="loader-center" />
+                      </div>
+                    </div>
                   </div>
+                  <iframe
+                    className={styles.avatarSelector}
+                    src={`/avatar-selector.html#avatar_id=${this.state.avatarId}`}
+                    ref={ifr => (this.avatarSelector = ifr)}
+                  />
+                </div>
+                <input
+                  className={styles.formSubmit}
+                  type="submit"
+                  value={formatMessage({ id: "profile.save" })}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                />
+                <div className={styles.links}>
+                  <a
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    href="https://github.com/mozilla/hubs/blob/master/TERMS.md"
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <FormattedMessage id="profile.terms_of_use" />
+                  </a>
+                  <a
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <FormattedMessage id="profile.privacy_notice" />
+                  </a>
                 </div>
               </div>
-              <iframe
-                className={styles.avatarSelector}
-                src={`/avatar-selector.html#avatar_id=${this.state.avatarId}`}
-                ref={ifr => (this.avatarSelector = ifr)}
-              />
-            </div>
-            <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} />
-            <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>
-            </div>
+            </form>
+            <img className={styles.logo} src={hubLogo} />
           </div>
-        </form>
-        <img className={styles.logo} src={hubLogo} />
-      </div>
+        )}
+      </AudioContext.Consumer>
     );
   }
 }
diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e6de88966a6c751fc5b2941d1a8ef95ed2b5296
--- /dev/null
+++ b/src/react-components/profile-info-header.js
@@ -0,0 +1,49 @@
+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 { AudioContext } from "../AudioContext";
+
+export const ProfileInfoHeader = props => (
+  <AudioContext.Consumer>
+    {audio => (
+      <div className="profile-info-header">
+        <div className="profile-info-header__menu-buttons">
+          <button
+            className="profile-info-header__menu-buttons__menu-button"
+            onClick={props.onClickHelp}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          >
+            <i className="profile-info-header__menu-buttons__menu-button__icon">
+              <FontAwesomeIcon icon={faQuestion} />
+            </i>
+          </button>
+        </div>
+        <div className="profile-info-header__profile_display_name">
+          <img
+            src="../assets/images/account.svg"
+            onClick={props.onClickName}
+            className="profile-info-header__icon"
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          />
+          <div
+            onClick={props.onClickName}
+            title={props.name}
+            onMouseEnter={audio.onMouseEnter}
+            onMouseLeave={audio.onMouseLeave}
+          >
+            {props.name}
+          </div>
+        </div>
+      </div>
+    )}
+  </AudioContext.Consumer>
+);
+
+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..12649646cf06865f46980b3d728dba01882822d6 100644
--- a/src/react-components/report-dialog.js
+++ b/src/react-components/report-dialog.js
@@ -1,31 +1,59 @@
 import React, { Component } from "react";
 import DialogContainer from "./dialog-container.js";
+import { AudioContext } from "../AudioContext";
 
 export default class ReportDialog extends Component {
   render() {
     return (
-      <DialogContainer title="Report an Issue" {...this.props}>
-        <span>
-          <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>.
-          </p>
-          <p>
-            You can also find us in{" "}
-            <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer">
-              #social
-            </a>{" "}
-            on the{" "}
-            <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer">
-              WebVR Slack
-            </a>.
-          </p>
-        </span>
-      </DialogContainer>
+      <AudioContext.Consumer>
+        {audio => (
+          <DialogContainer title="Report an Issue" {...this.props}>
+            <span>
+              <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"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  GitHub Issue
+                </a>{" "}
+                or e-mail us for support at{" "}
+                <a href="mailto:hubs@mozilla.com" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                  hubs@mozilla.com
+                </a>
+                .
+              </p>
+              <p>
+                You can also find us in{" "}
+                <a
+                  href="https://webvr.slack.com/messages/social"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  #social
+                </a>{" "}
+                on the{" "}
+                <a
+                  href="https://webvr-slack.herokuapp.com/"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  WebVR Slack
+                </a>
+                .
+              </p>
+            </span>
+          </DialogContainer>
+        )}
+      </AudioContext.Consumer>
     );
   }
 }
diff --git a/src/react-components/safari-dialog.js b/src/react-components/safari-dialog.js
index de96bfbf4545e6496e97a3e0dab81593adad7519..60fd609efab23ff56fa643d0ba42faf86b9bf36d 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 { AudioContext } from "../AudioContext";
 
 export default class SafariDialog extends Component {
   state = {
@@ -27,9 +28,18 @@ 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>
+              <AudioContext.Consumer>
+                {audio => (
+                  <button
+                    className="invite-form__action-button"
+                    onClick={onCopyClicked}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <span>{this.state.copyLinkButtonText}</span>
+                  </button>
+                )}
+              </AudioContext.Consumer>
             </div>
           </div>
         </div>
diff --git a/src/react-components/scene-ui.js b/src/react-components/scene-ui.js
index ec04e095e135bac8bb85beff8c27c7cf2b6b3ae9..65af8699649ec67523b36fe160157f8b11065e5a 100644
--- a/src/react-components/scene-ui.js
+++ b/src/react-components/scene-ui.js
@@ -7,6 +7,7 @@ import styles from "../assets/stylesheets/scene-ui.scss";
 import hubLogo from "../assets/images/hub-preview-white.png";
 import { getReticulumFetchUrl } from "../utils/phoenix-utils";
 import { generateHubName } from "../utils/name-generation";
+import { AudioContext } from "../AudioContext";
 
 import { lang, messages } from "../utils/i18n";
 
@@ -68,34 +69,43 @@ class SceneUI extends Component {
   render() {
     return (
       <IntlProvider locale={lang} messages={messages}>
-        <div className={styles.ui}>
-          <div
-            className={classNames({
-              [styles.screenshot]: true,
-              [styles.screenshotHidden]: this.props.sceneLoaded
-            })}
-          >
-            {this.state.showScreenshot && <img src={this.props.sceneScreenshotURL} />}
-          </div>
-          <div className={styles.whiteOverlay} />
-          <div className={styles.grid}>
-            <div className={styles.mainPanel}>
-              <a href="/" className={styles.logo}>
-                <img src={hubLogo} />
-              </a>
-              <div className={styles.logoTagline}>
-                <FormattedMessage id="scene.logo_tagline" />
+        <AudioContext.Consumer>
+          {audio => (
+            <div className={styles.ui}>
+              <div
+                className={classNames({
+                  [styles.screenshot]: true,
+                  [styles.screenshotHidden]: this.props.sceneLoaded
+                })}
+              >
+                {this.state.showScreenshot && <img src={this.props.sceneScreenshotURL} />}
+              </div>
+              <div className={styles.whiteOverlay} />
+              <div className={styles.grid}>
+                <div className={styles.mainPanel}>
+                  <a
+                    href="/"
+                    className={styles.logo}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <img src={hubLogo} />
+                  </a>
+                  <div className={styles.logoTagline}>
+                    <FormattedMessage id="scene.logo_tagline" />
+                  </div>
+                  <button onClick={this.createRoom} onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                    <FormattedMessage id="scene.create_button" />
+                  </button>
+                </div>
+              </div>
+              <div className={styles.info}>
+                <div className={styles.name}>{this.props.sceneName}</div>
+                <div className={styles.attribution}>{this.props.sceneAttribution}</div>
               </div>
-              <button onClick={this.createRoom}>
-                <FormattedMessage id="scene.create_button" />
-              </button>
             </div>
-          </div>
-          <div className={styles.info}>
-            <div className={styles.name}>{this.props.sceneName}</div>
-            <div className={styles.attribution}>{this.props.sceneAttribution}</div>
-          </div>
-        </div>
+          )}
+        </AudioContext.Consumer>
       </IntlProvider>
     );
   }
diff --git a/src/react-components/slack-dialog.js b/src/react-components/slack-dialog.js
index 5a024855ba2a206ebcc4a8a80e92e1460daa4b7a..8b70d8504ce9eeedd1e8b197b14f10557e10053d 100644
--- a/src/react-components/slack-dialog.js
+++ b/src/react-components/slack-dialog.js
@@ -1,32 +1,56 @@
 import React, { Component } from "react";
 import DialogContainer from "./dialog-container.js";
+import { AudioContext } from "../AudioContext";
 
 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{" "}
-            <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer">
-              WebVR Slack
-            </a>{" "}
-            in the{" "}
-            <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer">
-              #social
-            </a>{" "}
-            channel.<br />
-            VR meetups every Friday at noon PDT!
-          </p>
-          <p>
-            Or, tweet at{" "}
-            <a href="https://twitter.com/mozillareality" target="_blank" rel="noopener noreferrer">
-              @mozillareality
-            </a>{" "}
-            on Twitter.
-          </p>
-        </span>
+        <AudioContext.Consumer>
+          {audio => (
+            <span>
+              <p>Want to join the conversation?</p>
+              <p>
+                Join us on the{" "}
+                <a
+                  href="https://webvr-slack.herokuapp.com/"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  WebVR Slack
+                </a>{" "}
+                in the{" "}
+                <a
+                  href="https://webvr.slack.com/messages/social"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  #social
+                </a>{" "}
+                channel.
+                <br />
+                VR meetups every Friday at noon PDT!
+              </p>
+              <p>
+                Or, tweet at{" "}
+                <a
+                  href="https://twitter.com/mozillareality"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  @mozillareality
+                </a>{" "}
+                on Twitter.
+              </p>
+            </span>
+          )}
+        </AudioContext.Consumer>
       </DialogContainer>
     );
   }
diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js
index 73c2a1e871da4c1f4185daf1a85efaaaac80eb62..0fec42f87ca6accec6cc7cf07fab958ca6344eb2 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 { AudioContext } from "../AudioContext";
 
 import { lang, messages } from "../utils/i18n";
 import AutoExitWarning from "./auto-exit-warning";
@@ -133,6 +134,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: {
+        onMouseEnter: () => {
+          scene.emit("play_sound-hud_mouse_enter");
+        },
+        onMouseLeave: () => {
+          //          scene.emit("play_sound-hud_mouse_leave");
+        }
+      }
+    });
   }
 
   componentWillUnmount() {
@@ -532,25 +544,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.hubEntryCode;
@@ -572,35 +594,69 @@ class UIRoot extends Component {
     if (this.props.roomUnavailableReason === "closed") {
       // TODO i18n, due to links and markup
       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 />
-          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>.
-        </div>
+        <AudioContext.Consumer>
+          {audio => (
+            <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"
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                Terms of Use
+              </a>
+              .<br />
+              If you have questions, contact us at{" "}
+              <a href="mailto:hubs@mozilla.com" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                hubs@mozilla.com
+              </a>
+              .<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"
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                GitHub
+              </a>
+              .
+            </div>
+          )}
+        </AudioContext.Consumer>
       );
     } else if (this.props.platformUnsupportedReason === "no_data_channels") {
       // TODO i18n, due to links and markup
       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>.
-        </div>
+        <AudioContext.Consumer>
+          {audio => (
+            <div>
+              Your browser does not support{" "}
+              <a
+                href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility"
+                rel="noreferrer noopener"
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                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"
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                Download Firefox
+              </a>
+              .
+            </div>
+          )}
+        </AudioContext.Consumer>
       );
     } else {
       const reason = this.props.roomUnavailableReason || this.props.platformUnsupportedReason;
@@ -610,9 +666,17 @@ class UIRoot extends Component {
           <FormattedMessage id={exitSubtitleId} />
           <p />
           {this.props.roomUnavailableReason && (
-            <div>
-              You can also <a href="/">create a new room</a>.
-            </div>
+            <AudioContext.Consumer>
+              {audio => (
+                <div>
+                  You can also{" "}
+                  <a href="/" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}>
+                    create a new room
+                  </a>
+                  .
+                </div>
+              )}
+            </AudioContext.Consumer>
           )}
         </div>
       );
@@ -656,25 +720,38 @@ class UIRoot extends Component {
 
   renderEntryStartPanel = () => {
     return (
-      <div className={entryStyles.entryPanel}>
-        <div className={entryStyles.title}>{this.props.hubName}</div>
+      <AudioContext.Consumer>
+        {audio => (
+          <div className={entryStyles.entryPanel}>
+            <div className={entryStyles.title}>{this.props.hubName}</div>
+
+            <div className={entryStyles.center}>
+              <div
+                onClick={() => this.setState({ showProfileEntry: true })}
+                className={entryStyles.profileName}
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                <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>
+            </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 className={entryStyles.buttonContainer}>
+              <button
+                className={classNames([entryStyles.actionButton, entryStyles.wideButton])}
+                onClick={() => this.handleStartEntry()}
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                <FormattedMessage id="entry.enter-room" />
+              </button>
+            </div>
           </div>
-        </div>
-
-        <div className={entryStyles.buttonContainer}>
-          <button
-            className={classNames([entryStyles.actionButton, entryStyles.wideButton])}
-            onClick={() => this.handleStartEntry()}
-          >
-            <FormattedMessage id="entry.enter-room" />
-          </button>
-        </div>
-      </div>
+        )}
+      </AudioContext.Consumer>
     );
   };
 
@@ -707,9 +784,18 @@ class UIRoot extends Component {
           )}
           <DeviceEntryButton onClick={() => this.attemptLink()} isInHMD={this.props.availableVREntryTypes.isInHMD} />
           {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && (
-            <div className={entryStyles.secondary} onClick={this.enterVR}>
-              <FormattedMessage id="entry.cardboard" />
-            </div>
+            <AudioContext.Consumer>
+              {audio => (
+                <div
+                  className={entryStyles.secondary}
+                  onClick={this.enterVR}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <FormattedMessage id="entry.cardboard" />
+                </div>
+              )}
+            </AudioContext.Consumer>
           )}
           {screenSharingCheckbox}
         </div>
@@ -733,36 +819,55 @@ class UIRoot extends Component {
 
   renderMicPanel = () => {
     return (
-      <div className="mic-grant-panel">
-        <div className="mic-grant-panel__grant-container">
-          <div className="mic-grant-panel__title">
-            <FormattedMessage
-              id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-title" : "audio.granted-title"}
-            />
-          </div>
-          <div className="mic-grant-panel__subtitle">
-            <FormattedMessage
-              id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"}
-            />
-          </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>
-            ) : (
-              <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>
-            )}
+      <AudioContext.Consumer>
+        {audio => (
+          <div className="mic-grant-panel">
+            <div className="mic-grant-panel__grant-container">
+              <div className="mic-grant-panel__title">
+                <FormattedMessage
+                  id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-title" : "audio.granted-title"}
+                />
+              </div>
+              <div className="mic-grant-panel__subtitle">
+                <FormattedMessage
+                  id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"}
+                />
+              </div>
+              <div className="mic-grant-panel__button-container">
+                {this.state.entryStep == ENTRY_STEPS.mic_grant ? (
+                  <button
+                    className="mic-grant-panel__button"
+                    onClick={this.onMicGrantButton}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" />
+                  </button>
+                ) : (
+                  <button
+                    className="mic-grant-panel__button"
+                    onClick={this.onMicGrantButton}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" />
+                  </button>
+                )}
+              </div>
+              <div className="mic-grant-panel__next-container">
+                <button
+                  className={classNames("mic-grant-panel__next")}
+                  onClick={this.onMicGrantButton}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <FormattedMessage id="audio.granted-next" />
+                </button>
+              </div>
+            </div>
           </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>
-      </div>
+        )}
+      </AudioContext.Consumer>
     );
   };
 
@@ -774,104 +879,121 @@ class UIRoot extends Component {
     const speakerClip = { clip: `rect(${this.state.tonePlaying ? 0 : maxLevelHeight}px, 111px, 111px, 0px)` };
     const subtitleId = AFRAME.utils.device.isMobile() ? "audio.subtitle-mobile" : "audio.subtitle-desktop";
     return (
-      <div className="audio-setup-panel">
-        <div>
-          <div className="audio-setup-panel__title">
-            <FormattedMessage id="audio.title" />
-          </div>
-          <div className="audio-setup-panel__subtitle">
-            {(AFRAME.utils.device.isMobile() || this.state.enterInVR) && <FormattedMessage id={subtitleId} />}
-          </div>
-          <div className="audio-setup-panel__levels">
-            <div className="audio-setup-panel__levels__icon">
-              <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={micClip}
-              />
-              {this.state.audioTrack ? (
-                <img
-                  src="../assets/images/mic_level.png"
-                  srcSet="../assets/images/mic_level@2x.png 2x"
-                  className="audio-setup-panel__levels__icon-part"
-                />
-              ) : (
-                <img
-                  src="../assets/images/mic_denied.png"
-                  srcSet="../assets/images/mic_denied@2x.png 2x"
-                  className="audio-setup-panel__levels__icon-part"
-                />
+      <AudioContext.Consumer>
+        {audio => (
+          <div className="audio-setup-panel">
+            <div>
+              <div className="audio-setup-panel__title">
+                <FormattedMessage id="audio.title" />
+              </div>
+              <div className="audio-setup-panel__subtitle">
+                {(AFRAME.utils.device.isMobile() || this.state.enterInVR) && <FormattedMessage id={subtitleId} />}
+              </div>
+              <div className="audio-setup-panel__levels">
+                <div className="audio-setup-panel__levels__icon">
+                  <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={micClip}
+                  />
+                  {this.state.audioTrack ? (
+                    <img
+                      src="../assets/images/mic_level.png"
+                      srcSet="../assets/images/mic_level@2x.png 2x"
+                      className="audio-setup-panel__levels__icon-part"
+                    />
+                  ) : (
+                    <img
+                      src="../assets/images/mic_denied.png"
+                      srcSet="../assets/images/mic_denied@2x.png 2x"
+                      className="audio-setup-panel__levels__icon-part"
+                    />
+                  )}
+                </div>
+                <div
+                  className="audio-setup-panel__levels__icon"
+                  onClick={this.playTestTone}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <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>
+              </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}
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  >
+                    {this.state.micDevices.map(d => (
+                      <option key={d.deviceId} value={d.deviceId}>
+                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                        {d.label}
+                      </option>
+                    ))}
+                  </select>
+                  <img
+                    className="audio-setup-panel__device-chooser__mic-icon"
+                    src="../assets/images/mic_small.png"
+                    srcSet="../assets/images/mic_small@2x.png 2x"
+                  />
+                  <img
+                    className="audio-setup-panel__device-chooser__dropdown-arrow"
+                    src="../assets/images/dropdown_arrow.png"
+                    srcSet="../assets/images/dropdown_arrow@2x.png 2x"
+                  />
+                </div>
+              )}
+              {this.shouldShowHmdMicWarning() && (
+                <div className="audio-setup-panel__hmd-mic-warning">
+                  <img
+                    src="../assets/images/warning_icon.png"
+                    srcSet="../assets/images/warning_icon@2x.png 2x"
+                    className="audio-setup-panel__hmd-mic-warning__icon"
+                  />
+                  <span className="audio-setup-panel__hmd-mic-warning__label">
+                    <FormattedMessage id="audio.hmd-mic-warning" />
+                  </span>
+                </div>
               )}
             </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>
-          </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}
+            <div className="audio-setup-panel__enter-button-container">
+              <button
+                className="audio-setup-panel__enter-button"
+                onClick={this.onAudioReadyButton}
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
               >
-                {this.state.micDevices.map(d => (
-                  <option key={d.deviceId} value={d.deviceId}>
-                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{d.label}
-                  </option>
-                ))}
-              </select>
-              <img
-                className="audio-setup-panel__device-chooser__mic-icon"
-                src="../assets/images/mic_small.png"
-                srcSet="../assets/images/mic_small@2x.png 2x"
-              />
-              <img
-                className="audio-setup-panel__device-chooser__dropdown-arrow"
-                src="../assets/images/dropdown_arrow.png"
-                srcSet="../assets/images/dropdown_arrow@2x.png 2x"
-              />
-            </div>
-          )}
-          {this.shouldShowHmdMicWarning() && (
-            <div className="audio-setup-panel__hmd-mic-warning">
-              <img
-                src="../assets/images/warning_icon.png"
-                srcSet="../assets/images/warning_icon@2x.png 2x"
-                className="audio-setup-panel__hmd-mic-warning__icon"
-              />
-              <span className="audio-setup-panel__hmd-mic-warning__label">
-                <FormattedMessage id="audio.hmd-mic-warning" />
-              </span>
+                <FormattedMessage id="audio.enter-now" />
+              </button>
             </div>
-          )}
-        </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>
-        </div>
-      </div>
+          </div>
+        )}
+      </AudioContext.Consumer>
     );
   };
 
@@ -897,14 +1019,23 @@ class UIRoot extends Component {
 
     if (this.state.entryPanelCollapsed && !this.isWaitingForAutoExit()) {
       dialogContents = (
-        <div className={entryStyles.entryDialog}>
-          <div>&nbsp;</div>
-          <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}>
-            <i>
-              <FontAwesomeIcon icon={faChevronUp} />
-            </i>
-          </button>
-        </div>
+        <AudioContext.Consumer>
+          {audio => (
+            <div className={entryStyles.entryDialog}>
+              <div>&nbsp;</div>
+              <button
+                onClick={() => this.setState({ entryPanelCollapsed: false })}
+                className={entryStyles.expand}
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                <i>
+                  <FontAwesomeIcon icon={faChevronUp} />
+                </i>
+              </button>
+            </div>
+          )}
+        </AudioContext.Consumer>
       );
     } else {
       dialogContents = this.isWaitingForAutoExit() ? (
@@ -915,11 +1046,20 @@ 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>
+            <AudioContext.Consumer>
+              {audio => (
+                <button
+                  onClick={() => this.setState({ entryPanelCollapsed: true })}
+                  className={entryStyles.collapse}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <i>
+                    <FontAwesomeIcon icon={faChevronDown} />
+                  </i>
+                </button>
+              )}
+            </AudioContext.Consumer>
           )}
           {startPanel}
           {devicePanel}
@@ -940,111 +1080,143 @@ class UIRoot extends Component {
 
     return (
       <IntlProvider locale={lang} messages={messages}>
-        <div className={styles.ui}>
-          {this.state.dialog}
-
-          {this.state.showProfileEntry && (
-            <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} />
-          )}
-
-          {(!entryFinished || this.isWaitingForAutoExit()) && (
-            <div className={styles.uiDialog}>
-              <div className={dialogBoxContentsClassNames}>{dialogContents}</div>
-            </div>
-          )}
+        <AudioContext.Provider value={this.state.audioContext}>
+          <AudioContext.Consumer>
+            {audio => (
+              <div className={styles.ui}>
+                {this.state.dialog}
+
+                {this.state.showProfileEntry && (
+                  <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} />
+                )}
+
+                {(!entryFinished || this.isWaitingForAutoExit()) && (
+                  <div className={styles.uiDialog}>
+                    <div className={dialogBoxContentsClassNames}>{dialogContents}</div>
+                  </div>
+                )}
+
+                <div
+                  className={classNames({
+                    [styles.inviteContainer]: true,
+                    [styles.inviteContainerBelowHud]: entryFinished,
+                    [styles.inviteContainerInverted]: this.state.showInviteDialog
+                  })}
+                >
+                  {!showVREntryButton && (
+                    <button
+                      className={classNames({
+                        [styles.hideSmallScreens]: this.props.occupantCount > 1 && entryFinished
+                      })}
+                      onClick={() => this.toggleInviteDialog()}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      <FormattedMessage id="entry.invite-others-nag" />
+                    </button>
+                  )}
+                  {!showVREntryButton &&
+                    this.props.occupantCount > 1 &&
+                    entryFinished && (
+                      <button
+                        onClick={this.onMiniInviteClicked}
+                        className={styles.inviteMiniButton}
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <span>
+                          {this.state.miniInviteActivated
+                            ? navigator.share
+                              ? "sharing..."
+                              : "copied!"
+                            : "hub.link/" + this.props.hubEntryCode}
+                        </span>
+                      </button>
+                    )}
+                  {showVREntryButton && (
+                    <button
+                      onClick={() => this.props.scene.enterVR()}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    >
+                      <FormattedMessage id="entry.return-to-vr" />
+                    </button>
+                  )}
+                  {this.state.showInviteDialog && (
+                    <InviteDialog
+                      allowShare={!this.props.availableVREntryTypes.isInHMD}
+                      entryCode={this.props.hubEntryCode}
+                      onClose={() => this.setState({ showInviteDialog: false })}
+                    />
+                  )}
+                </div>
 
-          <div
-            className={classNames({
-              [styles.inviteContainer]: true,
-              [styles.inviteContainerBelowHud]: entryFinished,
-              [styles.inviteContainerInverted]: this.state.showInviteDialog
-            })}
-          >
-            {!showVREntryButton && (
-              <button
-                className={classNames({ [styles.hideSmallScreens]: this.props.occupantCount > 1 && entryFinished })}
-                onClick={() => this.toggleInviteDialog()}
-              >
-                <FormattedMessage id="entry.invite-others-nag" />
-              </button>
-            )}
-            {!showVREntryButton &&
-              this.props.occupantCount > 1 &&
-              entryFinished && (
-                <button onClick={this.onMiniInviteClicked} className={styles.inviteMiniButton}>
-                  <span>
-                    {this.state.miniInviteActivated
-                      ? navigator.share
-                        ? "sharing..."
-                        : "copied!"
-                      : "hub.link/" + this.props.hubEntryCode}
-                  </span>
+                {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}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <i>
+                    <FontAwesomeIcon icon={faQuestion} />
+                  </i>
                 </button>
-              )}
-            {showVREntryButton && (
-              <button onClick={() => this.props.scene.enterVR()}>
-                <FormattedMessage id="entry.return-to-vr" />
-              </button>
-            )}
-            {this.state.showInviteDialog && (
-              <InviteDialog
-                allowShare={!this.props.availableVREntryTypes.isInHMD}
-                entryCode={this.props.hubEntryCode}
-                onClose={() => this.setState({ showInviteDialog: false })}
-              />
-            )}
-          </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 className={styles.presenceInfo}>
-            <FontAwesomeIcon icon={faUsers} />
-            <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span>
-          </div>
-
-          {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" />
-                  </button>
+                <div
+                  className={styles.presenceInfo}
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  <FontAwesomeIcon icon={faUsers} />
+                  <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span>
                 </div>
-              )}
-              {!this.isWaitingForAutoExit() && (
-                <TwoDHUD.BottomHUD
-                  onCreateObject={() => this.showCreateObjectDialog()}
-                  showPhotoPicker={AFRAME.utils.device.isMobile()}
-                  onMediaPicked={this.createObject}
-                />
-              )}
-            </div>
-          ) : null}
-        </div>
+
+                {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()}
+                          onMouseEnter={audio.onMouseEnter}
+                          onMouseLeave={audio.onMouseLeave}
+                        >
+                          <FormattedMessage id="entry.invite-team-nag" />
+                        </button>
+                      </div>
+                    )}
+                    {!this.isWaitingForAutoExit() && (
+                      <TwoDHUD.BottomHUD
+                        onCreateObject={() => this.showCreateObjectDialog()}
+                        showPhotoPicker={AFRAME.utils.device.isMobile()}
+                        onMediaPicked={this.createObject}
+                      />
+                    )}
+                  </div>
+                ) : null}
+              </div>
+            )}
+          </AudioContext.Consumer>
+        </AudioContext.Provider>
       </IntlProvider>
     );
   }
diff --git a/src/react-components/updates-dialog.js b/src/react-components/updates-dialog.js
index 71d732c2404a366a7bb5e858ba0609bc6d241473..2437d6f229b85dc73133989e2aa018232f7d671e 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 { AudioContext } from "../AudioContext";
 
 export default class UpdatesDialog extends Component {
   static propTypes = {
@@ -40,37 +41,57 @@ export default class UpdatesDialog extends Component {
 
     return (
       <DialogContainer {...other}>
-        <span>
-          <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">
-                <input
-                  className="mailing-list-form__privacy_checkbox"
-                  type="checkbox"
-                  required
-                  value={this.state.mailingListPrivacy}
-                  onChange={e => this.setState({ mailingListPrivacy: e.target.checked })}
-                />
-                <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>
-                </span>
-              </label>
-              <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" />
-            </div>
-          </form>
-        </span>
+        <AudioContext.Consumer>
+          {audio => (
+            <span>
+              <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"
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  />
+                  <label className="mailing-list-form__privacy">
+                    <input
+                      className="mailing-list-form__privacy_checkbox"
+                      type="checkbox"
+                      required
+                      value={this.state.mailingListPrivacy}
+                      onChange={e => this.setState({ mailingListPrivacy: e.target.checked })}
+                      onMouseEnter={audio.onMouseEnter}
+                      onMouseLeave={audio.onMouseLeave}
+                    />
+                    <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/"
+                        onMouseEnter={audio.onMouseEnter}
+                        onMouseLeave={audio.onMouseLeave}
+                      >
+                        <FormattedMessage id="mailing_list.privacy_link" />
+                      </a>
+                    </span>
+                  </label>
+                  <input
+                    className="mailing-list-form__submit"
+                    type="submit"
+                    value="Sign Up Now"
+                    onMouseEnter={audio.onMouseEnter}
+                    onMouseLeave={audio.onMouseLeave}
+                  />
+                </div>
+              </form>
+            </span>
+          )}
+        </AudioContext.Consumer>
       </DialogContainer>
     );
   }
diff --git a/src/react-components/webvr-recommend-dialog.js b/src/react-components/webvr-recommend-dialog.js
index 264245b273f69e3ba6c17a893362d3f0f715c2a7..edfd4af5d64de6fc658de59336e7c1e42e05b182 100644
--- a/src/react-components/webvr-recommend-dialog.js
+++ b/src/react-components/webvr-recommend-dialog.js
@@ -1,22 +1,39 @@
 import React, { Component } from "react";
 import DialogContainer from "./dialog-container.js";
+import { AudioContext } from "../AudioContext";
 
 export default class WebVRRecommendDialog extends Component {
   render() {
     return (
       <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>
-          <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>.
-          </p>
-        </div>
+        <AudioContext.Consumer>
+          {audio => (
+            <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"
+                onMouseEnter={audio.onMouseEnter}
+                onMouseLeave={audio.onMouseLeave}
+              >
+                Download Firefox
+              </a>
+              <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"
+                  onMouseEnter={audio.onMouseEnter}
+                  onMouseLeave={audio.onMouseLeave}
+                >
+                  WebVR Rocks
+                </a>
+                .
+              </p>
+            </div>
+          )}
+        </AudioContext.Consumer>
       </DialogContainer>
     );
   }