diff --git a/tools/demo-gui/demo-gui/sof_controller_engine.py b/tools/demo-gui/demo-gui/sof_controller_engine.py index 25d40b820b8a..c1dd6c1d9294 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 @@ -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(): @@ -22,24 +23,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 +44,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 @@ -86,11 +88,16 @@ 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(), + 'mute': lambda x: handle_mute(), 'record': lambda x: handle_record(start=data, filename=file_name) } 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 +107,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 +120,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 + try: + if paused and current_file == play_file_name: + os.kill(aplay_process.pid, signal.SIGCONT) + print("Playback resumed.") + paused = False + 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: - 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.") + print("Previous process is not running, starting new playback.") - 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) + 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 file_path is None: - print(f"Error: File '{play_file_name}' not found in the default 'audios' directory or any provided paths.") - return - - 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 @@ -161,6 +177,37 @@ 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_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 @@ -174,7 +221,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 +235,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}") diff --git a/tools/demo-gui/demo-gui/sof_demo_gui.py b/tools/demo-gui/demo-gui/sof_demo_gui.py index 37174de7b1a7..d4a49a8ad333 100644 --- a/tools/demo-gui/demo-gui/sof_demo_gui.py +++ b/tools/demo-gui/demo-gui/sof_demo_gui.py @@ -12,13 +12,19 @@ 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 = """ * { font-family: "Segoe UI", "Arial", sans-serif; font-size: 14px; - color: #FFFFFF; + color: #020202; } window { background-color: #2C3E50; @@ -32,29 +38,29 @@ def __init__(self, audio_paths, config_paths): } 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; @@ -64,35 +70,57 @@ 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) + 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) + + 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): 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 +141,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 +151,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 +160,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) @@ -167,6 +196,21 @@ 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_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")