This commit is contained in:
aprilnightk 2025-08-09 20:24:24 +03:00
parent 9560346cdd
commit 72e828aaae
3 changed files with 183 additions and 41 deletions

View file

@ -5,25 +5,50 @@ from .soundnode import *
TAU = 2 * math.pi
def stb(i: int) -> bytes:
return i.to_bytes(3, byteorder='little', signed=True)
def stb(i: int, bytelen: int) -> bytes:
return i.to_bytes(bytelen, byteorder='little', signed=True)
class SinkNode(SoundNode):
def __init__(self, name, room):
super().__init__(name, room)
def fill_amp_cache(self, t):
# This function returns volumes of each relevant freq
# at tick t
res = dict()
for source_node in self.wire_in:
for freq, vol in self.sample_freqs_by_wire(source_node, t).items():
if not freq in res:
res[freq] = vol
else:
res[freq] += vol
for source_node in self.air_in:
for freq, vol in self.sample_freqs_by_air(source_node, t).items():
if not freq in res:
res[freq] = vol
else:
res[freq] += vol
self.amp_cache[t] = res
class Room:
def __init__(self):
self.nodes = []
self.dissipation_quotient = 0.9993
self.sample_rate = 44100
self.speed_of_sound = 343 / float(self.sample_rate) #m/tick
self.speed_of_sound = 343 / float(self.sample_rate) # m/tick
self.set_bit_depth(24)
self.lowest_freq = 430
self.highest_freq = 450
self.freq_sample_step = 1
self.producers = []
self.left_sink = SoundNode('LEFT', self)
self.right_sink = SoundNode('RIGHT', self)
self.left_sink = SinkNode('LEFT', self)
self.right_sink = SinkNode('RIGHT', self)
self.sine_multiplier = TAU / self.sample_rate
@ -32,29 +57,98 @@ class Room:
self.bit_depth = bit_depth
self.max_amp = int((2**bit_depth) / 2.0 - 1)
self.min_amp = -int((2**bit_depth) / 2.0)
self.sample_width = int(self.bit_depth / 8.0)
def link_air(self, node1, node2):
if not node2 in node1.air_out:
node1.air_out.append(node2)
if not node1 in node2.air_in:
node2.air_in.append(node1)
def generate_frames(self, start_t_sec, end_t_sec):
def link_wire(self, node1, node2):
frames = []
if not node2 in node1.wire_out:
node1.wire_out.append(node2)
start_t = start_t_sec * self.sample_rate
end_t = end_t_sec * self.sample_rate
if not node1 in node2.wire_in:
node2.wire_in.append(node1)
for t in range(start_t, end_t):
for node in self.nodes:
node.tick_done = False
if t%1000 == 0:
print(t)
updates = True
while updates:
updates = False
for node in self.nodes:
if not (t in node.amp_cache):
has_unprocessed_inputs = False
for in_node in node.air_in:
if not in_node.tick_done:
has_unprocessed_inputs = True
break
if not has_unprocessed_inputs:
for in_node in node.wire_in:
if not in_node.tick_done:
has_unprocessed_inputs = True
break
if not has_unprocessed_inputs:
node.fill_amp_cache(t)
node.tick_done = True
updates = True
left_total_amp = self.max_amp * sum([vol for freq, vol in self.left_sink.amp_cache[t].items()])
right_total_amp = self.max_amp * sum([vol for freq, vol in self.right_sink.amp_cache[t].items()])
if left_total_amp > self.max_amp:
left_total_amp = self.max_amp
if left_total_amp < self.min_amp:
left_total_amp = self.min_amp
if right_total_amp > self.max_amp:
right_total_amp = self.max_amp
if right_total_amp < self.min_amp:
right_total_amp = self.min_amp
frames.append(stb(int(left_total_amp), self.sample_width))
frames.append(stb(int(right_total_amp), self.sample_width))
return frames
def write_frames_to_wavefile(self, fn, frames):
with wave.open(fn, 'wb') as wav:
wav.setnchannels(2)
wav.setsampwidth(self.sample_width)
wav.setframerate(self.sample_rate)
wav.writeframes(b''.join(frames))
def record(self, fn, start_t_sec, end_t_sec):
frames = self.generate_frames(start_t_sec, end_t_sec)
self.write_frames_to_wavefile(fn, frames)
"""
def record(self, fn, start_t_sec, end_t_sec):
with wave.open(fn, 'wb') as wav:
wav.setnchannels(2)
wav.setsampwidth(int(self.bit_depth / 8.0))
self.sample_width = int(self.bit_depth / 8.0)
wav.setsampwidth(self.sample_width)
wav.setframerate(self.sample_rate)
frames = []
@ -90,8 +184,8 @@ class Room:
f += self.freq_sample_step
left_total_amp = sum(left_amps)
right_total_amp = sum(right_amps)
left_total_amp = self.max_amp * sum(left_amps)
right_total_amp = self.max_amp * sum(right_amps)
if left_total_amp > self.max_amp:
left_total_amp = self.max_amp
@ -105,7 +199,8 @@ class Room:
if right_total_amp < self.min_amp:
right_total_amp = self.min_amp
frames.append(stb(int(left_total_amp)))
frames.append(stb(int(right_total_amp)))
frames.append(stb(int(left_total_amp), self.sample_width))
frames.append(stb(int(right_total_amp), self.sample_width))
wav.writeframes(b''.join(frames))
wav.writeframes(b''.join(frames))
"""

