Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extra BS2076-2 changes #71

Merged
merged 17 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions ear/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from attr import attrs, attrib
from attr.validators import instance_of
from math import isfinite
import numpy as np


Expand Down Expand Up @@ -35,6 +36,20 @@ def f(inst, attr, value):
actual=item.__class__, item=item),
attr, type, item,
)

return f


def finite_float():
"""Attrs validator that checks for finite floats (i.e. not +-inf or NaN)."""
validate_float = instance_of(float)

def f(inst, attr, value):
validate_float(inst, attr, value)

if not isfinite(value):
raise ValueError(f"'{attr.name}' must be finite, but {value} is not")

return f


Expand Down Expand Up @@ -217,9 +232,10 @@ class CartesianScreen(object):
centrePosition (CartesianPosition): screenCentrePosition element
widthX (float): screenWidth X attribute
"""
aspectRatio = attrib(validator=instance_of(float))

aspectRatio = attrib(validator=finite_float())
centrePosition = attrib(validator=instance_of(CartesianPosition))
widthX = attrib(validator=instance_of(float))
widthX = attrib(validator=finite_float())


@attrs(slots=True, frozen=True)
Expand All @@ -234,9 +250,10 @@ class PolarScreen(object):
centrePosition (PolarPosition): screenCentrePosition element
widthX (float): screenWidth azimuth attribute
"""
aspectRatio = attrib(validator=instance_of(float))

aspectRatio = attrib(validator=finite_float())
centrePosition = attrib(validator=instance_of(PolarPosition))
widthAzimuth = attrib(validator=instance_of(float))
widthAzimuth = attrib(validator=finite_float())


