Refactoring objectmix

Object mix classes in new file
This commit is contained in:
Jamie Hardt
2020-09-20 14:18:21 -07:00
parent b4b3f071ae
commit faf0c41249
2 changed files with 153 additions and 143 deletions

View File

@@ -1,5 +1,4 @@
import bpy import bpy
import os
from contextlib import contextmanager from contextlib import contextmanager
@@ -16,23 +15,20 @@ from typing import List
from ear.fileio.utils import openBw64 from ear.fileio.utils import openBw64
from ear.fileio.bw64 import Bw64Reader
from ear.fileio.bw64.chunks import (FormatInfoChunk, ChnaChunk) from ear.fileio.bw64.chunks import (FormatInfoChunk, ChnaChunk)
from ear.fileio.adm import chna as adm_chna from ear.fileio.adm import chna as adm_chna
from ear.fileio.adm.xml import adm_to_xml 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.builder import (ADMBuilder)
from ear.fileio.adm.generate_ids import generate_ids from ear.fileio.adm.generate_ids import generate_ids
from sound_objects.intern.geom_utils import (compute_relative_vector, from sound_objects.intern.geom_utils import (speaker_active_time_range,
room_norm_vector,
speaker_active_time_range,
speakers_by_min_distance, speakers_by_min_distance,
speakers_by_start_time) 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 @contextmanager
@@ -54,123 +50,6 @@ def adm_object_rendering_context(scene: bpy.types.Scene):
scene.render.ffmpeg.audio_channels = old_chans 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 group_speakers(speakers, scene) -> List[List[bpy.types.Object]]:
def list_can_accept_speaker(speaker_list, speaker_to_test): 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 cursor = cursor + to_read
def mux_adm_from_object_mixdowns(scene, sound_objects: List['ObjectMix'], output_filename, room_size=1.): def mux_adm_from_object_mix_pool(scene, mix_pool: ObjectMixPool, output_filename, room_size=1.):
"""
mixdowns are a tuple of wave filename, and corresponding speaker object
"""
object_count = len(sound_objects) object_count = len(mix_pool.object_mixes)
assert object_count > 0 assert object_count > 0
out_format = FormatInfoChunk(channelCount=object_count, out_format = FormatInfoChunk(channelCount=object_count,
sampleRate=scene.render.ffmpeg.audio_mixrate, sampleRate=scene.render.ffmpeg.audio_mixrate,
bitsPerSample=24) bitsPerSample=24)
with ObjectMixPool(sound_objects) as mix_pool: with openBw64(output_filename, 'w', formatInfo=out_format) as outfile:
with openBw64(output_filename, 'w', formatInfo=out_format) as outfile: write_muxed_wav(mix_pool, scene, out_format, room_size,
write_muxed_wav(mix_pool, scene, out_format, room_size, outfile, mix_pool.shortest_file_length)
outfile, mix_pool.shortest_file_length)
def partition_sounds_to_objects(scene, max_objects): def partition_sounds_to_objects(scene, max_objects):
sound_sources = all_speakers(scene) sound_sources = all_speakers(scene)
if len(sound_sources) == 0: if len(sound_sources) == 0:
@@ -334,7 +208,7 @@ def partition_sounds_to_objects(scene, max_objects):
return object_groups, too_far_speakers 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 scene = context.scene
object_groups, _ = partition_sounds_to_objects(scene, max_objects) 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: if len(object_groups) == 0:
return {'FINISHED'} 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'} return {'FINISHED'}

View File

@@ -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)