python threading for elevator simulation - python

I am trying to make an elevator simulation because of an interesting problem I saw on CareerCup. My problem is that I want the elevator to "take time" to move from one floor to another. Right now it just instantly moves to the next floor in its "to visit" list. I'm not sure how to program it so that "pickup requests" can be coming in while the elevator is moving. I think this may require threading, and the time.sleep() function. How do I make one thread that makes random requests to the elevator, and another thread that has the elevator trying to meet all of the requests? This is what I have so far:
import time
from random import *
import math
class Elevator:
def __init__(self, num_floors):
self.going_up = False
self.going_down = False
self.docked = True
self.curr_floor = 0
self.num_floors = num_floors
self.floors_to_visit = []
self.people_waiting = []
def print_curr_status(self):
for i in range(self.num_floors):
if i == self.curr_floor:
print('. []')
else:
print('.')
print ("to_visit: ", self.floors_to_visit)
def handle_call_request(self, person):
if not self.going_up and not self.going_down:
self.floors_to_visit = [person.curr_floor] + self.floors_to_visit
self.going_up = True
self.docked = False
self.people_waiting.append(person)
else:
self.floors_to_visit.append(person.curr_floor)
self.people_waiting.append(person)
def handle_input_request(self, floor_num):
self.floors_to_visit.append(floor_num)
def go_to_next(self):
if not self.floors_to_visit:
self.print_curr_status()
return
self.curr_floor = self.floors_to_visit.pop(0)
for i,person in enumerate(self.people_waiting):
if person.curr_floor == self.curr_floor:
person.riding = True
person.press_floor_num()
self.people_waiting.pop(i)
return
class Person:
def __init__(self, assigned_elevator, curr_floor):
self.curr_floor = curr_floor
self.desired_floor = math.floor(random() * 10)
self.assigned_elevator = assigned_elevator
self.riding = False
def print_floor(self):
print(self.desired_floor)
def call_elevator(self):
self.assigned_elevator.handle_call_request(self)
def press_floor_num(self):
self.assigned_elevator.handle_input_request(self.desired_floor)
my_elevator = Elevator(20)
while True:
for i in range(3):
some_person = Person(my_elevator, math.floor(random() * 10))
some_person.call_elevator()
my_elevator.go_to_next()
my_elevator.print_curr_status()
time.sleep(1)

No threding is neccessary. You can introduce 2 new variables: one keeping track on the time the elevator started and one for the time an elevator ride should take. Then just just check when the elevator has run long enough. You can do this calling the function time.time(); it'll return the time in seconds since January 1, 1970 (since you're only interested in the difference it doesn't matter; you just need a function that increment in time). Although, this function often can't give a more accurate time period than 1 second. If you feel it's to inaccurate on your machine then you could use datetime.
class Elevator:
def __init__(self, num_floors):
self.start_time = 0
self.ride_duration = 1
...
def call_elevator(self):
self.start_time = time.time()
self.assigned_elevator.handle_call_request(self)
def go_to_next(self):
if time.time() - self.start_time < self.ride_duration:
return # Do nothing.
else:
...
You'll probably need to refactor the code to suit your needs and add some logic on what to do when the elevator is in use, etc.

Related

Running a Python web scraper every hour [duplicate]

