How to read the volume level with

I start playing with RoonCommandLine on macOS. It is great for starting and stopping the music, and for setting the volume level.

One thing though I cannot figure out: How do I read the current volume level? I tag @Ronald_Record here, because he might know whether I have overlooked something…

(Background for this: I want to build a fade-out script, that lowers slowly the volume to 0, stops the music and then sets the volume back to what is was before. So that when I play the music again the volume will be as it was before the fading operation. - I have a script like this for Spotify, but now I want to move over to Roon. :smile:)

@Ronald_Record will no doubt be along shortly.

The roonapi exposes this, as does my python library (that command line uses), but don’t know if the command line exposes this.

There is a possible pending change to the python API. The main user of the api is Home Assistant - which requires a volume between 0 and 100. At the moment this conversation between the two roon standards is done in HA (and has a bug for some endpoints)

I will probably provide this in the python library at some point.

1 Like

@hallo_leo @GregD I do not see how to get the current volume level. The Roon API and Python Roon API have a change_volume method which is used by RoonCommandLine. This method supports both absolute and relative volume level changes in specified increments or as percentage. So it would be possible to change the volume as a percentage relative to the current volume then change it back up as a percentage relative again to the current volume. But how to figure out what the original volume was is going to be a little complicated. By ‘complicated’ I mean I do not know how to do it.

Maybe there is a way to get the current volume from the API and I just do not see it. I will look into it since this is a useful feature and one I should be supporting. I do the same type of thing with mplayer in another project, fading one song out as another fades in, and I like this ability.

I hope I am just overlooking the obvious. Greg, do you have a simple way to get the current volume? Like, making a change_volume request with 0 relative value or something? I don’t easily see it. Tell me I’m an idiot, it’s right there.

1 Like

You can see HA doing the task here:-

It’s not ideal as described here

At some point I will re-engineer this.

The conversion needs to be better, and should be a utility in the python library, not HA.

I replaced my laptop, and haven’t reset up the dev environment properly yet, just need some time….

1 Like

Thanks @GregD , I see how to get the volume now. For example, to get the current volume and other attributes of each of the Roon outputs, I could do something like the following (after discovery):

roonapi = RoonApi(appinfo, token, server[0], server[1], True)

# One way
# result = []
# for k, v in roonapi.outputs.items():
#     result.append({'key': k, 'value': v})
# print(json.dumps(result))

# Another way
json_dict = roonapi.outputs
json_array = [ {'key' : k, 'value' : json_dict[k]} for k in json_dict]

print(json.dumps(json_array))

This gives me a JSON array of the outputs and their attributes that I can parse with jq. Of course, there is the complicating matters of grouping and output types with their associated different ranges. It’s all something that should be handled at a lower level either by the Roon API itself or in the pyroon library. But, at least I see how to get at the output attributes using existing API and library.

What is the best way to proceed? I’m going to go into cogitation mode.

1 Like

@hallo_leo I have something in test for getting a zone’s outputs volume, along with its minimum and maximum volume limits. This is just a test but it is initially working for me. If you would like to try this out, it can be installed and removed pretty easily without impacting your existing setup. Or, you may prefer to wait until I have a more thoroughly tested and revised version ready for release. But, I thought I would make it available in case you want to try it out. Either way, ok.

The changes are two new files: a Bash shell script get_zone_volume and a Python script get_zone_attributes.py.

Currently it requires jq for parsing returned JSON.

To try this out, clone the RoonCommandLine repository and copy the new files into place:

git clone https://gitlab.com/doctorfree/RoonCommandLine.git
cd RoonCommandLine
sudo cp bin/get_zone_volume /usr/local/Roon/bin
sudo cp api/get_zone_attributes.py /usr/local/Roon/api
cd /usr/local/bin
sudo ln -s /usr/local/Roon/bin/get_zone_volume get_zone_volume

You may need to install jq if it is not already installed.

To test, just run get_zone_volume as your Roon user. Without any arguments it should return volume levels for the default zone and any zone groupings. To get the volume of a specific zone, use get_zone_volume -z "Zone Name".

