0

So, I wrote a little script to try out what's called a "microbreak" during my work. It's pretty simple, all it does is play a sound every random time between 20-30 minutes to notify me to take a break for a random range between 30 to 60 seconds. However, the winsound.Beep function is so infuriatingly inconsistent, or am I doing something wrong.

If I run python in the windows command prompt using "py", import winsound, and try a winsound.Beep it will not play, unless I run the command twice, only then the second time does it play.

If I turn what I described above into a python file and run it twice, the first time it runs, the only the second beep plays. The second time the file runs, both beeps play correctly??

In my script, its even worse. Sometimes it will work, sometimes it will skip, sometimes the duration is cut short. I literally have no idea why or how to recreate this.

I have python 3.10.7, Here is the code:

# Imports modules that are not accessible in the default scope
import time
import random
import winsound
import sys
import os

# Signals to the command prompt to use color mode
os.system('color')

# Function to achieve type-writer effect on print text
def typingPrint(text):
  for character in text:
    sys.stdout.write(character)
    sys.stdout.flush()
    time.sleep(0.01)

# Function to achieve type-writer effect on input text
def typingInput(text):
  for character in text:
    sys.stdout.write(character)
    sys.stdout.flush()
    time.sleep(0.01)
  value = input()  
  return value

# Function that returns a colored ANSI code for coloring text in the command prompts
def rgb(r, g, b): return f"\u001b[38;2;{r};{g};{b}m"

# ANSI color codes
yellow = rgb(255, 255, 0)
pink = rgb(255, 50, 255)
cyan = rgb(0, 255, 255)
green = rgb(50, 255, 50)
red = rgb(255, 50, 50)
reset = "\u001b[0m"

# Intro prints for information on the script
typingPrint(f"Every {yellow}random time{reset} between {yellow}20-30 minutes{reset}, you will be given a {green}random microbreak{reset} between {green}30-60 seconds{reset}.\n\n")
time.sleep(2)
typingPrint(f"{yellow}Work{reset} time will {red}NOT{reset} be {pink}announced{reset}, but {green}microbreak{reset} time {cyan}will be{reset} {pink}announced{reset} each cycle.\n\n")
time.sleep(1)
typingPrint(f"This script will keep running until {yellow}Ctrl+C{reset} is clicked\n\n")
time.sleep(1)
typingPrint(f"IMPORTANT: {yellow}One beep{reset} to get back to {yellow}work{reset}, {green}two for break{reset}. There will also be {pink}prints{reset} along with them like this one.\n\n")
time.sleep(2)

# Prompts the user to start the script
typingInput(f"{yellow}Press any key once ready.{reset}\n\n")
 
# Times stats
total_work = 0
total_break = 0
total = 0
 
try:
    while True:
        # Work time code block
        work_time = random.randint(20, 31) * 60                                           # The random range for work time
        print(f"Time to {yellow}work{reset}!\n")                                          # Print to notify work time began
        winsound.Beep(500, 1000)                                                          # Beep to notify work time began
        time.sleep(work_time)                                                             # Sleep (pause operations) for the entirety of work time
        total_work += work_time                                                           # Add work time to total work time
        
        # Microbreak time code block
        microbreak_time = random.randint(30, 61)                                          # The random range for microbreak time
        print(f"Take a {green}microbreak{reset} for: {green}{microbreak_time}{reset}\n")  # Print to notify microbreak time began
        winsound.Beep(1000, 500)                                                          # Beep to notify microbreak time began
        winsound.Beep(1000, 500)                                                          # Beep to notify microbreak time began
        time.sleep(microbreak_time)                                                       # Sleep (pause operations) for the entirety of microbreak time
        total_break += microbreak_time                                                    # Add microbreak time to total break time
        
        total = total_work + microbreak_time                                              # Sum the total time

# Stop the script if Ctrl+C is pressed.
except KeyboardInterrupt:
    # Prints the total work, total break, and overall total times.
    typingPrint(f"\n{red}Script was stopped{reset} using {yellow}Ctrl+C{reset}. Here is your stats:\t(Note: stats will only be counted once a full cycle was complete)\n")
    typingPrint(f"Total {yellow}work{reset} time: {yellow}{round(total_work/60, 2)}{reset} minutes ({yellow}{round(total_work/3600, 2)}{reset} hours)\n")
    typingPrint(f"Total {green}break{reset} time: {green}{round(total_break/60, 2)}{reset} minutes ({green}{round(total_break/3600, 2)}{reset} hours)\n")
    typingPrint(f"Total {cyan}time{reset}: {cyan}{round(total/60, 2)}{reset} minutes ({cyan}{round(total/3600, 2)}{reset} hours)\n\n")
    
    # Prompts the user to close the console by pressing any key
    typingInput(f"{red}Press any key to close this console.{reset}")

