Added basic voice limits, not amazing

This commit is contained in:
Jamie Hardt
2020-09-16 21:11:59 -07:00
parent bab7014aea
commit 008dacd28b

View File

@@ -77,6 +77,21 @@ def room_norm_vector(vec, room_size=1.):
return vec / chebyshev
def closest_approach_to_camera(scene, speaker_object):
max_dist = sys.float_info.max
at_time = scene.frame_start
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
rel = speaker_object.matrix_world.to_translation() - scene.camera.matrix_world.to_translation()
dist = norm(rel)
if dist < max_dist:
max_dist = dist
at_time = frame
return (max_dist, at_time)
def speaker_active_time_range(speaker):
"""
The time range this speaker must control in order to sound right.
@@ -95,6 +110,13 @@ def speaker_active_time_range(speaker):
return int(start), int(end)
def speakers_by_min_distance(scene, speakers):
def min_distance(speaker):
return closest_approach_to_camera(scene, speaker)[0]
return sorted(speakers, key=(lambda spk: min_distance(spk)))
def speakers_by_start_time(speaker_objs):
return sorted(speaker_objs, key=(lambda spk: speaker_active_time_range(spk)[0]))
@@ -128,18 +150,14 @@ def group_speakers(speaker_objs):
def adm_block_formats_for_speakers(scene, speaker_objs, room_size=1.):
block_formats = []
# frame_start = start_frame or scene.frame_start
# frame_end = end_frame or scene.frame_end
fps = scene.render.fps
block_formats = []
for speaker_obj in speakers_by_start_time(speaker_objs):
speaker_start, speaker_end = speaker_active_time_range(speaker_obj)
for frame in range(speaker_start, speaker_end + 1):
scene.frame_set(frame)
relative_vector = compute_relative_vector(camera=scene.camera,
object=speaker_obj)
relative_vector = compute_relative_vector(camera=scene.camera, target=speaker_obj)
norm_vec = room_norm_vector(relative_vector, room_size=room_size)
@@ -160,7 +178,21 @@ def adm_block_formats_for_speakers(scene, speaker_objs, room_size=1.):
return block_formats
def adm_data_for_scene(scene, speaker_objs_lists, wav_format, room_size):
def adm_for_object(scene, speakers_this_mixdown, room_size, b, i, frame_start, fps, frame_end, wav_format):
block_formats = adm_block_formats_for_speakers(scene=scene,
speaker_objs=speakers_this_mixdown,
room_size=room_size)
created = b.create_item_objects(track_index=i,
name=speakers_this_mixdown[0].name,
block_formats=block_formats)
created.audio_object.start = Fraction(frame_start, fps)
created.audio_object.duration = Fraction(frame_end - frame_start, fps)
created.track_uid.sampleRate = wav_format.sampleRate
created.track_uid.bitDepth = wav_format.bitsPerSample
def adm_for_scene(scene, speaker_groups, wav_format, room_size):
b = ADMBuilder()
@@ -174,17 +206,8 @@ def adm_data_for_scene(scene, speaker_objs_lists, wav_format, room_size):
b.create_content(audioContentName="Objects")
for i, speakers_this_mixdown in enumerate(speaker_objs_lists):
block_formats = adm_block_formats_for_speakers(scene, speakers_this_mixdown,
room_size=room_size)
created = b.create_item_objects(track_index=i,
name=speakers_this_mixdown[0].name,
block_formats=block_formats)
created.audio_object.start = Fraction(frame_start, fps)
created.audio_object.duration = Fraction(frame_end - frame_start, fps)
created.track_uid.sampleRate = wav_format.sampleRate
created.track_uid.bitDepth = wav_format.bitsPerSample
for i, speakers_this_mixdown in enumerate(speaker_groups):
adm_for_object(scene, speakers_this_mixdown, room_size, b, i, frame_start, fps, frame_end, wav_format)
adm = b.adm
@@ -195,6 +218,10 @@ def adm_data_for_scene(scene, speaker_objs_lists, wav_format, room_size):
return adm_to_xml(adm), chna
########################################################################
# File writing functions below
def bext_data(scene, speaker_obj, sample_rate, room_size):
description = "SCENE={};ROOM_SIZE={}\n".format(scene.name, room_size).encode("ascii")
originator_name = "Blender {}".format(bpy.app.version_string).encode("ascii")
@@ -212,17 +239,7 @@ def bext_data(scene, speaker_obj, sample_rate, room_size):
return data
def write_muxed_adm(scene, mixdowns, output_filename=None, room_size=1.):
"""
mixdowns are a tuple of wave filename, and corresponding speaker object
"""
object_count = len(mixdowns)
assert object_count > 0
READ_BLOCK=1024
out_file = output_filename or os.path.join(os.path.dirname(mixdowns[0][0]),
bpy.path.clean_name(scene.name) + ".wav")
def load_infiles_for_muxing(mixdowns):
infiles = []
shortest_file = 0xFFFFFFFFFFFF
for elem in mixdowns:
@@ -230,16 +247,22 @@ def write_muxed_adm(scene, mixdowns, output_filename=None, room_size=1.):
infiles.append(infile)
if len(infile) < shortest_file:
shortest_file = len(infile)
return infiles, shortest_file
out_format = FormatInfoChunk(channelCount=object_count,
sampleRate=infiles[0].sampleRate,
bitsPerSample=infiles[0].bitdepth)
def rm_object_mixes(mixdowns):
for elem in mixdowns:
os.unlink(elem[0])
with openBw64(out_file, 'w', formatInfo=out_format) as outfile:
speakers = list(map(lambda x: x[1], mixdowns))
adm, chna = adm_data_for_scene(scene, speakers, out_format, room_size=room_size)
def write_muxed_wav(mixdowns, scene, out_format, room_size, outfile, shortest_file, object_count, infiles):
READ_BLOCK=1024
speaker_groups = list(map(lambda x: x[1], mixdowns))
adm, chna = adm_for_scene(scene, speaker_groups, out_format, room_size=room_size)
outfile.axml = lxml.etree.tostring(adm, pretty_print=True)
outfile.chna = chna
outfile.bext = bext_data(scene, None, out_format.sampleRate, room_size=room_size)
@@ -258,11 +281,30 @@ def write_muxed_adm(scene, mixdowns, output_filename=None, room_size=1.):
outfile.write(buffer)
cursor = cursor + to_read
def mux_adm_from_object_mixdowns(scene, mixdowns_spk_list_tuple, output_filename=None, room_size=1.):
"""
mixdowns are a tuple of wave filename, and corresponding speaker object
"""
object_count = len(mixdowns_spk_list_tuple)
assert object_count > 0
infiles, shortest_file = load_infiles_for_muxing(mixdowns_spk_list_tuple)
out_file = output_filename or os.path.join(os.path.dirname(mixdowns_spk_list_tuple[0][0]),
bpy.path.clean_name(scene.name) + ".wav")
out_format = FormatInfoChunk(channelCount=object_count,
sampleRate=infiles[0].sampleRate,
bitsPerSample=infiles[0].bitdepth)
with openBw64(out_file, 'w', formatInfo=out_format) as outfile:
write_muxed_wav(mixdowns_spk_list_tuple, scene, out_format, room_size, outfile, shortest_file, object_count, infiles)
for infile in infiles:
infile._buffer.close()
for elem in mixdowns:
os.unlink(elem[0])
rm_object_mixes(mixdowns_spk_list_tuple)
def all_speakers(scene):
@@ -285,9 +327,7 @@ def unmute_all_speakers(scene):
speaker.data.update_tag()
def speaker_mixdowns(scene, filepath):
basedir = os.path.dirname(filepath)
for speaker_group in group_speakers(all_speakers(scene)):
def create_mixdown_for_object(scene, speaker_group, basedir):
solo_speakers(scene, speaker_group)
scene_name = bpy.path.clean_name(scene.name)
@@ -295,6 +335,14 @@ def speaker_mixdowns(scene, filepath):
fn = os.path.join(basedir, "%s_%s.wav" % (scene_name, speaker_name) )
bpy.ops.sound.mixdown(filepath=fn, container='WAV', codec='PCM', format='S24')
return fn
def generate_speaker_mixdowns(scene, speaker_groups, filepath):
basedir = os.path.dirname(filepath)
for speaker_group in speaker_groups:
fn = create_mixdown_for_object(scene, speaker_group, basedir)
yield (fn, speaker_group)
@@ -314,7 +362,7 @@ def restore_output_state(ctx, context):
context.scene.render.ffmpeg.audio_channels = ctx[2]
def write_some_data(context, filepath, room_size):
def write_some_data(context, filepath, room_size, max_objects):
ctx = save_output_state(context)
scene = bpy.context.scene
@@ -323,12 +371,35 @@ def write_some_data(context, filepath, room_size):
scene.render.ffmpeg.audio_codec = 'PCM'
scene.render.ffmpeg.audio_channels = 'MONO'
mixdowns = list(speaker_mixdowns(scene, filepath))
mixdown_count = len(mixdowns)
sound_sources = all_speakers(scene)
sorted_speakers = speakers_by_start_time(sound_sources)
object_groups = group_speakers(sorted_speakers)
closest_speakers = speakers_by_min_distance(scene, sound_sources)
too_far_speakers = []
n = len(closest_speakers) - 1
while len(object_groups) > max_objects:
sorted_speakers = speakers_by_start_time(closest_speakers[0:n])
too_far_speakers = closest_speakers[n:]
object_groups = group_speakers(sorted_speakers)
n = n - 1
print("Will create {} objects for {} sources, ignoring {} sources".format(
len(object_groups), len(sorted_speakers), len(too_far_speakers)))
mixdowns_spk_list_tuple = list(generate_speaker_mixdowns(scene, object_groups, filepath))
mixdown_count = len(mixdowns_spk_list_tuple)
if mixdown_count == 0:
return {'FINISHED'}
else:
write_muxed_adm(scene, mixdowns, output_filename= filepath, room_size=room_size)
mux_adm_from_object_mixdowns(scene, mixdowns_spk_list_tuple,
output_filename= filepath,
room_size=room_size)
#cleanup
unmute_all_speakers(scene)
@@ -344,7 +415,7 @@ def write_some_data(context, filepath, room_size):
# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty
from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty, IntProperty
from bpy.types import Operator
@@ -371,8 +442,16 @@ class ADMWaveExport(Operator, ExportHelper):
unit='LENGTH'
)
max_objects: IntProperty(
name="Max Objects",
description="Maximum number of objects to create",
default=24,
min=0,
max=118
)
def execute(self, context):
return write_some_data(context, self.filepath, self.room_size)
return write_some_data(context, self.filepath, self.room_size, self.max_objects)
# Only needed if you want to add into a dynamic menu