marketplace/audio

Keyboard Foley

Add Mechanical, Typewriter, or Soft Pop sounds to ordinary typing without Rebind Link. F8 mutes; F9 changes the theme.

@rebindv0.1.0free

More install options
$rebind install @rebind/keyboard-foley

Gallery

Keyboard Foley preview

Readme

Add sound to ordinary typing with three original synthesized themes. Letters, numbers, punctuation, and Tab use three alternating samples per theme. Space, Enter, and Backspace each have a distinct sound.

ThemeSound design
MechanicalShort switch clicks, a low case resonance, and a light return tick.
TypewriterA sharper impact, metallic resonances, and a two-part mechanism sound.
Soft PopRounded, falling-pitch pops with a muted noise attack.

Start and control

Requires Rebind Engine 3.5.0 or newer on Windows or macOS. Rebind Link is not required. See platform support for capture requirements.

Open the package's main.luau in Rebind and start it from the editor toolbar. For development, run rebind run library/keyboard-foley from the repository root. Open Options to change the theme, volume, or shortcut-sound setting. Settings save automatically and apply while running.

  • F8 mutes or unmutes this package.
  • F9 cycles Mechanical, Typewriter, and Soft Pop.
  • Volume starts at 35%. Set it to 0% for silence.
  • Sound with Ctrl, Alt, Cmd, or Win is off by default. Shifted typing still makes sound.

F8 and F9 are reserved while the script runs, including while muted. Other keys keep their normal behavior. Modifier keys by themselves, navigation keys, function keys, and mouse buttons are silent. Holding a key does not produce a sound for each operating-system repeat.

Stop the script from the editor or press Ctrl+C in its development terminal. To stop every script and release held input, press Left Ctrl + Left Alt + K. Keyboard Foley itself never presses or holds a key.

Samples and privacy

The 18 samples are original procedural synthesis, not recordings or borrowed sound packs. tools/synth.py generates 48 kHz, mono, 16-bit PCM WAV files using only Python's standard library. To regenerate them from the repository root:

python3 library/keyboard-foley/tools/synth.py

The runtime opens only its bundled sounds. It does not save typed text, keep keystroke history, use the clipboard, execute commands, or access the network. Sound plays for captured typing in any application, including password fields; the script cannot identify sensitive fields. Mute it when sound is unwanted.

Limits and verification

At most 12 sounds overlap. If another begins at that limit, the oldest stops. Finished sound handles are released, and muting or stopping ends active sounds. An audio error mutes the package and records the reason in Logs. Correct the problem and press F8 to retry.

Playback uses the host's default audio output. Output-device changes and audio latency need live verification on each platform. Sound quality cannot be established by source checks alone. Listing artwork is an illustration, not a screenshot or evidence of execution.

Reviews

No reviews yet. Write a review in the app.

Source

Version 0.1.0 (current)

main.luau

local cfg = UI.Schema({
  enabled = UI.Toggle(true, {
    label = "Typing sounds (F8)",
    group = "Keyboard Foley",
    tooltip = "F8 mutes or unmutes this package. Typing is not remapped.",
  }),
  theme = UI.Select("Mechanical", { "Mechanical", "Typewriter", "Soft Pop" }, {
    label = "Sound theme (F9)",
    group = "Sound",
    tooltip = "F9 cycles the three original synthesized themes.",
  }),
  volume = UI.Slider(35, {
    label = "Volume",
    group = "Sound",
    min = 0,
    max = 100,
    step = 5,
    suffix = "%",
  }),
  shortcuts = UI.Toggle(false, {
    label = "Sound with Ctrl, Alt, Cmd, or Win",
    group = "Typing",
    tooltip = "Off keeps these shortcuts quiet. Shifted typing still makes sound.",
  }),
})

local NEXT_THEME = {
  Mechanical = "Typewriter",
  Typewriter = "Soft Pop",
  ["Soft Pop"] = "Mechanical",
}
local FOLDERS = {
  Mechanical = "mechanical",
  Typewriter = "typewriter",
  ["Soft Pop"] = "soft-pop",
}
local SPECIAL = {
  Space = "space",
  Enter = "enter",
  KpEnter = "enter",
  Backspace = "backspace",
}
local TYPING_KEYS = {
  Tab = true,
  Minus = true,
  Equal = true,
  LeftBrace = true,
  RightBrace = true,
  Backslash = true,
  Semicolon = true,
  Apostrophe = true,
  Grave = true,
  Comma = true,
  Period = true,
  Slash = true,
  KpPlus = true,
  KpMinus = true,
  KpMultiply = true,
  KpDivide = true,
  KpDot = true,
}
local MAX_VOICES = 12
local voices = {}
local variation = 0
local last_theme = cfg.theme
local last_volume = nil

local function volume()
  local value = cfg.volume
  if type(value) ~= "number" or value ~= value then
    value = 35
  end
  return math.max(0, math.min(100, value)) / 100
end

local function silence()
  for _, sound in voices do
    sound:Stop()
  end
  voices = {}
end

local function prune()
  for i = #voices, 1, -1 do
    if not voices[i]:IsPlaying() then
      table.remove(voices, i)
    end
  end
end

local function update_sounds()
  if cfg.enabled ~= true or cfg.theme ~= last_theme then
    silence()
    last_theme = cfg.theme
  end
  prune()
  local level = volume()
  if level ~= last_volume then
    for _, sound in voices do
      sound:SetVolume(level)
    end
    last_volume = level
  end
end

Bind("F8", function()
  cfg.enabled = cfg.enabled ~= true
  silence()
  UI.Notify(
    cfg.enabled and "Keyboard Foley unmuted." or "Keyboard Foley muted."
  )
end)

Bind("F9", function()
  cfg.theme = NEXT_THEME[cfg.theme] or "Mechanical"
  silence()
  UI.Notify("Keyboard Foley: " .. cfg.theme .. ".")
end)

-- observers cannot swallow input and do not require exclusive key capture.
function OnInputDown(key)
  update_sounds()
  if cfg.enabled ~= true then
    return
  end
  local level = volume()
  if level == 0 then
    return
  end
  local sample = SPECIAL[key]
  if not sample then
    if
      not TYPING_KEYS[key]
      and not key:match("^[A-Z0-9]$")
      and not key:match("^Kp[0-9]$")
    then
      return
    end
    variation = variation % 3 + 1
    sample = "key-" .. variation
  end
  if cfg.shortcuts ~= true then
    local modifiers = Input.GetModifiers()
    if modifiers.ctrl or modifiers.alt or modifiers.win then
      return
    end
  end
  if #voices == MAX_VOICES then
    table.remove(voices, 1):Stop()
  end
  local folder = FOLDERS[cfg.theme] or "mechanical"
  local ok, sound =
    pcall(Audio.Play, "samples/" .. folder .. "/" .. sample .. ".wav", {
      volume = level,
    })
  if not ok then
    cfg.enabled = false
    silence()
    Log.Error("Keyboard Foley could not play a sample: " .. tostring(sound))
    UI.Notify(
      "Keyboard Foley muted after an audio error. Check Logs, then press F8 to retry.",
      "error"
    )
    return
  end
  -- retain handles until playback ends; collecting one stops its native sound.
  table.insert(voices, sound)
end

function OnTick()
  update_sounds()
end

function OnStop()
  silence()
end

19 files without text previews