Honestly, you can just ignore this or treat it as an interesting conversation. I am going to do additional work and testing. If you do test it, please let me know what you think and how you would like it improved. I could have done the parsing in Python with the dict but jq is very flexible and I prefer it over Python for this kind of work. That may change depending on use cases and need.

Thanks for pointing out the need for this feature.

1 Like

Hey @GregD! Just curious, what is your Python library?

Is it roonapi? – This is the only Python package RoonCommandLine added to my Python install.

@Ronald_Record This superdupercool! Thanks heaps. get_zone_volume works for me.

It is - details here GitHub - pavoni/pyroon: python library to interface with the Roon API

Roon provide a JavaScript api.

This is a partly reverse engineered python library calling the same underlying functions.

Ok, thanks. Now I understand: Pyroon is the roonapi package.

BTW, do you have a way to detect whether a zone (e.g. the default zone) is playing right now? I looked at roonapi.outputs but couldn’t find a "state" key in there. Is it stored somewhere else or am I missing something?

Also, the snippet you list from HA does have a notion of the player state – do they use roonapi?

@Ronald_Record As mentioned to Greg, I’m looking to read the play state in addition to the volume. From what I saw in the new get_zone_attributes.py the state should be handed out from roonapi, right?

Yes, there is a state zone attribute that can be used to see if a zone is currently playing, paused, or stopped. I use this and a few other zone attributes in the newly created get_zone_remaining command.

To try out get_zone_remaining do a git pull in your cloned RoonCommandLine directory. This should retrieve both bin/get_zone_remaining and api/get_zone_remaining.py. Copy those into /usr/local/Roon/bin/ and /usr/local/Roon/api/ and symlink to /usr/local/bin/ as you did with get_zone_volume.

I’m approaching this fade feature incrementally. First, what is the current volume what are the output ranges in the (grouped) zone. Next, how much time remaining in a playing zone. Finally, implement some sort of fade/restore in the playing zone at some time remaining. But, there are a number of ways to do this and several gotchas. How did you do it on Spotify? How would you like it to work on Roon?

Cool. So it is exposed in roonapi, right? I must have been blind. i didn’t see it there…

Works like a charm. Thanks heaps!

Well, I am thinking of an overall toggle command: When playing fade out, when not playing, fade in.

Now, the fade out would do this: Save the current volume, then slowly reduce the volume to zero, pause play back and set the volume back to the saved state.

For fade in the reverse logic would be used.

I wrote an Applescript using the specific Spotofy API (Yeah, Spotify supports Applescript!)

tell application "Spotify"
	try
		set saved_vol to sound volume
		
		set fade_time to 120
		
		set delta to saved_vol / fade_time
		if player state is playing then
			set act_vol to saved_vol
			repeat until act_vol < 0
				if act_vol < 0.3 * saved_vol then
					set act_vol to act_vol - (0.5 * delta)
				else
					set act_vol to act_vol - delta
				end if
				delay 0.05
				if act_vol > 0 then
					set sound volume to round act_vol
				end if
			end repeat
			pause
			set sound volume to saved_vol
		else
			set act_vol to 0
			set sound volume to act_vol
			play
			repeat until act_vol > saved_vol
				if act_vol < 0.3 * saved_vol then
					set act_vol to act_vol + (0.5 * delta)
				else
					set act_vol to act_vol + delta
				end if
				delay 0.05
				if act_vol < saved_vol then
					set sound volume to round act_vol
				else
					set sound volume to saved_vol
				end if
			end repeat
		end if
	on error -- no track is the current track
		my notify_msg("Error during \"Fade Spotify\"")
		beep
	end try
end tell

This implements the logic described above

@hallo_leo very cool. I am alternately amazed and disappointed with what can be done with AppleScript. You might be able to do something similar with Roon in AppleScript if Roon were running on a Mac and configured to use the system audio output. But, idk if AppleScript talks to Roon and even if it does that is not a good solution since it does not address other audio outputs and devices.

I am working on a fading feature for RoonCommandLine but it is trickier than I thought it would be. Roon can play multiple songs in multiple zones, zones can be grouped with the same audio in all zones, each zone in a zone grouping has separate volume controls and ranges, multiple devices can be used to adjust volume by zone or output. And there is the problem of a non-computable time delay for every API call over a local network. It becomes fairly complex trying to cover all the use cases. But, I have a start and it sorta works but not yet to my satisfaction.

