Im building a bot for fun and to test my knowledge with python. A quick overview of the project:
It takes sound from a game , cross refrences it with a target sound, if target sound is detected do some actions.
i have a timer that runs from the time i press start in gui , when timer reaches zero it should reset back to its orignal duration to start again and take another action. if target sound is detected at some point during that timer, stop and reset timer ,take an action and then restart.
this works , for a while as intended, but then it just stops.
trying the timer without sound doesnt restart the timer at all which is not intended.
basically i need the timer to reach 0, reset, then start again in a loop until manually interrupted
Print statements at key locations confirm the timer gets reset , however the restart is never called , see Code below for 3 python files to handle the project
Main/GUI.py
import ttkbootstrap as tb
from ttkbootstrap.constants import *
import threading
import pyautogui
import time
from AudioProcessing import AudioStreamProcessor
from CountDown import CountdownTimer
# Disable pyautogui failsafe
pyautogui.FAILSAFE = False
# Global variables and initialization
font = "Roboto"
start_timer = None
fishing_timer = None
long_term_timer = None
audio_processor = None
is_started = False
default_duration = 7200 # 2 hours
# Initialize root window
root = tb.Window(themename='yeti')
root.title("The Lazy Fisher")
root.geometry("1024x900")
# GUI Elements (Defined later)
info_relay = None # Info display textbox
progress_meter = None # Casting progress bar
vendoring_meter = None # Vendoring progress bar
threshold_entry = None # Threshold entry field
timer_entry = None # Timer entry field
# Function Definitions
def add_message_to_gui(message, color):
if info_relay:
info_relay.configure(state=tb.NORMAL)
info_relay.insert(tb.END, f"{message}\n", color)
info_relay.yview(tb.END)
info_relay.configure(state=tb.DISABLED)
def update_threshold_value():
try:
threshold_value = float(threshold_entry.get())
audio_processor.THRESHOLD = threshold_value
add_message_to_gui(f"Threshold set to {threshold_value}", "white")
except ValueError:
add_message_to_gui("Invalid input for threshold!", "red")
def fish_hooked():
root.after(0, add_message_to_gui("Fish Hooked", "orange"))
def start_fish_timer():
global is_started, fishing_timer, long_term_timer
if not is_started:
is_started = True
if not fishing_timer.is_running():
fishing_timer.reset()
fishing_timer.start()
long_term_timer.reset()
long_term_timer.start()
add_message_to_gui("Fishing process started.", "green")
else:
add_message_to_gui("Fishing process is already running.", "orange")
def start_timer_update_callback(remaining):
root.after(0, add_message_to_gui(f"Starting in {remaining}", "orange"))
def start_timer_end_callback():
global fishing_timer, long_term_timer
fishing_timer.start()
long_term_timer.start()
pyautogui.press('1') # Automated action to start
add_message_to_gui("Fishing process started.", "green")
def fishing_timer_update_callback(remaining):
global is_started
if is_started:
root.after(0, add_message_to_gui(f"Recast in {remaining}", "white"))
progress_meter.configure(amountused=remaining)
def fishing_timer_end_callback():
try:
fishing_timer.reset()
fishing_timer.start()
add_message_to_gui("Timer reset and started.", "white")
except Exception as e:
print(f"Error in fishing_timer_end_callback: {e}")
def long_term_timer_update_callback(remaining):
global is_started
if is_started:
vendoring_meter.configure(amountused=remaining)
def long_term_timer_end_callback():
pyautogui.press('2')
time.sleep(4)
pyautogui.press('f')
time.sleep(3)
pyautogui.press('1')
long_term_timer.reset()
long_term_timer.start()
def stop_process():
global is_started
if is_started:
is_started = False
if fishing_timer.is_running():
fishing_timer.stop()
fishing_timer.reset()
if long_term_timer.is_running():
long_term_timer.stop()
long_term_timer.reset()
add_message_to_gui("Fishing process stopped.", "red")
else:
add_message_to_gui("Fishing process is not running.", "orange")
def start_audio_processing():
global audio_thread
audio_thread = threading.Thread(target=audio_processor.open_stream)
audio_thread.start()
add_message_to_gui("Audio processing started.", "green")
def stop_audio_processing():
global is_started, audio_thread
if is_started:
is_started = False
audio_processor.close_stream()
if audio_thread.is_alive():
audio_thread.join()
add_message_to_gui("Audio processing stopped.", "red")
def update_vend_time():
try:
vendorTime = float(timer_entry.get()) * 60
long_term_timer.update_duration(vendorTime)
add_message_to_gui(f"Vendoring in {vendorTime/60} minutes", "white")
vendoring_meter.configure(amounttotal=vendorTime / 60)
except ValueError:
add_message_to_gui("Enter your time in minutes only", "red")
# Initialize timers and audio processor
start_timer = CountdownTimer(5, start_timer_update_callback, start_timer_end_callback, None)
fishing_timer = CountdownTimer(5, fishing_timer_update_callback, fishing_timer_end_callback, audio_processor)
long_term_timer = CountdownTimer(default_duration, long_term_timer_update_callback, long_term_timer_end_callback, None)
audio_processor = AudioStreamProcessor(timer=fishing_timer, on_detection_callback=fish_hooked)
# Set timer in the audio processor
audio_processor.set_timer(fishing_timer)
title = tb.Label(root, text="The Lazy Fisher", font=("Lobster", 48))
title.pack(pady=20, padx=50)
# Frame for Entries
entry_frame = tb.Frame(root)
entry_frame.pack(fill="x", padx=100)
#Threshold Label
threshold_label = tb.Label(entry_frame, text="Threshold: ", font=("Lobster", 20), justify="left")
threshold_label.pack(side="left")
threshold_entry = tb.Entry(entry_frame)
threshold_entry.pack(side="left", expand=True)
threshold_button = tb.Button(entry_frame, command=update_threshold_value, text="Set", bootstyle="Primary-outline", width=20)
threshold_button.pack(side="left")
#Frame for Timers
timer_frame = tb.Frame(root)
timer_frame.pack(fill="x", pady=10, padx=100)
timer_label = tb.Label(timer_frame, text="Time Until Vend:", font=("Lobster", 20), justify="left")
timer_label.pack(side="left")
timer_entry = tb.Entry(timer_frame, bootstyle="secondary")
timer_entry.pack(side="left", expand=True)
timer_button = tb.Button(timer_frame, text="Set", command=update_vend_time, bootstyle="secondary-outline", width=20)
timer_button.pack(side="right")
#Start/Stop Buttons Frame
state_button_frame = tb.Frame(root)
state_button_frame.pack()
#Start Button
start_button = tb.Button(state_button_frame, command=start_fish_timer, text="Start", bootstyle="success-outline", width=100)
start_button.pack(pady=5)
#Start Button
stop_button = tb.Button(state_button_frame, command=stop_process, text="Stop", bootstyle="danger-outline", width=100)
stop_button.pack(pady=5)
#Frame for Progress Bars
progress_frame = tb.Frame(root)
progress_frame.pack(pady=10)
#casting frame
casting_frame = tb.Frame(progress_frame)
casting_frame.pack(side="left", padx=20)
progress_label = tb.Label(casting_frame, text="Casting", font=("Lobster", 20), justify="center")
progress_label.pack(pady=10)
progress_meter = tb.Meter(casting_frame, bootstyle="success",amounttotal=fishing_timer.duration, amountused=fishing_timer.remaining ,wedgesize=10, metersize=100 )
progress_meter.pack()
#Vendoring Frame
vendoring_frame = tb.Frame(progress_frame)
vendoring_frame.pack(side="right", padx=20)
vendoring_label = tb.Label(vendoring_frame, text="Vendor in", font=("Lobster", 20), justify="center")
vendoring_label.pack(pady=10)
vendoring_meter = tb.Meter(vendoring_frame, bootstyle="danger",wedgesize=0,amounttotal=default_duration, amountused=long_term_timer.remaining ,metersize=100)
vendoring_meter.pack()
#TextBox Frame
textbox_frame = tb.Frame(root)
textbox_frame.pack(pady=5)
#textbox
info_relay = tb.Text(textbox_frame, width=300, height=300, state=tb.DISABLED)
info_relay.pack(padx=20)
info_relay.tag_config('green', foreground='green')
info_relay.tag_config('orange', foreground='orange')
info_relay.tag_config('red', foreground='red')
info_relay.tag_config('white', foreground='white')
# Start audio processing
start_audio_processing()
# Main loop
try:
root.mainloop()
except KeyboardInterrupt:
audio_processor.close_stream()
root.quit()
*** Custom AudioProcessor ***
from pydub import AudioSegment
import numpy as np
import pyaudio
from scipy.signal import correlate
from time import time
# ... other imports ...
# Audio stream parameters
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
CHUNK = 2048
VIRTUAL_CABLE_INDEX = 1
class AudioStreamProcessor:
def __init__(self, timer=None, on_detection_callback=None):
self.p = pyaudio.PyAudio()
self.stream = None
self.max_correlation = float(0)
self.THRESHOLD = float(25)
self.detected = False
self.on_detection_callback = on_detection_callback
self.timer = timer
self.last_detected_time = time.time()
self.debounce_period = 1
# Load the WAV file and prepare the target sound
wav_path = r"D:\fishing\fish\FishingBobber_ver2_2.wav"
target_sound = AudioSegment.from_wav(wav_path)
# Prepare the target sound array
target_sound_array = np.array(target_sound.get_array_of_samples(), dtype=np.float32)
if target_sound.channels == 2:
target_sound_array = target_sound_array.reshape(-1, 2)
target_sound_array = np.mean(target_sound_array, axis=1)
self.target_sound_array = target_sound_array / np.max(np.abs(target_sound_array))
def set_timer(self, timer):
self.timer = timer
def callback(self, in_data, frame_count, time_info, status):
audio_data = np.frombuffer(in_data, dtype=np.int16)
if CHANNELS == 2:
audio_data = audio_data.reshape(-1, 2)
audio_data = audio_data[:, 0]
if np.any(audio_data):
audio_data = audio_data / np.max(np.abs(audio_data))
correlation = correlate(audio_data, self.target_sound_array, mode="valid")
self.max_correlation = np.max(correlation)
current_time = time()
if self.max_correlation > self.THRESHOLD:
# Check if 2 seconds have passed since the last detection
if current_time - self.last_detected_time > self.debounce_period:
self.last_detected_time = current_time # Update the last detection time
self.detected = True # Set detected flag to True
if self.timer:
self.timer.handle_detection()
if self.on_detection_callback:
self.on_detection_callback()
else:
# If 2 seconds haven't passed, ignore this detection (debounce)
self.detected = False
else:
# If the correlation is not greater than the threshold
self.detected = False
return (in_data, pyaudio.paContinue)
def open_stream(self):
if self.stream is None:
self.stream = self.p.open(rate=RATE, channels=CHANNELS, format=FORMAT,
frames_per_buffer=CHUNK, input=True,
input_device_index=VIRTUAL_CABLE_INDEX,
stream_callback=self.callback)
self.stream.start_stream()
def close_stream(self):
if self.stream is not None:
self.stream.stop_stream()
self.stream.close()
self.stream = None
self.p.terminate()
audio_processor = AudioStreamProcessor()
*** Custom CountDownTimer ***
import time
from threading import Thread
import pyautogui
class CountdownTimer:
def __init__(self, duration, update_callback, end_callback, audio_processor):
self.duration = duration
self.remaining = duration
self.update_callback = update_callback
self.end_callback = end_callback
self.audio_processor = audio_processor
self._timer_thread = None
self._stop_flag = False
self.last_start_time = 0
self.minimum_restart_interval = 2
def start(self):
if self._timer_thread is None:
self._stop_flag = False
self.last_start_time = time.time()
self._timer_thread = Thread(target=self._run)
self._timer_thread.start()
def update_duration(self, new_duration):
self.duration = new_duration
self.remaining = new_duration
def stop(self):
self._stop_flag = True
def reset(self):
self.remaining = self.duration
def is_running(self):
return self._timer_thread is not None and self._timer_thread.is_alive()
def _run(self):
while self.remaining > 0 and not self._stop_flag:
time.sleep(1)
self.remaining -= 1
# Call update_callback if it's set
if self.update_callback is not None:
self.update_callback(self.remaining)
# Handle detection if audio_processor is set and detection is found
if self.audio_processor and self.audio_processor.detected:
if hasattr(self, 'handle_detection'):
self.handle_detection()
break
if not self._stop_flag:
if self.end_callback:
self.end_callback()
self.last_start_time = time.time() # Update the start time for the new cycle
self._timer_thread = None
def handle_detection(self):
pyautogui.press('f')
time.sleep(2)
self.reset()
self.start()
pyautogui.press('1')
i have spent the last 5 hours trying to get this to work and i just cannot get my head around why its not working as intended.Iv gone over the callbacks again and again trying try except blocks , if statements and just straight up code blocks with no luck. Any insight would be greatly appreciated , please keep in mind i am still learning as i go. Will gladly take any knowledge to be shared to improve