You are browsing as a guest. Sign up (or log in) to start making projects!

Soundboword

  • 13 Devlogs
  • 69 Total hours

A multi-platform, actually usable desktop soundboard

Ship #1 Pending review

Soundboword is an actually usable desktop soundboard for (primarily) Linux and (with apparent support for) windows

tired of other soundboards paywalling slots? not anymore! [insert Phil Swift Flex Tape meme, not sponsored]

this is a .NET app built with Avalonia, SoundFlow, and YoutubeExplode

  • you don’t need to install .NET, it’s compiled into a native binary
  • the SoundFlow backend in use is based on miniaudio, so the app only supports mp3 and wav files
  • download audio from YouTube (paste a link or search)

for Linux, there is additional PipeWire support
use the PipeWire wizard to create a virtual device named “Soundboword Microphone” which you can select in apps (e.g. Discord)

after setting up the microphone, you can use the toggles in the bottom left to manage some node links (connect/disconnect audio nodes)

  • Hear Sounds: plays the soundboard to your headphones
  • Mic Sounds: plays the soundboard into the virtual microphone
  • Mic Passthrough: connects the selected physical microphone to the virtual mic
  • Hear Myself: connects your physical mic to your headphones, echoing what you say

windows support is partial, macos support will not be provided

global hotkeys should work out of the box on losedows

the Linux version uses xdg-desktop-portal for global shortcuts

if you have a Launchpad Mini MKII lying around, you can use it with this program :3

  • 13 devlogs
  • 69h
Try project → See source code →
Open comments for this post

3h 40m 6s logged

most likely the last devlog before i outgrow hackclub :’3

finalized the project, improved the CI workflow to bundle the program properly and include 3rd party licenses

completed the README


added a simple sound “editor” that lets you specify the following:

  • start time: where to begin playback
  • loop start: where to jump when restarting on loop (start of file by default)
  • loop end: the point where loop triggers (end of file by default)

made the sidebar (Sounds and Inputs views) consistent


added icons to almost every window (Linux crashed because the PipeWire icon wasn’t in that project lol)

message to future self: you need the leading slash in the Icon property (e.g. /Assets/sus.ico works but not Assets/sus.ico)

the video shows the new configure sound on trigger click feature, resembling the behavior of Voicemod


implemented the missing “Auto Mic Passthrough” (as described in the README)

the physical microphone preference is now saved


fixed the hidden toggles taking up space on slopdows

RowDefinitions don’t support binding, not even OnPlatform :(


hopefully i don’t gotta fix anything, and it’s ready to ship :33:

0
0
4
Open comments for this post

9h 33m 59s logged

I’M GONNA TWEAK OUT

9 hours on slopdows bullshit… IT FINALLY WORKS (except not really because removing keybinds doesn’t..?)


okay so i restored some of the styling, 0 padding in the Inputs listbox looked bad

not sure what else i did that isn’t losedows suffering lmao


sooooooo “win”dows is special

if you hold Ctrl, it doesn’t call the TextInput event, meaning that i have to translate the Key enum

the TextInput event isn’t flawless either, it can be ambiguous (e.g. hungarian layout: AltGr+J = í, AltGr+I = Í, Soundboword makes them uppercase)

should i remove the automatic uppercase conversion?


i spent a lot of time “debugging” (as in, having the CI build it, download the zip, transfer it to a slopdows machine, since native cross-compilation is not supported)


the biggest headache was the shortcut list update handler… i negated the TryAdd call, which meant that the global hotkey would only get registered if the same gesture has been added to the dictionary already (therefore it would only work on subsequent binds)
i spent way too much time figuring this out


another oversight was the lack of Target assignment when you clicked ⚙️ on an input method, meaning that binding shortcuts did nothing

idk why the entire panel is bold, gotta fix that


hotkeys are temporarily removed while you’re assigning a shortcut
however, as mentioned before, this doesn’t work xd
i tried it on a slopdows laptop, and removing the A hotkey made the A key unusable until i closed Soundboword


setting up CI wasn’t difficult, gotta make sure to separate rm commands and use -f so files not found don’t error

using || doesn’t run the latter command if the first one succeeds, i should’ve known that :kek:

0
0
14
Open comments for this post

2h 37m 14s logged

i did mostly polishing work during this time, now i can torture myself with windows global shortcuts :harold:


gotta crash out first though cuz this wonderful website has 3 different markdown renderers somehow, and i’ve been using --- to make sections actually separate


changed the TabControl to a TabStrip so i can use my on page switching logic
most views are only created once now, which gives a slight performance boost
i’d noticed that quickly switching between tabs while using TabControl lead to slight crackles in audio, that’s taken care of now
oh and you can use the Tab key to navigate, cuz TabStrip can magically do that
getting the lazy-ish loading was pretty funky though, UserControls are lazy by default so i had to hack around it to preload the views by setting IsVisible to false after the control loads (i think that’s what i did, i kinda forgor)


fixed the YouTube stream loader, and added a local error display so you can immediately see if something went wrong


made it so non-bound XDG-Desktop-Portal shortcuts are no longer detected


removed the sound edit button for now, i would like to ship without having to figure all that out


made StackPanel spacing mostly uniform using a style selector, though it looks pretty bad in device selectors


general settings & logging settings are now saved and loaded


added Rescan and Relink All buttons
Rescan checks not found files again to locate them
Relink All lets you pick directories to look for not found sounds by their filenames

in the video, i unmounted the disk that had some files, then relinked sounds to locate it elsewhere

0
0
8
Open comments for this post

4h 49m 46s logged

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)

