enhancements

This commit is contained in:
aprilnightk 2025-08-10 23:55:07 +03:00
parent 35e8a2c448
commit 0a3bc7ab20
10 changed files with 281 additions and 93 deletions

View file

@ -26,7 +26,7 @@ class TimedAction:
self.end(node)
else:
self.progress(t - self.start_t, node)
self.progress((t - self.start_t) / float(self.duration_t), node)
# REIMPLEMENT
@ -38,6 +38,6 @@ class TimedAction:
pass
def progress(self, rel_t, node):
def progress(self, prc, node):
pass

82
core/actions/basics.py Normal file
View file

@ -0,0 +1,82 @@
from ..action import TimedAction
class PlayAction(TimedAction):
def __init__(self, start_t, duration_t, nodes, program):
super().__init__(start_t, duration_t, nodes, program)
def start(self, node):
node.active = True
def end(self, node):
node.active = False
class NoteAction(TimedAction):
def __init__(self, note, start_t, duration_t, nodes, program):
super().__init__(start_t, duration_t, nodes, program)
self.note = note
def start(self, node):
node.freq = self.program.note_to_freq(self.note)
node.active = True
def end(self, node):
node.active = False
class LinearPitchTransition(TimedAction):
def __init__(self, start_note, end_node, start_t, duration_t, nodes, program):
super().__init__(start_t, duration_t, nodes, program)
self.start_note = start_note
self.end_node = end_node
self.start_freq = self.program.note_to_freq(start_note)
self.end_freq = self.program.note_to_freq(end_node)
self.freq_range = self.end_freq - self.start_freq
def start(self, node):
node.freq = self.start_freq
node.active = True
def progress(self, prc, node):
node.freq = self.start_freq + (self.freq_range * prc)
def end(self, node):
node.active = False
class LinearSpatialTransition(TimedAction):
def __init__(self, start_coords, end_coords, start_t, duration_t, nodes, program):
super().__init__(start_t, duration_t, nodes, program)
self.start_coords = start_coords
self.end_coords = end_coords
self.delta_coords = [end_coords[0] - start_coords[0], end_coords[1] - start_coords[1], end_coords[2] - start_coords[2]]
def start(self, node):
node.start_location = self.start_coords
def progress(self, prc, node):
l = []
l.append(self.start_coords[0] + (self.delta_coords[0] * prc))
l.append(self.start_coords[1] + (self.delta_coords[1] * prc))
l.append(self.start_coords[2] + (self.delta_coords[2] * prc))
node.start_location = tuple(l)

27
core/nodes/sawtooth.py Normal file
View file

@ -0,0 +1,27 @@
import math
from ..soundnode import SoundNode
class SawtoothNode(SoundNode):
def __init__(self, freq, room):
super().__init__("saw", room)
self.freq = freq
self.harmonics_q = 50
self.active = False
self.volume = 0.5
def calc_r_amps(self, t):
if not self.active:
self.r_amps[t] = dict()
return
if not t in self.r_amps:
self.r_amps[t] = dict()
for i in range(0, self.harmonics_q):
freq = self.freq*(i + 1)
self.r_amps[t][freq] = self.volume / float(i+1)

View file

@ -1,14 +1,13 @@
import math
from ..soundnode import SoundNode
from ..actions import TimedAction
class SineNode(SoundNode):
def __init__(self, freqs, room):
def __init__(self, freq, room):
super().__init__("sine", room)
self.freqs = freqs
self.freq = freq
self.active = False
self.volume = 1
@ -17,44 +16,5 @@ class SineNode(SoundNode):
if not self.active:
self.r_amps[t] = dict()
return
tdct = dict()
for freq in self.freqs:
tdct[freq] = self.volume * math.sin(self.room.sine_multiplier * freq * t)
self.r_amps[t] = tdct
###
class PlayAction(TimedAction):
def __init__(self, start_t, duration_t, nodes, program):
super().__init__(start_t, duration_t, nodes, program)
def start(self, node):
node.active = True
def end(self, node):
node.active = False
class NoteAction(TimedAction):
def __init__(self, note, start_t, duration_t, nodes, program):
super().__init__(start_t, duration_t, nodes, program)
self.note = note
def start(self, node):
note_freq = self.program.note_to_freq(self.note)
node.freqs = [note_freq]
node.active = True
def end(self, node):
node.active = False
self.r_amps[t] = {self.freq: self.volume}

