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

Add interaction elements and attributes to audioObject #17

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Binary file added .DS_Store
Binary file not shown.
Binary file added ear/.DS_Store
Binary file not shown.
Binary file added ear/core/.DS_Store
Binary file not shown.
Binary file added ear/fileio/.DS_Store
Binary file not shown.
Binary file added ear/fileio/adm/.DS_Store
Binary file not shown.
10 changes: 6 additions & 4 deletions ear/fileio/adm/elements/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# flake8: noqa
from .main_elements import (AudioChannelFormat, AudioPackFormat, AudioTrackFormat,
AudioStreamFormat, AudioProgramme, AudioContent, AudioObject, AudioTrackUID,
FormatDefinition, TypeDefinition, Frequency)
AudioStreamFormat, AudioProgramme, AudioContent,
AudioObject, AudioTrackUID, FormatDefinition, TypeDefinition,
Frequency)
from .block_formats import (AudioBlockFormatObjects, ChannelLock, ObjectDivergence,
JumpPosition, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, AudioBlockFormatHoa,
AudioBlockFormatMatrix, MatrixCoefficient,
JumpPosition, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural,
AudioBlockFormatHoa, AudioBlockFormatMatrix, MatrixCoefficient,
CartesianZone, PolarZone)
from .geom import (DirectSpeakerPolarPosition, DirectSpeakerCartesianPosition, BoundCoordinate,
ObjectPolarPosition, ObjectCartesianPosition, ScreenEdgeLock)
from .interaction import (AudioObjectInteraction, GainInteractionRange, PositionInteractionRange)
37 changes: 37 additions & 0 deletions ear/fileio/adm/elements/interaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from attr import attrs, attrib, Factory
from attr.validators import instance_of, optional


@attrs(slots=True)
class PositionInteractionRange(object):
minAzimuth = attrib(default=None, validator=optional(instance_of(float)))
maxAzimuth = attrib(default=None, validator=optional(instance_of(float)))
minElevation = attrib(default=None, validator=optional(instance_of(float)))
maxElevation = attrib(default=None, validator=optional(instance_of(float)))
minDistance = attrib(default=None, validator=optional(instance_of(float)))
maxDistance = attrib(default=None, validator=optional(instance_of(float)))
minX = attrib(default=None, validator=optional(instance_of(float)))
maxX = attrib(default=None, validator=optional(instance_of(float)))
minY = attrib(default=None, validator=optional(instance_of(float)))
maxY = attrib(default=None, validator=optional(instance_of(float)))
minZ = attrib(default=None, validator=optional(instance_of(float)))
maxZ = attrib(default=None, validator=optional(instance_of(float)))


@attrs(slots=True)
class GainInteractionRange(object):
min = attrib(default=None, validator=optional(instance_of(float)))
max = attrib(default=None, validator=optional(instance_of(float)))


@attrs(slots=True)
class AudioObjectInteraction(object):
onOffInteract = attrib(validator=instance_of(bool))
gainInteract = attrib(default=None, validator=optional(instance_of(bool)))
positionInteract = attrib(default=None, validator=optional(instance_of(bool)))
gainInteractionRange = attrib(default=None,
validator=optional(instance_of(GainInteractionRange)),
)
positionInteractionRange = attrib(default=None,
validator=optional(instance_of(PositionInteractionRange)),
)
4 changes: 3 additions & 1 deletion ear/fileio/adm/elements/main_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from ..exceptions import AdmError
from ....common import CartesianScreen, PolarScreen, default_screen, list_of
from .interaction import AudioObjectInteraction


def _lookup_elements(adm, idRefs):
Expand Down Expand Up @@ -83,7 +84,6 @@ def lazy_lookup_references(self, adm):
self.audioObjects = _lookup_elements(adm, self.audioObjectIDRef)
self.audioObjectIDRef = None


@attrs(slots=True)
class AudioObject(ADMElement):
audioObjectName = attrib(default=None, validator=instance_of(string_types))
Expand All @@ -97,6 +97,8 @@ class AudioObject(ADMElement):
audioTrackUIDs = attrib(default=Factory(list), repr=False)
audioObjects = attrib(default=Factory(list), repr=False)
audioComplementaryObjects = attrib(default=Factory(list), repr=False)
audioObjectInteraction = attrib(default=None,
validator=optional(instance_of(AudioObjectInteraction)))

