def test_percent_possible(self): # Test guaranteed outcomes self.assertTrue(rand.prob_bool(1000)) self.assertTrue(rand.prob_bool(100)) self.assertFalse(rand.prob_bool(0)) self.assertFalse(rand.prob_bool(-1000)) # Test that probabilties are working as expected true_count = 0 for i in range(1000): if rand.percent_possible(20): true_count += 1 self.assertTrue(5 <= true_count <= 400)
def populate_item_list(self, item_count): """ Fill ``self.item_list`` with Node instances. Args: item_count (int): Number of items to create Returns: None """ graph_weights = [(network, 1) for network in self.word_graphs] # Pick a random network to start on current_network = random.choice(self.word_graphs) # Main item population loop for i in range(item_count): # Determine if we should change networks (crude) if rand.prob_bool(0.01): old_network = current_network while current_network == old_network: current_network = rand.weighted_choice(graph_weights) # Determine with self.pause_or_write_network whether to append a # blank line or a word to self.item_list if self.pause_or_write_network.pick().get_value() == 0: self.item_list.append(custom_nodes.BlankLine()) else: new_node = current_network.pick() self.item_list.append(new_node)
def manage_replayers(data_paths, replayer_list, add_player_prob, remove_player_prob): """ Manage a list of ``Replayer`` 's, sometimes adding and removing items. Args: data_paths (list[str]): replayer_list (list[str]): add_player_prob (float): probability between 0 and 1 to add a replayer to the replayer list remove_player_prob (float): probability between 0 and 1 to to add a replayer to the replayer list Returns: """ if rand.prob_bool(add_player_prob): add_random_replayer(replayer_list, data_paths) if rand.prob_bool(remove_player_prob): remove_random_replayer(replayer_list)
def build_chunk(oscillators): """ Build an audio chunk and progress the oscillator states. Args: oscillators (list): A list of oscillator.Oscillator objects to build chunks from Returns: str: a string of audio sample bytes ready to be written to a wave file """ step_random_processes(oscillators) subchunks = [] for osc in oscillators: osc.amplitude.step_amp() osc_chunk = osc.get_samples(config.CHUNK_SIZE) if osc_chunk is not None: subchunks.append(osc_chunk) if len(subchunks): new_chunk = sum(subchunks) else: new_chunk = numpy.zeros(config.CHUNK_SIZE) # If we exceed the maximum amplitude, handle it gracefully chunk_amplitude = amplitude.find_amplitude(new_chunk) if chunk_amplitude > config.MAX_AMPLITUDE: # Normalize the amplitude chunk to mitigate immediate clipping new_chunk = amplitude.normalize_amplitude(new_chunk, config.MAX_AMPLITUDE) # Pick some of the offending oscillators (and some random others) # and lower their drift targets avg_amp = (sum(osc.amplitude.value for osc in oscillators) / len(oscillators)) for osc in oscillators: if (osc.amplitude.value > avg_amp and rand.prob_bool(0.1) or rand.prob_bool(0.01)): osc.amplitude.drift_target = rand.weighted_rand( [(-5, 1), (0, 10)]) osc.amplitude.change_rate = rand.weighted_rand( osc.amplitude.change_rate_weights) return new_chunk.astype(config.SAMPLE_DATA_TYPE).tostring()
def build_chunk(oscillators): """ Build an audio chunk and progress the oscillator states. Args: oscillators (list): A list of oscillator.Oscillator objects to build chunks from Returns: str: a string of audio sample bytes ready to be written to a wave file """ step_random_processes(oscillators) subchunks = [] for osc in oscillators: osc.amplitude.step_amp() osc_chunk = osc.get_samples(config.CHUNK_SIZE) if osc_chunk is not None: subchunks.append(osc_chunk) if len(subchunks): new_chunk = sum(subchunks) else: new_chunk = numpy.zeros(config.CHUNK_SIZE) # If we exceed the maximum amplitude, handle it gracefully chunk_amplitude = amplitude.find_amplitude(new_chunk) if chunk_amplitude > config.MAX_AMPLITUDE: # Normalize the amplitude chunk to mitigate immediate clipping new_chunk = amplitude.normalize_amplitude(new_chunk, config.MAX_AMPLITUDE) # Pick some of the offending oscillators (and some random others) # and lower their drift targets avg_amp = (sum(osc.amplitude.value for osc in oscillators) / len(oscillators)) for osc in oscillators: if (osc.amplitude.value > avg_amp and rand.prob_bool(0.1) or rand.prob_bool(0.01)): osc.amplitude.drift_target = rand.weighted_rand([(-5, 1), (0, 10)]) osc.amplitude.change_rate = rand.weighted_rand( osc.amplitude.change_rate_weights) return new_chunk.astype(config.SAMPLE_DATA_TYPE).tostring()
def step_amp(self): """ Change the amplitude according to the change rate and drift target. Also offer a chance to re-roll behavior attributes. Returns: None """ # Roll for a chance to change the drift target and change rate if rand.prob_bool(self.move_freq): self.re_roll_behaviors() # step the amplitude difference = self.drift_target - self.value if abs(difference) < self.change_rate: self.value = self.drift_target else: delta = self.change_rate * numpy.sign(difference) self.value += delta
def step_random_processes(oscillators): """ Args: oscillators (list): A list of oscillator.Oscillator objects to operate on Returns: None """ if not rand.prob_bool(0.01): return amp_bias_weights = [(0.001, 1), (0.1, 100), (0.15, 40), (1, 0)] # Find out how many oscillators should move num_moves = iching.get_hexagram('NAIVE') % len(oscillators) for i in range(num_moves): pair = [gram % len(oscillators) for gram in iching.get_hexagram('THREE COIN')] amplitudes = [(gram / 64) * rand.weighted_rand(amp_bias_weights) for gram in iching.get_hexagram('THREE COIN')] oscillators[pair[0]].amplitude.drift_target = amplitudes[0] oscillators[pair[1]].amplitude.drift_target = amplitudes[1]
def step_random_processes(oscillators): """ Args: oscillators (list): A list of oscillator.Oscillator objects to operate on Returns: None """ if not rand.prob_bool(0.01): return amp_bias_weights = [(0.001, 1), (0.1, 100), (0.15, 40), (1, 0)] # Find out how many oscillators should move num_moves = iching.get_hexagram('NAIVE') % len(oscillators) for i in range(num_moves): pair = [ gram % len(oscillators) for gram in iching.get_hexagram('THREE COIN') ] amplitudes = [(gram / 64) * rand.weighted_rand(amp_bias_weights) for gram in iching.get_hexagram('THREE COIN')] oscillators[pair[0]].amplitude.drift_target = amplitudes[0] oscillators[pair[1]].amplitude.drift_target = amplitudes[1]
def get(self): """ Render the poem as an HTML string. Returns: str: the body of the poem in HTML """ if rand.prob_bool(self.mutable_chance): # Render text from a markov graph derived from the source text word_list = [] word_count = rand.weighted_rand( self.word_count_weights, round_result=True) word_graph = Graph.from_file(self.filepath, self.distance_weights) for i in range(word_count): word = word_graph.pick().get_value() word_list.append(word) else: # Otherwise, copy source contents literally source_file = open(self.filepath, 'r') word_list = source_file.read().split() # Combine words, process markups, and return HTML return self.render_markups(word_list)
def render_markups(self, word_list): """ Render a list of words and markups to html with automatic line breaks. This method performs several processing steps preparing the poem text for HTML delivery. It: * Converts `---` to stochastic length dashes in the form of empty `<span class="variable-length-dash"></span>` tags * Converts `|||` to stochastic height line breaks in the form of empty `<span class="variable-height-break"></span>` tags * Spontaneously inserts horizontal blank space between words in the form of empty `<span class="horizontal-blank-space"></span>` tags * Calculates the position of line breaks and renders them as divs in the form `<div class="poem-line"> ... </div>` Line breaks are triggered after every word which exceeds `LINE_LENGTH`. This character limit ignores HTML tags, allowing lines containing spans (variable-length-dash or horizontal-blank-space) to intentionally visually exceed the apparent right edge of the poem. Args: word_list (list[str]): The list of words (as well as punctuation marks and markups) to render. Returns: str: The contents of `word_list` rendered as HTML """ working_text = word_list[:] # Copy of word_list to avoid side-effects lines = [] # List of lines in the generated poem current_line = [] # List of words in the current line visible_char_count = 0 # Number of visible chars in current line for word in working_text: if word == '---': # Render triple dashes to variable length visible dashes # (in the form of inline-block spans) dash_length = rand.weighted_rand(self.dash_length_weights) word = variable_length_dash(dash_length) elif word == '|||': # Render triple pipes as variable height breaks # (in the form of fixed-height spans) y_gap = rand.weighted_rand( self.y_gap_height_weights) word = variable_height_break(y_gap) else: # Otherwise, the word will be rendered literally as visible # text, so count it toward the visible character count used # in placing line breaks visible_char_count += len(word) # Roll to insert x-axis gaps if rand.prob_bool(self.x_gap_freq): x_gap = rand.weighted_rand(self.x_gap_length_weights) # Sometimes place space before word, sometimes after (50/50) word = horizontal_blank_space(x_gap) + word # Break lines when LINE_LENGTH is exceeded if visible_char_count > LINE_LENGTH: visible_char_count = 0 lines.append(''.join(current_line)) current_line = [] # Handle spaces appropriately for punctuation marks if word in PUNCTUATIONS: current_line.append(word) else: current_line.append(' ' + word) # Attach final line if current_line: lines.append(''.join(current_line)) return (''.join((surround_with_tag(line, 'div', 'class="poem-line"') for line in lines)))