Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import numpy as np
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
from matplotlib.animation import FuncAnimation, PillowWriter
|
5 |
+
import scipy.io.wavfile as wavfile
|
6 |
+
import io
|
7 |
+
|
8 |
+
# Constants for sound generation
|
9 |
+
SAMPLE_RATE = 48000
|
10 |
+
COLUMN_DURATION = 1 # Duration of each column in seconds
|
11 |
+
|
12 |
+
# Mapping of matrix numbers to musical notes
|
13 |
+
notes = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"]
|
14 |
+
note_map = {2: "A", 3: "A#", 4: "B", 5: "C", 6: "C#", 7: "D", 8: "D#", 9: "E",
|
15 |
+
10: "F", 11: "F#", 12: "G", 13: "G#"}
|
16 |
+
|
17 |
+
def generate_tone(note, duration):
|
18 |
+
"""Generates a tone for a specified note and duration."""
|
19 |
+
frequency = 440 * 2 ** ((notes.index(note) - 9) / 12)
|
20 |
+
t = np.linspace(0, duration, int(SAMPLE_RATE * duration), endpoint=False)
|
21 |
+
return 0.5 * np.sin(2 * np.pi * frequency * t)
|
22 |
+
|
23 |
+
def get_matrix_audio(matrix):
|
24 |
+
"""Generate audio sequence for one matrix state."""
|
25 |
+
audio_sequence = []
|
26 |
+
|
27 |
+
for col in range(matrix.shape[1]):
|
28 |
+
col_audio = np.zeros(0)
|
29 |
+
for row in range(matrix.shape[0]):
|
30 |
+
num = matrix[row, col]
|
31 |
+
if num > 1:
|
32 |
+
note = note_map.get(num, "A")
|
33 |
+
tone = generate_tone(note, duration=COLUMN_DURATION / matrix.shape[0])
|
34 |
+
col_audio = np.concatenate((col_audio, tone))
|
35 |
+
|
36 |
+
audio_sequence.append(col_audio)
|
37 |
+
|
38 |
+
return np.concatenate(audio_sequence)
|
39 |
+
|
40 |
+
class NumberSpreadSimulator:
|
41 |
+
def __init__(self, initial_matrix):
|
42 |
+
self.grid = np.array(initial_matrix, dtype=int)
|
43 |
+
self.audio_frames = []
|
44 |
+
self.original_numbers = self.find_original_numbers()
|
45 |
+
self.initialize_audio()
|
46 |
+
|
47 |
+
def find_original_numbers(self):
|
48 |
+
"""Find all original numbers in the initial matrix."""
|
49 |
+
numbers = set()
|
50 |
+
for row in range(self.grid.shape[0]):
|
51 |
+
for col in range(self.grid.shape[1]):
|
52 |
+
if self.grid[row, col] > 1:
|
53 |
+
numbers.add(self.grid[row, col])
|
54 |
+
return list(numbers)
|
55 |
+
|
56 |
+
def initialize_audio(self):
|
57 |
+
"""Generate audio for initial state and store it."""
|
58 |
+
self.audio_frames.append(get_matrix_audio(self.grid))
|
59 |
+
|
60 |
+
def find_original_number(self, current):
|
61 |
+
"""Find the original source number based on the current number."""
|
62 |
+
closest = min(self.original_numbers, key=lambda x: abs(x - current))
|
63 |
+
return closest
|
64 |
+
|
65 |
+
def step(self):
|
66 |
+
"""Simulates a step in the matrix spread."""
|
67 |
+
new_grid = np.zeros_like(self.grid)
|
68 |
+
has_changes = False
|
69 |
+
|
70 |
+
# Process each position in the grid
|
71 |
+
for row in range(self.grid.shape[0]):
|
72 |
+
for col in range(self.grid.shape[1]):
|
73 |
+
current = self.grid[row, col]
|
74 |
+
|
75 |
+
if current > 1:
|
76 |
+
has_changes = True
|
77 |
+
half = current // 2
|
78 |
+
|
79 |
+
# Define potential target positions
|
80 |
+
targets = []
|
81 |
+
if current % 2 == 0: # Even
|
82 |
+
if row > 0: targets.append((row - 1, col, half))
|
83 |
+
if col > 0: targets.append((row, col - 1, half))
|
84 |
+
else: # Odd
|
85 |
+
if col > 0: targets.append((row, col - 1, half))
|
86 |
+
if row > 0: targets.append((row - 1, col, half))
|
87 |
+
if row > 0 and col > 0: targets.append((row - 1, col - 1, 1))
|
88 |
+
|
89 |
+
# Process each target position
|
90 |
+
for target_row, target_col, value in targets:
|
91 |
+
if self.grid[target_row, target_col] == -1:
|
92 |
+
# If target is -1, replace with original number
|
93 |
+
new_grid[target_row, target_col] = self.find_original_number(current)
|
94 |
+
elif self.grid[target_row, target_col] == -2:
|
95 |
+
# If target is -2, double the incoming value
|
96 |
+
new_grid[target_row, target_col] = value * 2
|
97 |
+
elif self.grid[target_row, target_col] == -3:
|
98 |
+
# If target is -3, set to 0
|
99 |
+
new_grid[target_row, target_col] = 0
|
100 |
+
else:
|
101 |
+
new_grid[target_row, target_col] += value
|
102 |
+
|
103 |
+
# Copy over any remaining special values
|
104 |
+
if (self.grid[row, col] in [-1, -2, -3]) and new_grid[row, col] == 0:
|
105 |
+
new_grid[row, col] = self.grid[row, col]
|
106 |
+
|
107 |
+
self.grid = new_grid
|
108 |
+
if has_changes:
|
109 |
+
self.audio_frames.append(get_matrix_audio(self.grid))
|
110 |
+
|
111 |
+
return has_changes, self.grid
|
112 |
+
|
113 |
+
def create_animation(matrix):
|
114 |
+
# Initialize simulator
|
115 |
+
sim = NumberSpreadSimulator(matrix)
|
116 |
+
|
117 |
+
# Determine matrix dimensions
|
118 |
+
rows, cols = sim.grid.shape
|
119 |
+
|
120 |
+
# Create figure and axis with dynamic limits
|
121 |
+
fig, ax = plt.subplots(figsize=(10, 10))
|
122 |
+
ax.set_xlim(-0.5, cols - 0.5)
|
123 |
+
ax.set_ylim(-0.5, rows - 0.5)
|
124 |
+
ax.set_title("Matrix Spread Visualization")
|
125 |
+
|
126 |
+
# Initialize plot elements
|
127 |
+
circles = []
|
128 |
+
labels = []
|
129 |
+
|
130 |
+
for i in range(rows):
|
131 |
+
for j in range(cols):
|
132 |
+
circle = plt.Circle((j, rows - 1 - i), 0.2,
|
133 |
+
color='blue',
|
134 |
+
fill=False)
|
135 |
+
label = ax.text(j, rows - 1 - i, '', ha='center', va='center', fontsize=12)
|
136 |
+
circles.append(circle)
|
137 |
+
labels.append(label)
|
138 |
+
ax.add_patch(circle)
|
139 |
+
|
140 |
+
current_frame = [0]
|
141 |
+
|
142 |
+
def update(frame):
|
143 |
+
if current_frame[0] == 0:
|
144 |
+
matrix = sim.grid
|
145 |
+
else:
|
146 |
+
has_changes, matrix = sim.step()
|
147 |
+
if not has_changes:
|
148 |
+
ani.event_source.stop()
|
149 |
+
return
|
150 |
+
|
151 |
+
for i in range(rows):
|
152 |
+
for j in range(cols):
|
153 |
+
value = matrix[i, j]
|
154 |
+
index = i * cols + j
|
155 |
+
|
156 |
+
if value != 0:
|
157 |
+
circles[index].set_radius(0.1 + 0.1 * (abs(value) / 10))
|
158 |
+
if value == -1:
|
159 |
+
circles[index].set_facecolor('green')
|
160 |
+
elif value == -2:
|
161 |
+
circles[index].set_facecolor('purple')
|
162 |
+
elif value == -3:
|
163 |
+
circles[index].set_facecolor('red')
|
164 |
+
else:
|
165 |
+
circles[index].set_facecolor('orange')
|
166 |
+
else:
|
167 |
+
circles[index].set_radius(0.1)
|
168 |
+
circles[index].set_facecolor('blue')
|
169 |
+
labels[index].set_text(str(value) if value != 0 else '')
|
170 |
+
|
171 |
+
current_frame[0] += 1
|
172 |
+
return circles + labels
|
173 |
+
|
174 |
+
ani = FuncAnimation(fig, update, frames=None, interval=1000, blit=True)
|
175 |
+
|
176 |
+
# Save the animation to a GIF
|
177 |
+
gif_buffer = io.BytesIO()
|
178 |
+
ani.save(gif_buffer, format='gif', writer=PillowWriter(fps=1))
|
179 |
+
plt.close(fig)
|
180 |
+
gif_buffer.seek(0)
|
181 |
+
|
182 |
+
return gif_buffer
|
183 |
+
|
184 |
+
def run_simulation(matrix_input):
|
185 |
+
"""
|
186 |
+
Run the full simulation based on user-input matrix
|
187 |
+
|
188 |
+
:param matrix_input: 2D list of integers representing the matrix
|
189 |
+
:return: tuple of (audio_path, gif_buffer)
|
190 |
+
"""
|
191 |
+
# Convert input to numpy array
|
192 |
+
matrix = np.array(matrix_input, dtype=int)
|
193 |
+
|
194 |
+
# Initialize simulator
|
195 |
+
sim = NumberSpreadSimulator(matrix)
|
196 |
+
|
197 |
+
# Run simulation until no more changes
|
198 |
+
while True:
|
199 |
+
has_changes, _ = sim.step()
|
200 |
+
if not has_changes:
|
201 |
+
break
|
202 |
+
|
203 |
+
# Generate audio
|
204 |
+
final_audio = np.concatenate(sim.audio_frames)
|
205 |
+
final_audio = np.int16(final_audio * 32767)
|
206 |
+
|
207 |
+
# Save audio
|
208 |
+
audio_path = "matrix_sound.wav"
|
209 |
+
wavfile.write(audio_path, SAMPLE_RATE, final_audio)
|
210 |
+
|
211 |
+
# Create animation
|
212 |
+
gif_buffer = create_animation(matrix)
|
213 |
+
|
214 |
+
return audio_path, gif_buffer
|
215 |
+
|
216 |
+
# Gradio Interface
|
217 |
+
def create_gradio_interface():
|
218 |
+
# Default initial matrix
|
219 |
+
default_matrix = [
|
220 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
221 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
222 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
223 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
224 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 47]
|
225 |
+
]
|
226 |
+
|
227 |
+
# Create Gradio interface
|
228 |
+
iface = gr.Interface(
|
229 |
+
fn=run_simulation,
|
230 |
+
inputs=[
|
231 |
+
gr.Dataframe(
|
232 |
+
headers=[str(i) for i in range(10)],
|
233 |
+
datatype="number",
|
234 |
+
value=default_matrix,
|
235 |
+
type="numpy",
|
236 |
+
label="Edit Matrix Values"
|
237 |
+
)
|
238 |
+
],
|
239 |
+
outputs=[
|
240 |
+
gr.Audio(type="filepath", label="Generated Sound"),
|
241 |
+
gr.Image(type="file", label="Matrix Animation")
|
242 |
+
],
|
243 |
+
title="Number Spread Simulator",
|
244 |
+
description="Edit the matrix and see how numbers spread, generating a unique sound and animation!"
|
245 |
+
)
|
246 |
+
|
247 |
+
return iface
|
248 |
+
|
249 |
+
# Launch the interface
|
250 |
+
iface = create_gradio_interface()
|
251 |
+
|
252 |
+
if __name__ == "__main__":
|
253 |
+
iface.launch()
|