I'm looking for a library in Python which will provide at and cron like functionality.
I'd quite like have a pure Python solution, rather than relying on tools installed on the box; this way I run on machines with no cron.
For those unfamiliar with cron: you can schedule tasks based upon an expression like:
0 2 * * 7 /usr/bin/run-backup # run the backups at 0200 on Every Sunday
0 9-17/2 * * 1-5 /usr/bin/purge-temps # run the purge temps command, every 2 hours between 9am and 5pm on Mondays to Fridays.
The cron time expression syntax is less important, but I would like to have something with this sort of flexibility.
If there isn't something that does this for me out-the-box, any suggestions for the building blocks to make something like this would be gratefully received.
Edit
I'm not interested in launching processes, just "jobs" also written in Python - python functions. By necessity I think this would be a different thread, but not in a different process.
To this end, I'm looking for the expressivity of the cron time expression, but in Python.
Cron has been around for years, but I'm trying to be as portable as possible. I cannot rely on its presence.
If you're looking for something lightweight checkout schedule:
import schedule
import time
def job():
print("I'm working...")
schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
while 1:
schedule.run_pending()
time.sleep(1)
Disclosure: I'm the author of that library.
You could just use normal Python argument passing syntax to specify your crontab. For example, suppose we define an Event class as below:
from datetime import datetime, timedelta
import time
# Some utility classes / functions first
class AllMatch(set):
"""Universal set - match everything"""
def __contains__(self, item): return True
allMatch = AllMatch()
def conv_to_set(obj): # Allow single integer to be provided
if isinstance(obj, (int,long)):
return set([obj]) # Single item
if not isinstance(obj, set):
obj = set(obj)
return obj
# The actual Event class
class Event(object):
def __init__(self, action, min=allMatch, hour=allMatch,
day=allMatch, month=allMatch, dow=allMatch,
args=(), kwargs={}):
self.mins = conv_to_set(min)
self.hours= conv_to_set(hour)
self.days = conv_to_set(day)
self.months = conv_to_set(month)
self.dow = conv_to_set(dow)
self.action = action
self.args = args
self.kwargs = kwargs
def matchtime(self, t):
"""Return True if this event should trigger at the specified datetime"""
return ((t.minute in self.mins) and
(t.hour in self.hours) and
(t.day in self.days) and
(t.month in self.months) and
(t.weekday() in self.dow))
def check(self, t):
if self.matchtime(t):
self.action(*self.args, **self.kwargs)
(Note: Not thoroughly tested)
Then your CronTab can be specified in normal python syntax as:
c = CronTab(
Event(perform_backup, 0, 2, dow=6 ),
Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)
This way you get the full power of Python's argument mechanics (mixing positional and keyword args, and can use symbolic names for names of weeks and months)
The CronTab class would be defined as simply sleeping in minute increments, and calling check() on each event. (There are probably some subtleties with daylight savings time / timezones to be wary of though). Here's a quick implementation:
class CronTab(object):
def __init__(self, *events):
self.events = events
def run(self):
t=datetime(*datetime.now().timetuple()[:5])
while 1:
for e in self.events:
e.check(t)
t += timedelta(minutes=1)
while datetime.now() < t:
time.sleep((t - datetime.now()).seconds)
A few things to note: Python's weekdays / months are zero indexed (unlike cron), and that range excludes the last element, hence syntax like "1-5" becomes range(0,5) - ie [0,1,2,3,4]. If you prefer cron syntax, parsing it shouldn't be too difficult however.
More or less same as above but concurrent using gevent :)
"""Gevent based crontab implementation"""
from datetime import datetime, timedelta
import gevent
# Some utility classes / functions first
def conv_to_set(obj):
"""Converts to set allowing single integer to be provided"""
if isinstance(obj, (int, long)):
return set([obj]) # Single item
if not isinstance(obj, set):
obj = set(obj)
return obj
class AllMatch(set):
"""Universal set - match everything"""
def __contains__(self, item):
return True
allMatch = AllMatch()
class Event(object):
"""The Actual Event Class"""
def __init__(self, action, minute=allMatch, hour=allMatch,
day=allMatch, month=allMatch, daysofweek=allMatch,
args=(), kwargs={}):
self.mins = conv_to_set(minute)
self.hours = conv_to_set(hour)
self.days = conv_to_set(day)
self.months = conv_to_set(month)
self.daysofweek = conv_to_set(daysofweek)
self.action = action
self.args = args
self.kwargs = kwargs
def matchtime(self, t1):
"""Return True if this event should trigger at the specified datetime"""
return ((t1.minute in self.mins) and
(t1.hour in self.hours) and
(t1.day in self.days) and
(t1.month in self.months) and
(t1.weekday() in self.daysofweek))
def check(self, t):
"""Check and run action if needed"""
if self.matchtime(t):
self.action(*self.args, **self.kwargs)
class CronTab(object):
"""The crontab implementation"""
def __init__(self, *events):
self.events = events
def _check(self):
"""Check all events in separate greenlets"""
t1 = datetime(*datetime.now().timetuple()[:5])
for event in self.events:
gevent.spawn(event.check, t1)
t1 += timedelta(minutes=1)
s1 = (t1 - datetime.now()).seconds + 1
print "Checking again in %s seconds" % s1
job = gevent.spawn_later(s1, self._check)
def run(self):
"""Run the cron forever"""
self._check()
while True:
gevent.sleep(60)
import os
def test_task():
"""Just an example that sends a bell and asd to all terminals"""
os.system('echo asd | wall')
cron = CronTab(
Event(test_task, 22, 1 ),
Event(test_task, 0, range(9,18,2), daysofweek=range(0,5)),
)
cron.run()
None of the listed solutions even attempt to parse a complex cron schedule string. So, here is my version, using croniter. Basic gist:
schedule = "*/5 * * * *" # Run every five minutes
nextRunTime = getNextCronRunTime(schedule)
while True:
roundedDownTime = roundDownTime()
if (roundedDownTime == nextRunTime):
####################################
### Do your periodic thing here. ###
####################################
nextRunTime = getNextCronRunTime(schedule)
elif (roundedDownTime > nextRunTime):
# We missed an execution. Error. Re initialize.
nextRunTime = getNextCronRunTime(schedule)
sleepTillTopOfNextMinute()
Helper routines:
from croniter import croniter
from datetime import datetime, timedelta
# Round time down to the top of the previous minute
def roundDownTime(dt=None, dateDelta=timedelta(minutes=1)):
roundTo = dateDelta.total_seconds()
if dt == None : dt = datetime.now()
seconds = (dt - dt.min).seconds
rounding = (seconds+roundTo/2) // roundTo * roundTo
return dt + timedelta(0,rounding-seconds,-dt.microsecond)
# Get next run time from now, based on schedule specified by cron string
def getNextCronRunTime(schedule):
return croniter(schedule, datetime.now()).get_next(datetime)
# Sleep till the top of the next minute
def sleepTillTopOfNextMinute():
t = datetime.utcnow()
sleeptime = 60 - (t.second + t.microsecond/1000000.0)
time.sleep(sleeptime)
I know there are a lot of answers, but another solution could be to go with decorators. This is an example to repeat a function everyday at a specific time. The cool think about using this way is that you only need to add the Syntactic Sugar to the function you want to schedule:
#repeatEveryDay(hour=6, minutes=30)
def sayHello(name):
print(f"Hello {name}")
sayHello("Bob") # Now this function will be invoked every day at 6.30 a.m
And the decorator will look like:
def repeatEveryDay(hour, minutes=0, seconds=0):
"""
Decorator that will run the decorated function everyday at that hour, minutes and seconds.
:param hour: 0-24
:param minutes: 0-60 (Optional)
:param seconds: 0-60 (Optional)
"""
def decoratorRepeat(func):
#functools.wraps(func)
def wrapperRepeat(*args, **kwargs):
def getLocalTime():
return datetime.datetime.fromtimestamp(time.mktime(time.localtime()))
# Get the datetime of the first function call
td = datetime.timedelta(seconds=15)
if wrapperRepeat.nextSent == None:
now = getLocalTime()
wrapperRepeat.nextSent = datetime.datetime(now.year, now.month, now.day, hour, minutes, seconds)
if wrapperRepeat.nextSent < now:
wrapperRepeat.nextSent += td
# Waiting till next day
while getLocalTime() < wrapperRepeat.nextSent:
time.sleep(1)
# Call the function
func(*args, **kwargs)
# Get the datetime of the next function call
wrapperRepeat.nextSent += td
wrapperRepeat(*args, **kwargs)
wrapperRepeat.nextSent = None
return wrapperRepeat
return decoratorRepeat
I like how the pycron package solves this problem.
import pycron
import time
while True:
if pycron.is_now('0 2 * * 0'): # True Every Sunday at 02:00
print('running backup')
time.sleep(60) # The process should take at least 60 sec
# to avoid running twice in one minute
else:
time.sleep(15) # Check again in 15 seconds
There isn't a "pure python" way to do this because some other process would have to launch python in order to run your solution. Every platform will have one or twenty different ways to launch processes and monitor their progress. On unix platforms, cron is the old standard. On Mac OS X there is also launchd, which combines cron-like launching with watchdog functionality that can keep your process alive if that's what you want. Once python is running, then you can use the sched module to schedule tasks.
Another trivial solution would be:
from aqcron import At
from time import sleep
from datetime import datetime
# Event scheduling
event_1 = At( second=5 )
event_2 = At( second=[0,20,40] )
while True:
now = datetime.now()
# Event check
if now in event_1: print "event_1"
if now in event_2: print "event_2"
sleep(1)
And the class aqcron.At is:
# aqcron.py
class At(object):
def __init__(self, year=None, month=None,
day=None, weekday=None,
hour=None, minute=None,
second=None):
loc = locals()
loc.pop("self")
self.at = dict((k, v) for k, v in loc.iteritems() if v != None)
def __contains__(self, now):
for k in self.at.keys():
try:
if not getattr(now, k) in self.at[k]: return False
except TypeError:
if self.at[k] != getattr(now, k): return False
return True
I don't know if something like that already exists. It would be easy to write your own with time, datetime and/or calendar modules, see http://docs.python.org/library/time.html
The only concern for a python solution is that your job needs to be always running and possibly be automatically "resurrected" after a reboot, something for which you do need to rely on system dependent solutions.