default_screen = PolarScreen(aspectRatio=1.78,
Expand Down
95 changes: 72 additions & 23 deletions ear/core/importance.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .metadata_input import MetadataSource, HOARenderingItem, ObjectRenderingItem
from .metadata_input import MetadataSource, HOARenderingItem
from attr import evolve


def filter_by_importance(rendering_items,
Expand All @@ -17,6 +18,7 @@ def filter_by_importance(rendering_items,
Yields: RenderingItem
"""
f = mute_audioBlockFormat_by_importance(rendering_items, threshold)
f = mute_hoa_channels_by_importance(f, threshold)
f = filter_audioObject_by_importance(f, threshold)
f = filter_audioPackFormat_by_importance(f, threshold)
return f
Expand Down Expand Up @@ -67,42 +69,89 @@ def filter_audioPackFormat_by_importance(rendering_items, threshold):
yield item


class MetadataSourceImportanceFilter(MetadataSource):
"""A Metadata source adapter to change block formats if their importance is below a given threshold.
class MetadataSourceMap(MetadataSource):
"""A metadata source which yields the blocks from input_source after
applying callback to them."""

The intended result of "muting" the rendering item during this block format
is emulated by setting its gain to zero and disabling any interpolation by
activating the jumpPosition flag.

Note: This MetadataSource can only be used for MetadataSources that
generate `ObjectTypeMetadata`.
"""
def __init__(self, adapted_source, threshold):
super(MetadataSourceImportanceFilter, self).__init__()
self._adapted = adapted_source
self._threshold = threshold
def __init__(self, input_source, callback):
super(MetadataSourceMap, self).__init__()
self._input_source = input_source
self._callback = callback

def get_next_block(self):
block = self._adapted.get_next_block()
block = self._input_source.get_next_block()
if block is None:
return None
if block.block_format.importance < self._threshold:
block.block_format.gain = 0
return block
return self._callback(block)


def mute_audioBlockFormat_by_importance(rendering_items, threshold):
"""Adapt rendering items of type `ObjectRenderingItem` to emulate block format importance handling
"""Adapt non-HOA rendering items to emulate block format importance handling

This installs an `MetadataSourceMap` which sets gains to 0 if the block
importance is less than the given threshold.

Parameters:
rendering_items (iterable of RenderingItems): RenderingItems to adapt
threshold (int): importance threshold

Yields: RenderingItem
"""

def mute_unimportant_block(type_metadata):
if type_metadata.block_format.importance < threshold:
return evolve(
type_metadata, block_format=evolve(type_metadata.block_format, gain=0.0)
)
else:
return type_metadata

for item in rendering_items:
if isinstance(item, HOARenderingItem):
yield item
else:
yield evolve(
item,
metadata_source=MetadataSourceMap(
item.metadata_source, mute_unimportant_block
),
)


This installs an `MetadataSourceImportanceFilter` with the given threshold
def mute_hoa_channels_by_importance(rendering_items, threshold):
"""Adapt HOA rendering items to emulate block format importance handling

This installs a `MetadataSourceMap` which sets the gain to zero if the
block importance is less than the given threshold. This operates
independently for each channel, so can reduce the HOA order (or make a mess
if the importances are not structured so that higher orders are discarded
first).

Parameters:
rendering_items (iterable of RenderingItems): RenderingItems to adapt
threshold (int): importance threshold

Yields: RenderingItem
"""
def mute_unimportant_channels(type_metadata):
if min(type_metadata.importances) < threshold:
new_gains = [
0.0 if importance < threshold else gain
for (gain, importance) in zip(
type_metadata.gains, type_metadata.importances
)
]
return evolve(type_metadata, gains=new_gains)
else:
return type_metadata

for item in rendering_items:
if isinstance(item, ObjectRenderingItem):
item.metadata_source = MetadataSourceImportanceFilter(adapted_source=item.metadata_source, threshold=threshold)
yield item
if isinstance(item, HOARenderingItem):
yield evolve(
item,
metadata_source=MetadataSourceMap(
item.metadata_source, mute_unimportant_channels
),
)
else:
yield item
22 changes: 16 additions & 6 deletions ear/core/metadata_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from attr.validators import instance_of, optional
from fractions import Fraction
from typing import Optional
from ..common import list_of, default_screen
from ..common import list_of, default_screen, finite_float
from ..fileio.adm.elements import (
AudioProgramme,
AudioContent,
Expand Down Expand Up @@ -76,9 +76,9 @@ class ExtraData(object):
object_duration = attrib(validator=optional(instance_of(Fraction)), default=None)
reference_screen = attrib(default=default_screen)
channel_frequency = attrib(validator=instance_of(Frequency), default=Factory(Frequency))
pack_absoluteDistance = attrib(validator=optional(instance_of(float)), default=None)
pack_absoluteDistance = attrib(validator=optional(finite_float()), default=None)

object_gain = attrib(validator=instance_of(float), default=1.0)
object_gain = attrib(validator=finite_float(), default=1.0)
object_mute = attrib(validator=instance_of(bool), default=False)
object_positionOffset = attrib(
validator=optional(instance_of(PositionOffset)), default=None
Expand Down Expand Up @@ -200,7 +200,7 @@ class GainTrackSpec(TrackSpec):
"""

input_track = attrib(validator=instance_of(TrackSpec))
gain = attrib(validator=instance_of(float))
gain = attrib(validator=finite_float())


#################################################
Expand Down Expand Up @@ -288,23 +288,29 @@ class HOATypeMetadata(TypeMetadata):
duration (fractions.Fraction or None): Duration of block.
extra_data (ExtraData): Info from object and channels for all channels.
gains (list of float): Gain for each input channel; defaults to 1.
importances (list of int): Importance for each input channel; defaults to 10.
"""
orders = attrib(validator=list_of(int))
degrees = attrib(validator=list_of(int))
normalization = attrib()
nfcRefDist = attrib(validator=optional(instance_of(float)), default=None)
nfcRefDist = attrib(validator=optional(finite_float()), default=None)
screenRef = attrib(validator=instance_of(bool), default=False)
rtime = attrib(default=None, validator=optional(instance_of(Fraction)))
duration = attrib(default=None, validator=optional(instance_of(Fraction)))

extra_data = attrib(validator=instance_of(ExtraData), default=Factory(ExtraData))

gains = attrib(validator=list_of(float))
importances = attrib(validator=list_of(int))

@gains.default
def _(self):
return [1.0] * len(self.orders)

@importances.default
def _(self):
return [10] * len(self.orders)


@attrs(slots=True)
class HOARenderingItem(RenderingItem):
Expand All @@ -322,14 +328,18 @@ class HOARenderingItem(RenderingItem):
track_specs = attrib(validator=list_of(TrackSpec))
metadata_source = attrib(validator=instance_of(MetadataSource))

importances = attrib(validator=optional(list_of(ImportanceData)), default=None)
importances = attrib(validator=optional(list_of(ImportanceData)))
adm_paths = attrib(validator=optional(list_of(ADMPath)), repr=False, default=None)

@importances.validator
def importances_valid(self, attribute, value):
if value is not None and len(value) != len(self.track_specs):
raise ValueError("wrong number of ImportanceDatas provided")

@importances.default
def _(self):
return [ImportanceData() for i in range(len(self.track_specs))]

@adm_paths.validator
def adm_paths_valid(self, attribute, value):
if value is not None and len(value) != len(self.track_specs):
Expand Down
1 change: 1 addition & 0 deletions ear/core/select_items/hoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ def _get_block_format_attr(_audioPackFormat_path, audioChannelFormat, attr):
get_rtime = partial(_get_block_format_attr, attr="rtime")
get_duration = partial(_get_block_format_attr, attr="duration")
get_gain = partial(_get_block_format_attr, attr="gain")
get_importance = partial(_get_block_format_attr, attr="importance")
16 changes: 13 additions & 3 deletions ear/core/select_items/select_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def audioObject(self):
def _select_programme(state, audio_programme=None):
"""Select an audioProgramme to render.

If audio_programme_id is provided, use that to make the selection,
If audio_programme is provided, use that to make the selection,
otherwise select the only audioProgramme, or the one with the lowest id.

Parameters:
Expand Down Expand Up @@ -734,8 +734,17 @@ def _get_RenderingItems_DirectSpeakers(state):

def _get_RenderingItems_HOA(state):
"""Get a HOARenderingItem given an _ItemSelectionState."""
from .hoa import (get_nfcRefDist, get_screenRef, get_normalization,
get_order, get_degree, get_rtime, get_duration, get_gain)
from .hoa import (
get_nfcRefDist,
get_screenRef,
get_normalization,
get_order,
get_degree,
get_rtime,
get_duration,
get_gain,
get_importance,
)

states = list(_select_single_channel(state))

Expand All @@ -748,6 +757,7 @@ def _get_RenderingItems_HOA(state):
orders=get_per_channel_param(pack_paths_channels, get_order),
degrees=get_per_channel_param(pack_paths_channels, get_degree),
gains=get_per_channel_param(pack_paths_channels, get_gain),
importances=get_per_channel_param(pack_paths_channels, get_importance),
normalization=get_single_param(pack_paths_channels, "normalization", get_normalization),
nfcRefDist=get_single_param(pack_paths_channels, "nfcRefDist", get_nfcRefDist),
screenRef=get_single_param(pack_paths_channels, "screenRef", get_screenRef),
Expand Down
21 changes: 21 additions & 0 deletions ear/core/select_items/test/test_hoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,24 @@ def test_hoa_gains():
meta = item.metadata_source.get_next_block()

assert meta.gains == gains


def test_hoa_importances():
builder = HOABuilder()

importances = [1, 2, 3, 4]

for channel, importance in zip(builder.first_pack.audioChannelFormats, importances):
channel.audioBlockFormats[0].importance = importance

for i, track in enumerate(builder.first_tracks, 1):
builder.create_track_uid(
audioPackFormat=builder.first_pack, audioTrackFormat=track, trackIndex=i
)

generate_ids(builder.adm)

[item] = select_rendering_items(builder.adm)
meta = item.metadata_source.get_next_block()

assert meta.importances == importances
Loading
Loading