It's just a file manager and command runner.
(Hey GitHub people! This repository has been moved to Codeberg and what you are reading is simply a mirror repository. I'm not publishing releases on GitHub anymore because it's annoying to do it on 2 places, so if you are looking for releases, please visit the Coderberg repository)
"cls" is also a abbreviation of a Cantonese swear, 痴撚線 (meaning "f*cking crazy"). It was the mental state of a Javascript dev trying to make a soundboard in Rust without prior Rust programming experience.
- Global Hotkey (also works on Wayland)
- Play a wide range of audio files
- If
ffmpegis available, it can play any fileffmpegsupports - Default audio file decoding is provided by
symphonium(symphonia)
- If
- Directory tabs
- Waves (>=2.0.0)
- Create waveforms consisting of multiple waves
- Play for a dynamic length
- Dialogs (>=3.0.0)
- Create a list of audio files that plays in quick succession, simulating dialogs in video games
- Play for a dynamic length
- Cross-platform (>=3.1.0)
- If
pacatis available, it will play sound to a virtual sink for easy routing - If
audio-deviceis supplied, it will play sound into the specified audio device (a bit janky) - Otherwise, it will play to the default output
- If
As I told you, this is just a file manager and command runner.
It only supports terminal user interface (TUI). If you want a soundboard for other platforms or providers similar to this, I recommend Soundux.
For Windows users, only PowerShell works with cls.
ffmpeg- If installed, the program will play any file it supports
pulseaudio/pipewire-pulse/ anything to providespactlandpacat- If installed, the program will create a virtual sink and loopback for easy audio routing
If you are using an Arch Linux-based distro, it's already available on the AUR as cls-rs.
For other distros, the binary file is provided in release, or you can build it yourself.
Binary files are also available for Windows user under x86_64 architecture. For any other operating system or architecture, please build it yourself.
You'll need cargo for this.
git clone https://codeberg.org/NorthWestWind/cls
cd cls
cargo build --releaseThe binary file will be available as target/release/cls
This soundboard is a very bare-bones program. It is built to fit my existing streaming configuration, so I'll run you down on how to set it up.
First of all, this program creates a null-sink from PulseAudio that is called cls, and all sounds are played into this sink. A loopback of the sink is automatically created to the current audio output. There's a good chance you don't (just) want this. To change that, press c in the TUI.
Starting from v1.2.0, settings have been implemented to automatically load loopback modules. To configure them, simply open the settings menu by pressing c in the TUI. On the right, you will see Loopback 1, Loopback 2 and Loopback Default (only in >=2.0.0).
Loopback 1andLoopback 2will create a loopback to the spcified sinks- This can be a monitor to a source
Loopback Defaultis a boolean variable that determines if theclssink will play to the current audio output
Before v2.0.0, changing these settings requires a reload (exiting and relaunching) of the program.
If you want the sound to be played somewhere, you'll have to load a few modules.
Redirecting the sink to a source is unreasonably complicated. I wish there was a single module that does it all.
We need to create an input mixer.
# create another null-sink for mixing mic and cls
pactl load-module module-null-sink sink_name=input_mixer
# redirect cls to the input mixer
pactl load-module module-loopback source=cls.monitor sink=input_mixer latency_msec=10
# redirect mic to the input mixer
pactl load-module module-loopback source=@DEFAULT_SOURCE@ sink=iput_mixer latency_msec=10
# redirect input mixer to an actual source
pactl load-module module-remap-source master=input_mixer.monitor source_name=micThe last step may not be necessary if you intend to use this for the browser, but other applications may not pick up the input mixer monitor as an input.
In comparison, redirecting a sink to another sink is much easier.
# redirect cls to the default speaker
pactl load-module module-loopback source=cls.monitor sink=@DEFAULT_SINK@Or, if you need it in a separated sink (for streaming and recording purposes like me):
# create a sink that also plays into the speaker
pactl load-module module-remap-sink master=@DEFAULT_SINK@ sink_name=out_sfx
# redirect cls to that sink
pactl load-module module-loopback source=cls.monitor sink=out_sfxOn the other hand, TUI should be rather intuitive. Press ? to bring up the help menu for instructions.

Hidden & Edit Mode
You can also run cls without the TUI, so it will only handle global hotkeys.
It is basically a read-only mode.
cls --hiddenThere is currently no indicator to tell you if it is running, so you will have to kill it using another command.
cls exitYou can also enter "write-only" mode, where you cannot play any files, but edit configurations.
cls --edit # or `cls -e`cls is now single-instance, meaning only one cls process can play sound files.
If there's already another instance running, cls will automatically enter edit mode.
To simplify, first time launching cls will be normal, but second time will be forced --edit.
If you want to run multiple instance for some reason (e.g. multi-user system), simply set the TMPDIR environment variable to something different when launching. See Socket Control to see how TMPDIR is used.
Starting from v1.1.0, cls can communicate using a Unix socket located at $TMPDIR/cls/cls.sock.
This allows cls --hidden to be controlled without the use of global inputs, and causes cls to be single-instance.
The subcommands of cls can be used to communicate with this primary instance.
Here's the current list of subcommands implemented:
cls exit: Terminate the instancecls reload-config: Reloads the configuration file into memorycls add-tab <dir>: Adds a new directory tabcls delete-tab [--index <index>] [--path <path>] [--name <name>]: Deletes a directory tab.- If no options are specified, the selected tab is deleted.
--indexwill delete the tab at that index (starting from 0).--pathwill delete the tab that matches the full path.--namewill delete the tab that matches the base name (i.e. the name you see in theTabsblock).
cls reload-tab [--index <index>] [--path <path>] [--name <name>]: Reloads a directory tab. Options serve the same functions as indelete-tab.cls play <path>: Plays a file.cls play-id <id>: Plays a file by its user-defined ID.cls play-wave <id>: Plays a waveform by its user-defined ID.cls play-dialog <id>: Plays a dialog by its user-defined ID.cls stop: Stops all the audio files that are playing.cls stop-wave <id>: Stops a waveform by its user-defined ID.cls stop-dialog <id>: Stops a dialog by its user-defined ID.cls set-volume <volume> [--increment] [--path <path>]: Set the volume for theclssink or a specific file.- If
--incrementis NOT set, the volume is set to<volume>provided. - If
--incrementis SET, the volume is incremented by<volume>(can be negative). - If
--pathis provided, volume is set for the file instead of the sink.
- If
The wave feature is added in version 2.0.0.

To toggle the wave menu, press 'w'. Users can create waveforms and play them for a dynamic amount of time.
It is highly recommended to add a global hotkey for waves. As long as you are holding the set of keys down, the waveform will keep playing.
The dialog feature is added in version 3.0.0.

To toggle the dialog menu, press 't'. Dialogs are meant to mimick dialog sound effects used in video games. Users can setup a list of sound files that will be played with a certain interval.
It is highly recommended to add a global hotkey for dialogs as well. As long as you are holding the set of keys down, the dialog will keep playing.
I was using another soundboard - Soundux. It was a solid program, until everyone wants to switch to Wayland. Due to Soundux being Electron-based, global hotkeys doesn't work on Wayland.
I would wait for them to add support for that, if it weren't for them going through a major rewrite. From the progress page, it hasn't been updated for like 2 years. Therefore, I took it as a challenge to write my own soundboard (and also learn Rust).
That's why this soundboard is structured like Soundux, except a bunch of missing features, such as passthrough, which I probably won't implement.