How to slow down asynchrounous API calls to match API limits?

I have a list of ~300K URLs for an API i need to get data from.
The API limit is 100 calls per second.
I have made a class for the asynchronous but this is working to fast and I am hitting an error on the API.
How do I slow down the asynchronous, so that I can make 100 calls per second?
import grequests
lst = ['url.com','url2.com']
class Test:
def __init__(self):
self.urls = lst
def exception(self, request, exception):
print ("Problem: {}: {}".format(request.url, exception))
def async(self):
return grequests.map((grequests.get(u) for u in self.urls), exception_handler=self.exception, size=5)
def collate_responses(self, results):
return [x.text for x in results]
test = Test()
#here we collect the results returned by the async function
results = test.async()
response_text = test.collate_responses(results)
The first step that I took was to create an object who can distribute a maximum of n coins every t ms.
import time
class CoinsDistribution:
"""Object that distribute a maximum of maxCoins every timeLimit ms"""
def __init__(self, maxCoins, timeLimit):
self.maxCoins = maxCoins
self.timeLimit = timeLimit
self.coin = maxCoins
self.time = time.perf_counter()
def getCoin(self):
if self.coin <= 0 and not self.restock():
return False
self.coin -= 1
return True
def restock(self):
t = time.perf_counter()
if (t - self.time) * 1000 < self.timeLimit:
return False
self.coin = self.maxCoins
self.time = t
return True
Now we need a way of forcing function to only get called if they can get a coin.
To do that we can write a decorator function that we could use like that:
#limitCalls(callLimit=1, timeLimit=1000)
def uniqFunctionRequestingServer1():
return 'response from s1'
But sometimes, multiple functions are calling requesting the same server so we would want them to get coins from the the same CoinsDistribution object.
Therefor, another use of the decorator would be by supplying the CoinsDistribution object:
server_2_limit = CoinsDistribution(3, 1000)
#limitCalls(server_2_limit)
def sendRequestToServer2():
return 'it worked !!'
#limitCalls(server_2_limit)
def sendAnOtherRequestToServer2():
return 'it worked too !!'
We now have to create the decorator, it can take either a CoinsDistribution object or enough data to create a new one.
import functools
def limitCalls(obj=None, *, callLimit=100, timeLimit=1000):
if obj is None:
obj = CoinsDistribution(callLimit, timeLimit)
def limit_decorator(func):
#functools.wraps(func)
def limit_wrapper(*args, **kwargs):
if obj.getCoin():
return func(*args, **kwargs)
return 'limit reached, please wait'
return limit_wrapper
return limit_decorator
And it's done ! Now you can limit the number of calls any API that you use and you can build a dictionary to keep track of your CoinsDistribution objects if you have to manage a lot of them (to differrent API endpoints or to different APIs).
Note: Here I have choosen to return an error message if there are no coins available. You should adapt this behaviour to your needs.
You can just keep track of how much time has passed and decide if you want to do more requests or not.
This will print 100 numbers per second, for example:
from datetime import datetime
import time
start = datetime.now()
time.sleep(1);
counter = 0
while (True):
end = datetime.now()
s = (end-start).seconds
if (counter >= 100):
if (s <= 1):
time.sleep(1) # You can keep track of the time and sleep less, actually
start = datetime.now()
counter = 0
print(counter)
counter += 1
This other question in SO shows exactly how to do this. By the way, what you need is usually called throttling.

