Skip to content

Loudness ​

createLoudness turns the mic into a level from 0 to 3 that your game can read. Audio goes to an analyser and no further: Earshot doesn't record it, send it or play it back.

Mic input ​

ts
import { createLoudness } from "@debuffjp/earshot";

const loud = createLoudness(ctx);
const off = loud.onLevel((level) => { if (level >= 2) monster.hear(player); });

// From a button: the player stays quiet for 3 seconds while it measures the room.
const { ok, error } = await loud.enableMic();

loud.level; // 0 to 3
loud.micStatus; // "off" | "calibrating" | "on"
loud.disableMic();
off();

createLoudness needs an AudioContext, so resume it from the same tap or key press. enableMic() never rejects. A denied mic returns { ok: false, error }, where error is the browser's error name, and press keeps working.

Browsers can ignore what you ask for, so read loud.applied for the settings you got. Echo cancellation, noise suppression and auto gain are off by default because they flatten loudness. Pass browserProcessing: true to ask for them.

The mic needs HTTPS or localhost, and an iframe needs allow="microphone". Opening it can change game audio on iPhones: see Limits.

Calibration ​

enableMic() opens the mic, listens for calibrateMs (3000 by default) and resolves when it's done. The 20th percentile of those readings becomes the noise floor. Tell the player to stay quiet first, and show micStatus while it's "calibrating".

If the room changes, measure it again:

ts
await loud.recalibrate(); // Ask the player to stay quiet first.
loud.overFloorDb; // dB above the noise floor, or null with no calibrated mic

Levels ​

Levels are relative to the noise floor, so loud and quiet rooms behave alike.

LevelAbove the noise floor
0Under 10 dB
110 dB or more
220 dB or more
330 dB or more
OptionMeaningDefault
stepsdB above the floor for levels 1, 2 and 3[10, 20, 30]
attackMsHow long a louder sound must last before the level rises150
releaseMsHow long it must stay quiet before the level falls400
hysteresisDbA level holds until the sound drops this far under its step5

loud.level is the louder of the mic and press. onLevel fires only when that changes, and returns a function that removes the callback.

To feed in your own readings, createLevelDetector is the same logic with no mic:

ts
import { createLevelDetector } from "@debuffjp/earshot";

const detector = createLevelDetector({ steps: [6, 12, 18] });
detector.calibrate([-62, -60, -61, -60]); // dB readings taken while quiet
detector.update(-40, 16); // dB and milliseconds since the last reading

update returns 0 until calibrate has run.

heardDb ​

To make walls and distance matter in your game rules, work out how loud the noise is where the monster stands. The threshold is yours to tune; 6 dB felt right in our demo.

ts
import { computeMix, heardDb, wallsOnGrid, DEFAULT_MATERIALS, DEFAULT_PARAMS } from "@debuffjp/earshot";

const walls = wallsOnGrid(tiles, player, monster); // Both in tile units.
const mix = computeMix(player, monster, walls, DEFAULT_PARAMS, DEFAULT_MATERIALS);
if (loud.level >= 2 && heardDb(loud.level, mix) >= 6) monster.wake();

heardDb(level, mix) adds the mix's gain, in dB, to the level's loudness. Level 0 and an out-of-range mix both give -Infinity. If you changed steps, pass matching values as the third argument, with level 0 first:

ts
heardDb(loud.level, mix, [-Infinity, 6, 12, 18]);

Accessibility rules ​

  • Always give players a key or button through press().
  • Don't require words or long strain.
  • Tell players what the mic is for before asking.

press holds a level until you change it, and everything the mic does works without one:

ts
addEventListener("keydown", (e) => { if (e.code === "KeyF") loud.press(2); });
addEventListener("keyup", (e) => { if (e.code === "KeyF") loud.press(0); });