From faf0c4124938341ed41545c765e32055b6445c80 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 20 Sep 2020 14:18:21 -0700 Subject: [PATCH] Refactoring objectmix Object mix classes in new file --- sound_objects/intern/generate_adm.py | 158 +++------------------------ sound_objects/intern/object_mix.py | 138 +++++++++++++++++++++++ 2 files changed, 153 insertions(+), 143 deletions(-) create mode 100644 sound_objects/intern/object_mix.py diff --git a/sound_objects/intern/generate_adm.py b/sound_objects/intern/generate_adm.py index 8ff17f3..e97f483 100644 --- a/sound_objects/intern/generate_adm.py +++ b/sound_objects/intern/generate_adm.py @@ -1,5 +1,4 @@ import bpy -import os from contextlib import contextmanager @@ -16,23 +15,20 @@ from typing import List from ear.fileio.utils import openBw64 -from ear.fileio.bw64 import Bw64Reader from ear.fileio.bw64.chunks import (FormatInfoChunk, ChnaChunk) from ear.fileio.adm import chna as adm_chna from ear.fileio.adm.xml import adm_to_xml -from ear.fileio.adm.elements.block_formats import (AudioBlockFormatObjects, JumpPosition) -from ear.fileio.adm.elements.geom import ObjectCartesianPosition from ear.fileio.adm.builder import (ADMBuilder) from ear.fileio.adm.generate_ids import generate_ids -from sound_objects.intern.geom_utils import (compute_relative_vector, - room_norm_vector, - speaker_active_time_range, +from sound_objects.intern.geom_utils import (speaker_active_time_range, speakers_by_min_distance, speakers_by_start_time) -from sound_objects.intern.speaker_utils import (all_speakers, solo_speakers, unmute_all_speakers) +from sound_objects.intern.object_mix import (ObjectMix, ObjectMixPool) + +from sound_objects.intern.speaker_utils import (all_speakers) @contextmanager @@ -54,123 +50,6 @@ def adm_object_rendering_context(scene: bpy.types.Scene): scene.render.ffmpeg.audio_channels = old_chans -class ObjectMix: - def __init__(self, sources: List[bpy.types.Speaker], - scene: bpy.types.Scene, base_dir: str): - self.sources = sources - self.intermediate_filename = None - self.base_dir = base_dir - self.scene = scene - self._mixdown_file_handle = None - self._mixdown_reader = None - - @property - def frame_start(self): - return self.scene.frame_start - - @property - def frame_end(self): - return self.scene.frame_end - - @property - def mixdown_reader(self) -> Bw64Reader: - if self._mixdown_reader is None: - self._mixdown_reader = Bw64Reader(self.mixdown_file_handle) - - return self._mixdown_reader - - @property - def mixdown_file_handle(self): - if self._mixdown_file_handle is None: - self._mixdown_file_handle = open(self.mixdown_filename, 'rb') - - return self._mixdown_file_handle - - @property - def mixdown_filename(self): - if self.intermediate_filename is None: - self.mixdown() - - return self.intermediate_filename - - @property - def object_name(self): - return self.sources[0].name - - def mixdown(self): - with adm_object_rendering_context(self.scene) as scene: - solo_speakers(scene, self.sources) - - scene_name = bpy.path.clean_name(scene.name) - speaker_name = bpy.path.clean_name(self.object_name) - - self.intermediate_filename = os.path.join(self.base_dir, "%s_%s.wav" % (scene_name, speaker_name)) - - bpy.ops.sound.mixdown(filepath=self.intermediate_filename, - container='WAV', codec='PCM', format='S24') - - print("Created mixdown named {}".format(self.intermediate_filename)) - - unmute_all_speakers(scene) - - def adm_block_formats(self, room_size=1.): - fps = self.scene.render.fps - block_formats = [] - - for speaker_obj in self.sources: - speaker_interval = speaker_active_time_range(speaker_obj) - for frame in range(speaker_interval.start_frame, speaker_interval.end_frame + 1): - self.scene.frame_set(frame) - relative_vector = compute_relative_vector(camera=self.scene.camera, target=speaker_obj) - - norm_vec = room_norm_vector(relative_vector, room_size=room_size) - - pos = ObjectCartesianPosition(X=norm_vec.x, Y=norm_vec.y, Z=norm_vec.z) - - if len(block_formats) == 0 or pos != block_formats[-1].position: - jp = JumpPosition(flag=True, interpolationLength=Fraction(1, fps * 2)) - block = AudioBlockFormatObjects(position=pos, - rtime=Fraction(frame, fps), - duration=Fraction(1, fps), - cartesian=True, - jumpPosition=jp) - - block_formats.append(block) - else: - block_formats[-1].duration = block_formats[-1].duration + Fraction(1, fps) - - return block_formats - - def rm_mixdown(self): - if self._mixdown_reader is not None: - self._mixdown_reader = None - - if self._mixdown_file_handle is not None: - self._mixdown_file_handle.close() - self._mixdown_file_handle = None - - os.remove(self.intermediate_filename) - self.intermediate_filename = None - - -@contextmanager -class ObjectMixPool: - def __init__(self, object_mixes: List[ObjectMix]): - self.object_mixes = object_mixes - - def __enter__(self): - return self - - @property - def shortest_file_length(self): - lengths = map(lambda f: len(f.mixdown_reader)) - return min(lengths) - - def __exit__(self, exc_type, exc_val, exc_tb): - for mix in self.object_mixes: - mix.rm_mixdown() - - def group_speakers(speakers, scene) -> List[List[bpy.types.Object]]: def list_can_accept_speaker(speaker_list, speaker_to_test): """ @@ -291,27 +170,22 @@ def write_muxed_wav(mix_pool: ObjectMixPool, scene, out_format, room_size, outfi cursor = cursor + to_read -def mux_adm_from_object_mixdowns(scene, sound_objects: List['ObjectMix'], output_filename, room_size=1.): - """ - mixdowns are a tuple of wave filename, and corresponding speaker object - """ +def mux_adm_from_object_mix_pool(scene, mix_pool: ObjectMixPool, output_filename, room_size=1.): - object_count = len(sound_objects) + object_count = len(mix_pool.object_mixes) assert object_count > 0 out_format = FormatInfoChunk(channelCount=object_count, sampleRate=scene.render.ffmpeg.audio_mixrate, bitsPerSample=24) - with ObjectMixPool(sound_objects) as mix_pool: - with openBw64(output_filename, 'w', formatInfo=out_format) as outfile: - write_muxed_wav(mix_pool, scene, out_format, room_size, - outfile, mix_pool.shortest_file_length) + with openBw64(output_filename, 'w', formatInfo=out_format) as outfile: + write_muxed_wav(mix_pool, scene, out_format, room_size, + outfile, mix_pool.shortest_file_length) def partition_sounds_to_objects(scene, max_objects): - sound_sources = all_speakers(scene) if len(sound_sources) == 0: @@ -334,7 +208,7 @@ def partition_sounds_to_objects(scene, max_objects): return object_groups, too_far_speakers -def generate_adm(context, filepath, room_size, max_objects): +def generate_adm(context: bpy.types.Context, filepath: str, room_size: float, max_objects: int): scene = context.scene object_groups, _ = partition_sounds_to_objects(scene, max_objects) @@ -342,13 +216,11 @@ def generate_adm(context, filepath, room_size, max_objects): if len(object_groups) == 0: return {'FINISHED'} - sound_objects = map(lambda objects: ObjectMix(sources=objects)) + with ObjectMixPool.pool_from_source_groups(object_groups) as pool: + mux_adm_from_object_mix_pool(scene, mix_pool=pool, + output_filename=filepath, + room_size=room_size) - mux_adm_from_object_mixdowns(scene, list(sound_objects), - output_filename=filepath, - room_size=room_size) - - for o in sound_objects: - o.rm_mixdown() + print("generate_adm exiting") return {'FINISHED'} diff --git a/sound_objects/intern/object_mix.py b/sound_objects/intern/object_mix.py new file mode 100644 index 0000000..042763d --- /dev/null +++ b/sound_objects/intern/object_mix.py @@ -0,0 +1,138 @@ +import os +import bpy +from contextlib import contextmanager +from fractions import Fraction +from typing import List + +from ear.fileio.adm.elements import ObjectCartesianPosition, JumpPosition, AudioBlockFormatObjects +from ear.fileio.bw64 import Bw64Reader + +from sound_objects.intern.generate_adm import adm_object_rendering_context +from sound_objects.intern.geom_utils import speaker_active_time_range, compute_relative_vector, room_norm_vector +from sound_objects.intern.speaker_utils import solo_speakers, unmute_all_speakers + + +class ObjectMix: + def __init__(self, sources: List[bpy.types.Speaker], + scene: bpy.types.Scene, base_dir: str): + self.sources = sources + self.intermediate_filename = None + self.base_dir = base_dir + self.scene = scene + self._mixdown_file_handle = None + self._mixdown_reader = None + + @property + def frame_start(self): + return self.scene.frame_start + + @property + def frame_end(self): + return self.scene.frame_end + + @property + def mixdown_reader(self) -> Bw64Reader: + if self._mixdown_reader is None: + self._mixdown_reader = Bw64Reader(self.mixdown_file_handle) + + return self._mixdown_reader + + @property + def mixdown_file_handle(self): + if self._mixdown_file_handle is None: + self._mixdown_file_handle = open(self.mixdown_filename, 'rb') + + return self._mixdown_file_handle + + @property + def mixdown_filename(self): + if self.intermediate_filename is None: + self.mixdown() + + return self.intermediate_filename + + @property + def object_name(self): + return self.sources[0].name + + def mixdown(self): + with adm_object_rendering_context(self.scene) as scene: + solo_speakers(scene, self.sources) + + scene_name = bpy.path.clean_name(scene.name) + speaker_name = bpy.path.clean_name(self.object_name) + + self.intermediate_filename = os.path.join(self.base_dir, "%s_%s.wav" % (scene_name, speaker_name)) + + bpy.ops.sound.mixdown(filepath=self.intermediate_filename, + container='WAV', codec='PCM', format='S24') + + print("Created mixdown named {}".format(self.intermediate_filename)) + + unmute_all_speakers(scene) + + def adm_block_formats(self, room_size=1.): + fps = self.scene.render.fps + block_formats = [] + + for speaker_obj in self.sources: + speaker_interval = speaker_active_time_range(speaker_obj) + for frame in range(speaker_interval.start_frame, speaker_interval.end_frame + 1): + self.scene.frame_set(frame) + relative_vector = compute_relative_vector(camera=self.scene.camera, target=speaker_obj) + + norm_vec = room_norm_vector(relative_vector, room_size=room_size) + + pos = ObjectCartesianPosition(X=norm_vec.x, Y=norm_vec.y, Z=norm_vec.z) + + if len(block_formats) == 0 or pos != block_formats[-1].position: + jp = JumpPosition(flag=True, interpolationLength=Fraction(1, fps * 2)) + block = AudioBlockFormatObjects(position=pos, + rtime=Fraction(frame, fps), + duration=Fraction(1, fps), + cartesian=True, + jumpPosition=jp) + + block_formats.append(block) + else: + block_formats[-1].duration = block_formats[-1].duration + Fraction(1, fps) + + return block_formats + + def rm_mixdown(self): + if self._mixdown_reader is not None: + self._mixdown_reader = None + + if self._mixdown_file_handle is not None: + self._mixdown_file_handle.close() + self._mixdown_file_handle = None + + os.remove(self.intermediate_filename) + self.intermediate_filename = None + + +@contextmanager +class ObjectMixPool: + + @classmethod + def pool_from_source_groups(cls, groups: List[List[bpy.types.Speaker]]): + mixes = [] + for group in groups: + mixes.append(ObjectMix(sources=group)) + + return ObjectMixPool(object_mixes=mixes) + + def __init__(self, object_mixes: List[ObjectMix]): + self.object_mixes = object_mixes + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + for mix in self.object_mixes: + mix.rm_mixdown() + + @property + def shortest_file_length(self): + lengths = map(lambda f: len(f.mixdown_reader)) + return min(lengths)