How can i make a time counter using QTime()

I am trying to do a time counter in python using QTime and show this time in a QLabel using PyQt. I need to do this, to show how many time has passed since my program started to work, using this format of time: 00:00:00. I read the docs of QTime and tried another code that I have searched for on the internet, but I can not make it work.
This is part of my code:
class MyApplication(QtGui.QApplication):
def __init__(self, *args, **kwargs):
super(MyApplication, self).__init__(*args, **kwargs)
self.t = QTime() #I start QTime at the same time
self.t.start() # the program starts.
self.label_1 = QtGui.QLabel("Time since the program started:")
self.time_label = QtGui.QLabel("00:00:00")
self.tmr = QTimer() #I use QTimer because I need it
#for another part of the code
self.tmr.timeout.connect(self.clock)
def clock(self):
self.m = 0
self.elapsed = self.t.elapsed()
self.s = int((self.elapsed)/1000)
if self.s == 60:
self.m += 1
self.s = 0
self.time_sec = str(self.s)
self.time_min = str(self.m)
self.time = self.time_min + ":" + self.time_sec
self.time_label.setText(self.time) #I show the time in this QLabel()
When I build this, I get this format of time: 0:0 and after 60 seconds (it shows the seconds) I get this result: 1:0, and nothing else happens.
How can I make the time counter that I need with this format: 00:00:00. Can I do it using QTimer? Hope you can help me.
------ EDIT ------
Thanks to #amicitas and #linusg answer, I´ve tried the datetime module, and wrote this simple code:
class MyApplication(QtGui.QApplication):
def __init__(self, *args, **kwargs):
super(MyApplication, self).__init__(*args, **kwargs)
self.t0 = datetime.now()
self.tmr = QTimer()
self.tmr.timeout.connect(self.clock)
def.clock(self):
self.t1 = datetime.now()
self.hour = self.t1.hour - self.t0.hour
self.minute = self.t1.minute - self.t0.minute
self.second = self.t1.second - self.t0.second
print self.hour, self.minute, self.second
But, when I build this, at the moment the counter reaches 45 seconds, it turns 45 to -15 and "1 minute" appears. This is:
When it reaches 0:0:44, it turns to 0:1:-15.
What could be the problem? And how can I show the time format that I need? This is 00:00:00. Hope you can help me.
I have written and tested the following code for you:
from datetime import datetime
import time
if __name__== '__main__':
initTimeObj = datetime.now()
nullRef = datetime(initTimeObj.year, initTimeObj.month, initTimeObj.day, 0, 0, 0)
print("Initial time:")
print(str(initTimeObj.hour) + ':' + str(initTimeObj.minute) + ':' + str(initTimeObj.second))
print("")
while(True):
time.sleep(1)
myDiff = datetime.now() - initTimeObj
myTimeObj = nullRef + myDiff
print(str(myTimeObj.hour) + ':' + str(myTimeObj.minute) + ':' + str(myTimeObj.second))
# Now you get a counting as follows:
# 0:0:1
# 0:0:2
# 0:0:3
# ...
# 0:0:59
# 0:1:0
# 0:1:1
# 0:1:2
# ...
This code does exactly what you need. It starts counting from 0:0:0and continues doing so. Some more tweaks might be necessary if you really want to have a double-digit format, like 00:00:00. If you want, I can look into that further.
I hope this helped you out. Please let me know if it worked for you.
import datetime
import time
start = datetime.datetime.now()
while True:
elapsed_seconds = (datetime.datetime.now() - start).total_seconds()
hour = int(elapsed_seconds // 3600)
min = int(elapsed_seconds % 3600 // 60)
seconds = int(elapsed_seconds % 60)
print '{:02d}:{:02d}:{:02d}'.format(hour, minute, second)
time.sleep(1)

Looping a function until another function is called

I am making a menu that runs on an LCD screen powered by a Raspberry Pi. I am trying to use the threading module to make the text, on the LCD, update until the menu position changes.
The menu is made up of a list of functions that are called when the menu position is changed. The switch_menu() function is called from outside the class, using an event handler, and is used to call the correct menu function. With some of these functions(item2); I want them to loop, and with others(item1); just display static text. The important thing is that they stop looping when switch_menu() is called again. How can I do this?
(here is a simplified version of my code)
class Menu:
def __init__(self):
self.LCD = Adafruit_CharLCD()
self.m_pos = 0
self.items = [self.item1,self.item2]
self.switch_menu(0)
def switch_menu(self,operation):
# 2. And here I want to stop it.
m_pos = self.m_pos
pos = m_pos
max_pos = len(self.items) - 1
m_pos = self.loop_selection(pos,max_pos,operation)
# 1. Here I want to start looping the function below.
self.items[m_pos]()
self.m_pos = m_pos
def loop_selection(self,pos,max_pos,operation):
if pos >= max_pos and operation == 1:
pos = 0
elif pos <= 0 and operation == -1:
pos = max_pos
else:
pos += operation
return pos
def item1(self):
self.LCD.clear()
text = "item1"
self.LCD.message(text)
def item2(self):
while True:
self.LCD.clear()
text = "item2"
self.LCD.message(text)
time.sleep(10)
There are many ways to achieve this, one simple way is to make the while loop in a variable and then set it to False outside the loop (for example, when calling switch_menu) once you want to stop it. Just beware of any race conditions that may be caused, of which I can't talk much more about since I don't know the rest of your code.
Typical, I have been trying to get this to work for days and as soon as I post a question; I find the answer.
Here is where I found my answer:
Stopping a thread after a certain amount of time
And this is what I did to make it work:
class Menu:
def __init__(self):
self.LCD = Adafruit_CharLCD()
self.m_pos = 0
self.items = [self.item1,self.item2]
self.switch_menu(0)
def switch_menu(self,operation):
try:
self.t_stop.set()
except:
pass
m_pos = self.m_pos
pos = m_pos
max_pos = len(self.items) - 1
m_pos = self.loop_selection(pos,max_pos,operation)
item = self.items[m_pos][0]
self.t_stop = threading.Event()
self.t = threading.Thread(target=item,args=(1,self.t_stop))
self.t.start()
self.m_pos = m_pos
def loop_selection(self,pos,max_pos,operation):
if pos >= max_pos and operation == 1:
pos = 0
elif pos <= 0 and operation == -1:
pos = max_pos
else:
pos += operation
return pos
def item1(self,arg):
while not stop_event.is_set():
text = "item1"
self.LCD.clear()
if not stop_event.is_set(): self.LCD.message(text)
stop_event.wait(10)
def item2(self,arg):
while not stop_event.is_set():
text = "item2"
self.LCD.clear()
if not stop_event.is_set(): self.LCD.message(text)
stop_event.wait(10)
I used a try/except to bypass the initial execution of switch_menu():
try:
self.t_stop.set()
except:
pass
I check the condition a second time as a workaround, to prevent race conditions:
if not stop_event.is_set(): self.LCD.message(text)
And I don't know why I had to pass in an argument when creating a thread, but it gave me errors when I didn't:
self.t = threading.Thread(target=item,args=(1,self.t_stop))
I know it needs some tidying up, but it works. If anyone has a more elegant solution feel free to post it.

Fade Between Two Music Tracks in-progress in Pygame

My intention is to have two music tracks, which are similar in nature, fade between each other at various times. When such a fade occurs, one music track should fade from full volume to muted in a short period of time, and, simultaneously, the other track should fade from 0 to 100 and continue playing from the same time index. They must be able to do this dynamically at any time - when a certain action occurs, the fade will occur and the new track will start playing at the same position that the other one left off at.
This might be plausible by either using volume manipulation or by starting and stopping the music (however, it appears that only a "fadeout" option exists, and there is a lack of a "fadein" option). How can I do this? What is the best method, if any, that exists? If it is impossible using Pygame, alternatives to Pygame are acceptable.
This isn't exactly an answer to the question, but for future-googlers I wrote a script to fade-in my music from volume 0 in the morning and this is what I used:
max_volume = 40
current_volume = 0
# set the volume to the given percent using amixer
def set_volume_to(percent):
subprocess.call(["amixer", "-D", "pulse", "sset", "Master",
str(percent) + "%", "stdout=devnull"])
# play the song and fade in the song to the max_volume
def play_song(song_file):
global current_volume
print("Song starting: " + song_file)
pygame.mixer.music.load(song_file)
pygame.mixer.music.play()
# gradually increase volume to max
while pygame.mixer.music.get_busy():
if current_volume < max_volume:
set_volume_to(current_volume)
current_volume += 1
pygame.time.Clock().tick(1)
play_song("foo.mp3")
Try this, it's pretty straight forward..
import pygame
pygame.mixer.init()
pygame.init()
# Maybe you can subclass the pygame.mixer.Sound and
# add the methods below to it..
class Fader(object):
instances = []
def __init__(self, fname):
super(Fader, self).__init__()
assert isinstance(fname, basestring)
self.sound = pygame.mixer.Sound(fname)
self.increment = 0.01 # tweak for speed of effect!!
self.next_vol = 1 # fade to 100 on start
Fader.instances.append(self)
def fade_to(self, new_vol):
# you could change the increment here based on something..
self.next_vol = new_vol
#classmethod
def update(cls):
for inst in cls.instances:
curr_volume = inst.sound.get_volume()
# print inst, curr_volume, inst.next_vol
if inst.next_vol > curr_volume:
inst.sound.set_volume(curr_volume + inst.increment)
elif inst.next_vol < curr_volume:
inst.sound.set_volume(curr_volume - inst.increment)
sound1 = Fader("1.wav")
sound2 = Fader("2.wav")
sound1.sound.play()
sound2.sound.play()
sound2.sound.set_volume(0)
# fading..
sound1.fade_to(0)
sound2.fade_to(1)
while True:
Fader.update() # a call that will update all the faders..
Pseudocode:
track1 = ...
track2 = ...
track1.play_forever()
track1.volume = 100
track2.play_forever()
track2.volume = 0
playing = track1
tracks = [track1, track2]
def volume_switcher():
while True:
playing.volume = min(playing.volume + 1, 100)
for track in tracks:
if track != playing:
track.volume = max(track.volume - 1, 100)
time.sleep(0.1)
Thread(target=volume_switcher).start()
So it looks like what you want to do in pygame is create two 'Sound' objects, and create a linear interpolation on the volume between the two of them.
I would create two vectors, each from [0,100], and relate them inversely with some constant.
So when sound A is at 100, sound b is at 0. Then when an action occurs, you modify the constant.
t=0
A: [0 ... 100]
B: [0 ... 100]
t=1
ACTION
t=1.1
A:[0 .. 50 .. 100]
B:[0 .. 50 .. 100]
t=2
A:[0 ... 100]
B:[0 ... 100]
Now some code. I'm not familiar with pygame, but this should put you on the right track.
class Song(object):
def __init__(self, songfilename):
self.song = pygame.mixer.Sound(songfilename)
def setVolume(self, somenumber):
#number validation
#possibly do some volume curve here if you wanted
self.song.set_volume(somenumber)
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.__xAxisMax = 100
self.__xAxisMin = 0
def fade(self, xaxis):
assert(self.__xAxisMin <= xaxis <= self.__xAxisMax)
#could be any numbers you want.
#i chose 0-100 for convenience
self.song1.setVolume(xaxis)
self.song2.setVolume(self.__xAxisMax-xaxis)
song1 = Song('Song1.wav')
song2 = Song('Song2.wav')
fader = SongFader(song1, song2)
#Inside some event loop when you action is triggered
fader.fade(100) #Only song2 is playing
fader.fade(50) #Songs are evenly split
fader.fade(0) #Only left song is playing
edit
The linear interpolation is probably the more important concept here, so i have modified the fader class, with inspiration from Eric 's thread idea.
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.lefttoright = False
self.starttime = 0
self.endtime = 0
def fade(self, starttime, fadeleft):
self.lefttoright = fadeleft == True #Being verbose here
self.starttime = starttime #assuming time is in millis
self.endtime = starttime + 1000
Thread(target = self.fadeHelper).start()
#this is where you define how the two songs are faded
def fadeHelper(self):
#if using thread, make sure you mutex the 'self.' variables
starttime = self.starttime
endtime = self.endtime
lefttoright = self.lefttoright
while starttime < endtime:
fadevalue = (starttime - endtime) / 1000 #a val between [0,1]
if lefttoright:
self.song1.setVolume(fadevalue)
self.song2.setVolume(1-fadevalue)
else:
self.song1.setVolume(1-fadevalue)
self.song2.setVolume(fadefalue)
starttime = getGameTimeFromSomewhere()

Categories

Resources