import gradio as gr import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation, PillowWriter import scipy.io.wavfile as wavfile # Constants for sound generation SAMPLE_RATE = 48000 COLUMN_DURATION = 1 # Duration of each column in seconds # Mapping of matrix numbers to musical notes notes = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"] note_map = { 2: "A", 3: "A#", 4: "B", 5: "C", 6: "C#", 7: "D", 8: "D#", 9: "E", 10: "F", 11: "F#", 12: "G", 13: "G#" } def generate_tone(note, duration): """Generates a tone for a specified note and duration.""" frequency = 440 * 2 ** ((notes.index(note) - 9) / 12) t = np.linspace(0, duration, int(SAMPLE_RATE * duration), endpoint=False) return 0.5 * np.sin(2 * np.pi * frequency * t) def get_matrix_audio(matrix): """Generate audio sequence for one matrix state.""" audio_sequence = [] for col in range(matrix.shape[1]): col_audio = np.zeros(0) for row in range(matrix.shape[0]): num = matrix[row, col] if num > 1: note = note_map.get(num, "A") tone = generate_tone(note, duration=COLUMN_DURATION / matrix.shape[0]) col_audio = np.concatenate((col_audio, tone)) audio_sequence.append(col_audio) return np.concatenate(audio_sequence) class NumberSpreadSimulator: def __init__(self, initial_matrix): self.grid = np.array(initial_matrix, dtype=int) self.audio_frames = [] self.original_numbers = self.find_original_numbers() self.initialize_audio() def find_original_numbers(self): """Find all original numbers in the initial matrix.""" numbers = set() for row in range(self.grid.shape[0]): for col in range(self.grid.shape[1]): if self.grid[row, col] > 1: numbers.add(self.grid[row, col]) return list(numbers) def initialize_audio(self): """Generate audio for initial state and store it.""" self.audio_frames.append(get_matrix_audio(self.grid)) def find_original_number(self, current): """Find the original source number based on the current number.""" closest = min(self.original_numbers, key=lambda x: abs(x - current)) return closest def step(self): """Simulates a step in the matrix spread.""" new_grid = np.zeros_like(self.grid) has_changes = False # Process each position in the grid for row in range(self.grid.shape[0]): for col in range(self.grid.shape[1]): current = self.grid[row, col] if current > 1: has_changes = True half = current // 2 # Define potential target positions targets = [] if current % 2 == 0: # Even if row > 0: targets.append((row - 1, col, half)) if col > 0: targets.append((row, col - 1, half)) else: # Odd if col > 0: targets.append((row, col - 1, half)) if row > 0: targets.append((row - 1, col, half)) if row > 0 and col > 0: targets.append((row - 1, col - 1, 1)) # Process each target position for target_row, target_col, value in targets: if self.grid[target_row, target_col] == -1: # If target is -1, replace with original number new_grid[target_row, target_col] = self.find_original_number(current) elif self.grid[target_row, target_col] == -2: # If target is -2, double the incoming value new_grid[target_row, target_col] = value * 2 elif self.grid[target_row, target_col] == -3: # If target is -3, set to 0 new_grid[target_row, target_col] = 0 else: new_grid[target_row, target_col] += value # Copy over any remaining special values if (self.grid[row, col] in [-1, -2, -3]) and new_grid[row, col] == 0: new_grid[row, col] = self.grid[row, col] self.grid = new_grid if has_changes: self.audio_frames.append(get_matrix_audio(self.grid)) return has_changes, self.grid def create_animation(matrix): # Initialize simulator sim = NumberSpreadSimulator(matrix) # Determine matrix dimensions rows, cols = sim.grid.shape # Create figure and axis with dynamic limits fig, ax = plt.subplots(figsize=(10, 10)) ax.set_xlim(-0.5, cols - 0.5) ax.set_ylim(-0.5, rows - 0.5) ax.set_title("Matrix Spread Visualization") # Initialize plot elements circles = [] labels = [] for i in range(rows): for j in range(cols): circle = plt.Circle((j, rows - 1 - i), 0.2, color='blue', fill=False) label = ax.text(j, rows - 1 - i, '', ha='center', va='center', fontsize=12) circles.append(circle) labels.append(label) ax.add_patch(circle) current_frame = [0] def update(frame): if current_frame[0] == 0: matrix = sim.grid else: has_changes, matrix = sim.step() if not has_changes: ani.event_source.stop() return circles + labels for i in range(rows): for j in range(cols): value = matrix[i, j] index = i * cols + j if value != 0: circles[index].set_radius(0.1 + 0.1 * (abs(value) / 10)) if value == -1: circles[index].set_facecolor('green') elif value == -2: circles[index].set_facecolor('purple') elif value == -3: circles[index].set_facecolor('red') else: circles[index].set_facecolor('orange') else: circles[index].set_radius(0.1) circles[index].set_facecolor('blue') labels[index].set_text(str(value) if value != 0 else '') current_frame[0] += 1 return circles + labels # Estimate the number of frames needed max_steps = 100 # Set a reasonable limit for the number of steps ani = FuncAnimation(fig, update, frames=max_steps, interval=1000, blit=True, cache_frame_data=False) # Save the animation to a GIF file gif_path = "matrix_animation.gif" ani.save(gif_path, writer=PillowWriter(fps=1)) plt.close(fig) return gif_path def run_simulation(matrix_input): """ Run the full simulation based on user-input matrix :param matrix_input: 2D list of integers representing the matrix :return: tuple of (audio_path, gif_path) """ # Convert input to numpy array matrix = np.array(matrix_input, dtype=int) # Initialize simulator sim = NumberSpreadSimulator(matrix) # Run simulation until no more changes while True: has_changes, _ = sim.step() if not has_changes: break # Generate audio final_audio = np.concatenate(sim.audio_frames) final_audio = np.int16(final_audio * 32767) # Save audio audio_path = "matrix_sound.wav" wavfile.write(audio_path, SAMPLE_RATE, final_audio) # Create animation gif_path = create_animation(matrix) return audio_path, gif_path # Gradio Interface def create_gradio_interface(): # Default initial matrix default_matrix = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 47] ] # Create Gradio interface iface = gr.Interface( fn=run_simulation, inputs=[ gr.Dataframe( headers=[str(i) for i in range(10)], datatype="number", value=default_matrix, type="numpy", label="Edit Matrix Values" ) ], outputs=[ gr.Audio(type="filepath", label="Generated Sound"), gr.Image(type="filepath", label="Matrix Animation") ], title="Number Spread Simulator", description="Edit the matrix and see how numbers spread, generating a unique sound and animation!" ) return iface # Launch the interface iface = create_gradio_interface() if __name__ == "__main__": iface.launch()