class FpsMeter: print_fps = True def __init__(self, name=None): if name is None: self._name_argument_string = "" else: self._name_argument_string = "(%s)" % name self._fps_history = collections.deque(maxlen=10) self._previous_time = None self._previous_calculated_fps_time = None self._stopwatch = Stopwatch() self._fps = None def update(self): self._now = self._stopwatch.get_elapsed_time() if self._previous_time is None: self._stopwatch.start() else: self._update_fps_history() self._update_fps_if_timely() self._previous_time = self._now def _update_fps_history(self): time_increment = self._now - self._previous_time fps = 1.0 / time_increment self._fps_history.append(fps) def _update_fps_if_timely(self): if self._previous_calculated_fps_time: if (self._stopwatch.get_elapsed_time() - self._previous_calculated_fps_time) > 1.0: self._calculate_fps() else: self._calculate_fps() def _calculate_fps(self): self._fps = sum(self._fps_history) / len(self._fps_history) if self.print_fps: print "FPS%s: %.1f" % (self._name_argument_string, self._fps) self._previous_calculated_fps_time = self._stopwatch.get_elapsed_time() def get_fps(self): return self._fps
class Orchestra: SAMPLE_RATE = 44100 PLAYABLE_FORMATS = ['mp3', 'flac', 'wav', 'm4b'] JACK = "jack" SSR = "ssr" @staticmethod def add_parser_arguments(parser): parser.add_argument("--rt", action="store_true", dest="realtime") parser.add_argument("-t", "--torrent", dest="torrentname", default="") parser.add_argument("-z", "--timefactor", dest="timefactor", type=float, default=1) parser.add_argument("--start", dest="start_time", type=float, default=0) parser.add_argument("-q", "--quiet", action="store_true", dest="quiet") parser.add_argument("--pretend-sequential", action="store_true", dest="pretend_sequential") parser.add_argument("--gui", action="store_true", dest="gui_enabled") parser.add_argument("--predecode", action="store_true", dest="predecode", default=True) parser.add_argument("--file-location", dest="file_location", default=DOWNLOAD_LOCATION) parser.add_argument("--fast-forward", action="store_true", dest="ff") parser.add_argument("--fast-forward-to-start", action="store_true", dest="ff_to_start") parser.add_argument("--quit-at-end", action="store_true", dest="quit_at_end") parser.add_argument("--loop", dest="loop", action="store_true") parser.add_argument("--max-passivity", dest="max_passivity", type=float) parser.add_argument("--max-pause-within-segment", dest="max_pause_within_segment", type=float) parser.add_argument("--looped-duration", dest="looped_duration", type=float) parser.add_argument("-o", "--output", dest="output", type=str, default=Orchestra.JACK) parser.add_argument("--include-non-playable", action="store_true") parser.add_argument("-f", "--file", dest="selected_files", type=int, nargs="+") parser.add_argument("--no-synth", action="store_true") parser.add_argument("--locate-peers", action="store_true") _extension_re = re.compile('\.(\w+)$') def __init__(self, sessiondir, tr_log, options): self.options = options self.sessiondir = sessiondir self.tr_log = tr_log self.realtime = options.realtime self.timefactor = options.timefactor self.quiet = options.quiet self.predecode = options.predecode self.file_location = options.file_location self._loop = options.loop self._max_passivity = options.max_passivity self.looped_duration = options.looped_duration self.output = options.output self.include_non_playable = options.include_non_playable if options.locate_peers: import geo.ip_locator self._peer_location = {} ip_locator = geo.ip_locator.IpLocator() for peeraddr in tr_log.peers: self._peer_location[peeraddr] = ip_locator.locate(peeraddr) if options.predecode: predecoder = Predecoder(tr_log, options.file_location, self.SAMPLE_RATE) predecoder.decode() if options.selected_files: tr_log.select_files(options.selected_files) self.playback_enabled = True self.fast_forwarding = False self._log_time_for_last_handled_event = 0 self.gui = None self._check_which_files_are_audio() if options.no_synth: self.synth = None else: from synth_controller import SynthController self.synth = SynthController() self._create_players() self._prepare_playable_files() self.stopwatch = Stopwatch() self.playable_chunks = self._filter_playable_chunks(tr_log.chunks) if self.include_non_playable: self.chunks = tr_log.chunks self._num_selected_files = len(self.tr_log.files) else: self.chunks = self.playable_chunks self._num_selected_files = self._num_playable_files logger.debug("total num chunks: %s" % len(tr_log.chunks)) logger.debug("num playable chunks: %s" % len(self.playable_chunks)) logger.debug("num selected chunks: %s" % len(self.chunks)) self._interpret_chunks_to_score(options.max_pause_within_segment) self._chunks_by_id = {} self.segments_by_id = {} self._playing = False self._quitting = False self.space = Space() if options.ff_to_start: self._ff_to_time = options.start_time self.set_time_cursor(0) else: self._ff_to_time = None self.set_time_cursor(options.start_time) self.scheduler = sched.scheduler(time.time, time.sleep) self._run_scheduler_thread() if self.output == self.SSR: self.ssr = SsrControl() self._warned_about_max_sources = False else: self.ssr = None def _interpret_chunks_to_score(self, max_pause_within_segment): self.score = Interpreter(max_pause_within_segment).interpret(self.playable_chunks, self.tr_log.files) if self._max_passivity: self._reduce_max_passivity_in_score() for segment in self.score: segment["duration"] /= self.timefactor def _reduce_max_passivity_in_score(self): previous_onset = 0 reduced_time = 0 for i in range(len(self.score)): if (self.score[i]["onset"] - reduced_time - previous_onset) > self._max_passivity: reduced_time += self.score[i]["onset"] - reduced_time - previous_onset - self._max_passivity self.score[i]["onset"] -= reduced_time previous_onset = self.score[i]["onset"] def _filter_playable_chunks(self, chunks): return filter(lambda chunk: (self._chunk_is_playable(chunk)), chunks) def _chunk_is_playable(self, chunk): file_info = self.tr_log.files[chunk["filenum"]] return file_info["playable_file_index"] != -1 def _run_scheduler_thread(self): self._scheduler_thread = threading.Thread(target=self._process_scheduled_events) self._scheduler_thread.daemon = True self._scheduler_thread.start() def _process_scheduled_events(self): while not self._quitting: self.scheduler.run() time.sleep(0.01) def _handle_visualizing_message(self, path, args, types, src, data): segment_id = args[0] segment = self.segments_by_id[segment_id] logger.debug("visualizing segment %s" % segment) if self.output == self.SSR: if segment["sound_source_id"]: channel = segment["sound_source_id"] - 1 self._ask_synth_to_play_segment(segment, channel=channel, pan=None) else: player = self.get_player_for_segment(segment) self._ask_synth_to_play_segment(segment, channel=0, pan=player.spatial_position.pan) def _ask_synth_to_play_segment(self, segment, channel, pan): if self.synth: logger.debug("asking synth to play %s" % segment) file_info = self.tr_log.files[segment["filenum"]] self.synth.play_segment( segment["id"], segment["filenum"], segment["start_time_in_file"] / file_info["duration"], segment["end_time_in_file"] / file_info["duration"], segment["duration"], self.looped_duration, channel, pan) self.scheduler.enter( segment["playback_duration"], 1, self.stopped_playing, [segment]) def _check_which_files_are_audio(self): for file_info in self.tr_log.files: file_info["is_audio"] = self._has_audio_extension(file_info["name"]) @staticmethod def _has_audio_extension(filename): return Orchestra._extension(filename) in Orchestra.PLAYABLE_FORMATS @staticmethod def _extension(filename): m = Orchestra._extension_re.search(filename) if m: return m.group(1).lower() def _create_players(self): self._player_class = WavPlayer self.players = [] self._player_for_peer = dict() def _prepare_playable_files(self): if self.predecode: self._get_wav_files_info() self._load_sounds() else: raise Exception("playing wav without precoding is not supported") def _load_sounds(self): if self.synth: print "loading sounds" for filenum in range(len(self.tr_log.files)): file_info = self.tr_log.files[filenum] if file_info["playable_file_index"] != -1: logger.debug("load_sound(%s)" % file_info["decoded_name"]) result = self.synth.load_sound(filenum, file_info["decoded_name"]) logger.debug("result: %s" % result) print "OK" def _get_wav_files_info(self): playable_file_index = 0 for filenum in range(len(self.tr_log.files)): file_info = self.tr_log.files[filenum] file_info["playable_file_index"] = -1 if "decoded_name" in file_info: file_info["duration"] = self._get_file_duration(file_info) if file_info["duration"] > 0: file_info["num_channels"] = self._get_num_channels(file_info) file_info["playable_file_index"] = playable_file_index logger.debug("duration for %r: %r\n" % (file_info["name"], file_info["duration"])) playable_file_index += 1 if self.include_non_playable: file_info["index"] = filenum else: file_info["index"] = file_info["playable_file_index"] self._num_playable_files = playable_file_index def _get_file_duration(self, file_info): if "decoded_name" in file_info: cmd = 'soxi -D "%s"' % file_info["decoded_name"] try: stdoutdata, stderrdata = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() return float(stdoutdata) except: logger.debug("failed to get duration for %s" % file_info["decoded_name"]) return 0 def _get_num_channels(self, file_info): if "decoded_name" in file_info: cmd = 'soxi -c "%s"' % file_info["decoded_name"] stdoutdata, stderrdata = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE).communicate() return int(stdoutdata) def get_current_log_time(self): if self.fast_forwarding: return self._log_time_for_last_handled_event else: return self.log_time_played_from + self.stopwatch.get_elapsed_time() * self.timefactor def play_non_realtime(self, quit_on_end=False): logger.debug("entering play_non_realtime") if self._loop: while True: self._play_until_end() self.set_time_cursor(0) else: self._play_until_end() if quit_on_end: self._quitting = True logger.debug("leaving play_non_realtime") def _play_until_end(self): logger.debug("entering _play_until_end") self._playing = True self.stopwatch.start() no_more_events = False while self._playing and not no_more_events: event = self._get_next_chunk_or_segment() if event: self._handle_event(event) else: no_more_events = True logger.debug("leaving _play_until_end") def _get_next_chunk_or_segment(self): logger.debug("chunk index = %d, segment index = %d" % ( self.current_chunk_index, self.current_segment_index)) chunk = self._get_next_chunk() segment = self._get_next_segment() logger.debug("next chunk: %s" % chunk) logger.debug("next segment: %s" % segment) if chunk and segment: return self._choose_nearest_chunk_or_segment(chunk, segment) elif chunk: return {"type": "chunk", "chunk": chunk} elif segment: return {"type": "segment", "segment": segment} else: return None def _get_next_chunk(self): if self.current_chunk_index < len(self.chunks): return self.chunks[self.current_chunk_index] def _get_next_segment(self): if len(self.score) == 0: return None elif self.current_segment_index < len(self.score): return self.score[self.current_segment_index] def _handle_event(self, event): if event["type"] == "chunk": self.handle_chunk(event["chunk"]) self.current_chunk_index += 1 elif event["type"] == "segment": self.handle_segment(event["segment"]) self.current_segment_index += 1 else: raise Exception("unexpected event %s" % event) def _choose_nearest_chunk_or_segment(self, chunk, segment): if chunk["t"] < segment["onset"]: return {"type": "chunk", "chunk": chunk} else: return {"type": "segment", "segment": segment} def stop(self): if self.synth: self.synth.stop_all() self._playing = False self.log_time_played_from = self.get_current_log_time() self.stopwatch.stop() def handle_segment(self, segment): logger.debug("handling segment %s" % segment) player = self.get_player_for_segment(segment) if not player: logger.debug("get_player_for_segment returned None - skipping playback") if self.fast_forwarding: self._stop_ff_if_necessary() else: now = self.get_current_log_time() time_margin = segment["onset"] - now logger.debug("time_margin=%f-%f=%f" % (segment["onset"], now, time_margin)) if not self.realtime and time_margin > 0: sleep_time = time_margin logger.debug("sleeping %f" % sleep_time) time.sleep(sleep_time) if player: logger.debug("player.enabled=%s" % player.enabled) if player and player.enabled: player.play(segment, pan=0.5) self._log_time_for_last_handled_event = segment["onset"] def handle_chunk(self, chunk): logger.debug("handling chunk %s" % chunk) player = self.get_player_for_chunk(chunk) logger.debug("get_player_for_chunk returned %s" % player) if self.fast_forwarding: self._stop_ff_if_necessary() else: now = self.get_current_log_time() time_margin = chunk["t"] - now logger.debug("time_margin=%f-%f=%f" % (chunk["t"], now, time_margin)) if not self.realtime and time_margin > 0: sleep_time = time_margin logger.debug("sleeping %f" % sleep_time) time.sleep(sleep_time) if player: logger.debug("player.enabled=%s" % player.enabled) if player and player.enabled: player.visualize(chunk) self._log_time_for_last_handled_event = chunk["t"] def _stop_ff_if_necessary(self): if self._ff_to_time is not None and \ self._log_time_for_last_handled_event >= self._ff_to_time: self._ff_to_time = None self.fast_forwarding = False self.set_time_cursor(self.log_time_played_from) def highlight_segment(self, segment): if self.gui: self.gui.highlight_segment(segment) def visualize_chunk(self, chunk, player): if len(self.visualizers) > 0: file_info = self.tr_log.files[chunk["filenum"]] self._chunks_by_id[chunk["id"]] = chunk self._tell_visualizers( "/chunk", chunk["id"], chunk["begin"], chunk["end"] - chunk["begin"], file_info["index"], player.id, chunk["t"]) def visualize_segment(self, segment, player): if len(self.visualizers) > 0: if self.ssr: segment["sound_source_id"] = self.ssr.allocate_source() if not segment["sound_source_id"] and not self._warned_about_max_sources: print "WARNING: max sources exceeded, skipping segment playback (this warning will not be repeated)" self._warned_about_max_sources = True file_info = self.tr_log.files[segment["filenum"]] self.segments_by_id[segment["id"]] = segment self._tell_visualizers( "/segment", segment["id"], segment["begin"], segment["end"] - segment["begin"], file_info["index"], player.id, segment["t"], segment["playback_duration"]) else: self._ask_synth_to_play_segment(segment, channel=0, pan=0.5) def stopped_playing(self, segment): logger.debug("stopped segment %s" % segment) if self.gui: self.gui.unhighlight_segment(segment) if len(self.visualizers) > 0: if self.ssr and segment["sound_source_id"]: self.ssr.free_source(segment["sound_source_id"]) def play_segment(self, segment, player): self.segments_by_id[segment["id"]] = segment if self.looped_duration: segment["playback_duration"] = self.looped_duration else: segment["playback_duration"] = segment["duration"] self.visualize_segment(segment, player) def _send_torrent_info_to_uninformed_visualizers(self): for visualizer in self.visualizers: if not visualizer.informed_about_torrent: self._send_torrent_info_to_visualizer(visualizer) def _send_torrent_info_to_visualizer(self, visualizer): visualizer.send( "/torrent", self._num_selected_files, self.tr_log.lastchunktime(), self.tr_log.total_file_size(), len(self.chunks), len(self.score)) for filenum in range(len(self.tr_log.files)): file_info = self.tr_log.files[filenum] if self.include_non_playable or file_info["playable_file_index"] != -1: visualizer.send( "/file", file_info["index"], file_info["offset"], file_info["length"]) visualizer.informed_about_torrent = True def get_player_for_chunk(self, chunk): try: return chunk["player"] except KeyError: peer_player = self.get_player_for_peer(chunk["peeraddr"]) chunk["player"] = peer_player return peer_player def get_player_for_segment(self, segment): try: return segment["player"] except KeyError: peer_player = self.get_player_for_peer(segment["peeraddr"]) segment["player"] = peer_player return peer_player def get_player_for_peer(self, peeraddr): peer_player = None try: peer_player = self._player_for_peer[peeraddr] except KeyError: peer_player = self._create_player(peeraddr) self.players.append(peer_player) self._player_for_peer[peeraddr] = peer_player return peer_player def _create_player(self, addr): count = len(self.players) logger.debug("creating player number %d" % count) player = self._player_class(self, count) if self.options.locate_peers and self._peer_location[addr] is not None: x, y = self._peer_location[addr] location_str = "%s,%s" % (x, y) else: location_str = "" self._tell_visualizers( "/peer", player.id, addr, player.spatial_position.bearing, location_str) return player def set_time_cursor(self, log_time): assert not self.realtime logger.debug("setting time cursor at %f" % log_time) self.log_time_played_from = log_time if self._playing: self.stopwatch.restart() self.current_chunk_index = self._get_current_chunk_index() self.current_segment_index = self._get_current_segment_index() def _get_current_chunk_index(self): index = 0 next_to_last_index = len(self.chunks) - 2 while index < next_to_last_index: if self.chunks[index+1]["t"] >= self.log_time_played_from: return index index += 1 return len(self.chunks) - 1 def _get_current_segment_index(self): index = 0 next_to_last_index = len(self.score) - 2 while index < next_to_last_index: if self.score[index+1]["onset"] >= self.log_time_played_from: return index index += 1 return len(self.score) - 1 def _handle_set_listener_position(self, path, args, types, src, data): if self.ssr: x, y = args self.ssr.set_listener_position(x, y) def _handle_set_listener_orientation(self, path, args, types, src, data): if self.ssr: orientation = args[0] self.ssr.set_listener_orientation(orientation) def _handle_place_segment(self, path, args, types, src, data): segment_id, x, y, duration = args if self.ssr: segment = self.segments_by_id[segment_id] sound_source_id = segment["sound_source_id"] if sound_source_id is not None: self.ssr.place_source(sound_source_id, x, y, duration) else: pan = self._spatial_position_to_stereo_pan(x, y) if self.synth: self.synth.pan(segment_id, pan) def _handle_enable_smooth_movement(self, path, args, types, src, data): if self.ssr: self.ssr.enable_smooth_movement() def _handle_start_segment_movement_from_peer(self, path, args, types, src, data): segment_id, duration = args if self.ssr: segment = self.segments_by_id[segment_id] sound_source_id = segment["sound_source_id"] if sound_source_id is not None: player = self.get_player_for_segment(segment) self.ssr.start_source_movement( sound_source_id, player.trajectory, duration) def _spatial_position_to_stereo_pan(self, x, y): # compare rectangular_visualizer.Visualizer.pan_segment # NOTE: assumes default listener position and orientation! return float(x) / 5 + 0.5 def reset(self): self._free_sounds() self._tell_visualizers("/reset") def _free_sounds(self): if self.synth: for filenum in range(len(self.tr_log.files)): file_info = self.tr_log.files[filenum] if file_info["playable_file_index"] != -1: self.synth.free_sound(filenum) def _tell_visualizers(self, *args): self._send_torrent_info_to_uninformed_visualizers() self.server._tell_visualizers(*args)
class MainWindow(QtGui.QWidget): def __init__(self, experiment): QtGui.QMainWindow.__init__(self) experiment.window = self self._experiment = experiment self._layout = QtGui.QVBoxLayout() self.sliders = {} self.setLayout(self._layout) self._add_parameter_form() self._add_map_view() self._create_menu() self._generate_map_and_path() self.stopwatch = Stopwatch() self._frame_count = 0 timer = QtCore.QTimer(self) timer.setInterval(1000. / FRAME_RATE) QtCore.QObject.connect(timer, QtCore.SIGNAL('timeout()'), self._update) timer.start() def _add_map_view(self): self._map_view = MapView(self, experiment) self._layout.addWidget(self._map_view) def _add_parameter_form(self): layout = QtGui.QFormLayout() self._add_slider(layout, "novelty") self._add_slider(layout, "extension") self._add_slider(layout, "location_preference") self._layout.addLayout(layout) def _add_slider(self, layout, name): slider = QtGui.QSlider(QtCore.Qt.Horizontal) slider.setRange(0, SLIDER_PRECISION) slider.setSingleStep(1) slider.setValue(0.0) layout.addRow(name, slider) self.sliders[name] = slider def _create_menu(self): menu_bar = QtGui.QMenuBar() self._layout.setMenuBar(menu_bar) self._menu = menu_bar.addMenu("Navigator test") self._add_generate_new_path_action() self._add_extend_path_action() self._add_generate_map_action() def _add_generate_new_path_action(self): action = QtGui.QAction('Generate new &path', self) action.setShortcut('Ctrl+P') action.triggered.connect(self._experiment.generate_new_path) self._menu.addAction(action) def _add_extend_path_action(self): action = QtGui.QAction('&Extend path', self) action.setShortcut('Ctrl+E') action.triggered.connect(self._experiment.extend_path) self._menu.addAction(action) def _add_generate_map_action(self): action = QtGui.QAction('Generate &map', self) action.setShortcut('Ctrl+M') action.triggered.connect(self._generate_map_and_path) self._menu.addAction(action) def _generate_map_and_path(self): self._experiment.generate_map() self._experiment.create_navigator() self._experiment.generate_new_path() def _update(self): self.now = self.stopwatch.get_elapsed_time() if self._frame_count == 0: self.stopwatch.start() else: time_increment = self.now - self.previous_frame_time self._experiment.proceed(time_increment) self._map_view.updateGL() self.previous_frame_time = self.now self._frame_count += 1
class MainWindow(QtGui.QWidget): def __init__(self, experiment): QtGui.QMainWindow.__init__(self) experiment.window = self self._experiment = experiment self._layout = QtGui.QVBoxLayout() self.setLayout(self._layout) self._add_parameter_form() self._add_map_view() self._create_menu() self.stopwatch = Stopwatch() self._frame_count = 0 timer = QtCore.QTimer(self) timer.setInterval(1000. / FRAME_RATE) QtCore.QObject.connect(timer, QtCore.SIGNAL('timeout()'), self._update) timer.start() def _add_map_view(self): self._map_view = MapView(self, experiment) self._layout.addWidget(self._map_view) def _add_parameter_form(self): self._parameter_sliders = {} layout = QtGui.QFormLayout() for parameter_name in PARAMETERS: default_value = getattr(args, parameter_name) self._add_slider(layout, parameter_name, default_value) self._layout.addLayout(layout) def _add_slider(self, layout, name, default_value): slider = QtGui.QSlider(QtCore.Qt.Horizontal) slider.setRange(0, SLIDER_PRECISION) slider.setSingleStep(1) slider.setValue(int(default_value * SLIDER_PRECISION)) slider.valueChanged.connect(lambda event: self.update_flaneur_parameter(name)) layout.addRow(name, slider) self._parameter_sliders[name] = slider def update_flaneur_parameter(self, parameter_name): slider = self._parameter_sliders[parameter_name] value = float(slider.value()) / SLIDER_PRECISION setattr(self._experiment.flaneur, parameter_name, value) def _create_menu(self): menu_bar = QtGui.QMenuBar() self._layout.setMenuBar(menu_bar) self._menu = menu_bar.addMenu("Flaneur test") self._add_reset_action() self._add_generate_map_action() def _add_reset_action(self): action = QtGui.QAction('Reset', self) action.setShortcut('R') action.triggered.connect(self._experiment.reset) self._menu.addAction(action) def _add_generate_map_action(self): action = QtGui.QAction('Generate &map', self) action.setShortcut('Ctrl+M') action.triggered.connect(self._generate_map) self._menu.addAction(action) def _generate_map(self): self._experiment.generate_map() def _update(self): self.now = self.stopwatch.get_elapsed_time() if self._frame_count == 0: self.stopwatch.start() else: time_increment = self.now - self.previous_frame_time self._experiment.proceed(time_increment) self._map_view.updateGL() self.previous_frame_time = self.now self._frame_count += 1
class Window: def __init__(self, args): self.args = args self.width = args.width self.height = args.height self.margin = args.margin self.show_fps = args.show_fps self.gl_display_mode = GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH self._fullscreen = False self.exiting = False self._frames = [] self.time_increment = 0 self.stopwatch = Stopwatch() self._frame_count = 0 self._set_camera_position(CAMERA_POSITION) self._set_camera_orientation(CAMERA_Y_ORIENTATION, CAMERA_X_ORIENTATION) self.fovy = 45 self.near = 0.1 self.far = 100.0 self._text_renderer_class = getattr(text_renderer_module, TEXT_RENDERERS[args.text_renderer]) if self.show_fps: self.fps_history = collections.deque(maxlen=10) self.previous_shown_fps_time = None def run(self): self.window_width = self.width + self.margin*2 self.window_height = self.height + self.margin*2 glutInit(sys.argv) if self.args.left is None: self._left = (glutGet(GLUT_SCREEN_WIDTH) - self.window_width) / 2 else: self._left = self.args.left if self.args.top is None: self._top = (glutGet(GLUT_SCREEN_HEIGHT) - self.window_height) / 2 else: self._top = self.args.top glutInitDisplayMode(self.gl_display_mode) glutInitWindowSize(self.window_width, self.window_height) self._non_fullscreen_window = glutCreateWindow("") glutDisplayFunc(self.DrawGLScene) glutIdleFunc(self.DrawGLScene) glutReshapeFunc(self.ReSizeGLScene) glutKeyboardFunc(self.keyPressed) self.InitGL() glutPositionWindow(self._left, self._top) if self.args.fullscreen: self._open_fullscreen_window() self._fullscreen = True self.ReSizeGLScene(self.window_width, self.window_height) glutMainLoop() def _open_fullscreen_window(self): glutGameModeString("%dx%d:32@75" % (self.window_width, self.window_height)) glutEnterGameMode() glutSetCursor(GLUT_CURSOR_NONE) glutDisplayFunc(self.DrawGLScene) glutIdleFunc(self.DrawGLScene) glutReshapeFunc(self.ReSizeGLScene) glutKeyboardFunc(self.keyPressed) self.InitGL() glutPositionWindow(self._left, self._top) def InitGL(self): glClearColor(1.0, 1.0, 1.0, 0.0) glClearAccum(0.0, 0.0, 0.0, 0.0) glClearDepth(1.0) glShadeModel(GL_SMOOTH) def ReSizeGLScene(self, window_width, window_height): self.window_width = window_width self.window_height = window_height if window_height == 0: window_height = 1 glViewport(0, 0, window_width, window_height) self.width = window_width - 2*self.margin self.height = window_height - 2*self.margin self._aspect_ratio = float(window_width) / window_height self.min_dimension = min(self.width, self.height) self.configure_2d_projection() self.resized_window() def resized_window(self): pass def configure_2d_projection(self): glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0.0, self.window_width, self.window_height, 0.0, -1.0, 1.0) glMatrixMode(GL_MODELVIEW) def _set_camera_position(self, position): self._camera_position = position def _set_camera_orientation(self, y_orientation, x_orientation): self._camera_y_orientation = y_orientation self._camera_x_orientation = x_orientation def configure_3d_projection(self, pixdx=0, pixdy=0): fov2 = ((self.fovy*math.pi) / 180.0) / 2.0 top = self.near * math.tan(fov2) bottom = -top right = top * self._aspect_ratio left = -right xwsize = right - left ywsize = top - bottom dx = -(pixdx*xwsize/self.width) dy = -(pixdy*ywsize/self.height) glMatrixMode(GL_PROJECTION) glLoadIdentity() glFrustum (left + dx, right + dx, bottom + dy, top + dy, self.near, self.far) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glRotatef(self._camera_x_orientation, 1.0, 0.0, 0.0) glRotatef(self._camera_y_orientation, 0.0, 1.0, 0.0) glTranslatef(self._camera_position.x, self._camera_position.y, self._camera_position.z) def DrawGLScene(self): if self.exiting: glutDestroyWindow(glutGetWindow()) return try: self._draw_gl_scene_error_handled() except Exception as error: traceback_printer.print_traceback() self.exiting = True raise error def _draw_gl_scene_error_handled(self): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() self.now = self.current_time() if self._frame_count == 0: self.stopwatch.start() else: self.time_increment = self.now - self.previous_frame_time self.configure_2d_projection() glTranslatef(self.margin, self.margin, 0) self._render_frames() self.render() if self.show_fps: self.update_fps_history() self.show_fps_if_timely() glutSwapBuffers() self.previous_frame_time = self.now self._frame_count += 1 def current_time(self): return self.stopwatch.get_elapsed_time() def update_fps_history(self): if self.time_increment > 0: fps = 1.0 / self.time_increment self.fps_history.append(fps) def show_fps_if_timely(self): if self.previous_shown_fps_time: if (self.now - self.previous_shown_fps_time) > 1.0: self.calculate_and_show_fps() else: self.calculate_and_show_fps() def calculate_and_show_fps(self): print sum(self.fps_history) / len(self.fps_history) self.previous_shown_fps_time = self.now def keyPressed(self, key, x, y): if key == ESCAPE: self.exiting = True elif key == 's': self._dump_screen() elif key == 'f': if self._fullscreen: glutSetCursor(GLUT_CURSOR_INHERIT) glutLeaveGameMode() glutSetWindow(self._non_fullscreen_window) self._fullscreen = False else: self._open_fullscreen_window() self._fullscreen = True def new_layer(self, rendering_function): layer = Layer(rendering_function, self.new_display_list_id()) self._layers.append(layer) return layer def new_display_list_id(self): return glGenLists(1) def draw_text(self, text, size, x, y, z, font=None, spacing=None, v_align="left", h_align="top"): if font is None: font = self.args.font self.text_renderer(text, size, font).render(x, y, z, v_align, h_align) def text_renderer(self, text, size, font=None): if font is None: font = self.args.font return self._text_renderer_class(self, text, size, font) @staticmethod def add_parser_arguments(parser): parser.add_argument('-width', dest='width', type=int, default=1024) parser.add_argument('-height', dest='height', type=int, default=768) parser.add_argument('-margin', dest='margin', type=int, default=0) parser.add_argument("-left", type=int) parser.add_argument("-top", type=int) parser.add_argument("-fullscreen", action="store_true") parser.add_argument('-show-fps', dest='show_fps', action='store_true') parser.add_argument("--text-renderer", choices=TEXT_RENDERERS.keys(), default="glut") parser.add_argument("--font", type=str) def add_frame(self, frame): self._frames.append(frame) def _render_frames(self): for frame in self._frames: frame.render_with_border()
class Experiment(EventListener): @staticmethod def add_parser_arguments(parser): parser.add_argument("-profile", "-p") parser.add_argument("-entity", type=str) parser.add_argument("-train", action="store_true") parser.add_argument("-training-data", type=str) parser.add_argument("-training-duration", type=float) parser.add_argument("-training-data-frame-rate", type=int, default=50) parser.add_argument("-bvh", type=str, help="If provided, this specifies both the skeleton and the training data.") parser.add_argument("-bvh-speed", type=float, default=1.0) parser.add_argument("-skeleton", type=str) parser.add_argument("-joint") parser.add_argument("-frame-rate", type=float, default=50.0) parser.add_argument("-unit-cube", action="store_true") parser.add_argument("-input-y-offset", type=float, default=.0) parser.add_argument("-output-y-offset", type=float, default=.0) parser.add_argument("-export-dir", default="export") parser.add_argument("--floor", action="store_true") parser.add_argument("--backend-only", action="store_true") parser.add_argument("--ui-only", action="store_true") parser.add_argument("--backend-host", default="localhost") parser.add_argument("--websockets", action="store_true", help="Force websockets support (enabled automatically by --backend-only)") parser.add_argument("--no-websockets", action="store_true", help="Force running without websockets support (e.g when combing --ui-only and --event-log-source)") parser.add_argument("--launch-when-ready", help="Run command when websocket server ready") parser.add_argument("--output-receiver-host") parser.add_argument("--output-receiver-port", type=int, default=10000) parser.add_argument("--with-profiler", action="store_true") parser.add_argument("--z-up", action="store_true", help="Use Z-up for BVHs") parser.add_argument("--show-fps", action="store_true") parser.add_argument("--receive-from-pn", action="store_true") parser.add_argument("--pn-host", default="localhost") parser.add_argument("--pn-port", type=int, default=tracking.pn.receiver.SERVER_PORT_BVH) parser.add_argument("--random-seed", type=int) parser.add_argument("--start-frame", type=int) parser.add_argument("--deterministic", action="store_true", help="Handle time deterministically (fixed time interval between updates) rather than taking " + "real time into account. May cause latency.") parser.add_argument("--stopped", action="store_true", help="Start in stopped mode") def __init__(self, parser, event_handlers={}): event_handlers.update({ Event.START: self._start, Event.STOP: self._stop, Event.START_EXPORT_BVH: self._start_export_bvh, Event.STOP_EXPORT_BVH: self._stop_export_bvh, Event.SET_CURSOR: lambda event: self.update_cursor(event.content), Event.PROCEED_TO_NEXT_FRAME: self._proceed_to_next_frame, Event.SAVE_STUDENT: self._save_student, Event.LOAD_STUDENT: self._load_student, Event.SET_FRICTION: lambda event: self.set_friction(event.content), Event.SET_LEARNING_RATE: lambda event: self.student.set_learning_rate(event.content), Event.SET_MODEL_NOISE_TO_ADD: self._set_model_noise_to_add, Event.SET_MIN_TRAINING_LOSS: self._set_min_training_loss, Event.SET_MAX_ANGULAR_STEP: lambda event: self.entity.set_max_angular_step( event.content), }) EventListener.__init__(self, handlers=event_handlers) args, _remaining_args = parser.parse_known_args() if args.random_seed is not None: random.seed(args.random_seed) if args.profile: profile_path = "%s/%s.profile" % (self.profiles_dir, args.profile) profile_args_string = open(profile_path).read() profile_args_strings = profile_args_string.split() args, _remaining_args = parser.parse_known_args(profile_args_strings, namespace=args) self._student_model_path = "%s/%s.model" % (self.profiles_dir, args.profile) self._entity_model_path = "%s/%s.entity.model" % (self.profiles_dir, args.profile) self._training_data_path = "%s/%s.data" % (self.profiles_dir, args.profile) entity_module = imp.load_source("entity", "entities/%s.py" % args.entity) if hasattr(entity_module, "Entity"): self.entity_class = entity_module.Entity else: self.entity_class = BaseEntity self.entity_class.add_parser_arguments(parser) if not args.backend_only: self._entity_scene_module = imp.load_source("entity", "entities/%s_scene.py" % args.entity) self._entity_scene_module.Scene.add_parser_arguments(parser) self.add_ui_parser_arguments(parser) self.add_parser_arguments_second_pass(parser, args) args = parser.parse_args() if args.profile: args = parser.parse_args(profile_args_strings, namespace=args) self.args = args skeleton_bvh_path = self._get_skeleton_bvh_path() if skeleton_bvh_path: self.bvh_reader = self._create_bvh_reader(skeleton_bvh_path) self.pose = self.bvh_reader.get_hierarchy().create_pose() else: self.bvh_reader = None self.pose = None if self.args.train or self.needs_training_data(): training_data_bvh_path = self._get_training_data_bvh_path() if training_data_bvh_path: if training_data_bvh_path == skeleton_bvh_path: self.training_data_bvh_reader = self.bvh_reader else: self.training_data_bvh_reader = self._create_bvh_reader( training_data_bvh_path, read_frames=self.should_read_bvh_frames()) else: self.training_data_bvh_reader = self.bvh_reader self.training_entity = self.entity_class( self.training_data_bvh_reader, self.pose, self.args.floor, self.args.z_up, self.args) if self.bvh_reader: self.bvh_writer = BvhWriter(self.bvh_reader.get_hierarchy(), self.bvh_reader.get_frame_time()) self.input = None self.output = None self.entity = self.entity_class(self.bvh_reader, self.pose, self.args.floor, self.args.z_up, self.args) self._running = not args.stopped self.stopwatch = Stopwatch() if self.args.show_fps: self._fps_meter = FpsMeter() self.now = None self._frame_count = 0 self._ui_handlers = set() self._ui_handlers_lock = threading.Lock() self._exporting_output = False if args.output_receiver_host: from connectivity import avatar_osc_sender self._output_sender = avatar_osc_sender.AvatarOscBvhSender( args.output_receiver_port, args.output_receiver_host) else: self._output_sender = None if self.args.entity == "hierarchical" and self.args.friction: self._enable_friction = True if args.receive_from_pn: self._pn_receiver = tracking.pn.receiver.PnReceiver() print "connecting to PN server..." self._pn_receiver.connect(args.pn_host, args.pn_port) print "ok" pn_pose = self.bvh_reader.get_hierarchy().create_pose() self._pn_entity = self.entity_class(self.bvh_reader, pn_pose, self.args.floor, self.args.z_up, self.args) self._input_from_pn = None pn_receiver_thread = threading.Thread(target=self._receive_from_pn) pn_receiver_thread.daemon = True pn_receiver_thread.start() def needs_training_data(self): return False def _get_skeleton_bvh_path(self): if self.args.bvh: if self.args.skeleton: raise Exception("Cannot provide both -bvh and -skeleton") return self.args.bvh return self.args.skeleton def _get_training_data_bvh_path(self): if self.args.bvh: if self.args.training_data: raise Exception("Cannot provide both -bvh and -training-data") return self.args.bvh return self.args.training_data def _create_bvh_reader(self, pattern, read_frames=True): bvh_filenames = glob.glob(pattern) if len(bvh_filenames) == 0: raise Exception("no files found matching the pattern %s" % pattern) print "loading BVHs from %s..." % pattern bvh_reader = BvhCollection(bvh_filenames) bvh_reader.read(read_frames=read_frames) print "ok" return bvh_reader def _save_student(self, event): filename = event.content print "saving %s..." % filename self.student.save(filename) print "ok" def _load_student(self, event): filename = event.content print "loading %s..." % filename self.student.load(filename) print "ok" def set_friction(self, enabled, inform_ui=False): if enabled != self._enable_friction: self._enable_friction = enabled self.entity.set_friction(enabled) if self.args.enable_io_blending: self._io_blending_entity.set_friction(enabled) if inform_ui: self.send_event_to_ui(Event(Event.SET_FRICTION, enabled)) def add_parser_arguments_second_pass(self, parser, args): pass def ui_connected(self, handler): with self._ui_handlers_lock: self._ui_handlers.add(handler) if self.entity.processed_input is not None: self.send_event_to_ui(Event(Event.INPUT, self.entity.processed_input)) if self.output is not None: self.process_and_broadcast_output() self.send_event_to_ui(Event(Event.FRAME_COUNT, self._frame_count)) def ui_disconnected(self, handler): with self._ui_handlers_lock: if handler in self._ui_handlers: self._ui_handlers.remove(handler) def update_cursor(self, cursor): self.entity.set_cursor(cursor) def add_ui_parser_arguments(self, parser): from ui.ui import MainWindow MainWindow.add_parser_arguments(parser) def run_backend_and_or_ui(self): if self.args.start_frame is not None: print "fast-forwarding to frame %d..." % self.args.start_frame self.time_increment = 1. / self.args.frame_rate for n in range(self.args.start_frame): self._proceed_and_update() print "ok" else: self.entity.update(self.input) self.update() run_backend = not self.args.ui_only run_ui = not self.args.backend_only if run_ui: self._scene_class = self._entity_scene_module.Scene if self.args.with_profiler: import yappi yappi.start() if run_backend and run_ui: if self.args.websockets: websocket_server = self._create_websocket_server() self._start_in_new_thread(websocket_server) self._server = self._create_single_process_server() self._set_up_timed_refresh() self._start_in_new_thread(self._server) client = SingleProcessClient(self._server) self.run_ui(client) elif run_backend: self._server = self._create_websocket_server() self._set_up_timed_refresh() try: self._server.start() except KeyboardInterrupt: pass elif run_ui: if self.args.no_websockets: client = None else: client = WebsocketClient(self.args.backend_host) self.run_ui(client) if self.args.with_profiler: yappi.get_func_stats().print_all() def _start(self, event): self._running = True def _stop(self, event): self._running = False def is_running(self): return self._running def update(self): pass def _update_and_refresh_uis(self): if self.now is None: self.now = 0 self.stopwatch.start() else: self.now = self.current_time() if self.is_running(): if self.args.deterministic: self.time_increment = 1. / self.args.frame_rate else: self.time_increment = self.now - self.previous_frame_time if self.args.show_fps: self._fps_meter.update() self._proceed_and_update() if self.entity.processed_input is not None: self.send_event_to_ui(Event(Event.INPUT, self.entity.processed_input)) if self.output is not None: self.process_and_broadcast_output() self.previous_frame_time = self.now if self.output is not None and (self._exporting_output or self._output_sender): self.entity.parameters_to_processed_pose(self.output, self.pose) if self._exporting_output: self._export_bvh() if self._output_sender: self._send_output() def _proceed_and_update(self): self.proceed() self.entity.update(self.input) self.update() self._frame_count += 1 self.send_event_to_ui(Event(Event.FRAME_COUNT, self._frame_count)) def process_and_broadcast_output(self): if self._server.client_subscribes_to(Event.OUTPUT): self.processed_output = self.entity.process_output(self.output) self.send_event_to_ui(Event(Event.OUTPUT, self.processed_output)) def _proceed_to_next_frame(self, event): self.time_increment = 1. / self.args.frame_rate self._proceed_and_update() if self.output is not None: self.process_and_broadcast_output() if self.entity.processed_input is not None: self.send_event_to_ui(Event(Event.INPUT, self.entity.processed_input)) def send_event_to_ui(self, event): with self._ui_handlers_lock: for ui_handler in self._ui_handlers: if not (event.source == "PythonUI" and ui_handler.__class__ == SingleProcessUiHandler): ui_handler.send_event(event) def current_time(self): return self.stopwatch.get_elapsed_time() def _training_duration(self): if self.args.training_duration: return self.args.training_duration elif hasattr(self.training_entity, "get_duration"): return self.training_entity.get_duration() else: raise Exception( "training duration specified in neither arguments nor the %s class" % \ self.training_entity.__class__.__name__) def _create_single_process_server(self): return SingleProcessServer(SingleProcessUiHandler, experiment=self) def _create_websocket_server(self): server = WebsocketServer(WebsocketUiHandler, {"experiment": self}) print "websocket server ready" self._invoke_potential_launcher_in_args() return server def _invoke_potential_launcher_in_args(self): if self.args.launch_when_ready: print "launching %r" % self.args.launch_when_ready self._launched_process = subprocess.Popen(self.args.launch_when_ready, shell=True) def _set_up_timed_refresh(self): self._server.add_periodic_callback( self._update_and_refresh_uis, 1000. / self.args.frame_rate) def _start_in_new_thread(self, server): server_thread = threading.Thread(target=server.start) server_thread.daemon = True server_thread.start() def _start_export_bvh(self, event): print "exporting BVH" self._exporting_output = True def _stop_export_bvh(self, event): if not os.path.exists(self.args.export_dir): os.mkdir(self.args.export_dir) export_path = self._get_export_path() print "saving export to %s" % export_path self.bvh_writer.write(export_path) self._exporting_output = False def _get_export_path(self): i = 1 while True: path = "%s/export%03d.bvh" % (self.args.export_dir, i) if not os.path.exists(path): return path i += 1 def _export_bvh(self): self.bvh_writer.add_pose_as_frame(self.pose) def _send_output(self): avatar_index = 0 self._output_sender.send_frame(avatar_index, self.pose, self.entity) def _receive_from_pn(self): for frame in self._pn_receiver.get_frames(): self._input_from_pn = self._pn_entity.get_value_from_frame(frame) def _set_model_noise_to_add(self, event): self.model_noise_to_add = event.content / 100 def _set_min_training_loss(self, event): self.min_training_loss = 0
class MasterBehavior(Behavior): def __init__(self): Behavior.__init__(self) self._recall_amount = args.recall_amount self.memorize = args.memorize self.auto_friction = args.auto_friction self._auto_switch_enabled = False self.input_only = False self._input = None self._noise_amount = 0 self.reset_translation() self._stopwatch = Stopwatch() self._stopwatch.start() def set_noise_amount(self, amount): self._noise_amount = amount def reset_translation(self): master_entity.reset_constrainers() self._chainer = Chainer() self._chainer.put(numpy.zeros(3)) self._chainer.get() self._chainer.switch_source() self._selector = Selector(self._chainer.switch_source) def on_recall_amount_changed(self): pass def set_recall_amount(self, recall_amount): self._recall_amount = recall_amount def get_recall_amount(self): return self._recall_amount def set_model(self, model_name): self._improvise = improvise_behaviors[model_name] self._chainer.switch_source() def proceed(self, time_increment): if self._noise_amount > 0: students["autoencoder"].add_noise(self._noise_amount) self._improvise.proceed(time_increment) recall_behavior.proceed(time_increment) if self.auto_friction: if self._recall_amount < 0.5: self._set_master_entity_friction_and_update_ui(True) else: self._set_master_entity_friction_and_update_ui(False) def _set_master_entity_friction_and_update_ui(self, value): master_entity.set_friction(value) application.on_friction_changed(value) def sends_output(self): return True def on_input(self, input_): self._input = input_ if self.memorize: memory.on_input(input_) def set_auto_switch_enabled(self, value): self._auto_switch_enabled = value def get_output(self): if self.input_only: return self._input if self._auto_switch_enabled: self._recall_amount = self._get_auto_switch_recall_amount() self.on_recall_amount_changed() improvise_output = self._get_improvise_output() recall_output = recall_behavior.get_output() if args.verbose: self._print_output_info("improvise_output", improvise_output) self._print_output_info("recall_output", recall_output) if recall_output is None: if self._recall_amount > 0: application.print_and_log("WARNING: recall amount > 0 but no recall output") translation = self._pass_through_selector_to_update_its_state( get_translation(improvise_output)) orientations = get_orientations(improvise_output) else: translation = self._selector.select( get_translation(improvise_output), get_translation(recall_output), self._recall_amount) orientations = get_orientations( master_entity.interpolate(improvise_output, recall_output, self._recall_amount)) self._chainer.put(translation) translation = self._chainer.get() output = combine_translation_and_orientations(translation, orientations) return output def _pass_through_selector_to_update_its_state(self, value): return self._selector.select(value, None, 0) def _get_auto_switch_recall_amount(self): return (math.sin(self._stopwatch.get_elapsed_time() * .5) + 1) / 2 def _print_output_info(self, name, output): if output is None: return root_quaternion = output[0:4] print "%s root: %s" % (name, root_quaternion) def _get_improvise_output(self): reduction = self._improvise.get_reduction() if reduction is None: return None return student.inverse_transform(numpy.array([reduction]))[0]
class Visualizer: def __init__(self, args, file_class=File, chunk_class=Chunk, segment_class=Segment, peer_class=Peer): if hasattr(self, "_initialized") and self._initialized: return self.file_class = file_class self.chunk_class = chunk_class self.segment_class = segment_class self.peer_class = peer_class self.args = args self.sync = args.sync self.width = args.width self.height = args.height self.margin = args.margin self.show_fps = args.show_fps self.export = args.export self.capture_message_log = args.capture_message_log self.play_message_log = args.play_message_log self.waveform_gain = args.waveform_gain self._standalone = args.standalone self._target_aspect_ratio = self._get_aspect_ratio_from_args() self.logger = logging.getLogger("visualizer") self.reset() self._frame_count = 0 self.exiting = False self.time_increment = 0 self.stopwatch = Stopwatch() self._synth_instance = None self._synth_port = None self._synced = False self._layers = [] self._warned_about_missing_pan_segment = False self.gl_display_mode = GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH self._accum_enabled = False self._3d_enabled = False self.fovy = 45 self.near = 0.1 self.far = 100.0 self._fullscreen = False self._text_renderer_class = getattr(text_renderer_module, TEXT_RENDERERS[args.text_renderer]) if args.camera_script: self._camera_script = CameraScriptInterpreter(args.camera_script) else: self._camera_script = None if self.show_fps: self.fps_history = collections.deque(maxlen=10) self.previous_shown_fps_time = None if not args.standalone: if args.port: port = args.port else: port = self._get_orchestra_port() self.orchestra_host = args.host self.orchestra_port = port self.setup_osc() self.orchestra.register(self.server.port) self._screen_dumper = Exporter(".", self.margin, self.margin, self.width, self.height) if self.export: self.export_fps = args.export_fps import shutil if args.export_dir: export_dir = args.export_dir elif hasattr(args, "sessiondir"): export_dir = "%s/rendered_%s" % (args.sessiondir, self.__class__.__name__) else: export_dir = "export" if os.path.exists(export_dir): shutil.rmtree(export_dir) os.mkdir(export_dir) self.exporter = Exporter(export_dir, self.margin, self.margin, self.width, self.height) if self.play_message_log: self._message_log_reader = MessageLogReader(self.play_message_log) if self.capture_message_log: self._message_log_writer = MessageLogWriter(self.capture_message_log) self._audio_capture_start_time = None self._initialized = True def _get_aspect_ratio_from_args(self): w, h = map(float, self.args.aspect.split(":")) return w / h def _get_orchestra_port(self): if self.args.host == "localhost": return self._read_port_from_disk() else: return self._read_port_from_network_share() def _read_port_from_disk(self): self._read_port_from_file("server_port.txt") def _read_port_from_file(self, filename): f = open(filename, "r") line = f.read() port = int(line) f.close() return port def _read_port_from_network_share(self): if platform.system() == "Linux": return self._read_port_with_unix_smbclient() elif platform.system() == "Windows": return self._read_port_via_windows_samba_access() else: raise Exception("don't know how to handle your OS (%s)" % platform.system()) def _read_port_with_unix_smbclient(self): subprocess.call( 'smbclient -N \\\\\\\\%s\\\\TorrentialForms -c "get server_port.txt server_remote_port.txt"' % self.args.host, shell=True) return self._read_port_from_file("server_remote_port.txt") def _read_port_via_windows_samba_access(self): return self._read_port_from_file( '\\\\%s\\TorrentialForms\\server_port.txt' % self.args.host) def reset(self): self.files = {} self.peers = {} self.peers_by_addr = {} self._segments_by_id = {} self.torrent_length = 0 self.torrent_title = "" self.torrent_download_completion_time = None self.num_segments = 0 self.num_received_segments = 0 self._notified_finished = False def enable_3d(self): self._3d_enabled = True def run(self): self.window_width = self.width + self.margin*2 self.window_height = self.height + self.margin*2 glutInit(sys.argv) if self.args.left is None: self._left = (glutGet(GLUT_SCREEN_WIDTH) - self.window_width) / 2 else: self._left = self.args.left if self.args.top is None: self._top = (glutGet(GLUT_SCREEN_HEIGHT) - self.window_height) / 2 else: self._top = self.args.top glutInitDisplayMode(self.gl_display_mode) glutInitWindowSize(self.window_width, self.window_height) self._non_fullscreen_window = glutCreateWindow("") glutDisplayFunc(self.DrawGLScene) glutIdleFunc(self.DrawGLScene) glutReshapeFunc(self.ReSizeGLScene) glutKeyboardFunc(self.keyPressed) self.InitGL() glutPositionWindow(self._left, self._top) if self.args.fullscreen: self._open_fullscreen_window() self._fullscreen = True self.ReSizeGLScene(self.window_width, self.window_height) glutMainLoop() def _open_fullscreen_window(self): glutGameModeString("%dx%d:32@75" % (self.window_width, self.window_height)) glutEnterGameMode() glutSetCursor(GLUT_CURSOR_NONE) glutDisplayFunc(self.DrawGLScene) glutIdleFunc(self.DrawGLScene) glutReshapeFunc(self.ReSizeGLScene) glutKeyboardFunc(self.keyPressed) self.InitGL() glutPositionWindow(self._left, self._top) def handle_torrent_message(self, num_files, download_duration, total_size, num_chunks, num_segments, encoded_torrent_title): self.num_files = num_files self.download_duration = download_duration self.total_size = total_size self.num_segments = num_segments self.torrent_title = encoded_torrent_title.decode("unicode_escape") def handle_file_message(self, filenum, offset, length): f = self.files[filenum] = self.file_class(self, filenum, offset, length) self.logger.debug("added file %s" % f) self.torrent_length += length self.added_file(f) if len(self.files) == self.num_files: self.logger.debug("added all files") self.added_all_files() def handle_chunk_message(self, chunk_id, torrent_position, byte_size, filenum, peer_id, t): if filenum in self.files: f = self.files[filenum] peer = self.peers[peer_id] begin = torrent_position - f.offset end = begin + byte_size chunk = self.chunk_class( chunk_id, begin, end, byte_size, filenum, f, peer, t, self.current_time(), self) self.files[filenum].add_chunk(chunk) else: print "ignoring chunk from undeclared file %s" % filenum def handle_segment_message(self, segment_id, torrent_position, byte_size, filenum, peer_id, t, duration): if filenum in self.files: f = self.files[filenum] peer = self.peers[peer_id] begin = torrent_position - f.offset end = begin + byte_size segment = self.segment_class( segment_id, begin, end, byte_size, filenum, f, peer, t, duration, self.current_time(), self) self._segments_by_id[segment_id] = segment self.add_segment(segment) else: print "ignoring segment from undeclared file %s" % filenum def handle_peer_message(self, peer_id, addr, bearing, pan, location): peer = self.peer_class(self, addr, bearing, pan, location) self.peers[peer_id] = peer self.peers_by_addr[addr] = peer def add_segment(self, segment): f = self.files[segment.filenum] segment.f = f segment.pan = 0.5 f.add_segment(segment) self.pan_segment(segment) segment.peer.add_segment(segment) self.num_received_segments += 1 def added_file(self, f): pass def added_all_files(self): pass def pan_segment(self, segment): if not self._warned_about_missing_pan_segment: print "WARNING: pan_segment undefined in visualizer. Orchestra and synth now control panning." self._warned_about_missing_pan_segment = True def handle_shutdown(self): self.exiting = True def handle_reset(self): self.reset() def handle_amp_message(self, segment_id, amp): try: segment = self._segments_by_id[segment_id] except KeyError: print "WARNING: amp message for unknown segment ID %s" % segment_id return self.handle_segment_amplitude(segment, amp) def handle_segment_amplitude(self, segment, amp): pass def handle_waveform_message(self, segment_id, value): try: segment = self._segments_by_id[segment_id] except KeyError: print "WARNING: waveform message for unknown segment ID %s" % segment_id return self.handle_segment_waveform_value(segment, value * self.waveform_gain) def handle_segment_waveform_value(self, segment, value): pass def handle_synth_address(self, port): self._synth_instance = None self._synth_port = port self.synth_address_received() def handle_audio_captured_started(self, start_time): self._audio_capture_start_time = float(start_time) def synth_address_received(self): pass def setup_osc(self): self.orchestra = OrchestraController(self.orchestra_host, self.orchestra_port) self.server = simple_osc_receiver.OscReceiver( listen=self.args.listen, name="Visualizer") self.server.add_method("/torrent", "ifiiis", self._handle_osc_message, "handle_torrent_message") self.server.add_method("/file", "iii", self._handle_osc_message, "handle_file_message") self.server.add_method("/chunk", "iiiiif", self._handle_osc_message, "handle_chunk_message") self.server.add_method("/segment", "iiiiiff", self._handle_osc_message, "handle_segment_message") self.server.add_method("/peer", "isffs", self._handle_osc_message, "handle_peer_message") self.server.add_method("/reset", "", self._handle_osc_message, "handle_reset") self.server.add_method("/shutdown", "", self._handle_osc_message, "handle_shutdown") self.server.add_method("/synth_address", "i", self._handle_osc_message, "handle_synth_address") self.server.add_method("/audio_captured_started", "s", self._handle_osc_message, "handle_audio_captured_started") self.server.start() self.waveform_server = None def setup_waveform_server(self): if not self._standalone: import osc_receiver self.waveform_server = osc_receiver.OscReceiver(proto=osc.UDP) self.waveform_server.add_method("/amp", "if", self._handle_osc_message, "handle_amp_message") self.waveform_server.add_method("/waveform", "if", self._handle_osc_message, "handle_waveform_message") self.waveform_server.start() def _handle_osc_message(self, path, args, types, src, handler_name): if self.capture_message_log: received_time = time.time() self._call_handler(handler_name, args) if self.capture_message_log: if self._audio_capture_start_time is None: capture_time = 0.0 print "WARNING: received OSC before audio capture started: %s" % path else: capture_time = received_time - self._audio_capture_start_time self._message_log_writer.write( capture_time, handler_name, args) def _call_handler(self, handler_name, args): handler = getattr(self, handler_name) handler(*args) def InitGL(self): glClearColor(1.0, 1.0, 1.0, 0.0) glClearAccum(0.0, 0.0, 0.0, 0.0) glClearDepth(1.0) glShadeModel(GL_SMOOTH) glutMouseFunc(self._mouse_clicked) glutMotionFunc(self._mouse_moved) glutSpecialFunc(self._special_key_pressed) def ReSizeGLScene(self, window_width, window_height): self.window_width = window_width self.window_height = window_height if window_height == 0: window_height = 1 glViewport(0, 0, window_width, window_height) self.width = window_width - 2*self.margin self.height = window_height - 2*self.margin self._aspect_ratio = float(window_width) / window_height self.min_dimension = min(self.width, self.height) self._refresh_layers() if not self._3d_enabled: self.configure_2d_projection() self.resized_window() def resized_window(self): pass def configure_2d_projection(self): glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0.0, self.window_width, self.window_height, 0.0, -1.0, 1.0) glMatrixMode(GL_MODELVIEW) def _refresh_layers(self): for layer in self._layers: layer.refresh() def DrawGLScene(self): if self.exiting: self.logger.debug("total number of rendered frames: %s" % self._frame_count) if self.stopwatch.get_elapsed_time() > 0: self.logger.debug("total FPS: %s" % (float(self._frame_count) / self.stopwatch.get_elapsed_time())) if self.args.profile: import yappi yappi.print_stats(sys.stdout, yappi.SORTTYPE_TTOT) glutDestroyWindow(glutGetWindow()) return try: self._draw_gl_scene_error_handled() except Exception as error: traceback_printer.print_traceback() self.exiting = True raise error def _draw_gl_scene_error_handled(self): if self._camera_script: self._move_camera_by_script() glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() if self.export: self.current_export_time = float(self._frame_count) / self.export_fps self.now = self.current_time() is_waiting_for_synth = (self.sync and not self._synth() and not self._synced) is_waiting_for_audio_capture_to_start = ( self.capture_message_log and self._audio_capture_start_time is None) if self._frame_count == 0 and \ not is_waiting_for_synth and \ not is_waiting_for_audio_capture_to_start: self.stopwatch.start() if self.sync: self._synced = True else: if self._frame_count == 0: self.time_increment = 0 else: self.time_increment = self.now - self.previous_frame_time self.handle_incoming_messages() self.update() if not self.capture_message_log: glTranslatef(self.margin, self.margin, 0) if self.args.border: self.draw_border() if self._3d_enabled and not self._accum_enabled: self.set_perspective( 0, 0, -self._camera_position.x, -self._camera_position.y, self._camera_position.z) self.render() if self.show_fps and self._frame_count > 0: self.update_fps_history() self.show_fps_if_timely() if self.export: self.exporter.export_frame() glutSwapBuffers() self.previous_frame_time = self.now finished = self.finished() if (self.export or self.args.exit_when_finished) and finished: self.exiting = True if not self._standalone: if finished and not self._notified_finished: self.orchestra.notify_finished() self._notified_finished = True if not is_waiting_for_synth: self._frame_count += 1 def finished(self): return False def handle_incoming_messages(self): if self.args.standalone: if self.play_message_log: self._process_message_log_until(self.now) else: self.server.serve() if self.waveform_server: self.waveform_server.serve() def _process_message_log_until(self, t): messages = self._message_log_reader.read_until(t) for _t, handler_name, args in messages: self._call_handler(handler_name, args) def update_fps_history(self): fps = 1.0 / self.time_increment self.fps_history.append(fps) def show_fps_if_timely(self): if self.previous_shown_fps_time: if (self.now - self.previous_shown_fps_time) > 1.0: self.calculate_and_show_fps() else: self.calculate_and_show_fps() def calculate_and_show_fps(self): print sum(self.fps_history) / len(self.fps_history) self.previous_shown_fps_time = self.now def draw_border(self): x1 = y1 = -1 x2 = self.width y2 = self.height glDisable(GL_LINE_SMOOTH) glLineWidth(1) glColor3f(BORDER_OPACITY, BORDER_OPACITY, BORDER_OPACITY) glBegin(GL_LINE_LOOP) glVertex2i(x1, y2) glVertex2i(x2, y2) glVertex2i(x2, y1) glVertex2i(x1, y1) glEnd() def keyPressed(self, key, x, y): if key == ESCAPE: # stop_all disabled as it also deletes ~reverb # self._synth().stop_all() self.exiting = True elif key == 's': self._dump_screen() elif key == 'f': if self._fullscreen: glutSetCursor(GLUT_CURSOR_INHERIT) glutLeaveGameMode() glutSetWindow(self._non_fullscreen_window) self._fullscreen = False else: self._open_fullscreen_window() self._fullscreen = True def _dump_screen(self): self._screen_dumper.export_frame() def playing_segment(self, segment): if not self._standalone: self.orchestra.visualizing_segment(segment.id) segment.playing = True def current_time(self): if self.export: return self.current_export_time else: return self.stopwatch.get_elapsed_time() def set_color(self, color_vector, alpha=1.0): glColor4f(color_vector[0], color_vector[1], color_vector[2], alpha) def set_listener_position(self, x, y): self.orchestra.set_listener_position(x, y) def set_listener_orientation(self, orientation): self.orchestra.set_listener_orientation(-orientation) def place_segment(self, segment_id, x, y, duration): self.orchestra.place_segment(segment_id, -x, y, duration) def _mouse_clicked(self, button, state, x, y): if self._3d_enabled: if button == GLUT_LEFT_BUTTON: self._dragging_orientation = (state == GLUT_DOWN) else: self._dragging_orientation = False if button == GLUT_RIGHT_BUTTON: self._dragging_y_position = (state == GLUT_DOWN) if state == GLUT_DOWN: self._drag_x_previous = x self._drag_y_previous = y def _mouse_moved(self, x, y): if self._3d_enabled: if self._dragging_orientation: self._disable_camera_script() self._set_camera_orientation( self._camera_y_orientation + x - self._drag_x_previous, self._camera_x_orientation - y + self._drag_y_previous) self._print_camera_settings() elif self._dragging_y_position: self._disable_camera_script() self._camera_position.y += CAMERA_Y_SPEED * (y - self._drag_y_previous) self._print_camera_settings() self._drag_x_previous = x self._drag_y_previous = y def _disable_camera_script(self): self._camera_script = None def _special_key_pressed(self, key, x, y): if self._3d_enabled: r = math.radians(self._camera_y_orientation) new_position = self._camera_position if key == GLUT_KEY_LEFT: new_position.x += CAMERA_KEY_SPEED * math.cos(r) new_position.z += CAMERA_KEY_SPEED * math.sin(r) elif key == GLUT_KEY_RIGHT: new_position.x -= CAMERA_KEY_SPEED * math.cos(r) new_position.z -= CAMERA_KEY_SPEED * math.sin(r) elif key == GLUT_KEY_UP: new_position.x += CAMERA_KEY_SPEED * math.cos(r + math.pi/2) new_position.z += CAMERA_KEY_SPEED * math.sin(r + math.pi/2) elif key == GLUT_KEY_DOWN: new_position.x -= CAMERA_KEY_SPEED * math.cos(r + math.pi/2) new_position.z -= CAMERA_KEY_SPEED * math.sin(r + math.pi/2) self._set_camera_position(new_position) self._print_camera_settings() def _print_camera_settings(self): print print "%s, %s, %s" % ( self._camera_position.v, self._camera_y_orientation, self._camera_x_orientation) def _set_camera_position(self, position): self._camera_position = position if not self._standalone: self.set_listener_position(position.z, position.x) def _set_camera_orientation(self, y_orientation, x_orientation): self._camera_y_orientation = y_orientation self._camera_x_orientation = x_orientation if not self._standalone: self.set_listener_orientation(y_orientation) def set_perspective(self, pixdx, pixdy, eyedx, eyedy, eyedz): assert self._3d_enabled fov2 = ((self.fovy*math.pi) / 180.0) / 2.0 top = self.near * math.tan(fov2) bottom = -top right = top * self._aspect_ratio left = -right xwsize = right - left ywsize = top - bottom # dx = -(pixdx*xwsize/self.width + eyedx*self.near/focus) # dy = -(pixdy*ywsize/self.height + eyedy*self.near/focus) # I don't understand why this modification solved the problem (focus was 1.0) dx = -(pixdx*xwsize/self.width) dy = -(pixdy*ywsize/self.height) glMatrixMode(GL_PROJECTION) glLoadIdentity() glFrustum (left + dx, right + dx, bottom + dy, top + dy, self.near, self.far) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glRotatef(self._camera_x_orientation, 1.0, 0.0, 0.0) glRotatef(self._camera_y_orientation, 0.0, 1.0, 0.0) glTranslatef(self._camera_position.x, self._camera_position.y, self._camera_position.z) def enable_accum(self): self.gl_display_mode |= GLUT_ACCUM self._accum_enabled = True def accum(self, render_method): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT) for jitter in range(NUM_ACCUM_SAMPLES): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) self.set_perspective(ACCUM_JITTER[jitter][0], ACCUM_JITTER[jitter][1], -self._camera_position.x, -self._camera_position.y, self._camera_position.z) render_method() glAccum(GL_ACCUM, 1.0/NUM_ACCUM_SAMPLES) glAccum(GL_RETURN, 1.0) def subscribe_to_amp(self): if not self.waveform_server: self.setup_waveform_server() self._synth().subscribe_to_amp(self.waveform_server.port) def subscribe_to_waveform(self): if not self._standalone: if not self.waveform_server: self.setup_waveform_server() self._synth().subscribe_to_waveform(self.waveform_server.port) def _move_camera_by_script(self): position, orientation = self._camera_script.position_and_orientation( self.current_time()) self._set_camera_position(position) self._set_camera_orientation(orientation.y, orientation.x) def new_layer(self, rendering_function): layer = Layer(rendering_function, self.new_display_list_id()) self._layers.append(layer) return layer def new_display_list_id(self): return glGenLists(1) def _synth(self): if self._synth_instance is None and self._synth_port: from synth_controller import SynthController self._synth_instance = SynthController(self.logger) self._synth_instance.connect(self._synth_port) return self._synth_instance def draw_text(self, text, size, x, y, font=None, spacing=None, v_align="left", h_align="top"): if font is None: font = self.args.font self.text_renderer(text, size, font).render(x, y, v_align, h_align) def text_renderer(self, text, size, font=None): if font is None: font = self.args.font return self._text_renderer_class(self, text, size, font, aspect_ratio=self._target_aspect_ratio) def download_completed(self): if self.torrent_download_completion_time: return True else: if self.num_segments > 0 and self.num_received_segments == self.num_segments and not self.active(): self.torrent_download_completion_time = self.current_time() return True def active(self): return False def update(self): pass @staticmethod def add_parser_arguments(parser): parser.add_argument("-host", type=str, default="localhost") parser.add_argument('-port', type=int) parser.add_argument("-listen", type=str) parser.add_argument('-sync', action='store_true') try: parser.add_argument('-width', dest='width', type=int, default=1024) parser.add_argument('-height', dest='height', type=int, default=768) except argparse.ArgumentError: pass parser.add_argument("-left", type=int) parser.add_argument("-top", type=int) parser.add_argument('-margin', dest='margin', type=int, default=0) parser.add_argument('-show-fps', dest='show_fps', action='store_true') parser.add_argument('-capture-message-log', dest='capture_message_log') parser.add_argument('-play-message-log', dest='play_message_log') parser.add_argument('-export', dest='export', action='store_true') parser.add_argument('-export-fps', dest='export_fps', default=25.0, type=float) parser.add_argument('-export-dir') parser.add_argument("-waveform", dest="waveform", action='store_true') parser.add_argument("-waveform-gain", dest="waveform_gain", default=1, type=float) parser.add_argument("-camera-script", dest="camera_script", type=str) parser.add_argument("-border", action="store_true") parser.add_argument("-fullscreen", action="store_true") parser.add_argument("-standalone", action="store_true") parser.add_argument("-profile", action="store_true") parser.add_argument("-check-opengl-errors", action="store_true") parser.add_argument("-exit-when-finished", action="store_true") parser.add_argument("--text-renderer", choices=TEXT_RENDERERS.keys(), default="glut") parser.add_argument("--font", type=str) parser.add_argument("-aspect", type=str, default="1:1", help="Target aspect ratio (e.g. 16:9)") @staticmethod def add_margin_argument(parser, name): parser.add_argument(name, type=str, default="0,0,0,0", help="top,right,bottom,left in relative units") def parse_margin_argument(self, argument_string): return MarginAttributes.from_argument(argument_string, self)
class Orchestra: SAMPLE_RATE = 44100 BYTES_PER_SAMPLE = 2 # mpg123, used by predecode, outputs 16-bit PCM mono PLAYABLE_FORMATS = ['mp3', 'flac', 'wav', 'm4b'] JACK = "jack" SSR = "ssr" @staticmethod def add_parser_arguments(parser): parser.add_argument("--rt", action="store_true", dest="realtime") parser.add_argument("-t", "--torrent", dest="torrentname", default="") parser.add_argument("-z", "--timefactor", dest="timefactor", type=float, default=1) parser.add_argument("--start", dest="start_time", type=float, default=0) parser.add_argument("-q", "--quiet", action="store_true", dest="quiet") parser.add_argument("--pretend-sequential", action="store_true", dest="pretend_sequential") parser.add_argument("--gui", action="store_true", dest="gui_enabled") parser.add_argument("--fast-forward", action="store_true", dest="ff") parser.add_argument("--fast-forward-to-start", action="store_true", dest="ff_to_start") parser.add_argument("--quit-at-end", action="store_true", dest="quit_at_end") parser.add_argument("--loop", dest="loop", action="store_true") parser.add_argument("--max-pause-within-segment", type=float) parser.add_argument("--max-segment-duration", type=float) parser.add_argument("--looped-duration", dest="looped_duration", type=float) parser.add_argument("-o", "--output", dest="output", type=str, default=Orchestra.JACK) parser.add_argument("--include-non-playable", action="store_true") parser.add_argument("-f", "--file", dest="selected_files", type=int, nargs="+") parser.add_argument("--title", type=str, default="") parser.add_argument("--pretend-audio", dest="pretend_audio_filename") parser.add_argument("--capture-audio") parser.add_argument("--leading-pause", type=float, default=0) _extension_re = re.compile('\.(\w+)$') def __init__(self, server, sessiondir, tr_log, options): self.server = server self.options = options self.sessiondir = sessiondir self.tr_log = tr_log self.realtime = options.realtime self.timefactor = options.timefactor self.quiet = options.quiet self._loop = options.loop self.looped_duration = options.looped_duration self.output = options.output self.include_non_playable = options.include_non_playable self._leading_pause = options.leading_pause if server.options.locate_peers: self._peer_location = {} for peeraddr in tr_log.peers: self._peer_location[peeraddr] = server.ip_locator.locate(peeraddr) self._peers_center_location_x = self._get_peers_center_location_x() if options.pretend_audio_filename: self._pretended_file = self._fileinfo_for_pretended_audio_file() self._pretended_file["duration"] = self._get_file_duration(self._pretended_file) self._pretended_files = [self._pretended_file] self._files_to_play = self._pretended_files else: self._files_to_play = self.tr_log.files self.predecode = server.options.predecode if self.predecode: predecoder = Predecoder( tr_log.files, sample_rate=self.SAMPLE_RATE, location=tr_log.file_location) predecoder.decode(server.options.force_predecode) if options.pretend_audio_filename: predecoder = Predecoder( self._pretended_files, sample_rate=self.SAMPLE_RATE) predecoder.decode(server.options.force_predecode) if options.selected_files: tr_log.select_files(options.selected_files) self.playback_enabled = True self.fast_forwarding = False self.gui = None self._check_which_files_are_audio() self._player_class = WavPlayer self.players = [] self._player_for_peer = dict() self._prepare_playable_files() self.stopwatch = Stopwatch() self.playable_chunks = self._filter_playable_chunks(tr_log, tr_log.chunks) if self.include_non_playable: self.chunks = tr_log.chunks self._num_selected_files = len(self.tr_log.files) else: self.chunks = self.playable_chunks self._num_selected_files = self._num_playable_files logger.debug("total num chunks: %s" % len(tr_log.chunks)) logger.debug("num playable chunks: %s" % len(self.playable_chunks)) logger.debug("num selected chunks: %s" % len(self.chunks)) self.score = self._interpret_chunks_to_score(tr_log, self.playable_chunks, options) self.estimated_duration = self._estimated_playback_duration(self.score, options) print "playback duration: %s" % datetime.timedelta(seconds=self.estimated_duration) self._chunks_by_id = {} self.segments_by_id = {} self._playing = False self._quitting = False self._was_stopped = False self.space = Space() self._scheduler_queue = Queue.Queue() self.scheduler = sched.scheduler(time.time, time.sleep) self._run_scheduler_thread() if self.output == self.SSR: self.ssr = SsrControl() self._warned_about_max_sources = False def init_playback(self): if self.server.options.no_synth: self.synth = None else: from synth_controller import SynthController self.synth = SynthController(logger) self.synth.launch_engine(self.server.options.sc_mode) self.synth.connect(self.synth.lang_port) self.synth.subscribe_to_info() if self.options.capture_audio: self._load_sounds() self._start_capture_audio() self._tell_visualizers("/synth_address", self.synth.lang_port) if self.output == self.SSR: self.ssr.run() if not self.options.capture_audio: self._load_sounds() self._log_time_for_last_handled_event = 0 if self.options.ff_to_start: self._ff_to_time = self.options.start_time self.set_time_cursor(0) else: self._ff_to_time = None self.set_time_cursor(self.options.start_time) def _start_capture_audio(self): self._audio_capture_filename = self.options.capture_audio if os.path.exists(self._audio_capture_filename): os.remove(self._audio_capture_filename) self._audio_capture_process = subprocess.Popen( ["./jack_capture/jack_capture", "-f", self._audio_capture_filename, "-d", "-1", "-B", "65536", "SuperCollider:out_1", "SuperCollider:out_2"], shell=False, stdout=subprocess.PIPE) self._wait_until_audio_capture_started() def _wait_until_audio_capture_started(self): print "waiting for audio capture to start" while True: line = self._audio_capture_process.stdout.readline().rstrip("\r\n") m = re.match('^audio capture started at (.*)$', line) if m: audio_capture_start_time = float(m.group(1)) self._tell_visualizers("/audio_captured_started", str(audio_capture_start_time)) print "audio capture started" return @classmethod def _estimated_playback_duration(cls, score, options): last_segment = score[-1] return last_segment["onset"] / options.timefactor + last_segment["duration"] @classmethod def _interpret_chunks_to_score(cls, tr_log, chunks, options): score = Interpreter(options.max_pause_within_segment, options.max_segment_duration).interpret( chunks, tr_log.files) for segment in score: segment["duration"] /= options.timefactor return score @classmethod def _filter_playable_chunks(cls, tr_log, chunks): return filter(lambda chunk: (cls._chunk_is_playable(tr_log, chunk)), chunks) @classmethod def _chunk_is_playable(cls, tr_log, chunk): file_info = tr_log.files[chunk["filenum"]] return file_info["playable_file_index"] != -1 def _run_scheduler_thread(self): self._scheduler_thread = threading.Thread(target=self._process_scheduled_events) self._scheduler_thread.daemon = True self._scheduler_thread.start() def _process_scheduled_events(self): while not self._quitting: while True: try: delay, priority, action, arguments = self._scheduler_queue.get(True, 0.01) except Queue.Empty: break self.scheduler.enter(delay, priority, action, arguments) self.scheduler.run() def _handle_visualizing_message(self, path, args, types, src, data): segment_id = args[0] segment = self.segments_by_id[segment_id] logger.debug("visualizing segment %s" % segment) player = self.get_player_for_segment(segment) self._ask_synth_to_play_segment(segment, channel=0, pan=player.spatial_position.pan) def _ask_synth_to_play_segment(self, segment, channel, pan): if self.synth: logger.debug("asking synth to play %s" % segment) file_info = self.tr_log.files[segment["filenum"]] if self.output == self.SSR: segment["sound_source_id"] = self.ssr.allocate_source() if segment["sound_source_id"] and not self._warned_about_max_sources: channel = segment["sound_source_id"] - 1 pan = None else: print "WARNING: max sources exceeded, skipping segment playback (this warning will not be repeated)" self._warned_about_max_sources = True return self.synth.play_segment( segment["id"], segment["audio_filenum"], segment["relative_start_time_in_file"], segment["relative_end_time_in_file"], segment["duration"], self.looped_duration, channel, pan) self._scheduler_queue.put( (segment["playback_duration"], 1, self.stopped_playing, [segment])) def _check_which_files_are_audio(self): for file_info in self.tr_log.files: file_info["is_audio"] = self._has_audio_extension(file_info["name"]) @staticmethod def _has_audio_extension(filename): return Orchestra._extension(filename) in Orchestra.PLAYABLE_FORMATS @staticmethod def _extension(filename): m = Orchestra._extension_re.search(filename) if m: return m.group(1).lower() def _prepare_playable_files(self): if self.predecode: self._num_playable_files = self._get_wav_files_info( self.tr_log, self.include_non_playable) else: raise Exception("playing wav without precoding is not supported") def _load_sounds(self): if self.synth: print "loading sounds" for filenum in range(len(self._files_to_play)): file_info = self._files_to_play[filenum] if file_info["playable_file_index"] != -1: logger.info("load_sound(%s)" % file_info["decoded_name"]) result = self._load_sound_stubbornly(filenum, file_info["decoded_name"]) logger.info("load_sound result: %s" % result) print "OK" def _load_sound_stubbornly(self, filenum, filename): while True: result = self.synth.load_sound(filenum, filename) if result > 0: return result else: warn(logger, "synth returned %s - retrying soon" % result) time.sleep(1.0) @classmethod def _get_wav_files_info(cls, tr_log, include_non_playable=False): playable_file_index = 0 for filenum in range(len(tr_log.files)): file_info = tr_log.files[filenum] file_info["playable_file_index"] = -1 if "decoded_name" in file_info: file_info["duration"] = cls._get_file_duration(file_info) if file_info["duration"] > 0: file_info["playable_file_index"] = playable_file_index logger.debug("duration for %r: %r\n" % (file_info["name"], file_info["duration"])) playable_file_index += 1 if include_non_playable: file_info["index"] = filenum else: file_info["index"] = file_info["playable_file_index"] return playable_file_index @classmethod def _get_file_duration(cls, file_info): if "decoded_name" in file_info: statinfo = os.stat(file_info["decoded_name"]) wav_header_size = 44 return float((statinfo.st_size - wav_header_size) / cls.BYTES_PER_SAMPLE) / cls.SAMPLE_RATE def get_current_log_time(self): if self.fast_forwarding: return self._log_time_for_last_handled_event else: return self.log_time_played_from + self.stopwatch.get_elapsed_time() * self.timefactor def play_non_realtime(self, quit_on_end=False): logger.info("entering play_non_realtime") self._was_stopped = False self._num_finished_visualizers = 0 if self._loop: while True: self._play_until_end() if not self._was_stopped: self._wait_for_visualizers_to_finish() self.set_time_cursor(0) else: self._play_until_end() if not self._was_stopped: self._wait_for_visualizers_to_finish() if quit_on_end: self._quit() logger.info("leaving play_non_realtime") def _quit(self): if self.options.capture_audio: self._audio_capture_process.kill() self._quitting = True def _play_until_end(self): logger.info("entering _play_until_end") self._playing = True self.stopwatch.start() time.sleep(self._leading_pause) no_more_events = False while self._playing and not no_more_events: event = self._get_next_chunk_or_segment() if event: self._handle_event(event) else: no_more_events = True logger.info("leaving _play_until_end") def _get_next_chunk_or_segment(self): logger.debug("chunk index = %d, segment index = %d" % ( self.current_chunk_index, self.current_segment_index)) chunk = self._get_next_chunk() segment = self._get_next_segment() logger.debug("next chunk: %s" % chunk) logger.debug("next segment: %s" % segment) if chunk and segment: return self._choose_nearest_chunk_or_segment(chunk, segment) elif chunk: return {"type": "chunk", "chunk": chunk} elif segment: return {"type": "segment", "segment": segment} else: return None def _get_next_chunk(self): if self.current_chunk_index < len(self.chunks): return self.chunks[self.current_chunk_index] def _get_next_segment(self): if len(self.score) == 0: return None elif self.current_segment_index < len(self.score): return self.score[self.current_segment_index] def _handle_event(self, event): if event["type"] == "chunk": self.handle_chunk(event["chunk"]) self.current_chunk_index += 1 elif event["type"] == "segment": self.handle_segment(event["segment"]) self.current_segment_index += 1 else: raise Exception("unexpected event %s" % event) def _choose_nearest_chunk_or_segment(self, chunk, segment): if chunk["t"] < segment["onset"]: return {"type": "chunk", "chunk": chunk} else: return {"type": "segment", "segment": segment} def stop(self): # stop_all disabled as it also deletes ~reverb # if self.synth: # self.synth.stop_all() self._was_stopped = True self._playing = False self.log_time_played_from = self.get_current_log_time() self.stopwatch.stop() def handle_segment(self, segment): logger.debug("handling segment %s" % segment) player = self.get_player_for_segment(segment) if not player: logger.debug("get_player_for_segment returned None - skipping playback") if self.fast_forwarding: self._stop_ff_if_necessary() else: now = self.get_current_log_time() time_margin = segment["onset"] - now logger.debug("time_margin=%f-%f=%f" % (segment["onset"], now, time_margin)) if not self.realtime and time_margin > 0: sleep_time = time_margin logger.debug("sleeping %f" % sleep_time) time.sleep(sleep_time) if player: logger.debug("player.enabled=%s" % player.enabled) if player and player.enabled: player.play(segment, pan=0.5) self._log_time_for_last_handled_event = segment["onset"] def handle_chunk(self, chunk): logger.debug("handling chunk %s" % chunk) player = self.get_player_for_chunk(chunk) logger.debug("get_player_for_chunk returned %s" % player) if self.fast_forwarding: self._stop_ff_if_necessary() else: now = self.get_current_log_time() time_margin = chunk["t"] - now logger.debug("time_margin=%f-%f=%f" % (chunk["t"], now, time_margin)) if not self.realtime and time_margin > 0: sleep_time = time_margin logger.debug("sleeping %f" % sleep_time) time.sleep(sleep_time) if player: logger.debug("player.enabled=%s" % player.enabled) if player and player.enabled: player.visualize(chunk) self._log_time_for_last_handled_event = chunk["t"] def _stop_ff_if_necessary(self): if self._ff_to_time is not None and \ self._log_time_for_last_handled_event >= self._ff_to_time: self._ff_to_time = None self.fast_forwarding = False self.set_time_cursor(self.log_time_played_from) def highlight_segment(self, segment): if self.gui: self.gui.highlight_segment(segment) def visualize_chunk(self, chunk, player): if len(self.visualizers) > 0: self._inform_visualizers_about_peer(player) file_info = self.tr_log.files[chunk["filenum"]] self._chunks_by_id[chunk["id"]] = chunk self._tell_visualizers( "/chunk", chunk["id"], chunk["begin"], chunk["end"] - chunk["begin"], file_info["index"], player.id, chunk["t"]) def visualize_segment(self, segment, player): if len(self.visualizers) > 0: self._inform_visualizers_about_peer(player) file_info = self.tr_log.files[segment["filenum"]] self.segments_by_id[segment["id"]] = segment self._tell_visualizers( "/segment", segment["id"], segment["begin"], segment["end"] - segment["begin"], file_info["index"], player.id, segment["t"], segment["playback_duration"]) else: self._ask_synth_to_play_segment(segment, channel=0, pan=0.5) def stopped_playing(self, segment): logger.debug("stopped segment %s" % segment) if self.gui: self.gui.unhighlight_segment(segment) if self.output == self.SSR and segment["sound_source_id"]: self.ssr.free_source(segment["sound_source_id"]) def play_segment(self, segment, player): self.segments_by_id[segment["id"]] = segment if self.looped_duration: segment["playback_duration"] = self.looped_duration else: segment["playback_duration"] = segment["duration"] self.visualize_segment(segment, player) def _send_torrent_info_to_uninformed_visualizers(self): for visualizer in self.visualizers: if not visualizer.informed_about_torrent: self._send_torrent_info_to_visualizer(visualizer) def _inform_visualizers_about_peer(self, player): for visualizer in self.visualizers: if player.id not in visualizer.informed_about_peer: if visualizer.send( "/peer", player.id, player.addr, player.spatial_position.bearing, player.spatial_position.pan, player.location_str): visualizer.informed_about_peer[player.id] = True def _send_torrent_info_to_visualizer(self, visualizer): if not visualizer.send( "/torrent", self._num_selected_files, self.tr_log.lastchunktime(), self.tr_log.total_file_size(), len(self.chunks), len(self.score), self.options.title): return for filenum in range(len(self.tr_log.files)): file_info = self.tr_log.files[filenum] if self.include_non_playable or file_info["playable_file_index"] != -1: if not visualizer.send( "/file", file_info["index"], file_info["offset"], file_info["length"]): return visualizer.informed_about_torrent = True def get_player_for_chunk(self, chunk): try: return chunk["player"] except KeyError: peer_player = self.get_player_for_peer(chunk["peeraddr"]) chunk["player"] = peer_player return peer_player def get_player_for_segment(self, segment): try: return segment["player"] except KeyError: peer_player = self.get_player_for_peer(segment["peeraddr"]) segment["player"] = peer_player return peer_player def get_player_for_peer(self, peeraddr): peer_player = None try: peer_player = self._player_for_peer[peeraddr] except KeyError: peer_player = self._create_player(peeraddr) self.players.append(peer_player) self._player_for_peer[peeraddr] = peer_player return peer_player def _create_player(self, addr): count = len(self.players) logger.debug("creating player number %d" % count) player = self._player_class(self, count) player.addr = addr if self.server.options.locate_peers and self._peer_location[addr] is not None: x, y, place_name = self._peer_location[addr] if place_name: place_name = place_name.encode("unicode_escape") else: place_name = "" player.location_str = "%s,%s,%s" % (x, y, place_name) if x < self._peers_center_location_x: player.spatial_position.pan = -1.0 else: player.spatial_position.pan = 1.0 else: player.location_str = "" return player def set_time_cursor(self, log_time): assert not self.realtime logger.debug("setting time cursor at %f" % log_time) self.log_time_played_from = log_time if self._playing: self.stopwatch.restart() self.current_chunk_index = self._get_current_chunk_index() self.current_segment_index = self._get_current_segment_index() def _get_current_chunk_index(self): index = 0 next_to_last_index = len(self.chunks) - 2 while index < next_to_last_index: if self.chunks[index+1]["t"] >= self.log_time_played_from: return index index += 1 return len(self.chunks) - 1 def _get_current_segment_index(self): index = 0 next_to_last_index = len(self.score) - 2 while index < next_to_last_index: if self.score[index+1]["onset"] >= self.log_time_played_from: return index index += 1 return len(self.score) - 1 def _handle_set_listener_position(self, path, args, types, src, data): if self.output == self.SSR: x, y = args self.ssr.set_listener_position(x, y) def _handle_set_listener_orientation(self, path, args, types, src, data): if self.output == self.SSR: orientation = args[0] self.ssr.set_listener_orientation(orientation) def _handle_place_segment(self, path, args, types, src, data): segment_id, x, y, duration = args if self.output == self.SSR: segment = self.segments_by_id[segment_id] sound_source_id = segment["sound_source_id"] if sound_source_id is not None: self.ssr.place_source(sound_source_id, x, y, duration) else: pan = self._spatial_position_to_stereo_pan(x, y) if self.synth: self.synth.pan(segment_id, pan) def _handle_enable_smooth_movement(self, path, args, types, src, data): pass # OBSOLETE after smooth movement made default def _handle_start_segment_movement_from_peer(self, path, args, types, src, data): segment_id, duration = args if self.output == self.SSR: segment = self.segments_by_id[segment_id] sound_source_id = segment["sound_source_id"] if sound_source_id is not None: player = self.get_player_for_segment(segment) self.ssr.start_source_movement( sound_source_id, player.trajectory, duration) def _spatial_position_to_stereo_pan(self, x, y): # compare rectangular_visualizer.Visualizer.pan_segment # NOTE: assumes default listener position and orientation! return float(x) / 5 + 0.5 def reset(self): if self.synth: self.synth.stop_engine() self._tell_visualizers("/reset") for visualizer in self.visualizers: visualizer.informed_about_torrent = False visualizer.informed_about_peer = {} def _tell_visualizers(self, *args): self._send_torrent_info_to_uninformed_visualizers() self.server._tell_visualizers(*args) def _fileinfo_for_pretended_audio_file(self): return {"offset": 0, "length": os.stat(self.options.pretend_audio_filename).st_size, "name": self.options.pretend_audio_filename, "playable_file_index": 0} def _handle_finished(self, path, args, types, src, data): self._num_finished_visualizers += 1 def _wait_for_visualizers_to_finish(self): while self._num_finished_visualizers < len(self.visualizers): time.sleep(0.1) def _get_peers_center_location_x(self): if len(self._peer_location) <= 1: return 0 else: sorted_xs = sorted([x for x,y,location_str in self._peer_location.values()]) center_index = int((len(self._peer_location)-1) / 2) return float(sorted_xs[center_index] + sorted_xs[center_index+1]) / 2