Edit: If I use Powershell to play a beep instead of Python, the same thing seems to happen. The first time it's played, there is nothing that plays, but the second time it does. Maybe it's not Python's fault but I literally have no idea why this happens.

  • After removing all of the printing and reducing the times to random 1-3 second periods, to allow for simple and quick testing, I am unable to reproduce your issue. Regardless of whether I have the IDE start the script, or run it directly from PowerShell or Command Prompt, it reliably produces the sound after the expected period. How do you start the script in the console, exactly? Do you leave the focus on the console window, or do you move it to the background (seems likely, given its function) - and are you always using the same application in the foreground? – Grismar Jun 04 '23 at 22:43
  • Also, you speak about the sound playing or not playing, but do you see the printing in both cases, or does it also not print when it doesn't play? – Grismar Jun 04 '23 at 22:46
  • @Grismar I have tried running the script in the cmd prompt by using "py microbreakScript.py", I have also tried running it directly by double-click. I have tried manually writing winsound.Beep on a fresh session of py in the cmdprompt, just winsound.Beep nothing else, first time of the session never plays. Yes, it prints the text regardless in the case of microbreakScript.py. The sound almost feels like its being cut short at times, while other times its fine. I have tested with both focused and unfocused. Could you test my script without removing any part? to deduce if the sleeps is the cause – Firefingertips Jun 05 '23 at 02:09
  • Of course, you could reduce the range to a few seconds between the loops. Keep in mind, I meant that I also tried using Powershell "[Console]::Beep()" command and the result is the same. First time doesn't play, second time does. The Beep problem seems to persist beyond the python script. Maybe my laptop is the issue, I just know how to test or fix that. It was literally working fine this morning and I did nothing to change that. It also worked long time ago when I was testing a little Powershell piano script using beeps on this same machine. – Firefingertips Jun 05 '23 at 02:14
  • Well, I ran it now multiple times and it's somehow fixed itself... I will test some more during the day and update this. – Firefingertips Jun 05 '23 at 02:25
  • 1
    It seems more like an issue with your audio setup, or perhaps other processes running that somehow interfere with sound coming from the console. Especially `[Console]::Beep()` causing the same issue is a pretty clear indication that this is no really a Python problem. – Grismar Jun 05 '23 at 04:19
  • @Grismar I did some more testing. Playing an audio file would cut the early part and only play from mid to last. It seems my audio driver, headphones, or whatever, is falling asleep every once in awhile, which makes a lot of sense for this behaviour. I had a youtube video playing in the background the beeps and audio files played normally. Do you have any idea how I could prevent whatever is falling asleep to not fall asleep? Apparently it's some "European environmental laws forcing electronic appliances to have an auto sleep function". – Firefingertips Jun 05 '23 at 05:37

1 Answers1

0

You indicated that the problem is that your audio playback device goes to sleep after a certain amount of time, to comply with energy preservation laws. There's probably not much you can do about that, but here's something to try to get around it.

Create a file that is just a few seconds of silence, called silence.wav (I created 5 seconds, but any reasonable range should work).

Add the following to your code:

winsound.PlaySound('silence.wav', winsound.SND_FILENAME | winsound.SND_ASYNC | winsound.SND_LOOP)

Your Beep calls will play over the top of this playback, but since the script is now technically constantly playing a sound, hopefully this prompts your device to remain active.

You may want to make sure the sound keeps playing in the background by first generating a tone instead of silence, and playing over the top of that. I'm not sure winsound behaves the same everywhere, or for any device.

I used the free Audacity to generate a .wav with silence and a tone, in case you don't have the appropriate software.

Grismar
  • 27,561
  • 4
  • 31
  • 54
  • Note that it's not a given that the device goes to sleep because no audio is played back - perhaps it's responding to you being away from your keyboard / mouse? In that case, it's not too bad, since you hardly need microbreaks from not using your computer. However, in that case you may instead want to detect the user arriving at the keyboard again, and send out a quick silence to wake up the hardware. – Grismar Jun 05 '23 at 07:05