mainwindow.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/bin/python
  2. import gi
  3. import subprocess
  4. from gi.repository import Gtk, Gdk, GdkPixbuf
  5. # Core imports
  6. import app.core.presets as presets
  7. import app.core.state as state
  8. import app.core.config as config
  9. import app.core.lock as lock
  10. from app.core.presets import Preset
  11. from app.core.audio import Audio
  12. from app.ui.alert import Alert
  13. class MainWindow(Gtk.Window):
  14. '''
  15. Main window for Lyrebird
  16. '''
  17. def __init__(self):
  18. Gtk.Window.__init__(self, title='Lyrebird')
  19. self.set_border_width(10)
  20. self.set_size_request(600, 500)
  21. self.set_default_size(600, 500)
  22. self.alert = Alert(self)
  23. headerbar = Gtk.HeaderBar()
  24. headerbar.set_show_close_button(True)
  25. headerbar.props.title = 'Lyrebird'
  26. about_btn = Gtk.Button.new_from_icon_name('help-about-symbolic', Gtk.IconSize.BUTTON);
  27. about_btn.connect('clicked', self.about_clicked)
  28. headerbar.pack_start(about_btn)
  29. self.set_wmclass ('Lyrebird', 'Lyrebird')
  30. self.set_title('Lyrebird')
  31. self.set_titlebar(headerbar)
  32. # Set the icon
  33. self.set_icon_from_file('icon.png')
  34. # Create the lock file to ensure only one instance of Lyrebird is running at once
  35. lock_file = lock.place_lock()
  36. if lock_file is None:
  37. alert.show_error_markup("Lyrebird Already Running", "Only one instance of Lyrebird can be ran at a time.")
  38. exit(1)
  39. else:
  40. self.lock_file = lock_file
  41. # Load the configuration file
  42. try:
  43. state.config = config.load_config()
  44. except BaseException as e:
  45. print(f"[error] Failed to load config file: {str(e)}")
  46. self.alert.show_warning("Failed to Config File", f"Lyrebird failed to load config, your config.toml file is most likely malformed. See the console for further details.\n\nConfig file location: {config.config_path}")
  47. # load with default options
  48. state.config = config.Configuration()
  49. state.audio = Audio()
  50. # Unload the null sink module if there is one from last time.
  51. # The only reason there would be one already, is if the application was closed without
  52. # toggling the switch to off (aka a crash was experienced).
  53. state.audio.unload_pa_modules()
  54. state.loaded_presets = presets.DEFAULT_PRESETS
  55. try:
  56. load_presets_state = presets.load_presets()
  57. loaded_presets = load_presets_state["presets"]
  58. failed_presets = load_presets_state["failed"]
  59. state.loaded_presets += loaded_presets
  60. if len(failed_presets) > 0:
  61. msg = f"The following presets failed to import: {', '.join(failed_presets)}. See the console for more details."
  62. self.alert.show_warning("Failed to Import Presets", msg)
  63. except BaseException as e:
  64. print(f"[error] Failed to load custom presets: {str(e)}")
  65. self.alert.show_warning("Failed to Load Presets", f"Lyrebird failed to load custom presets, your presets.toml file is most likely malformed. See the console for further details.\n\nPresets file location: {config.presets_path}")
  66. # Build the UI
  67. self.build_ui()
  68. def build_ui(self):
  69. self.vbox = Gtk.VBox()
  70. # Toggle switch for Lyrebird
  71. self.hbox_toggle = Gtk.HBox()
  72. self.toggle_label = Gtk.Label('Toggle Lyrebird')
  73. self.toggle_label.set_halign(Gtk.Align.START)
  74. self.toggle_switch = Gtk.Switch()
  75. self.toggle_switch.set_size_request(10, 25)
  76. self.toggle_switch.connect('notify::active', self.toggle_activated)
  77. self.hbox_toggle.pack_start(self.toggle_label, False, False, 0)
  78. self.hbox_toggle.pack_end(self.toggle_switch, False, False, 0)
  79. # Pitch shift scale
  80. self.hbox_pitch = Gtk.HBox()
  81. self.pitch_label = Gtk.Label('Pitch Shift ')
  82. self.pitch_label.set_halign(Gtk.Align.START)
  83. self.pitch_adj = Gtk.Adjustment(0, -10, 10, 5, 10, 0)
  84. self.pitch_scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=self.pitch_adj)
  85. self.pitch_scale.set_valign(Gtk.Align.CENTER)
  86. self.pitch_scale.connect('value-changed', self.pitch_scale_moved)
  87. self.hbox_pitch.pack_start(self.pitch_label, False, False, 0)
  88. self.hbox_pitch.pack_end(self.pitch_scale, True, True, 0)
  89. # Flow box containing the presets
  90. self.effects_label = Gtk.Label()
  91. self.effects_label.set_markup('<b>Presets</b>')
  92. self.effects_label.set_halign(Gtk.Align.START)
  93. self.flowbox = Gtk.FlowBox()
  94. self.flowbox.set_valign(Gtk.Align.START)
  95. self.flowbox.set_max_children_per_line(5)
  96. self.flowbox.set_selection_mode(Gtk.SelectionMode.NONE)
  97. # Create the flow box items
  98. self.preset_buttons = self.create_flowbox_items(self.flowbox)
  99. self.preset_buttons[9].set_sensitive(False)
  100. self.vbox.pack_start(self.hbox_toggle, False, False, 5)
  101. self.vbox.pack_start(self.hbox_pitch, False, False, 5)
  102. self.vbox.pack_start(self.effects_label, False, False, 5)
  103. self.vbox.pack_end(self.flowbox, True, True, 0)
  104. self.add(self.vbox)
  105. def create_flowbox_items(self, flowbox):
  106. buttons = []
  107. for preset in state.loaded_presets:
  108. button = Gtk.Button()
  109. button.set_size_request(80, 80)
  110. buttons.append(button)
  111. button.set_label(preset.name)
  112. button.connect('clicked', self.preset_clicked)
  113. flowbox.add(button)
  114. return buttons
  115. # Event handlers
  116. def about_clicked(self, button):
  117. about = Gtk.AboutDialog()
  118. about.set_program_name('Lyrebird Voice Changer')
  119. about.set_version("v1.2.0")
  120. about.set_copyright('Copyright (c) 2020-2023 megabytesofrem, Harry Stanton')
  121. about.set_comments('Simple and powerful voice changer for Linux, written in Python & GTK.')
  122. about.set_logo(GdkPixbuf.Pixbuf.new_from_file('icon.png'))
  123. about.run()
  124. about.destroy()
  125. def get_current_present(self):
  126. default_preset = state.loaded_presets[9]
  127. return state.current_preset or default_preset
  128. def start_voice_changer(self):
  129. preset = self.get_current_present()
  130. pitch = self.pitch_scale.get_value()
  131. state.audio.run_sox(pitch, preset, state.config.buffer_size)
  132. def stop_voice_changer(self):
  133. state.audio.kill_sox()
  134. state.audio.unload_pa_modules()
  135. def toggle_activated(self, switch, gparam):
  136. if switch.get_active():
  137. # Load module-null-sink
  138. state.audio.load_pa_modules()
  139. # Kill the sox process
  140. state.audio.kill_sox()
  141. # Use the default preset, which is "Man" if the loaded preset is not found.
  142. self.start_voice_changer()
  143. else:
  144. self.stop_voice_changer()
  145. def pitch_scale_moved(self, event):
  146. if self.toggle_switch.get_active():
  147. state.audio.kill_sox()
  148. self.start_voice_changer()
  149. def preset_clicked(self, button):
  150. # Use a filter to find the currently selected preset
  151. current_preset = list(filter(lambda p: p.name == button.props.label, state.loaded_presets))[0]
  152. state.current_preset = current_preset
  153. for preset_button in self.preset_buttons:
  154. preset_button.set_sensitive(True)
  155. button.set_sensitive(False)
  156. if current_preset.pitch_value != None:
  157. # Set the pitch of the slider
  158. self.pitch_scale.set_value(float(current_preset.pitch_value))
  159. if self.toggle_switch.get_active():
  160. state.audio.kill_sox()
  161. self.start_voice_changer()
  162. def close(self, *args):
  163. state.audio.kill_sox()
  164. state.audio.unload_pa_modules()
  165. self.lock_file.close()
  166. lock.destroy_lock()
  167. Gtk.main_quit()