def test_fit_hole(self): gatherer = Gatherer() gatherer.add(Piece(10, 20)) gatherer.add(Piece(40, 50)) gatherer.add(Piece(20, 40)) expected_pieces = [Piece(10, 50)] self.assertEquals(expected_pieces, gatherer.pieces())
def test_add_second_piece(self): gatherer = Gatherer() gatherer.add(Piece(10, 20)) gatherer.add(Piece(40, 50)) expected_pieces = [Piece(10, 20), Piece(40, 50)] self.assertEquals(expected_pieces, gatherer.pieces())
def test_overlap_multiple_pieces(self): gatherer = Gatherer() gatherer.add(Piece(10, 20)) gatherer.add(Piece(30, 40)) gatherer.add(Piece(0, 50)) expected_pieces = [Piece(0, 50)] self.assertEquals(expected_pieces, gatherer.pieces())
class File: def __init__(self, length, visualizer): self.visualizer = visualizer self.arriving_chunks = OrderedDict() self.gatherer = Gatherer() self.radius = 50.0 self.x = random.uniform(self.radius, visualizer.width - self.radius*2) self.y = random.uniform(self.radius, visualizer.height - self.radius*2) def add_chunk(self, chunk): chunk.boid = Boid(self.get_departure_position(chunk), 10.0, 3.0) chunk.arrival_position = self.get_arrival_position(chunk) chunk.boid.arrive(chunk.arrival_position) chunk.arrived = False self.arriving_chunks[chunk.id] = chunk def stopped_playing(self, chunk_id): chunk = self.arriving_chunks[chunk_id] del self.arriving_chunks[chunk_id] self.gatherer.add(chunk) def get_departure_position(self, chunk): if chunk.pan < 0.5: x = 0 else: x = self.visualizer.width y = chunk.height * self.visualizer.height return Vector2d(x, y) def get_arrival_position(self, chunk): angle = 2 * math.pi * chunk.begin / chunk.file_length x = self.x + self.radius * math.cos(angle) y = self.y + self.radius * math.sin(angle) return Vector2d(x, y)
class GathererTest(unittest.TestCase): def test_expected_order(self): for chunk in self.tr_log.chunks: self._gather(chunk) self.assertEquals(1, len(self.gatherer.pieces())) def test_random_order(self): while len(self.tr_log.chunks) > 0: index = random.randint(0, len(self.tr_log.chunks)-1) chunk = self.tr_log.chunks[index] del self.tr_log.chunks[index] self._gather(chunk) self.assertEquals(1, len(self.gatherer.pieces())) def setUp(self): self.tr_log = TrLogReader(SESSION_FILENAME).get_log() #self.segments = Interpreter().interpret(self.tr_log.chunks, self.tr_log.files) self.gatherer = Gatherer() self.files = [File(f["offset"], f["length"]) for f in self.tr_log.files] def _gather(self, piece_info): f = self.files[piece_info["filenum"]] begin = piece_info["begin"] - f.offset end = piece_info["end"] - f.offset self.gatherer.add(Piece(begin, end, f))
def test_prepend(self): gatherer = Gatherer() gatherer.add(Piece(10, 20)) gatherer.add(Piece(40, 50)) gatherer.add(Piece(30, 40)) expected_pieces = [Piece(10, 20), Piece(30, 50)] self.assertEquals(expected_pieces, gatherer.pieces())
def test_multiple_files_append(self): f1 = File(offset=0, length=30) f2 = File(offset=30, length=20) gatherer = Gatherer() gatherer.add(Piece(20, 30, f1)) gatherer.add(Piece(0, 10, f2)) expected_pieces = [Piece(20, 40, f1)] self.assertEquals(expected_pieces, gatherer.pieces())
class File: def __init__(self, length, visualizer): self.visualizer = visualizer self.arriving_chunks = OrderedDict() self.gatherer = Gatherer() def add_chunk(self, chunk): self.arriving_chunks[chunk.id] = chunk chunk.setup() def update(self): self.update_arriving_chunks() def update_arriving_chunks(self): for chunk in self.arriving_chunks.values(): chunk.update() if chunk.arrived(): self.gather_chunk(chunk) def gather_chunk(self, chunk): del self.arriving_chunks[chunk.id] chunk.has_arrived = True self.gatherer.add(chunk)
def test_overlap_begin(self): gatherer = Gatherer() gatherer.add(Piece(10, 20)) gatherer.add(Piece(0, 15)) expected_pieces = [Piece(0, 20)] self.assertEquals(expected_pieces, gatherer.pieces())
class Waves(visualizer.Visualizer): def __init__(self, args): self._gathered_segments_layer = None visualizer.Visualizer.__init__(self, args, file_class=File, peer_class=Peer, segment_class=Segment) if self.args.peer_info: self.layout_managers = {"left": LayoutManager1d(), "right": LayoutManager1d()} self.waves_margin = self.parse_margin_argument(self.args.waves_margin) @staticmethod def add_parser_arguments(parser): visualizer.Visualizer.add_parser_arguments(parser) parser.add_argument("--peer-info", action="store_true") parser.add_argument("--enable-title", action="store_true") parser.add_argument("--title-size", type=float, default=30.0) parser.add_argument("--test-title", type=str) visualizer.Visualizer.add_margin_argument(parser, "--waves-margin") def resized_window(self): self.waves_margin.update() self._waves_width = self.width - self.waves_margin.left - self.waves_margin.right self._waves_right = self.width - self.waves_margin.right self._waves_height = self.height - self.waves_margin.top - self.waves_margin.bottom self._waves_top = self.height - self.waves_margin.top self._title_renderer = None def configure_2d_projection(self): glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0.0, self.window_width, 0.0, self.window_height, -1.0, 1.0) glMatrixMode(GL_MODELVIEW) def synth_address_received(self): self.subscribe_to_waveform() def reset(self): visualizer.Visualizer.reset(self) self.playing_segments = collections.OrderedDict() self.gatherer = Gatherer() if self._gathered_segments_layer: self._gathered_segments_layer.refresh() self._first_segment_received = False self._title_renderer = None def pan_segment(self, segment): # let orchestra & synth spatialize pass def InitGL(self): visualizer.Visualizer.InitGL(self) glClearColor(0.0, 0.0, 0.0, 0.0) self._gathered_segments_layer = self.new_layer(self._render_gathered_segments) def update(self): outdated = [] for segment in self.playing_segments.values(): if not segment.is_playing(): self.gatherer.add(segment) outdated.append(segment.id) if len(outdated) > 0: for segment_id in outdated: self.playing_segments[segment_id].free() del self.playing_segments[segment_id] self._gathered_segments_layer.refresh() def render(self): self._set_gathered_color() self._gathered_segments_layer.draw() self.draw_playing_segments() if self.args.enable_title: if not self._title_renderer and (self._first_segment_received or self.args.test_title): self._create_title_renderer() if self._title_renderer: self._render_title() def _create_title_renderer(self): if self.args.test_title: title = self.args.test_title else: title = self.torrent_title size = self.args.title_size / 1024 * self.width self._title_renderer = TitleRenderer(title, size, self) def _render_title(self): if self.download_completed(): color = self._fade_out_color() else: color = Vector3d(1,1,1) glColor3f(*color) x = self.waves_margin.left + 10.0 / 640 * self.width y = self.height * 0.03 self._title_renderer.render(x, y) def _set_gathered_color(self): if self.download_completed(): self.gathered_color = self._fade_out_color() self._gathered_segments_layer.refresh() elif self.torrent_length > 0: torrent_progress = float(self.gatherer.gathered_bytes()) / self.torrent_length self.gathered_color = GATHERED_COLOR + (WAVEFORM_COLOR - GATHERED_COLOR) * pow(torrent_progress, 20) else: self.gathered_color = GATHERED_COLOR def _fade_out_color(self): time_after_completion = max(self.now - self.torrent_download_completion_time, 0) if time_after_completion > FADE_OUT_DURATION: return Vector3d(0,0,0) else: return WAVEFORM_COLOR * pow(1 - time_after_completion/FADE_OUT_DURATION, 0.15) def active(self): return len(self.playing_segments) > 0 def finished(self): if self.download_completed(): time_after_completion = max(self.now - self.torrent_download_completion_time, 0) if time_after_completion > FADE_OUT_DURATION: return True def draw_playing_segments(self): glEnable(GL_LINE_SMOOTH) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) for segment in self.playing_segments.values(): segment.render() def _render_gathered_segments(self): glBegin(GL_QUADS) x1 = self.waves_margin.left x2 = self._waves_right min_height = GATHERED_LINE_WIDTH * self._waves_height for segment in self.gatherer.pieces(): y1 = self.byte_to_py(segment.torrent_begin) y2 = max(self.byte_to_py(segment.torrent_end), y1 + min_height) if (y2 - y1) > min_height: d = min((y2 - y1) * 0.2, MAX_GRADIENT_HEIGHT * self._waves_height) y1d = y1 + d y2d = y2 - d glColor3f(0, 0, 0) glVertex2f(x1, y1) glColor3f(*self.gathered_color) glVertex2f(x1, y1d) glVertex2f(x2, y1d) glColor3f(0, 0, 0) glVertex2f(x2, y1) glColor3f(0, 0, 0) glVertex2f(x1, y2) glColor3f(*self.gathered_color) glVertex2f(x1, y2d) glVertex2f(x2, y2d) glColor3f(0, 0, 0) glVertex2f(x2, y2) glColor3f(*self.gathered_color) glVertex2f(x1, y1d) glVertex2f(x1, y2d) glVertex2f(x2, y2d) glVertex2f(x2, y1d) else: glColor3f(0, 0, 0) glVertex2f(x1, y1) glColor3f(*self.gathered_color) glVertex2f(x1, y2) glVertex2f(x2, y2) glColor3f(0, 0, 0) glVertex2f(x2, y1) glEnd() def byte_to_py(self, byte): return int(self.waves_margin.bottom + self.byte_to_relative_y(byte) * self._waves_height) def byte_to_relative_y(self, byte): return float(byte) / self.torrent_length def handle_segment_waveform_value(self, segment, value): segment.append_to_waveform(value)
class Waves(visualizer.Visualizer): def __init__(self, args): visualizer.Visualizer.__init__(self, args, file_class=File, peer_class=Peer, segment_class=Segment) self.subscribe_to_waveform() self.playing_segments = collections.OrderedDict() self.gatherer = Gatherer() def gather(self, segment): self.gatherer.add(segment) def render(self): #self.draw_outline() self.draw_gathered_segments() for peer in self.peers.values(): peer.update() peer.draw() def draw_outline(self): glDisable(GL_LINE_SMOOTH) glLineWidth(1.0) glColor3f(*OUTLINE_COLOR) glBegin(GL_LINE_STRIP) glVertex2f(self.x1, self.y1) glVertex2f(self.x1, self.y2) glVertex2f(self.x2, self.y2) glVertex2f(self.x2, self.y1) glVertex2f(self.x1, self.y1) glEnd() def draw_gathered_segments(self): glColor3f(*GATHERED_COLOR) glBegin(GL_QUADS) for segment in self.gatherer.pieces(): y1 = self.byte_to_py(segment.torrent_begin) y2 = self.byte_to_py(segment.torrent_end) if y2 == y1: y2 += 1 glVertex2f(self.x1, y1) glVertex2f(self.x1, y2) glVertex2f(self.x2, y2) glVertex2f(self.x2, y1) glEnd() def byte_to_py(self, byte): return self.y1 + int(self.byte_to_relative_y(byte) * (self.y2 - self.y1)) def byte_to_relative_y(self, byte): return float(byte) / self.torrent_length def handle_segment_waveform_value(self, segment, value): segment.add_to_waveform(value) def ReSizeGLScene(self, *args): visualizer.Visualizer.ReSizeGLScene(self, *args) margin_x = FILE_MARGIN_X * self.width margin_y = FILE_MARGIN_Y * self.height self.x1 = margin_x self.y1 = margin_y self.x2 = self.width - margin_x self.y2 = self.height - margin_y self.waveform_length_to_file = margin_x self.waveform_length_in_file = self.x2 - self.x1
class File(visualizer.File): def __init__(self, *args): visualizer.File.__init__(self, *args) self.playing_segments = OrderedDict() self.gatherer = Gatherer() def add_segment(self, segment): segment.pan = self.byte_to_relative_x((segment.begin + segment.end) / 2) self.visualizer.playing_segment(segment) self.playing_segments[segment.id] = segment def update(self): outdated = filter(lambda segment_id: self.playing_segments[segment_id].relative_age() > 1, self.playing_segments) for segment_id in outdated: self.gatherer.add(self.playing_segments[segment_id]) del self.playing_segments[segment_id] def render(self): self.y = float(self.visualizer.height) / (self.visualizer.num_files + 1) * ( self.filenum + 1) self.y1 = int(self.y - GATHERED_HEIGHT/2) self.y2 = int(self.y + GATHERED_HEIGHT/2) self.draw_background() self.draw_gathered_segments() self.draw_playing_segments() def draw_background(self): x1 = self.byte_to_px(0) x2 = self.byte_to_px(self.length) self.visualizer.set_color(BACKGROUND_COLOR) glBegin(GL_QUADS) glVertex2i(x1, self.y2) glVertex2i(x2, self.y2) glVertex2i(x2, self.y1) glVertex2i(x1, self.y1) glEnd() def draw_gathered_segments(self): for segment in self.gatherer.pieces(): self.draw_gathered_segment(segment) def draw_playing_segments(self): for segment in self.playing_segments.values(): self.draw_playing_segment(segment) def draw_gathered_segment(self, segment): self.visualizer.set_color(GATHERED_COLOR) x1, x2 = self.segment_position(segment) glBegin(GL_QUADS) glVertex2i(x1, self.y2) glVertex2i(x2, self.y2) glVertex2i(x2, self.y1) glVertex2i(x1, self.y1) glEnd() def draw_playing_segment(self, segment): trace_age = 0.1 previous_byte_cursor = segment.begin + max(segment.age()-trace_age, 0) / \ segment.duration * segment.byte_size height = PLAYING_HEIGHT y1 = int(self.y - height/2) y2 = int(self.y + height/2) x0 = self.byte_to_px(segment.begin) x1 = self.byte_to_px(previous_byte_cursor) x3 = self.byte_to_px(segment.playback_byte_cursor()) x3 = max(x3, x1 + 1) x2 = (x1 + x3) / 2 self.visualizer.set_color(GATHERED_COLOR) glBegin(GL_QUADS) glVertex2i(x0, y1) glVertex2i(x0, y2) glVertex2i(x1, y2) glVertex2i(x1, y1) glEnd() glBegin(GL_QUADS) self.visualizer.set_color(GATHERED_COLOR) glVertex2i(x1, y2) glVertex2i(x1, y1) self.visualizer.set_color(PLAYING_COLOR) glVertex2i(x2, y1) glVertex2i(x2, y2) glEnd() glBegin(GL_QUADS) self.visualizer.set_color(PLAYING_COLOR) glVertex2i(x2, y1) glVertex2i(x2, y2) self.visualizer.set_color(GATHERED_COLOR) glVertex2i(x3, y2) glVertex2i(x3, y1) glEnd() def segment_position(self, segment): x1 = self.byte_to_px(segment.begin) x2 = self.byte_to_px(segment.end) x2 = max(x2, x1 + 1) return x1, x2 def byte_to_px(self, byte): return MARGIN + int(self.byte_to_relative_x(byte) * (self.visualizer.width - 2*MARGIN)) def byte_to_relative_x(self, byte): return float((self.visualizer.max_file_length - self.length) / 2 + byte) / \ self.visualizer.max_file_length
class Stairs(visualizer.Visualizer): def __init__(self, args): visualizer.Visualizer.__init__(self, args, file_class=File, peer_class=Peer, segment_class=Segment) self.inner_x = WALL_X self.outer_x = WALL_X + STAIRS_WIDTH self.wall_rear_x = WALL_X - WALL_WIDTH self.wall_bottom = self.step_y(NUM_STEPS) - STEP_HEIGHT self.stairs_depth = self.step_z(NUM_STEPS) self.files = {} self.segments = {} self.gatherer = Gatherer() self._segments_split_at_step_boundaries = None self._dragging_orientation = False self._dragging_y_position = False self._set_camera_position(CAMERA_POSITION) self._set_camera_orientation(CAMERA_Y_ORIENTATION, CAMERA_X_ORIENTATION) self.enable_accum() self.enable_3d() if self.args.waveform: self.gathered_color_v = CURSOR_COLOR_V * GATHERED_OPACITY + STEPS_COLOR_V * (1 - GATHERED_OPACITY) self.gathered_color_h = CURSOR_COLOR_H * GATHERED_OPACITY + STEPS_COLOR_H * (1 - GATHERED_OPACITY) self.subscribe_to_waveform() else: self.gathered_color_v = GATHERED_COLOR_V self.gathered_color_h = GATHERED_COLOR_H self.subscribe_to_amp() def gather(self, segment): self.gatherer.add(segment) self._segments_split_at_step_boundaries = None def added_all_files(self): self._create_steps() def _create_steps(self): self._steps = [] remaining_bytes = self.torrent_length remaining_num_steps = NUM_STEPS byte_offset = 0 for n in range(NUM_STEPS): byte_size = int(remaining_bytes / remaining_num_steps) self._steps.append(Step(self, n, byte_offset, byte_size)) byte_offset += byte_size remaining_bytes -= byte_size remaining_num_steps -= 1 def pan_segment(self, segment): x = WALL_X + STAIRS_WIDTH/2 self.place_segment(segment.id, segment.step.z, x, segment.duration) def render(self): glEnable(GL_DEPTH_TEST) glEnable(GL_POLYGON_OFFSET_FILL) for peer in self.peers.values(): peer.update() self.accum(self.render_accum_objects) if len(self.files) > 0: self.draw_branches() glDisable(GL_DEPTH_TEST) self.draw_step_edges() self.draw_wall_edge() def render_accum_objects(self): glPolygonOffset(1.0, 0.0) self.draw_step_surfaces() self.draw_wall_surfaces() if len(self.files) > 0: glPolygonOffset(0.0, 0.0) self.draw_gathered_segments() def draw_branches(self): for peer in self.peers.values(): peer.draw() def draw_step_surfaces(self): self.draw_step_horizontal_surfaces() self.draw_step_vertical_surfaces() def draw_step_horizontal_surfaces(self): glColor3f(*STEPS_COLOR_H) glBegin(GL_QUADS) for n in range(0, NUM_STEPS): surface = self.step_h_surface(n) for vertex in surface: glVertex3f(*vertex) glEnd() def draw_step_vertical_surfaces(self): glColor3f(*STEPS_COLOR_V) glBegin(GL_QUADS) for n in range(1, NUM_STEPS+1): surface = self.step_v_surface(n) for vertex in surface: glVertex3f(*vertex) glEnd() def draw_step_edges(self): glLineWidth(1.0) glColor3f(*STEPS_COLOR_V) for n in range(1, NUM_STEPS+1): y = self.step_y(n) z = self.step_z(n) glBegin(GL_LINES) glVertex3f(self.inner_x, y, z) glVertex3f(self.outer_x, y, z) glEnd() def draw_wall_edge(self): glLineWidth(1.0) glColor3f(*WALL_SHADE_COLOR_H) glBegin(GL_LINES) glVertex3f(self.inner_x, WALL_TOP, self.stairs_depth) glVertex3f(self.inner_x, self.wall_bottom, self.stairs_depth) glEnd() def draw_wall_surfaces(self): glColor3f(*WALL_SHADE_COLOR_H) glBegin(GL_QUADS) glVertex3f(self.inner_x, WALL_TOP, 0) glVertex3f(self.inner_x, WALL_TOP, self.stairs_depth) glVertex3f(self.wall_rear_x, WALL_TOP, self.stairs_depth) glVertex3f(self.wall_rear_x, WALL_TOP, 0) glEnd() glColor3f(*WALL_SHADE_COLOR_V) glBegin(GL_QUADS) glVertex3f(self.inner_x, WALL_TOP, self.stairs_depth) glVertex3f(self.wall_rear_x, WALL_TOP, self.stairs_depth) glVertex3f(self.wall_rear_x, self.wall_bottom, self.stairs_depth) glVertex3f(self.inner_x, self.wall_bottom, self.stairs_depth) glEnd() glColor3f(*WALL_COLOR) glBegin(GL_QUADS) glVertex3f(self.inner_x, WALL_TOP, 0) glVertex3f(self.inner_x, WALL_TOP, self.stairs_depth) glVertex3f(self.inner_x, self.wall_bottom, self.stairs_depth) glVertex3f(self.inner_x, self.wall_bottom, 0) glEnd() for n in range(1, NUM_STEPS+1): glBegin(GL_POLYGON) z = self.step_z(n) y1 = self.step_y(n) y2 = self.step_y(n+1) glVertex3f(self.outer_x, y1, z) glVertex3f(self.outer_x, y2, z) glVertex3f(self.outer_x, y2, 0) glVertex3f(self.outer_x, y1, 0) glEnd() def step_h_surface(self, n): y1 = self.step_y(n) y2 = self.step_y(n+1) z1 = self.step_z(n) z2 = self.step_z(n+1) return [ Vector3d(self.inner_x, y2, z1), Vector3d(self.inner_x, y2, z2), Vector3d(self.outer_x, y2, z2), Vector3d(self.outer_x, y2, z1) ] def step_v_surface(self, n): y1 = self.step_y(n) y2 = self.step_y(n+1) z1 = self.step_z(n) z2 = self.step_z(n+1) return [ Vector3d(self.inner_x, y1, z1), Vector3d(self.inner_x, y2, z1), Vector3d(self.outer_x, y2, z1), Vector3d(self.outer_x, y1, z1) ] def step_y(self, step): return -step * STEP_HEIGHT def step_z(self, step): return step * STEP_DEPTH def draw_gathered_segments(self): if self._segments_split_at_step_boundaries is None: self._segments_split_at_step_boundaries = self._split_segments_at_step_boundaries( self.gatherer.pieces()) for segment in self._segments_split_at_step_boundaries: segment.draw_gathered() def _split_segments_at_step_boundaries(self, pieces): result = [] for piece in pieces: segments = self._split_at_step_boundaries(piece) result.extend(segments) return result def _split_at_step_boundaries(self, segment): result = [] for step in self._steps: if self._segment_matches_step(segment, step): new_segment = copy.copy(segment) new_segment.torrent_begin = max(segment.torrent_begin, step.byte_offset) new_segment.torrent_end = min(segment.torrent_end, step.byte_offset + step.byte_size) new_segment.step = step result.append(new_segment) return result def _segment_matches_step(self, segment, step): return (step.byte_offset <= segment.torrent_begin < (step.byte_offset + step.byte_size) or step.byte_offset < segment.torrent_end <= (step.byte_offset + step.byte_size)) def _byte_to_step(self, byte): for step in self._steps: if step.byte_offset <= byte and byte < step.byte_end: return step raise Exception("failed to get step for byte %s with steps %s" % (byte, self._steps)) def handle_segment_waveform_value(self, segment, value): segment.waveform.appendleft(value) def handle_segment_amplitude(self, segment, amp): segment.amp = amp
class File(visualizer.File): def __init__(self, *args): visualizer.File.__init__(self, *args) self.playing_chunks = OrderedDict() self.gatherer = Gatherer() def add_chunk(self, chunk): self.playing_chunks[chunk.id] = chunk def update(self): outdated = filter(lambda chunk_id: self.playing_chunks[chunk_id].age() > DURATION, self.playing_chunks) for chunk_id in outdated: self.gatherer.add(self.playing_chunks[chunk_id]) del self.playing_chunks[chunk_id] def render(self): self.y = float(self.visualizer.height) / (self.visualizer.num_files + 1) * ( self.filenum + 1) self.y1 = int(self.y - GATHERED_HEIGHT/2) self.y2 = int(self.y + GATHERED_HEIGHT/2) self.draw_background() self.draw_gathered_chunks() self.draw_playing_chunks() def draw_background(self): x1 = self.byte_to_px(0) x2 = self.byte_to_px(self.length) self.visualizer.set_color(BACKGROUND_COLOR) glBegin(GL_QUADS) glVertex2i(x1, self.y2) glVertex2i(x2, self.y2) glVertex2i(x2, self.y1) glVertex2i(x1, self.y1) glEnd() def draw_gathered_chunks(self): for chunk in self.gatherer.pieces(): self.draw_gathered_chunk(chunk) def draw_playing_chunks(self): for chunk in self.playing_chunks.values(): self.draw_playing_chunk(chunk) def draw_gathered_chunk(self, chunk): self.visualizer.set_color(GATHERED_COLOR) x1, x2 = self.chunk_position(chunk) glBegin(GL_QUADS) glVertex2i(x1, self.y2) glVertex2i(x2, self.y2) glVertex2i(x2, self.y1) glVertex2i(x1, self.y1) glEnd() def draw_playing_chunk(self, chunk): height = PLAYING_HEIGHT y1 = int(self.y - height/2) y2 = int(self.y + height/2) self.visualizer.set_color(PLAYING_COLOR) x1 = self.byte_to_px(chunk.begin) x2 = self.byte_to_px(chunk.end) x2 = max(x2, x1 + 1) glBegin(GL_QUADS) glVertex2i(x1, y2) glVertex2i(x2, y2) glVertex2i(x2, y1) glVertex2i(x1, y1) glEnd() def chunk_position(self, chunk): x1 = self.byte_to_px(chunk.begin) x2 = self.byte_to_px(chunk.end) x2 = max(x2, x1 + 1) return x1, x2 def byte_to_px(self, byte): return MARGIN + int( float((self.visualizer.max_file_length - self.length) / 2 + byte) / \ self.visualizer.max_file_length * (self.visualizer.width - 2*MARGIN))
class File(visualizer.File): def __init__(self, *args): visualizer.File.__init__(self, *args) self.playing_segments = OrderedDict() self.gatherer = Gatherer() self.x_scope = DynamicScope() def add_segment(self, segment): self.x_scope.put(segment.begin) self.x_scope.put(segment.end) pan = (self.byte_to_coord(segment.begin) + self.byte_to_coord(segment.end)) / 2 self.visualizer.playing_segment(segment, pan) self.playing_segments[segment.id] = segment def update(self): outdated = filter(lambda segment_id: self.playing_segments[segment_id].relative_age() > 1, self.playing_segments) for segment_id in outdated: self.gatherer.add(self.playing_segments[segment_id]) del self.playing_segments[segment_id] self.x_scope.update() def render(self): self.y = self.visualizer.filenum_to_y_coord(self.filenum) self.draw_gathered_segments() self.draw_playing_segments() def draw_gathered_segments(self): for segment in self.gatherer.pieces(): self.draw_segment(segment, 0) def draw_playing_segments(self): for segment in self.playing_segments.values(): self.draw_playing_segment(segment) def draw_playing_segment(self, segment): actuality = 1 - segment.relative_age() self.draw_segment(segment, actuality) def draw_segment(self, segment, actuality): y_offset = actuality * 10 height = 3 + actuality * 10 y1 = int(self.y + y_offset) y2 = int(self.y + y_offset + height) x1 = self.visualizer.prepend_margin_width + \ int(self.byte_to_coord(segment.begin) * self.visualizer.safe_width) x2 = self.visualizer.prepend_margin_width + \ int(self.byte_to_coord(segment.end) * self.visualizer.safe_width) x1, x2 = self.upscale(x1, x2, actuality) if x2 == x1: x2 = x1 + 1 opacity = 0.2 + (actuality * 0.8) glColor3f(1-opacity, 1-opacity, 1-opacity) glBegin(GL_LINE_LOOP) glVertex2i(x1, y2) glVertex2i(x2, y2) glVertex2i(x2, y1) glVertex2i(x1, y1) glEnd() def upscale(self, x1, x2, actuality): unscaled_size = x2 - x1 desired_size = actuality * ARRIVAL_SIZE if desired_size > unscaled_size: mid = (x1 + x2) / 2 half_desired_size = int(desired_size/2) x1 = mid - half_desired_size x2 = mid + half_desired_size return (x1, x2) def byte_to_coord(self, byte): return self.x_scope.map(byte)
class Waves(visualizer.Visualizer): def __init__(self, args): visualizer.Visualizer.__init__(self, args, file_class=File, segment_class=Segment) self.subscribe_to_waveform() def reset(self): visualizer.Visualizer.reset(self) self.playing_segments = collections.OrderedDict() self.gatherer = Gatherer() def pan_segment(self, segment): # let orchestra & synth spatialize pass def InitGL(self): visualizer.Visualizer.InitGL(self) glClearColor(0.0, 0.0, 0.0, 0.0) self._gathered_segments_layer = self.new_layer(self._render_gathered_segments) def update(self): outdated = [] for segment in self.playing_segments.values(): if not segment.is_playing(): self.gatherer.add(segment) outdated.append(segment.id) if len(outdated) > 0: for segment_id in outdated: del self.playing_segments[segment_id] self._gathered_segments_layer.refresh() def render(self): self.update() self._gathered_segments_layer.draw() self.draw_playing_segments() def draw_playing_segments(self): glEnable(GL_LINE_SMOOTH) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) for segment in self.playing_segments.values(): segment.render() def _render_gathered_segments(self): glBegin(GL_QUADS) x1 = 0 x2 = self.width min_height = GATHERED_LINE_WIDTH * self.height for segment in self.gatherer.pieces(): y1 = self.byte_to_py(segment.torrent_begin) y2 = max(self.byte_to_py(segment.torrent_end), y1 + min_height) if (y2 - y1) > min_height: d = min((y2 - y1) * 0.2, MAX_GRADIENT_HEIGHT * self.height) y1d = y1 + d y2d = y2 - d glColor3f(0, 0, 0) glVertex2f(x1, y1) glColor3f(*GATHERED_COLOR) glVertex2f(x1, y1d) glVertex2f(x2, y1d) glColor3f(0, 0, 0) glVertex2f(x2, y1) glColor3f(0, 0, 0) glVertex2f(x1, y2) glColor3f(*GATHERED_COLOR) glVertex2f(x1, y2d) glVertex2f(x2, y2d) glColor3f(0, 0, 0) glVertex2f(x2, y2) glColor3f(*GATHERED_COLOR) glVertex2f(x1, y1d) glVertex2f(x1, y2d) glVertex2f(x2, y2d) glVertex2f(x2, y1d) else: glColor3f(0, 0, 0) glVertex2f(x1, y1) glColor3f(*GATHERED_COLOR) glVertex2f(x1, y2) glVertex2f(x2, y2) glColor3f(0, 0, 0) glVertex2f(x2, y1) glEnd() def byte_to_py(self, byte): return int(self.byte_to_relative_y(byte) * self.height) def byte_to_relative_y(self, byte): return float(byte) / self.torrent_length def handle_segment_waveform_value(self, segment, value): segment.waveform.appendleft(value) def finished(self): if len(self.gatherer.pieces()) == 1: piece = self.gatherer.pieces()[0] return piece.begin == 0 and piece.end == self.torrent_length else: return False
class File: def __init__(self, length, visualizer): self.visualizer = visualizer self.arriving_chunks = OrderedDict() self.gatherer = Gatherer() def add_chunk(self, chunk): chunk.boid = Boid(self.get_departure_position(chunk), 10.0, 3.0) chunk.target = None chunk.target_type = None chunk.target_position = None chunk.target_position = self.get_target_position(chunk) chunk.boid.arrive(chunk.target_position) chunk.arrived = False self.arriving_chunks[chunk.id] = chunk def stopped_playing(self, chunk_id): chunk = self.arriving_chunks[chunk_id] self.gather_chunk(chunk) def gather_chunk(self, chunk): del self.arriving_chunks[chunk.id] # TODO: verify that these positions are really OK chunk.begin_position = Vector(chunk.boid.loc.x, chunk.boid.loc.y) chunk.end_position = Vector(chunk.boid.loc.x, chunk.boid.loc.y) self.gatherer.add(chunk) self.reorient_chunks_following(chunk) def get_departure_position(self, chunk): if chunk.pan < 0.5: x = 0 else: x = self.visualizer.width y = chunk.height * self.visualizer.height return Vector(x, y) def get_target_position(self, chunk): if not chunk.target_position: self.find_target(chunk) return chunk.target_position def find_target(self, chunk): position = self.find_joinable_piece(chunk) if position: chunk.target_type = TargetType.PIECE chunk.target_position = position return other_chunk = self.find_other_huntable_chunk(chunk) if other_chunk: chunk.target_type = TargetType.CHUNK chunk.target = other_chunk chunk.target_position = other_chunk.boid.loc return if len(self.gatherer.pieces()) > 0: chunk.target_position = self.close_to_existing_piece() return chunk.target_type = TargetType.NEW_PIECE chunk.target_position = self.anywhere() def anywhere(self): return Vector(random.uniform(self.visualizer.width * INNER_MARGIN, self.visualizer.width * (1 - INNER_MARGIN*2)), random.uniform(self.visualizer.height * INNER_MARGIN, self.visualizer.height * (1 - INNER_MARGIN*2))) def close_to_existing_piece(self): piece = random.choice(self.gatherer.pieces()) position = random.choice([piece.begin_position, piece.end_position]) angle = random.uniform(0, 2 * math.pi) distance = 1.0 movement = Vector(distance * math.cos(angle), distance * math.sin(angle)) return position + movement def find_joinable_piece(self, chunk): appendable_piece_key = self.gatherer.find_appendable_piece(chunk) prependable_piece_key = self.gatherer.find_prependable_piece(chunk) if appendable_piece_key and prependable_piece_key: appendable_position = self.gatherer.piece(appendable_piece_key).begin_position prepandable_position = self.gatherer.piece(prependable_piece_key).end_position chunk.target_position = (appendable_position + prepandable_position) / 2 elif appendable_piece_key: return self.gatherer.piece(appendable_piece_key).begin_position elif prependable_piece_key: return self.gatherer.piece(prependable_piece_key).end_position def find_other_huntable_chunk(self, chunk): # optimization: iterate appendables in reverse order, or use some kind of cache for other in self.arriving_chunks.values(): if other.end == chunk.begin: return other if other.begin == chunk.end: return other def reorient_chunks_following(self, targeted_chunk): for chunk in self.arriving_chunks.values(): if chunk.target_type == TargetType.CHUNK and \ chunk.target == targeted_chunk: self.find_target(chunk) def update(self): self.update_pieces() self.update_arriving_chunks() def update_pieces(self): for piece in self.gatherer.pieces(): self.get_piece_force(piece) for piece in self.gatherer.pieces(): self.apply_piece_force(piece) def get_piece_force(self, piece): piece.desired_length = self.desired_piece_length(piece) piece.begin_force = Vector(0,0) piece.end_force = Vector(0,0) self.consider_desired_length(piece, piece.begin_force, piece.begin_position, piece.end_position) self.consider_desired_length(piece, piece.end_force, piece.end_position, piece.begin_position) piece.begin_force.limit(3.0) piece.end_force.limit(3.0) def desired_piece_length(self, piece): return piece.byte_size / 10 def consider_desired_length(self, piece, force, position, opposite_position): force += spring_force(position, opposite_position, piece.desired_length) def apply_piece_force(self, piece): piece.begin_position += piece.begin_force piece.end_position += piece.end_force def update_arriving_chunks(self): for chunk in self.arriving_chunks.values(): if not chunk.arrived: chunk.boid.update() if self.arrived(chunk): #self.visualizer.play_chunk(chunk) # TEMP chunk.arrived = True self.gather_chunk(chunk) # TEMP def arrived(self, chunk): if chunk.target_type in [TargetType.PIECE, TargetType.NEW_PIECE]: distance = (chunk.target_position - chunk.boid.loc).mag() return distance < 1.0 else: return False