musicmatrix / app.py
circulartext's picture
Update app.py
2257e84 verified
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()