Frequently Questioned Answers
-
General
- Why the name "Benben"?
- Why did you turn Benben into a general music player?
- What formats are planned for future versions?
- Why the AGPLv3 license?
- Will Benben every be ported to other operating systems?
- Will you add support for format $OTHER_FORMAT?
- Why a TUI instead of a GUI?
- Can Benben help me make a pizza?
- Troubleshooting
-
Configuration
- How many equalizer bands can I have?
- I set
max-loops
to 1 and the song doesn't loop... - What are some good sizes for
buffer-size
? - What audio backend should I use?
- I have a song-specific config file, and I want it to match multiple files. How can I do that?
- I have a song that can be matched by two song-specific config files. Which one takes effect?
- Internals
General
Why the name "Benben"?
The program is actually named after Tsukumo Benben from Touhou Project, a character who was transformed from a musical instrument. Given her connection to music, and Remilia's obsession with Touhou, it made sense to name the program after her.
Why did you turn Benben into a general music player?
Benben started life as a VGM player. But as Remilia used it, she found that the interface worked better for her than other places, and she kept wishing she could listen to all her other formats with it as well. Thus Benben became a general-purpose music player starting with v0.5.0.
Benben will always have first-class support for VGM files, however.
What formats are planned for future versions?
Support for WavPack and Quite OK Audio Format are planned for whatever comes after Benben v0.5.0. C64 SID, Atari SAP, and True Audio (TTA) may also appear eventually.
Why the AGPLv3 license?
To be as hostile towards any potential commercial use of its codebase while still being GPL-like. If there hadn't been a need to link against GPL code, then Benben probably would have been written under the Cooperative Source License instead.
Will Benben every be ported to other operating systems?
Benben is primarily a Linux program, and meant for use on desktop/laptop computers. General Unix support also a goal, and releases are also tested on Dragonfly BSD. If Crystal ever gets support for Haiku and/or Plan 9, then it will likely be ported to those platforms as well. There are zero plans to port it to Windows or Mac, though you may be able to at least get it working on a Mac since it is still Unix under the hood. This is, however, totally unsupported.
Will you add support for format $OTHER_FORMAT?
Maybe. It depends on of there's a standalone library that can decode the format, if it's an open and free format, and if the library is open source. Chances are even better if there's a native Crystal library for it already, as native Crystal code is preferred over C bindings, though this isn't required.
An exception is AAC, which will never be supported.
Why a TUI instead of a GUI?
Four reasons, really:
- TUIs use a lot less memory and are very fast.
- GTK started to suck starting with Gtk 4 (at least IMO), and GTK+ 2.x is EOL.
- Qt is a pain to use from any language that isn't C++, and I've had more trouble themeing Qt apps than things written in other toolkits.
- TUIs are awesome, more portable, and can look really cool.
Also, Benben was partially inspired by a program I used to use on MS-DOS called MPXPLAY, which I loved using as a kid.
Can Benben help me make a pizza?
Technically yes, it will inspire and entertain you as you cook it, so the time passes faster.
Troubleshooting
The audio plays way too fast!
Are you using the PortAudio backend? If you are, try using PulseAudio or libao instead. Sometimes the PortAudio backend will have synchronization problems.
I have a custom skin, but the colors look all wrong.
You probably have a skin that uses 24-bit color values, so the first thing to do
is ensure that your terminal emulator supports 24-bit colors. If it does, then
the TUI toolkit probably just isn't detecting the support correctly. Try
setting the environment variable COLORTERM=truecolor
.
All the text looks wrong/missing.
Benben's default configuration assumes you have a terminal that supports Unicode, so first check that your terminal does indeed support this. If it does, it's possible that you have a font that doesn't have Unicode support, or lacks the characters you need.
If you don't want to change to a different terminal, and it's only the VU meters
that look incorrect, then your only option is to change the VU characters. This
can be done in the configuration file in
the vu-meter
section.
Benben crashed and I don't see an error message.
Check in ~/.local/share/benben/stderr.log
and see if an error message appeared
in that file. Note that this file is cleared each time you run Benben.
Configuration
How many equalizer bands can I have?
You can have one low-shelf, one high-shelf, and as many normal bands as you wish. Just keep in mind that each one adds a small amount of processing time, so more bands means more CPU usage.
I set max-loops
to 1 and the song doesn't loop...
max-loops
is kinda poorly named, sorry about that 😅 When max-loops
is equal
to 1
, Benben interprets this as "play the song through one time." If it's
equal to 2
then it will play the song through twice (so, it loops once).
What are some good sizes for buffer-size
?
In nearly all cases, a buffer size of 1024 should be perfectly fine. If your computer has trouble playing files, try increasing it to 2048. You shouldn't need to go over 4096, though the program will let you.
Keep in mind that very large buffer sizes will cause the VU meter to become choppy, and could also decrease performance.
What audio backend should I use?
In most cases, PulseAudio will work just fine. PortAudio is also a good one to
try, and may make the VU meter work smoother, depending on your computer's
setup. The ao
driver (short for libao) is solid as well, but has two
drawbacks: it always uses 16-bit integer output (as opposed to 32-bit float),
and it needs just a tiny bit more processing power. But, should neither PortAudio nor PulseAudio work for you, ao is a very good option.
For what it's worth, Remilia uses the PortAudio backend in most cases.
I have a song-specific config file, and I want it to match multiple files. How can I do that?
There are two ways you can do this. The most simple way is to just list multiple matches:
match:
- "/path/to/file1.ogg"
- "/path/to/file2.ogg"
- "/path/to/file3.ogg"
The alternative is to use wildcards ("globbing")). Benben's glob syntax is slightly different and follows how Crystal's standard library behaves. Here are some examples from Remilia's personal collection of song-specific configs:
# Look for files that are in a "vgms-and-mods/VGMs/X68000" directory (any
# parent), and then any subdirectory under that folder.
#
# So this would match any of these:
# - /home/remilia/mnt/server/vgms-and-mods/VGMs/X68000/Granada/03 Heavy Line (Stage 1 - Dead City).vgzst
# - /mnt/other-server/vgms-and-mods/VGMs/X68000/Akumajo Dracula/02 Black Mass (Opening).vgz
#
# And so on...
match:
- "/**/vgms-and-mods/VGMs/X68000/**/*.vg*"
# Matches any file that ends in .flac (regardless of the case of the extension)
match:
- "**.[fF][lL][aA][cC]"
I have a song that can be matched by two song-specific config files. Which one takes effect?
Song-specific config files are processed in alphabetical order, so files with later names take precedence.
Internals
Why write it in Crystal?
Because Crystal is fast, efficient, elegant, fun to use, doesn't use off-side rule syntax, and isn't C++ or Rust. Remilia had also already written some other audio-related code in Crystal, and reusing some of that code made sense.
Which parts are written in native Crystal, and which are just C bindings?
These components are 100% pure native Crystal code:
- YunoSynth (the VGM library, including all emulator cores)
- Haematite (MIDI sequencer and SoundFont synthesizer)
- FLAC decoder
- BZip2 implementation
- All DSP effects (reverbs, EQ, filters, stereo enhancer, soft clipping, etc.)
- High-level interfaces to PortAudio, PulseAudio, and libao.
- High-level interfaces to S-Lang, libopus, libvorbis, libmpg123, and libxmp
- Config management
- Ogg demuxer
- WAV/Au rendering
- WAV/Au reading
- Vorbis Comment parsing
- CUE writing
- XSPF/JSPF parsing
- Resampling (a 100% pure Crystal port of libsamplerate)
- Various other core stuff (bitreaders, arg parsing, etc. - basically the stuff in libremiliacr)
- All of Benben itself
The rest are bindings to C libraries:
- Opus decoder (libopus)
- Vorbis decoder (libvorbis)
- MPEG-1 loader/decoder (libmpg123)
- Module loader/decoder (libxmp)
- Low-level TUI stuff (S-Lang)
- Low-level PortAudio, PulseAudio, and libao bindings
- ZStandard bindings
What's Benben's relationship with YunoSynth? How about Haematite?
They're sister projects. Both YunoSynth and Haematite are written and maintained by Remilia.
YunoSynth is the backend library used to play VGM files. It's a combination of a port, fork, and enhancement of VGMPlay. The library is named after Gasai Yuno.
Haematite is what's used to play MIDI files using SoundFonts. This is Remilia's port of the excellent MeltySynth library, plus a few extensions. It's named after a mineral.
What's Benben's overall internal design?
In normal playback mode, Benben is basically a collection of subsystems that pass messages over a common bus. When a message is sent, all subsystems have access to this message, and can choose if they need to act on it.
Benben's core is the ProgramManager
class, which handles the setup and
teardown of other subsystems, and also dispatches messages between them. The
subsytems are:
- The interface, which does nothing but update the screen.
- The player, which sets up the playback engine, then generates audio and passes it to the backend driver
- The input loop, which gathers input from the terminal.
- A logging system that can handle concurrent requests, and either prints stuff to the screen (only during startup) or to the stderr log file (after playback). This isn't part of Benben itself, but part of Remilia's libremiliacr library.
There are also three subsystems which do not run on their own threads, but exist as singletons. These may be moved into their own threads in the future, however:
- The
FileManager
class is responsible for loading files, and managing the virtual playback queue. - The
EphemeralConfig
class handles the current in-memory configuration state for the program. - The
Driver
classes handle the audio backends. They do nothing but take audio from the player and send them to the backend library (e.g. PortAudio, libao, or PulseAudio).
Things change when Benben is in rendering mode since rendering files to disk is
an embarrassingly
parallel problem. In
this mode, Benben has a central Renderer
instance that acts as a general
manager, and also is responsible for updating the screen. It launches multiple
Job
instances in parallel, one for each file to be rendered, where each Job
is an instance of a subclass that's specific to a particular format of music.
These Job
subclasses do the actual rendering to WAV/Au, and report back their
progress via message passing to the Renderer
.
Files to be played are represented using very thin wrapper classes that all
derive from the PlayableFile
class. These allow audio data to be loaded
on-demand, decreasing overall memory usage, and allow instances of TagInfo
to
be created that store metadata info.
How do song-specific configurations work?
When Benben first starts up, it loads its main configuration file into RAM.
This is then turned into an EphemeralConfig
, which is the actual class that
handles the current configuration state of the program. After this, command
line arguments are applied.
Once the main configuration is loaded, all song-specific configuration files are loaded RAM.
Before playing a file, Benben will check its collection of song-specific
configurations to see if any of them match the song's filename. When one is
found, then that song-specific configuration is applied to the EphemeralConfig
instance.
The EphemeralConfig
knows which settings can be overridden by a song-specific
configuration, and stores these settings as a stack that always has either one
element or two, where the 0th element is the setting from the main configuration
file and the 1st element (if it exists) is the song-specific setting. When a
setting is accessed, the latest value is always returned. When the song
changes, any song-specific configurations that were applied are popped.