From 278c5731944184b72ec817226a8f0e6f9234f9de Mon Sep 17 00:00:00 2001 From: Iuliana Prodan Date: Thu, 6 Feb 2025 20:04:20 +0200 Subject: [PATCH 1/5] tools: demo-gui: improve error handling and readability in gui controller engine Improve error handling and readability by: - adding try-except blocks for better error handling; - enhancing error messages and checks; - removing redundant print statements. Signed-off-by: Iuliana Prodan --- .../demo-gui/sof_controller_engine.py | 96 +++++++++++-------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/tools/demo-gui/demo-gui/sof_controller_engine.py b/tools/demo-gui/demo-gui/sof_controller_engine.py index 25d40b820b8a..b871a9101d53 100644 --- a/tools/demo-gui/demo-gui/sof_controller_engine.py +++ b/tools/demo-gui/demo-gui/sof_controller_engine.py @@ -6,7 +6,7 @@ import re import math -# Global variables to store the aplay/arecord process, paused state, current file, detected device, volume control, and EQ numid +# Global variables aplay_process = None arecord_process = None paused = None @@ -22,24 +22,20 @@ def initialize_device(): try: output = subprocess.check_output(["aplay", "-l"], text=True, stderr=subprocess.DEVNULL) - match = re.search(r"card (\d+):.*\[.*sof.*\]", output, re.IGNORECASE) if match: card_number = match.group(1) device_string = f"hw:{card_number}" print(f"Detected SOF card: {device_string}") else: - print("No SOF card found.") raise RuntimeError("SOF card not found. Ensure the device is connected and recognized by the system.") controls_output = subprocess.check_output(["amixer", f"-D{device_string}", "controls"], text=True, stderr=subprocess.DEVNULL) - volume_match = re.search(r"numid=(\d+),iface=MIXER,name='(.*Master Playback Volume.*)'", controls_output) if volume_match: volume_control = volume_match.group(2) print(f"Detected Volume Control: {volume_control}") else: - print("Master GUI Playback Volume control not found.") raise RuntimeError("Volume control not found.") eq_match = re.search(r"numid=(\d+),iface=MIXER,name='EQIIR1\.0 eqiir_coef_1'", controls_output) @@ -47,33 +43,38 @@ def initialize_device(): eq_numid = eq_match.group(1) print(f"Detected EQ numid: {eq_numid}") else: - print("EQ control not found.") raise RuntimeError("EQ control not found.") except subprocess.CalledProcessError as e: print(f"Failed to run device detection commands: {e}") raise + except RuntimeError as e: + print(e) + raise def scale_volume(user_volume): normalized_volume = user_volume / 100.0 - scaled_volume = 31 * (math.sqrt(normalized_volume)) + scaled_volume = 31 * math.sqrt(normalized_volume) return int(round(scaled_volume)) def scan_for_files(directory_name: str, file_extension: str, extra_paths: list = None): found_files = [] dir_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), directory_name) - if os.path.exists(dir_path): - found_files.extend([f for f in os.listdir(dir_path) if f.endswith(file_extension)]) - else: - print(f"Error: The '{directory_name}' directory is missing. It should be located in the same folder as this script.") - - if extra_paths: - for path in extra_paths: - if os.path.exists(path): - found_files.extend([f for f in os.listdir(path) if f.endswith(file_extension)]) - else: - print(f"Warning: The directory '{path}' does not exist.") + try: + if os.path.exists(dir_path): + found_files.extend([f for f in os.listdir(dir_path) if f.endswith(file_extension)]) + else: + print(f"Error: The '{directory_name}' directory is missing. It should be located in the same folder as this script.") + + if extra_paths: + for path in extra_paths: + if os.path.exists(path): + found_files.extend([f for f in os.listdir(path) if f.endswith(file_extension)]) + else: + print(f"Warning: The directory '{path}' does not exist.") + except Exception as e: + print(f"Error scanning for files: {e}") return found_files @@ -90,7 +91,10 @@ def execute_command(command: str, data: int = 0, file_name: str = None): } command_function = command_switch.get(command, lambda x: handle_unknown_command(data)) - command_function(data) + try: + command_function(data) + except Exception as e: + print(f"Error executing command '{command}': {e}") def handle_volume(data: int): amixer_command = f"amixer -D{device_string} cset name='{volume_control}' {data}" @@ -100,6 +104,10 @@ def handle_volume(data: int): print(f"Failed to set volume: {e}") def handle_eq(eq_file_name: str): + if not eq_file_name: + print("No EQ file name provided.") + return + ctl_command = f"./sof-ctl -D{device_string} -n {eq_numid} -s {eq_file_name}" try: subprocess.run(ctl_command, shell=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) @@ -109,39 +117,44 @@ def handle_eq(eq_file_name: str): def handle_play(play_file_name: str): global aplay_process, paused, current_file - if paused and paused is not None and current_file == play_file_name: - os.kill(aplay_process.pid, signal.SIGCONT) - print("Playback resumed.") - paused = False + if not play_file_name: + print("No file name provided for playback.") return - if aplay_process is not None: - if aplay_process.poll() is None: - if current_file == play_file_name: - print("Playback is already in progress.") - return - else: - os.kill(aplay_process.pid, signal.SIGKILL) - print("Stopping current playback to play a new file.") - else: - print("Previous process is not running, starting new playback.") + try: + if paused and current_file == play_file_name: + os.kill(aplay_process.pid, signal.SIGCONT) + print("Playback resumed.") + paused = False + return - default_audio_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'audios') - file_path = next((os.path.join(path, play_file_name) for path in [default_audio_dir] + extra_audio_paths if os.path.exists(os.path.join(path, play_file_name))), None) + if aplay_process is not None: + if aplay_process.poll() is None: + if current_file == play_file_name: + print("Playback is already in progress.") + return + else: + os.kill(aplay_process.pid, signal.SIGKILL) + print("Stopping current playback to play a new file.") + else: + print("Previous process is not running, starting new playback.") - if file_path is None: - print(f"Error: File '{play_file_name}' not found in the default 'audios' directory or any provided paths.") - return + default_audio_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'audios') + file_path = next((os.path.join(path, play_file_name) for path in [default_audio_dir] + extra_audio_paths if os.path.exists(os.path.join(path, play_file_name))), None) - aplay_command = f"aplay -D{device_string} '{file_path}'" + if file_path is None: + print(f"Error: File '{play_file_name}' not found in the default 'audios' directory or any provided paths.") + return - try: + aplay_command = f"aplay -D{device_string} '{file_path}'" aplay_process = subprocess.Popen(aplay_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) current_file = play_file_name print(f"Playing file: {play_file_name}.") paused = False except subprocess.CalledProcessError as e: print(f"Failed to play file: {e}") + except Exception as e: + print(f"Error during playback: {e}") def handle_pause(): global aplay_process, paused @@ -174,7 +187,6 @@ def handle_record(start: bool, filename: str): return record_command = f"arecord -D{device_string} -f cd -t wav {filename}" - try: arecord_process = subprocess.Popen(record_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) print(f"Started recording: {filename}") @@ -189,7 +201,7 @@ def handle_record(start: bool, filename: str): try: os.kill(arecord_process.pid, signal.SIGINT) arecord_process = None - print(f"Stopped recording.") + print("Stopped recording.") except Exception as e: print(f"Failed to stop recording: {e}") From 65deba1edfabb697d792798e1ee180e66dcb6cf2 Mon Sep 17 00:00:00 2001 From: Iuliana Prodan Date: Fri, 7 Feb 2025 00:10:00 +0200 Subject: [PATCH 2/5] tools: demo-gui: refactor GUI initialization and structure Added apply_css() and init_ui() methods for better organization. Implemented create_control_frame(), create_file_frame(), and create_record_frame() methods to improve readability and maintainability. Signed-off-by: Iuliana Prodan --- tools/demo-gui/demo-gui/sof_demo_gui.py | 41 +++++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/tools/demo-gui/demo-gui/sof_demo_gui.py b/tools/demo-gui/demo-gui/sof_demo_gui.py index 37174de7b1a7..6de47f5d27a1 100644 --- a/tools/demo-gui/demo-gui/sof_demo_gui.py +++ b/tools/demo-gui/demo-gui/sof_demo_gui.py @@ -12,7 +12,13 @@ def __init__(self, audio_paths, config_paths): super().__init__(title="SOF Demo Gui") self.set_resizable(False) self.set_border_width(10) + self.apply_css() + self.init_ui(audio_paths, config_paths) + sof_ctl.initialize_device() + self.is_muted = False + self.previous_volume = 50 # Store the previous volume level + def apply_css(self): css_provider = Gtk.CssProvider() css = """ * { @@ -64,35 +70,49 @@ def __init__(self, audio_paths, config_paths): screen = Gdk.Screen.get_default() Gtk.StyleContext.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + def init_ui(self, audio_paths, config_paths): main_grid = Gtk.Grid() main_grid.set_row_spacing(10) main_grid.set_column_spacing(10) self.add(main_grid) + control_frame = self.create_control_frame() + main_grid.attach(control_frame, 0, 0, 1, 1) + + file_frame = self.create_file_frame() + main_grid.attach(file_frame, 0, 1, 1, 1) + + record_frame = self.create_record_frame() + main_grid.attach(record_frame, 0, 2, 1, 1) + + self.scan_and_populate_dropdowns(audio_paths, config_paths) + + def create_control_frame(self): control_frame = Gtk.Frame(label="Playback and Volume Control") control_grid = Gtk.Grid() control_grid.set_row_spacing(10) control_grid.set_column_spacing(10) control_frame.add(control_grid) - main_grid.attach(control_frame, 0, 0, 1, 1) - - self.play_pause_button = Gtk.ToggleButton(label="Play") - self.play_pause_button.connect("toggled", self.on_play_pause_toggled) - control_grid.attach(self.play_pause_button, 0, 0, 1, 1) volume_adjustment = Gtk.Adjustment(value=100, lower=0, upper=100, step_increment=1, page_increment=5, page_size=0) self.volume_button = Gtk.Scale(name="volume", orientation=Gtk.Orientation.HORIZONTAL, adjustment=volume_adjustment) self.volume_button.set_digits(0) self.volume_button.set_hexpand(True) self.volume_button.connect("value-changed", self.on_volume_changed) - control_grid.attach(self.volume_button, 1, 0, 1, 1) + control_grid.attach(self.volume_button, 0, 0, 2, 1) + + self.play_pause_button = Gtk.ToggleButton(label="Play") + self.play_pause_button.connect("toggled", self.on_play_pause_toggled) + control_grid.attach(self.play_pause_button, 0, 1, 1, 1) + + return control_frame + def create_file_frame(self): file_frame = Gtk.Frame(label="File Selection") file_grid = Gtk.Grid() file_grid.set_row_spacing(10) file_grid.set_column_spacing(10) file_frame.add(file_grid) - main_grid.attach(file_frame, 0, 1, 1, 1) self.wav_dropdown = Gtk.ComboBoxText() self.wav_dropdown.connect("changed", self.on_wav_file_selected) @@ -113,6 +133,9 @@ def __init__(self, audio_paths, config_paths): self.apply_eq_button.connect("clicked", self.on_apply_eq_clicked) file_grid.attach(self.apply_eq_button, 1, 2, 1, 1) + return file_frame + + def create_record_frame(self): record_frame = Gtk.Frame(label="Recording Control") record_grid = Gtk.Grid() record_grid.set_row_spacing(10) @@ -120,7 +143,6 @@ def __init__(self, audio_paths, config_paths): record_grid.set_hexpand(True) record_grid.set_vexpand(True) record_frame.add(record_grid) - main_grid.attach(record_frame, 0, 2, 1, 1) self.record_button = Gtk.ToggleButton(label="Record") self.record_button.connect("toggled", self.on_record_toggled) @@ -130,8 +152,7 @@ def __init__(self, audio_paths, config_paths): self.record_index = 1 - self.scan_and_populate_dropdowns(audio_paths, config_paths) - sof_ctl.initialize_device() + return record_frame def scan_and_populate_dropdowns(self, audio_paths, config_paths): wav_files = sof_ctl.scan_for_files('audios', '.wav', extra_paths=audio_paths) From f459a2d5450fdd673987136ff07116c4da9caf99 Mon Sep 17 00:00:00 2001 From: Iuliana Prodan Date: Fri, 7 Feb 2025 00:15:50 +0200 Subject: [PATCH 3/5] tools: demo-gui: add stop functionality to playback Added handle_stop() function to stop playback. Updated execute_command() to support the stop command. Added Stop button to GUI and linked it to on_stop_clicked() method. Signed-off-by: Iuliana Prodan --- .../demo-gui/sof_controller_engine.py | 21 +++++++++++++++++++ tools/demo-gui/demo-gui/sof_demo_gui.py | 8 +++++++ 2 files changed, 29 insertions(+) diff --git a/tools/demo-gui/demo-gui/sof_controller_engine.py b/tools/demo-gui/demo-gui/sof_controller_engine.py index b871a9101d53..a4816eaf306a 100644 --- a/tools/demo-gui/demo-gui/sof_controller_engine.py +++ b/tools/demo-gui/demo-gui/sof_controller_engine.py @@ -87,6 +87,7 @@ def execute_command(command: str, data: int = 0, file_name: str = None): 'eq': lambda x: handle_eq(file_name), 'play': lambda x: handle_play(file_name), 'pause': lambda x: handle_pause(), + 'stop': lambda x: handle_stop(), 'record': lambda x: handle_record(start=data, filename=file_name) } @@ -174,6 +175,26 @@ def handle_pause(): except Exception as e: print(f"Failed to pause playback: {e}") +def handle_stop(): + global aplay_process, paused, current_file + + if aplay_process is None: + print("No playback process to stop.") + return + + if aplay_process.poll() is not None: + print("Playback process has already finished.") + return + + try: + os.kill(aplay_process.pid, signal.SIGKILL) + aplay_process = None + paused = False + current_file = None + print("Playback stopped.") + except Exception as e: + print(f"Failed to stop playback: {e}") + def handle_record(start: bool, filename: str): global arecord_process diff --git a/tools/demo-gui/demo-gui/sof_demo_gui.py b/tools/demo-gui/demo-gui/sof_demo_gui.py index 6de47f5d27a1..5d5e9d209e0b 100644 --- a/tools/demo-gui/demo-gui/sof_demo_gui.py +++ b/tools/demo-gui/demo-gui/sof_demo_gui.py @@ -105,6 +105,10 @@ def create_control_frame(self): self.play_pause_button.connect("toggled", self.on_play_pause_toggled) control_grid.attach(self.play_pause_button, 0, 1, 1, 1) + self.stop_button = Gtk.Button(label="Stop") + self.stop_button.connect("clicked", self.on_stop_clicked) + control_grid.attach(self.stop_button, 1, 1, 1, 1) + return control_frame def create_file_frame(self): @@ -188,6 +192,10 @@ def on_play_pause_toggled(self, widget): widget.set_label("Play") sof_ctl.execute_command(command="pause") + def on_stop_clicked(self, widget): + self.play_pause_button.set_active(False) + sof_ctl.execute_command(command="stop") + def on_record_toggled(self, widget): if widget.get_active(): widget.set_label("Stop Recording") From 1a12a5588bdf0d3983d38a0150563354b3f74d04 Mon Sep 17 00:00:00 2001 From: Iuliana Prodan Date: Fri, 7 Feb 2025 00:27:19 +0200 Subject: [PATCH 4/5] tools: demo-gui: add mute functionality to playback Introduced handle_mute() function to toggle audio mute state. Updated execute_command() to include mute command. Added mute button to GUI and linked it to on_mute_clicked() method. Signed-off-by: Iuliana Prodan --- tools/demo-gui/demo-gui/sof_controller_engine.py | 15 ++++++++++++++- tools/demo-gui/demo-gui/sof_demo_gui.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tools/demo-gui/demo-gui/sof_controller_engine.py b/tools/demo-gui/demo-gui/sof_controller_engine.py index a4816eaf306a..c1dd6c1d9294 100644 --- a/tools/demo-gui/demo-gui/sof_controller_engine.py +++ b/tools/demo-gui/demo-gui/sof_controller_engine.py @@ -14,7 +14,8 @@ device_string = None volume_control = None eq_numid = None - +is_muted = False +previous_volume = 50 # Store the previous volume level extra_audio_paths = [] def initialize_device(): @@ -88,6 +89,7 @@ def execute_command(command: str, data: int = 0, file_name: str = None): 'play': lambda x: handle_play(file_name), 'pause': lambda x: handle_pause(), 'stop': lambda x: handle_stop(), + 'mute': lambda x: handle_mute(), 'record': lambda x: handle_record(start=data, filename=file_name) } @@ -195,6 +197,17 @@ def handle_stop(): except Exception as e: print(f"Failed to stop playback: {e}") +def handle_mute(): + if not is_muted: + previous_volume = scale_volume(50) # Assume 50 is the current volume + handle_volume(0) + is_muted = True + print("Audio muted.") + else: + handle_volume(previous_volume) + is_muted = False + print("Audio unmuted.") + def handle_record(start: bool, filename: str): global arecord_process diff --git a/tools/demo-gui/demo-gui/sof_demo_gui.py b/tools/demo-gui/demo-gui/sof_demo_gui.py index 5d5e9d209e0b..b208023f25dc 100644 --- a/tools/demo-gui/demo-gui/sof_demo_gui.py +++ b/tools/demo-gui/demo-gui/sof_demo_gui.py @@ -109,6 +109,10 @@ def create_control_frame(self): self.stop_button.connect("clicked", self.on_stop_clicked) control_grid.attach(self.stop_button, 1, 1, 1, 1) + self.mute_button = Gtk.Button(label="Mute") + self.mute_button.connect("clicked", self.on_mute_clicked) + control_grid.attach(self.mute_button, 0, 2, 2, 1) + return control_frame def create_file_frame(self): @@ -196,6 +200,17 @@ def on_stop_clicked(self, widget): self.play_pause_button.set_active(False) sof_ctl.execute_command(command="stop") + def on_mute_clicked(self, widget): + if not self.is_muted: + self.previous_volume = self.volume_button.get_value() + self.volume_button.set_value(0) + self.is_muted = True + widget.set_label("Unmute") + else: + self.volume_button.set_value(self.previous_volume) + self.is_muted = False + widget.set_label("Mute") + def on_record_toggled(self, widget): if widget.get_active(): widget.set_label("Stop Recording") From 3968efedfe1efd923db8eaf2622b5b31d2fd1108 Mon Sep 17 00:00:00 2001 From: Iuliana Prodan Date: Fri, 7 Feb 2025 18:32:13 +0200 Subject: [PATCH 5/5] tools: demo-gui: update css style Update GUI color scheme for improved readability. Signed-off-by: Iuliana Prodan --- tools/demo-gui/demo-gui/sof_demo_gui.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/demo-gui/demo-gui/sof_demo_gui.py b/tools/demo-gui/demo-gui/sof_demo_gui.py index b208023f25dc..d4a49a8ad333 100644 --- a/tools/demo-gui/demo-gui/sof_demo_gui.py +++ b/tools/demo-gui/demo-gui/sof_demo_gui.py @@ -24,7 +24,7 @@ def apply_css(self): * { font-family: "Segoe UI", "Arial", sans-serif; font-size: 14px; - color: #FFFFFF; + color: #020202; } window { background-color: #2C3E50; @@ -38,29 +38,29 @@ def apply_css(self): } button, togglebutton { background-color: #3b3b3b; - background: #3b3b3b; border: none; padding: 10px; - color: #3b3b3b; + color: #020202; } button:hover, togglebutton:hover { background-color: #A9A9A9; - color: #A9A9A9; + color: #020202; } togglebutton:checked { background-color: #A9A9A9; - color: #A9A9A9; + color: #020202; } scale { background-color: #34495E; border-radius: 5px; + color: #FFFFFF; } label { - color: #A9A9A9; + color: #020202; } headerbar { background-color: #2C3E50; - color: #2C3E50; + color: #FFFFFF; } headerbar.titlebar { background: #2C3E50;