Updated to operate with up to 8 nrsc5 streams, per the nrsc5 spec.

This commit is contained in:
Mark J. Fine 2022-12-09 15:30:42 -05:00
parent ded176ae46
commit a3068acddc
3 changed files with 369 additions and 65 deletions

View File

@ -21,8 +21,8 @@ The following programs are required to run NRSC5-DUI
It is also assumed you have a fully operational Gtk3 environment installed from [Homebrew](https://brew.sh/), if running on macOS.
# Setup
1. Install the latest version of Python 3.9, PyGObject, Pillow, and other python dependencies.
2. Compile and install nrsc5. If using an SDRPlay, you must compile and install the version provided by [fventuri](https://github.com/fventuri/nrsc5).
1. Install the latest version of Python 3.9, PyGObject, Pillow, and other python dependencies.
2. Compile and install nrsc5. If using an SDRPlay, you must compile and install the version provided by [fventuri](https://github.com/fventuri/nrsc5).
3. Install nrsc5-dui files in a directory where you have write permissions.
The configuration and resource directories will be created in a new `cfg` and `res` directory under where nrsc5-dui.py resides. Similarly, an `aas` directory will be created for downloaded files and a `map` directory will be created to store weather & traffic maps.
@ -43,21 +43,21 @@ Please ensure your RTL-SDR dongle or SDRPlay is first connected to an available
`python3 nrsc5-dui.py`
or something like:
`python3 nrsc5-dui.py /usr/local/bin/`
to include the path to nrsc5 when using scripts (like Apple Script) that seemingly ignore the environment.
The latter includes the path to nrsc5 when using scripts (like Apple Script) that seemingly ignore the environment.
## Settings
You may first change some optional parameters of how nrsc5 works from the Settings tab in nrsc5-dui:
Set the radio you are using to either RTL_SDR or SDRPlay.
You may first change some optional parameters of how nrsc5 works from the Settings tab in nrsc5-dui:
Set the radio you are using to either RTL_SDR or SDRPlay.
Set the gain to Auto, or optionally enter an RF gain in dB that has known to work well for some stations.
Enter a PPM correction value if your RTL-SDR dongle has an offset.
If using an RTL_SDR:
Enter the number of the desired device if you have more than one RTL-SDR dongle.
If using an RTL_SDR:
Enter the number of the desired device if you have more than one RTL-SDR dongle.
Enter the IP address that rtl_tcp is listening to and check the Enabled box if you are using a remote RTL-SDR.
If using an SDRPlay:
Enter the serial number of the SDRPlay.
Enter the antenna port used by the SDRPlay.
If using an SDRPlay:
Enter the serial number of the SDRPlay.
Enter the antenna port used by the SDRPlay.
Other settings:
Check `Log to file` to enable writing debug information from nrsc5 to nrsc5.log.
@ -66,7 +66,7 @@ Check `Include Station Art` to display album art that is generated by the statio
Check `Extended Queries` to apply several MusicBrainz queries to find album art. Turning this option on may be slower than non-extended queries.
## Playing
Enter the frequency in MHz of the station you want to play and either click the triangular Play button on the toolbar, or just hit return. When the receiver attains synchronization, the pilot in the lower left corner of the status bar will turn green. It will return to gray if synchronization is lost. If the device itself becomes 'lost', the pilot will turn red to indicate an error has occurred (this is the theory, though I've yet to see this status message happen in practice). The synchronization process may take about 10 seconds, and the station will begin to play. This depends upon signal strength and whether it's relatively free from adjacent interference. After a short while, the station name will appear to the right of the frequency, and the available streams will show on a row of buttons just beneath the frequency entry. Clicking one of these buttons will change to that particular stream. Note: No settings other than stream may be changed while the device is playing.
Enter the frequency in MHz of the station you want to play and either click the triangular Play button on the toolbar, or just hit return. When the receiver attains synchronization, the pilot in the lower left corner of the status bar will turn green. It will return to gray if synchronization is lost. If the device itself becomes 'lost', the pilot will turn red to indicate an error has occurred (this is the theory, though I've yet to see this status message happen in practice). The synchronization process may take about 10 seconds, and the station will begin to play. This depends upon signal strength and whether it's relatively free from adjacent interference. After a short while, the station name will appear to the right of the frequency, and the available streams will show on the two rows of buttons just beneath the frequency entry. Clicking one of these buttons will change to that particular stream. Note: No settings other than stream may be changed while the device is playing.
## Album Art & Track Info
Some stations will send album art and station logos. These will fill the Album Art tab, as they are made available by the station. Most stations will send the song title, artist, album, and genre. These are displayed in the Track Info pane, also if available.
@ -74,10 +74,10 @@ The user can override what the stations send by enabling the DL Album Art settin
The user can change the logo of the playing station by right-clicking in the Album Art area. This will display a popup prompting you for the URL of an image found on the web. Pasting the URL in the box and clicking 'Ok' will download the image and set the logo of the playing station with it.
## Bookmarks
When a station is playing, you can click the Bookmark Station button to add it to the bookmarks list. You can click on the Name in the bookmarks list to edit it. Double click the Station to tune to that particular station and stream. Click the Delete Bookmark button to delete it. Note that some stations use the default MPS/SPS or HDn naming for their streams. In this case, the bookmark will be used to name the stream button.
When a station is playing, you can click the Bookmark Station button to add it to the bookmarks list. You can click on the Name in the bookmarks list to edit it. Double click the Station to tune to that particular station and stream. Click the Delete Bookmark button to delete it. Note that some stations use the default MPS/SPS or HDn naming for their streams. In this case, the respective bookmark will be used to name the stream button.
## Station Info
The station name, slogan, message, and optional alert message will display if the station as pre-programmed them. The current audio bit rate will be displayed here as well as on the status bar. The stations available streams and data services, with a description of each will display, as the station has pre-programmed them. This is a useful feature for noting which stations have [Total Traffic & Weather Network](https://www.ttwnetwork.com/) traffic and weather images.
The station name, slogan, message, and optional alert message will display if the station as pre-programmed them. The current audio bit rate will be displayed here as well as on the status bar. The station's available streams and data services, with a description of each will display, as the station has pre-programmed them. This is a useful feature for noting which stations have [Total Traffic & Weather Network](https://www.ttwnetwork.com/) traffic and weather images.
### Signal Strength
The Modulation Error Ratio for the lower and upper sidebands are displayed as they are determined. Important: High MER values for both sidebands indicates a strong signal. The current, average, minimum and maximum Bit Error Rates will also be displayed as they are determined. High BER values will cause the audio to glitch or drop out. The current BER is also shown on the status bar and may be used as a tuning tool.
@ -102,3 +102,4 @@ The default map used for the weather radar comes from [OpenStreetMap](https://ww
1.2.0 zefie update to modern nrsc5 build
2.0.0 Updated to use the nrsc5 API
2.1.0 Updated and enhanced operation and use
2.2.0 Updated for use with SDRPlay and operates with up to 8 possible audio channels (per nrsc5 spec)

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# NRSC5 DUI - A graphical interface for nrsc5
# Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine
# Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021-2022 Mark J. Fine
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -18,7 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Updated by zefie for modern nrsc5 ~ 2019
# Updated and enhanced by markjfine ~ 2021
# Updated and enhanced by markjfine ~ 2021/2022
import os, pty, select, sys, shutil, re, json, datetime, numpy, glob, time, platform, io
from subprocess import Popen, PIPE
@ -76,9 +76,9 @@ class NRSC5_DUI(object):
self.debugLog("OS Determination: Windows = {}".format(self.windowsOS))
self.app_name = "NRSC5-DUI"
self.version = "2.1.1"
self.version = "2.2.0"
self.web_addr = "https://github.com/markjfine/nrsc5-dui"
self.copyright = "Copyright © 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine"
self.copyright = "Copyright © 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021-2022 Mark J. Fine"
musicbrainzngs.set_useragent(self.app_name,self.version,self.web_addr)
self.width = 0 # window width
@ -106,7 +106,7 @@ class NRSC5_DUI(object):
self.update_btns = True # whether to update the stream buttons
self.set_program_btns() # whether to set the stream buttons
self.bookmarks = [] # station bookmarks
self.booknames = ["","","",""] # station bookmark names
self.booknames = ["","","","","","","",""] # station bookmark names
self.stationLogos = {} # station logos
self.coverMetas = {} # cover metadata
self.bookmarked = False # is current station bookmarked
@ -208,16 +208,28 @@ class NRSC5_DUI(object):
self.set_tuning_actions(self.btnAudioPrgs1, "btn_prg1", False, False)
self.set_tuning_actions(self.btnAudioPrgs2, "btn_prg2", False, False)
self.set_tuning_actions(self.btnAudioPrgs3, "btn_prg3", False, False)
self.set_tuning_actions(self.btnAudioPrgs4, "btn_prg4", False, False)
self.set_tuning_actions(self.btnAudioPrgs5, "btn_prg5", False, False)
self.set_tuning_actions(self.btnAudioPrgs6, "btn_prg6", False, False)
self.set_tuning_actions(self.btnAudioPrgs7, "btn_prg7", False, False)
self.set_tuning_actions(self.lblAudioPrgs0, "prg0", True, True)
self.set_tuning_actions(self.lblAudioPrgs1, "prg1", True, True)
self.set_tuning_actions(self.lblAudioPrgs2, "prg2", True, True)
self.set_tuning_actions(self.lblAudioPrgs3, "prg3", True, True)
self.set_tuning_actions(self.lblAudioPrgs4, "prg4", True, True)
self.set_tuning_actions(self.lblAudioPrgs5, "prg5", True, True)
self.set_tuning_actions(self.lblAudioPrgs6, "prg6", True, True)
self.set_tuning_actions(self.lblAudioPrgs7, "prg7", True, True)
self.set_tuning_actions(self.lblAudioSvcs0, "svc0", True, True)
self.set_tuning_actions(self.lblAudioSvcs1, "svc1", True, True)
self.set_tuning_actions(self.lblAudioSvcs2, "svc2", True, True)
self.set_tuning_actions(self.lblAudioSvcs3, "svc3", True, True)
self.set_tuning_actions(self.lblAudioSvcs4, "svc4", True, True)
self.set_tuning_actions(self.lblAudioSvcs5, "svc5", True, True)
self.set_tuning_actions(self.lblAudioSvcs6, "svc6", True, True)
self.set_tuning_actions(self.lblAudioSvcs7, "svc7", True, True)
# setup bookmarks listview
nameRenderer = Gtk.CellRendererText()
@ -542,7 +554,7 @@ class NRSC5_DUI(object):
self.showArtwork(logo)
else:
# add entry in database for the station if it doesn't exist
self.stationLogos[self.stationStr] = ["", "", "", ""]
self.stationLogos[self.stationStr] = ["", "", "", "", "", "", "", ""]
def service_data_type_name(self, type):
for key, value in self.ServiceDataType.items():
@ -670,7 +682,7 @@ class NRSC5_DUI(object):
self.btnDelete.set_sensitive(self.bookmarked)
def get_bookmark_names(self):
self.booknames = ["","","",""]
self.booknames = ["","","","","","","",""]
freq = str(int((self.spinFreq.get_value()+0.005)*10))
for b in self.bookmarks:
test = str(b[2])
@ -780,7 +792,7 @@ class NRSC5_DUI(object):
license = """
NRSC5 DUI - A second-generation graphical interface for nrsc5
Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine
Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021/2022 Mark J. Fine
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
@ -840,6 +852,10 @@ class NRSC5_DUI(object):
self.btnAudioPrgs1.set_active(self.update_btns and self.streamNum == 1)
self.btnAudioPrgs2.set_active(self.update_btns and self.streamNum == 2)
self.btnAudioPrgs3.set_active(self.update_btns and self.streamNum == 3)
self.btnAudioPrgs4.set_active(self.update_btns and self.streamNum == 4)
self.btnAudioPrgs5.set_active(self.update_btns and self.streamNum == 5)
self.btnAudioPrgs6.set_active(self.update_btns and self.streamNum == 6)
self.btnAudioPrgs7.set_active(self.update_btns and self.streamNum == 7)
self.update_btns = True
def on_program_select(self, _label, evt):
@ -1057,14 +1073,26 @@ class NRSC5_DUI(object):
self.set_button_name(self.btnAudioPrgs1,self.btnAudioLbl1,1)
self.set_button_name(self.btnAudioPrgs2,self.btnAudioLbl2,2)
self.set_button_name(self.btnAudioPrgs3,self.btnAudioLbl3,3)
self.set_button_name(self.btnAudioPrgs4,self.btnAudioLbl4,4)
self.set_button_name(self.btnAudioPrgs5,self.btnAudioLbl5,5)
self.set_button_name(self.btnAudioPrgs6,self.btnAudioLbl6,6)
self.set_button_name(self.btnAudioPrgs7,self.btnAudioLbl7,7)
self.set_label_name(self.lblAudioPrgs0, self.streamInfo["Streams"][0], True)
self.set_label_name(self.lblAudioPrgs1, self.streamInfo["Streams"][1], True)
self.set_label_name(self.lblAudioPrgs2, self.streamInfo["Streams"][2], True)
self.set_label_name(self.lblAudioPrgs3, self.streamInfo["Streams"][3], True)
self.set_label_name(self.lblAudioPrgs4, self.streamInfo["Streams"][4], True)
self.set_label_name(self.lblAudioPrgs5, self.streamInfo["Streams"][5], True)
self.set_label_name(self.lblAudioPrgs6, self.streamInfo["Streams"][6], True)
self.set_label_name(self.lblAudioPrgs7, self.streamInfo["Streams"][7], True)
self.set_label_name(self.lblAudioSvcs0, self.streamInfo["Programs"][0], True)
self.set_label_name(self.lblAudioSvcs1, self.streamInfo["Programs"][1], True)
self.set_label_name(self.lblAudioSvcs2, self.streamInfo["Programs"][2], True)
self.set_label_name(self.lblAudioSvcs3, self.streamInfo["Programs"][3], True)
self.set_label_name(self.lblAudioSvcs4, self.streamInfo["Programs"][4], True)
self.set_label_name(self.lblAudioSvcs5, self.streamInfo["Programs"][5], True)
self.set_label_name(self.lblAudioSvcs6, self.streamInfo["Programs"][6], True)
self.set_label_name(self.lblAudioSvcs7, self.streamInfo["Programs"][7], True)
self.set_label_name(self.lblDataSvcs0, self.streamInfo["Services"][0], False)
self.set_label_name(self.lblDataSvcs1, self.streamInfo["Services"][1], False)
self.set_label_name(self.lblDataSvcs2, self.streamInfo["Services"][2], False)
@ -1372,7 +1400,7 @@ class NRSC5_DUI(object):
def checkPorts(self, port, type):
result = -1
for i in range(0,3):
for i in range(0,7):
if (len(self.streams[i]) > type):
if (port == self.streams[i][type]):
result = i
@ -1488,7 +1516,7 @@ class NRSC5_DUI(object):
self.debugLog("Found Stream: Type {:s}, Number {:02X}". format(t, s))
self.lastType = t
if (t == "audio" and s >= 1 and s <= 4):
if (t == "audio" and s >= 1 and s <= 8):
self.numStreams = s
self.streamInfo["Streams"][s-1] = n
if (t == "data"):
@ -1593,18 +1621,34 @@ class NRSC5_DUI(object):
self.btnAudioPrgs1 = builder.get_object("btn_audio_prgs1")
self.btnAudioPrgs2 = builder.get_object("btn_audio_prgs2")
self.btnAudioPrgs3 = builder.get_object("btn_audio_prgs3")
self.btnAudioPrgs4 = builder.get_object("btn_audio_prgs4")
self.btnAudioPrgs5 = builder.get_object("btn_audio_prgs5")
self.btnAudioPrgs6 = builder.get_object("btn_audio_prgs6")
self.btnAudioPrgs7 = builder.get_object("btn_audio_prgs7")
self.btnAudioLbl0 = builder.get_object("btn_audio_lbl0")
self.btnAudioLbl1 = builder.get_object("btn_audio_lbl1")
self.btnAudioLbl2 = builder.get_object("btn_audio_lbl2")
self.btnAudioLbl3 = builder.get_object("btn_audio_lbl3")
self.btnAudioLbl4 = builder.get_object("btn_audio_lbl4")
self.btnAudioLbl5 = builder.get_object("btn_audio_lbl5")
self.btnAudioLbl6 = builder.get_object("btn_audio_lbl6")
self.btnAudioLbl7 = builder.get_object("btn_audio_lbl7")
self.lblAudioPrgs0 = builder.get_object("lbl_audio_prgs0")
self.lblAudioPrgs1 = builder.get_object("lbl_audio_prgs1")
self.lblAudioPrgs2 = builder.get_object("lbl_audio_prgs2")
self.lblAudioPrgs3 = builder.get_object("lbl_audio_prgs3")
self.lblAudioPrgs4 = builder.get_object("lbl_audio_prgs4")
self.lblAudioPrgs5 = builder.get_object("lbl_audio_prgs5")
self.lblAudioPrgs6 = builder.get_object("lbl_audio_prgs6")
self.lblAudioPrgs7 = builder.get_object("lbl_audio_prgs7")
self.lblAudioSvcs0 = builder.get_object("lbl_audio_svcs0")
self.lblAudioSvcs1 = builder.get_object("lbl_audio_svcs1")
self.lblAudioSvcs2 = builder.get_object("lbl_audio_svcs2")
self.lblAudioSvcs3 = builder.get_object("lbl_audio_svcs3")
self.lblAudioSvcs4 = builder.get_object("lbl_audio_svcs4")
self.lblAudioSvcs5 = builder.get_object("lbl_audio_svcs5")
self.lblAudioSvcs6 = builder.get_object("lbl_audio_svcs6")
self.lblAudioSvcs7 = builder.get_object("lbl_audio_svcs7")
self.lblDataSvcs0 = builder.get_object("lbl_data_svcs0")
self.lblDataSvcs1 = builder.get_object("lbl_data_svcs1")
self.lblDataSvcs2 = builder.get_object("lbl_data_svcs2")
@ -1659,8 +1703,8 @@ class NRSC5_DUI(object):
"Artist": "", # track artist
"Cover": "", # filename of track cover
"Logo": "", # station logo
"Streams": ["","","",""], # audio stream names
"Programs": ["","","",""], # audio stream types
"Streams": ["","","","","","","",""], # audio stream names
"Programs": ["","","","","","","",""], # audio stream types
"Services": ["","","",""], # data service names
"SvcTypes": ["","","",""], # data service types
"Bitrate": 0, # current stream bit rate
@ -1669,7 +1713,7 @@ class NRSC5_DUI(object):
"Gain": 0 # automatic gain
}
self.streams = [[],[],[],[]]
self.streams = [[],[],[],[],[],[],[],[]]
self.numStreams = 0
self.numServices = 0
self.lastType = 0
@ -1680,6 +1724,10 @@ class NRSC5_DUI(object):
self.btnAudioLbl1.set_label("")
self.btnAudioLbl2.set_label("")
self.btnAudioLbl3.set_label("")
self.btnAudioLbl4.set_label("")
self.btnAudioLbl5.set_label("")
self.btnAudioLbl6.set_label("")
self.btnAudioLbl7.set_label("")
self.lblBitRate.set_label("")
self.lblBitRate2.set_label("")
self.lblError.set_label("")
@ -1699,6 +1747,10 @@ class NRSC5_DUI(object):
self.btnAudioPrgs1.set_sensitive(False)
self.btnAudioPrgs2.set_sensitive(False)
self.btnAudioPrgs3.set_sensitive(False)
self.btnAudioPrgs4.set_sensitive(False)
self.btnAudioPrgs5.set_sensitive(False)
self.btnAudioPrgs6.set_sensitive(False)
self.btnAudioPrgs7.set_sensitive(False)
self.lblAudioPrgs0.set_label("")
self.lblAudioPrgs0.set_sensitive(False)
self.lblAudioPrgs1.set_label("")
@ -1707,6 +1759,14 @@ class NRSC5_DUI(object):
self.lblAudioPrgs2.set_sensitive(False)
self.lblAudioPrgs3.set_label("")
self.lblAudioPrgs3.set_sensitive(False)
self.lblAudioPrgs4.set_label("")
self.lblAudioPrgs4.set_sensitive(False)
self.lblAudioPrgs5.set_label("")
self.lblAudioPrgs5.set_sensitive(False)
self.lblAudioPrgs6.set_label("")
self.lblAudioPrgs6.set_sensitive(False)
self.lblAudioPrgs7.set_label("")
self.lblAudioPrgs7.set_sensitive(False)
self.lblAudioSvcs0.set_label("")
self.lblAudioSvcs0.set_sensitive(False)
self.lblAudioSvcs1.set_label("")
@ -1715,6 +1775,14 @@ class NRSC5_DUI(object):
self.lblAudioSvcs2.set_sensitive(False)
self.lblAudioSvcs3.set_label("")
self.lblAudioSvcs3.set_sensitive(False)
self.lblAudioSvcs4.set_label("")
self.lblAudioSvcs4.set_sensitive(False)
self.lblAudioSvcs5.set_label("")
self.lblAudioSvcs5.set_sensitive(False)
self.lblAudioSvcs6.set_label("")
self.lblAudioSvcs6.set_sensitive(False)
self.lblAudioSvcs7.set_label("")
self.lblAudioSvcs7.set_sensitive(False)
self.lblDataSvcs0.set_label("")
self.lblDataSvcs1.set_label("")
self.lblDataSvcs2.set_label("")
@ -1740,6 +1808,9 @@ class NRSC5_DUI(object):
if (os.path.isfile(stationLogos)):
with open(stationLogos, mode='r') as f:
self.stationLogos = json.load(f)
for station in self.stationLogos:
while (len(self.stationLogos[station]) < 8):
self.stationLogos[station].append("")
except:
self.debugLog("Error: Unable to load station logo database", True)

View File

@ -263,7 +263,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_columns">4</property>
<property name="n_rows">1</property>
<property name="n_rows">2</property>
<property name="column_spacing">3</property>
<property name="homogeneous">True</property>
<child>
@ -346,6 +346,94 @@
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="btn_audio_prgs4">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkLabel" id="btn_audio_lbl4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="btn_audio_prgs5">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkLabel" id="btn_audio_lbl5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="btn_audio_prgs6">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkLabel" id="btn_audio_lbl6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="btn_audio_prgs7">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkLabel" id="btn_audio_lbl7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
</object>
</child>
</object>
@ -421,7 +509,7 @@
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">13</property>
<property name="n_rows">15</property>
<property name="n_columns">5</property>
<child>
<object class="GtkLabel" id="label6">
@ -608,7 +696,7 @@
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">7</property>
<property name="bottom_attach">9</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
@ -757,6 +845,150 @@
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_prgs4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_prgs5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_prgs6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_prgs7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">4</property>
<property name="right_attach">5</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_svcs4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_svcs5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_svcs6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lbl_audio_svcs7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">4</property>
<property name="right_attach">5</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="data_svcs">
<property name="visible">True</property>
@ -768,8 +1000,8 @@
<property name="xalign">1</property>
</object>
<packing>
<property name="top_attach">7</property>
<property name="bottom_attach">9</property>
<property name="top_attach">9</property>
<property name="bottom_attach">11</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
@ -787,8 +1019,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -805,8 +1037,8 @@
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -823,8 +1055,8 @@
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -841,8 +1073,8 @@
<packing>
<property name="left_attach">4</property>
<property name="right_attach">5</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -859,8 +1091,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -877,8 +1109,8 @@
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -895,8 +1127,8 @@
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -913,8 +1145,8 @@
<packing>
<property name="left_attach">4</property>
<property name="right_attach">5</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -925,8 +1157,8 @@
</object>
<packing>
<property name="right_attach">5</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="top_attach">11</property>
<property name="bottom_attach">12</property>
</packing>
</child>
<child>
@ -940,8 +1172,8 @@
<property name="xalign">1</property>
</object>
<packing>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="top_attach">12</property>
<property name="bottom_attach">13</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
@ -957,8 +1189,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="top_attach">12</property>
<property name="bottom_attach">13</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -973,8 +1205,8 @@
<packing>
<property name="left_attach">3</property>
<property name="right_attach">5</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="top_attach">12</property>
<property name="bottom_attach">13</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -989,8 +1221,8 @@
<property name="xalign">1</property>
</object>
<packing>
<property name="top_attach">11</property>
<property name="bottom_attach">13</property>
<property name="top_attach">13</property>
<property name="bottom_attach">15</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
@ -1006,8 +1238,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">11</property>
<property name="bottom_attach">12</property>
<property name="top_attach">13</property>
<property name="bottom_attach">14</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -1022,8 +1254,8 @@
<packing>
<property name="left_attach">3</property>
<property name="right_attach">5</property>
<property name="top_attach">11</property>
<property name="bottom_attach">12</property>
<property name="top_attach">13</property>
<property name="bottom_attach">14</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -1038,8 +1270,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">12</property>
<property name="bottom_attach">13</property>
<property name="top_attach">14</property>
<property name="bottom_attach">15</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
@ -1054,8 +1286,8 @@
<packing>
<property name="left_attach">3</property>
<property name="right_attach">5</property>
<property name="top_attach">12</property>
<property name="bottom_attach">13</property>
<property name="top_attach">14</property>
<property name="bottom_attach">15</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>