View file

@ -26,4 +26,7 @@ class SinkNode(SoundNode):
res[freq] = vol
else:
res[freq] += vol
self.r_amps[t] = res
self.r_amps[t] = res
#if res:
# print(self.r_amps[t])

33
core/nodes/triangle.py Normal file
View file

@ -0,0 +1,33 @@
import math
from ..soundnode import SoundNode
class TriangleNode(SoundNode):
def __init__(self, freq, room):
super().__init__("trg", room)
self.freq = freq
self.harmonics_q = 30
self.active = False
self.volume = 0.8
def calc_r_amps(self, t):
if not self.active:
self.r_amps[t] = dict()
return
if not t in self.r_amps:
self.r_amps[t] = dict()
for i in range(0, self.harmonics_q):
freq = self.freq*(i*2 + 1)
if i%2 == 0:
m = 1
else:
m = -1
self.r_amps[t][freq] = m * self.volume / float((i*2 + 1)**2)

View file

@ -1,8 +1,14 @@
import os
import wave
import math
from .room import Room
TAU = 2 * math.pi
def stb(i: int, bytelen: int) -> bytes:
return i.to_bytes(bytelen, byteorder='little', signed=True)
class Program:
@ -66,13 +72,75 @@ class Program:
start_t = int(start_t_sec * self.room.sample_rate)
end_t = int(end_t_sec * self.room.sample_rate)
order_of_passage = []
for t in range(start_t, end_t):
if t%1000 == 0:
print(f'{int(t / float(end_t - start_t) * 100)}%', end='\r')
self.tick(t)
left_frame, right_frame = self.room.generate_frame(t)
for node in self.room.nodes:
node.tick_done = False
updates = True
if not order_of_passage:
while updates:
updates = False
for node in self.room.nodes:
if not (t in node.r_amps):
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.calc_r_amps(t)
node.tick_done = True
updates = True
order_of_passage.append(node)
else:
for node in order_of_passage:
node.calc_r_amps(t)
left_total_amp = self.room.max_amp * sum([vol * math.sin(self.room.sine_multiplier * freq * t) for freq, vol in self.room.left_sink.r_amps[t].items()])
right_total_amp = self.room.max_amp * sum([vol * math.sin(self.room.sine_multiplier * freq * t) for freq, vol in self.room.right_sink.r_amps[t].items()])
if left_total_amp > self.room.max_amp:
left_total_amp = self.room.max_amp
if left_total_amp < self.room.min_amp:
left_total_amp = self.room.min_amp
if right_total_amp > self.room.max_amp:
right_total_amp = self.room.max_amp
if right_total_amp < self.room.min_amp:
right_total_amp = self.room.min_amp
left_frame, right_frame = stb(int(left_total_amp), self.room.sample_width), stb(int(right_total_amp), self.room.sample_width)
frames.append(left_frame)
frames.append(right_frame)
print('100%', end='\r')
return frames
@ -105,11 +173,13 @@ class Program:
cmd = ''
while cmd != 'q':
cmd = input('\n[vol {self.volume*100}%]>> ')
cmd = input(f'\n[vol {self.volume*100}%]>> ')
if cmd.startswith('g '):
import cProfile
end_sec = float(cmd[2:])
self.export(0, end_sec)
cProfile.runctx("self.export(0, end_sec)", globals(), locals())
#self.export(0, end_sec)
if cmd.startswith('vol '):
vol = float(cmd[4:])

View file