audioPackFormatIDRef = attrib(default=None)
audioTrackUIDRef = attrib(default=None)
Expand Down
Binary file added ear/fileio/adm/test/.DS_Store
Binary file not shown.
8 changes: 8 additions & 0 deletions ear/fileio/adm/test/test_adm_files/base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
<audioObject audioObjectID="AO_1001" audioObjectName="Noise" duration="00:00:12.00000" start="00:00:00.00000">
<audioPackFormatIDRef>AP_00031001</audioPackFormatIDRef>
<audioTrackUIDRef>ATU_00000001</audioTrackUIDRef>
<audioObjectInteraction onOffInteract="1" gainInteract="1" positionInteract="1">
<gainInteractionRange bound="min">0.5</gainInteractionRange>
<gainInteractionRange bound="max">2.0</gainInteractionRange>
<positionInteractionRange coordinate="elevation" bound="min">-10.0</positionInteractionRange>
<positionInteractionRange coordinate="elevation" bound="max">+10.0</positionInteractionRange>
<positionInteractionRange coordinate="azimuth" bound="min">-30.0</positionInteractionRange>
<positionInteractionRange coordinate="azimuth" bound="max">+30.0</positionInteractionRange>
</audioObjectInteraction>
</audioObject>
<audioPackFormat audioPackFormatID="AP_00031001" audioPackFormatName="Noise" typeDefinition="Objects" typeLabel="0003">
<audioChannelFormatIDRef>AC_00031001</audioChannelFormatIDRef>
Expand Down
3 changes: 3 additions & 0 deletions ear/fileio/adm/test/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def get_acf(adm):

bf_path = "//adm:audioBlockFormat"

def test_interact(base):
assert base.adm_after_mods()

def test_gain(base):
assert base.bf_after_mods(add_children(bf_path, E.gain("0"))).gain == 0.0
Expand Down Expand Up @@ -719,6 +721,7 @@ def check_round_trip(adm):
xml = adm_to_xml(adm)
xml_str = lxml.etree.tostring(xml, pretty_print=True)
parsed_adm = parse_string(xml_str)
print(xml_str)

assert len(list(parsed_adm.elements)) == len(list(adm.elements))

Expand Down
67 changes: 64 additions & 3 deletions ear/fileio/adm/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
AudioBlockFormatObjects, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, AudioBlockFormatHoa, AudioBlockFormatMatrix,
ChannelLock, BoundCoordinate, JumpPosition, ObjectDivergence, CartesianZone, PolarZone, ScreenEdgeLock, MatrixCoefficient)
from .elements import (
AudioProgramme, AudioContent, AudioObject, AudioChannelFormat, AudioPackFormat, AudioStreamFormat, AudioTrackFormat, AudioTrackUID,
FormatDefinition, TypeDefinition, Frequency)
AudioProgramme, AudioContent, AudioObject, AudioObjectInteraction, AudioChannelFormat, AudioPackFormat, AudioStreamFormat, AudioTrackFormat, AudioTrackUID,
FormatDefinition, GainInteractionRange, PositionInteractionRange, TypeDefinition, Frequency)
from .elements.geom import (DirectSpeakerPolarPosition, DirectSpeakerCartesianPosition,
ObjectPolarPosition, ObjectCartesianPosition)
from .time_format import parse_time, unparse_time
from ...common import PolarPosition, CartesianPosition, CartesianScreen, PolarScreen


