enhancements
This commit is contained in:
parent
35e8a2c448
commit
0a3bc7ab20
10 changed files with 281 additions and 93 deletions
|
|
@ -26,7 +26,7 @@ class TimedAction:
|
||||||
self.end(node)
|
self.end(node)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.progress(t - self.start_t, node)
|
self.progress((t - self.start_t) / float(self.duration_t), node)
|
||||||
|
|
||||||
# REIMPLEMENT
|
# REIMPLEMENT
|
||||||
|
|
||||||
|
|
@ -38,6 +38,6 @@ class TimedAction:
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def progress(self, rel_t, node):
|
def progress(self, prc, node):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
82
core/actions/basics.py
Normal file
82
core/actions/basics.py
Normal 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
27
core/nodes/sawtooth.py
Normal 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)
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from ..soundnode import SoundNode
|
from ..soundnode import SoundNode
|
||||||
from ..actions import TimedAction
|
|
||||||
|
|
||||||
class SineNode(SoundNode):
|
class SineNode(SoundNode):
|
||||||
|
|
||||||
def __init__(self, freqs, room):
|
def __init__(self, freq, room):
|
||||||
|
|
||||||
super().__init__("sine", room)
|
super().__init__("sine", room)
|
||||||
self.freqs = freqs
|
self.freq = freq
|
||||||
self.active = False
|
self.active = False
|
||||||
self.volume = 1
|
self.volume = 1
|
||||||
|
|
||||||
|
|
@ -17,44 +16,5 @@ class SineNode(SoundNode):
|
||||||
if not self.active:
|
if not self.active:
|
||||||
self.r_amps[t] = dict()
|
self.r_amps[t] = dict()
|
||||||
return
|
return
|
||||||
|
|
||||||
tdct = dict()
|
|
||||||
|
|
||||||
for freq in self.freqs:
|
self.r_amps[t] = {self.freq: self.volume}
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,7 @@ class SinkNode(SoundNode):
|
||||||
res[freq] = vol
|
res[freq] = vol
|
||||||
else:
|
else:
|
||||||
res[freq] += vol
|
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
33
core/nodes/triangle.py
Normal 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)
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
import os
|
import os
|
||||||
import wave
|
import wave
|
||||||
|
import math
|
||||||
|
|
||||||
from .room import Room
|
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:
|
class Program:
|
||||||
|
|
||||||
|
|
@ -66,13 +72,75 @@ class Program:
|
||||||
start_t = int(start_t_sec * self.room.sample_rate)
|
start_t = int(start_t_sec * self.room.sample_rate)
|
||||||
end_t = int(end_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):
|
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)
|
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(left_frame)
|
||||||
frames.append(right_frame)
|
frames.append(right_frame)
|
||||||
|
print('100%', end='\r')
|
||||||
|
|
||||||
return frames
|
return frames
|
||||||
|
|
||||||
|
|
@ -105,11 +173,13 @@ class Program:
|
||||||
|
|
||||||
cmd = ''
|
cmd = ''
|
||||||
while cmd != 'q':
|
while cmd != 'q':
|
||||||
cmd = input('\n[vol {self.volume*100}%]>> ')
|
cmd = input(f'\n[vol {self.volume*100}%]>> ')
|
||||||
|
|
||||||
if cmd.startswith('g '):
|
if cmd.startswith('g '):
|
||||||
|
import cProfile
|
||||||
end_sec = float(cmd[2:])
|
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 '):
|
if cmd.startswith('vol '):
|
||||||
vol = float(cmd[4:])
|
vol = float(cmd[4:])
|
||||||
|
|
|
||||||
54
core/room.py
54
core/room.py
|
|
@ -42,40 +42,46 @@ class Room:
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
node.tick_done = False
|
node.tick_done = False
|
||||||
|
|
||||||
if t%1000 == 0:
|
|
||||||
print(t, end='\r', flush=True)
|
|
||||||
|
|
||||||
updates = True
|
updates = True
|
||||||
|
order_of_passage = []
|
||||||
|
|
||||||
while updates:
|
if not order_of_passage:
|
||||||
|
|
||||||
updates = False
|
while updates:
|
||||||
|
|
||||||
for node in self.nodes:
|
|
||||||
|
|
||||||
if not (t in node.r_amps):
|
updates = False
|
||||||
|
|
||||||
|
for node in self.nodes:
|
||||||
|
|
||||||
has_unprocessed_inputs = False
|
if not (t in node.r_amps):
|
||||||
|
|
||||||
for in_node in node.air_in:
|
has_unprocessed_inputs = False
|
||||||
if not in_node.tick_done:
|
|
||||||
has_unprocessed_inputs = True
|
for in_node in node.air_in:
|
||||||
break
|
|
||||||
|
|
||||||
if not has_unprocessed_inputs:
|
|
||||||
for in_node in node.wire_in:
|
|
||||||
if not in_node.tick_done:
|
if not in_node.tick_done:
|
||||||
has_unprocessed_inputs = True
|
has_unprocessed_inputs = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not has_unprocessed_inputs:
|
|
||||||
|
|
||||||
node.calc_r_amps(t)
|
if not has_unprocessed_inputs:
|
||||||
node.tick_done = True
|
for in_node in node.wire_in:
|
||||||
updates = True
|
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()])
|
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 for freq, vol in self.right_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:
|
if left_total_amp > self.max_amp:
|
||||||
left_total_amp = self.max_amp
|
left_total_amp = self.max_amp
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,10 @@ class SoundNode:
|
||||||
|
|
||||||
loc = self.location(t)
|
loc = self.location(t)
|
||||||
other_loc = other_node.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
|
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):
|
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)
|
sample_t = current_t - int(dist / self.room.speed_of_sound)
|
||||||
|
|
||||||
if sample_t in source_node.r_amps:
|
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()
|
return dict()
|
||||||
|
|
||||||
|
|
|
||||||
38
test.py
38
test.py
|
|
@ -4,6 +4,9 @@ from core.room import Room
|
||||||
from core.program import Program
|
from core.program import Program
|
||||||
from core.soundnode import SoundNode
|
from core.soundnode import SoundNode
|
||||||
from core.nodes.sinenode import *
|
from core.nodes.sinenode import *
|
||||||
|
from core.nodes.triangle import *
|
||||||
|
from core.nodes.sawtooth import *
|
||||||
|
from core.actions.basics import *
|
||||||
|
|
||||||
class TestProgram(Program):
|
class TestProgram(Program):
|
||||||
|
|
||||||
|
|
@ -12,31 +15,30 @@ class TestProgram(Program):
|
||||||
super().__init__("testprogram")
|
super().__init__("testprogram")
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
|
||||||
self.reset()
|
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.left_sink)
|
||||||
#sn.air_to(self.room.right_sink)
|
sn.air_to(self.room.right_sink)
|
||||||
|
sn2.air_to(self.room.left_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)
|
|
||||||
sn2.air_to(self.room.right_sink)
|
sn2.air_to(self.room.right_sink)
|
||||||
|
|
||||||
NoteAction('A3', self.st(0), self.st(0.5), [sn2], self)
|
sn.start_location = (4,0,0)
|
||||||
NoteAction('G3', self.st(1), self.st(0.5), [sn2], self)
|
sn2.start_location = (-4,0,0)
|
||||||
NoteAction('F3', self.st(2), self.st(0.5), [sn2], self)
|
|
||||||
NoteAction('E3', self.st(3), self.st(0.5), [sn2], self)
|
"""
|
||||||
|
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 = TestProgram()
|
||||||
TP.setup()
|
TP.setup()
|
||||||
TP.interface()
|
TP.interface()
|
||||||
Loading…
Add table
Reference in a new issue