def __init__(self, file_manager): self.file = '' self.fileManager = file_manager self.log = Logger('Recorder') self.mouseListener = MouseListener(on_click=self.onClick, on_scroll=self.onScroll, on_move=self.onMove) self.keyboardListener = KeyboardListener(on_press=self.onPress, on_release=self.onRelease) self.clock = Clock() self.alert = Alert('RPTool - Recording') self.drag_start = (0, 0) self.drag_end = (0, 0) self.verifier = Verifier() self.pauseTrace = False
def __init__(self): Thread.__init__(self) self.session = Session() self.session.add_listener(self) self.play_state = PlayStates.stopped self.playhead = PlayHead() # dummy eventhandler, doesn't actually handle them. Inject acutual one via set_event_handler() self.event_handler: EventHandler = EventHandler() self.clock = Clock(tick_time_ms=1000) self.keep_thread_active = True self.update_looping_position() self.rewind() self.start()
def __init__(self, name, master_seed, **clock_params): """Create a new Container object :param master_seed: seed used to initialized random generatof of other seeds :type master_seed: int :rtype: Container :return: a new Container object, with the clock, is created """ self.name = name self.master_seed = master_seed self.clock_params = clock_params self.seeder = seed_provider(master_seed=master_seed) self.clock = Clock(seed=next(self.seeder), **clock_params) self.stories = [] self.populations = {} self.generators = {}
class Sequencer(Session.Listener, Thread): def __init__(self): Thread.__init__(self) self.session = Session() self.session.add_listener(self) self.play_state = PlayStates.stopped self.playhead = PlayHead() # dummy eventhandler, doesn't actually handle them. Inject acutual one via set_event_handler() self.event_handler: EventHandler = EventHandler() self.clock = Clock(tick_time_ms=1000) self.keep_thread_active = True self.update_looping_position() self.rewind() self.start() def load_session(self, session: Session) -> None: # get rid of the old session self.remove_all_session_samples_from_event_handler() self.session.remove_listener(self) # setup the new session self.session = session self.session.add_listener(self) self.add_all_session_samples_to_event_handler() self.clock.update_tick_time_ms(self.calculate_tick_time()) self.update_looping_position() self.rewind() def shut_down(self) -> None: self.keep_thread_active = False def is_playing(self) -> bool: return self.play_state == PlayStates.playing def set_event_handler(self, event_handler: EventHandler) -> None: self.event_handler = event_handler # load all currently available samples in the event handler self.add_all_session_samples_to_event_handler() def add_all_session_samples_to_event_handler(self) -> None: for s in self.session.samples: self.event_handler.add_sample(s) def remove_all_session_samples_from_event_handler(self) -> None: for s in self.session.samples: self.event_handler.remove_sample(s) def start_playback(self) -> None: self.play_state = PlayStates.playing self.clock.start() def stop_playback(self) -> None: self.play_state = PlayStates.stopped def update_looping_position(self) -> None: highest_time_stamp = find_highest_time_stamp_in_event_list(self.session.events) looping_point = find_looping_point_for_time_signature(highest_time_stamp, self.session.time_signature) self.playhead.set_looping(0, looping_point) def run(self) -> None: while self.keep_thread_active: if self.play_state == PlayStates.playing: self.handle_all_events_for_playhead_position() self.playhead.advance_tick() self.clock.block_until_next_tick() else: # if the sequencer is stopped, sleep before checking if it has started to safe cpu time.sleep(0.01) def handle_all_events_for_playhead_position(self) -> None: for e in self.session.events: if e.time_stamp == self.playhead.position_in_ticks: self.event_handler.handle(e) def calculate_tick_time(self) -> float: num_ticks_per_minute = self.session.time_signature.ticks_per_quarter_note * self.session.tempo_bpm ms_per_minute = 60_000 return ms_per_minute / num_ticks_per_minute def rewind(self) -> None: self.playhead.rewind() # -------------------------------------------------------------------------------------------- # below are all the methods inherited from Session.Listener: # with a new tempo, the clock needs to be updated # keep in mind that this method is not responable for making sure the tempo is not 0bpm # it will assert for it to let it know if it somehow is... def tempo_changed(self, tempo_bpm: float, session: Session) -> None: assert session == self.session assert tempo_bpm != 0 self.clock.update_tick_time_ms(self.calculate_tick_time()) # with a different time signature the tick time could be different # and the logical looping point could have moved too def time_signature_changed(self, time_signature: TimeSignature, session: Session) -> None: assert session == self.session self.update_looping_position() self.clock.update_tick_time_ms(self.calculate_tick_time()) # when a sample gets removed, events using that sample also get removed. # the removal of these events is already handled by the event_removed_from_sample() def sample_removed_from_session(self, sample: Sample, session: Session) -> None: assert session == self.session self.event_handler.remove_sample(sample) # when a sample gets added, tell the event handler about it def sample_added_to_session(self, sample: Sample, session: Session) -> None: assert session == self.session self.event_handler.add_sample(sample) # when a sample is added/removed, the loop could have changed in length -> update def event_added_to_session(self, event: Event, session: Session) -> None: assert session == self.session self.update_looping_position() # when a sample is added/removed, the loop could have changed in length -> update def event_removed_from_session(self, event: Event, session: Session) -> None: assert session == self.session self.update_looping_position()
def test_clock_start_with_zero(self): """ Test if the clock starts counting from zero. """ clock = Clock() clock.start() self.assertTrue(clock.getTime() == 0, "Expected clock counting zero when starts!")
def test_clock_should_count_time_between_start_and_stop(self): """ Test if is counting the time elapsed after start until stop. """ clock = Clock() clock.start() time.sleep(1) clock.stop() self.assertTrue(clock.getTime() == 1, "Expected time be 1s") clock.start() time.sleep(2) clock.stop() print(clock.getTime()) self.assertTrue(clock.getTime() == 2, "Expected time be 2s")
def test_clock_get_time(self): """ Test if the clock is started """ clock = Clock() clock.start() time.sleep(1) self.assertFalse(clock.getTime() == 1, "Expected be counting 1 second!")
class Recorder: def __init__(self, file_manager): self.file = '' self.fileManager = file_manager self.log = Logger('Recorder') self.mouseListener = MouseListener(on_click=self.onClick, on_scroll=self.onScroll, on_move=self.onMove) self.keyboardListener = KeyboardListener(on_press=self.onPress, on_release=self.onRelease) self.clock = Clock() self.alert = Alert('RPTool - Recording') self.drag_start = (0, 0) self.drag_end = (0, 0) self.verifier = Verifier() self.pauseTrace = False def save(self, trace): self.fileManager.write(self.file, trace) def onClick(self, *args): self.clock.start() print('click: {}'.format(args)) pressed = args[3] if pressed: self.drag_start = (args[0], args[1]) if not self.pauseTrace: trace = 'click x={}, y={}, time={}\n'.format(args[0], args[1], self.clock.getTime()) self.alert.notify(trace) self.save(trace) else: self.drag_end = (args[0], args[1]) if self.drag_start != self.drag_end: x1, y1 = self.drag_start x2, y2 = self.drag_end # Ignore drag while is paused for a print screen! if not self.pauseTrace: trace = 'drag x1={0}, y1={1}, x2={2}, y2={3}\n'.format(x1, y1, x2, y2) self.alert.notify(trace) self.save(trace) else: pass def onScroll(self, *args): dx, dy = args[2], args[3] trace = 'scroll dx={0}, dy={1}\n'.format(dx, dy) self.save(trace) def onMove(self, *args): pass # x, y # self.save('move {}\n'.format(args)) def onPress(self, *args): # key if not self.pauseTrace: # Exclude the keys used as commands by the tool. if args[0] not in [Key.f2, Key.f6, Key.f7]: trace = 'press key={}\n'.format(args[0]) self.alert.notify(trace) self.save(trace) else: pass def onRelease(self, *args): # Stop recording when press 'esc' if args[0] == Key.esc: self.stop() self.log.debug('[ESC] Stop recording!') self.alert.notify('[ESC] Stop recording!') return False if args[0] == Key.f2: self.pauseTrace = True self.alert.notify('[F2] Select a region!') checkpoint = self.verifier.printScreen(self.file) trace = 'checkpoint {}\n'.format(checkpoint) self.save(trace) self.pauseTrace = False if args[0] == Key.f4: self.pauseTrace = True self.alert.notify('[F4] Select a region!') location_point = self.verifier.printScreen(self.file) trace = 'find_and_click {}\n'.format(location_point) self.save(trace) self.pauseTrace = False if args[0] == Key.f6: self.alert.notify('[F6] Pause started!') self.pauseTrace = True if args[0] == Key.f7: self.alert.notify('[F7] Pause stopped!') self.pauseTrace = False def start(self, file): self.file = file self.mouseListener.start() self.keyboardListener.start() self.log.debug('start recording') def stop(self): self.mouseListener.stop() self.keyboardListener.stop() self.log.debug('stop recording')
class Container(object): """ A Container is just a container of a lot of objects that are required to make the simulation It is also the object that will execute the stories required for 1 iteration """ def __init__(self, name, master_seed, **clock_params): """Create a new Container object :param master_seed: seed used to initialized random generatof of other seeds :type master_seed: int :rtype: Container :return: a new Container object, with the clock, is created """ self.name = name self.master_seed = master_seed self.clock_params = clock_params self.seeder = seed_provider(master_seed=master_seed) self.clock = Clock(seed=next(self.seeder), **clock_params) self.stories = [] self.populations = {} self.generators = {} def create_population(self, name, **population_params): """ Creates a population with the specifed parameters and attach it to this container. """ if name in self.populations: raise ValueError("refusing to overwrite existing population: {} " "".format(name)) self.populations[name] = population.Population(container=self, **population_params) return self.populations[name] def load_population(self, population_id, namespace=None): """ Load this population definition add attach it to this container """ # Defaulting to the namespace associated to this container if none # specified if namespace is None: namespace = self.name loaded = db.load_population(namespace=namespace, population_id=population_id, container=self) self.populations[population_id] = loaded return loaded def create_story(self, name, **story_params): """ Creates a story with the provided parameters and attach it to this container. """ existing = self.get_story(name) if existing is None: story = Story(name=name, **story_params) self.stories.append(story) return story else: raise ValueError( "Cannot add story {}: another story with " "identical name is already in the container".format(name)) def get_story(self, story_name): """ Looks up and story by name in this container and returns it. Returns none if not found. """ remaining_stories = filter(lambda a: a.name == story_name, self.stories) try: return next(remaining_stories) except StopIteration: logging.warn("story not found: {}".format(story_name)) return None def get_population_of(self, story_name): """ Looks up the initiating population associated to this story """ return self.get_story(story_name).triggering_population def attach_generator(self, gen_id, generator): """ "attach" a random generator to this container, s.t. it gets persisted with the rest """ if gen_id in self.generators: raise ValueError("refusing to replace existing generator: {} " "".format(gen_id)) self.generators[gen_id] = generator def load_generator(self, gen_type, gen_id): """ Load this generator definition add attach it to this container """ gen = db.load_generator(namespace=self.name, gen_type=gen_type, gen_id=gen_id) self.attach_generator(gen_id, gen) return gen @staticmethod def save_logs(log_id, logs, log_output_folder): """ Appends those logs to the corresponding output file, creating it if it does not exist or appending lines to it otherwise. """ output_file = os.path.join(log_output_folder, "{}.csv".format(log_id)) if not os.path.exists(log_output_folder): os.makedirs(log_output_folder) if logs.shape[0] > 0: logging.info("appending {} rows to {}".format( logs.shape[0], output_file)) if not os.path.exists(output_file): # If these are this first persisted logs, we create the file # and include the field names as column header. logs.to_csv(output_file, index=False, header=True) else: # Otherwise, open the existing log file in append mode and add # the new logs at the end, this time without columns headers. with open(output_file, "a") as out_f: logs.to_csv(out_f, index=False, header=False) def run(self, duration, log_output_folder, delete_existing_logs=False): """ Executes all stories in the container for as long as requested. :param duration: duration of the desired simulation (start date is dictated by the clock) :type duration: pd.TimeDelta :param log_output_folder: folder where to write the logs. :type log_output_folder: string :param delete_existing_logs: """ n_iterations = self.clock.n_iterations(duration) logging.info("Starting container for {} iterations of {} for a " "total duration of {}".format(n_iterations, self.clock.step_duration, duration)) if os.path.exists(log_output_folder): if delete_existing_logs: ensure_non_existing_dir(log_output_folder) else: raise EnvironmentError( "{} exists and delete_existing_logs is " "False => refusing to start and " "overwrite logs".format(log_output_folder)) for step_number in range(n_iterations): logging.info("step : {}".format(step_number)) for story in self.stories: for log_id, logs in story.execute().items(): self.save_logs(log_id, logs, log_output_folder) self.clock.increment() @staticmethod def load_from_db(container_name): logging.info("loading container {}".format(container_name)) namespace_folder = db.namespace_folder(namespace=container_name) config_file = os.path.join(namespace_folder, "container_config.json") with open(config_file, "r") as config_h: config = json.load(config_h) clock_config = { "start": pd.Timestamp(config["clock_config"]["start"]), "step_duration": pd.Timedelta(str(config["clock_config"]["step_duration"])) } container = Container(name=container_name, master_seed=config["master_seed"], **clock_config) for population_id in db.list_populations(namespace=container_name): container.load_population(population_id) for gen_type, gen_id in db.list_generators( namespace=container_name): container.load_generator(gen_type=gen_type, gen_id=gen_id) return container def save_to_db(self, overwrite=False): """ Create a db namespace named after this container and saves all the populations there. Only static data is saved, not the stories. """ logging.info("saving container {}".format(self.name)) if db.is_namespace_existing(namespace=self.name): if overwrite: logging.warning("overwriting existing container {}".format( self.name)) db.remove_namespace(namespace=self.name) else: raise IOError("refusing to remove existing {} namespace since " "overwrite parameter is False".format(self.name)) namespace_folder = db.create_namespace(namespace=self.name) config_file = os.path.join(namespace_folder, "container_config.json") with open(config_file, "w") as o: config = { "master_seed": self.master_seed, "clock_config": { "start": self.clock_params["start"].isoformat(), "step_duration": str(self.clock_params["step_duration"]) } } json.dump(config, o, indent=4) logging.info("saving all populations") for population_id, ac in self.populations.items(): db.save_population(ac, namespace=self.name, population_id=population_id) logging.info("saving all generators") for gen_id, generator in self.generators.items(): db.save_generator(generator, namespace=self.name, gen_id=gen_id) logging.info("container saved") def save_params_to_db(self, params_type, params): """ Saves the params object to the container folder in the DB for future reference :param params_type: "build", "run" or "target" :param params: the params object """ target_file = os.path.join(db.namespace_folder(self.name), "params_{}.json".format(params_type)) with open(target_file, "w") as outfile: json.dump(params, outfile) def description(self): return { "container_name": self.name, "master_seed": self.master_seed, "populations": { id: population.description() for id, population in self.populations.items() }, "generators": { gen_id: gen.description() for gen_id, gen in self.generators.items() }, } def __str__(self): return json.dumps(self.description(), indent=4)