namespaces = [None,
"urn:ebu:metadata-schema:ebuCore_2014",
"urn:ebu:metadata-schema:ebuCore_2015",
Expand Down Expand Up @@ -875,6 +874,65 @@ def frequency_to_xml(parent, obj):
parent.append(element)


def handle_objectInteraction(kwargs, el):
objectInteraction = kwargs.setdefault("audioObjectInteraction",
AudioObjectInteraction(onOffInteract=BoolType.loads_func(el.attrib["onOffInteract"])))
if objectInteraction.gainInteract is not None:
objectInteraction.gainInteract = BoolType.loads_func(el.attrib["gainInteract"])
if objectInteraction.positionInteract is not None:
objectInteraction.positionInteract = BoolType.loads_func(el.attrib["positionInteract"])

if any([e.text for e in el.getiterator() if e.tag in qnames("gainInteractionRange")]):
objectInteraction.gainInteractionRange = GainInteractionRange()
if any([e.text for e in el.getiterator() if e.tag in qnames("positionInteractionRange")]):
objectInteraction.positionInteractionRange = PositionInteractionRange()
for element in el.getiterator():
if element.tag in qnames("gainInteractionRange"):
if element.attrib['bound'] == 'min':
objectInteraction.gainInteractionRange.min = float(element.text)
elif element.attrib['bound'] == 'max':
objectInteraction.gainInteractionRange.max = float(element.text)
elif element.tag in qnames("positionInteractionRange"):
attribute = str(element.attrib['bound'])+str(element.attrib['coordinate']).capitalize()
if attribute in [a for a in dir(objectInteraction.positionInteractionRange) if not a.startswith('__')]:
setattr(objectInteraction.positionInteractionRange, attribute, float(element.text))

def objectInteraction_to_xml(parent, obj):
if obj.audioObjectInteraction is not None:
if obj.audioObjectInteraction.gainInteract is not None and obj.audioObjectInteraction.positionInteract is not None:
element = parent.makeelement(QName(default_ns, "audioObjectInteraction"),
onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract),
gainInteract=BoolType.dumps(obj.audioObjectInteraction.gainInteract),
positionInteract=BoolType.dumps(obj.audioObjectInteraction.positionInteract))
elif obj.audioObjectInteraction.gainInteract is not None and obj.audioObjectInteraction.positionInteract is None:
element = parent.makeelement(QName(default_ns, "audioObjectInteraction"),
onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract),
gainInteract=BoolType.dumps(obj.audioObjectInteraction.gainInteract))
elif obj.audioObjectInteraction.gainInteract is None and obj.audioObjectInteraction.positionInteract is not None:
element = parent.makeelement(QName(default_ns, "audioObjectInteraction"),
onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract),
positionInteract=BoolType.dumps(obj.audioObjectInteraction.positionInteract))
else:
element = parent.makeelement(QName(default_ns, "audioObjectInteraction"),
onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract))

if obj.audioObjectInteraction.gainInteractionRange is not None:
for attribute in [a for a in dir(obj.audioObjectInteraction.gainInteractionRange) if not a.startswith('__')]:
if getattr(obj.audioObjectInteraction.gainInteractionRange, attribute) is not None:
element_c = element.makeelement(QName(default_ns, "gainInteractionRange"),
bound=attribute)
element_c.text = FloatType.dumps(getattr(obj.audioObjectInteraction.gainInteractionRange, attribute))
element.append(element_c)
if obj.audioObjectInteraction.positionInteractionRange is not None:
for attribute in [a for a in dir(obj.audioObjectInteraction.positionInteractionRange) if not a.startswith('__')]:
if getattr(obj.audioObjectInteraction.positionInteractionRange, attribute) is not None:
element_c = element.makeelement(QName(default_ns, "positionInteractionRange"),
coordinate=attribute[3:].lower(), bound=attribute[:3])
element_c.text = FloatType.dumps(getattr(obj.audioObjectInteraction.positionInteractionRange, attribute))
element.append(element_c)
parent.append(element)


channel_format_handler = ElementParser(AudioChannelFormat, "audioChannelFormat", [
Attribute(adm_name="audioChannelFormatID", arg_name="id", required=True),
Attribute(adm_name="audioChannelFormatName", arg_name="audioChannelFormatName", required=True),
Expand Down Expand Up @@ -1052,6 +1110,8 @@ def make_audio_programme(referenceScreen=None, **kwargs):
RefList("audioObject"),
])



object_handler = ElementParser(AudioObject, "audioObject", [
Attribute(adm_name="audioObjectID", arg_name="id", required=True),
Attribute(adm_name="audioObjectName", arg_name="audioObjectName", required=True),
Expand All @@ -1065,6 +1125,7 @@ def make_audio_programme(referenceScreen=None, **kwargs):
RefList("audioObject"),
RefList("audioComplementaryObject"),
ListElement(adm_name="audioTrackUIDRef", arg_name="audioTrackUIDRef", attr_name="audioTrackUIDs", type=TrackUIDRefType),
CustomElement("audioObjectInteraction", handle_objectInteraction, to_xml=objectInteraction_to_xml),
])

track_uid_handler = ElementParser(AudioTrackUID, "audioTrackUID", [
Expand Down