I should have something to test this weekend. I’ll let you know how it progresses.

That’s fantastic. You really want to take RoonCommandLine to the next level! :slight_smile:

@hallo_leo I’ve posted a new release of RoonCommandLine with fading implemented. See RoonCommandLine 2.0.7 with Fading feature, bug fixes

On a Mac you can just clone the repository, ./Uninstall, and ./Install.

RoonCommandLine version 2.0.7 release 1 can be found at:

https://gitlab.com/doctorfree/RoonCommandLine/-/releases/v2.0.7r1

1 Like

Thanks for this! Will check it out.

Just installed (and tweaked for me the install due to some homebrew troubles). Now I can execute roon_fade – and I can see you have done lots of work for this. Thanks heaps!

However I’m too dumb to understand how I can fade in and out with it:

  • roon_fade only displays that fading is enabled and
  • roon -c play still does a abrupt start.

What do I miss?

PS: Furthernore, do you mind me asking what the roon_faded service is for? Why cannot the roon_fade command do the fading itself? Of course it would block the command line while doing so, but for this the user could send the command to the background…

To enable fading, issue the command roon_fade on. To disable, roon_fade off. By default, fading is performed in the RoonCommandLine default zone and any other zones it may be grouped with. The default zone is whatever zone you last used in a RoonCommandLine command. You can specify which zone to use for fading with the -z zone command line arguments. For example, to perform fading in the Roon zone named “Living Room” you would issue the command roon_fade -z "Living Room" on. To disable fading in the Living Room zone, roon_fade -z "Living Room" off.

The default action after fading out is to fade back in for the next track. In your Spotify fade you restore the volume back to its unfaded level without fading back in. To do this with my implementation, set FADE_IN=false in /usr/local/Roon/etc/pyroonconf. This can be accomplished with the command roon_fade -I (that’s a capital ‘I’). To go back to fading in after fading out, roon_fade -i (lowercase ‘i’).

See man roon_fade for more command line usage of this command.

Yes, roon_fade could have just done the fading itself rather than spawn roon_faded to do the work. I split fading out into a separate command as roon_fade can also be used to do other tasks like set the fade duration, enable and disable fade in after fade out, force restore of original volume, and disable fading altogether. I decided to use a frontend, roon_fade, for this multi-purpose UI and a backend, roon_faded, as a daemon strictly dedicated to fading. The daemon has to do a lot of work in the background like monitoring zones to see if they are playing and if the time remaining is below a threshold. It sleeps a lot. The frontend can be used to alter fading parameters during fading so I don’t want it sleeping, I want it available at all times.

It could be architected differently. It’s just a choice I made. You may have good arguments to make for re-architecting fading in RoonCommandLine and I would be happy to hear any suggestions for how to do this better. It is still a work in progress.

Please continue to let me know what goes wrong, what you would prefer, what you suggest, what you do not understand how to do, and what goes right. I consider this feature to be experimental at this stage and a lot more complicated than i expected it to be or what it should be.

Aha, I think we envision in detail quite different things when we talk about “fading”. Here how I understand your description by now: You think of teh player having a “fading” mode. Once in this mode the player will in future at some point of time fade the music out – and this time is the end of the track, right? Please correct me if i got it wrong.

I am thinking of “fading” as something which happens right now when I issue the command: I want the music to fade out slowly (like for you), but starting right now. Here my use case is:

I listen to music. Then the phone rings or my wife/husband/son/mum/post woman/whoever knocks on the door and I would like to attend to them without music. So I want to switch the music off, but just pressing/issuing “pause” feels so abrupt and brutish! That’s the reason why I want a smooth fade-out, in say, 10 secs. (30 sec seems a bit long for my personal taste - and for the patience’s of the other person!)

After the interruption I want either to continue the music where I stopped it with a similar fade-in to the old volume level (I always had the idea of skipping back a bit “into the faded-out part” of the music, but have never implemented that) OR I want to start a new track with the current volume level.

Does this make sense?