View file

@ -7,14 +7,17 @@ class SoundNode:
self.name = name
self.room = room
self.room.nodes.append(self)
self.amp_cache = dict() # {tick: {freq: amp}}
self.air_in = []
self.air_out = []
self.wire_in = []
self.wire_out = []
self.start_location = (0, 0, 0)
self.tick_done = False
def location(self, t):
# Location of the soundnote (x,y,z) in meters
@ -29,17 +32,49 @@ class SoundNode:
return (loc[0]-other_loc[0])**2 + (loc[1]-other_loc[1])**2 + (loc[2]-other_loc[2])**2
def frequency_max_rel_amp(self, f, t):
return 0
def fill_amp_cache(self, t):
self.amp_cache[t] = dict()
def sample_freqs_by_wire(self, source_node, current_t):
if current_t in source_node.amp_cache:
return source_node.amp_cache[current_t]
return dict()
def sample_freqs_by_air(self, source_node, current_t):
dist = self.distance_to_node(source_node, current_t)
sample_t = current_t - int(dist / self.room.speed_of_sound)
if sample_t in source_node.amp_cache:
return source_node.amp_cache[sample_t]
return dict()
def add_air_output(self, out_node):
if not self in out_node.air_in:
out_node.air_in.append(self)
def add_wire_output(self, out_node):
if not self in out_node.wire_in:
out_node.wire_in.append(self)
"""
def amp_at_tick(self, f, t):
return self.room.max_amp * self.frequency_max_rel_amp(f, t) * math.sin(self.room.sine_multiplier * f * t)
return self.frequency_max_rel_amp(f, t) * math.sin(self.room.sine_multiplier * f * t)
def amp_at_tick_by_air(self, f, t, node):
dist = self.distance_to_node(node, t)
t = t - int(dist / self.room.speed_of_sound)
return self.room.max_amp * self.frequency_max_rel_amp(f, t) * math.sin(self.room.sine_multiplier * f * t)
return self.frequency_max_rel_amp(f, t) * math.sin(self.room.sine_multiplier * f * t)
"""

28
test.py
View file

@ -14,16 +14,28 @@ class SineNode(SoundNode):
self.freq = freq
self.volume = 0.8
def frequency_max_rel_amp(self, f, t):
if (f > self.freq-20) and (f < self.freq+20):
return self.volume*0.05*math.sin(f)
return 0
def calc_freqs_volumes(self, t):
# This function returns volumes of each relevant freq
# at tick t
res = dict()
for freq in range(self.freq-20, self.freq+20):
res[freq] = self.volume*0.05*math.sin(freq)
return res
def fill_amp_cache(self, t):
tdct = dict()
for freq, vol in self.calc_freqs_volumes(t).items():
tdct[freq] = vol * math.sin(self.room.sine_multiplier * freq * t)
self.amp_cache[t] = tdct
sn = SineNode(440, R)
R.link_air(sn, R.left_sink)
R.link_air(sn, R.right_sink)
sn.add_air_output(R.left_sink)
sn.add_air_output(R.right_sink)
R.record('test5.wav', 0, 6)
R.record('test6.wav', 0, 2)