def on_modified(self, event): global source, scope path = event.src_path # Check if preamp input has changed, then RESET if STATE_PATH in path: new_source = read_state_from_disk()['input'] if source != new_source: source = new_source self.meter.reset() sleep(.25) # anti bouncing # Check if metadata album or title has changed, then RESET if PLAYER_META_PATH in path: md = read_metadata_from_disk() if not md: return # Ignore if scope is not a metadata field name if not scope in ('album', 'track'): return # (i) 'track' is named 'title' in pe.audio.sys metadata fields md_key = scope if (scope != 'track') else 'title' if md[md_key] != self.last_album_track: self.last_album_track = md[md_key] self.meter.reset() sleep(.25) # anti bouncing
def start_brutefir(): """ runs Brutefir, connects to pream_in_loop and resets .state file with extra_delay = 0 ms (bool) """ result = core.bf.restart_and_reconnect( ['pre_in_loop:output_1', 'pre_in_loop:output_2'], delay=0.0) # Ensuring that .state keeps extra_delay = 0 ms from start tmp_state = read_state_from_disk() tmp_state["extra_delay"] = 0.0 force_to_flush_file(STATE_PATH, json_dumps(tmp_state)) return result
def manage_amp_switch(mode): def get_amp_state(): result = 'n/a' try: with open(f'{AMP_STATE_PATH}', 'r') as f: tmp = f.read().strip() if tmp.lower() in ('0', 'off'): result = 'off' elif tmp.lower() in ('1', 'on'): result = 'on' except: pass return result def set_amp_state(mode): if 'amp_manager' in CONFIG: AMP_MANAGER = CONFIG['amp_manager'] else: return '(aux) amp_manager not configured' print(f'(aux) running \'{AMP_MANAGER.split("/")[-1]} {mode}\'') Popen(f'{AMP_MANAGER} {mode}', shell=True) sleep(1) return get_amp_state() cur_state = get_amp_state() new_state = '' if mode == 'state': result = cur_state elif mode == 'toggle': # if unknown state, this switch defaults to 'on' new_state = {'on': 'off', 'off': 'on'}.get(cur_state, 'on') elif mode in ('on', 'off'): new_state = mode else: result = '(aux) bad amp_switch option' if new_state: result = set_amp_state(new_state) # Optionally will stop the current player as per CONFIG if new_state == 'off': if 'amp_off_stops_player' in CONFIG and CONFIG['amp_off_stops_player']: curr_input = read_state_from_disk()['input'] if not curr_input.startswith('remote'): send_cmd('player pause', timeout=1) return result
def get_meta(): """ Returns a dictionary with the current track metadata including the involved source player """ md = METATEMPLATE.copy() source = read_state_from_disk()['input'] if 'librespot' in source or 'spotify' in source.lower(): if SPOTIFY_CLIENT == 'desktop': md = spotify_meta(md) elif SPOTIFY_CLIENT == 'librespot': md = librespot_meta(md) # source is spotify like but no client running has been detected: else: md['player'] = 'Spotify' elif source == 'mpd': md = mpd_meta(md) elif source == 'istreams': md = mplayer_get_meta(md, service='istreams') elif source == 'tdt' or 'dvb' in source: md = mplayer_get_meta(md, service='dvb') elif 'cd' in source: md = mplayer_get_meta(md, service='cdda') elif source.startswith('remote'): # For a 'remote.....' named source, it is expected to have # an IP address kind of in its jack_pname field: # jack_pname: X.X.X.X:PPPP # so this way we can query metadata from the remote address. host = SOURCES[source]["jack_pname"].split(':')[0] port = SOURCES[source]["jack_pname"].split(':')[-1] if is_IP(host): if not port.isdigit(): port = 9990 md = remote_get_meta(host, port) # If there is no artist or album information, let's use the source name if md['artist'] == '-' and md['album'] == '-': md['artist'] = f'- {source.upper()} -' return md
def playback_control(cmd, arg=''): """ Controls the playback, depending on the involved source player. returns: 'stop' | 'play' | 'pause' """ result = 'stop' source = read_state_from_disk()['input'] if source == 'mpd': result = mpd_control(cmd, arg) elif source.lower() == 'spotify': if SPOTIFY_CLIENT == 'desktop': result = spotify_control(cmd, arg) elif SPOTIFY_CLIENT == 'librespot': result = librespot_control(cmd) else: result = 'stop' elif 'tdt' in source or 'dvb' in source: result = mplayer_control(cmd=cmd, service='dvb') elif source in ['istreams', 'iradio']: result = mplayer_control(cmd=cmd, service='istreams') elif source == 'cd': result = mplayer_control(cmd=cmd, arg=arg, service='cdda') elif source.startswith('remote'): # For a 'remote.....' named source, it is expected to have # an IP address kind of in its jack_pname field: # jack_pname: X.X.X.X:PPPP # so this way we can query metadata from the remote address. host = SOURCES[source]["jack_pname"].split(':')[0] port = SOURCES[source]["jack_pname"].split(':')[-1] # (i) we assume that the remote pe.audio.sys listen at standard 9990 port if is_IP(host): if not port.isdigit(): port = 9990 result = remote_player_control(cmd=cmd, arg=arg, host=host, port=port) return result
def random_control(arg): """ Controls the random/shuffle playback mode (i) Currently only works with: MPD """ result = 'n/a' source = read_state_from_disk()['input'] if source == 'mpd': result = mpd_control('random', arg) elif source == 'spotify': if SPOTIFY_CLIENT == 'desktop': result = spotify_control('random', arg) elif SPOTIFY_CLIENT == 'librespot': result = librespot_control('random', arg) return result
def playlists_control(cmd, arg): """ Manage playlists. (i) Currently only works with: Spotify Desktop, MPD. """ result = [] source = read_state_from_disk()['input'] if source == 'mpd': result = mpd_playlists(cmd, arg) elif source == 'spotify': if SPOTIFY_CLIENT == 'desktop': result = spotify_playlists(cmd, arg) elif source == 'cd': result = mplayer_playlists(cmd=cmd, arg=arg, service='cdda') return result
def main_loop(alertdB=CFG['alertdB'], beep=CFG['beep']): level_ups = False beeped = False while True: # Reading the mouse ev = getMouseEvent() # Sending the order to pe.audio.sys if ev == 'buttonLeftDown': # Level -- send_cmd(f'level -{CFG["STEPdB"]} add', sender='mouse_volume', verbose=True) level_ups = False elif ev == 'buttonRightDown': # Level ++ send_cmd(f'level +{CFG["STEPdB"]} add', sender='mouse_volume', verbose=True) level_ups = True elif ev == 'buttonMid': # Mute toggle send_cmd('mute toggle', sender='mouse_volume', verbose=True) # Alert if crossed the headroom threshold if level_ups: level = read_state_from_disk()['level'] if (level + CFG['STEPdB']) >= alertdB: if not beeped and beep: print('(mouse_volume_daemon) BEEEEEEP, BEEEEEP') beeps() beeped = True else: beeped = False
def __init__(self): # The available inputs self.inputs = CONFIG["sources"] # The state dictionary self.state = read_state_from_disk() self.state["convolver_runs"] = bf.is_running() # will add some informative values: self.state["loudspeaker"] = CONFIG["loudspeaker"] self.state["loudspeaker_ref_SPL"] = CONFIG["refSPL"] self.state["peq_set"] = get_peq_in_use() self.state["fs"] = jack.get_samplerate() # The target curves available under the 'eq' folder self.target_sets = self._find_target_sets() # The available span for tone curves self.bass_span = int( (EQ_CURVES["bass_mag"].shape[0] - 1) / 2 ) self.treble_span = int( (EQ_CURVES["treb_mag"].shape[0] - 1) / 2 ) # Max authorised gain self.gain_max = float(CONFIG["gain_max"]) # Max authorised balance self.balance_max = float(CONFIG["balance_max"]) # Initiate brutefir input connected ports (used from switch_convolver) self.bf_sources = bf.get_in_connections() # Powersave # State file info self.state["powersave"] = False # # Powersave loop: breaking flag self.ps_end = threading.Event() # # Powersave loop:reset elapsed low level detected counter flag self.ps_reset_elapsed = threading.Event() # # Convolver driving events def wait_PS_convolver_off(): """ Event handler for convolver switch off requests """ while True: # waiting ... self.ps_convolver_off.wait() print(f'(core) Thread \'waits for convolver OFF\' received event') self.ps_convolver_off.clear() self.switch_convolver('off') # def wait_PS_convolver_on(): """ Event handler for convolver switch on requests """ while True: # waiting ... self.ps_convolver_on.wait() print(f'(core) Thread \'waits for convolver ON\' received event') self.ps_convolver_on.clear() self.switch_convolver('on') # self.ps_convolver_off = threading.Event() self.ps_convolver_on = threading.Event() t1 = threading.Thread( name='waits for convolver OFF', target=wait_PS_convolver_off ) t2 = threading.Thread( name='waits for convolver ON', target=wait_PS_convolver_on ) t1.start() t2.start() self._print_threads()
def update_lcd_state(scr='scr_1'): """ Reads system .state file, then updates the LCD """ # http://lcdproc.sourceforge.net/docs/lcdproc-0-5-5-user.html global state def show_state(priority="info"): ws = Widgets() global equal_loudness for key, value in state.items(): # The LU bar disables displaying bass and treble if LCD_CONFIG["LUmon_bar"]: if key in ('bass', 'treble'): continue # some state dict keys cannot have its # correspondence into the widgets_state dict if key not in ws.state: continue pos = ws.state[key]['pos'] # pos ~> position lbl = ws.state[key]['val'] # lbl ~> label # When booleans (equal_loudness, muted) # will leave the defalul widget value or will supress it if type(value) == bool: if not value: lbl = '' # Update global to be accesible outside from auxiliary if key == 'equal_loudness': equal_loudness = value # Special case: lu_offset will be rounded to integer # or void if no equal_loudness elif key == 'lu_offset': if state['equal_loudness']: lbl += str(int(round(value, 0))).rjust(3) else: lbl = 'LOUDc off' # Special case: tone will be rounded to integer elif key == 'bass' or key == 'treble': lbl += str(int(round(value, 0))).rjust(2) # Special case: midside (formerly 'mono') elif key == 'midside': if state['midside'] == 'off': lbl = '>ST<' elif state['midside'] == 'mid': lbl = 'MONO' elif state['midside'] == 'side': lbl = 'SIDE' else: lbl = '' # Special usage mono label to display if convolver is off if 'convolver_runs' in state and not state['convolver_runs']: lbl = ' zzz' # brutefir is sleeping # Any else key: else: lbl += str(value) # sintax for string widgets: # widget_set screen widget coordinate "text" cmd = f'widget_set {scr} {key} {pos} "{lbl}"' LCD.send(cmd) # The big screen to display the level value #lcdbig.show_level( str(state['level']) ) pass # Reading state try: new_state = read_state_from_disk() except: return # If changed if new_state != state: # update global state state = new_state # refreshing the LU monitor bar in LCD if needed if LCD_CONFIG["LUmon_bar"]: update_lcd_loudness_monitor() # refresh state items in LCD show_state()
# (i) Only import LU_meter when 'start' because it takes a long time, # so it can trouble the stop pkill (can be too much delayed). from audiotools.loudness_meter import LU_meter else: print(__doc__) sys.exit() else: print(__doc__) sys.exit() # Initialize the scope of the measurements (input, album or track) scope = get_configured_scope() # Initialize current preamp source source = read_state_from_disk()['input'] # Starts a LU_meter instance with relevant parameters: # M_threshold = 10.0 To avoid stress saving values to disk, because this # measure only serves as a rough signal detector. # I_threshold = 1.0 LU-[I]ntegrated values are relatively stable. meter = LU_meter(device='pre_in_loop', display=False, M_threshold=10.0, I_threshold=1.0) meter.start() print(f'(loudness_monitor) spawn PortAudio ports in JACK') # Threading the fifo listening loop for controlling this module prepare_control_fifo(LDCTRL_PATH) control = threading.Thread(target=control_fifo_read_loop,