0
0
15
Open comments for this post

4h 1m 27s logged

made settings sections with a whole lot of unnecessary abstractions for the funny :33:
[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


PipeWire


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

0
0
3
Open comments for this post

8h 59m 12s logged

PIPEWIRE WORKS!!1!1!

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

how to use it

there are 4 toggles you can activate, as shown in the video:

  • sounds to primary output: this will play the soundboard’s audio in your headphones/primary speakers
  • sounds to virtual mic: forwards soundboard audio to the virtual microphone, letting others (e.g. in a call or in a game) hear the sounds
  • mic passthrough: connects your “real” microphone to the virtual microphone, allowing others to hear your voice when using the virtual mic
  • hear myself: connects your real microphone to your primary output device

more issues

  • upon startup, the first two switches don’t work sometimes (race condition maybe?)
  • certain links might not reconnect properly when switching the output device (it used to be worse when i queried links every time; it would connect only one port to the destination because the port ID was now assigned to something else)

some miscellaneous fixes:

  • fixed certain issues with xdg-desktop-portal shortcut assignments
  • removed the upload date from YouTube video views
  • fixed the NativeAOT build (an attribute was missing that let the ServiceProvider know not to strip view & viewmodel constructors)
  • added a DataToggle control that inherits from ToggleSwitch: clicking on it doesn’t toggle it automatically, the binding controls the state
0
0
6
Open comments for this post

6h 31m 18s logged

FINALLY 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

0
0
1
Open comments for this post

6h 33m 41s logged

Did a bit of refactoring to accomodate for later NativeAOT support:

  • Each view & viewmodel has to be registered as a service
  • Moved to source-generated JSON handling
  • Hopefully, NativeAOT will make the app noticeably faster when I publish the project

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)

0
0
1
Open comments for this post

5h 40m 16s logged

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

  • Nothing usable yet

Added a devices page to select the output device

  • I tried to implement device switching by creating a separate Mixer, but SoundFlow already has functionality for that, so it became much simpler
  • Refactored code to use the new SoundFlowDeviceManager

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

  • Editing context is reopened on page change
  • The SelectionChanged event fired for every child control, not just the TabControl -> I now check for the source of the event

Attempted 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

  • A sink for soundboard output which can be forwarded to the primary output device (e.g. headphones)
  • A mixer virtual device (discord doesn’t support sinks) that combines the microphone input and the soundboard sink, acting as a virtual mic which can be selected in apps
  • From a bit of testing, it turns out that connecting a sink to the primary output device produces some annoying popping, so I’ll probably just link/unlink the app’s playback to/from the primary output device, too
0
0
1
Open comments for this post

4h 9m 59s logged

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)

  • Using a MultiBinding and an IMultiValueConverter with an enum didn’t work for some reason
    New page: playbacks
  • It shows all sounds, their current time
  • Will need a redesign
  • Pro tip: don’t call Dispatcher.Post in a method you called Dispatcher.Post in (it will keep updating the UI but prevents user input, you can’t even close the window normally)
    Did a bit of refactoring:
  • AudioManager is now a service
  • Removed the Host service, the desktop lifetime and the TopLevel will be injected
    Added an overlay when a file dialog is open (because unlike windows, not all OSes/desktop environments prevent interactions in this situation)
0
0
2
Open comments for this post

5h 0m 25s logged

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

0
0
2
Open comments for this post

5h 31m 47s logged

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

0
0
3
Open comments for this post

1h 50m 44s logged

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):

0
0
4

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…