WRITINGUW-037
I Open-Sourced KeyEcho: Fast-Responsive Keyboard Sounds with Every Keystroke Using Tauri
TL;DR: I've recently open-sourced KeyEcho, an application that listens to keyboard events locally and plays pleasant typing sounds. Built with Tauri and Rust, it is cross-platform, lightweight, and designed for fast keystroke response. This article covers the technical decisions behind it.
I love the sound of mechanical keyboard keys and have tried many keyboards. Currently, I am using a capacitive keyboard, but it lacks a lot of the fun.
Before building KeyEcho, I tried a few keyboard sound tools and open-source sound libraries. That exploration gave me a clear personal target:
- It should work across Windows, macOS, and Linux.
- It should stay small enough to feel like a utility, not a full desktop suite.
- It should use local keyboard events only for sound playback.
- It should make custom sounds and future sound packs practical.
So I created KeyEcho. It uses Tauri and benefits from Rust, resulting in low memory usage, small builds, and fast audio response.
This post is a historical technical write-up from the first open-source release. The current product page and sound-pack roadmap live at keyecho.app.
KeyEcho Performance
KeyEcho's low CPU and memory usage during rapid key presses mainly comes from native event listening, Rust's runtime characteristics, and decoded audio caching.
Technical Overview
As mentioned earlier, it is built using the Tauri framework - unlike Electron, which bundles Chromium and Node.js, consuming a lot of memory and disk space. Instead, Tauri uses each platform's WebView to implement desktop applications, with Rust as the backend. This allows it to achieve high performance with minimal disk space usage.
Let's break down the composition of KeyEcho:
Backend
The backend uses Rust, where the main logic is to listen to user keystrokes and play the corresponding key sounds:
Listening to Keystrokes:
In this directory, you can see the native keyboard event listening logic for the three major platforms (Windows, MacOS, Linux). Thanks to Rust's friendly interaction with the C language, native keyboard listening can be implemented with minimal code and minimal use of unsafe code.
This logic is inspired by rdev, a Rust library for listening to and sending keyboard and mouse events. This is also what rustyvibes uses internally. KeyEcho does not use this library directly because it cannot customize the selection of listening to either keyboard or mouse events, and it contains a lot of logic that KeyEcho doesn't need. Therefore, I chose to implement it myself to achieve pure native keyboard event listening logic.
Playing Sounds Based on Keystrokes:
The logic here is to obtain an audio file and a key's start time and duration within the audio file, so that when a user keystroke is detected, the corresponding audio segment can be found and played.
Audio Files and Configuration Files:
These audio files are sourced from the existing Mechvibes repository, which contains many mechanical keyboard audio configurations. I processed them further to suit KeyEcho. For example, I chose the WAV format for audio files, which is lossless and requires no decompression, minimizing processing delay.
WAV Audio File Decoding and Playback:
After obtaining the entire WAV file, I used Symphonia for decoding and rodio for playback.
In fact, rodio can handle this task alone - decoding, seeking to the specified audio position, and playing for the specified duration. However, after reviewing its source code, I found that it performs additional tasks for greater compatibility, which consume more CPU and memory than I wanted. Therefore, I used its dependency, Symphonia, for lower-level decoding to obtain the decoded resources at a lower cost and then handed them over to rodio for playback.
Decoded Resource Caching:
Since we often press many repeat keys while using the keyboard, re-decoding each time is unnecessary. Using caching techniques allows us to quickly retrieve it from memory. Here, I chose LRU Cache, which is well-suited for this scenario. It automatically removes keys we rarely use when the capacity is full.
Frontend
For the frontend, I chose Vue3 + radix-vue + tailwindcss. This is a compromise to further reduce the size. I know using Svelte or Solid might reduce the size even further, but for future scalability and a better UI library, I chose Vue3 (yes, I gave up on React long ago due to its size).
This is where Tauri's charm comes into play; it allows defining commands in Rust and exposing them to the frontend, enabling simple communication between the frontend and the Rust backend via the @tauri-apps/api. I used tauri-specta to achieve type-safe Rust command exports to TypeScript.
Currently, the frontend only provides features like audio download selection, but there is much more we can do. Your valuable PRs are welcome.
If you found this helpful, consider subscribing to my newsletter for weekly web development insights and updates. Thanks for reading!