diff --git a/core/room.py b/core/room.py index 68b4dfb..a916c6c 100644 --- a/core/room.py +++ b/core/room.py @@ -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)) \ No newline at end of file + wav.writeframes(b''.join(frames)) + """ \ No newline at end of file diff --git a/core/soundnode.py b/core/soundnode.py index d80326a..6b2b44d 100644 --- a/core/soundnode.py +++ b/core/soundnode.py @@ -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) + """ \ No newline at end of file diff --git a/test.py b/test.py index b653244..922428f 100644 --- a/test.py +++ b/test.py @@ -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) \ No newline at end of file + +R.record('test6.wav', 0, 2) \ No newline at end of file