devgianlu / go-librespot

Yet another open source Spotify client, written in Go.
GNU General Public License v3.0
52 stars 7 forks source link

Add support for alsa mixer volume #36

Closed tooxo closed 2 months ago

tooxo commented 2 months ago

Right now, if the "external volume" configuration option is enabled, the spotify volume slider does nothing, so I propose that it at least should be possible to update the volume of for example an alsa mixer.

I have written a short snippet on how it could be implemented, but I am not sure how to implement it into the project without creating a mess.

var mixerHandle *C.snd_mixer_t
var sid *C.snd_mixer_selem_id_t

C.snd_mixer_open(&mixerHandle, 0)
C.snd_mixer_attach(mixerHandle, C.CString("default"))
C.snd_mixer_selem_register(mixerHandle, nil, nil)
C.snd_mixer_load(mixerHandle)

C.snd_mixer_selem_id_malloc(&sid)
defer C.free(unsafe.Pointer(sid))

C.snd_mixer_selem_id_set_index(sid, 0)
C.snd_mixer_selem_id_set_name(sid, C.CString("Master"))
var elem *C.snd_mixer_elem_t = C.snd_mixer_find_selem(mixerHandle, sid)

var minVol C.long
var maxVol C.long
C.snd_mixer_selem_get_playback_volume_range(elem, &minVol, &maxVol)

var volume = vol*(float32(maxVol-minVol)) + float32(minVol)
C.snd_mixer_selem_set_playback_volume_all(elem, C.long(volume))

C.snd_mixer_close(mixerHandle)
devgianlu commented 2 months ago

Hello, thank you very much for this! I am in the process of refactoring the linux audio driver to allow for better crossfade / gapless capabilities. I'll keep this in mind so that it can be implemented at some point.

tooxo commented 2 months ago

Another nice-to-have would be if the spotify-internal volume would be set to the mixer volume on connect or at least on startup of the daemon.

Edit: Also, let me know when you are done with your refactoring work, I'll be happy to implement the feature myself.

devgianlu commented 2 months ago

@tooxo I've just implemented this (sorry didn't see your note about implementing it yourself in time). It seems to be working pretty good, let me know if there's any mistake in the implementation or things I didn't think of. I am not really an expert at ALSA stuff.

Edit: I have just noticed that the string passed to snd_mixer_attach is not the device name, perhaps you can help me with that?

tooxo commented 2 months ago

@devgianlu Thank you for implementing!

The string passed to snd_mixer_attach is not the device name, but the name of a "high level control interface". I am also not 100% sure what this is supposed to mean, but it seems to be an abstraction of a mixer within ALSA. Usually every alsa device with a mixer also has a HLCI with the name of the device connected to it, I would have to do some further research on this. To maybe prevent errors here this could be a field in the config file.

I like your implementation, but for my use-case it would be even better if the program doesn't touch the alsa mixer volume once, but sets the volume of spotify to the current alsa volume maybe while connecting or during startup.

Synchronization with the alsa mixer could also be realized with callbacks, which are also supported.

I am still interested in helping, if anything comes up!

devgianlu commented 2 months ago

The string passed to snd_mixer_attach is not the device name

Yeah I had this feeling, I have moved it to a separate configuration variable for better customization.

I didn't initially realize what you meant for the synchronization of the volume: perhaps that could be a separate issue considering it requires some additional tinkering (especially because of callbacks I imagine). Or feel free to open a PR directly if you feel confident about it! Only thing I know already is that we may want it to be toggleable from the configuration (opt in / opt out?).

tooxo commented 2 months ago

@devgianlu I'll look what I can do, but it all should definitely be opt-in, because it can create some really mysterious errors if your alsa configuration is funky.

devgianlu commented 2 months ago

Take your time! Closing this for now, feel free to re-open if you find out any problem with the current implementation.