nonemm-recorder is a separate program (src/Nonemm.Recorder.App) over a library (src/Nonemm.Recorder). Each row in its list is one channel of one device, and each row becomes one channel in an Ogg Opus file: 60 ms frames, complexity 9, 32 kbps per channel by default. - A device that is not connected keeps its row, in red, and writes silence. It is recorded from the moment it returns, also during a recording. - Output devices are recorded as loopback: WASAPI on Windows, the "Monitor of" sources on PulseAudio and PipeWire. - A headphone button plays one channel, in mono, on the default device. - The rows, bitrate and folder are saved when the window closes. SoundFlow 1.4.2 numbers its backend enum one higher than the miniaudio it ships, so asking for PulseAudio gave ALSA and Wasapi gave DirectSound. SoundFlowBackend lowers the value by one and checks which backend started. Output devices and live capture are not tested on real hardware. The tests use a fake sound system and decode the written file. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
3.4 KiB
Sound recorder
nonemm-recorder is a separate program (src/Nonemm.Recorder.App). It records
channels of several audio devices into one Opus file. The code that does the
work is in src/Nonemm.Recorder, and the window is a thin layer over it.
Run it with ./build.sh run --project src/Nonemm.Recorder.App.
The file
- Ogg Opus, one channel in the file for each row in the list, in list order.
- Mapping family 255: each channel is its own mono stream, so a player does not mix them as speakers. A file with one row is plain mono.
- 48 kHz, 60 ms frames, complexity 9. The bitrate is for each channel and defaults to 32 kbps, so a file of four rows is about 128 kbps.
- A new file is named
recording-yyyyMMdd-HHmmss.opusin the chosen folder. - Pause leaves no gap: the audio after Resume follows directly on the audio before Pause.
Channels
- Each row picks one channel of one device:
Line In L,Line In R, or justMicrophonefor a mono device. The list of choices is the same in every row. - Output devices are listed as
Speakers (output) L. They record what that device plays (see "Output devices"). - A device that is not there keeps its row, in red, and writes silence. The program lists the devices every 2 seconds. When the device returns, its channel is recorded from that moment, also during a recording.
- Rows cannot be added, removed or changed during a recording, as they would change the channel count of the file.
- The headphone button plays that channel, in mono, on the default playback device. If it monitors an output device that is also the default one, it feeds back.
Configuration
One file, written when the window closes: nonemm-recorder/config.json in the
user's application data folder (%APPDATA% on Windows, ~/.config on Linux).
It holds the rows, the bitrate and the folder. Rows of devices that were not
connected stay in it.
Output devices
| System | How an output is recorded | Tested |
|---|---|---|
| Windows | WASAPI loopback of that playback device | no |
| Linux | the PulseAudio or PipeWire "Monitor of ..." source | no |
Neither was tested on real hardware: the machine this was written on has no
sound device. The tests in tests/Nonemm.Recorder.Tests use a fake sound
system and check the file by decoding it again.
On Windows, SoundFlow only loopback-records the default output through its
public API. SoundFlowBackend.MarkLoopback sets an internal setting by
reflection to pick any output, so it is tied to SoundFlow 1.4.2.
Sound system
On Linux the program asks for PulseAudio (PipeWire serves it) and falls back to ALSA only if that fails. The window shows which one is in use, and the reason in red when it is the fallback. ALSA lists sound cards and plugins, not the devices a PipeWire desktop shows, and has no "Monitor of ..." sources.
SoundFlow 1.4.2 numbers its backend enum one higher than the miniaudio library
it ships, so asking for PulseAudio gives ALSA and Wasapi gives DirectSound.
SoundFlowBackend.Start lowers the value by one and checks the result. If a
later SoundFlow fixes the numbering, that check fails with a message.
Timing
The devices do not share a clock, so the file is cut by the system clock, not by how much each device delivered. The encoder runs 120 ms behind the clock. A device that delivers too little is padded with zeros. One that delivers too much loses its oldest samples once it is 240 ms ahead.