@ -42,40 +42,46 @@ class Room:
for node in self.nodes:
node.tick_done = False
if t%1000 == 0:
print(t, end='\r', flush=True)
updates = True
order_of_passage = []
while updates:
if not order_of_passage:
updates = False
for node in self.nodes:
while updates:
if not (t in node.r_amps):
updates = False
for node in self.nodes:
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 (t in node.r_amps):
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:
node.calc_r_amps(t)
node.tick_done = True
updates = True
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.calc_r_amps(t)
node.tick_done = True
updates = True
order_of_passage.append(node)
else:
for node in order_of_passage:
node.calc_r_amps(t)
left_total_amp = self.max_amp * sum([vol for freq, vol in self.left_sink.r_amps[t].items()])
right_total_amp = self.max_amp * sum([vol for freq, vol in self.right_sink.r_amps[t].items()])
left_total_amp = self.max_amp * sum([vol * math.sin(self.sine_multiplier * freq * t) for freq, vol in self.left_sink.r_amps[t].items()])
right_total_amp = self.max_amp * sum([vol * math.sin(self.sine_multiplier * freq * t) for freq, vol in self.right_sink.r_amps[t].items()])
if left_total_amp > self.max_amp:
left_total_amp = self.max_amp

View file

@ -24,7 +24,10 @@ class SoundNode:
loc = self.location(t)
other_loc = other_node.location(t)
diff_x = loc[0]-other_loc[0]
diff_y = loc[1]-other_loc[1]
diff_z = loc[2]-other_loc[2]
return diff_x*diff_x + diff_y*diff_y + diff_z * diff_z
return (loc[0]-other_loc[0])**2 + (loc[1]-other_loc[1])**2 + (loc[2]-other_loc[2])**2
def sample_r_amps_by_wire(self, source_node, current_t):
@ -40,7 +43,9 @@ class SoundNode:
sample_t = current_t - int(dist / self.room.speed_of_sound)
if sample_t in source_node.r_amps:
return {f: a / float(dist) for f, a in source_node.r_amps[sample_t].items()}
attenuation = math.exp(-(dist/100.0))
return {f: a * attenuation for f, a in source_node.r_amps[sample_t].items()}
return dict()

38
test.py
View file

@ -4,6 +4,9 @@ from core.room import Room
from core.program import Program
from core.soundnode import SoundNode
from core.nodes.sinenode import *
from core.nodes.triangle import *
from core.nodes.sawtooth import *
from core.actions.basics import *
class TestProgram(Program):
@ -12,31 +15,30 @@ class TestProgram(Program):
super().__init__("testprogram")
def setup(self):
self.reset()
sn = SineNode([], self.room)
sn = SineNode(440, self.room)
sn2 = SawtoothNode(440, self.room)
sn.air_to(self.room.left_sink)
#sn.air_to(self.room.right_sink)
NoteAction('A4', self.st(0), self.st(0.5), [sn], self)
NoteAction('G4', self.st(1), self.st(0.5), [sn], self)
NoteAction('F4', self.st(2), self.st(0.5), [sn], self)
NoteAction('E4', self.st(3), self.st(0.5), [sn], self)
sn2 = SineNode([], self.room)
#sn2.air_to(self.room.left_sink)
sn.air_to(self.room.right_sink)
sn2.air_to(self.room.left_sink)
sn2.air_to(self.room.right_sink)
NoteAction('A3', self.st(0), self.st(0.5), [sn2], self)
NoteAction('G3', self.st(1), self.st(0.5), [sn2], self)
NoteAction('F3', self.st(2), self.st(0.5), [sn2], self)
NoteAction('E3', self.st(3), self.st(0.5), [sn2], self)
sn.start_location = (4,0,0)
sn2.start_location = (-4,0,0)
"""
NoteAction('A4', self.st(0), self.st(0.5), [sn, tn], self)
NoteAction('G4', self.st(1), self.st(0.5), [sn, tn], self)
NoteAction('F4', self.st(2), self.st(0.5), [sn, tn], self)
NoteAction('E4', self.st(3), self.st(0.5), [sn, tn], self)
"""
LinearPitchTransition('A4', 'E4', self.st(0), self.st(5), [sn], self)
LinearPitchTransition('E4', 'A4', self.st(0), self.st(5), [sn2], self)
LinearSpatialTransition((-8,0,0),(8,0,0), self.st(0), self.st(5), [sn], self)
TP = TestProgram()
TP.setup()
TP.interface()