Soundboword
- 10 Devlogs
- 53 Total hours
A multi-platform, actually usable desktop soundboard
A multi-platform, actually usable desktop soundboard
devlog about logging, crazy
added logging in-app and also to files
you can configure log levels, None disables file logging altogether
i was looking around to find a simple file logger for Microsoft.Extensions.Logging, but i didn’t like it so i made my own, even simpler version
took a bunch of attempts to make the architecture bearable, but it’s still kinda jank with the existence of LogPreferences.Instance (instead of using the service provider… but whatever, i make the rules :3)
turns out that MultiBinding doesn’t work for a bool that hides the bottom bar (idk why), so i did some wiring by subscribing to PropertyChanged in the preferences and the log list
only at the time of recording did i realize that the initial value isn’t set properly, so i gotta fix that
tried to set PublishAot to true for all projects (so i don’t have to specify it in dotnet publish), but that breaks the preview, so i had to revert the change
unhandled UI exceptions are now written to the file
the unobserved task exception event just… doesn’t seem to fire… i got rid of that
tried to use SemaphoreSlim to make the exiting thread block until an in-progress write operation completes, but that probably caused a deadlock because the app wouldn’t close
if a log entry happens to be in the middle of being written while you’re quitting, it gets cut in half, oh well
fun fact: the ItemsRepeater Avalonia control exists on GitHub, even the docs describe how to use it, but it just didn’t show up for me despite using the latest Avalonia version
this made me use a ListBox, which has selection (although you can’t see it because of the background of each log entry)
made settings sections with a whole lot of unnecessary abstractions for the funny 
[FromKeyedServices] when i could just access a static instance? absolutely yes!!
i couldn’t figure out how to style the children of an ItemsControl externally, so i had to give it an ItemTemplate with a class (which would be unnecessary if i could style the thing)
all that for a separator line… and padding… and margin… what if i just nuke this idea and simply have some spacing between the sections?
i tried organizing classes into vertical slices architecture, but i gave up and only did it for the Linux project instead
the using updates would’ve been funky + some global usings would become useless
the toggle list now no longer “flickers” on/off when refreshing devices, as i only set the link manager after PipeWire objects have been queried
device lists and link toggles are now disabled when switching devices so the user doesn’t accidentally break the linking process
added a button that refreshes links (in case things go wrong somehow)
you can now select the physical microphone from the PipeWire list, and it will update links
this preference isn’t saved to disk just yet
in my last devlog i mentioned some issues, which have now been fixed:
upon startup, the first two switches don’t work sometimes (race condition maybe?)
turns out that i had forgotten to add a potentially valid node to the list after looping through all lines, so it just discarded the last entry
certain links might not reconnect properly when switching the output device
what did i do to fix that? i’m not sure anymore
maybe it was a bit of delay?
this one might be linked related to the former issue
spent almost all day adding PipeWire node linking support
i was genuinely tweaking when i found out that the pw-cli create-link command doesn’t create a link… it does NOTHING
wishful thinking to assume that i can simply tell PipeWire to connect nodes automatically
now i have to query the ports, too, and connect those automatically; make sure to sort them, otherwise the channels might be mixed up ahaha (a0b498e7 khm khm)
i spent plenty of the time debugging, the parser i wrote had some funky stuff
there are 4 toggles you can activate, as shown in the video:
some miscellaneous fixes:
DataToggle control that inherits from ToggleSwitch: clicking on it doesn’t toggle it automatically, the binding controls the stateFINALLY figured out how to interface with xdg-desktop-portal, global shortcuts now work on Linux!!! (with a caveat ofc)
subsequent bind requests to the same action will just be ignored by the portal, even if you closed the dialog without binding any shortcuts
you should still be abe to bind them in System Settings
props to Pinta for the example on how to do this (the DBus flow kinda sucks ngl)
making this was partially possible because Avalonia updated to the latest Tmds.DBus version
adjusted the YouTube downloader layout: removed the description in favor of a stream selector
that also got rid of the upload date, oops
added a dummy ✏️ button to sounds (basic editor coming soon)
i don’t even want to think about how i’ll render waveforms without making the system go kaboom
also added a number input for volume, letting users type in the desired percentage
Avalonia magically handles the percentage conversion, i didn’t have to make a converter that multiplies the input by 0.01
made pages dynamic, fixing the inconsistent layout
this will make it easier to add platform-specific tabs instead of having to dump everything into settings
TabStrip just didn’t work properly, so i’ll stick with the TabControl (which doesn’t have… tab… focus…)
OtherSoundInteraction now lets you mute, pause or stop other sounds when a new playback is triggered
adopted Injectio so i can slap a [RegisterSingleton] attribute on each class i want in dependency injection without having to go to a different place to add a statement
the “Stop All Instances” button is now replaced by the “Relink” button for files that were not found
you can now drag-and-drop sounds into the board from the file manager
Did a bit of refactoring to accomodate for later NativeAOT support:
Added global usings, and split up the project for better platform-specific service organization
Fixed the delete button not stopping the sound (how did I manage to fail that??)
Replaced the loop toggle with an Assign Shortcut button for quick access
I think the latter will be used more often, but having the loop toggle update two buttons at once was really cool
Sounds can now be added from YouTube; they are cached for fast access (how else would it work xd)
The window is not a dialog, so you can still interact with the soundboard while waiting for download(s)
(Idk if dialogs prevent interactions on some desktop environments tbh)
This is not the final layout, but it looks decent for now: clicking on a video (left side) takes you to the details page (right side) in the same window
Fun fact: pasting a youtube link automatically opens the video view, letting you download it even while the details are loading (will have to fix that up a little)
More (almost zero) progress on global shortcuts: I’ve figured out the object path & destination from the Kotlin library zugaldia/stargate, but xdg-desktop-portal just crashes now upon calling CreateSession
Added a devices page to select the output device
A “Stop All Sounds” shortcut can now be assigned to each input when clicking the gear icon next to it
Fixed shortcut assignment not working after switching pages
SelectionChanged event fired for every child control, not just the TabControl -> I now check for the source of the eventAttempted to add consistent text alignment in tab headers, that turned out to be stupid, so I just added a space after the Playbacks emoji
Removed the “Raise Maximum Volume” (a 0-150% range makes more sense imo)
Adjusted buttons in the sound editing view, this is not yet final
On the image you can see a pipewire graph I created, this is similar to how I want it to work
Added pages & tabs to better separate UI
Activated inputs are now saved and loaded
Sound state is now displayed better, it includes missing file detection (exceptions are not yet handled)
Implemented input handling for the Launchpad Mini, now I’ll be able to press pads to play sounds
Creating the shortcuts system took a few iterations, I wasn’t happy with the code I wrote
Shortcuts can be removed even when the input method isn’t active, which wasn’t possible in an earlier iteration
Also implemented settings saving when the app exits
Added a proper UI for triggering sounds with modes similar to that of voicemod (start -> stop, start -> restart, play -> pause, duplicate)
I played around with 2 rows below the big button but that seemed to take up too much space, so I moved them to the sound editor (which isn’t fully functional yet)
On the right side you can see input methods, which will include global shortcuts later (DBus is confusing af, the win32 api might actually be easier to work with 💀)
(Yes, I’ve half-made a global keyboard handler for windows previously)
Emojis are fairly expressive imo, but there are tooltips that describe what each button does
The layout definitely needs improvements, but it’s something ig
Also implemented loading sounds on startup, as well as saving when a new sound is added
Created the very base of the project with Avalonia and SoundFlow, sound playback works
Spent a bunch of time trying to figure out the DBus interface for XDG global shortcuts (Linux part of keybinds):